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