1
0
Fork 0
forked from wry/wry

Order nested scale phases by hierarchy

This commit is contained in:
atagen 2026-05-21 20:38:12 +10:00
parent a770089b88
commit 0b6da9d8e0
3 changed files with 237 additions and 25 deletions

View file

@ -85,6 +85,8 @@ pub struct MultiphaseHierarchyPosition {
pub depth: u16,
pub sibling_index: Option<u16>,
pub split_axis: Option<PhaseAxis>,
pub nearest_horizontal_split_depth: Option<u16>,
pub nearest_vertical_split_depth: Option<u16>,
pub parent_is_mono: bool,
pub mono_active: bool,
}
@ -320,6 +322,13 @@ fn plan_forward(request: &MultiphaseRequest) -> Result<MultiphasePlan, Multiphas
rejection.get_or_insert(error);
}
}
match plan_hierarchy_ordered_axis_scales(request) {
Ok(plan) => return Ok(plan),
Err(MultiphasePlanFailure::NoPattern) => {}
Err(error) => {
rejection.get_or_insert(error);
}
}
for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] {
match plan_axis_crossing_lanes(request, axis) {
Ok(plan) => return Ok(plan),
@ -383,6 +392,96 @@ fn plan_single_action_phase(
build_validated_plan(request, [(action.kind, action.axis, steps)])
}
fn plan_hierarchy_ordered_axis_scales(
request: &MultiphaseRequest,
) -> Result<MultiphasePlan, MultiphasePlanFailure> {
let mut changed_axes = vec![];
for axis in [PhaseAxis::Horizontal, PhaseAxis::Vertical] {
if request
.windows
.iter()
.any(|window| interval_changed(window.from, window.to, axis))
{
changed_axes.push(axis);
}
}
let [first_axis, second_axis] = changed_axes
.try_into()
.map_err(|_| MultiphasePlanFailure::NoPattern)?;
let axes = hierarchy_scale_axis_order(request, first_axis, second_axis)
.ok_or(MultiphasePlanFailure::NoPattern)?;
let mut current: Vec<_> = request
.windows
.iter()
.map(|window| (window.node_id, window.from))
.collect();
let mut phases = vec![];
for axis in axes {
let mut steps = vec![];
for window in &request.windows {
let (_, rect) = current
.iter_mut()
.find(|(node_id, _)| *node_id == window.node_id)
.unwrap();
let next = with_main_interval(
*rect,
axis,
main_start(window.to, axis),
main_end(window.to, axis),
);
if next == *rect {
continue;
}
if main_size(*rect, axis) == main_size(next, axis) {
return Err(MultiphasePlanFailure::NoPattern);
}
steps.push(MultiphaseStep {
node_id: window.node_id,
from: *rect,
to: next,
});
*rect = next;
}
if steps.is_empty() {
return Err(MultiphasePlanFailure::NoPattern);
}
phases.push((PhaseKind::Scale, axis, steps));
}
let [first, second] = phases
.try_into()
.map_err(|_| MultiphasePlanFailure::NoPattern)?;
build_validated_plan(request, [first, second])
}
fn hierarchy_scale_axis_order(
request: &MultiphaseRequest,
first_axis: PhaseAxis,
second_axis: PhaseAxis,
) -> Option<[PhaseAxis; 2]> {
let first_priority = hierarchy_axis_priority(request, first_axis)?;
let second_priority = hierarchy_axis_priority(request, second_axis)?;
match first_priority.cmp(&second_priority) {
std::cmp::Ordering::Less => Some([first_axis, second_axis]),
std::cmp::Ordering::Greater => Some([second_axis, first_axis]),
std::cmp::Ordering::Equal => None,
}
}
fn hierarchy_axis_priority(request: &MultiphaseRequest, axis: PhaseAxis) -> Option<u16> {
request
.windows
.iter()
.filter(|window| interval_changed(window.from, window.to, axis))
.flat_map(|window| {
[
split_depth_for_axis(window.hierarchy.source, axis),
split_depth_for_axis(window.hierarchy.target, axis),
]
})
.flatten()
.min()
}
fn plan_axis_crossing_lanes(
request: &MultiphaseRequest,
axis: PhaseAxis,
@ -847,6 +946,17 @@ fn motion_bounds(window: MultiphaseWindow) -> Rect {
window.from.union(window.to)
}
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)
}
fn split_depth_for_axis(position: MultiphaseHierarchyPosition, axis: PhaseAxis) -> Option<u16> {
match axis {
PhaseAxis::Horizontal => position.nearest_horizontal_split_depth,
PhaseAxis::Vertical => position.nearest_vertical_split_depth,
}
}
fn push_step(steps: &mut Vec<MultiphaseStep>, node_id: NodeId, from: Rect, to: Rect) {
if from != to {
steps.push(MultiphaseStep { node_id, from, to });
@ -951,18 +1061,37 @@ mod tests {
fn layout_tree(tree: &TestTree, bounds: Rect) -> Vec<TestLeaf> {
let mut leaves = vec![];
layout_tree_inner(tree, bounds, None, 0, None, None, &mut leaves);
layout_tree_inner(
tree,
bounds,
TestHierarchy {
parent: None,
depth: 0,
sibling_index: None,
split_axis: None,
nearest_horizontal_split_depth: None,
nearest_vertical_split_depth: None,
},
&mut leaves,
);
leaves.sort_by_key(|leaf| leaf.node_id.0);
leaves
}
#[derive(Copy, Clone)]
struct TestHierarchy {
parent: Option<NodeId>,
depth: u16,
sibling_index: Option<u16>,
split_axis: Option<PhaseAxis>,
nearest_horizontal_split_depth: Option<u16>,
nearest_vertical_split_depth: Option<u16>,
}
fn layout_tree_inner(
tree: &TestTree,
bounds: Rect,
parent: Option<NodeId>,
depth: u16,
sibling_index: Option<u16>,
split_axis: Option<PhaseAxis>,
hierarchy: TestHierarchy,
leaves: &mut Vec<TestLeaf>,
) {
match tree {
@ -970,10 +1099,12 @@ mod tests {
node_id: id(*raw),
rect: bounds,
hierarchy: MultiphaseHierarchyPosition {
parent,
depth,
sibling_index,
split_axis,
parent: hierarchy.parent,
depth: hierarchy.depth,
sibling_index: hierarchy.sibling_index,
split_axis: hierarchy.split_axis,
nearest_horizontal_split_depth: hierarchy.nearest_horizontal_split_depth,
nearest_vertical_split_depth: hierarchy.nearest_vertical_split_depth,
..Default::default()
},
}),
@ -986,15 +1117,24 @@ mod tests {
assert_eq!(weights.len(), children.len());
let rects = split_rect_by_weights(bounds, *axis, weights);
for (idx, (child, rect)) in children.iter().zip(rects).enumerate() {
layout_tree_inner(
child,
rect,
Some(id(*split_id)),
depth.saturating_add(1),
Some(idx.min(u16::MAX as usize) as u16),
Some(*axis),
leaves,
);
let depth = hierarchy.depth.saturating_add(1);
let mut child_hierarchy = TestHierarchy {
parent: Some(id(*split_id)),
depth,
sibling_index: Some(idx.min(u16::MAX as usize) as u16),
split_axis: Some(*axis),
nearest_horizontal_split_depth: hierarchy.nearest_horizontal_split_depth,
nearest_vertical_split_depth: hierarchy.nearest_vertical_split_depth,
};
match axis {
PhaseAxis::Horizontal => {
child_hierarchy.nearest_horizontal_split_depth = Some(depth);
}
PhaseAxis::Vertical => {
child_hierarchy.nearest_vertical_split_depth = Some(depth);
}
}
layout_tree_inner(child, rect, child_hierarchy, leaves);
}
}
}
@ -1249,6 +1389,57 @@ mod tests {
assert!(validate_plan_continuous(&vertical_req, &vertical_plan));
}
#[test]
fn generated_nested_size_redistribution_scales_parent_axis_first() {
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, 3],
vec![
leaf(1),
split(11, PhaseAxis::Vertical, &[3, 1], vec![leaf(2), leaf(3)]),
],
);
let req = generated_request(&old, &new, rect(0, 0, 400, 100));
let plan = plan_no_overlap(&req).unwrap();
assert_eq!(
actions(&plan),
vec![
PhaseAction {
kind: PhaseKind::Scale,
axis: PhaseAxis::Horizontal,
},
PhaseAction {
kind: PhaseKind::Scale,
axis: PhaseAxis::Vertical,
},
]
);
assert_eq!(step_to(&plan, 0, id(1)), rect(0, 0, 100, 100));
assert_eq!(step_to(&plan, 0, id(2)), rect(100, 0, 400, 50));
assert_eq!(step_to(&plan, 0, id(3)), rect(100, 50, 400, 100));
assert!(validate_plan_continuous(&req, &plan));
}
#[test]
fn two_axis_redistribution_without_hierarchy_still_falls_back() {
let req = request(vec![
window(1, rect(0, 0, 200, 100), rect(0, 0, 100, 100)),
window(2, rect(200, 0, 400, 50), rect(100, 0, 400, 75)),
window(3, rect(200, 50, 400, 100), rect(100, 75, 400, 100)),
]);
assert_eq!(plan_no_overlap(&req), Err(MultiphaseError::NoPlan));
}
#[test]
fn generated_stack_extractions_plan_for_both_axes_and_directions() {
let horizontal_old = split(
@ -1525,6 +1716,8 @@ mod tests {
depth: 2,
sibling_index: Some(0),
split_axis: Some(PhaseAxis::Vertical),
nearest_horizontal_split_depth: Some(1),
nearest_vertical_split_depth: Some(2),
..Default::default()
};
let target = MultiphaseHierarchyPosition {
@ -1532,12 +1725,14 @@ mod tests {
depth: 1,
sibling_index: Some(2),
split_axis: Some(PhaseAxis::Horizontal),
nearest_horizontal_split_depth: Some(1),
..Default::default()
};
assert_eq!(
MultiphaseWindowHierarchy::new(source, target).transition,
MultiphaseHierarchyTransition::Ascending
);
assert_eq!(source.nearest_vertical_split_depth, Some(2));
let entering_mono = MultiphaseWindowHierarchy::new(
source,