Skip to main content

cranpose_ui/widgets/nodes/
layout_node.rs

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