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

@ -229,6 +229,10 @@ Current pure planner status:
- Live layout batches are partitioned by overlapping motion bounds, so unrelated - Live layout batches are partitioned by overlapping motion bounds, so unrelated
groups can still use multiphase animation when another group falls back to groups can still use multiphase animation when another group falls back to
linear motion. linear motion.
- Planner requests now carry per-window hierarchy metadata for source/target
parent, depth, sibling index, split axis, mono state, and transition kind.
The current planner records this information but does not yet use it to order
nested-container phases.
Tests: Tests:

View file

@ -13,6 +13,104 @@ pub struct MultiphaseWindow {
pub node_id: NodeId, pub node_id: NodeId,
pub from: Rect, pub from: Rect,
pub to: 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)] #[derive(Clone, Debug, Eq, PartialEq)]
@ -519,6 +617,7 @@ fn reverse_request(request: &MultiphaseRequest) -> MultiphaseRequest {
node_id: window.node_id, node_id: window.node_id,
from: window.to, from: window.to,
to: window.from, to: window.from,
hierarchy: window.hierarchy.reversed(),
}) })
.collect(), .collect(),
} }
@ -628,6 +727,10 @@ mod tests {
Rect::new_saturating(x1, y1, x2, y2) 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 { fn request(windows: Vec<MultiphaseWindow>) -> MultiphaseRequest {
let bounds = windows let bounds = windows
.iter() .iter()
@ -653,15 +756,12 @@ mod tests {
#[test] #[test]
fn horizontal_swap_shrinks_moves_then_grows_without_overlap() { fn horizontal_swap_shrinks_moves_then_grows_without_overlap() {
let req = request(vec![ let req = request(vec![
MultiphaseWindow { window(1, rect(0, 0, 100, 100), rect(100, 0, 200, 100)),
node_id: id(1),
from: rect(0, 0, 100, 100),
to: rect(100, 0, 200, 100),
},
MultiphaseWindow { MultiphaseWindow {
node_id: id(2), node_id: id(2),
from: rect(100, 0, 200, 100), from: rect(100, 0, 200, 100),
to: rect(0, 0, 100, 100), to: rect(0, 0, 100, 100),
hierarchy: Default::default(),
}, },
]); ]);
let plan = plan_no_overlap(&req).unwrap(); let plan = plan_no_overlap(&req).unwrap();
@ -694,11 +794,13 @@ mod tests {
node_id: id(1), node_id: id(1),
from: rect(100, 0, 200, 100), from: rect(100, 0, 200, 100),
to: rect(0, 0, 100, 100), to: rect(0, 0, 100, 100),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(2), node_id: id(2),
from: rect(0, 0, 100, 100), from: rect(0, 0, 100, 100),
to: rect(100, 0, 200, 100), to: rect(100, 0, 200, 100),
hierarchy: Default::default(),
}, },
]); ]);
let plan = plan_no_overlap(&req).unwrap(); let plan = plan_no_overlap(&req).unwrap();
@ -714,11 +816,13 @@ mod tests {
node_id: id(1), node_id: id(1),
from: rect(100, 0, 200, 100), from: rect(100, 0, 200, 100),
to: rect(0, 0, 100, 100), to: rect(0, 0, 100, 100),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(2), node_id: id(2),
from: rect(0, 0, 100, 100), from: rect(0, 0, 100, 100),
to: rect(100, 0, 200, 100), to: rect(100, 0, 200, 100),
hierarchy: Default::default(),
}, },
]); ]);
let plan = plan_no_overlap(&req).unwrap(); let plan = plan_no_overlap(&req).unwrap();
@ -734,11 +838,13 @@ mod tests {
node_id: id(1), node_id: id(1),
from: rect(0, 100, 100, 200), from: rect(0, 100, 100, 200),
to: rect(0, 0, 100, 100), to: rect(0, 0, 100, 100),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(2), node_id: id(2),
from: rect(0, 0, 100, 100), from: rect(0, 0, 100, 100),
to: rect(0, 100, 100, 200), to: rect(0, 100, 100, 200),
hierarchy: Default::default(),
}, },
]); ]);
let plan = plan_no_overlap(&req).unwrap(); let plan = plan_no_overlap(&req).unwrap();
@ -754,16 +860,19 @@ mod tests {
node_id: id(1), node_id: id(1),
from: rect(0, 0, 200, 100), from: rect(0, 0, 200, 100),
to: rect(0, 0, 100, 100), to: rect(0, 0, 100, 100),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(2), node_id: id(2),
from: rect(200, 0, 400, 50), from: rect(200, 0, 400, 50),
to: rect(100, 0, 300, 100), to: rect(100, 0, 300, 100),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(3), node_id: id(3),
from: rect(200, 50, 400, 100), from: rect(200, 50, 400, 100),
to: rect(300, 0, 400, 100), to: rect(300, 0, 400, 100),
hierarchy: Default::default(),
}, },
]); ]);
let plan = plan_no_overlap(&req).unwrap(); let plan = plan_no_overlap(&req).unwrap();
@ -799,16 +908,19 @@ mod tests {
node_id: id(1), node_id: id(1),
from: rect(0, 0, 100, 100), from: rect(0, 0, 100, 100),
to: rect(0, 0, 200, 100), to: rect(0, 0, 200, 100),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(2), node_id: id(2),
from: rect(100, 0, 300, 100), from: rect(100, 0, 300, 100),
to: rect(200, 0, 400, 50), to: rect(200, 0, 400, 50),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(3), node_id: id(3),
from: rect(300, 0, 400, 100), from: rect(300, 0, 400, 100),
to: rect(200, 50, 400, 100), to: rect(200, 50, 400, 100),
hierarchy: Default::default(),
}, },
]); ]);
let plan = plan_no_overlap(&req).unwrap(); let plan = plan_no_overlap(&req).unwrap();
@ -839,16 +951,19 @@ mod tests {
node_id: id(1), node_id: id(1),
from: rect(0, 0, 100, 200), from: rect(0, 0, 100, 200),
to: rect(0, 0, 100, 100), to: rect(0, 0, 100, 100),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(2), node_id: id(2),
from: rect(0, 200, 50, 400), from: rect(0, 200, 50, 400),
to: rect(0, 100, 100, 300), to: rect(0, 100, 100, 300),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(3), node_id: id(3),
from: rect(50, 200, 100, 400), from: rect(50, 200, 100, 400),
to: rect(0, 300, 100, 400), to: rect(0, 300, 100, 400),
hierarchy: Default::default(),
}, },
]); ]);
let plan = plan_no_overlap(&req).unwrap(); let plan = plan_no_overlap(&req).unwrap();
@ -884,16 +999,19 @@ mod tests {
node_id: id(1), node_id: id(1),
from: rect(0, 0, 100, 100), from: rect(0, 0, 100, 100),
to: rect(0, 0, 100, 200), to: rect(0, 0, 100, 200),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(2), node_id: id(2),
from: rect(0, 100, 100, 300), from: rect(0, 100, 100, 300),
to: rect(0, 200, 50, 400), to: rect(0, 200, 50, 400),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(3), node_id: id(3),
from: rect(0, 300, 100, 400), from: rect(0, 300, 100, 400),
to: rect(50, 200, 100, 400), to: rect(50, 200, 100, 400),
hierarchy: Default::default(),
}, },
]); ]);
let plan = plan_no_overlap(&req).unwrap(); let plan = plan_no_overlap(&req).unwrap();
@ -923,10 +1041,50 @@ mod tests {
node_id: id(1), node_id: id(1),
from: rect(0, 0, 100, 100), from: rect(0, 0, 100, 100),
to: rect(100, 100, 200, 200), to: rect(100, 100, 200, 200),
hierarchy: Default::default(),
}]); }]);
assert_eq!(plan_no_overlap(&req), Err(MultiphaseError::NoPlan)); 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] #[test]
fn continuous_validation_rejects_narrow_mid_phase_overlap() { fn continuous_validation_rejects_narrow_mid_phase_overlap() {
let req = request(vec![ let req = request(vec![
@ -934,11 +1092,13 @@ mod tests {
node_id: id(1), node_id: id(1),
from: rect(0, 0, 10, 10), from: rect(0, 0, 10, 10),
to: rect(100, 0, 110, 10), to: rect(100, 0, 110, 10),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(2), node_id: id(2),
from: rect(13, 0, 14, 10), from: rect(13, 0, 14, 10),
to: rect(13, 0, 14, 10), to: rect(13, 0, 14, 10),
hierarchy: Default::default(),
}, },
]); ]);
let plan = MultiphasePlan { let plan = MultiphasePlan {
@ -965,11 +1125,13 @@ mod tests {
node_id: id(1), node_id: id(1),
from: rect(0, 0, 10, 10), from: rect(0, 0, 10, 10),
to: rect(10, 0, 20, 10), to: rect(10, 0, 20, 10),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(2), node_id: id(2),
from: rect(20, 0, 30, 10), from: rect(20, 0, 30, 10),
to: rect(20, 0, 30, 10), to: rect(20, 0, 30, 10),
hierarchy: Default::default(),
}, },
]); ]);
let plan = MultiphasePlan { let plan = MultiphasePlan {
@ -995,6 +1157,7 @@ mod tests {
node_id: id(1), node_id: id(1),
from: rect(0, 0, 10, 10), from: rect(0, 0, 10, 10),
to: rect(20, 0, 30, 10), to: rect(20, 0, 30, 10),
hierarchy: Default::default(),
}]); }]);
let plan = MultiphasePlan { let plan = MultiphasePlan {
phases: vec![MultiphasePhase { phases: vec![MultiphasePhase {
@ -1020,16 +1183,19 @@ mod tests {
node_id: id(1), node_id: id(1),
from: rect(0, 0, 100, 100), from: rect(0, 0, 100, 100),
to: rect(100, 0, 200, 100), to: rect(100, 0, 200, 100),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(2), node_id: id(2),
from: rect(100, 0, 200, 100), from: rect(100, 0, 200, 100),
to: rect(0, 0, 100, 100), to: rect(0, 0, 100, 100),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(3), node_id: id(3),
from: rect(300, 0, 400, 100), from: rect(300, 0, 400, 100),
to: rect(400, 0, 500, 100), to: rect(400, 0, 500, 100),
hierarchy: Default::default(),
}, },
]; ];
assert_eq!(partition_motion_groups(&windows), vec![vec![0, 1], vec![2]]); assert_eq!(partition_motion_groups(&windows), vec![vec![0, 1], vec![2]]);
@ -1042,16 +1208,19 @@ mod tests {
node_id: id(1), node_id: id(1),
from: rect(0, 0, 100, 100), from: rect(0, 0, 100, 100),
to: rect(80, 0, 180, 100), to: rect(80, 0, 180, 100),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(2), node_id: id(2),
from: rect(170, 0, 270, 100), from: rect(170, 0, 270, 100),
to: rect(250, 0, 350, 100), to: rect(250, 0, 350, 100),
hierarchy: Default::default(),
}, },
MultiphaseWindow { MultiphaseWindow {
node_id: id(3), node_id: id(3),
from: rect(90, 0, 180, 100), from: rect(90, 0, 180, 100),
to: rect(180, 0, 260, 100), to: rect(180, 0, 260, 100),
hierarchy: Default::default(),
}, },
]; ];
assert_eq!(partition_motion_groups(&windows), vec![vec![0, 1, 2]]); assert_eq!(partition_motion_groups(&windows), vec![vec![0, 1, 2]]);

