cranpose_ui/widgets/nodes/
layout_node.rs

1use crate::{
2    layout::MeasuredNode,
3    modifier::{
4        collect_modifier_slices, Modifier, ModifierChainHandle, ModifierLocalSource,
5        ModifierLocalToken, ModifierLocalsHandle, ModifierNodeSlices, ResolvedModifierLocal,
6        ResolvedModifiers,
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#[derive(Clone)]
21struct MeasurementCacheEntry {
22    constraints: Constraints,
23    measured: Rc<MeasuredNode>,
24}
25
26#[derive(Clone, Copy, Debug)]
27pub enum IntrinsicKind {
28    MinWidth(f32),
29    MaxWidth(f32),
30    MinHeight(f32),
31    MaxHeight(f32),
32}
33
34impl IntrinsicKind {
35    fn discriminant(&self) -> u8 {
36        match self {
37            IntrinsicKind::MinWidth(_) => 0,
38            IntrinsicKind::MaxWidth(_) => 1,
39            IntrinsicKind::MinHeight(_) => 2,
40            IntrinsicKind::MaxHeight(_) => 3,
41        }
42    }
43
44    fn value_bits(&self) -> u32 {
45        match self {
46            IntrinsicKind::MinWidth(value)
47            | IntrinsicKind::MaxWidth(value)
48            | IntrinsicKind::MinHeight(value)
49            | IntrinsicKind::MaxHeight(value) => value.to_bits(),
50        }
51    }
52}
53
54impl PartialEq for IntrinsicKind {
55    fn eq(&self, other: &Self) -> bool {
56        self.discriminant() == other.discriminant() && self.value_bits() == other.value_bits()
57    }
58}
59
60impl Eq for IntrinsicKind {}
61
62impl Hash for IntrinsicKind {
63    fn hash<H: Hasher>(&self, state: &mut H) {
64        self.discriminant().hash(state);
65        self.value_bits().hash(state);
66    }
67}
68
69#[derive(Default)]
70struct NodeCacheState {
71    epoch: u64,
72    measurements: Vec<MeasurementCacheEntry>,
73    intrinsics: Vec<(IntrinsicKind, f32)>,
74}
75
76#[derive(Clone, Default)]
77pub(crate) struct LayoutNodeCacheHandles {
78    state: Rc<RefCell<NodeCacheState>>,
79}
80
81impl LayoutNodeCacheHandles {
82    pub(crate) fn clear(&self) {
83        let mut state = self.state.borrow_mut();
84        state.measurements.clear();
85        state.intrinsics.clear();
86        state.epoch = 0;
87    }
88
89    pub(crate) fn activate(&self, epoch: u64) {
90        let mut state = self.state.borrow_mut();
91        if state.epoch != epoch {
92            state.measurements.clear();
93            state.intrinsics.clear();
94            state.epoch = epoch;
95        }
96    }
97
98    pub(crate) fn epoch(&self) -> u64 {
99        self.state.borrow().epoch
100    }
101
102    pub(crate) fn get_measurement(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
103        let state = self.state.borrow();
104        state
105            .measurements
106            .iter()
107            .find(|entry| entry.constraints == constraints)
108            .map(|entry| Rc::clone(&entry.measured))
109    }
110
111    pub(crate) fn store_measurement(&self, constraints: Constraints, measured: Rc<MeasuredNode>) {
112        let mut state = self.state.borrow_mut();
113        if let Some(entry) = state
114            .measurements
115            .iter_mut()
116            .find(|entry| entry.constraints == constraints)
117        {
118            entry.measured = measured;
119        } else {
120            state.measurements.push(MeasurementCacheEntry {
121                constraints,
122                measured,
123            });
124        }
125    }
126
127    pub(crate) fn get_intrinsic(&self, kind: &IntrinsicKind) -> Option<f32> {
128        let state = self.state.borrow();
129        state
130            .intrinsics
131            .iter()
132            .find(|(stored_kind, _)| stored_kind == kind)
133            .map(|(_, value)| *value)
134    }
135
136    pub(crate) fn store_intrinsic(&self, kind: IntrinsicKind, value: f32) {
137        let mut state = self.state.borrow_mut();
138        if let Some((_, existing)) = state
139            .intrinsics
140            .iter_mut()
141            .find(|(stored_kind, _)| stored_kind == &kind)
142        {
143            *existing = value;
144        } else {
145            state.intrinsics.push((kind, value));
146        }
147    }
148}
149
150pub struct LayoutNode {
151    pub modifier: Modifier,
152    modifier_chain: ModifierChainHandle,
153    resolved_modifiers: ResolvedModifiers,
154    modifier_capabilities: NodeCapabilities,
155    modifier_child_capabilities: NodeCapabilities,
156    pub measure_policy: Rc<dyn MeasurePolicy>,
157    /// The actual children of this node (folded view - includes virtual nodes as-is)
158    pub children: IndexSet<NodeId>,
159    cache: LayoutNodeCacheHandles,
160    // Dirty flags for selective measure/layout/render
161    needs_measure: Cell<bool>,
162    needs_layout: Cell<bool>,
163    needs_semantics: Cell<bool>,
164    needs_redraw: Cell<bool>,
165    needs_pointer_pass: Cell<bool>,
166    needs_focus_sync: Cell<bool>,
167    /// Parent for dirty flag bubbling (skips virtual nodes)
168    parent: Cell<Option<NodeId>>,
169    /// Direct parent in the tree (may be virtual)
170    folded_parent: Cell<Option<NodeId>>,
171    // Node's own ID (set by applier after creation)
172    id: Cell<Option<NodeId>>,
173    debug_modifiers: Cell<bool>,
174    /// Virtual node flag - virtual nodes are transparent containers for subcomposition
175    /// Their children are flattened into the parent's children list for measurement
176    is_virtual: bool,
177    /// Count of virtual children (for lazy unfolded children computation)
178    virtual_children_count: Cell<usize>,
179}
180
181impl LayoutNode {
182    pub fn new(modifier: Modifier, measure_policy: Rc<dyn MeasurePolicy>) -> Self {
183        Self::new_with_virtual(modifier, measure_policy, false)
184    }
185
186    /// Create a virtual LayoutNode for subcomposition slot containers.
187    /// Virtual nodes are transparent - their children are flattened into parent's children list.
188    pub fn new_virtual() -> Self {
189        Self::new_with_virtual(
190            Modifier::empty(),
191            Rc::new(crate::layout::policies::EmptyMeasurePolicy),
192            true,
193        )
194    }
195
196    fn new_with_virtual(
197        modifier: Modifier,
198        measure_policy: Rc<dyn MeasurePolicy>,
199        is_virtual: bool,
200    ) -> Self {
201        let mut node = Self {
202            modifier: Modifier::empty(),
203            modifier_chain: ModifierChainHandle::new(),
204            resolved_modifiers: ResolvedModifiers::default(),
205            modifier_capabilities: NodeCapabilities::default(),
206            modifier_child_capabilities: NodeCapabilities::default(),
207            measure_policy,
208            children: IndexSet::new(),
209            cache: LayoutNodeCacheHandles::default(),
210            needs_measure: Cell::new(true), // New nodes need initial measure
211            needs_layout: Cell::new(true),  // New nodes need initial layout
212            needs_semantics: Cell::new(true), // Semantics snapshot needs initial build
213            needs_redraw: Cell::new(true),  // First render should draw the node
214            needs_pointer_pass: Cell::new(false),
215            needs_focus_sync: Cell::new(false),
216            parent: Cell::new(None),        // Non-virtual parent for bubbling
217            folded_parent: Cell::new(None), // Direct parent (may be virtual)
218            id: Cell::new(None),            // ID set by applier after creation
219            debug_modifiers: Cell::new(false),
220            is_virtual,
221            virtual_children_count: Cell::new(0),
222        };
223        if !is_virtual {
224            node.set_modifier(modifier);
225        }
226        node
227    }
228
229    pub fn set_modifier(&mut self, modifier: Modifier) {
230        // Always sync the modifier chain because element equality is used for node
231        // matching/reuse, not for skipping updates. Closures may capture updated
232        // state values that need to be passed to nodes even when the Modifier
233        // compares as equal. This matches Jetpack Compose where update() is always
234        // called on matched nodes.
235        let modifier_changed = self.modifier != modifier;
236        self.modifier = modifier;
237        self.sync_modifier_chain();
238        if modifier_changed {
239            self.cache.clear();
240            self.mark_needs_measure();
241            self.request_semantics_update();
242        }
243    }
244
245    fn sync_modifier_chain(&mut self) {
246        let start_parent = self.parent();
247        let mut resolver = move |token: ModifierLocalToken| {
248            resolve_modifier_local_from_parent_chain(start_parent, token)
249        };
250        self.modifier_chain
251            .set_debug_logging(self.debug_modifiers.get());
252        self.modifier_chain.set_node_id(self.id.get());
253        let modifier_local_invalidations = self
254            .modifier_chain
255            .update_with_resolver(&self.modifier, &mut resolver);
256        self.resolved_modifiers = self.modifier_chain.resolved_modifiers();
257        self.modifier_capabilities = self.modifier_chain.capabilities();
258        self.modifier_child_capabilities = self.modifier_chain.aggregate_child_capabilities();
259        let mut invalidations = self.modifier_chain.take_invalidations();
260        invalidations.extend(modifier_local_invalidations);
261        self.dispatch_modifier_invalidations(&invalidations);
262        self.refresh_registry_state();
263    }
264
265    fn dispatch_modifier_invalidations(&self, invalidations: &[ModifierInvalidation]) {
266        for invalidation in invalidations {
267            match invalidation.kind() {
268                InvalidationKind::Layout => {
269                    if self.has_layout_modifier_nodes() {
270                        self.mark_needs_measure();
271                        if let Some(id) = self.id.get() {
272                            crate::schedule_layout_repass(id);
273                        }
274                    }
275                }
276                InvalidationKind::Draw => {
277                    if self.has_draw_modifier_nodes() {
278                        self.mark_needs_redraw();
279                    }
280                }
281                InvalidationKind::PointerInput => {
282                    if self.has_pointer_input_modifier_nodes() {
283                        self.mark_needs_pointer_pass();
284                        crate::request_pointer_invalidation();
285                        // Schedule pointer repass for this node
286                        if let Some(id) = self.id.get() {
287                            crate::schedule_pointer_repass(id);
288                        }
289                    }
290                }
291                InvalidationKind::Semantics => {
292                    self.request_semantics_update();
293                }
294                InvalidationKind::Focus => {
295                    if self.has_focus_modifier_nodes() {
296                        self.mark_needs_focus_sync();
297                        crate::request_focus_invalidation();
298                        // Schedule focus invalidation for this node
299                        if let Some(id) = self.id.get() {
300                            crate::schedule_focus_invalidation(id);
301                        }
302                    }
303                }
304            }
305        }
306    }
307
308    pub fn set_measure_policy(&mut self, policy: Rc<dyn MeasurePolicy>) {
309        // Only mark dirty if policy actually changed (pointer comparison)
310        if !Rc::ptr_eq(&self.measure_policy, &policy) {
311            self.measure_policy = policy;
312            self.cache.clear();
313            self.mark_needs_measure();
314        }
315    }
316
317    /// Mark this node as needing measure. Also marks it as needing layout.
318    pub fn mark_needs_measure(&self) {
319        self.needs_measure.set(true);
320        self.needs_layout.set(true);
321    }
322
323    /// Mark this node as needing layout (but not necessarily measure).
324    pub fn mark_needs_layout(&self) {
325        self.needs_layout.set(true);
326    }
327
328    /// Mark this node as needing redraw without forcing measure/layout.
329    pub fn mark_needs_redraw(&self) {
330        self.needs_redraw.set(true);
331        if let Some(id) = self.id.get() {
332            crate::schedule_draw_repass(id);
333        }
334        crate::request_render_invalidation();
335    }
336
337    /// Check if this node needs measure.
338    pub fn needs_measure(&self) -> bool {
339        self.needs_measure.get()
340    }
341
342    /// Check if this node needs layout.
343    pub fn needs_layout(&self) -> bool {
344        self.needs_layout.get()
345    }
346
347    /// Mark this node as needing semantics recomputation.
348    pub fn mark_needs_semantics(&self) {
349        self.needs_semantics.set(true);
350    }
351
352    /// Clear the semantics dirty flag after rebuilding semantics.
353    pub(crate) fn clear_needs_semantics(&self) {
354        self.needs_semantics.set(false);
355    }
356
357    /// Returns true when semantics need to be recomputed.
358    pub fn needs_semantics(&self) -> bool {
359        self.needs_semantics.get()
360    }
361
362    /// Returns true when this node requested a redraw since the last render pass.
363    pub fn needs_redraw(&self) -> bool {
364        self.needs_redraw.get()
365    }
366
367    pub fn clear_needs_redraw(&self) {
368        self.needs_redraw.set(false);
369    }
370
371    fn request_semantics_update(&self) {
372        let already_dirty = self.needs_semantics.replace(true);
373        if already_dirty {
374            return;
375        }
376
377        if let Some(id) = self.id.get() {
378            cranpose_core::queue_semantics_invalidation(id);
379        }
380    }
381
382    /// Clear the measure dirty flag after measuring.
383    pub(crate) fn clear_needs_measure(&self) {
384        self.needs_measure.set(false);
385    }
386
387    /// Clear the layout dirty flag after laying out.
388    pub(crate) fn clear_needs_layout(&self) {
389        self.needs_layout.set(false);
390    }
391
392    /// Marks this node as needing a fresh pointer-input pass.
393    pub fn mark_needs_pointer_pass(&self) {
394        self.needs_pointer_pass.set(true);
395    }
396
397    /// Returns true when pointer-input state needs to be recomputed.
398    pub fn needs_pointer_pass(&self) -> bool {
399        self.needs_pointer_pass.get()
400    }
401
402    /// Clears the pointer-input dirty flag after hosts service it.
403    pub fn clear_needs_pointer_pass(&self) {
404        self.needs_pointer_pass.set(false);
405    }
406
407    /// Marks this node as needing a focus synchronization.
408    pub fn mark_needs_focus_sync(&self) {
409        self.needs_focus_sync.set(true);
410    }
411
412    /// Returns true when focus state needs to be synchronized.
413    pub fn needs_focus_sync(&self) -> bool {
414        self.needs_focus_sync.get()
415    }
416
417    /// Clears the focus dirty flag after the focus manager processes it.
418    pub fn clear_needs_focus_sync(&self) {
419        self.needs_focus_sync.set(false);
420    }
421
422    /// Set this node's ID (called by applier after creation).
423    pub fn set_node_id(&mut self, id: NodeId) {
424        if let Some(existing) = self.id.replace(Some(id)) {
425            unregister_layout_node(existing);
426        }
427        register_layout_node(id, self);
428        self.refresh_registry_state();
429
430        // Propagate the ID to the modifier chain. This triggers a lifecycle update
431        // for nodes that depend on the node ID for invalidation (e.g., ScrollNode).
432        self.modifier_chain.set_node_id(Some(id));
433    }
434
435    /// Get this node's ID.
436    pub fn node_id(&self) -> Option<NodeId> {
437        self.id.get()
438    }
439
440    /// Set this node's parent (called when node is added as child).
441    /// Sets both folded_parent (direct) and parent (first non-virtual ancestor for bubbling).
442    pub fn set_parent(&self, parent: NodeId) {
443        self.folded_parent.set(Some(parent));
444        // For now, parent = folded_parent. Virtual parent skipping requires applier access.
445        // The actual virtual-skipping happens in bubble_measure_dirty via applier traversal.
446        self.parent.set(Some(parent));
447        self.refresh_registry_state();
448    }
449
450    /// Clear this node's parent (called when node is removed from parent).
451    pub fn clear_parent(&self) {
452        self.folded_parent.set(None);
453        self.parent.set(None);
454        self.refresh_registry_state();
455    }
456
457    /// Get this node's parent for dirty flag bubbling (may skip virtual nodes).
458    pub fn parent(&self) -> Option<NodeId> {
459        self.parent.get()
460    }
461
462    /// Get this node's direct parent (may be a virtual node).
463    pub fn folded_parent(&self) -> Option<NodeId> {
464        self.folded_parent.get()
465    }
466
467    /// Returns true if this is a virtual node (transparent container for subcomposition).
468    pub fn is_virtual(&self) -> bool {
469        self.is_virtual
470    }
471
472    pub(crate) fn cache_handles(&self) -> LayoutNodeCacheHandles {
473        self.cache.clone()
474    }
475
476    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
477        self.resolved_modifiers
478    }
479
480    pub fn modifier_capabilities(&self) -> NodeCapabilities {
481        self.modifier_capabilities
482    }
483
484    pub fn modifier_child_capabilities(&self) -> NodeCapabilities {
485        self.modifier_child_capabilities
486    }
487
488    pub fn set_debug_modifiers(&mut self, enabled: bool) {
489        self.debug_modifiers.set(enabled);
490        self.modifier_chain.set_debug_logging(enabled);
491    }
492
493    pub fn debug_modifiers_enabled(&self) -> bool {
494        self.debug_modifiers.get()
495    }
496
497    pub fn modifier_locals_handle(&self) -> ModifierLocalsHandle {
498        self.modifier_chain.modifier_locals_handle()
499    }
500
501    pub fn has_layout_modifier_nodes(&self) -> bool {
502        self.modifier_capabilities
503            .contains(NodeCapabilities::LAYOUT)
504    }
505
506    pub fn has_draw_modifier_nodes(&self) -> bool {
507        self.modifier_capabilities.contains(NodeCapabilities::DRAW)
508    }
509
510    pub fn has_pointer_input_modifier_nodes(&self) -> bool {
511        self.modifier_capabilities
512            .contains(NodeCapabilities::POINTER_INPUT)
513    }
514
515    pub fn has_semantics_modifier_nodes(&self) -> bool {
516        self.modifier_capabilities
517            .contains(NodeCapabilities::SEMANTICS)
518    }
519
520    pub fn has_focus_modifier_nodes(&self) -> bool {
521        self.modifier_capabilities.contains(NodeCapabilities::FOCUS)
522    }
523
524    fn refresh_registry_state(&self) {
525        if let Some(id) = self.id.get() {
526            let parent = self.parent();
527            let capabilities = self.modifier_child_capabilities();
528            let modifier_locals = self.modifier_locals_handle();
529            LAYOUT_NODE_REGISTRY.with(|registry| {
530                if let Some(entry) = registry.borrow_mut().get_mut(&id) {
531                    entry.parent = parent;
532                    entry.modifier_child_capabilities = capabilities;
533                    entry.modifier_locals = modifier_locals;
534                }
535            });
536        }
537    }
538
539    pub fn modifier_slices_snapshot(&self) -> ModifierNodeSlices {
540        collect_modifier_slices(self.modifier_chain.chain())
541    }
542
543    pub fn semantics_configuration(&self) -> Option<SemanticsConfiguration> {
544        crate::modifier::collect_semantics_from_chain(self.modifier_chain.chain())
545    }
546
547    /// Returns a reference to the modifier chain for layout/draw pipeline integration.
548    pub(crate) fn modifier_chain(&self) -> &ModifierChainHandle {
549        &self.modifier_chain
550    }
551
552    /// Access the text field modifier node (if present) with a mutable callback.
553    ///
554    /// This is used for keyboard event dispatch to text fields.
555    /// Returns `None` if no text field modifier is found in the chain.
556    pub fn with_text_field_modifier_mut<R>(
557        &mut self,
558        f: impl FnMut(&mut crate::TextFieldModifierNode) -> R,
559    ) -> Option<R> {
560        self.modifier_chain.with_text_field_modifier_mut(f)
561    }
562}
563impl Clone for LayoutNode {
564    fn clone(&self) -> Self {
565        let mut node = Self {
566            modifier: self.modifier.clone(),
567            modifier_chain: ModifierChainHandle::new(),
568            resolved_modifiers: ResolvedModifiers::default(),
569            modifier_capabilities: self.modifier_capabilities,
570            modifier_child_capabilities: self.modifier_child_capabilities,
571            measure_policy: self.measure_policy.clone(),
572            children: self.children.clone(),
573            cache: self.cache.clone(),
574            needs_measure: Cell::new(self.needs_measure.get()),
575            needs_layout: Cell::new(self.needs_layout.get()),
576            needs_semantics: Cell::new(self.needs_semantics.get()),
577            needs_redraw: Cell::new(self.needs_redraw.get()),
578            needs_pointer_pass: Cell::new(self.needs_pointer_pass.get()),
579            needs_focus_sync: Cell::new(self.needs_focus_sync.get()),
580            parent: Cell::new(self.parent.get()),
581            folded_parent: Cell::new(self.folded_parent.get()),
582            id: Cell::new(None),
583            debug_modifiers: Cell::new(self.debug_modifiers.get()),
584            is_virtual: self.is_virtual,
585            virtual_children_count: Cell::new(self.virtual_children_count.get()),
586        };
587        node.sync_modifier_chain();
588        node
589    }
590}
591
592impl Node for LayoutNode {
593    fn mount(&mut self) {
594        let (chain, mut context) = self.modifier_chain.chain_and_context_mut();
595        chain.repair_chain();
596        chain.attach_nodes(&mut *context);
597    }
598
599    fn unmount(&mut self) {
600        self.modifier_chain.chain_mut().detach_nodes();
601    }
602
603    fn set_node_id(&mut self, id: NodeId) {
604        // Delegate to inherent method to ensure proper registration and chain updates
605        LayoutNode::set_node_id(self, id);
606    }
607
608    fn insert_child(&mut self, child: NodeId) {
609        if is_virtual_node(child) {
610            let count = self.virtual_children_count.get();
611            self.virtual_children_count.set(count + 1);
612        }
613        self.children.insert(child);
614        self.cache.clear();
615        self.mark_needs_measure();
616    }
617
618    fn remove_child(&mut self, child: NodeId) {
619        if self.children.shift_remove(&child) {
620            if is_virtual_node(child) {
621                let count = self.virtual_children_count.get();
622                if count > 0 {
623                    self.virtual_children_count.set(count - 1);
624                }
625            }
626            self.cache.clear();
627            self.mark_needs_measure();
628        }
629    }
630
631    fn move_child(&mut self, from: usize, to: usize) {
632        if from == to || from >= self.children.len() {
633            return;
634        }
635        let mut ordered: Vec<NodeId> = self.children.iter().copied().collect();
636        let child = ordered.remove(from);
637        let target = to.min(ordered.len());
638        ordered.insert(target, child);
639        self.children.clear();
640        for id in ordered {
641            self.children.insert(id);
642        }
643        self.cache.clear();
644        self.mark_needs_measure();
645        // Parent doesn't change when moving within same parent
646    }
647
648    fn update_children(&mut self, children: &[NodeId]) {
649        self.children.clear();
650        for &child in children {
651            self.children.insert(child);
652        }
653        self.cache.clear();
654        self.mark_needs_measure();
655    }
656
657    fn children(&self) -> Vec<NodeId> {
658        self.children.iter().copied().collect()
659    }
660
661    fn on_attached_to_parent(&mut self, parent: NodeId) {
662        self.set_parent(parent);
663    }
664
665    fn on_removed_from_parent(&mut self) {
666        self.clear_parent();
667    }
668
669    fn parent(&self) -> Option<NodeId> {
670        self.parent.get()
671    }
672
673    fn mark_needs_layout(&self) {
674        self.needs_layout.set(true);
675    }
676
677    fn needs_layout(&self) -> bool {
678        self.needs_layout.get()
679    }
680
681    fn mark_needs_measure(&self) {
682        self.needs_measure.set(true);
683        self.needs_layout.set(true);
684    }
685
686    fn needs_measure(&self) -> bool {
687        self.needs_measure.get()
688    }
689
690    fn mark_needs_semantics(&self) {
691        self.needs_semantics.set(true);
692    }
693
694    fn needs_semantics(&self) -> bool {
695        self.needs_semantics.get()
696    }
697
698    /// Minimal parent setter for dirty flag bubbling.
699    /// Only sets the parent Cell without triggering registry updates.
700    /// This is used during SubcomposeLayout measurement where we need parent
701    /// pointers for bubble_measure_dirty but don't want full attachment side effects.
702    fn set_parent_for_bubbling(&mut self, parent: NodeId) {
703        self.parent.set(Some(parent));
704    }
705}
706
707impl Drop for LayoutNode {
708    fn drop(&mut self) {
709        if let Some(id) = self.id.get() {
710            unregister_layout_node(id);
711        }
712    }
713}
714
715thread_local! {
716    static LAYOUT_NODE_REGISTRY: RefCell<HashMap<NodeId, LayoutNodeRegistryEntry>> =
717        RefCell::new(HashMap::new());
718    // Start at a high value to avoid conflicts with SlotTable IDs (which start low).
719    // We use a value compatible with 32-bit (WASM) usize to prevent truncation issues.
720    // 0xC0000000 is ~3.2 billion, leaving ~1 billion IDs before overflow.
721    static VIRTUAL_NODE_ID_COUNTER: std::sync::atomic::AtomicUsize = const { std::sync::atomic::AtomicUsize::new(0xC0000000) };
722}
723
724struct LayoutNodeRegistryEntry {
725    parent: Option<NodeId>,
726    modifier_child_capabilities: NodeCapabilities,
727    modifier_locals: ModifierLocalsHandle,
728    is_virtual: bool,
729}
730
731pub(crate) fn register_layout_node(id: NodeId, node: &LayoutNode) {
732    LAYOUT_NODE_REGISTRY.with(|registry| {
733        registry.borrow_mut().insert(
734            id,
735            LayoutNodeRegistryEntry {
736                parent: node.parent(),
737                modifier_child_capabilities: node.modifier_child_capabilities(),
738                modifier_locals: node.modifier_locals_handle(),
739                is_virtual: node.is_virtual(),
740            },
741        );
742    });
743}
744
745pub(crate) fn unregister_layout_node(id: NodeId) {
746    LAYOUT_NODE_REGISTRY.with(|registry| {
747        registry.borrow_mut().remove(&id);
748    });
749}
750
751pub(crate) fn is_virtual_node(id: NodeId) -> bool {
752    LAYOUT_NODE_REGISTRY.with(|registry| {
753        registry
754            .borrow()
755            .get(&id)
756            .map(|entry| entry.is_virtual)
757            .unwrap_or(false)
758    })
759}
760
761pub(crate) fn allocate_virtual_node_id() -> NodeId {
762    use std::sync::atomic::Ordering;
763    // Allocate IDs from a high range to avoid conflict with SlotTable IDs.
764    // Thread-local counter avoids cross-thread contention (WASM is single-threaded anyway).
765    VIRTUAL_NODE_ID_COUNTER.with(|counter| counter.fetch_add(1, Ordering::Relaxed))
766}
767
768fn resolve_modifier_local_from_parent_chain(
769    start: Option<NodeId>,
770    token: ModifierLocalToken,
771) -> Option<ResolvedModifierLocal> {
772    let mut current = start;
773    while let Some(parent_id) = current {
774        let (next_parent, resolved) = LAYOUT_NODE_REGISTRY.with(|registry| {
775            let registry = registry.borrow();
776            if let Some(entry) = registry.get(&parent_id) {
777                let resolved = if entry
778                    .modifier_child_capabilities
779                    .contains(NodeCapabilities::MODIFIER_LOCALS)
780                {
781                    entry
782                        .modifier_locals
783                        .borrow()
784                        .resolve(token)
785                        .map(|value| value.with_source(ModifierLocalSource::Ancestor))
786                } else {
787                    None
788                };
789                (entry.parent, resolved)
790            } else {
791                (None, None)
792            }
793        });
794        if let Some(value) = resolved {
795            return Some(value);
796        }
797        current = next_parent;
798    }
799    None
800}
801
802#[cfg(test)]
803mod tests {
804    use super::*;
805    use cranpose_ui_graphics::Size as GeometrySize;
806    use cranpose_ui_layout::{Measurable, MeasureResult};
807    use std::rc::Rc;
808
809    #[derive(Default)]
810    struct TestMeasurePolicy;
811
812    impl MeasurePolicy for TestMeasurePolicy {
813        fn measure(
814            &self,
815            _measurables: &[Box<dyn Measurable>],
816            _constraints: Constraints,
817        ) -> MeasureResult {
818            MeasureResult::new(
819                GeometrySize {
820                    width: 0.0,
821                    height: 0.0,
822                },
823                Vec::new(),
824            )
825        }
826
827        fn min_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
828            0.0
829        }
830
831        fn max_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
832            0.0
833        }
834
835        fn min_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
836            0.0
837        }
838
839        fn max_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
840            0.0
841        }
842    }
843
844    fn fresh_node() -> LayoutNode {
845        LayoutNode::new(Modifier::empty(), Rc::new(TestMeasurePolicy))
846    }
847
848    fn invalidation(kind: InvalidationKind) -> ModifierInvalidation {
849        ModifierInvalidation::new(kind, NodeCapabilities::for_invalidation(kind))
850    }
851
852    #[test]
853    fn layout_invalidation_requires_layout_capability() {
854        let mut node = fresh_node();
855        node.clear_needs_measure();
856        node.clear_needs_layout();
857        node.modifier_capabilities = NodeCapabilities::DRAW;
858        node.modifier_child_capabilities = node.modifier_capabilities;
859
860        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Layout)]);
861
862        assert!(!node.needs_measure());
863        assert!(!node.needs_layout());
864    }
865
866    #[test]
867    fn semantics_configuration_reflects_modifier_state() {
868        let mut node = fresh_node();
869        node.set_modifier(Modifier::empty().semantics(|config| {
870            config.content_description = Some("greeting".into());
871            config.is_clickable = true;
872        }));
873
874        let config = node
875            .semantics_configuration()
876            .expect("expected semantics configuration");
877        assert_eq!(config.content_description.as_deref(), Some("greeting"));
878        assert!(config.is_clickable);
879    }
880
881    #[test]
882    fn layout_invalidation_marks_flags_when_capability_present() {
883        let mut node = fresh_node();
884        node.clear_needs_measure();
885        node.clear_needs_layout();
886        node.modifier_capabilities = NodeCapabilities::LAYOUT;
887        node.modifier_child_capabilities = node.modifier_capabilities;
888
889        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Layout)]);
890
891        assert!(node.needs_measure());
892        assert!(node.needs_layout());
893    }
894
895    #[test]
896    fn draw_invalidation_marks_redraw_flag_when_capable() {
897        let mut node = fresh_node();
898        node.clear_needs_measure();
899        node.clear_needs_layout();
900        node.modifier_capabilities = NodeCapabilities::DRAW;
901        node.modifier_child_capabilities = node.modifier_capabilities;
902
903        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Draw)]);
904
905        assert!(node.needs_redraw());
906        assert!(!node.needs_layout());
907    }
908
909    #[test]
910    fn semantics_invalidation_sets_semantics_flag_only() {
911        let mut node = fresh_node();
912        node.clear_needs_measure();
913        node.clear_needs_layout();
914        node.clear_needs_semantics();
915        node.modifier_capabilities = NodeCapabilities::SEMANTICS;
916        node.modifier_child_capabilities = node.modifier_capabilities;
917
918        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Semantics)]);
919
920        assert!(node.needs_semantics());
921        assert!(!node.needs_measure());
922        assert!(!node.needs_layout());
923    }
924
925    #[test]
926    fn pointer_invalidation_requires_pointer_capability() {
927        let mut node = fresh_node();
928        node.clear_needs_pointer_pass();
929        node.modifier_capabilities = NodeCapabilities::DRAW;
930        node.modifier_child_capabilities = node.modifier_capabilities;
931        // Note: We don't assert on global take_pointer_invalidation() because
932        // it's shared across tests running in parallel and causes flakiness.
933        // The node's local state is sufficient to verify correct dispatch behavior.
934
935        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::PointerInput)]);
936
937        assert!(!node.needs_pointer_pass());
938    }
939
940    #[test]
941    fn pointer_invalidation_marks_flag_and_requests_queue() {
942        let mut node = fresh_node();
943        node.clear_needs_pointer_pass();
944        node.modifier_capabilities = NodeCapabilities::POINTER_INPUT;
945        node.modifier_child_capabilities = node.modifier_capabilities;
946        // Note: We don't assert on global take_pointer_invalidation() because
947        // it's shared across tests running in parallel and causes flakiness.
948        // The node's local state is sufficient to verify correct dispatch behavior.
949
950        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::PointerInput)]);
951
952        assert!(node.needs_pointer_pass());
953    }
954
955    #[test]
956    fn focus_invalidation_requires_focus_capability() {
957        let mut node = fresh_node();
958        node.clear_needs_focus_sync();
959        node.modifier_capabilities = NodeCapabilities::DRAW;
960        node.modifier_child_capabilities = node.modifier_capabilities;
961        crate::take_focus_invalidation();
962
963        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Focus)]);
964
965        assert!(!node.needs_focus_sync());
966        assert!(!crate::take_focus_invalidation());
967    }
968
969    #[test]
970    fn focus_invalidation_marks_flag_and_requests_queue() {
971        let mut node = fresh_node();
972        node.clear_needs_focus_sync();
973        node.modifier_capabilities = NodeCapabilities::FOCUS;
974        node.modifier_child_capabilities = node.modifier_capabilities;
975        crate::take_focus_invalidation();
976
977        node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Focus)]);
978
979        assert!(node.needs_focus_sync());
980        assert!(crate::take_focus_invalidation());
981    }
982
983    #[test]
984    fn set_modifier_marks_semantics_dirty() {
985        let mut node = fresh_node();
986        node.clear_needs_semantics();
987        node.set_modifier(Modifier::empty().semantics(|config| {
988            config.is_clickable = true;
989        }));
990
991        assert!(node.needs_semantics());
992    }
993
994    #[test]
995    fn modifier_child_capabilities_reflect_chain_head() {
996        let mut node = fresh_node();
997        node.set_modifier(Modifier::empty().padding(4.0));
998        assert!(
999            node.modifier_child_capabilities()
1000                .contains(NodeCapabilities::LAYOUT),
1001            "padding should introduce layout capability"
1002        );
1003    }
1004}