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 indexmap::IndexSet;
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: IndexSet<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
219    /// Retained layout state (size, position) for this node.
220    /// Updated by measure/place and read by renderer.
221    /// Wrapped in Rc to ensure state is shared across clones (e.g. SubcomposeLayout usage).
222    layout_state: Rc<RefCell<LayoutState>>,
223}
224
225impl LayoutNode {
226    pub fn new(modifier: Modifier, measure_policy: Rc<dyn MeasurePolicy>) -> Self {
227        Self::new_with_virtual(modifier, measure_policy, false)
228    }
229
230    /// Create a virtual LayoutNode for subcomposition slot containers.
231    /// Virtual nodes are transparent - their children are flattened into parent's children list.
232    pub fn new_virtual() -> Self {
233        Self::new_with_virtual(
234            Modifier::empty(),
235            Rc::new(crate::layout::policies::EmptyMeasurePolicy),
236            true,
237        )
238    }
239
240    fn new_with_virtual(
241        modifier: Modifier,
242        measure_policy: Rc<dyn MeasurePolicy>,
243        is_virtual: bool,
244    ) -> Self {
245        let mut node = Self {
246            modifier,
247            modifier_chain: ModifierChainHandle::new(),
248            resolved_modifiers: ResolvedModifiers::default(),
249            modifier_capabilities: NodeCapabilities::default(),
250            modifier_child_capabilities: NodeCapabilities::default(),
251            measure_policy,
252            children: IndexSet::new(),
253            cache: LayoutNodeCacheHandles::default(),
254            needs_measure: Cell::new(true), // New nodes need initial measure
255            needs_layout: Cell::new(true),  // New nodes need initial layout
256            needs_semantics: Cell::new(true), // Semantics snapshot needs initial build
257            needs_redraw: Cell::new(true),  // First render should draw the node
258            needs_pointer_pass: Cell::new(false),
259            needs_focus_sync: Cell::new(false),
260            parent: Cell::new(None),        // Non-virtual parent for bubbling
261            folded_parent: Cell::new(None), // Direct parent (may be virtual)
262            id: Cell::new(None),            // ID set by applier after creation
263            debug_modifiers: Cell::new(false),
264            is_virtual,
265            virtual_children_count: Cell::new(0),
266            modifier_slices_buffer: RefCell::new(ModifierNodeSlices::default()),
267            modifier_slices_snapshot: RefCell::new(Rc::default()),
268            layout_state: Rc::new(RefCell::new(LayoutState::default())),
269        };
270        node.sync_modifier_chain();
271        node
272    }
273
274    pub fn set_modifier(&mut self, modifier: Modifier) {
275        // Always sync the modifier chain because element equality is used for node
276        // matching/reuse, not for skipping updates. Closures may capture updated
277        // state values that need to be passed to nodes even when the Modifier
278        // compares as equal. This matches Jetpack Compose where update() is always
279        // called on matched nodes.
280        let modifier_changed = !self.modifier.structural_eq(&modifier);
281        self.modifier = modifier;
282        self.sync_modifier_chain();
283        if modifier_changed {
284            self.cache.clear();
285            self.mark_needs_measure();
286            self.request_semantics_update();
287        }
288    }
289
290    fn sync_modifier_chain(&mut self) {
291        let start_parent = self.parent();
292        let mut resolver = move |token: ModifierLocalToken| {
293            resolve_modifier_local_from_parent_chain(start_parent, token)
294        };
295        self.modifier_chain
296            .set_debug_logging(self.debug_modifiers.get());
297        self.modifier_chain.set_node_id(self.id.get());
298        let modifier_local_invalidations = self
299            .modifier_chain
300            .update_with_resolver(&self.modifier, &mut resolver);
301        self.resolved_modifiers = self.modifier_chain.resolved_modifiers();
302        self.modifier_capabilities = self.modifier_chain.capabilities();
303        self.modifier_child_capabilities = self.modifier_chain.aggregate_child_capabilities();
304
305        // Update modifier slices cache
306        // We reuse the buffer to avoid allocation, then create a shared snapshot
307        use crate::modifier::collect_modifier_slices_into;
308        let mut buffer = self.modifier_slices_buffer.borrow_mut();
309        collect_modifier_slices_into(self.modifier_chain.chain(), &mut buffer);
310        *self.modifier_slices_snapshot.borrow_mut() = Rc::new(buffer.clone());
311
312        let mut invalidations = self.modifier_chain.take_invalidations();
313        invalidations.extend(modifier_local_invalidations);
314        self.dispatch_modifier_invalidations(&invalidations);
315        self.refresh_registry_state();
316    }
317
318    fn dispatch_modifier_invalidations(&self, invalidations: &[ModifierInvalidation]) {
319        for invalidation in invalidations {
320            match invalidation.kind() {
321                InvalidationKind::Layout => {
322                    if self.has_layout_modifier_nodes() {
323                        self.mark_needs_measure();
324                        if let Some(id) = self.id.get() {
325                            crate::schedule_layout_repass(id);
326                        }
327                    }
328                }
329                InvalidationKind::Draw => {
330                    if self.has_draw_modifier_nodes() {
331                        self.mark_needs_redraw();
332                    }
333                }
334                InvalidationKind::PointerInput => {
335                    if self.has_pointer_input_modifier_nodes() {
336                        self.mark_needs_pointer_pass();
337                        crate::request_pointer_invalidation();
338                        // Schedule pointer repass for this node
339                        if let Some(id) = self.id.get() {
340                            crate::schedule_pointer_repass(id);
341                        }
342                    }
343                }
344                InvalidationKind::Semantics => {
345                    self.request_semantics_update();
346                }
347                InvalidationKind::Focus => {
348                    if self.has_focus_modifier_nodes() {
349                        self.mark_needs_focus_sync();
350                        crate::request_focus_invalidation();
351                        // Schedule focus invalidation for this node
352                        if let Some(id) = self.id.get() {
353                            crate::schedule_focus_invalidation(id);
354                        }
355                    }
356                }
357            }
358        }
359    }
360
361    pub fn set_measure_policy(&mut self, policy: Rc<dyn MeasurePolicy>) {
362        // Only mark dirty if policy actually changed (pointer comparison)
363        if !Rc::ptr_eq(&self.measure_policy, &policy) {
364            self.measure_policy = policy;
365            self.cache.clear();
366            self.mark_needs_measure();
367        }
368    }
369
370    /// Mark this node as needing measure. Also marks it as needing layout.
371    pub fn mark_needs_measure(&self) {
372        self.needs_measure.set(true);
373        self.needs_layout.set(true);
374    }
375
376    /// Mark this node as needing layout (but not necessarily measure).
377    pub fn mark_needs_layout(&self) {
378        self.needs_layout.set(true);
379    }
380
381    /// Mark this node as needing redraw without forcing measure/layout.
382    pub fn mark_needs_redraw(&self) {
383        self.needs_redraw.set(true);
384        if let Some(id) = self.id.get() {
385            crate::schedule_draw_repass(id);
386        }
387        crate::request_render_invalidation();
388    }
389
390    /// Check if this node needs measure.
391    pub fn needs_measure(&self) -> bool {
392        self.needs_measure.get()
393    }
394
395    /// Check if this node needs layout.
396    pub fn needs_layout(&self) -> bool {
397        self.needs_layout.get()
398    }
399
400    /// Mark this node as needing semantics recomputation.
401    pub fn mark_needs_semantics(&self) {
402        self.needs_semantics.set(true);
403    }
404
405    /// Clear the semantics dirty flag after rebuilding semantics.
406    pub(crate) fn clear_needs_semantics(&self) {
407        self.needs_semantics.set(false);
408    }
409
410    /// Returns true when semantics need to be recomputed.
411    pub fn needs_semantics(&self) -> bool {
412        self.needs_semantics.get()
413    }
414
415    /// Returns true when this node requested a redraw since the last render pass.
416    pub fn needs_redraw(&self) -> bool {
417        self.needs_redraw.get()
418    }
419
420    pub fn clear_needs_redraw(&self) {
421        self.needs_redraw.set(false);
422    }
423
424    fn request_semantics_update(&self) {
425        let already_dirty = self.needs_semantics.replace(true);
426        if already_dirty {
427            return;
428        }
429
430        if let Some(id) = self.id.get() {
431            cranpose_core::queue_semantics_invalidation(id);
432        }
433    }
434
435    /// Clear the measure dirty flag after measuring.
436    pub(crate) fn clear_needs_measure(&self) {
437        self.needs_measure.set(false);
438    }
439
440    /// Clear the layout dirty flag after laying out.
441    pub(crate) fn clear_needs_layout(&self) {
442        self.needs_layout.set(false);
443    }
444
445    /// Marks this node as needing a fresh pointer-input pass.
446    pub fn mark_needs_pointer_pass(&self) {
447        self.needs_pointer_pass.set(true);
448    }
449
450    /// Returns true when pointer-input state needs to be recomputed.
451    pub fn needs_pointer_pass(&self) -> bool {
452        self.needs_pointer_pass.get()
453    }
454
455    /// Clears the pointer-input dirty flag after hosts service it.
456    pub fn clear_needs_pointer_pass(&self) {
457        self.needs_pointer_pass.set(false);
458    }
459
460    /// Marks this node as needing a focus synchronization.
461    pub fn mark_needs_focus_sync(&self) {
462        self.needs_focus_sync.set(true);
463    }
464
465    /// Returns true when focus state needs to be synchronized.
466    pub fn needs_focus_sync(&self) -> bool {
467        self.needs_focus_sync.get()
468    }
469
470    /// Clears the focus dirty flag after the focus manager processes it.
471    pub fn clear_needs_focus_sync(&self) {
472        self.needs_focus_sync.set(false);
473    }
474
475    /// Set this node's ID (called by applier after creation).
476    pub fn set_node_id(&mut self, id: NodeId) {
477        if let Some(existing) = self.id.replace(Some(id)) {
478            unregister_layout_node(existing);
479        }
480        register_layout_node(id, self);
481        self.refresh_registry_state();
482
483        // Propagate the ID to the modifier chain. This triggers a lifecycle update
484        // for nodes that depend on the node ID for invalidation (e.g., ScrollNode).
485        self.modifier_chain.set_node_id(Some(id));
486    }
487
488    /// Get this node's ID.
489    pub fn node_id(&self) -> Option<NodeId> {
490        self.id.get()
491    }
492
493    /// Set this node's parent (called when node is added as child).
494    /// Sets both folded_parent (direct) and parent (first non-virtual ancestor for bubbling).
495    pub fn set_parent(&self, parent: NodeId) {
496        self.folded_parent.set(Some(parent));
497        // For now, parent = folded_parent. Virtual parent skipping requires applier access.
498        // The actual virtual-skipping happens in bubble_measure_dirty via applier traversal.
499        self.parent.set(Some(parent));
500        self.refresh_registry_state();
501    }
502
503    /// Clear this node's parent (called when node is removed from parent).
504    pub fn clear_parent(&self) {
505        self.folded_parent.set(None);
506        self.parent.set(None);
507        self.refresh_registry_state();
508    }
509
510    /// Get this node's parent for dirty flag bubbling (may skip virtual nodes).
511    pub fn parent(&self) -> Option<NodeId> {
512        self.parent.get()
513    }
514
515    /// Get this node's direct parent (may be a virtual node).
516    pub fn folded_parent(&self) -> Option<NodeId> {
517        self.folded_parent.get()
518    }
519
520    /// Returns true if this is a virtual node (transparent container for subcomposition).
521    pub fn is_virtual(&self) -> bool {
522        self.is_virtual
523    }
524
525    pub(crate) fn cache_handles(&self) -> LayoutNodeCacheHandles {
526        self.cache.clone()
527    }
528
529    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
530        self.resolved_modifiers
531    }
532
533    pub fn modifier_capabilities(&self) -> NodeCapabilities {
534        self.modifier_capabilities
535    }
536
537    pub fn modifier_child_capabilities(&self) -> NodeCapabilities {
538        self.modifier_child_capabilities
539    }
540
541    pub fn set_debug_modifiers(&mut self, enabled: bool) {
542        self.debug_modifiers.set(enabled);
543        self.modifier_chain.set_debug_logging(enabled);
544    }
545
546    pub fn debug_modifiers_enabled(&self) -> bool {
547        self.debug_modifiers.get()
548    }
549
550    pub fn modifier_locals_handle(&self) -> ModifierLocalsHandle {
551        self.modifier_chain.modifier_locals_handle()
552    }
553
554    pub fn has_layout_modifier_nodes(&self) -> bool {
555        self.modifier_capabilities
556            .contains(NodeCapabilities::LAYOUT)
557    }
558
559    pub fn has_draw_modifier_nodes(&self) -> bool {
560        self.modifier_capabilities.contains(NodeCapabilities::DRAW)
561    }
562
563    pub fn has_pointer_input_modifier_nodes(&self) -> bool {
564        self.modifier_capabilities
565            .contains(NodeCapabilities::POINTER_INPUT)
566    }
567
568    pub fn has_semantics_modifier_nodes(&self) -> bool {
569        self.modifier_capabilities
570            .contains(NodeCapabilities::SEMANTICS)
571    }
572
573    pub fn has_focus_modifier_nodes(&self) -> bool {
574        self.modifier_capabilities.contains(NodeCapabilities::FOCUS)
575    }
576
577    fn refresh_registry_state(&self) {
578        if let Some(id) = self.id.get() {
579            let parent = self.parent();
580            let capabilities = self.modifier_child_capabilities();
581            let modifier_locals = self.modifier_locals_handle();
582            LAYOUT_NODE_REGISTRY.with(|registry| {
583                if let Some(entry) = registry.borrow_mut().get_mut(&id) {
584                    entry.parent = parent;
585                    entry.modifier_child_capabilities = capabilities;
586                    entry.modifier_locals = modifier_locals;
587                }
588            });
589        }
590    }
591
592    pub fn modifier_slices_snapshot(&self) -> Rc<ModifierNodeSlices> {
593        self.modifier_slices_snapshot.borrow().clone()
594    }
595
596    // ═══════════════════════════════════════════════════════════════════════
597    // Retained Layout State API
598    // ═══════════════════════════════════════════════════════════════════════
599
600    /// Returns a clone of the current layout state.
601    pub fn layout_state(&self) -> LayoutState {
602        self.layout_state.borrow().clone()
603    }
604
605    /// Returns the measured size of this node.
606    pub fn measured_size(&self) -> Size {
607        self.layout_state.borrow().size
608    }
609
610    /// Returns the position of this node relative to its parent.
611    pub fn position(&self) -> Point {
612        self.layout_state.borrow().position
613    }
614
615    /// Returns true if this node has been placed in the current layout pass.
616    pub fn is_placed(&self) -> bool {
617        self.layout_state.borrow().is_placed
618    }
619
620    /// Updates the measured size of this node. Called during measurement.
621    pub fn set_measured_size(&self, size: Size) {
622        let mut state = self.layout_state.borrow_mut();
623        state.size = size;
624    }
625
626    /// Updates the position of this node. Called during placement.
627    pub fn set_position(&self, position: Point) {
628        let mut state = self.layout_state.borrow_mut();
629        state.position = position;
630        state.is_placed = true;
631    }
632
633    /// Records the constraints used for measurement. Used for relayout optimization.
634    pub fn set_measurement_constraints(&self, constraints: Constraints) {
635        self.layout_state.borrow_mut().measurement_constraints = constraints;
636    }
637
638    /// Records the content offset (e.g. from padding).
639    pub fn set_content_offset(&self, offset: Point) {
640        self.layout_state.borrow_mut().content_offset = offset;
641    }
642
643    /// Clears the is_placed flag. Called at the start of a layout pass.
644    pub fn clear_placed(&self) {
645        self.layout_state.borrow_mut().is_placed = false;
646    }
647
648    pub fn semantics_configuration(&self) -> Option<SemanticsConfiguration> {
649        crate::modifier::collect_semantics_from_chain(self.modifier_chain.chain())
650    }
651
652    /// Returns a reference to the modifier chain for layout/draw pipeline integration.
653    pub(crate) fn modifier_chain(&self) -> &ModifierChainHandle {
654        &self.modifier_chain
655    }
656
657    /// Access the text field modifier node (if present) with a mutable callback.
658    ///
659    /// This is used for keyboard event dispatch to text fields.
660    /// Returns `None` if no text field modifier is found in the chain.
661    pub fn with_text_field_modifier_mut<R>(
662        &mut self,
663        f: impl FnMut(&mut crate::TextFieldModifierNode) -> R,
664    ) -> Option<R> {
665        self.modifier_chain.with_text_field_modifier_mut(f)
666    }
667
668    /// Returns a handle to the shared layout state.
669    /// Used by layout system to update state without borrowing the Applier.
670    pub fn layout_state_handle(&self) -> Rc<RefCell<LayoutState>> {
671        self.layout_state.clone()
672    }
673}
674impl Clone for LayoutNode {
675    fn clone(&self) -> Self {
676        let mut node = Self {
677            modifier: self.modifier.clone(),
678            modifier_chain: ModifierChainHandle::new(),
679            resolved_modifiers: ResolvedModifiers::default(),
680            modifier_capabilities: self.modifier_capabilities,
681            modifier_child_capabilities: self.modifier_child_capabilities,
682            measure_policy: self.measure_policy.clone(),
683            children: self.children.clone(),
684            cache: self.cache.clone(),
685            needs_measure: Cell::new(self.needs_measure.get()),
686            needs_layout: Cell::new(self.needs_layout.get()),
687            needs_semantics: Cell::new(self.needs_semantics.get()),
688            needs_redraw: Cell::new(self.needs_redraw.get()),
689            needs_pointer_pass: Cell::new(self.needs_pointer_pass.get()),
690            needs_focus_sync: Cell::new(self.needs_focus_sync.get()),
691            parent: Cell::new(self.parent.get()),
692            folded_parent: Cell::new(self.folded_parent.get()),
693            id: Cell::new(None),
694            debug_modifiers: Cell::new(self.debug_modifiers.get()),
695            is_virtual: self.is_virtual,
696            virtual_children_count: Cell::new(self.virtual_children_count.get()),
697            modifier_slices_buffer: RefCell::new(ModifierNodeSlices::default()),
698            modifier_slices_snapshot: RefCell::new(Rc::default()),
699            // Share the same layout state across clones
700            layout_state: self.layout_state.clone(),
701        };
702        node.sync_modifier_chain();
703        node
704    }
705}
706
707impl Node for LayoutNode {
708    fn mount(&mut self) {
709        let (chain, mut context) = self.modifier_chain.chain_and_context_mut();
710        chain.repair_chain();
711        chain.attach_nodes(&mut *context);
712    }
713
714    fn unmount(&mut self) {
715        self.modifier_chain.chain_mut().detach_nodes();
716    }
717
718    fn set_node_id(&mut self, id: NodeId) {
719        // Delegate to inherent method to ensure proper registration and chain updates
720        LayoutNode::set_node_id(self, id);
721    }
722
723    fn insert_child(&mut self, child: NodeId) {
724        if is_virtual_node(child) {
725            let count = self.virtual_children_count.get();
726            self.virtual_children_count.set(count + 1);
727        }
728        self.children.insert(child);
729        self.cache.clear();
730        self.mark_needs_measure();
731    }
732
733    fn remove_child(&mut self, child: NodeId) {
734        if self.children.shift_remove(&child) {
735            if is_virtual_node(child) {
736                let count = self.virtual_children_count.get();
737                if count > 0 {
738                    self.virtual_children_count.set(count - 1);
739                }
740            }
741            self.cache.clear();
742            self.mark_needs_measure();
743        }
744    }
745
746    fn move_child(&mut self, from: usize, to: usize) {
747        if from == to || from >= self.children.len() {
748            return;
749        }
750        let mut ordered: Vec<NodeId> = self.children.iter().copied().collect();
751        let child = ordered.remove(from);
752        let target = to.min(ordered.len());
753        ordered.insert(target, child);
754        self.children.clear();
755        for id in ordered {
756            self.children.insert(id);
757        }
758        self.cache.clear();
759        self.mark_needs_measure();
760        // Parent doesn't change when moving within same parent
761    }
762
763    fn update_children(&mut self, children: &[NodeId]) {
764        self.children.clear();
765        for &child in children {
766            self.children.insert(child);
767        }
768        self.cache.clear();
769        self.mark_needs_measure();
770    }
771
772    fn children(&self) -> Vec<NodeId> {
773        self.children.iter().copied().collect()
774    }
775
776    fn on_attached_to_parent(&mut self, parent: NodeId) {
777        self.set_parent(parent);
778    }
779
780    fn on_removed_from_parent(&mut self) {
781        self.clear_parent();
782    }
783
784    fn parent(&self) -> Option<NodeId> {
785        self.parent.get()
786    }
787
788    fn mark_needs_layout(&self) {
789        self.needs_layout.set(true);
790    }
791
792    fn needs_layout(&self) -> bool {
793        self.needs_layout.get()
794    }
795
796    fn mark_needs_measure(&self) {
797        self.needs_measure.set(true);
798        self.needs_layout.set(true);
799    }
800
801    fn needs_measure(&self) -> bool {
802        self.needs_measure.get()
803    }
804
805    fn mark_needs_semantics(&self) {
806        self.needs_semantics.set(true);
807    }
808
809    fn needs_semantics(&self) -> bool {
810        self.needs_semantics.get()
811    }
812
813    /// Minimal parent setter for dirty flag bubbling.
814    /// Only sets the parent Cell without triggering registry updates.
815    /// This is used during SubcomposeLayout measurement where we need parent
816    /// pointers for bubble_measure_dirty but don't want full attachment side effects.
817    fn set_parent_for_bubbling(&mut self, parent: NodeId) {
818        self.parent.set(Some(parent));
819    }
820}
821
822impl Drop for LayoutNode {
823    fn drop(&mut self) {
824        if let Some(id) = self.id.get() {
825            unregister_layout_node(id);
826        }
827    }
828}
829
830thread_local! {
831    static LAYOUT_NODE_REGISTRY: RefCell<HashMap<NodeId, LayoutNodeRegistryEntry>> =
832        RefCell::new(HashMap::new());
833    // Start at a high value to avoid conflicts with SlotTable IDs (which start low).
834    // We use a value compatible with 32-bit (WASM) usize to prevent truncation issues.
835    // 0xC0000000 is ~3.2 billion, leaving ~1 billion IDs before overflow.
836    static VIRTUAL_NODE_ID_COUNTER: std::sync::atomic::AtomicUsize = const { std::sync::atomic::AtomicUsize::new(0xC0000000) };
837}
838
839struct LayoutNodeRegistryEntry {
840    parent: Option<NodeId>,
841    modifier_child_capabilities: NodeCapabilities,
842    modifier_locals: ModifierLocalsHandle,
843    is_virtual: bool,
844}
845
846pub(crate) fn register_layout_node(id: NodeId, node: &LayoutNode) {
847    LAYOUT_NODE_REGISTRY.with(|registry| {
848        registry.borrow_mut().insert(
849            id,
850            LayoutNodeRegistryEntry {
851                parent: node.parent(),
852                modifier_child_capabilities: node.modifier_child_capabilities(),
853                modifier_locals: node.modifier_locals_handle(),
854                is_virtual: node.is_virtual(),
855            },
856        );
857    });
858}
859
860pub(crate) fn unregister_layout_node(id: NodeId) {
861    LAYOUT_NODE_REGISTRY.with(|registry| {
862        registry.borrow_mut().remove(&id);
863    });
864}
865
866pub(crate) fn is_virtual_node(id: NodeId) -> bool {
867    LAYOUT_NODE_REGISTRY.with(|registry| {
868        registry
869            .borrow()
870            .get(&id)
871            .map(|entry| entry.is_virtual)
872            .unwrap_or(false)
873    })
874}
875
876pub(crate) fn allocate_virtual_node_id() -> NodeId {
877    use std::sync::atomic::Ordering;
878    // Allocate IDs from a high range to avoid conflict with SlotTable IDs.
879    // Thread-local counter avoids cross-thread contention (WASM is single-threaded anyway).
880    VIRTUAL_NODE_ID_COUNTER.with(|counter| counter.fetch_add(1, Ordering::Relaxed))
881}
882
883fn resolve_modifier_local_from_parent_chain(
884    start: Option<NodeId>,
885    token: ModifierLocalToken,
886) -> Option<ResolvedModifierLocal> {
887    let mut current = start;
888    while let Some(parent_id) = current {
889        let (next_parent, resolved) = LAYOUT_NODE_REGISTRY.with(|registry| {
890            let registry = registry.borrow();
891            if let Some(entry) = registry.get(&parent_id) {
892                let resolved = if entry
893                    .modifier_child_capabilities
894                    .contains(NodeCapabilities::MODIFIER_LOCALS)
895                {
896                    entry
897                        .modifier_locals
898                        .borrow()
899                        .resolve(token)
900                        .map(|value| value.with_source(ModifierLocalSource::Ancestor))
901                } else {
902                    None
903                };
904                (entry.parent, resolved)
905            } else {
906                (None, None)
907            }
908        });
909        if let Some(value) = resolved {
910            return Some(value);
911        }
912        current = next_parent;
913    }
914    None
915}
916
917#[cfg(test)]
918mod tests {
919    use super::*;
920    use cranpose_ui_graphics::Size as GeometrySize;
921    use cranpose_ui_layout::{Measurable, MeasureResult};
922    use std::rc::Rc;
923
924    #[derive(Default)]
925    struct TestMeasurePolicy;
926
927    impl MeasurePolicy for TestMeasurePolicy {
928        fn measure(
929            &self,
930            _measurables: &[Box<dyn Measurable>],
931            _constraints: Constraints,
932        ) -> MeasureResult {
933            MeasureResult::new(
934                GeometrySize {
935                    width: 0.0,
936                    height: 0.0,
937                },
938                Vec::new(),
939            )
940        }
941
942        fn min_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
943            0.0
944        }
945
946        fn max_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
947            0.0
948        }
949
950        fn min_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
951            0.0
952        }
953
954        fn max_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
955            0.0
956        }
957    }
958
959    fn fresh_node() -> LayoutNode {
960        LayoutNode::new(Modifier::empty(), Rc::new(TestMeasurePolicy))
961    }
962
963    fn invalidation(kind: InvalidationKind) -> ModifierInvalidation {
964        ModifierInvalidation::new(kind, NodeCapabilities::for_invalidation(kind))
965    }
966
967    #[test]
968    fn layout_invalidation_requires_layout_capability() {
969        let mut node = fresh_node();
970        node.clear_needs_measure();
971        node.clear_needs_layout();
972        node.modifier_capabilities = NodeCapabilities::DRAW;
973        node.modifier_child_capabilities = node.modifier_capabilities;
974
975        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Layout)]);
976
977        assert!(!node.needs_measure());
978        assert!(!node.needs_layout());
979    }
980
981    #[test]
982    fn semantics_configuration_reflects_modifier_state() {
983        let mut node = fresh_node();
984        node.set_modifier(Modifier::empty().semantics(|config| {
985            config.content_description = Some("greeting".into());
986            config.is_clickable = true;
987        }));
988
989        let config = node
990            .semantics_configuration()
991            .expect("expected semantics configuration");
992        assert_eq!(config.content_description.as_deref(), Some("greeting"));
993        assert!(config.is_clickable);
994    }
995
996    #[test]
997    fn layout_invalidation_marks_flags_when_capability_present() {
998        let mut node = fresh_node();
999        node.clear_needs_measure();
1000        node.clear_needs_layout();
1001        node.modifier_capabilities = NodeCapabilities::LAYOUT;
1002        node.modifier_child_capabilities = node.modifier_capabilities;
1003
1004        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Layout)]);
1005
1006        assert!(node.needs_measure());
1007        assert!(node.needs_layout());
1008    }
1009
1010    #[test]
1011    fn draw_invalidation_marks_redraw_flag_when_capable() {
1012        let mut node = fresh_node();
1013        node.clear_needs_measure();
1014        node.clear_needs_layout();
1015        node.modifier_capabilities = NodeCapabilities::DRAW;
1016        node.modifier_child_capabilities = node.modifier_capabilities;
1017
1018        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Draw)]);
1019
1020        assert!(node.needs_redraw());
1021        assert!(!node.needs_layout());
1022    }
1023
1024    #[test]
1025    fn semantics_invalidation_sets_semantics_flag_only() {
1026        let mut node = fresh_node();
1027        node.clear_needs_measure();
1028        node.clear_needs_layout();
1029        node.clear_needs_semantics();
1030        node.modifier_capabilities = NodeCapabilities::SEMANTICS;
1031        node.modifier_child_capabilities = node.modifier_capabilities;
1032
1033        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Semantics)]);
1034
1035        assert!(node.needs_semantics());
1036        assert!(!node.needs_measure());
1037        assert!(!node.needs_layout());
1038    }
1039
1040    #[test]
1041    fn pointer_invalidation_requires_pointer_capability() {
1042        let mut node = fresh_node();
1043        node.clear_needs_pointer_pass();
1044        node.modifier_capabilities = NodeCapabilities::DRAW;
1045        node.modifier_child_capabilities = node.modifier_capabilities;
1046        // Note: We don't assert on global take_pointer_invalidation() because
1047        // it's shared across tests running in parallel and causes flakiness.
1048        // The node's local state is sufficient to verify correct dispatch behavior.
1049
1050        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::PointerInput)]);
1051
1052        assert!(!node.needs_pointer_pass());
1053    }
1054
1055    #[test]
1056    fn pointer_invalidation_marks_flag_and_requests_queue() {
1057        let mut node = fresh_node();
1058        node.clear_needs_pointer_pass();
1059        node.modifier_capabilities = NodeCapabilities::POINTER_INPUT;
1060        node.modifier_child_capabilities = node.modifier_capabilities;
1061        // Note: We don't assert on global take_pointer_invalidation() because
1062        // it's shared across tests running in parallel and causes flakiness.
1063        // The node's local state is sufficient to verify correct dispatch behavior.
1064
1065        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::PointerInput)]);
1066
1067        assert!(node.needs_pointer_pass());
1068    }
1069
1070    #[test]
1071    fn focus_invalidation_requires_focus_capability() {
1072        let mut node = fresh_node();
1073        node.clear_needs_focus_sync();
1074        node.modifier_capabilities = NodeCapabilities::DRAW;
1075        node.modifier_child_capabilities = node.modifier_capabilities;
1076        crate::take_focus_invalidation();
1077
1078        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Focus)]);
1079
1080        assert!(!node.needs_focus_sync());
1081        assert!(!crate::take_focus_invalidation());
1082    }
1083
1084    #[test]
1085    fn focus_invalidation_marks_flag_and_requests_queue() {
1086        let mut node = fresh_node();
1087        node.clear_needs_focus_sync();
1088        node.modifier_capabilities = NodeCapabilities::FOCUS;
1089        node.modifier_child_capabilities = node.modifier_capabilities;
1090        crate::take_focus_invalidation();
1091
1092        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Focus)]);
1093
1094        assert!(node.needs_focus_sync());
1095        assert!(crate::take_focus_invalidation());
1096    }
1097
1098    #[test]
1099    fn set_modifier_marks_semantics_dirty() {
1100        let mut node = fresh_node();
1101        node.clear_needs_semantics();
1102        node.set_modifier(Modifier::empty().semantics(|config| {
1103            config.is_clickable = true;
1104        }));
1105
1106        assert!(node.needs_semantics());
1107    }
1108
1109    #[test]
1110    fn modifier_child_capabilities_reflect_chain_head() {
1111        let mut node = fresh_node();
1112        node.set_modifier(Modifier::empty().padding(4.0));
1113        assert!(
1114            node.modifier_child_capabilities()
1115                .contains(NodeCapabilities::LAYOUT),
1116            "padding should introduce layout capability"
1117        );
1118    }
1119}