Skip to main content

cranpose_ui/widgets/nodes/
layout_node.rs

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