1
0
Fork 0
forked from wry/wry

Carry hierarchy metadata into multiphase planning

This commit is contained in:
atagen 2026-05-21 19:55:16 +10:00
parent a712786ecf
commit 90c00bcdf3
4 changed files with 276 additions and 16 deletions

View file

@ -13,6 +13,104 @@ pub struct MultiphaseWindow {
pub node_id: NodeId,
pub from: Rect,
pub to: Rect,
pub hierarchy: MultiphaseWindowHierarchy,
}
impl MultiphaseWindow {
pub fn new(node_id: NodeId, from: Rect, to: Rect) -> Self {
Self {
node_id,
from,
to,
hierarchy: Default::default(),
}
}
pub fn with_hierarchy(
node_id: NodeId,
from: Rect,
to: Rect,
hierarchy: MultiphaseWindowHierarchy,
) -> Self {
Self {
node_id,
from,
to,
hierarchy,
}
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct MultiphaseWindowHierarchy {
pub source: MultiphaseHierarchyPosition,
pub target: MultiphaseHierarchyPosition,
pub transition: MultiphaseHierarchyTransition,
}
impl MultiphaseWindowHierarchy {
pub fn new(source: MultiphaseHierarchyPosition, target: MultiphaseHierarchyPosition) -> Self {
let transition = if !source.parent_is_mono && target.parent_is_mono {
MultiphaseHierarchyTransition::EnteringMono
} else if source.parent_is_mono && !target.parent_is_mono {
MultiphaseHierarchyTransition::ExitingMono
} else if source.parent.is_none() || target.parent.is_none() {
MultiphaseHierarchyTransition::Unknown
} else if target.depth < source.depth {
MultiphaseHierarchyTransition::Ascending
} else if target.depth > source.depth {
MultiphaseHierarchyTransition::Descending
} else {
MultiphaseHierarchyTransition::SameLevel
};
Self {
source,
target,
transition,
}
}
fn reversed(self) -> Self {
Self {
source: self.target,
target: self.source,
transition: self.transition.reversed(),
}
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct MultiphaseHierarchyPosition {
pub parent: Option<NodeId>,
pub depth: u16,
pub sibling_index: Option<u16>,
pub split_axis: Option<PhaseAxis>,
pub parent_is_mono: bool,
pub mono_active: bool,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub enum MultiphaseHierarchyTransition {
#[default]
Unknown,
SameLevel,
Ascending,
Descending,
EnteringMono,
ExitingMono,
}
impl MultiphaseHierarchyTransition {
fn reversed(self) -> Self {
match self {
Self::Unknown => Self::Unknown,
Self::SameLevel => Self::SameLevel,
Self::Ascending => Self::Descending,
Self::Descending => Self::Ascending,
Self::EnteringMono => Self::ExitingMono,
Self::ExitingMono => Self::EnteringMono,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
@ -519,6 +617,7 @@ fn reverse_request(request: &MultiphaseRequest) -> MultiphaseRequest {
node_id: window.node_id,
from: window.to,
to: window.from,
hierarchy: window.hierarchy.reversed(),
})
.collect(),
}
@ -628,6 +727,10 @@ mod tests {
Rect::new_saturating(x1, y1, x2, y2)
}
fn window(raw: u32, from: Rect, to: Rect) -> MultiphaseWindow {
MultiphaseWindow::new(id(raw), from, to)
}
fn request(windows: Vec<MultiphaseWindow>) -> MultiphaseRequest {
let bounds = windows
.iter()
@ -653,15 +756,12 @@ mod tests {
#[test]
fn horizontal_swap_shrinks_moves_then_grows_without_overlap() {
let req = request(vec![
MultiphaseWindow {
node_id: id(1),
from: rect(0, 0, 100, 100),
to: rect(100, 0, 200, 100),
},
window(1, rect(0, 0, 100, 100), rect(100, 0, 200, 100)),
MultiphaseWindow {
node_id: id(2),
from: rect(100, 0, 200, 100),
to: rect(0, 0, 100, 100),
hierarchy: Default::default(),
},
]);
let plan = plan_no_overlap(&req).unwrap();
@ -694,11 +794,13 @@ mod tests {
node_id: id(1),
from: rect(100, 0, 200, 100),
to: rect(0, 0, 100, 100),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(2),
from: rect(0, 0, 100, 100),
to: rect(100, 0, 200, 100),
hierarchy: Default::default(),
},
]);
let plan = plan_no_overlap(&req).unwrap();
@ -714,11 +816,13 @@ mod tests {
node_id: id(1),
from: rect(100, 0, 200, 100),
to: rect(0, 0, 100, 100),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(2),
from: rect(0, 0, 100, 100),
to: rect(100, 0, 200, 100),
hierarchy: Default::default(),
},
]);
let plan = plan_no_overlap(&req).unwrap();
@ -734,11 +838,13 @@ mod tests {
node_id: id(1),
from: rect(0, 100, 100, 200),
to: rect(0, 0, 100, 100),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(2),
from: rect(0, 0, 100, 100),
to: rect(0, 100, 100, 200),
hierarchy: Default::default(),
},
]);
let plan = plan_no_overlap(&req).unwrap();
@ -754,16 +860,19 @@ mod tests {
node_id: id(1),
from: rect(0, 0, 200, 100),
to: rect(0, 0, 100, 100),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(2),
from: rect(200, 0, 400, 50),
to: rect(100, 0, 300, 100),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(3),
from: rect(200, 50, 400, 100),
to: rect(300, 0, 400, 100),
hierarchy: Default::default(),
},
]);
let plan = plan_no_overlap(&req).unwrap();
@ -799,16 +908,19 @@ mod tests {
node_id: id(1),
from: rect(0, 0, 100, 100),
to: rect(0, 0, 200, 100),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(2),
from: rect(100, 0, 300, 100),
to: rect(200, 0, 400, 50),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(3),
from: rect(300, 0, 400, 100),
to: rect(200, 50, 400, 100),
hierarchy: Default::default(),
},
]);
let plan = plan_no_overlap(&req).unwrap();
@ -839,16 +951,19 @@ mod tests {
node_id: id(1),
from: rect(0, 0, 100, 200),
to: rect(0, 0, 100, 100),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(2),
from: rect(0, 200, 50, 400),
to: rect(0, 100, 100, 300),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(3),
from: rect(50, 200, 100, 400),
to: rect(0, 300, 100, 400),
hierarchy: Default::default(),
},
]);
let plan = plan_no_overlap(&req).unwrap();
@ -884,16 +999,19 @@ mod tests {
node_id: id(1),
from: rect(0, 0, 100, 100),
to: rect(0, 0, 100, 200),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(2),
from: rect(0, 100, 100, 300),
to: rect(0, 200, 50, 400),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(3),
from: rect(0, 300, 100, 400),
to: rect(50, 200, 100, 400),
hierarchy: Default::default(),
},
]);
let plan = plan_no_overlap(&req).unwrap();
@ -923,10 +1041,50 @@ mod tests {
node_id: id(1),
from: rect(0, 0, 100, 100),
to: rect(100, 100, 200, 200),
hierarchy: Default::default(),
}]);
assert_eq!(plan_no_overlap(&req), Err(MultiphaseError::NoPlan));
}
#[test]
fn hierarchy_metadata_classifies_depth_and_mono_transitions() {
let source = MultiphaseHierarchyPosition {
parent: Some(id(10)),
depth: 2,
sibling_index: Some(0),
split_axis: Some(PhaseAxis::Vertical),
..Default::default()
};
let target = MultiphaseHierarchyPosition {
parent: Some(id(11)),
depth: 1,
sibling_index: Some(2),
split_axis: Some(PhaseAxis::Horizontal),
..Default::default()
};
assert_eq!(
MultiphaseWindowHierarchy::new(source, target).transition,
MultiphaseHierarchyTransition::Ascending
);
let entering_mono = MultiphaseWindowHierarchy::new(
source,
MultiphaseHierarchyPosition {
parent_is_mono: true,
mono_active: true,
..target
},
);
assert_eq!(
entering_mono.transition,
MultiphaseHierarchyTransition::EnteringMono
);
assert_eq!(
entering_mono.reversed().transition,
MultiphaseHierarchyTransition::ExitingMono
);
}
#[test]
fn continuous_validation_rejects_narrow_mid_phase_overlap() {
let req = request(vec![
@ -934,11 +1092,13 @@ mod tests {
node_id: id(1),
from: rect(0, 0, 10, 10),
to: rect(100, 0, 110, 10),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(2),
from: rect(13, 0, 14, 10),
to: rect(13, 0, 14, 10),
hierarchy: Default::default(),
},
]);
let plan = MultiphasePlan {
@ -965,11 +1125,13 @@ mod tests {
node_id: id(1),
from: rect(0, 0, 10, 10),
to: rect(10, 0, 20, 10),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(2),
from: rect(20, 0, 30, 10),
to: rect(20, 0, 30, 10),
hierarchy: Default::default(),
},
]);
let plan = MultiphasePlan {
@ -995,6 +1157,7 @@ mod tests {
node_id: id(1),
from: rect(0, 0, 10, 10),
to: rect(20, 0, 30, 10),
hierarchy: Default::default(),
}]);
let plan = MultiphasePlan {
phases: vec![MultiphasePhase {
@ -1020,16 +1183,19 @@ mod tests {
node_id: id(1),
from: rect(0, 0, 100, 100),
to: rect(100, 0, 200, 100),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(2),
from: rect(100, 0, 200, 100),
to: rect(0, 0, 100, 100),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(3),
from: rect(300, 0, 400, 100),
to: rect(400, 0, 500, 100),
hierarchy: Default::default(),
},
];
assert_eq!(partition_motion_groups(&windows), vec![vec![0, 1], vec![2]]);
@ -1042,16 +1208,19 @@ mod tests {
node_id: id(1),
from: rect(0, 0, 100, 100),
to: rect(80, 0, 180, 100),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(2),
from: rect(170, 0, 270, 100),
to: rect(250, 0, 350, 100),
hierarchy: Default::default(),
},
MultiphaseWindow {
node_id: id(3),
from: rect(90, 0, 180, 100),
to: rect(180, 0, 260, 100),
hierarchy: Default::default(),
},
];
assert_eq!(partition_motion_groups(&windows), vec![vec![0, 1, 2]]);