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