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