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, ModifierInvalidations, NodeCapabilities,
12 SemanticsConfiguration,
13};
14use cranpose_ui_layout::{Constraints, MeasurePolicy};
15
16#[cfg(test)]
17use crate::layout::LayoutRuntimeDebugStats;
18use crate::{
19 layout::{LayoutRuntimeState, MeasuredNode},
20 modifier::{
21 Modifier, ModifierChainHandle, ModifierLocalSource, ModifierLocalToken,
22 ModifierLocalsHandle, ModifierNodeSlices, Point, ResolvedModifierLocal, ResolvedModifiers,
23 Size,
24 },
25};
26
27#[derive(Clone, Copy)]
28enum LayoutInvalidationDispatchDiag {
29 Disabled,
30 All,
31 Node(NodeId),
32}
33
34fn layout_invalidation_dispatch_diag() -> LayoutInvalidationDispatchDiag {
35 static MODE: std::sync::OnceLock<LayoutInvalidationDispatchDiag> = std::sync::OnceLock::new();
36 *MODE.get_or_init(|| {
37 let Some(value) = std::env::var_os("CRANPOSE_LAYOUT_INVALIDATION_DISPATCH_DIAG") else {
38 return LayoutInvalidationDispatchDiag::Disabled;
39 };
40 if value == "all" {
41 return LayoutInvalidationDispatchDiag::All;
42 }
43 value.to_string_lossy().parse::<NodeId>().map_or(
44 LayoutInvalidationDispatchDiag::Disabled,
45 LayoutInvalidationDispatchDiag::Node,
46 )
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={id} invalidation={invalidation:?} curr_caps={curr_caps:?} prev_caps={prev_caps:?} modifier={modifier}"
65 );
66 }
67}
68
69struct PlacementPass {
73 id: Cell<u64>,
74 unplaced: Cell<usize>,
75}
76
77thread_local! {
78 static PLACEMENT_PASS: PlacementPass = const {
79 PlacementPass {
80 id: Cell::new(1),
81 unplaced: Cell::new(0),
82 }
83 };
84}
85
86pub(crate) fn begin_placement_pass() {
88 PLACEMENT_PASS.with(|pass| {
89 pass.id.set(pass.id.get() + 1);
90 pass.unplaced.set(0);
91 });
92}
93
94pub(crate) fn placement_pass_with_unplaced_nodes() -> Option<u64> {
96 PLACEMENT_PASS.with(|pass| (pass.unplaced.get() > 0).then(|| pass.id.get()))
97}
98
99#[derive(Clone, Copy, Default)]
105pub(crate) struct DescendantDirt {
106 pub(crate) layout: bool,
107 pub(crate) measure: bool,
108}
109
110impl DescendantDirt {
111 pub(crate) fn marked(self, measure: bool) -> Self {
112 Self {
113 layout: true,
114 measure: self.measure || measure,
115 }
116 }
117}
118
119#[derive(Clone, Debug, Default)]
120pub struct LayoutState {
121 size: Size,
122 position: Point,
123 is_placed: bool,
124 node_id: Option<NodeId>,
125 cleared_in_pass: u64,
127 content_offset: Point,
128 semantics_stamp: u64,
129}
130
131impl LayoutState {
132 pub fn size(&self) -> Size {
133 self.size
134 }
135
136 pub fn position(&self) -> Point {
137 self.position
138 }
139
140 pub fn content_offset(&self) -> Point {
143 self.content_offset
144 }
145
146 pub fn set_content_offset(&mut self, offset: Point) {
149 if self.content_offset != offset {
150 self.content_offset = offset;
151 self.note_semantics_change();
152 }
153 }
154
155 pub fn at_origin(mut self) -> Self {
159 self.position = Point::default();
160 self
161 }
162
163 pub fn is_placed(&self) -> bool {
164 self.is_placed
165 }
166
167 pub(crate) fn set_node_id(&mut self, node_id: NodeId) {
168 self.node_id = Some(node_id);
169 }
170
171 pub fn set_size(&mut self, size: Size) {
174 if self.size != size {
175 if let Some(id) = self.node_id {
176 crate::render_state::record_geometry_scene_node(id);
177 }
178 self.size = size;
179 self.note_semantics_change();
180 }
181 }
182
183 pub fn place(&mut self, position: Point) {
188 let newly_placed = !self.is_placed && !self.restore_placement();
189 let moved = self.position != position;
190 if let Some(id) = self.node_id {
191 if newly_placed {
192 crate::render_state::record_geometry_scene_node(id);
193 } else if moved {
194 crate::render_state::record_moved_scene_node(id);
195 }
196 }
197 self.position = position;
198 if moved || !self.is_placed {
199 self.is_placed = true;
200 self.note_semantics_change();
201 }
202 }
203
204 fn restore_placement(&self) -> bool {
207 PLACEMENT_PASS.with(|pass| {
208 let restored = self.cleared_in_pass == pass.id.get();
209 if restored {
210 pass.unplaced.set(pass.unplaced.get().saturating_sub(1));
211 }
212 restored
213 })
214 }
215
216 pub fn clear_placed(&mut self) {
220 if !self.is_placed {
221 return;
222 }
223 self.is_placed = false;
224 self.cleared_in_pass = PLACEMENT_PASS.with(|pass| {
225 pass.unplaced.set(pass.unplaced.get() + 1);
226 pass.id.get()
227 });
228 self.note_semantics_change();
229 }
230
231 pub(crate) fn unplaced_in(&self, pass: u64) -> bool {
233 !self.is_placed && self.cleared_in_pass == pass
234 }
235
236 pub(crate) fn note_semantics_change(&mut self) {
237 if let Some(id) = self.node_id {
238 crate::semantics_layout_log::record_layout_change(id, &mut self.semantics_stamp);
239 }
240 }
241}
242
243#[derive(Clone)]
244struct MeasurementCacheEntry {
245 constraints: Constraints,
246 measured: Rc<MeasuredNode>,
247}
248
249#[derive(Clone, Copy, Debug)]
250pub enum IntrinsicKind {
251 MinWidth(f32),
252 MaxWidth(f32),
253 MinHeight(f32),
254 MaxHeight(f32),
255}
256
257impl IntrinsicKind {
258 fn discriminant(&self) -> u8 {
259 match self {
260 IntrinsicKind::MinWidth(_) => 0,
261 IntrinsicKind::MaxWidth(_) => 1,
262 IntrinsicKind::MinHeight(_) => 2,
263 IntrinsicKind::MaxHeight(_) => 3,
264 }
265 }
266
267 fn value_bits(&self) -> u32 {
268 match self {
269 IntrinsicKind::MinWidth(value)
270 | IntrinsicKind::MaxWidth(value)
271 | IntrinsicKind::MinHeight(value)
272 | IntrinsicKind::MaxHeight(value) => value.to_bits(),
273 }
274 }
275}
276
277impl PartialEq for IntrinsicKind {
278 fn eq(&self, other: &Self) -> bool {
279 self.discriminant() == other.discriminant() && self.value_bits() == other.value_bits()
280 }
281}
282
283impl Eq for IntrinsicKind {}
284
285impl Hash for IntrinsicKind {
286 fn hash<H: Hasher>(&self, state: &mut H) {
287 self.discriminant().hash(state);
288 self.value_bits().hash(state);
289 }
290}
291
292#[derive(Default)]
293struct NodeCacheState {
294 epoch: u64,
295 measurement: Option<MeasurementCacheEntry>,
296 intrinsics: Vec<(IntrinsicKind, f32)>,
297 intrinsics_read: bool,
299}
300
301#[derive(Default)]
302pub(crate) struct LayoutNodeCacheHandles {
303 state: Rc<RefCell<NodeCacheState>>,
304}
305
306impl Clone for LayoutNodeCacheHandles {
307 fn clone(&self) -> Self {
308 Self {
309 state: Rc::clone(&self.state),
310 }
311 }
312
313 fn clone_from(&mut self, source: &Self) {
314 if !Rc::ptr_eq(&self.state, &source.state) {
315 self.state = Rc::clone(&source.state);
316 }
317 }
318}
319
320impl LayoutNodeCacheHandles {
321 pub(crate) fn clear(&self) {
322 let mut state = self.state.borrow_mut();
323 state.measurement = None;
324 state.intrinsics.clear();
325 state.intrinsics_read = false;
326 state.epoch = 0;
327 }
328
329 pub(crate) fn activate(&self, epoch: u64) {
330 let mut state = self.state.borrow_mut();
331 if state.epoch != epoch {
332 state.measurement = None;
333 state.intrinsics.clear();
334 state.intrinsics_read = false;
335 state.epoch = epoch;
336 }
337 }
338
339 pub(crate) fn forget_intrinsics(&self) {
343 self.state.borrow_mut().intrinsics.clear();
344 }
345
346 pub(crate) fn epoch(&self) -> u64 {
347 self.state.borrow().epoch
348 }
349
350 pub(crate) fn get_measurement(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
351 let state = self.state.borrow();
352 state
353 .measurement
354 .as_ref()
355 .filter(|entry| entry.constraints == constraints)
356 .map(|entry| Rc::clone(&entry.measured))
357 }
358
359 pub(crate) fn measured_constraints(&self) -> Option<Constraints> {
361 self.state
362 .borrow()
363 .measurement
364 .as_ref()
365 .map(|entry| entry.constraints)
366 }
367
368 pub(crate) fn has_intrinsics(&self) -> bool {
370 self.state.borrow().intrinsics_read
371 }
372
373 pub(crate) fn store_measurement(&self, constraints: Constraints, measured: Rc<MeasuredNode>) {
374 self.state.borrow_mut().measurement = Some(MeasurementCacheEntry {
375 constraints,
376 measured,
377 });
378 }
379
380 pub(crate) fn get_intrinsic(&self, kind: &IntrinsicKind) -> Option<f32> {
381 let state = self.state.borrow();
382 state
383 .intrinsics
384 .iter()
385 .find(|(stored_kind, _)| stored_kind == kind)
386 .map(|(_, value)| *value)
387 }
388
389 pub(crate) fn store_intrinsic(&self, kind: IntrinsicKind, value: f32) {
390 let mut state = self.state.borrow_mut();
391 state.intrinsics_read = true;
392 if let Some((_, existing)) = state
393 .intrinsics
394 .iter_mut()
395 .find(|(stored_kind, _)| stored_kind == &kind)
396 {
397 *existing = value;
398 } else {
399 state.intrinsics.push((kind, value));
400 }
401 }
402}
403
404pub struct LayoutNode {
405 #[cfg(feature = "inspection")]
407 pub source_trace: Rc<[cranpose_core::source_trace::SourceLocation]>,
408 pub modifier: Modifier,
409 modifier_chain: ModifierChainHandle,
410 pub measure_policy: Rc<dyn MeasurePolicy>,
411 density: crate::density::Density,
412 pub children: Vec<NodeId>,
414 cache: LayoutNodeCacheHandles,
415 needs_measure: Cell<bool>,
416 needs_layout: Cell<bool>,
417 descendant_dirt: Cell<DescendantDirt>,
419 needs_semantics: Cell<bool>,
420 descendant_needs_semantics: Cell<bool>,
424 semantics_reach: Cell<Option<cranpose_foundation::SemanticsReach>>,
428 needs_redraw: Cell<bool>,
429 needs_pointer_pass: Cell<bool>,
430 needs_focus_sync: Cell<bool>,
431 parent: Cell<Option<NodeId>>,
432 folded_parent: Cell<Option<NodeId>>,
433 id: Cell<Option<NodeId>>,
434 owner_context_id: Cell<Option<crate::render_state::AppContextId>>,
435 debug_modifiers: Cell<bool>,
436 is_virtual: bool,
437 virtual_children_count: Cell<usize>,
438
439 modifier_slices_snapshot: RefCell<Rc<ModifierNodeSlices>>,
440 modifier_slices_dirty: Cell<bool>,
441
442 layout_state: Rc<RefCell<LayoutState>>,
443 layout_runtime_state: Rc<RefCell<LayoutRuntimeState>>,
444 coordinator_geometry: Rc<crate::modifier::CoordinatorGeometry>,
447}
448
449pub(crate) const RECYCLED_LAYOUT_NODE_POOL_LIMIT: usize = 128;
450
451thread_local! {
452 static EMPTY_MEASURE_POLICY: Rc<dyn MeasurePolicy> =
453 Rc::new(crate::layout::policies::EmptyMeasurePolicy);
454}
455
456fn empty_measure_policy() -> Rc<dyn MeasurePolicy> {
457 EMPTY_MEASURE_POLICY.with(Rc::clone)
458}
459
460impl LayoutNode {
461 pub fn new(modifier: Modifier, measure_policy: Rc<dyn MeasurePolicy>) -> Self {
462 Self::new_with_virtual(modifier, measure_policy, false)
463 }
464
465 pub fn new_virtual() -> Self {
468 Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), true)
469 }
470
471 fn new_recycled_shell(is_virtual: bool) -> Self {
472 let mut shell =
473 Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), is_virtual);
474 shell.needs_measure.set(false);
475 shell.needs_layout.set(false);
476 shell.needs_semantics.set(false);
477 shell.descendant_needs_semantics.set(false);
478 shell.needs_redraw.set(false);
479 shell.needs_pointer_pass.set(false);
480 shell.needs_focus_sync.set(false);
481 shell.parent.set(None);
482 shell.folded_parent.set(None);
483 shell.id.set(None);
484 shell.owner_context_id.set(None);
485 shell.debug_modifiers.set(false);
486 shell.virtual_children_count.set(0);
487 shell.modifier_slices_dirty = Cell::new(true);
488 shell
489 }
490
491 fn new_with_virtual(
492 modifier: Modifier,
493 measure_policy: Rc<dyn MeasurePolicy>,
494 is_virtual: bool,
495 ) -> Self {
496 let layout_runtime_state = Rc::new(RefCell::new(LayoutRuntimeState::new(Rc::clone(
497 &measure_policy,
498 ))));
499 let mut node = Self {
500 #[cfg(feature = "inspection")]
501 source_trace: cranpose_core::source_trace::current_source_trace(),
502 modifier,
503 modifier_chain: ModifierChainHandle::new(),
504 measure_policy,
505 density: crate::density::Density::default(),
506 children: Vec::new(),
507 cache: LayoutNodeCacheHandles::default(),
508 needs_measure: Cell::new(true),
509 needs_layout: Cell::new(true),
510 descendant_dirt: Cell::default(),
511 needs_semantics: Cell::new(true),
512 descendant_needs_semantics: Cell::new(false),
513 semantics_reach: Cell::new(None),
514 needs_redraw: Cell::new(true),
515 needs_pointer_pass: Cell::new(false),
516 needs_focus_sync: Cell::new(false),
517 parent: Cell::new(None),
518 folded_parent: Cell::new(None),
519 id: Cell::new(None),
520 owner_context_id: Cell::new(None),
521 debug_modifiers: Cell::new(false),
522 is_virtual,
523 virtual_children_count: Cell::new(0),
524 modifier_slices_snapshot: RefCell::new(Rc::default()),
525 modifier_slices_dirty: Cell::new(true),
526 layout_state: Rc::new(RefCell::new(LayoutState::default())),
527 layout_runtime_state,
528 coordinator_geometry: Rc::default(),
529 };
530 node.sync_modifier_chain(false);
531 node
532 }
533
534 pub fn set_modifier(&mut self, modifier: Modifier) {
535 #[cfg(feature = "inspection")]
536 {
537 let trace = cranpose_core::source_trace::current_source_trace();
538 if !trace.is_empty() {
539 self.source_trace = trace;
540 }
541 }
542 if self.modifier == modifier
546 && !self
547 .modifier_capabilities()
548 .contains(NodeCapabilities::MODIFIER_LOCALS)
549 {
550 return;
551 }
552 let text_only = self
555 .modifier
556 .differs_only_in::<crate::text_modifier_node::TextModifierElement>(&modifier);
557 let modifier_changed = text_only || !self.modifier.structural_eq(&modifier);
558 self.modifier = modifier;
559 self.sync_modifier_chain(text_only);
560 if modifier_changed {
561 self.cache.clear();
562 self.request_semantics_update();
563 }
564 }
565
566 fn sync_modifier_chain(&mut self, text_only: bool) {
570 let prev_caps = self.modifier_capabilities();
571 let start_parent = self.parent();
572 let mut resolver = move |token: &ModifierLocalToken| {
573 resolve_modifier_local_from_parent_chain(start_parent, token)
574 };
575 self.modifier_chain
576 .set_debug_logging(self.debug_modifiers.get());
577 self.modifier_chain.set_node_id(self.id.get());
578 let in_place = text_only
579 && self
580 .modifier_chain
581 .update_elements_in_place::<crate::text_modifier_node::TextModifierElement>(
582 &self.modifier,
583 );
584 let modifier_local_invalidations = if in_place {
585 ModifierInvalidations::new()
586 } else {
587 self.modifier_chain
588 .update_with_resolver(&self.modifier, &mut resolver)
589 };
590 if prev_caps.contains(NodeCapabilities::WINDOW_ROOT)
591 != self
592 .modifier_capabilities()
593 .contains(NodeCapabilities::WINDOW_ROOT)
594 {
595 self.note_semantics_layout_change();
596 }
597 if !in_place {
600 self.forget_semantics_reach();
601 }
602 let keep_slices =
603 text_only && !self.modifier_slices_dirty.get() && self.point_slices_at_text();
604 if !keep_slices {
605 self.modifier_slices_dirty.set(true);
606 }
607
608 let mut invalidations = self.modifier_chain.take_invalidations();
609 invalidations.extend(modifier_local_invalidations);
610 self.dispatch_modifier_invalidations_with_prev(&invalidations, prev_caps, keep_slices);
611 if !in_place {
614 self.refresh_registry_state();
615 }
616 }
617
618 fn point_slices_at_text(&self) -> bool {
621 let mut layout = None;
622 self.modifier_chain.chain().for_each_forward_matching(
623 NodeCapabilities::LAYOUT,
624 |node_ref| {
625 node_ref.with_node(|node| {
626 if layout.is_none()
627 && let Some(text) = node
628 .as_any()
629 .downcast_ref::<crate::text_modifier_node::TextModifierNode>()
630 {
631 layout = Some(text.prepared_layout_handle());
632 }
633 });
634 },
635 );
636 let Some(layout) = layout else {
637 return false;
638 };
639 Rc::make_mut(&mut *self.modifier_slices_snapshot.borrow_mut()).replace_text_layout(layout);
640 true
641 }
642
643 fn update_modifier_slices_cache(&self) {
644 let mut snapshot = self.modifier_slices_snapshot.borrow_mut();
645 crate::modifier::collect_modifier_slices_into_shared(
646 self.modifier_chain.chain(),
647 &mut snapshot,
648 &self.coordinator_geometry,
649 self.density.density(),
650 );
651 self.modifier_slices_dirty.set(false);
652 }
653
654 #[cfg(test)]
656 pub(crate) fn modifier_slices_ready(&self) -> bool {
657 !self.modifier_slices_dirty.get()
658 }
659
660 pub(crate) fn mark_modifier_slices_dirty(&self) {
661 self.modifier_slices_dirty.set(true);
662 }
663
664 #[cfg(test)]
665 fn dispatch_modifier_invalidations(&self, invalidations: &[ModifierInvalidation]) {
666 self.dispatch_modifier_invalidations_with_prev(
667 invalidations,
668 NodeCapabilities::empty(),
669 false,
670 );
671 }
672
673 fn dispatch_modifier_invalidations_with_prev(
676 &self,
677 invalidations: &[ModifierInvalidation],
678 prev_caps: NodeCapabilities,
679 keep_slices: bool,
680 ) {
681 let curr_caps = self.modifier_capabilities();
682 for invalidation in invalidations {
683 if !keep_slices {
684 self.modifier_slices_dirty.set(true);
685 }
686 let has_capability =
687 |capability| curr_caps.contains(capability) || prev_caps.contains(capability);
688 match invalidation.kind() {
689 InvalidationKind::Layout => {
690 if has_capability(NodeCapabilities::LAYOUT) {
691 self.mark_needs_measure();
692 if let Some(id) = self.id.get() {
693 log_layout_invalidation_dispatch(
694 id,
695 invalidation,
696 curr_caps,
697 prev_caps,
698 &self.modifier,
699 );
700 let inside_composition =
701 cranpose_core::composer_context::try_with_composer(|_| ())
702 .is_some();
703 if inside_composition {
704 cranpose_core::bubble_measure_dirty_in_composer(id);
705 } else {
706 crate::schedule_layout_repass(id);
707 }
708 }
709 }
710 }
711 InvalidationKind::Draw => {
712 if has_capability(NodeCapabilities::DRAW)
713 || invalidation.capabilities().contains(NodeCapabilities::DRAW)
714 {
715 self.mark_needs_redraw();
716 }
717 }
718 InvalidationKind::PointerInput => {
719 if has_capability(NodeCapabilities::POINTER_INPUT) {
720 self.mark_needs_pointer_pass();
721 crate::request_pointer_invalidation();
722 if let Some(id) = self.id.get() {
723 crate::schedule_pointer_repass(id);
724 }
725 }
726 }
727 InvalidationKind::Semantics => {
728 self.request_semantics_update();
729 }
730 InvalidationKind::Focus => {
731 if has_capability(NodeCapabilities::FOCUS) {
732 self.mark_needs_focus_sync();
733 crate::request_focus_invalidation();
734 if let Some(id) = self.id.get() {
735 crate::schedule_focus_invalidation(id);
736 }
737 }
738 }
739 }
740 }
741 }
742
743 pub fn density(&self) -> crate::density::Density {
745 self.density
746 }
747
748 pub fn set_density(&mut self, density: crate::density::Density) {
750 if self.density != density {
751 self.density = density;
752 self.cache.clear();
753 self.modifier_slices_dirty.set(true);
754 self.mark_needs_measure();
755 }
756 }
757
758 pub fn set_layout_direction(&mut self, direction: crate::LayoutDirection) {
760 if self.modifier_chain.set_layout_direction(direction) {
761 self.cache.clear();
762 self.modifier_slices_dirty.set(true);
763 self.mark_needs_measure();
764 }
765 }
766
767 pub fn set_measure_policy(&mut self, policy: Rc<dyn MeasurePolicy>) {
768 if !Rc::ptr_eq(&self.measure_policy, &policy) {
769 self.measure_policy = policy;
770 self.cache.clear();
771 self.mark_needs_measure();
772 if let Some(id) = self.id.get() {
773 cranpose_core::bubble_measure_dirty_in_composer(id);
774 }
775 }
776 }
777
778 pub fn mark_needs_measure(&self) {
780 self.needs_measure.set(true);
781 self.needs_layout.set(true);
782 }
783
784 pub fn mark_needs_layout(&self) {
786 self.needs_layout.set(true);
787 }
788
789 pub fn mark_needs_redraw(&self) {
791 self.needs_redraw.set(true);
792 if let Some(id) = self.id.get() {
793 crate::schedule_draw_repass(id);
794 }
795 crate::request_render_invalidation();
796 }
797
798 pub fn needs_measure(&self) -> bool {
800 self.needs_measure.get()
801 }
802
803 pub fn needs_layout(&self) -> bool {
805 self.needs_layout.get()
806 }
807
808 pub fn mark_needs_semantics(&self) {
810 self.needs_semantics.set(true);
811 self.forget_semantics_reach();
812 }
813
814 pub(crate) fn clear_needs_semantics(&self) {
815 self.needs_semantics.set(false);
816 self.descendant_needs_semantics.set(false);
817 }
818
819 pub fn needs_semantics(&self) -> bool {
822 self.needs_semantics.get() || self.descendant_needs_semantics.get()
823 }
824
825 pub(crate) fn semantics_changed(&self) -> bool {
828 self.needs_semantics.get()
829 }
830
831 pub fn needs_redraw(&self) -> bool {
833 self.needs_redraw.get()
834 }
835
836 pub fn clear_needs_redraw(&self) {
837 self.needs_redraw.set(false);
838 }
839
840 fn request_semantics_update(&self) {
841 let already_dirty = self.needs_semantics.replace(true);
842 if already_dirty {
843 return;
844 }
845
846 if let Some(id) = self.id.get() {
847 cranpose_core::queue_semantics_invalidation(id);
848 }
849 }
850
851 pub(crate) fn clear_needs_measure(&self) {
852 self.needs_measure.set(false);
853 }
854
855 pub(crate) fn clear_needs_layout(&self) {
858 self.needs_layout.set(false);
859 self.descendant_dirt.set(DescendantDirt::default());
860 }
861
862 pub fn mark_needs_pointer_pass(&self) {
864 self.needs_pointer_pass.set(true);
865 }
866
867 pub fn needs_pointer_pass(&self) -> bool {
869 self.needs_pointer_pass.get()
870 }
871
872 pub fn clear_needs_pointer_pass(&self) {
874 self.needs_pointer_pass.set(false);
875 }
876
877 pub fn mark_needs_focus_sync(&self) {
879 self.needs_focus_sync.set(true);
880 }
881
882 pub fn needs_focus_sync(&self) -> bool {
884 self.needs_focus_sync.get()
885 }
886
887 pub fn clear_needs_focus_sync(&self) {
889 self.needs_focus_sync.set(false);
890 }
891
892 pub fn set_node_id(&mut self, id: NodeId) {
894 if let Some(existing) = self.id.replace(Some(id))
895 && let Some(owner_context_id) = self.owner_context_id.take()
896 {
897 unregister_layout_node(owner_context_id, existing);
898 }
899 self.layout_state.borrow_mut().set_node_id(id);
900 let owner_context_id = register_layout_node(id, self);
901 self.owner_context_id.set(Some(owner_context_id));
902 self.refresh_registry_state();
903
904 self.modifier_chain.set_node_id(Some(id));
905 let invalidations = self.modifier_chain.take_invalidations();
906 self.dispatch_modifier_invalidations_with_prev(
907 &invalidations,
908 NodeCapabilities::empty(),
909 false,
910 );
911 self.modifier_slices_dirty.set(true);
913 }
914
915 pub fn node_id(&self) -> Option<NodeId> {
917 self.id.get()
918 }
919
920 pub fn set_parent(&self, parent: NodeId) {
923 self.folded_parent.set(Some(parent));
924 self.parent.set(Some(parent));
925 self.refresh_registry_state();
926 }
927
928 pub fn clear_parent(&self) {
930 self.folded_parent.set(None);
931 self.parent.set(None);
932 self.refresh_registry_state();
933 }
934
935 pub fn parent(&self) -> Option<NodeId> {
937 self.parent.get()
938 }
939
940 pub fn folded_parent(&self) -> Option<NodeId> {
942 self.folded_parent.get()
943 }
944
945 pub(crate) fn cache_handles(&self) -> &LayoutNodeCacheHandles {
946 &self.cache
947 }
948
949 pub fn resolved_modifiers(&self) -> ResolvedModifiers {
950 self.modifier_chain.resolved_modifiers()
951 }
952
953 pub fn modifier_capabilities(&self) -> NodeCapabilities {
954 self.modifier_chain.capabilities()
955 }
956
957 pub fn is_window_root(&self) -> bool {
961 self.modifier_capabilities()
962 .contains(NodeCapabilities::WINDOW_ROOT)
963 }
964
965 pub fn modifier_child_capabilities(&self) -> NodeCapabilities {
966 self.modifier_chain.aggregate_child_capabilities()
967 }
968
969 pub fn set_debug_modifiers(&mut self, enabled: bool) {
970 self.debug_modifiers.set(enabled);
971 self.modifier_chain.set_debug_logging(enabled);
972 }
973
974 pub fn debug_modifiers_enabled(&self) -> bool {
975 self.debug_modifiers.get()
976 }
977
978 pub fn modifier_locals_handle(&self) -> Option<ModifierLocalsHandle> {
981 self.modifier_chain.modifier_locals_handle()
982 }
983
984 pub fn has_layout_modifier_nodes(&self) -> bool {
985 self.modifier_capabilities()
986 .contains(NodeCapabilities::LAYOUT)
987 }
988
989 pub fn has_draw_modifier_nodes(&self) -> bool {
990 self.modifier_capabilities()
991 .contains(NodeCapabilities::DRAW)
992 }
993
994 pub fn has_pointer_input_modifier_nodes(&self) -> bool {
995 self.modifier_capabilities()
996 .contains(NodeCapabilities::POINTER_INPUT)
997 }
998
999 pub fn has_semantics_modifier_nodes(&self) -> bool {
1000 self.modifier_capabilities()
1001 .contains(NodeCapabilities::SEMANTICS)
1002 }
1003
1004 pub fn has_focus_modifier_nodes(&self) -> bool {
1005 self.modifier_capabilities()
1006 .contains(NodeCapabilities::FOCUS)
1007 }
1008
1009 fn refresh_registry_state(&self) {
1010 if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
1011 let parent = self.parent();
1012 let capabilities = self.modifier_child_capabilities();
1013 let modifier_locals = self.modifier_locals_handle();
1014 let _ = crate::render_state::with_layout_node_registry_by_app_context(
1015 owner_context_id,
1016 |registry| {
1017 registry.update_entry(id, parent, capabilities, modifier_locals);
1018 },
1019 );
1020 }
1021 }
1022
1023 pub fn modifier_slices_snapshot(&self) -> Rc<ModifierNodeSlices> {
1024 if self.modifier_slices_dirty.get() {
1025 self.update_modifier_slices_cache();
1026 }
1027 self.modifier_slices_snapshot.borrow().clone()
1028 }
1029
1030 pub fn layout_state(&self) -> LayoutState {
1032 self.layout_state.borrow().clone()
1033 }
1034
1035 pub fn measured_size(&self) -> Size {
1037 self.layout_state.borrow().size
1038 }
1039
1040 pub fn position(&self) -> Point {
1042 self.layout_state.borrow().position
1043 }
1044
1045 pub fn is_placed(&self) -> bool {
1047 self.layout_state.borrow().is_placed
1048 }
1049
1050 pub fn set_measured_size(&self, size: Size) {
1054 self.layout_state.borrow_mut().set_size(size);
1055 }
1056
1057 pub fn set_position(&self, position: Point) {
1060 self.layout_state.borrow_mut().place(position);
1061 }
1062
1063 pub fn set_content_offset(&self, offset: Point) {
1065 self.layout_state.borrow_mut().set_content_offset(offset);
1066 }
1067
1068 pub fn clear_placed(&self) {
1070 self.layout_state.borrow_mut().clear_placed();
1071 }
1072
1073 fn note_semantics_layout_change(&self) {
1074 if let Ok(mut state) = self.layout_state.try_borrow_mut() {
1075 state.note_semantics_change();
1076 }
1077 }
1078
1079 pub fn semantics_configuration(&self) -> Option<SemanticsConfiguration> {
1080 crate::modifier::collect_semantics_from_chain(self.modifier_chain.chain())
1081 }
1082
1083 fn forget_semantics_reach(&self) {
1085 self.semantics_reach.set(None);
1086 crate::modal_nodes::reach_changed(self.id.get());
1087 }
1088
1089 pub fn semantics_reach(&self) -> cranpose_foundation::SemanticsReach {
1091 if !self
1093 .modifier_capabilities()
1094 .contains(NodeCapabilities::SEMANTICS)
1095 {
1096 return cranpose_foundation::SemanticsReach::default();
1097 }
1098 if let Some(reach) = self.semantics_reach.get() {
1099 return reach;
1100 }
1101 let reach = crate::modifier::semantics_reach_of_chain(self.modifier_chain.chain());
1102 self.semantics_reach.set(Some(reach));
1103 reach
1104 }
1105
1106 pub(crate) fn modifier_chain(&self) -> &ModifierChainHandle {
1107 &self.modifier_chain
1108 }
1109
1110 pub fn with_text_field_modifier_mut<R>(
1115 &mut self,
1116 f: impl FnMut(&mut crate::TextFieldModifierNode) -> R,
1117 ) -> Option<R> {
1118 self.modifier_chain.with_text_field_modifier_mut(f)
1119 }
1120
1121 pub fn layout_state_handle(&self) -> &Rc<RefCell<LayoutState>> {
1124 &self.layout_state
1125 }
1126
1127 pub(crate) fn coordinator_geometry(&self) -> &crate::modifier::CoordinatorGeometry {
1128 &self.coordinator_geometry
1129 }
1130
1131 pub(crate) fn layout_runtime_state_handle(&self) -> Rc<RefCell<LayoutRuntimeState>> {
1132 self.layout_runtime_state.clone()
1133 }
1134
1135 #[cfg(test)]
1136 pub(crate) fn layout_runtime_debug_stats(&self) -> LayoutRuntimeDebugStats {
1137 self.layout_runtime_state.borrow().debug_stats()
1138 }
1139}
1140impl Clone for LayoutNode {
1141 fn clone(&self) -> Self {
1142 let mut node = Self {
1143 #[cfg(feature = "inspection")]
1144 source_trace: self.source_trace.clone(),
1145 modifier: self.modifier.clone(),
1146 modifier_chain: ModifierChainHandle::for_layout_direction(
1147 self.modifier_chain.layout_direction(),
1148 ),
1149 measure_policy: self.measure_policy.clone(),
1150 density: self.density,
1151 children: self.children.clone(),
1152 cache: self.cache.clone(),
1153 needs_measure: Cell::new(self.needs_measure.get()),
1154 needs_layout: Cell::new(self.needs_layout.get()),
1155 descendant_dirt: Cell::new(self.descendant_dirt.get()),
1156 needs_semantics: Cell::new(self.needs_semantics.get()),
1157 descendant_needs_semantics: Cell::new(self.descendant_needs_semantics.get()),
1158 semantics_reach: Cell::new(None),
1159 needs_redraw: Cell::new(self.needs_redraw.get()),
1160 needs_pointer_pass: Cell::new(self.needs_pointer_pass.get()),
1161 needs_focus_sync: Cell::new(self.needs_focus_sync.get()),
1162 parent: Cell::new(self.parent.get()),
1163 folded_parent: Cell::new(self.folded_parent.get()),
1164 id: Cell::new(None),
1165 owner_context_id: Cell::new(None),
1166 debug_modifiers: Cell::new(self.debug_modifiers.get()),
1167 is_virtual: self.is_virtual,
1168 virtual_children_count: Cell::new(self.virtual_children_count.get()),
1169 modifier_slices_snapshot: RefCell::new(Rc::default()),
1170 modifier_slices_dirty: Cell::new(true),
1171 layout_state: self.layout_state.clone(),
1172 layout_runtime_state: self.layout_runtime_state.clone(),
1173 coordinator_geometry: Rc::clone(&self.coordinator_geometry),
1174 };
1175 node.sync_modifier_chain(false);
1176 node
1177 }
1178}
1179
1180impl Node for LayoutNode {
1181 fn mount(&mut self) {
1182 let (chain, mut context) = self.modifier_chain.chain_and_context_mut();
1183 chain.repair_chain();
1184 chain.attach_nodes(&mut *context);
1185 crate::modal_nodes::reach_changed(self.id.get());
1186 }
1187
1188 fn unmount(&mut self) {
1189 self.modifier_chain.chain_mut().detach_nodes();
1190 }
1191
1192 fn set_node_id(&mut self, id: NodeId) {
1193 LayoutNode::set_node_id(self, id);
1194 }
1195
1196 fn insert_child(&mut self, child: NodeId) -> bool {
1197 if self.children.contains(&child) {
1198 return false;
1199 }
1200 if is_virtual_node(child) {
1201 let count = self.virtual_children_count.get();
1202 self.virtual_children_count.set(count + 1);
1203 }
1204 self.children.push(child);
1205 self.cache.clear();
1206 self.mark_needs_measure();
1207 self.note_semantics_layout_change();
1208 true
1209 }
1210
1211 fn remove_child(&mut self, child: NodeId) -> bool {
1212 let before = self.children.len();
1213 self.children.retain(|&id| id != child);
1214 let removed = self.children.len() < before;
1215 if removed {
1216 if is_virtual_node(child) {
1217 let count = self.virtual_children_count.get();
1218 if count > 0 {
1219 self.virtual_children_count.set(count - 1);
1220 }
1221 }
1222 self.cache.clear();
1223 self.mark_needs_measure();
1224 self.note_semantics_layout_change();
1225 }
1226 removed
1227 }
1228
1229 fn move_child(&mut self, from: usize, to: usize) {
1230 if from == to || from >= self.children.len() {
1231 return;
1232 }
1233 let child = self.children.remove(from);
1234 let target = to.min(self.children.len());
1235 self.children.insert(target, child);
1236 self.cache.clear();
1237 self.mark_needs_measure();
1238 self.note_semantics_layout_change();
1239 }
1240
1241 fn update_children(&mut self, children: &[NodeId]) {
1242 self.children.clear();
1243 self.children.extend_from_slice(children);
1244 self.cache.clear();
1245 self.mark_needs_measure();
1246 self.note_semantics_layout_change();
1247 }
1248
1249 fn collect_children_into(&self, out: &mut smallvec::SmallVec<[NodeId; 8]>) {
1250 out.clear();
1251 out.extend_from_slice(&self.children);
1252 }
1253
1254 fn owned_child_index(&self, child: NodeId) -> Option<usize> {
1255 self.children.iter().position(|&id| id == child)
1256 }
1257
1258 fn on_attached_to_parent(&mut self, parent: NodeId) {
1259 self.set_parent(parent);
1260 }
1261
1262 fn on_removed_from_parent(&mut self) {
1263 self.clear_parent();
1264 }
1265
1266 fn parent(&self) -> Option<NodeId> {
1267 self.parent.get()
1268 }
1269
1270 fn mark_needs_layout(&self) {
1271 self.needs_layout.set(true);
1272 }
1273
1274 fn needs_layout(&self) -> bool {
1275 self.needs_layout.get()
1276 }
1277
1278 fn mark_needs_measure(&self) {
1279 self.needs_measure.set(true);
1280 self.needs_layout.set(true);
1281 }
1282
1283 fn needs_measure(&self) -> bool {
1284 self.needs_measure.get()
1285 }
1286
1287 fn mark_descendant_needs_layout(&self, measure: bool) {
1288 self.descendant_dirt
1289 .set(self.descendant_dirt.get().marked(measure));
1290 if measure {
1291 self.cache.forget_intrinsics();
1292 }
1293 }
1294
1295 fn descendant_needs_layout(&self) -> bool {
1296 self.descendant_dirt.get().layout
1297 }
1298
1299 fn is_virtual(&self) -> bool {
1300 self.is_virtual
1301 }
1302
1303 fn descendant_needs_measure(&self) -> bool {
1304 self.descendant_dirt.get().measure
1305 }
1306
1307 fn mark_needs_semantics(&self) {
1308 self.needs_semantics.set(true);
1309 self.forget_semantics_reach();
1310 }
1311
1312 fn mark_descendant_needs_semantics(&self) {
1313 self.descendant_needs_semantics.set(true);
1314 }
1315
1316 fn needs_semantics(&self) -> bool {
1317 LayoutNode::needs_semantics(self)
1318 }
1319
1320 fn set_parent_for_bubbling(&mut self, parent: NodeId) {
1321 if self.parent.get().is_none() {
1322 self.parent.set(Some(parent));
1323 }
1324 }
1325
1326 fn recycle_key(&self) -> Option<TypeId> {
1327 Some(TypeId::of::<Self>())
1328 }
1329
1330 fn recycle_pool_limit(&self) -> Option<usize> {
1331 Some(RECYCLED_LAYOUT_NODE_POOL_LIMIT)
1332 }
1333
1334 fn prepare_for_recycle(&mut self) {
1335 *self = Self::new_recycled_shell(self.is_virtual);
1336 }
1337
1338 fn rehouse_for_recycle(&self) -> Option<Box<dyn cranpose_core::Node>> {
1339 Some(Box::new(Self::new_recycled_shell(self.is_virtual)))
1340 }
1341
1342 fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn cranpose_core::Node>> {
1343 let mut previous = std::mem::replace(self, Self::new_recycled_shell(self.is_virtual));
1344 let node_id = previous.id.replace(None);
1345 let parent = previous.parent.get();
1346 let folded_parent = previous.folded_parent.get();
1347 let debug_modifiers = previous.debug_modifiers.get();
1348 let needs_measure = previous.needs_measure.get();
1349 let needs_layout = previous.needs_layout.get();
1350 let needs_semantics = previous.needs_semantics.get();
1351 let descendant_needs_semantics = previous.descendant_needs_semantics.get();
1352 let needs_redraw = previous.needs_redraw.get();
1353 let needs_pointer_pass = previous.needs_pointer_pass.get();
1354 let needs_focus_sync = previous.needs_focus_sync.get();
1355 let virtual_children_count = previous.virtual_children_count.get();
1356 let children = previous.children.to_vec();
1357 let modifier = previous.modifier.rehouse_for_live_compaction();
1358 let measure_policy = previous.measure_policy.clone();
1359 let layout_state = previous.layout_state.clone();
1360 let layout_runtime_state = previous.layout_runtime_state.clone();
1361 let coordinator_geometry = Rc::clone(&previous.coordinator_geometry);
1362
1363 previous.modifier_chain.chain_mut().detach_nodes();
1364
1365 let mut compact = Self::new_with_virtual(modifier, measure_policy, previous.is_virtual);
1366 compact.children = children;
1367 #[cfg(feature = "inspection")]
1368 {
1369 compact.source_trace = previous.source_trace.clone();
1370 }
1371 compact.parent.set(parent);
1372 compact.folded_parent.set(folded_parent);
1373 compact.id.set(node_id);
1374 compact.debug_modifiers.set(debug_modifiers);
1375 compact.needs_measure.set(needs_measure);
1376 compact.needs_layout.set(needs_layout);
1377 compact.needs_semantics.set(needs_semantics);
1378 compact
1379 .descendant_needs_semantics
1380 .set(descendant_needs_semantics);
1381 compact.needs_redraw.set(needs_redraw);
1382 compact.needs_pointer_pass.set(needs_pointer_pass);
1383 compact.needs_focus_sync.set(needs_focus_sync);
1384 compact.virtual_children_count.set(virtual_children_count);
1385 compact.layout_state = layout_state;
1386 compact.layout_runtime_state = layout_runtime_state;
1387 compact.coordinator_geometry = coordinator_geometry;
1388 compact.sync_modifier_chain(false);
1389 if let Some(id) = node_id {
1390 let owner_context_id = register_layout_node(id, &compact);
1391 compact.owner_context_id.set(Some(owner_context_id));
1392 }
1393
1394 Some(Box::new(compact))
1395 }
1396}
1397
1398impl Drop for LayoutNode {
1399 fn drop(&mut self) {
1400 if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
1401 unregister_layout_node(owner_context_id, id);
1402 }
1403 }
1404}
1405
1406const MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY: usize = 128;
1407const VIRTUAL_NODE_ID_START: NodeId = 0xC0000000;
1408
1409#[cfg(test)]
1410#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1411struct LayoutNodeRegistryDebugStats {
1412 len: usize,
1413 capacity: usize,
1414}
1415
1416struct LayoutNodeRegistryEntry {
1417 parent: Option<NodeId>,
1418 modifier_child_capabilities: NodeCapabilities,
1419 modifier_locals: Option<ModifierLocalsHandle>,
1420 is_virtual: bool,
1421}
1422
1423pub(crate) struct LayoutNodeRegistryState {
1424 entries: RefCell<HashMap<NodeId, LayoutNodeRegistryEntry>>,
1425 virtual_node_id_counter: Cell<NodeId>,
1426}
1427
1428impl LayoutNodeRegistryState {
1429 pub(crate) fn new() -> Self {
1430 Self {
1431 entries: RefCell::new(HashMap::new()),
1432 virtual_node_id_counter: Cell::new(VIRTUAL_NODE_ID_START),
1433 }
1434 }
1435
1436 fn register(&self, id: NodeId, node: &LayoutNode) {
1437 self.entries.borrow_mut().insert(
1438 id,
1439 LayoutNodeRegistryEntry {
1440 parent: node.parent(),
1441 modifier_child_capabilities: node.modifier_child_capabilities(),
1442 modifier_locals: node.modifier_locals_handle(),
1443 is_virtual: node.is_virtual(),
1444 },
1445 );
1446 }
1447
1448 fn unregister(&self, id: NodeId) {
1449 let mut entries = self.entries.borrow_mut();
1450 entries.remove(&id);
1451 let should_shrink = (entries.len() <= MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY
1452 && entries.capacity() > MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1453 || entries.capacity()
1454 > entries
1455 .len()
1456 .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1457 .saturating_mul(4);
1458 if should_shrink {
1459 let retained = entries
1460 .len()
1461 .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY);
1462 let mut rebuilt = HashMap::new();
1463 rebuilt.reserve(retained);
1464 rebuilt.extend(entries.drain());
1465 *entries = rebuilt;
1466 }
1467 }
1468
1469 fn update_entry(
1470 &self,
1471 id: NodeId,
1472 parent: Option<NodeId>,
1473 modifier_child_capabilities: NodeCapabilities,
1474 modifier_locals: Option<ModifierLocalsHandle>,
1475 ) {
1476 if let Some(entry) = self.entries.borrow_mut().get_mut(&id) {
1477 entry.parent = parent;
1478 entry.modifier_child_capabilities = modifier_child_capabilities;
1479 entry.modifier_locals = modifier_locals;
1480 }
1481 }
1482
1483 #[cfg(test)]
1484 fn stats(&self) -> LayoutNodeRegistryDebugStats {
1485 let entries = self.entries.borrow();
1486 LayoutNodeRegistryDebugStats {
1487 len: entries.len(),
1488 capacity: entries.capacity(),
1489 }
1490 }
1491
1492 fn is_virtual_node(&self, id: NodeId) -> bool {
1493 self.entries
1494 .borrow()
1495 .get(&id)
1496 .is_some_and(|entry| entry.is_virtual)
1497 }
1498
1499 fn allocate_virtual_node_id(&self) -> NodeId {
1500 let id = self.virtual_node_id_counter.get();
1501 self.virtual_node_id_counter.set(id.wrapping_add(1));
1502 id
1503 }
1504
1505 fn resolve_modifier_local_from_parent_chain(
1506 &self,
1507 start: Option<NodeId>,
1508 token: &ModifierLocalToken,
1509 ) -> Option<ResolvedModifierLocal> {
1510 let mut current = start;
1511 while let Some(parent_id) = current {
1512 let (next_parent, resolved) = {
1513 let entries = self.entries.borrow();
1514 if let Some(entry) = entries.get(&parent_id) {
1515 let resolved = entry
1516 .modifier_child_capabilities
1517 .contains(NodeCapabilities::MODIFIER_LOCALS)
1518 .then_some(entry.modifier_locals.as_ref())
1519 .flatten()
1520 .and_then(|locals| locals.borrow().resolve(token))
1521 .map(|value| value.with_source(ModifierLocalSource::Ancestor));
1522 (entry.parent, resolved)
1523 } else {
1524 (None, None)
1525 }
1526 };
1527 if let Some(value) = resolved {
1528 return Some(value);
1529 }
1530 current = next_parent;
1531 }
1532 None
1533 }
1534}
1535
1536pub(crate) fn register_layout_node(
1537 id: NodeId,
1538 node: &LayoutNode,
1539) -> crate::render_state::AppContextId {
1540 let owner_context_id = crate::render_state::current_app_context_id();
1541 let _ = crate::render_state::with_layout_node_registry_by_app_context(
1542 owner_context_id,
1543 |registry| {
1544 registry.register(id, node);
1545 },
1546 );
1547 owner_context_id
1548}
1549
1550pub(crate) fn unregister_layout_node(
1551 owner_context_id: crate::render_state::AppContextId,
1552 id: NodeId,
1553) {
1554 let _ = crate::render_state::with_layout_node_registry_by_app_context(
1555 owner_context_id,
1556 |registry| {
1557 registry.unregister(id);
1558 },
1559 );
1560}
1561
1562#[cfg(test)]
1563fn layout_node_registry_stats() -> LayoutNodeRegistryDebugStats {
1564 crate::render_state::with_layout_node_registry(LayoutNodeRegistryState::stats)
1565}
1566
1567pub(crate) fn is_virtual_node(id: NodeId) -> bool {
1568 crate::render_state::with_layout_node_registry(|registry| registry.is_virtual_node(id))
1569}
1570
1571pub(crate) fn allocate_virtual_node_id() -> NodeId {
1572 crate::render_state::with_layout_node_registry(
1573 LayoutNodeRegistryState::allocate_virtual_node_id,
1574 )
1575}
1576
1577fn resolve_modifier_local_from_parent_chain(
1578 start: Option<NodeId>,
1579 token: &ModifierLocalToken,
1580) -> Option<ResolvedModifierLocal> {
1581 crate::render_state::with_layout_node_registry(|registry| {
1582 registry.resolve_modifier_local_from_parent_chain(start, token)
1583 })
1584}
1585
1586#[cfg(test)]
1587#[path = "tests/layout_node_tests.rs"]
1588mod tests;