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