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