View file

@ -6,7 +6,8 @@ use {
AnimationCurve, AnimationState, AnimationTick, RetainedExitLayer, RetainedToplevel, AnimationCurve, AnimationState, AnimationTick, RetainedExitLayer, RetainedToplevel,
expand_damage_rect, expand_damage_rect,
multiphase::{ multiphase::{
MultiphaseRequest, MultiphaseWindow, partition_motion_groups, plan_no_overlap, MultiphaseRequest, MultiphaseWindow, MultiphaseWindowHierarchy,
partition_motion_groups, plan_no_overlap,
}, },
spawn_in_start_rect, spawn_in_start_rect,
}, },
@ -168,6 +169,7 @@ pub(crate) struct LayoutAnimationCandidate {
new: Rect, new: Rect,
retained: Option<Rc<RetainedToplevel>>, retained: Option<Rc<RetainedToplevel>>,
curve: AnimationCurve, curve: AnimationCurve,
hierarchy: MultiphaseWindowHierarchy,
} }
pub struct State { pub struct State {
@ -1500,7 +1502,29 @@ impl State {
.layout_animation_curve_override .layout_animation_curve_override
.get() .get()
.unwrap_or_else(|| self.animations.curve.get()); .unwrap_or_else(|| self.animations.curve.get());
self.queue_layout_animation(node_id, old, new, retained, curve); self.queue_layout_animation(
node_id,
old,
new,
retained,
curve,
MultiphaseWindowHierarchy::default(),
);
}
pub fn queue_tiled_animation_with_hierarchy(
self: &Rc<Self>,
node_id: NodeId,
old: Rect,
new: Rect,
retained: Option<Rc<RetainedToplevel>>,
hierarchy: MultiphaseWindowHierarchy,
) {
let curve = self
.layout_animation_curve_override
.get()
.unwrap_or_else(|| self.animations.curve.get());
self.queue_layout_animation(node_id, old, new, retained, curve, hierarchy);
} }
pub fn queue_linear_layout_animation( pub fn queue_linear_layout_animation(
@ -1510,7 +1534,14 @@ impl State {
new: Rect, new: Rect,
retained: Option<Rc<RetainedToplevel>>, retained: Option<Rc<RetainedToplevel>>,
) { ) {
self.queue_layout_animation(node_id, old, new, retained, AnimationCurve::Linear); self.queue_layout_animation(
node_id,
old,
new,
retained,
AnimationCurve::Linear,
MultiphaseWindowHierarchy::default(),
);
} }
fn queue_layout_animation( fn queue_layout_animation(
@ -1520,6 +1551,7 @@ impl State {
new: Rect, new: Rect,
retained: Option<Rc<RetainedToplevel>>, retained: Option<Rc<RetainedToplevel>>,
curve: AnimationCurve, curve: AnimationCurve,
hierarchy: MultiphaseWindowHierarchy,
) { ) {
if !self.animations.enabled.get() if !self.animations.enabled.get()
|| !self.layout_animations_active.get() || !self.layout_animations_active.get()
@ -1546,6 +1578,7 @@ impl State {
new, new,
retained, retained,
curve, curve,
hierarchy,
}; };
if let Some(batch) = self.layout_animation_batch.borrow_mut().as_mut() { if let Some(batch) = self.layout_animation_batch.borrow_mut().as_mut() {
batch.push(candidate); batch.push(candidate);
@ -1590,12 +1623,14 @@ impl State {
let now = self.now_nsec(); let now = self.now_nsec();
let windows: Vec<_> = candidates let windows: Vec<_> = candidates
.iter() .iter()
.map(|candidate| MultiphaseWindow { .map(|candidate| {
node_id: candidate.node_id, MultiphaseWindow::with_hierarchy(
from: self candidate.node_id,
.animations self.animations
.visual_rect(candidate.node_id, candidate.old, now), .visual_rect(candidate.node_id, candidate.old, now),
to: candidate.new, candidate.new,
candidate.hierarchy,
)
}) })
.collect(); .collect();
for group in partition_motion_groups(&windows) { for group in partition_motion_groups(&windows) {

View file

@ -1,6 +1,9 @@
use { use {
crate::{ crate::{
animation::{RetainedExitLayer, RetainedToplevel}, animation::{
RetainedExitLayer, RetainedToplevel,
multiphase::{MultiphaseHierarchyPosition, MultiphaseWindowHierarchy, PhaseAxis},
},
client::{Client, ClientId}, client::{Client, ClientId},
criteria::{ criteria::{
CritDestroyListener, CritMatcherId, CritDestroyListener, CritMatcherId,
@ -186,6 +189,11 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
fn tl_change_extents(self: Rc<Self>, rect: &Rect) { fn tl_change_extents(self: Rc<Self>, rect: &Rect) {
let data = self.tl_data(); let data = self.tl_data();
let prev = data.desired_extents.replace(*rect); let prev = data.desired_extents.replace(*rect);
let target_hierarchy = self.tl_multiphase_hierarchy_position();
let hierarchy = MultiphaseWindowHierarchy::new(
data.layout_animation_position.replace(target_hierarchy),
target_hierarchy,
);
let spawn_in_pending = data.spawn_in_pending.get(); let spawn_in_pending = data.spawn_in_pending.get();
let parent_is_mono = data let parent_is_mono = data
.parent .parent
@ -200,11 +208,12 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
&& !self.node_is_container() && !self.node_is_container()
&& !parent_is_mono && !parent_is_mono
{ {
data.state.clone().queue_tiled_animation( data.state.clone().queue_tiled_animation_with_hierarchy(
data.node_id, data.node_id,
prev, prev,
*rect, *rect,
self.tl_animation_snapshot(), self.tl_animation_snapshot(),
hierarchy,
); );
} }
if spawn_in_pending if spawn_in_pending
@ -314,6 +323,35 @@ pub trait ToplevelNodeBase: Node {
true true
} }
fn tl_multiphase_hierarchy_position(&self) -> MultiphaseHierarchyPosition {
let data = self.tl_data();
let Some(parent) = data.parent.get() else {
return Default::default();
};
let mut position = MultiphaseHierarchyPosition {
parent: Some(parent.node_id()),
depth: multiphase_parent_depth(Some(parent.clone())),
..Default::default()
};
if let Some(container) = parent.node_into_container() {
position.split_axis = Some(match container.split.get() {
ContainerSplit::Horizontal => PhaseAxis::Horizontal,
ContainerSplit::Vertical => PhaseAxis::Vertical,
});
if let Some(mono) = container.mono_child.get() {
position.parent_is_mono = true;
position.mono_active = mono.node.node_id() == data.node_id;
}
for (idx, child) in container.children.iter().enumerate() {
if child.node.node_id() == data.node_id {
position.sibling_index = Some(idx.min(u16::MAX as usize) as u16);
break;
}
}
}
position
}
fn tl_set_active(&self, active: bool) { fn tl_set_active(&self, active: bool) {
let _ = active; let _ = active;
} }
@ -383,6 +421,18 @@ pub trait ToplevelNodeBase: Node {
} }
} }
fn multiphase_parent_depth(mut parent: Option<Rc<dyn ContainingNode>>) -> u16 {
let mut depth = 0u16;
while let Some(node) = parent {
let Some(toplevel) = node.node_into_toplevel() else {
break;
};
depth = depth.saturating_add(1);
parent = toplevel.tl_data().parent.get();
}
depth
}
pub struct FullscreenedData { pub struct FullscreenedData {
pub placeholder: Rc<PlaceholderNode>, pub placeholder: Rc<PlaceholderNode>,
pub workspace: Rc<WorkspaceNode>, pub workspace: Rc<WorkspaceNode>,
@ -453,6 +503,7 @@ pub struct ToplevelData {
pub spawn_in_pending: Cell<bool>, pub spawn_in_pending: Cell<bool>,
pub pos: Cell<Rect>, pub pos: Cell<Rect>,
pub desired_extents: Cell<Rect>, pub desired_extents: Cell<Rect>,
pub layout_animation_position: Cell<MultiphaseHierarchyPosition>,
pub seat_state: NodeSeatState, pub seat_state: NodeSeatState,
pub wants_attention: Cell<bool>, pub wants_attention: Cell<bool>,
pub requested_attention: Cell<bool>, pub requested_attention: Cell<bool>,
@ -517,6 +568,7 @@ impl ToplevelData {
spawn_in_pending: Cell::new(false), spawn_in_pending: Cell::new(false),
pos: Default::default(), pos: Default::default(),
desired_extents: Default::default(), desired_extents: Default::default(),
layout_animation_position: Default::default(),
seat_state: Default::default(), seat_state: Default::default(),
wants_attention: Cell::new(false), wants_attention: Cell::new(false),
requested_attention: Cell::new(false), requested_attention: Cell::new(false),