1
0
Fork 0
forked from wry/wry

Handle uneven swap lanes and clearance grouping

This commit is contained in:
atagen 2026-05-27 13:36:46 +10:00
parent 313323888b
commit 6c133018aa
2 changed files with 127 additions and 11 deletions

View file

@ -448,7 +448,11 @@ pub(crate) fn validate_phase_paths(
.map_err(MultiphasePlanFailure::Validation)
}
pub(crate) fn partition_motion_groups(windows: &[MultiphaseWindow]) -> Vec<Vec<usize>> {
pub(crate) fn partition_motion_groups(
windows: &[MultiphaseWindow],
clearance: i32,
) -> Vec<Vec<usize>> {
let clearance = clearance.max(0);
let mut groups = vec![];
let mut seen = vec![false; windows.len()];
for start in 0..windows.len() {
@ -460,9 +464,11 @@ pub(crate) fn partition_motion_groups(windows: &[MultiphaseWindow]) -> Vec<Vec<u
let mut pending = vec![start];
while let Some(idx) = pending.pop() {
group.push(idx);
let bounds = motion_bounds(windows[idx]);
let bounds = motion_bounds_with_clearance(windows[idx], clearance);
for other in 0..windows.len() {
if seen[other] || !bounds.intersects(&motion_bounds(windows[other])) {
if seen[other]
|| !bounds.intersects(&motion_bounds_with_clearance(windows[other], clearance))
{
continue;
}
seen[other] = true;
@ -819,8 +825,7 @@ fn plan_axis_crossing_lanes(
.max()
.ok_or(MultiphasePlanFailure::NoPattern)?;
if moving_windows.iter().any(|window| {
main_size(window.from, axis) != main_size(window.to, axis)
|| orth_start(window.from, axis) != orth_min
orth_start(window.from, axis) != orth_min
|| orth_end(window.from, axis) != orth_max
|| orth_start(window.to, axis) != orth_min
|| orth_end(window.to, axis) != orth_max
@ -859,6 +864,7 @@ fn plan_axis_crossing_lanes(
let mut phase1 = vec![];
let mut phase2 = vec![];
let mut phase3 = vec![];
let mut phase4 = vec![];
let mut lane_start = orth_min;
for (idx, window) in windows.iter().enumerate() {
let extra = if lane_remainder > 0 {
@ -875,9 +881,11 @@ fn plan_axis_crossing_lanes(
main_start(window.to, axis),
main_end(window.to, axis),
);
let lane_move = crossing_lane_move_rect(lane_from, window.to, axis);
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);
push_step(&mut phase2, window.node_id, lane_from, lane_move);
push_step(&mut phase3, window.node_id, lane_move, lane_to);
push_step(&mut phase4, window.node_id, lane_to, window.to);
if idx + 1 < windows.len() {
lane_start = lane_end + clearance;
}
@ -902,14 +910,31 @@ fn plan_axis_crossing_lanes(
),
phase_draft(
PhaseKind::Scale,
axis.other(),
axis,
phase3,
PhaseReason::SameAxisRedistribution,
),
phase_draft(
PhaseKind::Scale,
axis.other(),
phase4,
PhaseReason::GrowOutOfLanes,
),
],
)
}
fn crossing_lane_move_rect(from: Rect, target: Rect, axis: PhaseAxis) -> Rect {
let size = main_size(from, axis);
if main_start(target, axis) > main_start(from, axis) {
let end = main_end(target, axis);
with_main_interval(from, axis, end - size, end)
} else {
let start = main_start(target, axis);
with_main_interval(from, axis, start, start + size)
}
}
fn lane_index_for_direction(window: MultiphaseWindow, axis: PhaseAxis) -> Option<usize> {
let delta = main_start(window.to, axis) - main_start(window.from, axis);
match delta.cmp(&0) {
@ -1535,6 +1560,16 @@ fn motion_bounds(window: MultiphaseWindow) -> Rect {
window.from.union(window.to)
}
fn motion_bounds_with_clearance(window: MultiphaseWindow, clearance: i32) -> Rect {
let bounds = motion_bounds(window);
Rect::new_saturating(
bounds.x1().saturating_sub(clearance),
bounds.y1().saturating_sub(clearance),
bounds.x2().saturating_add(clearance),
bounds.y2().saturating_add(clearance),
)
}
fn interval_changed(from: Rect, to: Rect, axis: PhaseAxis) -> bool {
main_start(from, axis) != main_start(to, axis) || main_end(from, axis) != main_end(to, axis)
}
@ -2055,6 +2090,67 @@ mod tests {
assert!(validate_plan_continuous(&req, &planned.plan));
}
#[test]
fn uneven_swap_lanes_split_move_and_same_axis_scale() {
let req = request(vec![
window(1, rect(0, 0, 101, 100), rect(101, 0, 201, 100)),
window(2, rect(101, 0, 201, 100), rect(0, 0, 101, 100)),
]);
let planned = plan_no_overlap_explained(&req).unwrap();
let plan = &planned.plan;
assert_eq!(
planned.explanation.strategy,
PlanStrategy::SwapLanes {
axis: PhaseAxis::Horizontal,
}
);
assert_eq!(
actions(plan),
vec![
PhaseAction {
kind: PhaseKind::Scale,
axis: PhaseAxis::Vertical,
},
PhaseAction {
kind: PhaseKind::Move,
axis: PhaseAxis::Horizontal,
},
PhaseAction {
kind: PhaseKind::Scale,
axis: PhaseAxis::Horizontal,
},
PhaseAction {
kind: PhaseKind::Scale,
axis: PhaseAxis::Vertical,
},
]
);
assert_eq!(step_to(plan, 1, id(1)), rect(100, 0, 201, 50));
assert_eq!(step_to(plan, 1, id(2)), rect(0, 50, 100, 100));
assert_eq!(step_to(plan, 2, id(1)), rect(101, 0, 201, 50));
assert_eq!(step_to(plan, 2, id(2)), rect(0, 50, 101, 100));
assert!(validate_plan_continuous(&req, plan));
}
#[test]
fn uneven_swap_lanes_tolerate_stationary_sibling_in_odd_layout() {
let req = request(vec![
window(1, rect(0, 0, 101, 100), rect(101, 0, 201, 100)),
window(2, rect(101, 0, 201, 100), rect(0, 0, 101, 100)),
window(3, rect(201, 0, 300, 100), rect(201, 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![
@ -3118,7 +3214,7 @@ mod tests {
hierarchy: Default::default(),
},
];
assert_eq!(partition_motion_groups(&windows), vec![vec![0, 1], vec![2]]);
assert_eq!(partition_motion_groups(&windows, 0), vec![vec![0, 1], vec![2]]);
}
#[test]
@ -3143,6 +3239,26 @@ mod tests {
hierarchy: Default::default(),
},
];
assert_eq!(partition_motion_groups(&windows), vec![vec![0, 1, 2]]);
assert_eq!(partition_motion_groups(&windows, 0), vec![vec![0, 1, 2]]);
}
#[test]
fn motion_groups_join_across_animation_clearance() {
let windows = vec![
MultiphaseWindow {
node_id: id(1),
from: rect(0, 0, 100, 100),
to: rect(0, 0, 80, 100),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(2),
from: rect(120, 0, 220, 100),
to: rect(110, 0, 210, 100),
hierarchy: Default::default(),
},
];
assert_eq!(partition_motion_groups(&windows, 0), vec![vec![0], vec![1]]);
assert_eq!(partition_motion_groups(&windows, 10), vec![vec![0, 1]]);
}
}

View file

@ -1640,7 +1640,7 @@ impl State {
)
})
.collect();
for group in partition_motion_groups(&windows) {
for group in partition_motion_groups(&windows, self.layout_animation_clearance()) {
if self.start_multiphase_layout_animation(&candidates, &windows, &group, now) {
continue;
}