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
404#[repr(C)]
411pub struct LayoutNode {
412 cache: LayoutNodeCacheHandles,
413 layout_state: Rc<RefCell<LayoutState>>,
414 layout_runtime_state: Rc<RefCell<LayoutRuntimeState>>,
415 pub children: Vec<NodeId>,
417 parent_data: Cell<cranpose_ui_layout::ParentData>,
420 needs_measure: Cell<bool>,
421 needs_layout: Cell<bool>,
422 descendant_dirt: Cell<DescendantDirt>,
424
425 pub measure_policy: Rc<dyn MeasurePolicy>,
426 coordinator_geometry: Rc<crate::modifier::CoordinatorGeometry>,
429 density: crate::density::Density,
430 is_virtual: bool,
431 needs_semantics: Cell<bool>,
432 descendant_needs_semantics: Cell<bool>,
436 needs_redraw: Cell<bool>,
437 needs_pointer_pass: Cell<bool>,
438 needs_focus_sync: Cell<bool>,
439 modifier_slices_dirty: Cell<bool>,
440 debug_modifiers: Cell<bool>,
441
442 id: Cell<Option<NodeId>>,
443 parent: Cell<Option<NodeId>>,
444 folded_parent: Cell<Option<NodeId>>,
445 modifier_slices_snapshot: RefCell<Rc<ModifierNodeSlices>>,
446
447 semantics_reach: Cell<Option<cranpose_foundation::SemanticsReach>>,
451 virtual_children_count: Cell<usize>,
452 owner_context_id: Cell<Option<crate::render_state::AppContextId>>,
453 modifier_chain: ModifierChainHandle,
454 pub modifier: Modifier,
455 #[cfg(feature = "inspection")]
457 pub source_trace: Rc<[cranpose_core::source_trace::SourceLocation]>,
458}
459
460pub(crate) const RECYCLED_LAYOUT_NODE_POOL_LIMIT: usize = 128;
461
462thread_local! {
463 static EMPTY_MEASURE_POLICY: Rc<dyn MeasurePolicy> =
464 Rc::new(crate::layout::policies::EmptyMeasurePolicy);
465}
466
467fn empty_measure_policy() -> Rc<dyn MeasurePolicy> {
468 EMPTY_MEASURE_POLICY.with(Rc::clone)
469}
470
471impl LayoutNode {
472 pub fn new(modifier: Modifier, measure_policy: Rc<dyn MeasurePolicy>) -> Self {
473 Self::new_with_virtual(modifier, measure_policy, false)
474 }
475
476 pub fn new_virtual() -> Self {
479 Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), true)
480 }
481
482 fn new_recycled_shell(is_virtual: bool) -> Self {
483 let mut shell =
484 Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), is_virtual);
485 shell.needs_measure.set(false);
486 shell.needs_layout.set(false);
487 shell.needs_semantics.set(false);
488 shell.descendant_needs_semantics.set(false);
489 shell.needs_redraw.set(false);
490 shell.needs_pointer_pass.set(false);
491 shell.needs_focus_sync.set(false);
492 shell.parent.set(None);
493 shell.folded_parent.set(None);
494 shell.id.set(None);
495 shell.owner_context_id.set(None);
496 shell.debug_modifiers.set(false);
497 shell.virtual_children_count.set(0);
498 shell.modifier_slices_dirty = Cell::new(true);
499 shell
500 }
501
502 fn new_with_virtual(
503 modifier: Modifier,
504 measure_policy: Rc<dyn MeasurePolicy>,
505 is_virtual: bool,
506 ) -> Self {
507 let layout_runtime_state = Rc::new(RefCell::new(LayoutRuntimeState::new(Rc::clone(
508 &measure_policy,
509 ))));
510 let mut node = Self {
511 #[cfg(feature = "inspection")]
512 source_trace: cranpose_core::source_trace::current_source_trace(),
513 modifier,
514 modifier_chain: ModifierChainHandle::new(),
515 measure_policy,
516 density: crate::density::Density::default(),
517 children: Vec::new(),
518 cache: LayoutNodeCacheHandles::default(),
519 needs_measure: Cell::new(true),
520 needs_layout: Cell::new(true),
521 descendant_dirt: Cell::default(),
522 parent_data: Cell::default(),
523 needs_semantics: Cell::new(true),
524 descendant_needs_semantics: Cell::new(false),
525 semantics_reach: Cell::new(None),
526 needs_redraw: Cell::new(true),
527 needs_pointer_pass: Cell::new(false),
528 needs_focus_sync: Cell::new(false),
529 parent: Cell::new(None),
530 folded_parent: Cell::new(None),
531 id: Cell::new(None),
532 owner_context_id: Cell::new(None),
533 debug_modifiers: Cell::new(false),
534 is_virtual,
535 virtual_children_count: Cell::new(0),
536 modifier_slices_snapshot: RefCell::new(Rc::default()),
537 modifier_slices_dirty: Cell::new(true),
538 layout_state: Rc::new(RefCell::new(LayoutState::default())),
539 layout_runtime_state,
540 coordinator_geometry: Rc::default(),
541 };
542 node.sync_modifier_chain(false);
543 node
544 }
545
546 pub fn set_modifier(&mut self, modifier: Modifier) {
547 #[cfg(feature = "inspection")]
548 {
549 let trace = cranpose_core::source_trace::current_source_trace();
550 if !trace.is_empty() {
551 self.source_trace = trace;
552 }
553 }
554 if self.modifier == modifier
558 && !self
559 .modifier_capabilities()
560 .contains(NodeCapabilities::MODIFIER_LOCALS)
561 {
562 return;
563 }
564 let text_only = self
567 .modifier
568 .differs_only_in::<crate::text_modifier_node::TextModifierElement>(&modifier);
569 let modifier_changed = text_only || !self.modifier.structural_eq(&modifier);
570 self.modifier = modifier;
571 self.sync_modifier_chain(text_only);
572 if modifier_changed {
573 self.cache.clear();
574 self.request_semantics_update();
575 }
576 }
577
578 fn sync_modifier_chain(&mut self, text_only: bool) {
582 let prev_caps = self.modifier_capabilities();
583 let start_parent = self.parent();
584 let mut resolver = move |token: &ModifierLocalToken| {
585 resolve_modifier_local_from_parent_chain(start_parent, token)
586 };
587 self.modifier_chain
588 .set_debug_logging(self.debug_modifiers.get());
589 self.modifier_chain.set_node_id(self.id.get());
590 let in_place = text_only
591 && self
592 .modifier_chain
593 .update_elements_in_place::<crate::text_modifier_node::TextModifierElement>(
594 &self.modifier,
595 );
596 let modifier_local_invalidations = if in_place {
597 ModifierInvalidations::new()
598 } else {
599 let invalidations = self
600 .modifier_chain
601 .update_with_resolver(&self.modifier, &mut resolver);
602 self.refresh_parent_data();
603 invalidations
604 };
605 if prev_caps.contains(NodeCapabilities::WINDOW_ROOT)
606 != self
607 .modifier_capabilities()
608 .contains(NodeCapabilities::WINDOW_ROOT)
609 {
610 self.note_semantics_layout_change();
611 }
612 if !in_place {
615 self.forget_semantics_reach();
616 }
617 let keep_slices =
618 text_only && !self.modifier_slices_dirty.get() && self.point_slices_at_text();
619 if !keep_slices {
620 self.modifier_slices_dirty.set(true);
621 }
622
623 let mut invalidations = self.modifier_chain.take_invalidations();
624 invalidations.extend(modifier_local_invalidations);
625 self.dispatch_modifier_invalidations_with_prev(&invalidations, prev_caps, keep_slices);
626 if !in_place {
629 self.refresh_registry_state();
630 }
631 }
632
633 fn point_slices_at_text(&self) -> bool {
636 let mut layout = None;
637 self.modifier_chain.chain().for_each_forward_matching(
638 NodeCapabilities::LAYOUT,
639 |node_ref| {
640 node_ref.with_node(|node| {
641 if layout.is_none()
642 && let Some(text) = node
643 .as_any()
644 .downcast_ref::<crate::text_modifier_node::TextModifierNode>()
645 {
646 layout = Some(text.prepared_layout_handle());
647 }
648 });
649 },
650 );
651 let Some(layout) = layout else {
652 return false;
653 };
654 Rc::make_mut(&mut *self.modifier_slices_snapshot.borrow_mut()).replace_text_layout(layout);
655 true
656 }
657
658 fn update_modifier_slices_cache(&self) {
659 let mut snapshot = self.modifier_slices_snapshot.borrow_mut();
660 crate::modifier::collect_modifier_slices_into_shared(
661 self.modifier_chain.chain(),
662 &mut snapshot,
663 &self.coordinator_geometry,
664 self.density.density(),
665 );
666 self.modifier_slices_dirty.set(false);
667 }
668
669 #[cfg(test)]
671 pub(crate) fn modifier_slices_ready(&self) -> bool {
672 !self.modifier_slices_dirty.get()
673 }
674
675 pub(crate) fn mark_modifier_slices_dirty(&self) {
676 self.modifier_slices_dirty.set(true);
677 }
678
679 #[cfg(test)]
680 fn dispatch_modifier_invalidations(&self, invalidations: &[ModifierInvalidation]) {
681 self.dispatch_modifier_invalidations_with_prev(
682 invalidations,
683 NodeCapabilities::empty(),
684 false,
685 );
686 }
687
688 fn dispatch_modifier_invalidations_with_prev(
691 &self,
692 invalidations: &[ModifierInvalidation],
693 prev_caps: NodeCapabilities,
694 keep_slices: bool,
695 ) {
696 let curr_caps = self.modifier_capabilities();
697 for invalidation in invalidations {
698 if !keep_slices {
699 self.modifier_slices_dirty.set(true);
700 }
701 let has_capability =
702 |capability| curr_caps.contains(capability) || prev_caps.contains(capability);
703 match invalidation.kind() {
704 InvalidationKind::Layout => {
705 if has_capability(NodeCapabilities::LAYOUT) {
706 self.mark_needs_measure();
707 if let Some(id) = self.id.get() {
708 log_layout_invalidation_dispatch(
709 id,
710 invalidation,
711 curr_caps,
712 prev_caps,
713 &self.modifier,
714 );
715 let inside_composition =
716 cranpose_core::composer_context::try_with_composer(|_| ())
717 .is_some();
718 if inside_composition {
719 cranpose_core::bubble_measure_dirty_in_composer(id);
720 } else {
721 crate::schedule_layout_repass(id);
722 }
723 }
724 }
725 }
726 InvalidationKind::Draw => {
727 if has_capability(NodeCapabilities::DRAW)
728 || invalidation.capabilities().contains(NodeCapabilities::DRAW)
729 {
730 self.mark_needs_redraw();
731 }
732 }
733 InvalidationKind::PointerInput => {
734 if has_capability(NodeCapabilities::POINTER_INPUT) {
735 self.mark_needs_pointer_pass();
736 crate::request_pointer_invalidation();
737 if let Some(id) = self.id.get() {
738 crate::schedule_pointer_repass(id);
739 }
740 }
741 }
742 InvalidationKind::Semantics => {
743 self.request_semantics_update();
744 }
745 InvalidationKind::Focus => {
746 if has_capability(NodeCapabilities::FOCUS) {
747 self.mark_needs_focus_sync();
748 crate::request_focus_invalidation();
749 if let Some(id) = self.id.get() {
750 crate::schedule_focus_invalidation(id);
751 }
752 }
753 }
754 }
755 }
756 }
757
758 pub fn density(&self) -> crate::density::Density {
760 self.density
761 }
762
763 pub fn set_density(&mut self, density: crate::density::Density) {
765 if self.density != density {
766 self.density = density;
767 self.cache.clear();
768 self.modifier_slices_dirty.set(true);
769 self.mark_needs_measure();
770 }
771 }
772
773 pub fn set_layout_direction(&mut self, direction: crate::LayoutDirection) {
775 if self.modifier_chain.set_layout_direction(direction) {
776 self.refresh_parent_data();
777 self.cache.clear();
778 self.modifier_slices_dirty.set(true);
779 self.mark_needs_measure();
780 }
781 }
782
783 pub fn set_measure_policy(&mut self, policy: Rc<dyn MeasurePolicy>) {
784 if !Rc::ptr_eq(&self.measure_policy, &policy) {
785 self.measure_policy = policy;
786 self.cache.clear();
787 self.mark_needs_measure();
788 if let Some(id) = self.id.get() {
789 cranpose_core::bubble_measure_dirty_in_composer(id);
790 }
791 }
792 }
793
794 pub fn mark_needs_measure(&self) {
796 self.needs_measure.set(true);
797 self.needs_layout.set(true);
798 }
799
800 pub fn mark_needs_layout(&self) {
802 self.needs_layout.set(true);
803 }
804
805 pub fn mark_needs_redraw(&self) {
807 self.needs_redraw.set(true);
808 if let Some(id) = self.id.get() {
809 crate::schedule_draw_repass(id);
810 }
811 crate::request_render_invalidation();
812 }
813
814 pub fn needs_measure(&self) -> bool {
816 self.needs_measure.get()
817 }
818
819 pub fn needs_layout(&self) -> bool {
821 self.needs_layout.get()
822 }
823
824 pub fn mark_needs_semantics(&self) {
826 self.needs_semantics.set(true);
827 self.forget_semantics_reach();
828 }
829
830 pub(crate) fn clear_needs_semantics(&self) {
831 self.needs_semantics.set(false);
832 self.descendant_needs_semantics.set(false);
833 }
834
835 pub fn needs_semantics(&self) -> bool {
838 self.needs_semantics.get() || self.descendant_needs_semantics.get()
839 }
840
841 pub(crate) fn semantics_changed(&self) -> bool {
844 self.needs_semantics.get()
845 }
846
847 pub fn needs_redraw(&self) -> bool {
849 self.needs_redraw.get()
850 }
851
852 pub fn clear_needs_redraw(&self) {
853 self.needs_redraw.set(false);
854 }
855
856 fn request_semantics_update(&self) {
857 let already_dirty = self.needs_semantics.replace(true);
858 if already_dirty {
859 return;
860 }
861
862 if let Some(id) = self.id.get() {
863 cranpose_core::queue_semantics_invalidation(id);
864 }
865 }
866
867 pub(crate) fn clear_needs_measure(&self) {
868 self.needs_measure.set(false);
869 }
870
871 pub(crate) fn clear_needs_layout(&self) {
874 self.needs_layout.set(false);
875 self.descendant_dirt.set(DescendantDirt::default());
876 }
877
878 pub fn mark_needs_pointer_pass(&self) {
880 self.needs_pointer_pass.set(true);
881 }
882
883 pub fn needs_pointer_pass(&self) -> bool {
885 self.needs_pointer_pass.get()
886 }
887
888 pub fn clear_needs_pointer_pass(&self) {
890 self.needs_pointer_pass.set(false);
891 }
892
893 pub fn mark_needs_focus_sync(&self) {
895 self.needs_focus_sync.set(true);
896 }
897
898 pub fn needs_focus_sync(&self) -> bool {
900 self.needs_focus_sync.get()
901 }
902
903 pub fn clear_needs_focus_sync(&self) {
905 self.needs_focus_sync.set(false);
906 }
907
908 pub fn set_node_id(&mut self, id: NodeId) {
910 if let Some(existing) = self.id.replace(Some(id))
911 && let Some(owner_context_id) = self.owner_context_id.take()
912 {
913 unregister_layout_node(owner_context_id, existing);
914 }
915 self.layout_state.borrow_mut().set_node_id(id);
916 let owner_context_id = register_layout_node(id, self);
917 self.owner_context_id.set(Some(owner_context_id));
918 self.refresh_registry_state();
919
920 self.modifier_chain.set_node_id(Some(id));
921 let invalidations = self.modifier_chain.take_invalidations();
922 self.dispatch_modifier_invalidations_with_prev(
923 &invalidations,
924 NodeCapabilities::empty(),
925 false,
926 );
927 self.modifier_slices_dirty.set(true);
929 }
930
931 pub fn node_id(&self) -> Option<NodeId> {
933 self.id.get()
934 }
935
936 pub fn set_parent(&self, parent: NodeId) {
939 self.folded_parent.set(Some(parent));
940 self.parent.set(Some(parent));
941 self.refresh_registry_state();
942 }
943
944 pub fn clear_parent(&self) {
946 self.folded_parent.set(None);
947 self.parent.set(None);
948 self.refresh_registry_state();
949 }
950
951 pub fn parent(&self) -> Option<NodeId> {
953 self.parent.get()
954 }
955
956 pub fn folded_parent(&self) -> Option<NodeId> {
958 self.folded_parent.get()
959 }
960
961 pub(crate) fn cache_handles(&self) -> &LayoutNodeCacheHandles {
962 &self.cache
963 }
964
965 pub fn resolved_modifiers(&self) -> ResolvedModifiers {
966 self.modifier_chain.resolved_modifiers()
967 }
968
969 pub(crate) fn parent_data(&self) -> cranpose_ui_layout::ParentData {
971 self.parent_data.get()
972 }
973
974 fn refresh_parent_data(&self) {
977 self.parent_data.set(crate::layout::parent_data_of(
978 self.modifier_chain.resolved_modifiers().layout_properties(),
979 ));
980 }
981
982 pub fn modifier_capabilities(&self) -> NodeCapabilities {
983 self.modifier_chain.capabilities()
984 }
985
986 pub fn is_window_root(&self) -> bool {
990 self.modifier_capabilities()
991 .contains(NodeCapabilities::WINDOW_ROOT)
992 }
993
994 pub fn modifier_child_capabilities(&self) -> NodeCapabilities {
995 self.modifier_chain.aggregate_child_capabilities()
996 }
997
998 pub fn set_debug_modifiers(&mut self, enabled: bool) {
999 self.debug_modifiers.set(enabled);
1000 self.modifier_chain.set_debug_logging(enabled);
1001 }
1002
1003 pub fn debug_modifiers_enabled(&self) -> bool {
1004 self.debug_modifiers.get()
1005 }
1006
1007 pub fn modifier_locals_handle(&self) -> Option<ModifierLocalsHandle> {
1010 self.modifier_chain.modifier_locals_handle()
1011 }
1012
1013 pub fn has_layout_modifier_nodes(&self) -> bool {
1014 self.modifier_capabilities()
1015 .contains(NodeCapabilities::LAYOUT)
1016 }
1017
1018 pub fn has_draw_modifier_nodes(&self) -> bool {
1019 self.modifier_capabilities()
1020 .contains(NodeCapabilities::DRAW)
1021 }
1022
1023 pub fn has_pointer_input_modifier_nodes(&self) -> bool {
1024 self.modifier_capabilities()
1025 .contains(NodeCapabilities::POINTER_INPUT)
1026 }
1027
1028 pub fn has_semantics_modifier_nodes(&self) -> bool {
1029 self.modifier_capabilities()
1030 .contains(NodeCapabilities::SEMANTICS)
1031 }
1032
1033 pub fn has_focus_modifier_nodes(&self) -> bool {
1034 self.modifier_capabilities()
1035 .contains(NodeCapabilities::FOCUS)
1036 }
1037
1038 fn refresh_registry_state(&self) {
1039 if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
1040 let parent = self.parent();
1041 let capabilities = self.modifier_child_capabilities();
1042 let modifier_locals = self.modifier_locals_handle();
1043 let _ = crate::render_state::with_layout_node_registry_by_app_context(
1044 owner_context_id,
1045 |registry| {
1046 registry.update_entry(id, parent, capabilities, modifier_locals);
1047 },
1048 );
1049 }
1050 }
1051
1052 pub fn modifier_slices_snapshot(&self) -> Rc<ModifierNodeSlices> {
1053 if self.modifier_slices_dirty.get() {
1054 self.update_modifier_slices_cache();
1055 }
1056 self.modifier_slices_snapshot.borrow().clone()
1057 }
1058
1059 pub fn layout_state(&self) -> LayoutState {
1061 self.layout_state.borrow().clone()
1062 }
1063
1064 pub fn measured_size(&self) -> Size {
1066 self.layout_state.borrow().size
1067 }
1068
1069 pub fn position(&self) -> Point {
1071 self.layout_state.borrow().position
1072 }
1073
1074 pub fn is_placed(&self) -> bool {
1076 self.layout_state.borrow().is_placed
1077 }
1078
1079 pub fn set_measured_size(&self, size: Size) {
1083 self.layout_state.borrow_mut().set_size(size);
1084 }
1085
1086 pub fn set_position(&self, position: Point) {
1089 self.layout_state.borrow_mut().place(position);
1090 }
1091
1092 pub fn set_content_offset(&self, offset: Point) {
1094 self.layout_state.borrow_mut().set_content_offset(offset);
1095 }
1096
1097 pub fn clear_placed(&self) {
1099 self.layout_state.borrow_mut().clear_placed();
1100 }
1101
1102 fn note_semantics_layout_change(&self) {
1103 if let Ok(mut state) = self.layout_state.try_borrow_mut() {
1104 state.note_semantics_change();
1105 }
1106 }
1107
1108 pub fn semantics_configuration(&self) -> Option<SemanticsConfiguration> {
1109 crate::modifier::collect_semantics_from_chain(self.modifier_chain.chain())
1110 }
1111
1112 fn forget_semantics_reach(&self) {
1114 self.semantics_reach.set(None);
1115 crate::modal_nodes::reach_changed(self.id.get());
1116 }
1117
1118 pub fn semantics_reach(&self) -> cranpose_foundation::SemanticsReach {
1120 if !self
1122 .modifier_capabilities()
1123 .contains(NodeCapabilities::SEMANTICS)
1124 {
1125 return cranpose_foundation::SemanticsReach::default();
1126 }
1127 if let Some(reach) = self.semantics_reach.get() {
1128 return reach;
1129 }
1130 let reach = crate::modifier::semantics_reach_of_chain(self.modifier_chain.chain());
1131 self.semantics_reach.set(Some(reach));
1132 reach
1133 }
1134
1135 pub(crate) fn modifier_chain(&self) -> &ModifierChainHandle {
1136 &self.modifier_chain
1137 }
1138
1139 pub fn with_text_field_modifier_mut<R>(
1144 &mut self,
1145 f: impl FnMut(&mut crate::TextFieldModifierNode) -> R,
1146 ) -> Option<R> {
1147 self.modifier_chain.with_text_field_modifier_mut(f)
1148 }
1149
1150 pub fn layout_state_handle(&self) -> &Rc<RefCell<LayoutState>> {
1153 &self.layout_state
1154 }
1155
1156 pub(crate) fn coordinator_geometry(&self) -> &crate::modifier::CoordinatorGeometry {
1157 &self.coordinator_geometry
1158 }
1159
1160 pub(crate) fn layout_runtime_state_handle(&self) -> Rc<RefCell<LayoutRuntimeState>> {
1161 self.layout_runtime_state.clone()
1162 }
1163
1164 #[cfg(test)]
1165 pub(crate) fn layout_runtime_debug_stats(&self) -> LayoutRuntimeDebugStats {
1166 self.layout_runtime_state.borrow().debug_stats()
1167 }
1168}
1169impl Clone for LayoutNode {
1170 fn clone(&self) -> Self {
1171 let mut node = Self {
1172 #[cfg(feature = "inspection")]
1173 source_trace: self.source_trace.clone(),
1174 modifier: self.modifier.clone(),
1175 modifier_chain: ModifierChainHandle::for_layout_direction(
1176 self.modifier_chain.layout_direction(),
1177 ),
1178 measure_policy: self.measure_policy.clone(),
1179 density: self.density,
1180 children: self.children.clone(),
1181 cache: self.cache.clone(),
1182 needs_measure: Cell::new(self.needs_measure.get()),
1183 needs_layout: Cell::new(self.needs_layout.get()),
1184 descendant_dirt: Cell::new(self.descendant_dirt.get()),
1185 parent_data: Cell::new(self.parent_data.get()),
1186 needs_semantics: Cell::new(self.needs_semantics.get()),
1187 descendant_needs_semantics: Cell::new(self.descendant_needs_semantics.get()),
1188 semantics_reach: Cell::new(None),
1189 needs_redraw: Cell::new(self.needs_redraw.get()),
1190 needs_pointer_pass: Cell::new(self.needs_pointer_pass.get()),
1191 needs_focus_sync: Cell::new(self.needs_focus_sync.get()),
1192 parent: Cell::new(self.parent.get()),
1193 folded_parent: Cell::new(self.folded_parent.get()),
1194 id: Cell::new(None),
1195 owner_context_id: Cell::new(None),
1196 debug_modifiers: Cell::new(self.debug_modifiers.get()),
1197 is_virtual: self.is_virtual,
1198 virtual_children_count: Cell::new(self.virtual_children_count.get()),
1199 modifier_slices_snapshot: RefCell::new(Rc::default()),
1200 modifier_slices_dirty: Cell::new(true),
1201 layout_state: self.layout_state.clone(),
1202 layout_runtime_state: self.layout_runtime_state.clone(),
1203 coordinator_geometry: Rc::clone(&self.coordinator_geometry),
1204 };
1205 node.sync_modifier_chain(false);
1206 node
1207 }
1208}
1209
1210impl Node for LayoutNode {
1211 fn mount(&mut self) {
1212 let (chain, mut context) = self.modifier_chain.chain_and_context_mut();
1213 chain.repair_chain();
1214 chain.attach_nodes(&mut *context);
1215 crate::modal_nodes::reach_changed(self.id.get());
1216 }
1217
1218 fn unmount(&mut self) {
1219 self.modifier_chain.chain_mut().detach_nodes();
1220 }
1221
1222 fn set_node_id(&mut self, id: NodeId) {
1223 LayoutNode::set_node_id(self, id);
1224 }
1225
1226 fn insert_child(&mut self, child: NodeId) -> bool {
1227 if self.children.contains(&child) {
1228 return false;
1229 }
1230 if is_virtual_node(child) {
1231 let count = self.virtual_children_count.get();
1232 self.virtual_children_count.set(count + 1);
1233 }
1234 self.children.push(child);
1235 self.cache.clear();
1236 self.mark_needs_measure();
1237 self.note_semantics_layout_change();
1238 true
1239 }
1240
1241 fn remove_child(&mut self, child: NodeId) -> bool {
1242 let before = self.children.len();
1243 self.children.retain(|&id| id != child);
1244 let removed = self.children.len() < before;
1245 if removed {
1246 if is_virtual_node(child) {
1247 let count = self.virtual_children_count.get();
1248 if count > 0 {
1249 self.virtual_children_count.set(count - 1);
1250 }
1251 }
1252 self.cache.clear();
1253 self.mark_needs_measure();
1254 self.note_semantics_layout_change();
1255 }
1256 removed
1257 }
1258
1259 fn move_child(&mut self, from: usize, to: usize) {
1260 if from == to || from >= self.children.len() {
1261 return;
1262 }
1263 let child = self.children.remove(from);
1264 let target = to.min(self.children.len());
1265 self.children.insert(target, child);
1266 self.cache.clear();
1267 self.mark_needs_measure();
1268 self.note_semantics_layout_change();
1269 }
1270
1271 fn update_children(&mut self, children: &[NodeId]) {
1272 self.children.clear();
1273 self.children.extend_from_slice(children);
1274 self.cache.clear();
1275 self.mark_needs_measure();
1276 self.note_semantics_layout_change();
1277 }
1278
1279 fn collect_children_into(&self, out: &mut smallvec::SmallVec<[NodeId; 8]>) {
1280 out.clear();
1281 out.extend_from_slice(&self.children);
1282 }
1283
1284 fn owned_child_index(&self, child: NodeId) -> Option<usize> {
1285 self.children.iter().position(|&id| id == child)
1286 }
1287
1288 fn on_attached_to_parent(&mut self, parent: NodeId) {
1289 self.set_parent(parent);
1290 }
1291
1292 fn on_removed_from_parent(&mut self) {
1293 self.clear_parent();
1294 }
1295
1296 fn parent(&self) -> Option<NodeId> {
1297 self.parent.get()
1298 }
1299
1300 fn mark_needs_layout(&self) {
1301 self.needs_layout.set(true);
1302 }
1303
1304 fn needs_layout(&self) -> bool {
1305 self.needs_layout.get()
1306 }
1307
1308 fn mark_needs_measure(&self) {
1309 self.needs_measure.set(true);
1310 self.needs_layout.set(true);
1311 }
1312
1313 fn needs_measure(&self) -> bool {
1314 self.needs_measure.get()
1315 }
1316
1317 fn mark_descendant_needs_layout(&self, measure: bool) {
1318 self.descendant_dirt
1319 .set(self.descendant_dirt.get().marked(measure));
1320 if measure {
1321 self.cache.forget_intrinsics();
1322 }
1323 }
1324
1325 fn descendant_needs_layout(&self) -> bool {
1326 self.descendant_dirt.get().layout
1327 }
1328
1329 fn is_virtual(&self) -> bool {
1330 self.is_virtual
1331 }
1332
1333 fn descendant_needs_measure(&self) -> bool {
1334 self.descendant_dirt.get().measure
1335 }
1336
1337 fn mark_needs_semantics(&self) {
1338 self.needs_semantics.set(true);
1339 self.forget_semantics_reach();
1340 }
1341
1342 fn mark_descendant_needs_semantics(&self) {
1343 self.descendant_needs_semantics.set(true);
1344 }
1345
1346 fn needs_semantics(&self) -> bool {
1347 LayoutNode::needs_semantics(self)
1348 }
1349
1350 fn set_parent_for_bubbling(&mut self, parent: NodeId) {
1351 if self.parent.get().is_none() {
1352 self.parent.set(Some(parent));
1353 }
1354 }
1355
1356 fn recycle_key(&self) -> Option<TypeId> {
1357 Some(TypeId::of::<Self>())
1358 }
1359
1360 fn recycle_pool_limit(&self) -> Option<usize> {
1361 Some(RECYCLED_LAYOUT_NODE_POOL_LIMIT)
1362 }
1363
1364 fn prepare_for_recycle(&mut self) {
1365 *self = Self::new_recycled_shell(self.is_virtual);
1366 }
1367
1368 fn rehouse_for_recycle(&self) -> Option<Box<dyn cranpose_core::Node>> {
1369 Some(Box::new(Self::new_recycled_shell(self.is_virtual)))
1370 }
1371
1372 fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn cranpose_core::Node>> {
1373 let mut previous = std::mem::replace(self, Self::new_recycled_shell(self.is_virtual));
1374 let node_id = previous.id.replace(None);
1375 let parent = previous.parent.get();
1376 let folded_parent = previous.folded_parent.get();
1377 let debug_modifiers = previous.debug_modifiers.get();
1378 let needs_measure = previous.needs_measure.get();
1379 let needs_layout = previous.needs_layout.get();
1380 let needs_semantics = previous.needs_semantics.get();
1381 let descendant_needs_semantics = previous.descendant_needs_semantics.get();
1382 let needs_redraw = previous.needs_redraw.get();
1383 let needs_pointer_pass = previous.needs_pointer_pass.get();
1384 let needs_focus_sync = previous.needs_focus_sync.get();
1385 let virtual_children_count = previous.virtual_children_count.get();
1386 let children = previous.children.to_vec();
1387 let modifier = previous.modifier.rehouse_for_live_compaction();
1388 let measure_policy = previous.measure_policy.clone();
1389 let layout_state = previous.layout_state.clone();
1390 let layout_runtime_state = previous.layout_runtime_state.clone();
1391 let coordinator_geometry = Rc::clone(&previous.coordinator_geometry);
1392
1393 previous.modifier_chain.chain_mut().detach_nodes();
1394
1395 let mut compact = Self::new_with_virtual(modifier, measure_policy, previous.is_virtual);
1396 compact.children = children;
1397 #[cfg(feature = "inspection")]
1398 {
1399 compact.source_trace = previous.source_trace.clone();
1400 }
1401 compact.parent.set(parent);
1402 compact.folded_parent.set(folded_parent);
1403 compact.id.set(node_id);
1404 compact.debug_modifiers.set(debug_modifiers);
1405 compact.needs_measure.set(needs_measure);
1406 compact.needs_layout.set(needs_layout);
1407 compact.needs_semantics.set(needs_semantics);
1408 compact
1409 .descendant_needs_semantics
1410 .set(descendant_needs_semantics);
1411 compact.needs_redraw.set(needs_redraw);
1412 compact.needs_pointer_pass.set(needs_pointer_pass);
1413 compact.needs_focus_sync.set(needs_focus_sync);
1414 compact.virtual_children_count.set(virtual_children_count);
1415 compact.layout_state = layout_state;
1416 compact.layout_runtime_state = layout_runtime_state;
1417 compact.coordinator_geometry = coordinator_geometry;
1418 compact.sync_modifier_chain(false);
1419 if let Some(id) = node_id {
1420 let owner_context_id = register_layout_node(id, &compact);
1421 compact.owner_context_id.set(Some(owner_context_id));
1422 }
1423
1424 Some(Box::new(compact))
1425 }
1426}
1427
1428impl Drop for LayoutNode {
1429 fn drop(&mut self) {
1430 if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
1431 unregister_layout_node(owner_context_id, id);
1432 }
1433 }
1434}
1435
1436const MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY: usize = 128;
1437const VIRTUAL_NODE_ID_START: NodeId = 0xC0000000;
1438
1439#[cfg(test)]
1440#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1441struct LayoutNodeRegistryDebugStats {
1442 len: usize,
1443 capacity: usize,
1444}
1445
1446struct LayoutNodeRegistryEntry {
1447 parent: Option<NodeId>,
1448 modifier_child_capabilities: NodeCapabilities,
1449 modifier_locals: Option<ModifierLocalsHandle>,
1450 is_virtual: bool,
1451}
1452
1453pub(crate) struct LayoutNodeRegistryState {
1454 entries: RefCell<HashMap<NodeId, LayoutNodeRegistryEntry>>,
1455 virtual_node_id_counter: Cell<NodeId>,
1456}
1457
1458impl LayoutNodeRegistryState {
1459 pub(crate) fn new() -> Self {
1460 Self {
1461 entries: RefCell::new(HashMap::new()),
1462 virtual_node_id_counter: Cell::new(VIRTUAL_NODE_ID_START),
1463 }
1464 }
1465
1466 fn register(&self, id: NodeId, node: &LayoutNode) {
1467 self.entries.borrow_mut().insert(
1468 id,
1469 LayoutNodeRegistryEntry {
1470 parent: node.parent(),
1471 modifier_child_capabilities: node.modifier_child_capabilities(),
1472 modifier_locals: node.modifier_locals_handle(),
1473 is_virtual: node.is_virtual(),
1474 },
1475 );
1476 }
1477
1478 fn unregister(&self, id: NodeId) {
1479 let mut entries = self.entries.borrow_mut();
1480 entries.remove(&id);
1481 let should_shrink = (entries.len() <= MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY
1482 && entries.capacity() > MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1483 || entries.capacity()
1484 > entries
1485 .len()
1486 .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1487 .saturating_mul(4);
1488 if should_shrink {
1489 let retained = entries
1490 .len()
1491 .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY);
1492 let mut rebuilt = HashMap::new();
1493 rebuilt.reserve(retained);
1494 rebuilt.extend(entries.drain());
1495 *entries = rebuilt;
1496 }
1497 }
1498
1499 fn update_entry(
1500 &self,
1501 id: NodeId,
1502 parent: Option<NodeId>,
1503 modifier_child_capabilities: NodeCapabilities,
1504 modifier_locals: Option<ModifierLocalsHandle>,
1505 ) {
1506 if let Some(entry) = self.entries.borrow_mut().get_mut(&id) {
1507 entry.parent = parent;
1508 entry.modifier_child_capabilities = modifier_child_capabilities;
1509 entry.modifier_locals = modifier_locals;
1510 }
1511 }
1512
1513 #[cfg(test)]
1514 fn stats(&self) -> LayoutNodeRegistryDebugStats {
1515 let entries = self.entries.borrow();
1516 LayoutNodeRegistryDebugStats {
1517 len: entries.len(),
1518 capacity: entries.capacity(),
1519 }
1520 }
1521
1522 fn is_virtual_node(&self, id: NodeId) -> bool {
1523 self.entries
1524 .borrow()
1525 .get(&id)
1526 .is_some_and(|entry| entry.is_virtual)
1527 }
1528
1529 fn allocate_virtual_node_id(&self) -> NodeId {
1530 let id = self.virtual_node_id_counter.get();
1531 self.virtual_node_id_counter.set(id.wrapping_add(1));
1532 id
1533 }
1534
1535 fn resolve_modifier_local_from_parent_chain(
1536 &self,
1537 start: Option<NodeId>,
1538 token: &ModifierLocalToken,
1539 ) -> Option<ResolvedModifierLocal> {
1540 let mut current = start;
1541 while let Some(parent_id) = current {
1542 let (next_parent, resolved) = {
1543 let entries = self.entries.borrow();
1544 if let Some(entry) = entries.get(&parent_id) {
1545 let resolved = entry
1546 .modifier_child_capabilities
1547 .contains(NodeCapabilities::MODIFIER_LOCALS)
1548 .then_some(entry.modifier_locals.as_ref())
1549 .flatten()
1550 .and_then(|locals| locals.borrow().resolve(token))
1551 .map(|value| value.with_source(ModifierLocalSource::Ancestor));
1552 (entry.parent, resolved)
1553 } else {
1554 (None, None)
1555 }
1556 };
1557 if let Some(value) = resolved {
1558 return Some(value);
1559 }
1560 current = next_parent;
1561 }
1562 None
1563 }
1564}
1565
1566pub(crate) fn register_layout_node(
1567 id: NodeId,
1568 node: &LayoutNode,
1569) -> crate::render_state::AppContextId {
1570 let owner_context_id = crate::render_state::current_app_context_id();
1571 let _ = crate::render_state::with_layout_node_registry_by_app_context(
1572 owner_context_id,
1573 |registry| {
1574 registry.register(id, node);
1575 },
1576 );
1577 owner_context_id
1578}
1579
1580pub(crate) fn unregister_layout_node(
1581 owner_context_id: crate::render_state::AppContextId,
1582 id: NodeId,
1583) {
1584 let _ = crate::render_state::with_layout_node_registry_by_app_context(
1585 owner_context_id,
1586 |registry| {
1587 registry.unregister(id);
1588 },
1589 );
1590}
1591
1592#[cfg(test)]
1593fn layout_node_registry_stats() -> LayoutNodeRegistryDebugStats {
1594 crate::render_state::with_layout_node_registry(LayoutNodeRegistryState::stats)
1595}
1596
1597pub(crate) fn is_virtual_node(id: NodeId) -> bool {
1598 crate::render_state::with_layout_node_registry(|registry| registry.is_virtual_node(id))
1599}
1600
1601pub(crate) fn allocate_virtual_node_id() -> NodeId {
1602 crate::render_state::with_layout_node_registry(
1603 LayoutNodeRegistryState::allocate_virtual_node_id,
1604 )
1605}
1606
1607fn resolve_modifier_local_from_parent_chain(
1608 start: Option<NodeId>,
1609 token: &ModifierLocalToken,
1610) -> Option<ResolvedModifierLocal> {
1611 crate::render_state::with_layout_node_registry(|registry| {
1612 registry.resolve_modifier_local_from_parent_chain(start, token)
1613 })
1614}
1615
1616#[cfg(test)]
1617#[path = "tests/layout_node_tests.rs"]
1618mod tests;