Skip to main content

cranpose_ui/widgets/nodes/
layout_node.rs

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