Skip to main content

cranpose_ui/widgets/nodes/
layout_node.rs

1use std::{
2    any::TypeId,
3    cell::{Cell, RefCell},
4    collections::HashMap,
5    hash::{Hash, Hasher},
6    rc::Rc,
7};
8
9use cranpose_core::{Node, NodeId};
10use cranpose_foundation::{
11    InvalidationKind, ModifierInvalidation, ModifierInvalidations, NodeCapabilities,
12    SemanticsConfiguration,
13};
14use cranpose_ui_layout::{Constraints, MeasurePolicy};
15
16#[cfg(test)]
17use crate::layout::LayoutRuntimeDebugStats;
18use crate::{
19    layout::{LayoutRuntimeState, MeasuredNode},
20    modifier::{
21        Modifier, ModifierChainHandle, ModifierLocalSource, ModifierLocalToken,
22        ModifierLocalsHandle, ModifierNodeSlices, Point, ResolvedModifierLocal, ResolvedModifiers,
23        Size,
24    },
25};
26
27#[derive(Clone, Copy)]
28enum LayoutInvalidationDispatchDiag {
29    Disabled,
30    All,
31    Node(NodeId),
32}
33
34fn layout_invalidation_dispatch_diag() -> LayoutInvalidationDispatchDiag {
35    static MODE: std::sync::OnceLock<LayoutInvalidationDispatchDiag> = std::sync::OnceLock::new();
36    *MODE.get_or_init(|| {
37        let Some(value) = std::env::var_os("CRANPOSE_LAYOUT_INVALIDATION_DISPATCH_DIAG") else {
38            return LayoutInvalidationDispatchDiag::Disabled;
39        };
40        if value == "all" {
41            return LayoutInvalidationDispatchDiag::All;
42        }
43        value.to_string_lossy().parse::<NodeId>().map_or(
44            LayoutInvalidationDispatchDiag::Disabled,
45            LayoutInvalidationDispatchDiag::Node,
46        )
47    })
48}
49
50fn log_layout_invalidation_dispatch(
51    id: NodeId,
52    invalidation: &ModifierInvalidation,
53    curr_caps: NodeCapabilities,
54    prev_caps: NodeCapabilities,
55    modifier: &Modifier,
56) {
57    let enabled = match layout_invalidation_dispatch_diag() {
58        LayoutInvalidationDispatchDiag::Disabled => false,
59        LayoutInvalidationDispatchDiag::All => true,
60        LayoutInvalidationDispatchDiag::Node(target) => target == id,
61    };
62    if enabled {
63        log::warn!(
64            "[layout-invalidation-dispatch] node={id} invalidation={invalidation:?} curr_caps={curr_caps:?} prev_caps={prev_caps:?} modifier={modifier}"
65        );
66    }
67}
68
69/// The running layout pass and how many placed nodes it has cleared and not
70/// placed again yet: the nodes it unplaces, unless their parents place them
71/// before it ends. Ids start past 0, the pass a node no pass cleared names.
72struct PlacementPass {
73    id: Cell<u64>,
74    unplaced: Cell<usize>,
75}
76
77thread_local! {
78    static PLACEMENT_PASS: PlacementPass = const {
79        PlacementPass {
80            id: Cell::new(1),
81            unplaced: Cell::new(0),
82        }
83    };
84}
85
86/// Starts the placement bookkeeping of a layout pass.
87pub(crate) fn begin_placement_pass() {
88    PLACEMENT_PASS.with(|pass| {
89        pass.id.set(pass.id.get() + 1);
90        pass.unplaced.set(0);
91    });
92}
93
94/// The running pass, when it has left a node unplaced that it found placed.
95pub(crate) fn placement_pass_with_unplaced_nodes() -> Option<u64> {
96    PLACEMENT_PASS.with(|pass| (pass.unplaced.get() > 0).then(|| pass.id.get()))
97}
98
99/// Retained layout state for a LayoutNode.
100/// This mirrors Jetpack Compose's approach where each node stores its own
101/// measured size and placed position, eliminating the need for per-frame
102/// LayoutTree reconstruction.
103/// What a node below a node needs: layout, and measure when `measure` is set.
104#[derive(Clone, Copy, Default)]
105pub(crate) struct DescendantDirt {
106    pub(crate) layout: bool,
107    pub(crate) measure: bool,
108}
109
110impl DescendantDirt {
111    pub(crate) fn marked(self, measure: bool) -> Self {
112        Self {
113            layout: true,
114            measure: self.measure || measure,
115        }
116    }
117}
118
119#[derive(Clone, Debug, Default)]
120pub struct LayoutState {
121    size: Size,
122    position: Point,
123    is_placed: bool,
124    node_id: Option<NodeId>,
125    /// The layout pass that cleared the placed flag while the node was placed.
126    cleared_in_pass: u64,
127    content_offset: Point,
128    semantics_stamp: u64,
129}
130
131impl LayoutState {
132    pub fn size(&self) -> Size {
133        self.size
134    }
135
136    pub fn position(&self) -> Point {
137        self.position
138    }
139
140    /// Offset of the content box relative to the node origin (e.g. due to
141    /// padding).
142    pub fn content_offset(&self) -> Point {
143        self.content_offset
144    }
145
146    /// Writes the content offset, reporting an actual change to the
147    /// semantics tree.
148    pub fn set_content_offset(&mut self, offset: Point) {
149        if self.content_offset != offset {
150            self.content_offset = offset;
151            self.note_semantics_change();
152        }
153    }
154
155    /// The same state placed at the origin: what a window root's own scene
156    /// starts from, since the position its parent gave it belongs to the
157    /// parent's window.
158    pub fn at_origin(mut self) -> Self {
159        self.position = Point::default();
160        self
161    }
162
163    pub fn is_placed(&self) -> bool {
164        self.is_placed
165    }
166
167    pub(crate) fn set_node_id(&mut self, node_id: NodeId) {
168        self.node_id = Some(node_id);
169    }
170
171    /// Writes the measured size, self-reporting an actual change to the
172    /// scene phase.
173    pub fn set_size(&mut self, size: Size) {
174        if self.size != size {
175            if let Some(id) = self.node_id {
176                crate::render_state::record_geometry_scene_node(id);
177            }
178            self.size = size;
179            self.note_semantics_change();
180        }
181    }
182
183    /// Writes the placed position and marks the node placed, self-reporting
184    /// an actual move to the scene phase, and a placement of a node the
185    /// scene does not draw: one the running pass did not find placed. A move
186    /// is reported apart, so the scene can keep what it drew for the node.
187    pub fn place(&mut self, position: Point) {
188        let newly_placed = !self.is_placed && !self.restore_placement();
189        let moved = self.position != position;
190        if let Some(id) = self.node_id {
191            if newly_placed {
192                crate::render_state::record_geometry_scene_node(id);
193            } else if moved {
194                crate::render_state::record_moved_scene_node(id);
195            }
196        }
197        self.position = position;
198        if moved || !self.is_placed {
199            self.is_placed = true;
200            self.note_semantics_change();
201        }
202    }
203
204    /// Whether the running pass cleared the flag of this node while it was
205    /// placed, so placing it again changes nothing the scene draws.
206    fn restore_placement(&self) -> bool {
207        PLACEMENT_PASS.with(|pass| {
208            let restored = self.cleared_in_pass == pass.id.get();
209            if restored {
210                pass.unplaced.set(pass.unplaced.get().saturating_sub(1));
211            }
212            restored
213        })
214    }
215
216    /// Clears the placed flag at the start of a layout pass. Until its parent
217    /// places it again the pass counts the node unplaced, and the pass names
218    /// it to the scene phase if it ends that way.
219    pub fn clear_placed(&mut self) {
220        if !self.is_placed {
221            return;
222        }
223        self.is_placed = false;
224        self.cleared_in_pass = PLACEMENT_PASS.with(|pass| {
225            pass.unplaced.set(pass.unplaced.get() + 1);
226            pass.id.get()
227        });
228        self.note_semantics_change();
229    }
230
231    /// Whether `pass` found this node placed and left it unplaced.
232    pub(crate) fn unplaced_in(&self, pass: u64) -> bool {
233        !self.is_placed && self.cleared_in_pass == pass
234    }
235
236    pub(crate) fn note_semantics_change(&mut self) {
237        if let Some(id) = self.node_id {
238            crate::semantics_layout_log::record_layout_change(id, &mut self.semantics_stamp);
239        }
240    }
241}
242
243#[derive(Clone)]
244struct MeasurementCacheEntry {
245    constraints: Constraints,
246    measured: Rc<MeasuredNode>,
247}
248
249#[derive(Clone, Copy, Debug)]
250pub enum IntrinsicKind {
251    MinWidth(f32),
252    MaxWidth(f32),
253    MinHeight(f32),
254    MaxHeight(f32),
255}
256
257impl IntrinsicKind {
258    fn discriminant(&self) -> u8 {
259        match self {
260            IntrinsicKind::MinWidth(_) => 0,
261            IntrinsicKind::MaxWidth(_) => 1,
262            IntrinsicKind::MinHeight(_) => 2,
263            IntrinsicKind::MaxHeight(_) => 3,
264        }
265    }
266
267    fn value_bits(&self) -> u32 {
268        match self {
269            IntrinsicKind::MinWidth(value)
270            | IntrinsicKind::MaxWidth(value)
271            | IntrinsicKind::MinHeight(value)
272            | IntrinsicKind::MaxHeight(value) => value.to_bits(),
273        }
274    }
275}
276
277impl PartialEq for IntrinsicKind {
278    fn eq(&self, other: &Self) -> bool {
279        self.discriminant() == other.discriminant() && self.value_bits() == other.value_bits()
280    }
281}
282
283impl Eq for IntrinsicKind {}
284
285impl Hash for IntrinsicKind {
286    fn hash<H: Hasher>(&self, state: &mut H) {
287        self.discriminant().hash(state);
288        self.value_bits().hash(state);
289    }
290}
291
292#[derive(Default)]
293struct NodeCacheState {
294    epoch: u64,
295    measurement: Option<MeasurementCacheEntry>,
296    intrinsics: Vec<(IntrinsicKind, f32)>,
297    /// Whether a parent read intrinsic sizes since the cache was cleared.
298    intrinsics_read: bool,
299}
300
301#[derive(Default)]
302pub(crate) struct LayoutNodeCacheHandles {
303    state: Rc<RefCell<NodeCacheState>>,
304}
305
306impl Clone for LayoutNodeCacheHandles {
307    fn clone(&self) -> Self {
308        Self {
309            state: Rc::clone(&self.state),
310        }
311    }
312
313    fn clone_from(&mut self, source: &Self) {
314        if !Rc::ptr_eq(&self.state, &source.state) {
315            self.state = Rc::clone(&source.state);
316        }
317    }
318}
319
320impl LayoutNodeCacheHandles {
321    pub(crate) fn clear(&self) {
322        let mut state = self.state.borrow_mut();
323        state.measurement = None;
324        state.intrinsics.clear();
325        state.intrinsics_read = false;
326        state.epoch = 0;
327    }
328
329    pub(crate) fn activate(&self, epoch: u64) {
330        let mut state = self.state.borrow_mut();
331        if state.epoch != epoch {
332            state.measurement = None;
333            state.intrinsics.clear();
334            state.intrinsics_read = false;
335            state.epoch = epoch;
336        }
337    }
338
339    /// Drops the intrinsic sizes, which depend on every node below: one of
340    /// them changed. A parent's earlier read still counts for
341    /// [`Self::has_intrinsics`].
342    pub(crate) fn forget_intrinsics(&self) {
343        self.state.borrow_mut().intrinsics.clear();
344    }
345
346    pub(crate) fn epoch(&self) -> u64 {
347        self.state.borrow().epoch
348    }
349
350    pub(crate) fn get_measurement(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
351        let state = self.state.borrow();
352        state
353            .measurement
354            .as_ref()
355            .filter(|entry| entry.constraints == constraints)
356            .map(|entry| Rc::clone(&entry.measured))
357    }
358
359    /// The constraints of the measurement the cache holds.
360    pub(crate) fn measured_constraints(&self) -> Option<Constraints> {
361        self.state
362            .borrow()
363            .measurement
364            .as_ref()
365            .map(|entry| entry.constraints)
366    }
367
368    /// Whether a parent read intrinsic sizes from this cache.
369    pub(crate) fn has_intrinsics(&self) -> bool {
370        self.state.borrow().intrinsics_read
371    }
372
373    pub(crate) fn store_measurement(&self, constraints: Constraints, measured: Rc<MeasuredNode>) {
374        self.state.borrow_mut().measurement = Some(MeasurementCacheEntry {
375            constraints,
376            measured,
377        });
378    }
379
380    pub(crate) fn get_intrinsic(&self, kind: &IntrinsicKind) -> Option<f32> {
381        let state = self.state.borrow();
382        state
383            .intrinsics
384            .iter()
385            .find(|(stored_kind, _)| stored_kind == kind)
386            .map(|(_, value)| *value)
387    }
388
389    pub(crate) fn store_intrinsic(&self, kind: IntrinsicKind, value: f32) {
390        let mut state = self.state.borrow_mut();
391        state.intrinsics_read = true;
392        if let Some((_, existing)) = state
393            .intrinsics
394            .iter_mut()
395            .find(|(stored_kind, _)| stored_kind == &kind)
396        {
397            *existing = value;
398        } else {
399            state.intrinsics.push((kind, value));
400        }
401    }
402}
403
404/// A node of the layout tree.
405///
406/// Its fields keep the order they are declared in: what a layout pass reads
407/// for every child it binds comes first and fits one cache line, then what
408/// measuring the node reads, then what composition reads. Layout reads
409/// thousands of nodes a frame, and on a phone its cost is the lines it loads.
410#[repr(C)]
411pub struct LayoutNode {
412    cache: LayoutNodeCacheHandles,
413    layout_state: Rc<RefCell<LayoutState>>,
414    layout_runtime_state: Rc<RefCell<LayoutRuntimeState>>,
415    /// The actual children of this node (folded view - includes virtual nodes as-is)
416    pub children: Vec<NodeId>,
417    /// What the chain tells the parent's policy about this node: its
418    /// weight and alignments, kept beside the binding fields.
419    parent_data: Cell<cranpose_ui_layout::ParentData>,
420    needs_measure: Cell<bool>,
421    needs_layout: Cell<bool>,
422    /// A node below this one needs layout or measure.
423    descendant_dirt: Cell<DescendantDirt>,
424
425    pub measure_policy: Rc<dyn MeasurePolicy>,
426    /// Where the chain's layout modifiers put their content, written by
427    /// layout and read by the draws and text the slices collect.
428    coordinator_geometry: Rc<crate::modifier::CoordinatorGeometry>,
429    density: crate::density::Density,
430    is_virtual: bool,
431    needs_semantics: Cell<bool>,
432    /// The semantics tree has to read this node again, though its own
433    /// semantics are unchanged: a node below it changed, or its placement
434    /// or children did.
435    descendant_needs_semantics: Cell<bool>,
436    needs_redraw: Cell<bool>,
437    needs_pointer_pass: Cell<bool>,
438    needs_focus_sync: Cell<bool>,
439    modifier_slices_dirty: Cell<bool>,
440    debug_modifiers: Cell<bool>,
441
442    id: Cell<Option<NodeId>>,
443    parent: Cell<Option<NodeId>>,
444    folded_parent: Cell<Option<NodeId>>,
445    modifier_slices_snapshot: RefCell<Rc<ModifierNodeSlices>>,
446
447    /// The chain's modal and hidden flags, read by the modal count and the
448    /// modal walk: dropped whenever the chain syncs or semantics are
449    /// invalidated, the two ways its semantics change.
450    semantics_reach: Cell<Option<cranpose_foundation::SemanticsReach>>,
451    virtual_children_count: Cell<usize>,
452    owner_context_id: Cell<Option<crate::render_state::AppContextId>>,
453    modifier_chain: ModifierChainHandle,
454    pub modifier: Modifier,
455    /// Composable definitions responsible for this node, captured only for inspection builds.
456    #[cfg(feature = "inspection")]
457    pub source_trace: Rc<[cranpose_core::source_trace::SourceLocation]>,
458}
459
460pub(crate) const RECYCLED_LAYOUT_NODE_POOL_LIMIT: usize = 128;
461
462thread_local! {
463    static EMPTY_MEASURE_POLICY: Rc<dyn MeasurePolicy> =
464        Rc::new(crate::layout::policies::EmptyMeasurePolicy);
465}
466
467fn empty_measure_policy() -> Rc<dyn MeasurePolicy> {
468    EMPTY_MEASURE_POLICY.with(Rc::clone)
469}
470
471impl LayoutNode {
472    pub fn new(modifier: Modifier, measure_policy: Rc<dyn MeasurePolicy>) -> Self {
473        Self::new_with_virtual(modifier, measure_policy, false)
474    }
475
476    /// Create a virtual LayoutNode for subcomposition slot containers.
477    /// Virtual nodes are transparent - their children are flattened into parent's children list.
478    pub fn new_virtual() -> Self {
479        Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), true)
480    }
481
482    fn new_recycled_shell(is_virtual: bool) -> Self {
483        let mut shell =
484            Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), is_virtual);
485        shell.needs_measure.set(false);
486        shell.needs_layout.set(false);
487        shell.needs_semantics.set(false);
488        shell.descendant_needs_semantics.set(false);
489        shell.needs_redraw.set(false);
490        shell.needs_pointer_pass.set(false);
491        shell.needs_focus_sync.set(false);
492        shell.parent.set(None);
493        shell.folded_parent.set(None);
494        shell.id.set(None);
495        shell.owner_context_id.set(None);
496        shell.debug_modifiers.set(false);
497        shell.virtual_children_count.set(0);
498        shell.modifier_slices_dirty = Cell::new(true);
499        shell
500    }
501
502    fn new_with_virtual(
503        modifier: Modifier,
504        measure_policy: Rc<dyn MeasurePolicy>,
505        is_virtual: bool,
506    ) -> Self {
507        let layout_runtime_state = Rc::new(RefCell::new(LayoutRuntimeState::new(Rc::clone(
508            &measure_policy,
509        ))));
510        let mut node = Self {
511            #[cfg(feature = "inspection")]
512            source_trace: cranpose_core::source_trace::current_source_trace(),
513            modifier,
514            modifier_chain: ModifierChainHandle::new(),
515            measure_policy,
516            density: crate::density::Density::default(),
517            children: Vec::new(),
518            cache: LayoutNodeCacheHandles::default(),
519            needs_measure: Cell::new(true),
520            needs_layout: Cell::new(true),
521            descendant_dirt: Cell::default(),
522            parent_data: Cell::default(),
523            needs_semantics: Cell::new(true),
524            descendant_needs_semantics: Cell::new(false),
525            semantics_reach: Cell::new(None),
526            needs_redraw: Cell::new(true),
527            needs_pointer_pass: Cell::new(false),
528            needs_focus_sync: Cell::new(false),
529            parent: Cell::new(None),
530            folded_parent: Cell::new(None),
531            id: Cell::new(None),
532            owner_context_id: Cell::new(None),
533            debug_modifiers: Cell::new(false),
534            is_virtual,
535            virtual_children_count: Cell::new(0),
536            modifier_slices_snapshot: RefCell::new(Rc::default()),
537            modifier_slices_dirty: Cell::new(true),
538            layout_state: Rc::new(RefCell::new(LayoutState::default())),
539            layout_runtime_state,
540            coordinator_geometry: Rc::default(),
541        };
542        node.sync_modifier_chain(false);
543        node
544    }
545
546    pub fn set_modifier(&mut self, modifier: Modifier) {
547        #[cfg(feature = "inspection")]
548        {
549            let trace = cranpose_core::source_trace::current_source_trace();
550            if !trace.is_empty() {
551                self.source_trace = trace;
552            }
553        }
554        // An equal modifier leaves every element's node as it was. Only a
555        // chain reading modifier locals resyncs, since its parent may now
556        // provide others.
557        if self.modifier == modifier
558            && !self
559                .modifier_capabilities()
560                .contains(NodeCapabilities::MODIFIER_LOCALS)
561        {
562            return;
563        }
564        // A text that changed and nothing else, as a ticker's does, keeps
565        // the slices the chain gave: only their text moves to the new layout.
566        let text_only = self
567            .modifier
568            .differs_only_in::<crate::text_modifier_node::TextModifierElement>(&modifier);
569        let modifier_changed = text_only || !self.modifier.structural_eq(&modifier);
570        self.modifier = modifier;
571        self.sync_modifier_chain(text_only);
572        if modifier_changed {
573            self.cache.clear();
574            self.request_semantics_update();
575        }
576    }
577
578    /// Reconciles the chain with the node's modifier. `text_only` says the
579    /// modifier changed only in its text elements: current slices are then
580    /// kept, with their text pointed at the new layout.
581    fn sync_modifier_chain(&mut self, text_only: bool) {
582        let prev_caps = self.modifier_capabilities();
583        let start_parent = self.parent();
584        let mut resolver = move |token: &ModifierLocalToken| {
585            resolve_modifier_local_from_parent_chain(start_parent, token)
586        };
587        self.modifier_chain
588            .set_debug_logging(self.debug_modifiers.get());
589        self.modifier_chain.set_node_id(self.id.get());
590        let in_place = text_only
591            && self
592                .modifier_chain
593                .update_elements_in_place::<crate::text_modifier_node::TextModifierElement>(
594                    &self.modifier,
595                );
596        let modifier_local_invalidations = if in_place {
597            ModifierInvalidations::new()
598        } else {
599            let invalidations = self
600                .modifier_chain
601                .update_with_resolver(&self.modifier, &mut resolver);
602            self.refresh_parent_data();
603            invalidations
604        };
605        if prev_caps.contains(NodeCapabilities::WINDOW_ROOT)
606            != self
607                .modifier_capabilities()
608                .contains(NodeCapabilities::WINDOW_ROOT)
609        {
610            self.note_semantics_layout_change();
611        }
612        // A text element reaches nothing, so an update in place leaves the
613        // chain's reach as it was, and the modal count does not read it again.
614        if !in_place {
615            self.forget_semantics_reach();
616        }
617        let keep_slices =
618            text_only && !self.modifier_slices_dirty.get() && self.point_slices_at_text();
619        if !keep_slices {
620            self.modifier_slices_dirty.set(true);
621        }
622
623        let mut invalidations = self.modifier_chain.take_invalidations();
624        invalidations.extend(modifier_local_invalidations);
625        self.dispatch_modifier_invalidations_with_prev(&invalidations, prev_caps, keep_slices);
626        // An update in place leaves the node's parent, capabilities and
627        // modifier locals as the registry holds them.
628        if !in_place {
629            self.refresh_registry_state();
630        }
631    }
632
633    /// Points the current slices' text at the chain's text node, and
634    /// returns whether the chain has one.
635    fn point_slices_at_text(&self) -> bool {
636        let mut layout = None;
637        self.modifier_chain.chain().for_each_forward_matching(
638            NodeCapabilities::LAYOUT,
639            |node_ref| {
640                node_ref.with_node(|node| {
641                    if layout.is_none()
642                        && let Some(text) = node
643                            .as_any()
644                            .downcast_ref::<crate::text_modifier_node::TextModifierNode>()
645                    {
646                        layout = Some(text.prepared_layout_handle());
647                    }
648                });
649            },
650        );
651        let Some(layout) = layout else {
652            return false;
653        };
654        Rc::make_mut(&mut *self.modifier_slices_snapshot.borrow_mut()).replace_text_layout(layout);
655        true
656    }
657
658    fn update_modifier_slices_cache(&self) {
659        let mut snapshot = self.modifier_slices_snapshot.borrow_mut();
660        crate::modifier::collect_modifier_slices_into_shared(
661            self.modifier_chain.chain(),
662            &mut snapshot,
663            &self.coordinator_geometry,
664            self.density.density(),
665        );
666        self.modifier_slices_dirty.set(false);
667    }
668
669    /// Whether the modifier slices are built and current.
670    #[cfg(test)]
671    pub(crate) fn modifier_slices_ready(&self) -> bool {
672        !self.modifier_slices_dirty.get()
673    }
674
675    pub(crate) fn mark_modifier_slices_dirty(&self) {
676        self.modifier_slices_dirty.set(true);
677    }
678
679    #[cfg(test)]
680    fn dispatch_modifier_invalidations(&self, invalidations: &[ModifierInvalidation]) {
681        self.dispatch_modifier_invalidations_with_prev(
682            invalidations,
683            NodeCapabilities::empty(),
684            false,
685        );
686    }
687
688    /// Applies `invalidations` to the node. Each marks the slices for a
689    /// rebuild unless `keep_slices`.
690    fn dispatch_modifier_invalidations_with_prev(
691        &self,
692        invalidations: &[ModifierInvalidation],
693        prev_caps: NodeCapabilities,
694        keep_slices: bool,
695    ) {
696        let curr_caps = self.modifier_capabilities();
697        for invalidation in invalidations {
698            if !keep_slices {
699                self.modifier_slices_dirty.set(true);
700            }
701            let has_capability =
702                |capability| curr_caps.contains(capability) || prev_caps.contains(capability);
703            match invalidation.kind() {
704                InvalidationKind::Layout => {
705                    if has_capability(NodeCapabilities::LAYOUT) {
706                        self.mark_needs_measure();
707                        if let Some(id) = self.id.get() {
708                            log_layout_invalidation_dispatch(
709                                id,
710                                invalidation,
711                                curr_caps,
712                                prev_caps,
713                                &self.modifier,
714                            );
715                            let inside_composition =
716                                cranpose_core::composer_context::try_with_composer(|_| ())
717                                    .is_some();
718                            if inside_composition {
719                                cranpose_core::bubble_measure_dirty_in_composer(id);
720                            } else {
721                                crate::schedule_layout_repass(id);
722                            }
723                        }
724                    }
725                }
726                InvalidationKind::Draw => {
727                    if has_capability(NodeCapabilities::DRAW)
728                        || invalidation.capabilities().contains(NodeCapabilities::DRAW)
729                    {
730                        self.mark_needs_redraw();
731                    }
732                }
733                InvalidationKind::PointerInput => {
734                    if has_capability(NodeCapabilities::POINTER_INPUT) {
735                        self.mark_needs_pointer_pass();
736                        crate::request_pointer_invalidation();
737                        if let Some(id) = self.id.get() {
738                            crate::schedule_pointer_repass(id);
739                        }
740                    }
741                }
742                InvalidationKind::Semantics => {
743                    self.request_semantics_update();
744                }
745                InvalidationKind::Focus => {
746                    if has_capability(NodeCapabilities::FOCUS) {
747                        self.mark_needs_focus_sync();
748                        crate::request_focus_invalidation();
749                        if let Some(id) = self.id.get() {
750                            crate::schedule_focus_invalidation(id);
751                        }
752                    }
753                }
754            }
755        }
756    }
757
758    /// The grid this node was composed against.
759    pub fn density(&self) -> crate::density::Density {
760        self.density
761    }
762
763    /// Records the grid the composition provided, re-measuring if it moved.
764    pub fn set_density(&mut self, density: crate::density::Density) {
765        if self.density != density {
766            self.density = density;
767            self.cache.clear();
768            self.modifier_slices_dirty.set(true);
769            self.mark_needs_measure();
770        }
771    }
772
773    /// Updates relative modifier placement for this node's composition scope.
774    pub fn set_layout_direction(&mut self, direction: crate::LayoutDirection) {
775        if self.modifier_chain.set_layout_direction(direction) {
776            self.refresh_parent_data();
777            self.cache.clear();
778            self.modifier_slices_dirty.set(true);
779            self.mark_needs_measure();
780        }
781    }
782
783    pub fn set_measure_policy(&mut self, policy: Rc<dyn MeasurePolicy>) {
784        if !Rc::ptr_eq(&self.measure_policy, &policy) {
785            self.measure_policy = policy;
786            self.cache.clear();
787            self.mark_needs_measure();
788            if let Some(id) = self.id.get() {
789                cranpose_core::bubble_measure_dirty_in_composer(id);
790            }
791        }
792    }
793
794    /// Mark this node as needing measure. Also marks it as needing layout.
795    pub fn mark_needs_measure(&self) {
796        self.needs_measure.set(true);
797        self.needs_layout.set(true);
798    }
799
800    /// Mark this node as needing layout (but not necessarily measure).
801    pub fn mark_needs_layout(&self) {
802        self.needs_layout.set(true);
803    }
804
805    /// Mark this node as needing redraw without forcing measure/layout.
806    pub fn mark_needs_redraw(&self) {
807        self.needs_redraw.set(true);
808        if let Some(id) = self.id.get() {
809            crate::schedule_draw_repass(id);
810        }
811        crate::request_render_invalidation();
812    }
813
814    /// Check if this node needs measure.
815    pub fn needs_measure(&self) -> bool {
816        self.needs_measure.get()
817    }
818
819    /// Check if this node needs layout.
820    pub fn needs_layout(&self) -> bool {
821        self.needs_layout.get()
822    }
823
824    /// Mark this node as needing semantics recomputation.
825    pub fn mark_needs_semantics(&self) {
826        self.needs_semantics.set(true);
827        self.forget_semantics_reach();
828    }
829
830    pub(crate) fn clear_needs_semantics(&self) {
831        self.needs_semantics.set(false);
832        self.descendant_needs_semantics.set(false);
833    }
834
835    /// Returns true when this node's semantics or a descendant's need to be
836    /// recomputed.
837    pub fn needs_semantics(&self) -> bool {
838        self.needs_semantics.get() || self.descendant_needs_semantics.get()
839    }
840
841    /// Whether this node's own semantics changed since the tree last read
842    /// them, as opposed to only a descendant's.
843    pub(crate) fn semantics_changed(&self) -> bool {
844        self.needs_semantics.get()
845    }
846
847    /// Returns true when this node requested a redraw since the last render pass.
848    pub fn needs_redraw(&self) -> bool {
849        self.needs_redraw.get()
850    }
851
852    pub fn clear_needs_redraw(&self) {
853        self.needs_redraw.set(false);
854    }
855
856    fn request_semantics_update(&self) {
857        let already_dirty = self.needs_semantics.replace(true);
858        if already_dirty {
859            return;
860        }
861
862        if let Some(id) = self.id.get() {
863            cranpose_core::queue_semantics_invalidation(id);
864        }
865    }
866
867    pub(crate) fn clear_needs_measure(&self) {
868        self.needs_measure.set(false);
869    }
870
871    /// Clears the node's layout flag and its descendants': a layout pass
872    /// visited them.
873    pub(crate) fn clear_needs_layout(&self) {
874        self.needs_layout.set(false);
875        self.descendant_dirt.set(DescendantDirt::default());
876    }
877
878    /// Marks this node as needing a fresh pointer-input pass.
879    pub fn mark_needs_pointer_pass(&self) {
880        self.needs_pointer_pass.set(true);
881    }
882
883    /// Returns true when pointer-input state needs to be recomputed.
884    pub fn needs_pointer_pass(&self) -> bool {
885        self.needs_pointer_pass.get()
886    }
887
888    /// Clears the pointer-input dirty flag after hosts service it.
889    pub fn clear_needs_pointer_pass(&self) {
890        self.needs_pointer_pass.set(false);
891    }
892
893    /// Marks this node as needing a focus synchronization.
894    pub fn mark_needs_focus_sync(&self) {
895        self.needs_focus_sync.set(true);
896    }
897
898    /// Returns true when focus state needs to be synchronized.
899    pub fn needs_focus_sync(&self) -> bool {
900        self.needs_focus_sync.get()
901    }
902
903    /// Clears the focus dirty flag after the focus manager processes it.
904    pub fn clear_needs_focus_sync(&self) {
905        self.needs_focus_sync.set(false);
906    }
907
908    /// Set this node's ID (called by applier after creation).
909    pub fn set_node_id(&mut self, id: NodeId) {
910        if let Some(existing) = self.id.replace(Some(id))
911            && let Some(owner_context_id) = self.owner_context_id.take()
912        {
913            unregister_layout_node(owner_context_id, existing);
914        }
915        self.layout_state.borrow_mut().set_node_id(id);
916        let owner_context_id = register_layout_node(id, self);
917        self.owner_context_id.set(Some(owner_context_id));
918        self.refresh_registry_state();
919
920        self.modifier_chain.set_node_id(Some(id));
921        let invalidations = self.modifier_chain.take_invalidations();
922        self.dispatch_modifier_invalidations_with_prev(
923            &invalidations,
924            NodeCapabilities::empty(),
925            false,
926        );
927        // The slices carry the node's id; they are collected again when next read.
928        self.modifier_slices_dirty.set(true);
929    }
930
931    /// Get this node's ID.
932    pub fn node_id(&self) -> Option<NodeId> {
933        self.id.get()
934    }
935
936    /// Set this node's parent (called when node is added as child).
937    /// Sets both folded_parent (direct) and parent (first non-virtual ancestor for bubbling).
938    pub fn set_parent(&self, parent: NodeId) {
939        self.folded_parent.set(Some(parent));
940        self.parent.set(Some(parent));
941        self.refresh_registry_state();
942    }
943
944    /// Clear this node's parent (called when node is removed from parent).
945    pub fn clear_parent(&self) {
946        self.folded_parent.set(None);
947        self.parent.set(None);
948        self.refresh_registry_state();
949    }
950
951    /// Get this node's parent for dirty flag bubbling (may skip virtual nodes).
952    pub fn parent(&self) -> Option<NodeId> {
953        self.parent.get()
954    }
955
956    /// Get this node's direct parent (may be a virtual node).
957    pub fn folded_parent(&self) -> Option<NodeId> {
958        self.folded_parent.get()
959    }
960
961    pub(crate) fn cache_handles(&self) -> &LayoutNodeCacheHandles {
962        &self.cache
963    }
964
965    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
966        self.modifier_chain.resolved_modifiers()
967    }
968
969    /// What the chain tells the parent's measure policy about this node.
970    pub(crate) fn parent_data(&self) -> cranpose_ui_layout::ParentData {
971        self.parent_data.get()
972    }
973
974    /// Copies the parent data out of the chain's resolved modifiers, after
975    /// they were computed again.
976    fn refresh_parent_data(&self) {
977        self.parent_data.set(crate::layout::parent_data_of(
978            self.modifier_chain.resolved_modifiers().layout_properties(),
979        ));
980    }
981
982    pub fn modifier_capabilities(&self) -> NodeCapabilities {
983        self.modifier_chain.capabilities()
984    }
985
986    /// Whether this node's modifier chain makes it the root of a separate
987    /// window. Its parent lays out as if it had no size and its parent's
988    /// scene skips it; its own scene starts here.
989    pub fn is_window_root(&self) -> bool {
990        self.modifier_capabilities()
991            .contains(NodeCapabilities::WINDOW_ROOT)
992    }
993
994    pub fn modifier_child_capabilities(&self) -> NodeCapabilities {
995        self.modifier_chain.aggregate_child_capabilities()
996    }
997
998    pub fn set_debug_modifiers(&mut self, enabled: bool) {
999        self.debug_modifiers.set(enabled);
1000        self.modifier_chain.set_debug_logging(enabled);
1001    }
1002
1003    pub fn debug_modifiers_enabled(&self) -> bool {
1004        self.debug_modifiers.get()
1005    }
1006
1007    /// The node's modifier locals, or `None` while its modifiers have never
1008    /// provided or read one.
1009    pub fn modifier_locals_handle(&self) -> Option<ModifierLocalsHandle> {
1010        self.modifier_chain.modifier_locals_handle()
1011    }
1012
1013    pub fn has_layout_modifier_nodes(&self) -> bool {
1014        self.modifier_capabilities()
1015            .contains(NodeCapabilities::LAYOUT)
1016    }
1017
1018    pub fn has_draw_modifier_nodes(&self) -> bool {
1019        self.modifier_capabilities()
1020            .contains(NodeCapabilities::DRAW)
1021    }
1022
1023    pub fn has_pointer_input_modifier_nodes(&self) -> bool {
1024        self.modifier_capabilities()
1025            .contains(NodeCapabilities::POINTER_INPUT)
1026    }
1027
1028    pub fn has_semantics_modifier_nodes(&self) -> bool {
1029        self.modifier_capabilities()
1030            .contains(NodeCapabilities::SEMANTICS)
1031    }
1032
1033    pub fn has_focus_modifier_nodes(&self) -> bool {
1034        self.modifier_capabilities()
1035            .contains(NodeCapabilities::FOCUS)
1036    }
1037
1038    fn refresh_registry_state(&self) {
1039        if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
1040            let parent = self.parent();
1041            let capabilities = self.modifier_child_capabilities();
1042            let modifier_locals = self.modifier_locals_handle();
1043            let _ = crate::render_state::with_layout_node_registry_by_app_context(
1044                owner_context_id,
1045                |registry| {
1046                    registry.update_entry(id, parent, capabilities, modifier_locals);
1047                },
1048            );
1049        }
1050    }
1051
1052    pub fn modifier_slices_snapshot(&self) -> Rc<ModifierNodeSlices> {
1053        if self.modifier_slices_dirty.get() {
1054            self.update_modifier_slices_cache();
1055        }
1056        self.modifier_slices_snapshot.borrow().clone()
1057    }
1058
1059    /// Returns a clone of the current layout state.
1060    pub fn layout_state(&self) -> LayoutState {
1061        self.layout_state.borrow().clone()
1062    }
1063
1064    /// Returns the measured size of this node.
1065    pub fn measured_size(&self) -> Size {
1066        self.layout_state.borrow().size
1067    }
1068
1069    /// Returns the position of this node relative to its parent.
1070    pub fn position(&self) -> Point {
1071        self.layout_state.borrow().position
1072    }
1073
1074    /// Returns true if this node has been placed in the current layout pass.
1075    pub fn is_placed(&self) -> bool {
1076        self.layout_state.borrow().is_placed
1077    }
1078
1079    /// Updates the measured size of this node. Called during measurement.
1080    /// [`LayoutState::set_size`] self-reports actual changes to the scene
1081    /// phase.
1082    pub fn set_measured_size(&self, size: Size) {
1083        self.layout_state.borrow_mut().set_size(size);
1084    }
1085
1086    /// Updates the position of this node. Called during placement.
1087    /// [`LayoutState::place`] self-reports actual moves to the scene phase.
1088    pub fn set_position(&self, position: Point) {
1089        self.layout_state.borrow_mut().place(position);
1090    }
1091
1092    /// Records the content offset (e.g. from padding).
1093    pub fn set_content_offset(&self, offset: Point) {
1094        self.layout_state.borrow_mut().set_content_offset(offset);
1095    }
1096
1097    /// Clears the is_placed flag. Called at the start of a layout pass.
1098    pub fn clear_placed(&self) {
1099        self.layout_state.borrow_mut().clear_placed();
1100    }
1101
1102    fn note_semantics_layout_change(&self) {
1103        if let Ok(mut state) = self.layout_state.try_borrow_mut() {
1104            state.note_semantics_change();
1105        }
1106    }
1107
1108    pub fn semantics_configuration(&self) -> Option<SemanticsConfiguration> {
1109        crate::modifier::collect_semantics_from_chain(self.modifier_chain.chain())
1110    }
1111
1112    /// Drops the cached reach and queues the node for the modal count.
1113    fn forget_semantics_reach(&self) {
1114        self.semantics_reach.set(None);
1115        crate::modal_nodes::reach_changed(self.id.get());
1116    }
1117
1118    /// Whether this node's modifiers make it modal or hidden.
1119    pub fn semantics_reach(&self) -> cranpose_foundation::SemanticsReach {
1120        // A chain without semantics reaches nothing, known without touching it.
1121        if !self
1122            .modifier_capabilities()
1123            .contains(NodeCapabilities::SEMANTICS)
1124        {
1125            return cranpose_foundation::SemanticsReach::default();
1126        }
1127        if let Some(reach) = self.semantics_reach.get() {
1128            return reach;
1129        }
1130        let reach = crate::modifier::semantics_reach_of_chain(self.modifier_chain.chain());
1131        self.semantics_reach.set(Some(reach));
1132        reach
1133    }
1134
1135    pub(crate) fn modifier_chain(&self) -> &ModifierChainHandle {
1136        &self.modifier_chain
1137    }
1138
1139    /// Access the text field modifier node (if present) with a mutable callback.
1140    ///
1141    /// This is used for keyboard event dispatch to text fields.
1142    /// Returns `None` if no text field modifier is found in the chain.
1143    pub fn with_text_field_modifier_mut<R>(
1144        &mut self,
1145        f: impl FnMut(&mut crate::TextFieldModifierNode) -> R,
1146    ) -> Option<R> {
1147        self.modifier_chain.with_text_field_modifier_mut(f)
1148    }
1149
1150    /// Returns the handle to the shared layout state, which layout clones
1151    /// to update the state without borrowing the Applier.
1152    pub fn layout_state_handle(&self) -> &Rc<RefCell<LayoutState>> {
1153        &self.layout_state
1154    }
1155
1156    pub(crate) fn coordinator_geometry(&self) -> &crate::modifier::CoordinatorGeometry {
1157        &self.coordinator_geometry
1158    }
1159
1160    pub(crate) fn layout_runtime_state_handle(&self) -> Rc<RefCell<LayoutRuntimeState>> {
1161        self.layout_runtime_state.clone()
1162    }
1163
1164    #[cfg(test)]
1165    pub(crate) fn layout_runtime_debug_stats(&self) -> LayoutRuntimeDebugStats {
1166        self.layout_runtime_state.borrow().debug_stats()
1167    }
1168}
1169impl Clone for LayoutNode {
1170    fn clone(&self) -> Self {
1171        let mut node = Self {
1172            #[cfg(feature = "inspection")]
1173            source_trace: self.source_trace.clone(),
1174            modifier: self.modifier.clone(),
1175            modifier_chain: ModifierChainHandle::for_layout_direction(
1176                self.modifier_chain.layout_direction(),
1177            ),
1178            measure_policy: self.measure_policy.clone(),
1179            density: self.density,
1180            children: self.children.clone(),
1181            cache: self.cache.clone(),
1182            needs_measure: Cell::new(self.needs_measure.get()),
1183            needs_layout: Cell::new(self.needs_layout.get()),
1184            descendant_dirt: Cell::new(self.descendant_dirt.get()),
1185            parent_data: Cell::new(self.parent_data.get()),
1186            needs_semantics: Cell::new(self.needs_semantics.get()),
1187            descendant_needs_semantics: Cell::new(self.descendant_needs_semantics.get()),
1188            semantics_reach: Cell::new(None),
1189            needs_redraw: Cell::new(self.needs_redraw.get()),
1190            needs_pointer_pass: Cell::new(self.needs_pointer_pass.get()),
1191            needs_focus_sync: Cell::new(self.needs_focus_sync.get()),
1192            parent: Cell::new(self.parent.get()),
1193            folded_parent: Cell::new(self.folded_parent.get()),
1194            id: Cell::new(None),
1195            owner_context_id: Cell::new(None),
1196            debug_modifiers: Cell::new(self.debug_modifiers.get()),
1197            is_virtual: self.is_virtual,
1198            virtual_children_count: Cell::new(self.virtual_children_count.get()),
1199            modifier_slices_snapshot: RefCell::new(Rc::default()),
1200            modifier_slices_dirty: Cell::new(true),
1201            layout_state: self.layout_state.clone(),
1202            layout_runtime_state: self.layout_runtime_state.clone(),
1203            coordinator_geometry: Rc::clone(&self.coordinator_geometry),
1204        };
1205        node.sync_modifier_chain(false);
1206        node
1207    }
1208}
1209
1210impl Node for LayoutNode {
1211    fn mount(&mut self) {
1212        let (chain, mut context) = self.modifier_chain.chain_and_context_mut();
1213        chain.repair_chain();
1214        chain.attach_nodes(&mut *context);
1215        crate::modal_nodes::reach_changed(self.id.get());
1216    }
1217
1218    fn unmount(&mut self) {
1219        self.modifier_chain.chain_mut().detach_nodes();
1220    }
1221
1222    fn set_node_id(&mut self, id: NodeId) {
1223        LayoutNode::set_node_id(self, id);
1224    }
1225
1226    fn insert_child(&mut self, child: NodeId) -> bool {
1227        if self.children.contains(&child) {
1228            return false;
1229        }
1230        if is_virtual_node(child) {
1231            let count = self.virtual_children_count.get();
1232            self.virtual_children_count.set(count + 1);
1233        }
1234        self.children.push(child);
1235        self.cache.clear();
1236        self.mark_needs_measure();
1237        self.note_semantics_layout_change();
1238        true
1239    }
1240
1241    fn remove_child(&mut self, child: NodeId) -> bool {
1242        let before = self.children.len();
1243        self.children.retain(|&id| id != child);
1244        let removed = self.children.len() < before;
1245        if removed {
1246            if is_virtual_node(child) {
1247                let count = self.virtual_children_count.get();
1248                if count > 0 {
1249                    self.virtual_children_count.set(count - 1);
1250                }
1251            }
1252            self.cache.clear();
1253            self.mark_needs_measure();
1254            self.note_semantics_layout_change();
1255        }
1256        removed
1257    }
1258
1259    fn move_child(&mut self, from: usize, to: usize) {
1260        if from == to || from >= self.children.len() {
1261            return;
1262        }
1263        let child = self.children.remove(from);
1264        let target = to.min(self.children.len());
1265        self.children.insert(target, child);
1266        self.cache.clear();
1267        self.mark_needs_measure();
1268        self.note_semantics_layout_change();
1269    }
1270
1271    fn update_children(&mut self, children: &[NodeId]) {
1272        self.children.clear();
1273        self.children.extend_from_slice(children);
1274        self.cache.clear();
1275        self.mark_needs_measure();
1276        self.note_semantics_layout_change();
1277    }
1278
1279    fn collect_children_into(&self, out: &mut smallvec::SmallVec<[NodeId; 8]>) {
1280        out.clear();
1281        out.extend_from_slice(&self.children);
1282    }
1283
1284    fn owned_child_index(&self, child: NodeId) -> Option<usize> {
1285        self.children.iter().position(|&id| id == child)
1286    }
1287
1288    fn on_attached_to_parent(&mut self, parent: NodeId) {
1289        self.set_parent(parent);
1290    }
1291
1292    fn on_removed_from_parent(&mut self) {
1293        self.clear_parent();
1294    }
1295
1296    fn parent(&self) -> Option<NodeId> {
1297        self.parent.get()
1298    }
1299
1300    fn mark_needs_layout(&self) {
1301        self.needs_layout.set(true);
1302    }
1303
1304    fn needs_layout(&self) -> bool {
1305        self.needs_layout.get()
1306    }
1307
1308    fn mark_needs_measure(&self) {
1309        self.needs_measure.set(true);
1310        self.needs_layout.set(true);
1311    }
1312
1313    fn needs_measure(&self) -> bool {
1314        self.needs_measure.get()
1315    }
1316
1317    fn mark_descendant_needs_layout(&self, measure: bool) {
1318        self.descendant_dirt
1319            .set(self.descendant_dirt.get().marked(measure));
1320        if measure {
1321            self.cache.forget_intrinsics();
1322        }
1323    }
1324
1325    fn descendant_needs_layout(&self) -> bool {
1326        self.descendant_dirt.get().layout
1327    }
1328
1329    fn is_virtual(&self) -> bool {
1330        self.is_virtual
1331    }
1332
1333    fn descendant_needs_measure(&self) -> bool {
1334        self.descendant_dirt.get().measure
1335    }
1336
1337    fn mark_needs_semantics(&self) {
1338        self.needs_semantics.set(true);
1339        self.forget_semantics_reach();
1340    }
1341
1342    fn mark_descendant_needs_semantics(&self) {
1343        self.descendant_needs_semantics.set(true);
1344    }
1345
1346    fn needs_semantics(&self) -> bool {
1347        LayoutNode::needs_semantics(self)
1348    }
1349
1350    fn set_parent_for_bubbling(&mut self, parent: NodeId) {
1351        if self.parent.get().is_none() {
1352            self.parent.set(Some(parent));
1353        }
1354    }
1355
1356    fn recycle_key(&self) -> Option<TypeId> {
1357        Some(TypeId::of::<Self>())
1358    }
1359
1360    fn recycle_pool_limit(&self) -> Option<usize> {
1361        Some(RECYCLED_LAYOUT_NODE_POOL_LIMIT)
1362    }
1363
1364    fn prepare_for_recycle(&mut self) {
1365        *self = Self::new_recycled_shell(self.is_virtual);
1366    }
1367
1368    fn rehouse_for_recycle(&self) -> Option<Box<dyn cranpose_core::Node>> {
1369        Some(Box::new(Self::new_recycled_shell(self.is_virtual)))
1370    }
1371
1372    fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn cranpose_core::Node>> {
1373        let mut previous = std::mem::replace(self, Self::new_recycled_shell(self.is_virtual));
1374        let node_id = previous.id.replace(None);
1375        let parent = previous.parent.get();
1376        let folded_parent = previous.folded_parent.get();
1377        let debug_modifiers = previous.debug_modifiers.get();
1378        let needs_measure = previous.needs_measure.get();
1379        let needs_layout = previous.needs_layout.get();
1380        let needs_semantics = previous.needs_semantics.get();
1381        let descendant_needs_semantics = previous.descendant_needs_semantics.get();
1382        let needs_redraw = previous.needs_redraw.get();
1383        let needs_pointer_pass = previous.needs_pointer_pass.get();
1384        let needs_focus_sync = previous.needs_focus_sync.get();
1385        let virtual_children_count = previous.virtual_children_count.get();
1386        let children = previous.children.to_vec();
1387        let modifier = previous.modifier.rehouse_for_live_compaction();
1388        let measure_policy = previous.measure_policy.clone();
1389        let layout_state = previous.layout_state.clone();
1390        let layout_runtime_state = previous.layout_runtime_state.clone();
1391        let coordinator_geometry = Rc::clone(&previous.coordinator_geometry);
1392
1393        previous.modifier_chain.chain_mut().detach_nodes();
1394
1395        let mut compact = Self::new_with_virtual(modifier, measure_policy, previous.is_virtual);
1396        compact.children = children;
1397        #[cfg(feature = "inspection")]
1398        {
1399            compact.source_trace = previous.source_trace.clone();
1400        }
1401        compact.parent.set(parent);
1402        compact.folded_parent.set(folded_parent);
1403        compact.id.set(node_id);
1404        compact.debug_modifiers.set(debug_modifiers);
1405        compact.needs_measure.set(needs_measure);
1406        compact.needs_layout.set(needs_layout);
1407        compact.needs_semantics.set(needs_semantics);
1408        compact
1409            .descendant_needs_semantics
1410            .set(descendant_needs_semantics);
1411        compact.needs_redraw.set(needs_redraw);
1412        compact.needs_pointer_pass.set(needs_pointer_pass);
1413        compact.needs_focus_sync.set(needs_focus_sync);
1414        compact.virtual_children_count.set(virtual_children_count);
1415        compact.layout_state = layout_state;
1416        compact.layout_runtime_state = layout_runtime_state;
1417        compact.coordinator_geometry = coordinator_geometry;
1418        compact.sync_modifier_chain(false);
1419        if let Some(id) = node_id {
1420            let owner_context_id = register_layout_node(id, &compact);
1421            compact.owner_context_id.set(Some(owner_context_id));
1422        }
1423
1424        Some(Box::new(compact))
1425    }
1426}
1427
1428impl Drop for LayoutNode {
1429    fn drop(&mut self) {
1430        if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
1431            unregister_layout_node(owner_context_id, id);
1432        }
1433    }
1434}
1435
1436const MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY: usize = 128;
1437const VIRTUAL_NODE_ID_START: NodeId = 0xC0000000;
1438
1439#[cfg(test)]
1440#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1441struct LayoutNodeRegistryDebugStats {
1442    len: usize,
1443    capacity: usize,
1444}
1445
1446struct LayoutNodeRegistryEntry {
1447    parent: Option<NodeId>,
1448    modifier_child_capabilities: NodeCapabilities,
1449    modifier_locals: Option<ModifierLocalsHandle>,
1450    is_virtual: bool,
1451}
1452
1453pub(crate) struct LayoutNodeRegistryState {
1454    entries: RefCell<HashMap<NodeId, LayoutNodeRegistryEntry>>,
1455    virtual_node_id_counter: Cell<NodeId>,
1456}
1457
1458impl LayoutNodeRegistryState {
1459    pub(crate) fn new() -> Self {
1460        Self {
1461            entries: RefCell::new(HashMap::new()),
1462            virtual_node_id_counter: Cell::new(VIRTUAL_NODE_ID_START),
1463        }
1464    }
1465
1466    fn register(&self, id: NodeId, node: &LayoutNode) {
1467        self.entries.borrow_mut().insert(
1468            id,
1469            LayoutNodeRegistryEntry {
1470                parent: node.parent(),
1471                modifier_child_capabilities: node.modifier_child_capabilities(),
1472                modifier_locals: node.modifier_locals_handle(),
1473                is_virtual: node.is_virtual(),
1474            },
1475        );
1476    }
1477
1478    fn unregister(&self, id: NodeId) {
1479        let mut entries = self.entries.borrow_mut();
1480        entries.remove(&id);
1481        let should_shrink = (entries.len() <= MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY
1482            && entries.capacity() > MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1483            || entries.capacity()
1484                > entries
1485                    .len()
1486                    .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1487                    .saturating_mul(4);
1488        if should_shrink {
1489            let retained = entries
1490                .len()
1491                .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY);
1492            let mut rebuilt = HashMap::new();
1493            rebuilt.reserve(retained);
1494            rebuilt.extend(entries.drain());
1495            *entries = rebuilt;
1496        }
1497    }
1498
1499    fn update_entry(
1500        &self,
1501        id: NodeId,
1502        parent: Option<NodeId>,
1503        modifier_child_capabilities: NodeCapabilities,
1504        modifier_locals: Option<ModifierLocalsHandle>,
1505    ) {
1506        if let Some(entry) = self.entries.borrow_mut().get_mut(&id) {
1507            entry.parent = parent;
1508            entry.modifier_child_capabilities = modifier_child_capabilities;
1509            entry.modifier_locals = modifier_locals;
1510        }
1511    }
1512
1513    #[cfg(test)]
1514    fn stats(&self) -> LayoutNodeRegistryDebugStats {
1515        let entries = self.entries.borrow();
1516        LayoutNodeRegistryDebugStats {
1517            len: entries.len(),
1518            capacity: entries.capacity(),
1519        }
1520    }
1521
1522    fn is_virtual_node(&self, id: NodeId) -> bool {
1523        self.entries
1524            .borrow()
1525            .get(&id)
1526            .is_some_and(|entry| entry.is_virtual)
1527    }
1528
1529    fn allocate_virtual_node_id(&self) -> NodeId {
1530        let id = self.virtual_node_id_counter.get();
1531        self.virtual_node_id_counter.set(id.wrapping_add(1));
1532        id
1533    }
1534
1535    fn resolve_modifier_local_from_parent_chain(
1536        &self,
1537        start: Option<NodeId>,
1538        token: &ModifierLocalToken,
1539    ) -> Option<ResolvedModifierLocal> {
1540        let mut current = start;
1541        while let Some(parent_id) = current {
1542            let (next_parent, resolved) = {
1543                let entries = self.entries.borrow();
1544                if let Some(entry) = entries.get(&parent_id) {
1545                    let resolved = entry
1546                        .modifier_child_capabilities
1547                        .contains(NodeCapabilities::MODIFIER_LOCALS)
1548                        .then_some(entry.modifier_locals.as_ref())
1549                        .flatten()
1550                        .and_then(|locals| locals.borrow().resolve(token))
1551                        .map(|value| value.with_source(ModifierLocalSource::Ancestor));
1552                    (entry.parent, resolved)
1553                } else {
1554                    (None, None)
1555                }
1556            };
1557            if let Some(value) = resolved {
1558                return Some(value);
1559            }
1560            current = next_parent;
1561        }
1562        None
1563    }
1564}
1565
1566pub(crate) fn register_layout_node(
1567    id: NodeId,
1568    node: &LayoutNode,
1569) -> crate::render_state::AppContextId {
1570    let owner_context_id = crate::render_state::current_app_context_id();
1571    let _ = crate::render_state::with_layout_node_registry_by_app_context(
1572        owner_context_id,
1573        |registry| {
1574            registry.register(id, node);
1575        },
1576    );
1577    owner_context_id
1578}
1579
1580pub(crate) fn unregister_layout_node(
1581    owner_context_id: crate::render_state::AppContextId,
1582    id: NodeId,
1583) {
1584    let _ = crate::render_state::with_layout_node_registry_by_app_context(
1585        owner_context_id,
1586        |registry| {
1587            registry.unregister(id);
1588        },
1589    );
1590}
1591
1592#[cfg(test)]
1593fn layout_node_registry_stats() -> LayoutNodeRegistryDebugStats {
1594    crate::render_state::with_layout_node_registry(LayoutNodeRegistryState::stats)
1595}
1596
1597pub(crate) fn is_virtual_node(id: NodeId) -> bool {
1598    crate::render_state::with_layout_node_registry(|registry| registry.is_virtual_node(id))
1599}
1600
1601pub(crate) fn allocate_virtual_node_id() -> NodeId {
1602    crate::render_state::with_layout_node_registry(
1603        LayoutNodeRegistryState::allocate_virtual_node_id,
1604    )
1605}
1606
1607fn resolve_modifier_local_from_parent_chain(
1608    start: Option<NodeId>,
1609    token: &ModifierLocalToken,
1610) -> Option<ResolvedModifierLocal> {
1611    crate::render_state::with_layout_node_registry(|registry| {
1612        registry.resolve_modifier_local_from_parent_chain(start, token)
1613    })
1614}
1615
1616#[cfg(test)]
1617#[path = "tests/layout_node_tests.rs"]
1618mod tests;