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