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    pub(crate) fn mark_modifier_slices_dirty(&self) {
463        self.modifier_slices_dirty.set(true);
464    }
465
466    #[cfg(test)]
467    fn dispatch_modifier_invalidations(&self, invalidations: &[ModifierInvalidation]) {
468        self.dispatch_modifier_invalidations_with_prev(invalidations, NodeCapabilities::empty());
469    }
470
471    fn dispatch_modifier_invalidations_with_prev(
472        &self,
473        invalidations: &[ModifierInvalidation],
474        prev_caps: NodeCapabilities,
475    ) {
476        let curr_caps = self.modifier_capabilities;
477        for invalidation in invalidations {
478            self.modifier_slices_dirty.set(true);
479            let has_capability =
480                |capability| curr_caps.contains(capability) || prev_caps.contains(capability);
481            match invalidation.kind() {
482                InvalidationKind::Layout => {
483                    if has_capability(NodeCapabilities::LAYOUT) {
484                        self.mark_needs_measure();
485                        if let Some(id) = self.id.get() {
486                            log_layout_invalidation_dispatch(
487                                id,
488                                invalidation,
489                                curr_caps,
490                                prev_caps,
491                                &self.modifier,
492                            );
493                            let inside_composition =
494                                cranpose_core::composer_context::try_with_composer(|_| ())
495                                    .is_some();
496                            if inside_composition {
497                                cranpose_core::bubble_measure_dirty_in_composer(id);
498                            } else {
499                                crate::schedule_layout_repass(id);
500                            }
501                        }
502                    }
503                }
504                InvalidationKind::Draw => {
505                    if has_capability(NodeCapabilities::DRAW)
506                        || invalidation.capabilities().contains(NodeCapabilities::DRAW)
507                    {
508                        self.mark_needs_redraw();
509                    }
510                }
511                InvalidationKind::PointerInput => {
512                    if has_capability(NodeCapabilities::POINTER_INPUT) {
513                        self.mark_needs_pointer_pass();
514                        crate::request_pointer_invalidation();
515                        if let Some(id) = self.id.get() {
516                            crate::schedule_pointer_repass(id);
517                        }
518                    }
519                }
520                InvalidationKind::Semantics => {
521                    self.request_semantics_update();
522                }
523                InvalidationKind::Focus => {
524                    if has_capability(NodeCapabilities::FOCUS) {
525                        self.mark_needs_focus_sync();
526                        crate::request_focus_invalidation();
527                        if let Some(id) = self.id.get() {
528                            crate::schedule_focus_invalidation(id);
529                        }
530                    }
531                }
532            }
533        }
534    }
535
536    /// The grid this node was composed against.
537    pub fn density(&self) -> crate::density::Density {
538        self.density
539    }
540
541    /// Records the grid the composition provided, re-measuring if it moved.
542    pub fn set_density(&mut self, density: crate::density::Density) {
543        if self.density != density {
544            self.density = density;
545            self.cache.clear();
546            self.modifier_slices_dirty.set(true);
547            self.mark_needs_measure();
548        }
549    }
550
551    pub fn set_measure_policy(&mut self, policy: Rc<dyn MeasurePolicy>) {
552        if !Rc::ptr_eq(&self.measure_policy, &policy) {
553            self.measure_policy = policy;
554            self.cache.clear();
555            self.mark_needs_measure();
556            if let Some(id) = self.id.get() {
557                cranpose_core::bubble_measure_dirty_in_composer(id);
558            }
559        }
560    }
561
562    /// Mark this node as needing measure. Also marks it as needing layout.
563    pub fn mark_needs_measure(&self) {
564        self.needs_measure.set(true);
565        self.needs_layout.set(true);
566    }
567
568    /// Mark this node as needing layout (but not necessarily measure).
569    pub fn mark_needs_layout(&self) {
570        self.needs_layout.set(true);
571    }
572
573    /// Mark this node as needing redraw without forcing measure/layout.
574    pub fn mark_needs_redraw(&self) {
575        self.needs_redraw.set(true);
576        if let Some(id) = self.id.get() {
577            crate::schedule_draw_repass(id);
578        }
579        crate::request_render_invalidation();
580    }
581
582    /// Check if this node needs measure.
583    pub fn needs_measure(&self) -> bool {
584        self.needs_measure.get()
585    }
586
587    /// Check if this node needs layout.
588    pub fn needs_layout(&self) -> bool {
589        self.needs_layout.get()
590    }
591
592    /// Mark this node as needing semantics recomputation.
593    pub fn mark_needs_semantics(&self) {
594        self.needs_semantics.set(true);
595        self.forget_semantics_reach();
596    }
597
598    pub(crate) fn clear_needs_semantics(&self) {
599        self.needs_semantics.set(false);
600        self.descendant_needs_semantics.set(false);
601    }
602
603    /// Returns true when this node's semantics or a descendant's need to be
604    /// recomputed.
605    pub fn needs_semantics(&self) -> bool {
606        self.needs_semantics.get() || self.descendant_needs_semantics.get()
607    }
608
609    /// Whether this node's own semantics changed since the tree last read
610    /// them, as opposed to only a descendant's.
611    pub(crate) fn semantics_changed(&self) -> bool {
612        self.needs_semantics.get()
613    }
614
615    /// Returns true when this node requested a redraw since the last render pass.
616    pub fn needs_redraw(&self) -> bool {
617        self.needs_redraw.get()
618    }
619
620    pub fn clear_needs_redraw(&self) {
621        self.needs_redraw.set(false);
622    }
623
624    fn request_semantics_update(&self) {
625        let already_dirty = self.needs_semantics.replace(true);
626        if already_dirty {
627            return;
628        }
629
630        if let Some(id) = self.id.get() {
631            cranpose_core::queue_semantics_invalidation(id);
632        }
633    }
634
635    pub(crate) fn clear_needs_measure(&self) {
636        self.needs_measure.set(false);
637    }
638
639    pub(crate) fn clear_needs_layout(&self) {
640        self.needs_layout.set(false);
641    }
642
643    /// Marks this node as needing a fresh pointer-input pass.
644    pub fn mark_needs_pointer_pass(&self) {
645        self.needs_pointer_pass.set(true);
646    }
647
648    /// Returns true when pointer-input state needs to be recomputed.
649    pub fn needs_pointer_pass(&self) -> bool {
650        self.needs_pointer_pass.get()
651    }
652
653    /// Clears the pointer-input dirty flag after hosts service it.
654    pub fn clear_needs_pointer_pass(&self) {
655        self.needs_pointer_pass.set(false);
656    }
657
658    /// Marks this node as needing a focus synchronization.
659    pub fn mark_needs_focus_sync(&self) {
660        self.needs_focus_sync.set(true);
661    }
662
663    /// Returns true when focus state needs to be synchronized.
664    pub fn needs_focus_sync(&self) -> bool {
665        self.needs_focus_sync.get()
666    }
667
668    /// Clears the focus dirty flag after the focus manager processes it.
669    pub fn clear_needs_focus_sync(&self) {
670        self.needs_focus_sync.set(false);
671    }
672
673    /// Set this node's ID (called by applier after creation).
674    pub fn set_node_id(&mut self, id: NodeId) {
675        if let Some(existing) = self.id.replace(Some(id))
676            && let Some(owner_context_id) = self.owner_context_id.take()
677        {
678            unregister_layout_node(owner_context_id, existing);
679        }
680        self.layout_state.borrow_mut().set_node_id(id);
681        let owner_context_id = register_layout_node(id, self);
682        self.owner_context_id.set(Some(owner_context_id));
683        self.refresh_registry_state();
684
685        self.modifier_chain.set_node_id(Some(id));
686        let invalidations = self.modifier_chain.take_invalidations();
687        self.dispatch_modifier_invalidations_with_prev(&invalidations, NodeCapabilities::empty());
688        // The slices carry the node's id; they are collected again when next read.
689        self.modifier_slices_dirty.set(true);
690    }
691
692    /// Get this node's ID.
693    pub fn node_id(&self) -> Option<NodeId> {
694        self.id.get()
695    }
696
697    /// Set this node's parent (called when node is added as child).
698    /// Sets both folded_parent (direct) and parent (first non-virtual ancestor for bubbling).
699    pub fn set_parent(&self, parent: NodeId) {
700        self.folded_parent.set(Some(parent));
701        self.parent.set(Some(parent));
702        self.refresh_registry_state();
703    }
704
705    /// Clear this node's parent (called when node is removed from parent).
706    pub fn clear_parent(&self) {
707        self.folded_parent.set(None);
708        self.parent.set(None);
709        self.refresh_registry_state();
710    }
711
712    /// Get this node's parent for dirty flag bubbling (may skip virtual nodes).
713    pub fn parent(&self) -> Option<NodeId> {
714        self.parent.get()
715    }
716
717    /// Get this node's direct parent (may be a virtual node).
718    pub fn folded_parent(&self) -> Option<NodeId> {
719        self.folded_parent.get()
720    }
721
722    /// Returns true if this is a virtual node (transparent container for subcomposition).
723    pub fn is_virtual(&self) -> bool {
724        self.is_virtual
725    }
726
727    pub(crate) fn cache_handles(&self) -> LayoutNodeCacheHandles {
728        self.cache.clone()
729    }
730
731    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
732        self.resolved_modifiers
733    }
734
735    pub fn modifier_capabilities(&self) -> NodeCapabilities {
736        self.modifier_capabilities
737    }
738
739    /// Whether this node's modifier chain makes it the root of a separate
740    /// window. Its parent lays out as if it had no size and its parent's
741    /// scene skips it; its own scene starts here.
742    pub fn is_window_root(&self) -> bool {
743        self.modifier_capabilities
744            .contains(NodeCapabilities::WINDOW_ROOT)
745    }
746
747    pub fn modifier_child_capabilities(&self) -> NodeCapabilities {
748        self.modifier_child_capabilities
749    }
750
751    pub fn set_debug_modifiers(&mut self, enabled: bool) {
752        self.debug_modifiers.set(enabled);
753        self.modifier_chain.set_debug_logging(enabled);
754    }
755
756    pub fn debug_modifiers_enabled(&self) -> bool {
757        self.debug_modifiers.get()
758    }
759
760    pub fn modifier_locals_handle(&self) -> ModifierLocalsHandle {
761        self.modifier_chain.modifier_locals_handle()
762    }
763
764    pub fn has_layout_modifier_nodes(&self) -> bool {
765        self.modifier_capabilities
766            .contains(NodeCapabilities::LAYOUT)
767    }
768
769    pub fn has_draw_modifier_nodes(&self) -> bool {
770        self.modifier_capabilities.contains(NodeCapabilities::DRAW)
771    }
772
773    pub fn has_pointer_input_modifier_nodes(&self) -> bool {
774        self.modifier_capabilities
775            .contains(NodeCapabilities::POINTER_INPUT)
776    }
777
778    pub fn has_semantics_modifier_nodes(&self) -> bool {
779        self.modifier_capabilities
780            .contains(NodeCapabilities::SEMANTICS)
781    }
782
783    pub fn has_focus_modifier_nodes(&self) -> bool {
784        self.modifier_capabilities.contains(NodeCapabilities::FOCUS)
785    }
786
787    fn refresh_registry_state(&self) {
788        if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
789            let parent = self.parent();
790            let capabilities = self.modifier_child_capabilities();
791            let modifier_locals = self.modifier_locals_handle();
792            let _ = crate::render_state::with_layout_node_registry_by_app_context(
793                owner_context_id,
794                |registry| {
795                    registry.update_entry(id, parent, capabilities, modifier_locals);
796                },
797            );
798        }
799    }
800
801    pub fn modifier_slices_snapshot(&self) -> Rc<ModifierNodeSlices> {
802        if self.modifier_slices_dirty.get() {
803            self.update_modifier_slices_cache();
804        }
805        self.modifier_slices_snapshot.borrow().clone()
806    }
807
808    /// Returns a clone of the current layout state.
809    pub fn layout_state(&self) -> LayoutState {
810        self.layout_state.borrow().clone()
811    }
812
813    /// Returns the measured size of this node.
814    pub fn measured_size(&self) -> Size {
815        self.layout_state.borrow().size
816    }
817
818    /// Returns the position of this node relative to its parent.
819    pub fn position(&self) -> Point {
820        self.layout_state.borrow().position
821    }
822
823    /// Returns true if this node has been placed in the current layout pass.
824    pub fn is_placed(&self) -> bool {
825        self.layout_state.borrow().is_placed
826    }
827
828    /// Updates the measured size of this node. Called during measurement.
829    /// [`LayoutState::set_size`] self-reports actual changes to the scene
830    /// phase.
831    pub fn set_measured_size(&self, size: Size) {
832        self.layout_state.borrow_mut().set_size(size);
833    }
834
835    /// Updates the position of this node. Called during placement.
836    /// [`LayoutState::place`] self-reports actual moves to the scene phase.
837    pub fn set_position(&self, position: Point) {
838        self.layout_state.borrow_mut().place(position);
839    }
840
841    /// Records the content offset (e.g. from padding).
842    pub fn set_content_offset(&self, offset: Point) {
843        self.layout_state.borrow_mut().content_offset = offset;
844    }
845
846    /// Clears the is_placed flag. Called at the start of a layout pass.
847    pub fn clear_placed(&self) {
848        self.layout_state.borrow_mut().is_placed = false;
849    }
850
851    pub fn semantics_configuration(&self) -> Option<SemanticsConfiguration> {
852        crate::modifier::collect_semantics_from_chain(self.modifier_chain.chain())
853    }
854
855    /// Drops the cached reach and queues the node for the modal count.
856    fn forget_semantics_reach(&self) {
857        self.semantics_reach.set(None);
858        crate::modal_nodes::reach_changed(self.id.get());
859    }
860
861    /// Whether this node's modifiers make it modal or hidden.
862    pub fn semantics_reach(&self) -> cranpose_foundation::SemanticsReach {
863        // A chain without semantics reaches nothing, known without touching it.
864        if !self
865            .modifier_capabilities
866            .contains(NodeCapabilities::SEMANTICS)
867        {
868            return cranpose_foundation::SemanticsReach::default();
869        }
870        if let Some(reach) = self.semantics_reach.get() {
871            return reach;
872        }
873        let reach = crate::modifier::semantics_reach_of_chain(self.modifier_chain.chain());
874        self.semantics_reach.set(Some(reach));
875        reach
876    }
877
878    pub(crate) fn modifier_chain(&self) -> &ModifierChainHandle {
879        &self.modifier_chain
880    }
881
882    /// Access the text field modifier node (if present) with a mutable callback.
883    ///
884    /// This is used for keyboard event dispatch to text fields.
885    /// Returns `None` if no text field modifier is found in the chain.
886    pub fn with_text_field_modifier_mut<R>(
887        &mut self,
888        f: impl FnMut(&mut crate::TextFieldModifierNode) -> R,
889    ) -> Option<R> {
890        self.modifier_chain.with_text_field_modifier_mut(f)
891    }
892
893    /// Returns a handle to the shared layout state.
894    /// Used by layout system to update state without borrowing the Applier.
895    pub fn layout_state_handle(&self) -> Rc<RefCell<LayoutState>> {
896        self.layout_state.clone()
897    }
898
899    pub(crate) fn coordinator_geometry(&self) -> Rc<crate::modifier::CoordinatorGeometry> {
900        Rc::clone(&self.coordinator_geometry)
901    }
902
903    pub(crate) fn layout_runtime_state_handle(&self) -> Rc<RefCell<LayoutRuntimeState>> {
904        self.layout_runtime_state.clone()
905    }
906
907    #[cfg(test)]
908    pub(crate) fn layout_runtime_debug_stats(&self) -> LayoutRuntimeDebugStats {
909        self.layout_runtime_state.borrow().debug_stats()
910    }
911}
912impl Clone for LayoutNode {
913    fn clone(&self) -> Self {
914        let mut node = Self {
915            #[cfg(feature = "inspection")]
916            source_trace: self.source_trace.clone(),
917            modifier: self.modifier.clone(),
918            modifier_chain: ModifierChainHandle::new(),
919            resolved_modifiers: ResolvedModifiers::default(),
920            modifier_capabilities: self.modifier_capabilities,
921            modifier_child_capabilities: self.modifier_child_capabilities,
922            measure_policy: self.measure_policy.clone(),
923            density: self.density,
924            children: self.children.clone(),
925            cache: self.cache.clone(),
926            needs_measure: Cell::new(self.needs_measure.get()),
927            needs_layout: Cell::new(self.needs_layout.get()),
928            needs_semantics: Cell::new(self.needs_semantics.get()),
929            descendant_needs_semantics: Cell::new(self.descendant_needs_semantics.get()),
930            semantics_reach: Cell::new(None),
931            needs_redraw: Cell::new(self.needs_redraw.get()),
932            needs_pointer_pass: Cell::new(self.needs_pointer_pass.get()),
933            needs_focus_sync: Cell::new(self.needs_focus_sync.get()),
934            parent: Cell::new(self.parent.get()),
935            folded_parent: Cell::new(self.folded_parent.get()),
936            id: Cell::new(None),
937            owner_context_id: Cell::new(None),
938            debug_modifiers: Cell::new(self.debug_modifiers.get()),
939            is_virtual: self.is_virtual,
940            virtual_children_count: Cell::new(self.virtual_children_count.get()),
941            modifier_slices_snapshot: RefCell::new(Rc::default()),
942            modifier_slices_dirty: Cell::new(true),
943            layout_state: self.layout_state.clone(),
944            layout_runtime_state: self.layout_runtime_state.clone(),
945            coordinator_geometry: Rc::clone(&self.coordinator_geometry),
946        };
947        node.sync_modifier_chain();
948        node
949    }
950}
951
952impl Node for LayoutNode {
953    fn mount(&mut self) {
954        let (chain, mut context) = self.modifier_chain.chain_and_context_mut();
955        chain.repair_chain();
956        chain.attach_nodes(&mut *context);
957        crate::modal_nodes::reach_changed(self.id.get());
958    }
959
960    fn unmount(&mut self) {
961        self.modifier_chain.chain_mut().detach_nodes();
962    }
963
964    fn set_node_id(&mut self, id: NodeId) {
965        LayoutNode::set_node_id(self, id);
966    }
967
968    fn insert_child(&mut self, child: NodeId) -> bool {
969        if self.children.contains(&child) {
970            return false;
971        }
972        if is_virtual_node(child) {
973            let count = self.virtual_children_count.get();
974            self.virtual_children_count.set(count + 1);
975        }
976        self.children.push(child);
977        self.cache.clear();
978        self.mark_needs_measure();
979        true
980    }
981
982    fn remove_child(&mut self, child: NodeId) -> bool {
983        let before = self.children.len();
984        self.children.retain(|&id| id != child);
985        let removed = self.children.len() < before;
986        if removed {
987            if is_virtual_node(child) {
988                let count = self.virtual_children_count.get();
989                if count > 0 {
990                    self.virtual_children_count.set(count - 1);
991                }
992            }
993            self.cache.clear();
994            self.mark_needs_measure();
995        }
996        removed
997    }
998
999    fn move_child(&mut self, from: usize, to: usize) {
1000        if from == to || from >= self.children.len() {
1001            return;
1002        }
1003        let child = self.children.remove(from);
1004        let target = to.min(self.children.len());
1005        self.children.insert(target, child);
1006        self.cache.clear();
1007        self.mark_needs_measure();
1008    }
1009
1010    fn update_children(&mut self, children: &[NodeId]) {
1011        self.children.clear();
1012        self.children.extend_from_slice(children);
1013        self.cache.clear();
1014        self.mark_needs_measure();
1015    }
1016
1017    fn collect_children_into(&self, out: &mut smallvec::SmallVec<[NodeId; 8]>) {
1018        out.clear();
1019        out.extend_from_slice(&self.children);
1020    }
1021
1022    fn on_attached_to_parent(&mut self, parent: NodeId) {
1023        self.set_parent(parent);
1024    }
1025
1026    fn on_removed_from_parent(&mut self) {
1027        self.clear_parent();
1028    }
1029
1030    fn parent(&self) -> Option<NodeId> {
1031        self.parent.get()
1032    }
1033
1034    fn mark_needs_layout(&self) {
1035        self.needs_layout.set(true);
1036    }
1037
1038    fn needs_layout(&self) -> bool {
1039        self.needs_layout.get()
1040    }
1041
1042    fn mark_needs_measure(&self) {
1043        self.needs_measure.set(true);
1044        self.needs_layout.set(true);
1045    }
1046
1047    fn needs_measure(&self) -> bool {
1048        self.needs_measure.get()
1049    }
1050
1051    fn mark_needs_semantics(&self) {
1052        self.needs_semantics.set(true);
1053        self.forget_semantics_reach();
1054    }
1055
1056    fn mark_descendant_needs_semantics(&self) {
1057        self.descendant_needs_semantics.set(true);
1058    }
1059
1060    fn needs_semantics(&self) -> bool {
1061        LayoutNode::needs_semantics(self)
1062    }
1063
1064    fn set_parent_for_bubbling(&mut self, parent: NodeId) {
1065        if self.parent.get().is_none() {
1066            self.parent.set(Some(parent));
1067        }
1068    }
1069
1070    fn recycle_key(&self) -> Option<TypeId> {
1071        Some(TypeId::of::<Self>())
1072    }
1073
1074    fn recycle_pool_limit(&self) -> Option<usize> {
1075        Some(RECYCLED_LAYOUT_NODE_POOL_LIMIT)
1076    }
1077
1078    fn prepare_for_recycle(&mut self) {
1079        *self = Self::new_recycled_shell(self.is_virtual);
1080    }
1081
1082    fn rehouse_for_recycle(&self) -> Option<Box<dyn cranpose_core::Node>> {
1083        Some(Box::new(Self::new_recycled_shell(self.is_virtual)))
1084    }
1085
1086    fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn cranpose_core::Node>> {
1087        let mut previous = std::mem::replace(self, Self::new_recycled_shell(self.is_virtual));
1088        let node_id = previous.id.replace(None);
1089        let parent = previous.parent.get();
1090        let folded_parent = previous.folded_parent.get();
1091        let debug_modifiers = previous.debug_modifiers.get();
1092        let needs_measure = previous.needs_measure.get();
1093        let needs_layout = previous.needs_layout.get();
1094        let needs_semantics = previous.needs_semantics.get();
1095        let descendant_needs_semantics = previous.descendant_needs_semantics.get();
1096        let needs_redraw = previous.needs_redraw.get();
1097        let needs_pointer_pass = previous.needs_pointer_pass.get();
1098        let needs_focus_sync = previous.needs_focus_sync.get();
1099        let virtual_children_count = previous.virtual_children_count.get();
1100        let children = previous.children.to_vec();
1101        let modifier = previous.modifier.rehouse_for_live_compaction();
1102        let measure_policy = previous.measure_policy.clone();
1103        let layout_state = previous.layout_state.clone();
1104        let layout_runtime_state = previous.layout_runtime_state.clone();
1105        let coordinator_geometry = Rc::clone(&previous.coordinator_geometry);
1106
1107        previous.modifier_chain.chain_mut().detach_nodes();
1108
1109        let mut compact = Self::new_with_virtual(modifier, measure_policy, previous.is_virtual);
1110        compact.children = children;
1111        #[cfg(feature = "inspection")]
1112        {
1113            compact.source_trace = previous.source_trace.clone();
1114        }
1115        compact.parent.set(parent);
1116        compact.folded_parent.set(folded_parent);
1117        compact.id.set(node_id);
1118        compact.debug_modifiers.set(debug_modifiers);
1119        compact.needs_measure.set(needs_measure);
1120        compact.needs_layout.set(needs_layout);
1121        compact.needs_semantics.set(needs_semantics);
1122        compact
1123            .descendant_needs_semantics
1124            .set(descendant_needs_semantics);
1125        compact.needs_redraw.set(needs_redraw);
1126        compact.needs_pointer_pass.set(needs_pointer_pass);
1127        compact.needs_focus_sync.set(needs_focus_sync);
1128        compact.virtual_children_count.set(virtual_children_count);
1129        compact.layout_state = layout_state;
1130        compact.layout_runtime_state = layout_runtime_state;
1131        compact.coordinator_geometry = coordinator_geometry;
1132        compact.sync_modifier_chain();
1133        if let Some(id) = node_id {
1134            let owner_context_id = register_layout_node(id, &compact);
1135            compact.owner_context_id.set(Some(owner_context_id));
1136        }
1137
1138        Some(Box::new(compact))
1139    }
1140}
1141
1142impl Drop for LayoutNode {
1143    fn drop(&mut self) {
1144        if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
1145            unregister_layout_node(owner_context_id, id);
1146        }
1147    }
1148}
1149
1150const MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY: usize = 128;
1151const VIRTUAL_NODE_ID_START: NodeId = 0xC0000000;
1152
1153#[cfg(test)]
1154#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1155struct LayoutNodeRegistryDebugStats {
1156    len: usize,
1157    capacity: usize,
1158}
1159
1160struct LayoutNodeRegistryEntry {
1161    parent: Option<NodeId>,
1162    modifier_child_capabilities: NodeCapabilities,
1163    modifier_locals: ModifierLocalsHandle,
1164    is_virtual: bool,
1165}
1166
1167pub(crate) struct LayoutNodeRegistryState {
1168    entries: RefCell<HashMap<NodeId, LayoutNodeRegistryEntry>>,
1169    virtual_node_id_counter: Cell<NodeId>,
1170}
1171
1172impl LayoutNodeRegistryState {
1173    pub(crate) fn new() -> Self {
1174        Self {
1175            entries: RefCell::new(HashMap::new()),
1176            virtual_node_id_counter: Cell::new(VIRTUAL_NODE_ID_START),
1177        }
1178    }
1179
1180    fn register(&self, id: NodeId, node: &LayoutNode) {
1181        self.entries.borrow_mut().insert(
1182            id,
1183            LayoutNodeRegistryEntry {
1184                parent: node.parent(),
1185                modifier_child_capabilities: node.modifier_child_capabilities(),
1186                modifier_locals: node.modifier_locals_handle(),
1187                is_virtual: node.is_virtual(),
1188            },
1189        );
1190    }
1191
1192    fn unregister(&self, id: NodeId) {
1193        let mut entries = self.entries.borrow_mut();
1194        entries.remove(&id);
1195        let should_shrink = (entries.len() <= MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY
1196            && entries.capacity() > MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1197            || entries.capacity()
1198                > entries
1199                    .len()
1200                    .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1201                    .saturating_mul(4);
1202        if should_shrink {
1203            let retained = entries
1204                .len()
1205                .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY);
1206            let mut rebuilt = HashMap::new();
1207            rebuilt.reserve(retained);
1208            rebuilt.extend(entries.drain());
1209            *entries = rebuilt;
1210        }
1211    }
1212
1213    fn update_entry(
1214        &self,
1215        id: NodeId,
1216        parent: Option<NodeId>,
1217        modifier_child_capabilities: NodeCapabilities,
1218        modifier_locals: ModifierLocalsHandle,
1219    ) {
1220        if let Some(entry) = self.entries.borrow_mut().get_mut(&id) {
1221            entry.parent = parent;
1222            entry.modifier_child_capabilities = modifier_child_capabilities;
1223            entry.modifier_locals = modifier_locals;
1224        }
1225    }
1226
1227    #[cfg(test)]
1228    fn stats(&self) -> LayoutNodeRegistryDebugStats {
1229        let entries = self.entries.borrow();
1230        LayoutNodeRegistryDebugStats {
1231            len: entries.len(),
1232            capacity: entries.capacity(),
1233        }
1234    }
1235
1236    fn is_virtual_node(&self, id: NodeId) -> bool {
1237        self.entries
1238            .borrow()
1239            .get(&id)
1240            .is_some_and(|entry| entry.is_virtual)
1241    }
1242
1243    fn allocate_virtual_node_id(&self) -> NodeId {
1244        let id = self.virtual_node_id_counter.get();
1245        self.virtual_node_id_counter.set(id.wrapping_add(1));
1246        id
1247    }
1248
1249    fn resolve_modifier_local_from_parent_chain(
1250        &self,
1251        start: Option<NodeId>,
1252        token: &ModifierLocalToken,
1253    ) -> Option<ResolvedModifierLocal> {
1254        let mut current = start;
1255        while let Some(parent_id) = current {
1256            let (next_parent, resolved) = {
1257                let entries = self.entries.borrow();
1258                if let Some(entry) = entries.get(&parent_id) {
1259                    let resolved = if entry
1260                        .modifier_child_capabilities
1261                        .contains(NodeCapabilities::MODIFIER_LOCALS)
1262                    {
1263                        entry
1264                            .modifier_locals
1265                            .borrow()
1266                            .resolve(token)
1267                            .map(|value| value.with_source(ModifierLocalSource::Ancestor))
1268                    } else {
1269                        None
1270                    };
1271                    (entry.parent, resolved)
1272                } else {
1273                    (None, None)
1274                }
1275            };
1276            if let Some(value) = resolved {
1277                return Some(value);
1278            }
1279            current = next_parent;
1280        }
1281        None
1282    }
1283}
1284
1285pub(crate) fn register_layout_node(
1286    id: NodeId,
1287    node: &LayoutNode,
1288) -> crate::render_state::AppContextId {
1289    let owner_context_id = crate::render_state::current_app_context_id();
1290    let _ = crate::render_state::with_layout_node_registry_by_app_context(
1291        owner_context_id,
1292        |registry| {
1293            registry.register(id, node);
1294        },
1295    );
1296    owner_context_id
1297}
1298
1299pub(crate) fn unregister_layout_node(
1300    owner_context_id: crate::render_state::AppContextId,
1301    id: NodeId,
1302) {
1303    let _ = crate::render_state::with_layout_node_registry_by_app_context(
1304        owner_context_id,
1305        |registry| {
1306            registry.unregister(id);
1307        },
1308    );
1309}
1310
1311#[cfg(test)]
1312fn layout_node_registry_stats() -> LayoutNodeRegistryDebugStats {
1313    crate::render_state::with_layout_node_registry(LayoutNodeRegistryState::stats)
1314}
1315
1316pub(crate) fn is_virtual_node(id: NodeId) -> bool {
1317    crate::render_state::with_layout_node_registry(|registry| registry.is_virtual_node(id))
1318}
1319
1320pub(crate) fn allocate_virtual_node_id() -> NodeId {
1321    crate::render_state::with_layout_node_registry(
1322        LayoutNodeRegistryState::allocate_virtual_node_id,
1323    )
1324}
1325
1326fn resolve_modifier_local_from_parent_chain(
1327    start: Option<NodeId>,
1328    token: &ModifierLocalToken,
1329) -> Option<ResolvedModifierLocal> {
1330    crate::render_state::with_layout_node_registry(|registry| {
1331        registry.resolve_modifier_local_from_parent_chain(start, token)
1332    })
1333}
1334
1335#[cfg(test)]
1336#[path = "tests/layout_node_tests.rs"]
1337mod tests;