1
0
Fork 0
forked from wry/wry

Refine animation planner test fixes

This commit is contained in:
atagen 2026-05-22 16:26:03 +10:00
parent d2138b45f6
commit 1a75f47709
3 changed files with 214 additions and 77 deletions

View file

@ -6,6 +6,7 @@ const MIN_SHRINK_DENOMINATOR: i32 = 4;
pub struct MultiphaseRequest {
pub bounds: Rect,
pub windows: Vec<MultiphaseWindow>,
pub clearance: i32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
@ -488,6 +489,22 @@ fn plan_forward(
}
}
}
for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] {
match plan_space_then_orthogonal_growth(request, axis) {
Ok(plan) => return Ok(plan),
Err(error) => {
record_rejection(
&mut attempted,
direction,
PlanStrategy::SpaceThenOrthogonalGrowth { axis },
error,
);
if error != MultiphasePlanFailure::NoPattern {
rejection.get_or_insert(error);
}
}
}
}
match plan_hierarchy_ordered_axis_scales(request) {
Ok(plan) => return Ok(plan),
Err(error) => {
@ -534,22 +551,6 @@ fn plan_forward(
}
}
}
for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] {
match plan_space_then_orthogonal_growth(request, axis) {
Ok(plan) => return Ok(plan),
Err(error) => {
record_rejection(
&mut attempted,
direction,
PlanStrategy::SpaceThenOrthogonalGrowth { axis },
error,
);
if error != MultiphasePlanFailure::NoPattern {
rejection.get_or_insert(error);
}
}
}
}
Err(PlanForwardFailure {
reason: rejection.unwrap_or(MultiphasePlanFailure::NoPattern),
attempted,
@ -750,7 +751,13 @@ fn plan_axis_crossing_lanes(
request: &MultiphaseRequest,
axis: PhaseAxis,
) -> Result<MultiphasePlanned, MultiphasePlanFailure> {
if request.windows.len() != 2 {
let moving_windows: Vec<_> = request
.windows
.iter()
.copied()
.filter(|window| window.from != window.to)
.collect();
if moving_windows.len() != 2 {
return Err(MultiphasePlanFailure::NoPattern);
}
let orth_min = request
@ -765,7 +772,7 @@ fn plan_axis_crossing_lanes(
.map(|window| orth_end(window.from, axis))
.max()
.ok_or(MultiphasePlanFailure::NoPattern)?;
if request.windows.iter().any(|window| {
if moving_windows.iter().any(|window| {
main_size(window.from, axis) != main_size(window.to, axis)
|| orth_start(window.from, axis) != orth_min
|| orth_end(window.from, axis) != orth_max
@ -775,7 +782,18 @@ fn plan_axis_crossing_lanes(
}) {
return Err(MultiphasePlanFailure::NoPattern);
}
let lane_size = (orth_max - orth_min) / request.windows.len() as i32;
let clearance = request.clearance.max(0);
let lane_count = moving_windows.len() as i32;
let available = (orth_max - orth_min) - clearance.saturating_mul(lane_count - 1);
if available <= 0 {
return Err(MultiphasePlanFailure::ShrinkBound {
axis: axis.other(),
available: 0,
required: sane_min_size(orth_max - orth_min),
});
}
let lane_size = available / lane_count;
let mut lane_remainder = available % lane_count;
let required = sane_min_size(orth_max - orth_min);
if lane_size < required {
return Err(MultiphasePlanFailure::ShrinkBound {
@ -785,7 +803,7 @@ fn plan_axis_crossing_lanes(
});
}
let mut windows = request.windows.clone();
let mut windows = moving_windows;
windows.sort_by_key(|window| lane_index_for_direction(*window, axis));
if windows.windows(2).any(|pair| {
lane_index_for_direction(pair[0], axis) == lane_index_for_direction(pair[1], axis)
@ -795,13 +813,15 @@ fn plan_axis_crossing_lanes(
let mut phase1 = vec![];
let mut phase2 = vec![];
let mut phase3 = vec![];
let mut lane_start = orth_min;
for (idx, window) in windows.iter().enumerate() {
let lane_start = orth_min + lane_size * idx as i32;
let lane_end = if idx + 1 == windows.len() {
orth_max
let extra = if lane_remainder > 0 {
lane_remainder -= 1;
1
} else {
lane_start + lane_size
0
};
let lane_end = lane_start + lane_size + extra;
let lane_from = with_orth_interval(window.from, axis, lane_start, lane_end);
let lane_to = with_main_interval(
lane_from,
@ -812,6 +832,9 @@ fn plan_axis_crossing_lanes(
push_step(&mut phase1, window.node_id, window.from, lane_from);
push_step(&mut phase2, window.node_id, lane_from, lane_to);
push_step(&mut phase3, window.node_id, lane_to, window.to);
if idx + 1 < windows.len() {
lane_start = lane_end + clearance;
}
}
build_validated_plan(
request,
@ -882,6 +905,7 @@ fn plan_space_then_orthogonal_growth(
|| main_end(window.from, axis) != main_end(window.to, axis);
let orth_changes = orth_start(window.from, axis) != orth_start(window.to, axis)
|| orth_end(window.from, axis) != orth_end(window.to, axis);
let mut orth_from = window.from;
if main_changes && main_size(window.from, axis) == main_size(window.to, axis) {
let after_move = with_main_interval(
window.from,
@ -890,22 +914,50 @@ fn plan_space_then_orthogonal_growth(
main_end(window.to, axis),
);
push_step(&mut phase2, window.node_id, window.from, after_move);
if orth_changes {
push_step(&mut phase3, window.node_id, after_move, window.to);
}
orth_from = after_move;
} else if main_changes {
let after_main_scale = with_main_interval(
window.from,
axis,
main_start(window.to, axis),
main_end(window.to, axis),
);
let target_size = main_size(window.to, axis);
let after_main_scale = if main_start(window.from, axis) == main_start(window.to, axis)
|| main_end(window.from, axis) == main_end(window.to, axis)
{
with_main_interval(
window.from,
axis,
main_start(window.to, axis),
main_end(window.to, axis),
)
} else if main_start(window.to, axis) < main_start(window.from, axis) {
with_main_interval(
window.from,
axis,
main_end(window.from, axis) - target_size,
main_end(window.from, axis),
)
} else {
with_main_interval(
window.from,
axis,
main_start(window.from, axis),
main_start(window.from, axis) + target_size,
)
};
push_step(&mut phase1, window.node_id, window.from, after_main_scale);
if orth_changes {
push_step(&mut phase3, window.node_id, after_main_scale, window.to);
orth_from = after_main_scale;
if main_start(after_main_scale, axis) != main_start(window.to, axis)
|| main_end(after_main_scale, axis) != main_end(window.to, axis)
{
let after_move = with_main_interval(
after_main_scale,
axis,
main_start(window.to, axis),
main_end(window.to, axis),
);
push_step(&mut phase2, window.node_id, after_main_scale, after_move);
orth_from = after_move;
}
} else if orth_changes {
push_step(&mut phase3, window.node_id, window.from, window.to);
}
if orth_changes {
push_step(&mut phase3, window.node_id, orth_from, window.to);
}
}
if phase1.is_empty() || phase2.is_empty() || phase3.is_empty() {
@ -1372,6 +1424,7 @@ fn single_action_reason(action: PhaseAction) -> PhaseReason {
fn reverse_request(request: &MultiphaseRequest) -> MultiphaseRequest {
MultiphaseRequest {
bounds: request.bounds,
clearance: request.clearance,
windows: request
.windows
.iter()
@ -1686,7 +1739,11 @@ mod tests {
MultiphaseWindowHierarchy::new(old_leaf.hierarchy, new_leaf.hierarchy),
));
}
MultiphaseRequest { bounds, windows }
MultiphaseRequest {
bounds,
windows,
clearance: 0,
}
}
fn assert_generated_case_plans(old: &TestTree, new: &TestTree, bounds: Rect) {
@ -1739,7 +1796,11 @@ mod tests {
.map(|window| window.from.union(window.to))
.reduce(|bounds, rect| bounds.union(rect))
.unwrap_or_else(|| rect(0, 0, 1, 1));
MultiphaseRequest { bounds, windows }
MultiphaseRequest {
bounds,
windows,
clearance: 0,
}
}
fn actions(plan: &MultiphasePlan) -> Vec<PhaseAction> {
@ -1765,6 +1826,20 @@ mod tests {
strategy: PlanStrategy::SingleAction,
reason: MultiphasePlanFailure::NoPattern,
},
RejectedStrategy {
direction,
strategy: PlanStrategy::SpaceThenOrthogonalGrowth {
axis: PhaseAxis::Horizontal,
},
reason: MultiphasePlanFailure::NoPattern,
},
RejectedStrategy {
direction,
strategy: PlanStrategy::SpaceThenOrthogonalGrowth {
axis: PhaseAxis::Vertical,
},
reason: MultiphasePlanFailure::NoPattern,
},
RejectedStrategy {
direction,
strategy: PlanStrategy::HierarchyOrderedScales,
@ -1798,20 +1873,6 @@ mod tests {
},
reason: MultiphasePlanFailure::NoPattern,
},
RejectedStrategy {
direction,
strategy: PlanStrategy::SpaceThenOrthogonalGrowth {
axis: PhaseAxis::Horizontal,
},
reason: MultiphasePlanFailure::NoPattern,
},
RejectedStrategy {
direction,
strategy: PlanStrategy::SpaceThenOrthogonalGrowth {
axis: PhaseAxis::Vertical,
},
reason: MultiphasePlanFailure::NoPattern,
},
]
}
@ -1916,6 +1977,38 @@ mod tests {
assert!(validate_plan_continuous(&req, &plan));
}
#[test]
fn swap_lanes_respect_requested_clearance() {
let mut req = request(vec![
window(1, rect(0, 0, 100, 100), rect(100, 0, 200, 100)),
window(2, rect(100, 0, 200, 100), rect(0, 0, 100, 100)),
]);
req.clearance = 10;
let plan = plan_no_overlap(&req).unwrap();
assert_eq!(step_to(&plan, 0, id(1)), rect(0, 0, 100, 45));
assert_eq!(step_to(&plan, 0, id(2)), rect(100, 55, 200, 100));
assert!(validate_plan_continuous(&req, &plan));
}
#[test]
fn swap_lanes_tolerate_stationary_siblings_in_request() {
let req = request(vec![
window(1, rect(0, 0, 100, 100), rect(100, 0, 200, 100)),
window(2, rect(100, 0, 200, 100), rect(0, 0, 100, 100)),
window(3, rect(200, 0, 300, 100), rect(200, 0, 300, 100)),
]);
let planned = plan_no_overlap_explained(&req).unwrap();
assert_eq!(
planned.explanation.strategy,
PlanStrategy::SwapLanes {
axis: PhaseAxis::Horizontal,
}
);
assert!(validate_plan_continuous(&req, &planned.plan));
}
#[test]
fn vertical_swap_lanes_follow_motion_direction_not_node_id() {
let req = request(vec![
@ -2232,6 +2325,59 @@ mod tests {
assert_generated_case_plans(&vertical_new, &vertical_old, rect(0, 0, 100, 400));
}
#[test]
fn stack_extraction_with_resized_moving_child_still_moves_before_growth() {
let old = split(
10,
PhaseAxis::Horizontal,
&[1, 1],
vec![
leaf(1),
split(11, PhaseAxis::Vertical, &[1, 1], vec![leaf(2), leaf(3)]),
],
);
let new = split(
10,
PhaseAxis::Horizontal,
&[1, 1, 1],
vec![leaf(1), leaf(2), leaf(3)],
);
let req = generated_request(&old, &new, rect(0, 0, 300, 120));
let planned = plan_no_overlap_explained(&req).unwrap();
let plan = &planned.plan;
assert_eq!(
planned.explanation.strategy,
PlanStrategy::SpaceThenOrthogonalGrowth {
axis: PhaseAxis::Horizontal,
}
);
assert_eq!(
actions(plan),
vec![
PhaseAction {
kind: PhaseKind::Scale,
axis: PhaseAxis::Horizontal,
},
PhaseAction {
kind: PhaseKind::Move,
axis: PhaseAxis::Horizontal,
},
PhaseAction {
kind: PhaseKind::Scale,
axis: PhaseAxis::Vertical,
},
]
);
assert_eq!(step_to(plan, 0, id(1)), rect(0, 0, 100, 120));
assert_eq!(step_to(plan, 0, id(2)), rect(200, 0, 300, 60));
assert_eq!(step_to(plan, 0, id(3)), rect(200, 60, 300, 120));
assert_eq!(step_to(plan, 1, id(2)), rect(100, 0, 200, 60));
assert_eq!(step_to(plan, 2, id(2)), rect(100, 0, 200, 120));
assert_eq!(step_to(plan, 2, id(3)), rect(200, 0, 300, 120));
assert!(validate_plan_continuous(&req, plan));
}
#[test]
fn bounded_generated_supported_split_tree_corpus_is_deterministic() {
let mut cases = vec![];
@ -2543,6 +2689,7 @@ mod tests {
fn diagnostics_report_shrink_bound_rejections() {
let req = MultiphaseRequest {
bounds: rect(0, 0, 400, 100),
clearance: 0,
windows: vec![
MultiphaseWindow {
node_id: id(1),