Skip to main content

cranpose_ui/widgets/nodes/
layout_node.rs

1use std::{
2    any::TypeId,
3    cell::{Cell, RefCell},
4    collections::HashMap,
5    hash::{Hash, Hasher},
6    rc::Rc,
7};
8
9use cranpose_core::{Node, NodeId};
10use cranpose_foundation::{
11    InvalidationKind, ModifierInvalidation, NodeCapabilities, 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/// Retained layout state for a LayoutNode.
69/// This mirrors Jetpack Compose's approach where each node stores its own
70/// measured size and placed position, eliminating the need for per-frame
71/// LayoutTree reconstruction.
72#[derive(Clone, Debug, Default)]
73pub struct LayoutState {
74    size: Size,
75    position: Point,
76    is_placed: bool,
77    node_id: Option<NodeId>,
78    /// Offset of the content box relative to the node origin (e.g. due to padding).
79    pub content_offset: Point,
80}
81
82impl LayoutState {
83    pub fn size(&self) -> Size {
84        self.size
85    }
86
87    pub fn position(&self) -> Point {
88        self.position
89    }
90
91    /// The same state placed at the origin: what a window root's own scene
92    /// starts from, since the position its parent gave it belongs to the
93    /// parent's window.
94    pub fn at_origin(mut self) -> Self {
95        self.position = Point::default();
96        self
97    }
98
99    pub fn is_placed(&self) -> bool {
100        self.is_placed
101    }
102
103    pub(crate) fn set_node_id(&mut self, node_id: NodeId) {
104        self.node_id = Some(node_id);
105    }
106
107    /// Writes the measured size, self-reporting an actual change to the
108    /// scene phase.
109    pub fn set_size(&mut self, size: Size) {
110        if self.size != size {
111            if let Some(id) = self.node_id {
112                crate::render_state::record_geometry_scene_node(id);
113            }
114            self.size = size;
115        }
116    }
117
118    /// Writes the placed position and marks the node placed, self-reporting
119    /// an actual move to the scene phase.
120    pub fn place(&mut self, position: Point) {
121        if self.position != position {
122            if let Some(id) = self.node_id {
123                crate::render_state::record_geometry_scene_node(id);
124            }
125            self.position = position;
126        }
127        self.is_placed = true;
128    }
129
130    /// Clears the placed flag at the start of a layout pass.
131    pub fn clear_placed(&mut self) {
132        self.is_placed = false;
133    }
134}
135
136#[derive(Clone)]
137struct MeasurementCacheEntry {
138    constraints: Constraints,
139    measured: Rc<MeasuredNode>,
140}
141
142#[derive(Clone, Copy, Debug)]
143pub enum IntrinsicKind {
144    MinWidth(f32),
145    MaxWidth(f32),
146    MinHeight(f32),
147    MaxHeight(f32),
148}
149
150impl IntrinsicKind {
151    fn discriminant(&self) -> u8 {
152        match self {
153            IntrinsicKind::MinWidth(_) => 0,
154            IntrinsicKind::MaxWidth(_) => 1,
155            IntrinsicKind::MinHeight(_) => 2,
156            IntrinsicKind::MaxHeight(_) => 3,
157        }
158    }
159
160    fn value_bits(&self) -> u32 {
161        match self {
162            IntrinsicKind::MinWidth(value)
163            | IntrinsicKind::MaxWidth(value)
164            | IntrinsicKind::MinHeight(value)
165            | IntrinsicKind::MaxHeight(value) => value.to_bits(),
166        }
167    }
168}
169
170impl PartialEq for IntrinsicKind {
171    fn eq(&self, other: &Self) -> bool {
172        self.discriminant() == other.discriminant() && self.value_bits() == other.value_bits()
173    }
174}
175
176impl Eq for IntrinsicKind {}
177
178impl Hash for IntrinsicKind {
179    fn hash<H: Hasher>(&self, state: &mut H) {
180        self.discriminant().hash(state);
181        self.value_bits().hash(state);
182    }
183}
184
185#[derive(Default)]
186struct NodeCacheState {
187    epoch: u64,
188    measurements: Vec<MeasurementCacheEntry>,
189    intrinsics: Vec<(IntrinsicKind, f32)>,
190}
191
192#[derive(Clone, Default)]
193pub(crate) struct LayoutNodeCacheHandles {
194    state: Rc<RefCell<NodeCacheState>>,
195}
196
197impl LayoutNodeCacheHandles {
198    pub(crate) fn clear(&self) {
199        let mut state = self.state.borrow_mut();
200        state.measurements.clear();
201        state.intrinsics.clear();
202        state.epoch = 0;
203    }
204
205    pub(crate) fn activate(&self, epoch: u64) {
206        let mut state = self.state.borrow_mut();
207        if state.epoch != epoch {
208            state.measurements.clear();
209            state.intrinsics.clear();
210            state.epoch = epoch;
211        }
212    }
213
214    pub(crate) fn epoch(&self) -> u64 {
215        self.state.borrow().epoch
216    }
217
218    pub(crate) fn get_measurement(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
219        let state = self.state.borrow();
220        state
221            .measurements
222            .iter()
223            .find(|entry| entry.constraints == constraints)
224            .map(|entry| Rc::clone(&entry.measured))
225    }
226
227    pub(crate) fn store_measurement(&self, constraints: Constraints, measured: Rc<MeasuredNode>) {
228        let mut state = self.state.borrow_mut();
229        if let Some(entry) = state
230            .measurements
231            .iter_mut()
232            .find(|entry| entry.constraints == constraints)
233        {
234            entry.measured = measured;
235        } else {
236            state.measurements.push(MeasurementCacheEntry {
237                constraints,
238                measured,
239            });
240        }
241    }
242
243    pub(crate) fn get_intrinsic(&self, kind: &IntrinsicKind) -> Option<f32> {
244        let state = self.state.borrow();
245        state
246            .intrinsics
247            .iter()
248            .find(|(stored_kind, _)| stored_kind == kind)
249            .map(|(_, value)| *value)
250    }
251
252    pub(crate) fn store_intrinsic(&self, kind: IntrinsicKind, value: f32) {
253        let mut state = self.state.borrow_mut();
254        if let Some((_, existing)) = state
255            .intrinsics
256            .iter_mut()
257            .find(|(stored_kind, _)| stored_kind == &kind)
258        {
259            *existing = value;
260        } else {
261            state.intrinsics.push((kind, value));
262        }
263    }
264}
265
266pub struct LayoutNode {
267    /// Composable definitions responsible for this node, captured only for inspection builds.
268    #[cfg(feature = "inspection")]
269    pub source_trace: Rc<[cranpose_core::source_trace::SourceLocation]>,
270    pub modifier: Modifier,
271    modifier_chain: ModifierChainHandle,
272    resolved_modifiers: ResolvedModifiers,
273    modifier_capabilities: NodeCapabilities,
274    modifier_child_capabilities: NodeCapabilities,
275    pub measure_policy: Rc<dyn MeasurePolicy>,
276    density: crate::density::Density,
277    /// The actual children of this node (folded view - includes virtual nodes as-is)
278    pub children: Vec<NodeId>,
279    cache: LayoutNodeCacheHandles,
280    needs_measure: Cell<bool>,
281    needs_layout: Cell<bool>,
282    needs_semantics: Cell<bool>,
283    /// A node below this one needs semantics recomputation; this node's own
284    /// semantics are unchanged.
285    descendant_needs_semantics: Cell<bool>,
286    /// The chain's modal and hidden flags, read by the modal count and the
287    /// modal walk: dropped whenever the chain syncs or semantics are
288    /// invalidated, the two ways its semantics change.
289    semantics_reach: Cell<Option<cranpose_foundation::SemanticsReach>>,
290    needs_redraw: Cell<bool>,
291    needs_pointer_pass: Cell<bool>,
292    needs_focus_sync: Cell<bool>,
293    parent: Cell<Option<NodeId>>,
294    folded_parent: Cell<Option<NodeId>>,
295    id: Cell<Option<NodeId>>,
296    owner_context_id: Cell<Option<crate::render_state::AppContextId>>,
297    debug_modifiers: Cell<bool>,
298    is_virtual: bool,
299    virtual_children_count: Cell<usize>,
300
301    modifier_slices_snapshot: RefCell<Rc<ModifierNodeSlices>>,
302    modifier_slices_dirty: Cell<bool>,
303
304    layout_state: Rc<RefCell<LayoutState>>,
305    layout_runtime_state: Rc<RefCell<LayoutRuntimeState>>,
306    /// Where the chain's layout modifiers put their content, written by
307    /// layout and read by the draws and text the slices collect.
308    coordinator_geometry: Rc<crate::modifier::CoordinatorGeometry>,
309}
310
311pub(crate) const RECYCLED_LAYOUT_NODE_POOL_LIMIT: usize = 128;
312
313thread_local! {
314    static EMPTY_MEASURE_POLICY: Rc<dyn MeasurePolicy> =
315        Rc::new(crate::layout::policies::EmptyMeasurePolicy);
316}
317
318fn empty_measure_policy() -> Rc<dyn MeasurePolicy> {
319    EMPTY_MEASURE_POLICY.with(Rc::clone)
320}
321
322impl LayoutNode {
323    pub fn new(modifier: Modifier, measure_policy: Rc<dyn MeasurePolicy>) -> Self {
324        Self::new_with_virtual(modifier, measure_policy, false)
325    }
326
327    /// Create a virtual LayoutNode for subcomposition slot containers.
328    /// Virtual nodes are transparent - their children are flattened into parent's children list.
329    pub fn new_virtual() -> Self {
330        Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), true)
331    }
332
333    fn new_recycled_shell(is_virtual: bool) -> Self {
334        let mut shell =
335            Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), is_virtual);
336        shell.needs_measure.set(false);
337        shell.needs_layout.set(false);
338        shell.needs_semantics.set(false);
339        shell.descendant_needs_semantics.set(false);
340        shell.needs_redraw.set(false);
341        shell.needs_pointer_pass.set(false);
342        shell.needs_focus_sync.set(false);
343        shell.parent.set(None);
344        shell.folded_parent.set(None);
345        shell.id.set(None);
346        shell.owner_context_id.set(None);
347        shell.debug_modifiers.set(false);
348        shell.virtual_children_count.set(0);
349        shell.cache = LayoutNodeCacheHandles::default();
350        shell.modifier_slices_snapshot = RefCell::new(Rc::default());
351        shell.modifier_slices_dirty = Cell::new(true);
352        shell.layout_state = Rc::new(RefCell::new(LayoutState::default()));
353        shell.layout_runtime_state = Rc::new(RefCell::new(LayoutRuntimeState::default()));
354        shell.coordinator_geometry = Rc::default();
355        shell
356    }
357
358    fn new_with_virtual(
359        modifier: Modifier,
360        measure_policy: Rc<dyn MeasurePolicy>,
361        is_virtual: bool,
362    ) -> Self {
363        let mut node = Self {
364            #[cfg(feature = "inspection")]
365            source_trace: cranpose_core::source_trace::current_source_trace(),
366            modifier,
367            modifier_chain: ModifierChainHandle::new(),
368            resolved_modifiers: ResolvedModifiers::default(),
369            modifier_capabilities: NodeCapabilities::default(),
370            modifier_child_capabilities: NodeCapabilities::default(),
371            measure_policy,
372            density: crate::density::Density::default(),
373            children: Vec::new(),
374            cache: LayoutNodeCacheHandles::default(),
375            needs_measure: Cell::new(true),
376            needs_layout: Cell::new(true),
377            needs_semantics: Cell::new(true),
378            descendant_needs_semantics: Cell::new(false),
379            semantics_reach: Cell::new(None),
380            needs_redraw: Cell::new(true),
381            needs_pointer_pass: Cell::new(false),
382            needs_focus_sync: Cell::new(false),
383            parent: Cell::new(None),
384            folded_parent: Cell::new(None),
385            id: Cell::new(None),
386            owner_context_id: Cell::new(None),
387            debug_modifiers: Cell::new(false),
388            is_virtual,
389            virtual_children_count: Cell::new(0),
390            modifier_slices_snapshot: RefCell::new(Rc::default()),
391            modifier_slices_dirty: Cell::new(true),
392            layout_state: Rc::new(RefCell::new(LayoutState::default())),
393            layout_runtime_state: Rc::new(RefCell::new(LayoutRuntimeState::default())),
394            coordinator_geometry: Rc::default(),
395        };
396        node.sync_modifier_chain();
397        node
398    }
399
400    pub fn set_modifier(&mut self, modifier: Modifier) {
401        #[cfg(feature = "inspection")]
402        {
403            let trace = cranpose_core::source_trace::current_source_trace();
404            if !trace.is_empty() {
405                self.source_trace = trace;
406            }
407        }
408        // An equal modifier leaves every element's node as it was. Only a
409        // chain reading modifier locals resyncs, since its parent may now
410        // provide others.
411        if self.modifier == modifier
412            && !self
413                .modifier_capabilities
414                .contains(NodeCapabilities::MODIFIER_LOCALS)
415        {
416            return;
417        }
418        let modifier_changed = !self.modifier.structural_eq(&modifier);
419        self.modifier = modifier;
420        self.sync_modifier_chain();
421        if modifier_changed {
422            self.cache.clear();
423            self.request_semantics_update();
424        }
425    }
426
427    fn sync_modifier_chain(&mut self) {
428        let prev_caps = self.modifier_capabilities;
429        let start_parent = self.parent();
430        let mut resolver = move |token: &ModifierLocalToken| {
431            resolve_modifier_local_from_parent_chain(start_parent, token)
432        };
433        self.modifier_chain
434            .set_debug_logging(self.debug_modifiers.get());
435        self.modifier_chain.set_node_id(self.id.get());
436        let modifier_local_invalidations = self
437            .modifier_chain
438            .update_with_resolver(&self.modifier, &mut resolver);
439        self.resolved_modifiers = self.modifier_chain.resolved_modifiers();
440        self.modifier_capabilities = self.modifier_chain.capabilities();
441        self.modifier_child_capabilities = self.modifier_chain.aggregate_child_capabilities();
442        self.forget_semantics_reach();
443        self.modifier_slices_dirty.set(true);
444
445        let mut invalidations = self.modifier_chain.take_invalidations();
446        invalidations.extend(modifier_local_invalidations);
447        self.dispatch_modifier_invalidations_with_prev(&invalidations, prev_caps);
448        self.refresh_registry_state();
449    }
450
451    fn update_modifier_slices_cache(&self) {
452        let mut snapshot = self.modifier_slices_snapshot.borrow_mut();
453        crate::modifier::collect_modifier_slices_into_shared(
454            self.modifier_chain.chain(),
455            &mut snapshot,
456            &self.coordinator_geometry,
457            self.density.density(),
458        );
459        self.modifier_slices_dirty.set(false);
460    }
461
462    /// Whether the modifier slices are built and current.
463    #[cfg(test)]
464    pub(crate) fn modifier_slices_ready(&self) -> bool {
465        !self.modifier_slices_dirty.get()
466    }
467
468    pub(crate) fn mark_modifier_slices_dirty(&self) {
469        self.modifier_slices_dirty.set(true);
470    }
471
472    #[cfg(test)]
473    fn dispatch_modifier_invalidations(&self, invalidations: &[ModifierInvalidation]) {
474        self.dispatch_modifier_invalidations_with_prev(invalidations, NodeCapabilities::empty());
475    }
476
477    fn dispatch_modifier_invalidations_with_prev(
478        &self,
479        invalidations: &[ModifierInvalidation],
480        prev_caps: NodeCapabilities,
481    ) {
482        let curr_caps = self.modifier_capabilities;
483        for invalidation in invalidations {
484            self.modifier_slices_dirty.set(true);
485            let has_capability =
486                |capability| curr_caps.contains(capability) || prev_caps.contains(capability);
487            match invalidation.kind() {
488                InvalidationKind::Layout => {
489                    if has_capability(NodeCapabilities::LAYOUT) {
490                        self.mark_needs_measure();
491                        if let Some(id) = self.id.get() {
492                            log_layout_invalidation_dispatch(
493                                id,
494                                invalidation,
495                                curr_caps,
496                                prev_caps,
497                                &self.modifier,
498                            );
499                            let inside_composition =
500                                cranpose_core::composer_context::try_with_composer(|_| ())
501                                    .is_some();
502                            if inside_composition {
503                                cranpose_core::bubble_measure_dirty_in_composer(id);
504                            } else {
505                                crate::schedule_layout_repass(id);
506                            }
507                        }
508                    }
509                }
510                InvalidationKind::Draw => {
511                    if has_capability(NodeCapabilities::DRAW)
512                        || invalidation.capabilities().contains(NodeCapabilities::DRAW)
513                    {
514                        self.mark_needs_redraw();
515                    }
516                }
517                InvalidationKind::PointerInput => {
518                    if has_capability(NodeCapabilities::POINTER_INPUT) {
519                        self.mark_needs_pointer_pass();
520                        crate::request_pointer_invalidation();
521                        if let Some(id) = self.id.get() {
522                            crate::schedule_pointer_repass(id);
523                        }
524                    }
525                }
526                InvalidationKind::Semantics => {
527                    self.request_semantics_update();
528                }
529                InvalidationKind::Focus => {
530                    if has_capability(NodeCapabilities::FOCUS) {
531                        self.mark_needs_focus_sync();
532                        crate::request_focus_invalidation();
533                        if let Some(id) = self.id.get() {
534                            crate::schedule_focus_invalidation(id);
535                        }
536                    }
537                }
538            }
539        }
540    }
541
542    /// The grid this node was composed against.
543    pub fn density(&self) -> crate::density::Density {
544        self.density
545    }
546
547    /// Records the grid the composition provided, re-measuring if it moved.
548    pub fn set_density(&mut self, density: crate::density::Density) {
549        if self.density != density {
550            self.density = density;
551            self.cache.clear();
552            self.modifier_slices_dirty.set(true);
553            self.mark_needs_measure();
554        }
555    }
556
557    pub fn set_measure_policy(&mut self, policy: Rc<dyn MeasurePolicy>) {
558        if !Rc::ptr_eq(&self.measure_policy, &policy) {
559            self.measure_policy = policy;
560            self.cache.clear();
561            self.mark_needs_measure();
562            if let Some(id) = self.id.get() {
563                cranpose_core::bubble_measure_dirty_in_composer(id);
564            }
565        }
566    }
567
568    /// Mark this node as needing measure. Also marks it as needing layout.
569    pub fn mark_needs_measure(&self) {
570        self.needs_measure.set(true);
571        self.needs_layout.set(true);
572    }
573
574    /// Mark this node as needing layout (but not necessarily measure).
575    pub fn mark_needs_layout(&self) {
576        self.needs_layout.set(true);
577    }
578
579    /// Mark this node as needing redraw without forcing measure/layout.
580    pub fn mark_needs_redraw(&self) {
581        self.needs_redraw.set(true);
582        if let Some(id) = self.id.get() {
583            crate::schedule_draw_repass(id);
584        }
585        crate::request_render_invalidation();
586    }
587
588    /// Check if this node needs measure.
589    pub fn needs_measure(&self) -> bool {
590        self.needs_measure.get()
591    }
592
593    /// Check if this node needs layout.
594    pub fn needs_layout(&self) -> bool {
595        self.needs_layout.get()
596    }
597
598    /// Mark this node as needing semantics recomputation.
599    pub fn mark_needs_semantics(&self) {
600        self.needs_semantics.set(true);
601        self.forget_semantics_reach();
602    }
603
604    pub(crate) fn clear_needs_semantics(&self) {
605        self.needs_semantics.set(false);
606        self.descendant_needs_semantics.set(false);
607    }
608
609    /// Returns true when this node's semantics or a descendant's need to be
610    /// recomputed.
611    pub fn needs_semantics(&self) -> bool {
612        self.needs_semantics.get() || self.descendant_needs_semantics.get()
613    }
614
615    /// Whether this node's own semantics changed since the tree last read
616    /// them, as opposed to only a descendant's.
617    pub(crate) fn semantics_changed(&self) -> bool {
618        self.needs_semantics.get()
619    }
620
621    /// Returns true when this node requested a redraw since the last render pass.
622    pub fn needs_redraw(&self) -> bool {
623        self.needs_redraw.get()
624    }
625
626    pub fn clear_needs_redraw(&self) {
627        self.needs_redraw.set(false);
628    }
629
630    fn request_semantics_update(&self) {
631        let already_dirty = self.needs_semantics.replace(true);
632        if already_dirty {
633            return;
634        }
635
636        if let Some(id) = self.id.get() {
637            cranpose_core::queue_semantics_invalidation(id);
638        }
639    }
640
641    pub(crate) fn clear_needs_measure(&self) {
642        self.needs_measure.set(false);
643    }
644
645    pub(crate) fn clear_needs_layout(&self) {
646        self.needs_layout.set(false);
647    }
648
649    /// Marks this node as needing a fresh pointer-input pass.
650    pub fn mark_needs_pointer_pass(&self) {
651        self.needs_pointer_pass.set(true);
652    }
653
654    /// Returns true when pointer-input state needs to be recomputed.
655    pub fn needs_pointer_pass(&self) -> bool {
656        self.needs_pointer_pass.get()
657    }
658
659    /// Clears the pointer-input dirty flag after hosts service it.
660    pub fn clear_needs_pointer_pass(&self) {
661        self.needs_pointer_pass.set(false);
662    }
663
664    /// Marks this node as needing a focus synchronization.
665    pub fn mark_needs_focus_sync(&self) {
666        self.needs_focus_sync.set(true);
667    }
668
669    /// Returns true when focus state needs to be synchronized.
670    pub fn needs_focus_sync(&self) -> bool {
671        self.needs_focus_sync.get()
672    }
673
674    /// Clears the focus dirty flag after the focus manager processes it.
675    pub fn clear_needs_focus_sync(&self) {
676        self.needs_focus_sync.set(false);
677    }
678
679    /// Set this node's ID (called by applier after creation).
680    pub fn set_node_id(&mut self, id: NodeId) {
681        if let Some(existing) = self.id.replace(Some(id))
682            && let Some(owner_context_id) = self.owner_context_id.take()
683        {
684            unregister_layout_node(owner_context_id, existing);
685        }
686        self.layout_state.borrow_mut().set_node_id(id);
687        let owner_context_id = register_layout_node(id, self);
688        self.owner_context_id.set(Some(owner_context_id));
689        self.refresh_registry_state();
690
691        self.modifier_chain.set_node_id(Some(id));
692        let invalidations = self.modifier_chain.take_invalidations();
693        self.dispatch_modifier_invalidations_with_prev(&invalidations, NodeCapabilities::empty());
694        // The slices carry the node's id; they are collected again when next read.
695        self.modifier_slices_dirty.set(true);
696    }
697
698    /// Get this node's ID.
699    pub fn node_id(&self) -> Option<NodeId> {
700        self.id.get()
701    }
702
703    /// Set this node's parent (called when node is added as child).
704    /// Sets both folded_parent (direct) and parent (first non-virtual ancestor for bubbling).
705    pub fn set_parent(&self, parent: NodeId) {
706        self.folded_parent.set(Some(parent));
707        self.parent.set(Some(parent));
708        self.refresh_registry_state();
709    }
710
711    /// Clear this node's parent (called when node is removed from parent).
712    pub fn clear_parent(&self) {
713        self.folded_parent.set(None);
714        self.parent.set(None);
715        self.refresh_registry_state();
716    }
717
718    /// Get this node's parent for dirty flag bubbling (may skip virtual nodes).
719    pub fn parent(&self) -> Option<NodeId> {
720        self.parent.get()
721    }
722
723    /// Get this node's direct parent (may be a virtual node).
724    pub fn folded_parent(&self) -> Option<NodeId> {
725        self.folded_parent.get()
726    }
727
728    /// Returns true if this is a virtual node (transparent container for subcomposition).
729    pub fn is_virtual(&self) -> bool {
730        self.is_virtual
731    }
732
733    pub(crate) fn cache_handles(&self) -> LayoutNodeCacheHandles {
734        self.cache.clone()
735    }
736
737    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
738        self.resolved_modifiers
739    }
740
741    pub fn modifier_capabilities(&self) -> NodeCapabilities {
742        self.modifier_capabilities
743    }
744
745    /// Whether this node's modifier chain makes it the root of a separate
746    /// window. Its parent lays out as if it had no size and its parent's
747    /// scene skips it; its own scene starts here.
748    pub fn is_window_root(&self) -> bool {
749        self.modifier_capabilities
750            .contains(NodeCapabilities::WINDOW_ROOT)
751    }
752
753    pub fn modifier_child_capabilities(&self) -> NodeCapabilities {
754        self.modifier_child_capabilities
755    }
756
757    pub fn set_debug_modifiers(&mut self, enabled: bool) {
758        self.debug_modifiers.set(enabled);
759        self.modifier_chain.set_debug_logging(enabled);
760    }
761
762    pub fn debug_modifiers_enabled(&self) -> bool {
763        self.debug_modifiers.get()
764    }
765
766    pub fn modifier_locals_handle(&self) -> ModifierLocalsHandle {
767        self.modifier_chain.modifier_locals_handle()
768    }
769
770    pub fn has_layout_modifier_nodes(&self) -> bool {
771        self.modifier_capabilities
772            .contains(NodeCapabilities::LAYOUT)
773    }
774
775    pub fn has_draw_modifier_nodes(&self) -> bool {
776        self.modifier_capabilities.contains(NodeCapabilities::DRAW)
777    }
778
779    pub fn has_pointer_input_modifier_nodes(&self) -> bool {
780        self.modifier_capabilities
781            .contains(NodeCapabilities::POINTER_INPUT)
782    }
783
784    pub fn has_semantics_modifier_nodes(&self) -> bool {
785        self.modifier_capabilities
786            .contains(NodeCapabilities::SEMANTICS)
787    }
788
789    pub fn has_focus_modifier_nodes(&self) -> bool {
790        self.modifier_capabilities.contains(NodeCapabilities::FOCUS)
791    }
792
793    fn refresh_registry_state(&self) {
794        if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
795            let parent = self.parent();
796            let capabilities = self.modifier_child_capabilities();
797            let modifier_locals = self.modifier_locals_handle();
798            let _ = crate::render_state::with_layout_node_registry_by_app_context(
799                owner_context_id,
800                |registry| {
801                    registry.update_entry(id, parent, capabilities, modifier_locals);
802                },
803            );
804        }
805    }
806
807    pub fn modifier_slices_snapshot(&self) -> Rc<ModifierNodeSlices> {
808        if self.modifier_slices_dirty.get() {
809            self.update_modifier_slices_cache();
810        }
811        self.modifier_slices_snapshot.borrow().clone()
812    }
813
814    /// Returns a clone of the current layout state.
815    pub fn layout_state(&self) -> LayoutState {
816        self.layout_state.borrow().clone()
817    }
818
819    /// Returns the measured size of this node.
820    pub fn measured_size(&self) -> Size {
821        self.layout_state.borrow().size
822    }
823
824    /// Returns the position of this node relative to its parent.
825    pub fn position(&self) -> Point {
826        self.layout_state.borrow().position
827    }
828
829    /// Returns true if this node has been placed in the current layout pass.
830    pub fn is_placed(&self) -> bool {
831        self.layout_state.borrow().is_placed
832    }
833
834    /// Updates the measured size of this node. Called during measurement.
835    /// [`LayoutState::set_size`] self-reports actual changes to the scene
836    /// phase.
837    pub fn set_measured_size(&self, size: Size) {
838        self.layout_state.borrow_mut().set_size(size);
839    }
840
841    /// Updates the position of this node. Called during placement.
842    /// [`LayoutState::place`] self-reports actual moves to the scene phase.
843    pub fn set_position(&self, position: Point) {
844        self.layout_state.borrow_mut().place(position);
845    }
846
847    /// Records the content offset (e.g. from padding).
848    pub fn set_content_offset(&self, offset: Point) {
849        self.layout_state.borrow_mut().content_offset = offset;
850    }
851
852    /// Clears the is_placed flag. Called at the start of a layout pass.
853    pub fn clear_placed(&self) {
854        self.layout_state.borrow_mut().is_placed = false;
855    }
856
857    pub fn semantics_configuration(&self) -> Option<SemanticsConfiguration> {
858        crate::modifier::collect_semantics_from_chain(self.modifier_chain.chain())
859    }
860
861    /// Drops the cached reach and queues the node for the modal count.
862    fn forget_semantics_reach(&self) {
863        self.semantics_reach.set(None);
864        crate::modal_nodes::reach_changed(self.id.get());
865    }
866
867    /// Whether this node's modifiers make it modal or hidden.
868    pub fn semantics_reach(&self) -> cranpose_foundation::SemanticsReach {
869        // A chain without semantics reaches nothing, known without touching it.
870        if !self
871            .modifier_capabilities
872            .contains(NodeCapabilities::SEMANTICS)
873        {
874            return cranpose_foundation::SemanticsReach::default();
875        }
876        if let Some(reach) = self.semantics_reach.get() {
877            return reach;
878        }
879        let reach = crate::modifier::semantics_reach_of_chain(self.modifier_chain.chain());
880        self.semantics_reach.set(Some(reach));
881        reach
882    }
883
884    pub(crate) fn modifier_chain(&self) -> &ModifierChainHandle {
885        &self.modifier_chain
886    }
887
888    /// Access the text field modifier node (if present) with a mutable callback.
889    ///
890    /// This is used for keyboard event dispatch to text fields.
891    /// Returns `None` if no text field modifier is found in the chain.
892    pub fn with_text_field_modifier_mut<R>(
893        &mut self,
894        f: impl FnMut(&mut crate::TextFieldModifierNode) -> R,
895    ) -> Option<R> {
896        self.modifier_chain.with_text_field_modifier_mut(f)
897    }
898
899    /// Returns a handle to the shared layout state.
900    /// Used by layout system to update state without borrowing the Applier.
901    pub fn layout_state_handle(&self) -> Rc<RefCell<LayoutState>> {
902        self.layout_state.clone()
903    }
904
905    pub(crate) fn coordinator_geometry(&self) -> Rc<crate::modifier::CoordinatorGeometry> {
906        Rc::clone(&self.coordinator_geometry)
907    }
908
909    pub(crate) fn layout_runtime_state_handle(&self) -> Rc<RefCell<LayoutRuntimeState>> {
910        self.layout_runtime_state.clone()
911    }
912
913    #[cfg(test)]
914    pub(crate) fn layout_runtime_debug_stats(&self) -> LayoutRuntimeDebugStats {
915        self.layout_runtime_state.borrow().debug_stats()
916    }
917}
918impl Clone for LayoutNode {
919    fn clone(&self) -> Self {
920        let mut node = Self {
921            #[cfg(feature = "inspection")]
922            source_trace: self.source_trace.clone(),
923            modifier: self.modifier.clone(),
924            modifier_chain: ModifierChainHandle::new(),
925            resolved_modifiers: ResolvedModifiers::default(),
926            modifier_capabilities: self.modifier_capabilities,
927            modifier_child_capabilities: self.modifier_child_capabilities,
928            measure_policy: self.measure_policy.clone(),
929            density: self.density,
930            children: self.children.clone(),
931            cache: self.cache.clone(),
932            needs_measure: Cell::new(self.needs_measure.get()),
933            needs_layout: Cell::new(self.needs_layout.get()),
934            needs_semantics: Cell::new(self.needs_semantics.get()),
935            descendant_needs_semantics: Cell::new(self.descendant_needs_semantics.get()),
936            semantics_reach: Cell::new(None),
937            needs_redraw: Cell::new(self.needs_redraw.get()),
938            needs_pointer_pass: Cell::new(self.needs_pointer_pass.get()),
939            needs_focus_sync: Cell::new(self.needs_focus_sync.get()),
940            parent: Cell::new(self.parent.get()),
941            folded_parent: Cell::new(self.folded_parent.get()),
942            id: Cell::new(None),
943            owner_context_id: Cell::new(None),
944            debug_modifiers: Cell::new(self.debug_modifiers.get()),
945            is_virtual: self.is_virtual,
946            virtual_children_count: Cell::new(self.virtual_children_count.get()),
947            modifier_slices_snapshot: RefCell::new(Rc::default()),
948            modifier_slices_dirty: Cell::new(true),
949            layout_state: self.layout_state.clone(),
950            layout_runtime_state: self.layout_runtime_state.clone(),
951            coordinator_geometry: Rc::clone(&self.coordinator_geometry),
952        };
953        node.sync_modifier_chain();
954        node
955    }
956}
957
958impl Node for LayoutNode {
959    fn mount(&mut self) {
960        let (chain, mut context) = self.modifier_chain.chain_and_context_mut();
961        chain.repair_chain();
962        chain.attach_nodes(&mut *context);
963        crate::modal_nodes::reach_changed(self.id.get());
964    }
965
966    fn unmount(&mut self) {
967        self.modifier_chain.chain_mut().detach_nodes();
968    }
969
970    fn set_node_id(&mut self, id: NodeId) {
971        LayoutNode::set_node_id(self, id);
972    }
973
974    fn insert_child(&mut self, child: NodeId) -> bool {
975        if self.children.contains(&child) {
976            return false;
977        }
978        if is_virtual_node(child) {
979            let count = self.virtual_children_count.get();
980            self.virtual_children_count.set(count + 1);
981        }
982        self.children.push(child);
983        self.cache.clear();
984        self.mark_needs_measure();
985        true
986    }
987
988    fn remove_child(&mut self, child: NodeId) -> bool {
989        let before = self.children.len();
990        self.children.retain(|&id| id != child);
991        let removed = self.children.len() < before;
992        if removed {
993            if is_virtual_node(child) {
994                let count = self.virtual_children_count.get();
995                if count > 0 {
996                    self.virtual_children_count.set(count - 1);
997                }
998            }
999            self.cache.clear();
1000            self.mark_needs_measure();
1001        }
1002        removed
1003    }
1004
1005    fn move_child(&mut self, from: usize, to: usize) {
1006        if from == to || from >= self.children.len() {
1007            return;
1008        }
1009        let child = self.children.remove(from);
1010        let target = to.min(self.children.len());
1011        self.children.insert(target, child);
1012        self.cache.clear();
1013        self.mark_needs_measure();
1014    }
1015
1016    fn update_children(&mut self, children: &[NodeId]) {
1017        self.children.clear();
1018        self.children.extend_from_slice(children);
1019        self.cache.clear();
1020        self.mark_needs_measure();
1021    }
1022
1023    fn collect_children_into(&self, out: &mut smallvec::SmallVec<[NodeId; 8]>) {
1024        out.clear();
1025        out.extend_from_slice(&self.children);
1026    }
1027
1028    fn on_attached_to_parent(&mut self, parent: NodeId) {
1029        self.set_parent(parent);
1030    }
1031
1032    fn on_removed_from_parent(&mut self) {
1033        self.clear_parent();
1034    }
1035
1036    fn parent(&self) -> Option<NodeId> {
1037        self.parent.get()
1038    }
1039
1040    fn mark_needs_layout(&self) {
1041        self.needs_layout.set(true);
1042    }
1043
1044    fn needs_layout(&self) -> bool {
1045        self.needs_layout.get()
1046    }
1047
1048    fn mark_needs_measure(&self) {
1049        self.needs_measure.set(true);
1050        self.needs_layout.set(true);
1051    }
1052
1053    fn needs_measure(&self) -> bool {
1054        self.needs_measure.get()
1055    }
1056
1057    fn mark_needs_semantics(&self) {
1058        self.needs_semantics.set(true);
1059        self.forget_semantics_reach();
1060    }
1061
1062    fn mark_descendant_needs_semantics(&self) {
1063        self.descendant_needs_semantics.set(true);
1064    }
1065
1066    fn needs_semantics(&self) -> bool {
1067        LayoutNode::needs_semantics(self)
1068    }
1069
1070    fn set_parent_for_bubbling(&mut self, parent: NodeId) {
1071        if self.parent.get().is_none() {
1072            self.parent.set(Some(parent));
1073        }
1074    }
1075
1076    fn recycle_key(&self) -> Option<TypeId> {
1077        Some(TypeId::of::<Self>())
1078    }
1079
1080    fn recycle_pool_limit(&self) -> Option<usize> {
1081        Some(RECYCLED_LAYOUT_NODE_POOL_LIMIT)
1082    }
1083
1084    fn prepare_for_recycle(&mut self) {
1085        *self = Self::new_recycled_shell(self.is_virtual);
1086    }
1087
1088    fn rehouse_for_recycle(&self) -> Option<Box<dyn cranpose_core::Node>> {
1089        Some(Box::new(Self::new_recycled_shell(self.is_virtual)))
1090    }
1091
1092    fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn cranpose_core::Node>> {
1093        let mut previous = std::mem::replace(self, Self::new_recycled_shell(self.is_virtual));
1094        let node_id = previous.id.replace(None);
1095        let parent = previous.parent.get();
1096        let folded_parent = previous.folded_parent.get();
1097        let debug_modifiers = previous.debug_modifiers.get();
1098        let needs_measure = previous.needs_measure.get();
1099        let needs_layout = previous.needs_layout.get();
1100        let needs_semantics = previous.needs_semantics.get();
1101        let descendant_needs_semantics = previous.descendant_needs_semantics.get();
1102        let needs_redraw = previous.needs_redraw.get();
1103        let needs_pointer_pass = previous.needs_pointer_pass.get();
1104        let needs_focus_sync = previous.needs_focus_sync.get();
1105        let virtual_children_count = previous.virtual_children_count.get();
1106        let children = previous.children.to_vec();
1107        let modifier = previous.modifier.rehouse_for_live_compaction();
1108        let measure_policy = previous.measure_policy.clone();
1109        let layout_state = previous.layout_state.clone();
1110        let layout_runtime_state = previous.layout_runtime_state.clone();
1111        let coordinator_geometry = Rc::clone(&previous.coordinator_geometry);
1112
1113        previous.modifier_chain.chain_mut().detach_nodes();
1114
1115        let mut compact = Self::new_with_virtual(modifier, measure_policy, previous.is_virtual);
1116        compact.children = children;
1117        #[cfg(feature = "inspection")]
1118        {
1119            compact.source_trace = previous.source_trace.clone();
1120        }
1121        compact.parent.set(parent);
1122        compact.folded_parent.set(folded_parent);
1123        compact.id.set(node_id);
1124        compact.debug_modifiers.set(debug_modifiers);
1125        compact.needs_measure.set(needs_measure);
1126        compact.needs_layout.set(needs_layout);
1127        compact.needs_semantics.set(needs_semantics);
1128        compact
1129            .descendant_needs_semantics
1130            .set(descendant_needs_semantics);
1131        compact.needs_redraw.set(needs_redraw);
1132        compact.needs_pointer_pass.set(needs_pointer_pass);
1133        compact.needs_focus_sync.set(needs_focus_sync);
1134        compact.virtual_children_count.set(virtual_children_count);
1135        compact.layout_state = layout_state;
1136        compact.layout_runtime_state = layout_runtime_state;
1137        compact.coordinator_geometry = coordinator_geometry;
1138        compact.sync_modifier_chain();
1139        if let Some(id) = node_id {
1140            let owner_context_id = register_layout_node(id, &compact);
1141            compact.owner_context_id.set(Some(owner_context_id));
1142        }
1143
1144        Some(Box::new(compact))
1145    }
1146}
1147
1148impl Drop for LayoutNode {
1149    fn drop(&mut self) {
1150        if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
1151            unregister_layout_node(owner_context_id, id);
1152        }
1153    }
1154}
1155
1156const MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY: usize = 128;
1157const VIRTUAL_NODE_ID_START: NodeId = 0xC0000000;
1158
1159#[cfg(test)]
1160#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1161struct LayoutNodeRegistryDebugStats {
1162    len: usize,
1163    capacity: usize,
1164}
1165
1166struct LayoutNodeRegistryEntry {
1167    parent: Option<NodeId>,
1168    modifier_child_capabilities: NodeCapabilities,
1169    modifier_locals: ModifierLocalsHandle,
1170    is_virtual: bool,
1171}
1172
1173pub(crate) struct LayoutNodeRegistryState {
1174    entries: RefCell<HashMap<NodeId, LayoutNodeRegistryEntry>>,
1175    virtual_node_id_counter: Cell<NodeId>,
1176}
1177
1178impl LayoutNodeRegistryState {
1179    pub(crate) fn new() -> Self {
1180        Self {
1181            entries: RefCell::new(HashMap::new()),
1182            virtual_node_id_counter: Cell::new(VIRTUAL_NODE_ID_START),
1183        }
1184    }
1185
1186    fn register(&self, id: NodeId, node: &LayoutNode) {
1187        self.entries.borrow_mut().insert(
1188            id,
1189            LayoutNodeRegistryEntry {
1190                parent: node.parent(),
1191                modifier_child_capabilities: node.modifier_child_capabilities(),
1192                modifier_locals: node.modifier_locals_handle(),
1193                is_virtual: node.is_virtual(),
1194            },
1195        );
1196    }
1197
1198    fn unregister(&self, id: NodeId) {
1199        let mut entries = self.entries.borrow_mut();
1200        entries.remove(&id);
1201        let should_shrink = (entries.len() <= MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY
1202            && entries.capacity() > MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1203            || entries.capacity()
1204                > entries
1205                    .len()
1206                    .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1207                    .saturating_mul(4);
1208        if should_shrink {
1209            let retained = entries
1210                .len()
1211                .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY);
1212            let mut rebuilt = HashMap::new();
1213            rebuilt.reserve(retained);
1214            rebuilt.extend(entries.drain());
1215            *entries = rebuilt;
1216        }
1217    }
1218
1219    fn update_entry(
1220        &self,
1221        id: NodeId,
1222        parent: Option<NodeId>,
1223        modifier_child_capabilities: NodeCapabilities,
1224        modifier_locals: ModifierLocalsHandle,
1225    ) {
1226        if let Some(entry) = self.entries.borrow_mut().get_mut(&id) {
1227            entry.parent = parent;
1228            entry.modifier_child_capabilities = modifier_child_capabilities;
1229            entry.modifier_locals = modifier_locals;
1230        }
1231    }
1232
1233    #[cfg(test)]
1234    fn stats(&self) -> LayoutNodeRegistryDebugStats {
1235        let entries = self.entries.borrow();
1236        LayoutNodeRegistryDebugStats {
1237            len: entries.len(),
1238            capacity: entries.capacity(),
1239        }
1240    }
1241
1242    fn is_virtual_node(&self, id: NodeId) -> bool {
1243        self.entries
1244            .borrow()
1245            .get(&id)
1246            .is_some_and(|entry| entry.is_virtual)
1247    }
1248
1249    fn allocate_virtual_node_id(&self) -> NodeId {
1250        let id = self.virtual_node_id_counter.get();
1251        self.virtual_node_id_counter.set(id.wrapping_add(1));
1252        id
1253    }
1254
1255    fn resolve_modifier_local_from_parent_chain(
1256        &self,
1257        start: Option<NodeId>,
1258        token: &ModifierLocalToken,
1259    ) -> Option<ResolvedModifierLocal> {
1260        let mut current = start;
1261        while let Some(parent_id) = current {
1262            let (next_parent, resolved) = {
1263                let entries = self.entries.borrow();
1264                if let Some(entry) = entries.get(&parent_id) {
1265                    let resolved = if entry
1266                        .modifier_child_capabilities
1267                        .contains(NodeCapabilities::MODIFIER_LOCALS)
1268                    {
1269                        entry
1270                            .modifier_locals
1271                            .borrow()
1272                            .resolve(token)
1273                            .map(|value| value.with_source(ModifierLocalSource::Ancestor))
1274                    } else {
1275                        None
1276                    };
1277                    (entry.parent, resolved)
1278                } else {
1279                    (None, None)
1280                }
1281            };
1282            if let Some(value) = resolved {
1283                return Some(value);
1284            }
1285            current = next_parent;
1286        }
1287        None
1288    }
1289}
1290
1291pub(crate) fn register_layout_node(
1292    id: NodeId,
1293    node: &LayoutNode,
1294) -> crate::render_state::AppContextId {
1295    let owner_context_id = crate::render_state::current_app_context_id();
1296    let _ = crate::render_state::with_layout_node_registry_by_app_context(
1297        owner_context_id,
1298        |registry| {
1299            registry.register(id, node);
1300        },
1301    );
1302    owner_context_id
1303}
1304
1305pub(crate) fn unregister_layout_node(
1306    owner_context_id: crate::render_state::AppContextId,
1307    id: NodeId,
1308) {
1309    let _ = crate::render_state::with_layout_node_registry_by_app_context(
1310        owner_context_id,
1311        |registry| {
1312            registry.unregister(id);
1313        },
1314    );
1315}
1316
1317#[cfg(test)]
1318fn layout_node_registry_stats() -> LayoutNodeRegistryDebugStats {
1319    crate::render_state::with_layout_node_registry(LayoutNodeRegistryState::stats)
1320}
1321
1322pub(crate) fn is_virtual_node(id: NodeId) -> bool {
1323    crate::render_state::with_layout_node_registry(|registry| registry.is_virtual_node(id))
1324}
1325
1326pub(crate) fn allocate_virtual_node_id() -> NodeId {
1327    crate::render_state::with_layout_node_registry(
1328        LayoutNodeRegistryState::allocate_virtual_node_id,
1329    )
1330}
1331
1332fn resolve_modifier_local_from_parent_chain(
1333    start: Option<NodeId>,
1334    token: &ModifierLocalToken,
1335) -> Option<ResolvedModifierLocal> {
1336    crate::render_state::with_layout_node_registry(|registry| {
1337        registry.resolve_modifier_local_from_parent_chain(start, token)
1338    })
1339}
1340
1341#[cfg(test)]
1342#[path = "tests/layout_node_tests.rs"]
1343mod tests;