Skip to main content

cranpose_ui/widgets/nodes/
layout_node.rs

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