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