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 hold: Option<cranpose_ui_layout::ConstraintsHold>,
249}
250
251#[derive(Clone, Copy, Debug)]
252pub enum IntrinsicKind {
253 MinWidth(f32),
254 MaxWidth(f32),
255 MinHeight(f32),
256 MaxHeight(f32),
257}
258
259impl IntrinsicKind {
260 fn discriminant(&self) -> u8 {
261 match self {
262 IntrinsicKind::MinWidth(_) => 0,
263 IntrinsicKind::MaxWidth(_) => 1,
264 IntrinsicKind::MinHeight(_) => 2,
265 IntrinsicKind::MaxHeight(_) => 3,
266 }
267 }
268
269 fn value_bits(&self) -> u32 {
270 match self {
271 IntrinsicKind::MinWidth(value)
272 | IntrinsicKind::MaxWidth(value)
273 | IntrinsicKind::MinHeight(value)
274 | IntrinsicKind::MaxHeight(value) => value.to_bits(),
275 }
276 }
277}
278
279impl PartialEq for IntrinsicKind {
280 fn eq(&self, other: &Self) -> bool {
281 self.discriminant() == other.discriminant() && self.value_bits() == other.value_bits()
282 }
283}
284
285impl Eq for IntrinsicKind {}
286
287impl Hash for IntrinsicKind {
288 fn hash<H: Hasher>(&self, state: &mut H) {
289 self.discriminant().hash(state);
290 self.value_bits().hash(state);
291 }
292}
293
294#[derive(Default)]
295struct NodeCacheState {
296 epoch: u64,
297 measurement: Option<MeasurementCacheEntry>,
298 intrinsics: Vec<(IntrinsicKind, f32)>,
299 intrinsics_read: bool,
301}
302
303#[derive(Default)]
304pub(crate) struct LayoutNodeCacheHandles {
305 state: Rc<RefCell<NodeCacheState>>,
306}
307
308impl Clone for LayoutNodeCacheHandles {
309 fn clone(&self) -> Self {
310 Self {
311 state: Rc::clone(&self.state),
312 }
313 }
314
315 fn clone_from(&mut self, source: &Self) {
316 if !Rc::ptr_eq(&self.state, &source.state) {
317 self.state = Rc::clone(&source.state);
318 }
319 }
320}
321
322impl LayoutNodeCacheHandles {
323 pub(crate) fn clear(&self) {
324 let mut state = self.state.borrow_mut();
325 state.measurement = None;
326 state.intrinsics.clear();
327 state.intrinsics_read = false;
328 state.epoch = 0;
329 }
330
331 pub(crate) fn activate(&self, epoch: u64) {
332 let mut state = self.state.borrow_mut();
333 if state.epoch != epoch {
334 state.measurement = None;
335 state.intrinsics.clear();
336 state.intrinsics_read = false;
337 state.epoch = epoch;
338 }
339 }
340
341 pub(crate) fn forget_intrinsics(&self) {
345 self.state.borrow_mut().intrinsics.clear();
346 }
347
348 pub(crate) fn epoch(&self) -> u64 {
349 self.state.borrow().epoch
350 }
351
352 pub(crate) fn get_measurement(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
356 let state = self.state.borrow();
357 state
358 .measurement
359 .as_ref()
360 .filter(|entry| {
361 entry.constraints == constraints
362 || entry.hold.is_some_and(|hold| hold.contains(constraints))
363 })
364 .map(|entry| Rc::clone(&entry.measured))
365 }
366
367 pub(crate) fn measured_constraints(&self) -> Option<Constraints> {
369 self.state
370 .borrow()
371 .measurement
372 .as_ref()
373 .map(|entry| entry.constraints)
374 }
375
376 pub(crate) fn has_intrinsics(&self) -> bool {
378 self.state.borrow().intrinsics_read
379 }
380
381 pub(crate) fn hold_of(
383 &self,
384 measured: &Rc<MeasuredNode>,
385 ) -> Option<cranpose_ui_layout::ConstraintsHold> {
386 self.state
387 .borrow()
388 .measurement
389 .as_ref()
390 .filter(|entry| Rc::ptr_eq(&entry.measured, measured))
391 .and_then(|entry| entry.hold)
392 }
393
394 pub(crate) fn store_measurement(&self, constraints: Constraints, measured: Rc<MeasuredNode>) {
397 let mut state = self.state.borrow_mut();
398 let hold = state
399 .measurement
400 .as_ref()
401 .filter(|entry| Rc::ptr_eq(&entry.measured, &measured))
402 .and_then(|entry| entry.hold);
403 state.measurement = Some(MeasurementCacheEntry {
404 constraints,
405 measured,
406 hold,
407 });
408 }
409
410 pub(crate) fn store_held_measurement(
413 &self,
414 constraints: Constraints,
415 measured: Rc<MeasuredNode>,
416 hold: Option<cranpose_ui_layout::ConstraintsHold>,
417 ) {
418 self.state.borrow_mut().measurement = Some(MeasurementCacheEntry {
419 constraints,
420 measured,
421 hold,
422 });
423 }
424
425 pub(crate) fn get_intrinsic(&self, kind: &IntrinsicKind) -> Option<f32> {
426 let state = self.state.borrow();
427 state
428 .intrinsics
429 .iter()
430 .find(|(stored_kind, _)| stored_kind == kind)
431 .map(|(_, value)| *value)
432 }
433
434 pub(crate) fn store_intrinsic(&self, kind: IntrinsicKind, value: f32) {
435 let mut state = self.state.borrow_mut();
436 state.intrinsics_read = true;
437 if let Some((_, existing)) = state
438 .intrinsics
439 .iter_mut()
440 .find(|(stored_kind, _)| stored_kind == &kind)
441 {
442 *existing = value;
443 } else {
444 state.intrinsics.push((kind, value));
445 }
446 }
447}
448
449#[repr(C)]
456pub struct LayoutNode {
457 cache: LayoutNodeCacheHandles,
458 layout_state: Rc<RefCell<LayoutState>>,
459 layout_runtime_state: Rc<RefCell<LayoutRuntimeState>>,
460 pub children: Vec<NodeId>,
462 parent_data: Cell<cranpose_ui_layout::ParentData>,
465 needs_measure: Cell<bool>,
466 needs_layout: Cell<bool>,
467 descendant_dirt: Cell<DescendantDirt>,
469
470 pub measure_policy: Rc<dyn MeasurePolicy>,
471 coordinator_geometry: Rc<crate::modifier::CoordinatorGeometry>,
474 density: crate::density::Density,
475 is_virtual: bool,
476 needs_semantics: Cell<bool>,
477 descendant_needs_semantics: Cell<bool>,
481 needs_redraw: Cell<bool>,
482 needs_pointer_pass: Cell<bool>,
483 needs_focus_sync: Cell<bool>,
484 modifier_slices_dirty: Cell<bool>,
485 debug_modifiers: Cell<bool>,
486
487 id: Cell<Option<NodeId>>,
488 parent: Cell<Option<NodeId>>,
489 folded_parent: Cell<Option<NodeId>>,
490 modifier_slices_snapshot: RefCell<Rc<ModifierNodeSlices>>,
491
492 semantics_reach: Cell<Option<cranpose_foundation::SemanticsReach>>,
496 virtual_children_count: Cell<usize>,
497 owner_context_id: Cell<Option<crate::render_state::AppContextId>>,
498 modifier_chain: ModifierChainHandle,
499 pub modifier: Modifier,
500 #[cfg(feature = "inspection")]
502 pub source_trace: Rc<[cranpose_core::source_trace::SourceLocation]>,
503}
504
505pub(crate) const RECYCLED_LAYOUT_NODE_POOL_LIMIT: usize = 128;
506
507thread_local! {
508 static EMPTY_MEASURE_POLICY: Rc<dyn MeasurePolicy> =
509 Rc::new(crate::layout::policies::EmptyMeasurePolicy);
510}
511
512fn empty_measure_policy() -> Rc<dyn MeasurePolicy> {
513 EMPTY_MEASURE_POLICY.with(Rc::clone)
514}
515
516impl LayoutNode {
517 pub fn new(modifier: Modifier, measure_policy: Rc<dyn MeasurePolicy>) -> Self {
518 Self::new_with_virtual(modifier, measure_policy, false)
519 }
520
521 pub fn new_virtual() -> Self {
524 Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), true)
525 }
526
527 fn new_recycled_shell(is_virtual: bool) -> Self {
528 let mut shell =
529 Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), is_virtual);
530 shell.needs_measure.set(false);
531 shell.needs_layout.set(false);
532 shell.needs_semantics.set(false);
533 shell.descendant_needs_semantics.set(false);
534 shell.needs_redraw.set(false);
535 shell.needs_pointer_pass.set(false);
536 shell.needs_focus_sync.set(false);
537 shell.parent.set(None);
538 shell.folded_parent.set(None);
539 shell.id.set(None);
540 shell.owner_context_id.set(None);
541 shell.debug_modifiers.set(false);
542 shell.virtual_children_count.set(0);
543 shell.modifier_slices_dirty = Cell::new(true);
544 shell
545 }
546
547 fn new_with_virtual(
548 modifier: Modifier,
549 measure_policy: Rc<dyn MeasurePolicy>,
550 is_virtual: bool,
551 ) -> Self {
552 let layout_runtime_state = Rc::new(RefCell::new(LayoutRuntimeState::new(Rc::clone(
553 &measure_policy,
554 ))));
555 let mut node = Self {
556 #[cfg(feature = "inspection")]
557 source_trace: cranpose_core::source_trace::current_source_trace(),
558 modifier,
559 modifier_chain: ModifierChainHandle::new(),
560 measure_policy,
561 density: crate::density::Density::default(),
562 children: Vec::new(),
563 cache: LayoutNodeCacheHandles::default(),
564 needs_measure: Cell::new(true),
565 needs_layout: Cell::new(true),
566 descendant_dirt: Cell::default(),
567 parent_data: Cell::default(),
568 needs_semantics: Cell::new(true),
569 descendant_needs_semantics: Cell::new(false),
570 semantics_reach: Cell::new(None),
571 needs_redraw: Cell::new(true),
572 needs_pointer_pass: Cell::new(false),
573 needs_focus_sync: Cell::new(false),
574 parent: Cell::new(None),
575 folded_parent: Cell::new(None),
576 id: Cell::new(None),
577 owner_context_id: Cell::new(None),
578 debug_modifiers: Cell::new(false),
579 is_virtual,
580 virtual_children_count: Cell::new(0),
581 modifier_slices_snapshot: RefCell::new(Rc::default()),
582 modifier_slices_dirty: Cell::new(true),
583 layout_state: Rc::new(RefCell::new(LayoutState::default())),
584 layout_runtime_state,
585 coordinator_geometry: Rc::default(),
586 };
587 node.sync_modifier_chain(false);
588 node
589 }
590
591 pub fn set_modifier(&mut self, modifier: Modifier) {
592 #[cfg(feature = "inspection")]
593 {
594 let trace = cranpose_core::source_trace::current_source_trace();
595 if !trace.is_empty() {
596 self.source_trace = trace;
597 }
598 }
599 if self.modifier == modifier
603 && !self
604 .modifier_capabilities()
605 .contains(NodeCapabilities::MODIFIER_LOCALS)
606 {
607 return;
608 }
609 let text_only = self
612 .modifier
613 .differs_only_in::<crate::text_modifier_node::TextModifierElement>(&modifier);
614 let modifier_changed = text_only || !self.modifier.structural_eq(&modifier);
615 self.modifier = modifier;
616 self.sync_modifier_chain(text_only);
617 if modifier_changed {
618 self.cache.clear();
619 self.request_semantics_update();
620 }
621 }
622
623 fn sync_modifier_chain(&mut self, text_only: bool) {
627 let prev_caps = self.modifier_capabilities();
628 let start_parent = self.parent();
629 let mut resolver = move |token: &ModifierLocalToken| {
630 resolve_modifier_local_from_parent_chain(start_parent, token)
631 };
632 self.modifier_chain
633 .set_debug_logging(self.debug_modifiers.get());
634 self.modifier_chain.set_node_id(self.id.get());
635 let in_place = text_only
636 && self
637 .modifier_chain
638 .update_elements_in_place::<crate::text_modifier_node::TextModifierElement>(
639 &self.modifier,
640 );
641 let modifier_local_invalidations = if in_place {
642 ModifierInvalidations::new()
643 } else {
644 let invalidations = self
645 .modifier_chain
646 .update_with_resolver(&self.modifier, &mut resolver);
647 self.refresh_parent_data();
648 invalidations
649 };
650 if prev_caps.contains(NodeCapabilities::WINDOW_ROOT)
651 != self
652 .modifier_capabilities()
653 .contains(NodeCapabilities::WINDOW_ROOT)
654 {
655 self.note_semantics_layout_change();
656 }
657 if !in_place {
660 self.forget_semantics_reach();
661 }
662 let keep_slices =
663 text_only && !self.modifier_slices_dirty.get() && self.point_slices_at_text();
664 if !keep_slices {
665 self.modifier_slices_dirty.set(true);
666 }
667
668 let mut invalidations = self.modifier_chain.take_invalidations();
669 invalidations.extend(modifier_local_invalidations);
670 self.dispatch_modifier_invalidations_with_prev(&invalidations, prev_caps, keep_slices);
671 if !in_place {
674 self.refresh_registry_state();
675 }
676 }
677
678 fn point_slices_at_text(&self) -> bool {
681 let mut layout = None;
682 self.modifier_chain.chain().for_each_forward_matching(
683 NodeCapabilities::LAYOUT,
684 |node_ref| {
685 node_ref.with_node(|node| {
686 if layout.is_none()
687 && let Some(text) = node
688 .as_any()
689 .downcast_ref::<crate::text_modifier_node::TextModifierNode>()
690 {
691 layout = Some(text.prepared_layout_handle());
692 }
693 });
694 },
695 );
696 let Some(layout) = layout else {
697 return false;
698 };
699 Rc::make_mut(&mut *self.modifier_slices_snapshot.borrow_mut()).replace_text_layout(layout);
700 true
701 }
702
703 fn update_modifier_slices_cache(&self) {
704 let mut snapshot = self.modifier_slices_snapshot.borrow_mut();
705 crate::modifier::collect_modifier_slices_into_shared(
706 self.modifier_chain.chain(),
707 &mut snapshot,
708 &self.coordinator_geometry,
709 self.density.density(),
710 );
711 self.modifier_slices_dirty.set(false);
712 }
713
714 #[cfg(test)]
716 pub(crate) fn modifier_slices_ready(&self) -> bool {
717 !self.modifier_slices_dirty.get()
718 }
719
720 pub(crate) fn mark_modifier_slices_dirty(&self) {
721 self.modifier_slices_dirty.set(true);
722 }
723
724 #[cfg(test)]
725 fn dispatch_modifier_invalidations(&self, invalidations: &[ModifierInvalidation]) {
726 self.dispatch_modifier_invalidations_with_prev(
727 invalidations,
728 NodeCapabilities::empty(),
729 false,
730 );
731 }
732
733 fn dispatch_modifier_invalidations_with_prev(
736 &self,
737 invalidations: &[ModifierInvalidation],
738 prev_caps: NodeCapabilities,
739 keep_slices: bool,
740 ) {
741 let curr_caps = self.modifier_capabilities();
742 for invalidation in invalidations {
743 if !keep_slices {
744 self.modifier_slices_dirty.set(true);
745 }
746 let has_capability =
747 |capability| curr_caps.contains(capability) || prev_caps.contains(capability);
748 match invalidation.kind() {
749 InvalidationKind::Layout => {
750 if has_capability(NodeCapabilities::LAYOUT) {
751 self.mark_needs_measure();
752 if let Some(id) = self.id.get() {
753 log_layout_invalidation_dispatch(
754 id,
755 invalidation,
756 curr_caps,
757 prev_caps,
758 &self.modifier,
759 );
760 let inside_composition =
761 cranpose_core::composer_context::try_with_composer(|_| ())
762 .is_some();
763 if inside_composition {
764 cranpose_core::bubble_measure_dirty_in_composer(id);
765 } else {
766 crate::schedule_layout_repass(id);
767 }
768 }
769 }
770 }
771 InvalidationKind::Draw => {
772 if has_capability(NodeCapabilities::DRAW)
773 || invalidation.capabilities().contains(NodeCapabilities::DRAW)
774 {
775 self.mark_needs_redraw();
776 }
777 }
778 InvalidationKind::PointerInput => {
779 if has_capability(NodeCapabilities::POINTER_INPUT) {
780 self.mark_needs_pointer_pass();
781 crate::request_pointer_invalidation();
782 if let Some(id) = self.id.get() {
783 crate::schedule_pointer_repass(id);
784 }
785 }
786 }
787 InvalidationKind::Semantics => {
788 self.request_semantics_update();
789 }
790 InvalidationKind::Focus => {
791 if has_capability(NodeCapabilities::FOCUS) {
792 self.mark_needs_focus_sync();
793 crate::request_focus_invalidation();
794 if let Some(id) = self.id.get() {
795 crate::schedule_focus_invalidation(id);
796 }
797 }
798 }
799 }
800 }
801 }
802
803 pub fn density(&self) -> crate::density::Density {
805 self.density
806 }
807
808 pub fn set_density(&mut self, density: crate::density::Density) {
810 if self.density != density {
811 self.density = density;
812 self.cache.clear();
813 self.modifier_slices_dirty.set(true);
814 self.mark_needs_measure();
815 }
816 }
817
818 pub fn set_layout_direction(&mut self, direction: crate::LayoutDirection) {
820 if self.modifier_chain.set_layout_direction(direction) {
821 self.refresh_parent_data();
822 self.cache.clear();
823 self.modifier_slices_dirty.set(true);
824 self.mark_needs_measure();
825 }
826 }
827
828 pub fn set_measure_policy(&mut self, policy: Rc<dyn MeasurePolicy>) {
829 if !Rc::ptr_eq(&self.measure_policy, &policy) {
830 self.measure_policy = policy;
831 self.cache.clear();
832 self.mark_needs_measure();
833 if let Some(id) = self.id.get() {
834 cranpose_core::bubble_measure_dirty_in_composer(id);
835 }
836 }
837 }
838
839 pub fn mark_needs_measure(&self) {
841 self.needs_measure.set(true);
842 self.needs_layout.set(true);
843 }
844
845 pub fn mark_needs_layout(&self) {
847 self.needs_layout.set(true);
848 }
849
850 pub fn mark_needs_redraw(&self) {
852 self.needs_redraw.set(true);
853 if let Some(id) = self.id.get() {
854 crate::schedule_draw_repass(id);
855 }
856 crate::request_render_invalidation();
857 }
858
859 pub fn needs_measure(&self) -> bool {
861 self.needs_measure.get()
862 }
863
864 pub fn needs_layout(&self) -> bool {
866 self.needs_layout.get()
867 }
868
869 pub fn mark_needs_semantics(&self) {
871 self.needs_semantics.set(true);
872 self.forget_semantics_reach();
873 }
874
875 pub(crate) fn clear_needs_semantics(&self) {
876 self.needs_semantics.set(false);
877 self.descendant_needs_semantics.set(false);
878 }
879
880 pub fn needs_semantics(&self) -> bool {
883 self.needs_semantics.get() || self.descendant_needs_semantics.get()
884 }
885
886 pub(crate) fn semantics_changed(&self) -> bool {
889 self.needs_semantics.get()
890 }
891
892 pub fn needs_redraw(&self) -> bool {
894 self.needs_redraw.get()
895 }
896
897 pub fn clear_needs_redraw(&self) {
898 self.needs_redraw.set(false);
899 }
900
901 fn request_semantics_update(&self) {
902 let already_dirty = self.needs_semantics.replace(true);
903 if already_dirty {
904 return;
905 }
906
907 if let Some(id) = self.id.get() {
908 cranpose_core::queue_semantics_invalidation(id);
909 }
910 }
911
912 pub(crate) fn clear_needs_measure(&self) {
913 self.needs_measure.set(false);
914 }
915
916 pub(crate) fn clear_needs_layout(&self) {
919 self.needs_layout.set(false);
920 self.descendant_dirt.set(DescendantDirt::default());
921 }
922
923 pub fn mark_needs_pointer_pass(&self) {
925 self.needs_pointer_pass.set(true);
926 }
927
928 pub fn needs_pointer_pass(&self) -> bool {
930 self.needs_pointer_pass.get()
931 }
932
933 pub fn clear_needs_pointer_pass(&self) {
935 self.needs_pointer_pass.set(false);
936 }
937
938 pub fn mark_needs_focus_sync(&self) {
940 self.needs_focus_sync.set(true);
941 }
942
943 pub fn needs_focus_sync(&self) -> bool {
945 self.needs_focus_sync.get()
946 }
947
948 pub fn clear_needs_focus_sync(&self) {
950 self.needs_focus_sync.set(false);
951 }
952
953 pub fn set_node_id(&mut self, id: NodeId) {
955 if let Some(existing) = self.id.replace(Some(id))
956 && let Some(owner_context_id) = self.owner_context_id.take()
957 {
958 unregister_layout_node(owner_context_id, existing);
959 }
960 self.layout_state.borrow_mut().set_node_id(id);
961 let owner_context_id = register_layout_node(id, self);
962 self.owner_context_id.set(Some(owner_context_id));
963 self.refresh_registry_state();
964
965 self.modifier_chain.set_node_id(Some(id));
966 let invalidations = self.modifier_chain.take_invalidations();
967 self.dispatch_modifier_invalidations_with_prev(
968 &invalidations,
969 NodeCapabilities::empty(),
970 false,
971 );
972 self.modifier_slices_dirty.set(true);
974 }
975
976 pub fn node_id(&self) -> Option<NodeId> {
978 self.id.get()
979 }
980
981 pub fn set_parent(&self, parent: NodeId) {
984 self.folded_parent.set(Some(parent));
985 self.parent.set(Some(parent));
986 self.refresh_registry_state();
987 }
988
989 pub fn clear_parent(&self) {
991 self.folded_parent.set(None);
992 self.parent.set(None);
993 self.refresh_registry_state();
994 }
995
996 pub fn parent(&self) -> Option<NodeId> {
998 self.parent.get()
999 }
1000
1001 pub fn folded_parent(&self) -> Option<NodeId> {
1003 self.folded_parent.get()
1004 }
1005
1006 pub(crate) fn cache_handles(&self) -> &LayoutNodeCacheHandles {
1007 &self.cache
1008 }
1009
1010 pub fn resolved_modifiers(&self) -> ResolvedModifiers {
1011 self.modifier_chain.resolved_modifiers()
1012 }
1013
1014 pub(crate) fn parent_data(&self) -> cranpose_ui_layout::ParentData {
1016 self.parent_data.get()
1017 }
1018
1019 fn refresh_parent_data(&self) {
1022 self.parent_data.set(crate::layout::parent_data_of(
1023 self.modifier_chain.resolved_modifiers().layout_properties(),
1024 ));
1025 }
1026
1027 pub fn modifier_capabilities(&self) -> NodeCapabilities {
1028 self.modifier_chain.capabilities()
1029 }
1030
1031 pub fn is_window_root(&self) -> bool {
1035 self.modifier_capabilities()
1036 .contains(NodeCapabilities::WINDOW_ROOT)
1037 }
1038
1039 pub fn modifier_child_capabilities(&self) -> NodeCapabilities {
1040 self.modifier_chain.aggregate_child_capabilities()
1041 }
1042
1043 pub fn set_debug_modifiers(&mut self, enabled: bool) {
1044 self.debug_modifiers.set(enabled);
1045 self.modifier_chain.set_debug_logging(enabled);
1046 }
1047
1048 pub fn debug_modifiers_enabled(&self) -> bool {
1049 self.debug_modifiers.get()
1050 }
1051
1052 pub fn modifier_locals_handle(&self) -> Option<ModifierLocalsHandle> {
1055 self.modifier_chain.modifier_locals_handle()
1056 }
1057
1058 pub fn has_layout_modifier_nodes(&self) -> bool {
1059 self.modifier_capabilities()
1060 .contains(NodeCapabilities::LAYOUT)
1061 }
1062
1063 pub fn has_draw_modifier_nodes(&self) -> bool {
1064 self.modifier_capabilities()
1065 .contains(NodeCapabilities::DRAW)
1066 }
1067
1068 pub fn has_pointer_input_modifier_nodes(&self) -> bool {
1069 self.modifier_capabilities()
1070 .contains(NodeCapabilities::POINTER_INPUT)
1071 }
1072
1073 pub fn has_semantics_modifier_nodes(&self) -> bool {
1074 self.modifier_capabilities()
1075 .contains(NodeCapabilities::SEMANTICS)
1076 }
1077
1078 pub fn has_focus_modifier_nodes(&self) -> bool {
1079 self.modifier_capabilities()
1080 .contains(NodeCapabilities::FOCUS)
1081 }
1082
1083 fn refresh_registry_state(&self) {
1084 if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
1085 let parent = self.parent();
1086 let capabilities = self.modifier_child_capabilities();
1087 let modifier_locals = self.modifier_locals_handle();
1088 let _ = crate::render_state::with_layout_node_registry_by_app_context(
1089 owner_context_id,
1090 |registry| {
1091 registry.update_entry(id, parent, capabilities, modifier_locals);
1092 },
1093 );
1094 }
1095 }
1096
1097 pub fn modifier_slices_snapshot(&self) -> Rc<ModifierNodeSlices> {
1098 if self.modifier_slices_dirty.get() {
1099 self.update_modifier_slices_cache();
1100 }
1101 self.modifier_slices_snapshot.borrow().clone()
1102 }
1103
1104 pub fn layout_state(&self) -> LayoutState {
1106 self.layout_state.borrow().clone()
1107 }
1108
1109 pub fn measured_size(&self) -> Size {
1111 self.layout_state.borrow().size
1112 }
1113
1114 pub fn position(&self) -> Point {
1116 self.layout_state.borrow().position
1117 }
1118
1119 pub fn is_placed(&self) -> bool {
1121 self.layout_state.borrow().is_placed
1122 }
1123
1124 pub fn set_measured_size(&self, size: Size) {
1128 self.layout_state.borrow_mut().set_size(size);
1129 }
1130
1131 pub fn set_position(&self, position: Point) {
1134 self.layout_state.borrow_mut().place(position);
1135 }
1136
1137 pub fn set_content_offset(&self, offset: Point) {
1139 self.layout_state.borrow_mut().set_content_offset(offset);
1140 }
1141
1142 pub fn clear_placed(&self) {
1144 self.layout_state.borrow_mut().clear_placed();
1145 }
1146
1147 fn note_semantics_layout_change(&self) {
1148 if let Ok(mut state) = self.layout_state.try_borrow_mut() {
1149 state.note_semantics_change();
1150 }
1151 }
1152
1153 pub fn semantics_configuration(&self) -> Option<SemanticsConfiguration> {
1154 crate::modifier::collect_semantics_from_chain(self.modifier_chain.chain())
1155 }
1156
1157 fn forget_semantics_reach(&self) {
1159 self.semantics_reach.set(None);
1160 crate::modal_nodes::reach_changed(self.id.get());
1161 }
1162
1163 pub fn semantics_reach(&self) -> cranpose_foundation::SemanticsReach {
1165 if !self
1167 .modifier_capabilities()
1168 .contains(NodeCapabilities::SEMANTICS)
1169 {
1170 return cranpose_foundation::SemanticsReach::default();
1171 }
1172 if let Some(reach) = self.semantics_reach.get() {
1173 return reach;
1174 }
1175 let reach = crate::modifier::semantics_reach_of_chain(self.modifier_chain.chain());
1176 self.semantics_reach.set(Some(reach));
1177 reach
1178 }
1179
1180 pub(crate) fn modifier_chain(&self) -> &ModifierChainHandle {
1181 &self.modifier_chain
1182 }
1183
1184 pub fn with_text_field_modifier_mut<R>(
1189 &mut self,
1190 f: impl FnMut(&mut crate::TextFieldModifierNode) -> R,
1191 ) -> Option<R> {
1192 self.modifier_chain.with_text_field_modifier_mut(f)
1193 }
1194
1195 pub fn layout_state_handle(&self) -> &Rc<RefCell<LayoutState>> {
1198 &self.layout_state
1199 }
1200
1201 pub(crate) fn coordinator_geometry(&self) -> &crate::modifier::CoordinatorGeometry {
1202 &self.coordinator_geometry
1203 }
1204
1205 pub(crate) fn layout_runtime_state_handle(&self) -> Rc<RefCell<LayoutRuntimeState>> {
1206 self.layout_runtime_state.clone()
1207 }
1208
1209 #[cfg(test)]
1210 pub(crate) fn layout_runtime_debug_stats(&self) -> LayoutRuntimeDebugStats {
1211 self.layout_runtime_state.borrow().debug_stats()
1212 }
1213}
1214impl Clone for LayoutNode {
1215 fn clone(&self) -> Self {
1216 let mut node = Self {
1217 #[cfg(feature = "inspection")]
1218 source_trace: self.source_trace.clone(),
1219 modifier: self.modifier.clone(),
1220 modifier_chain: ModifierChainHandle::for_layout_direction(
1221 self.modifier_chain.layout_direction(),
1222 ),
1223 measure_policy: self.measure_policy.clone(),
1224 density: self.density,
1225 children: self.children.clone(),
1226 cache: self.cache.clone(),
1227 needs_measure: Cell::new(self.needs_measure.get()),
1228 needs_layout: Cell::new(self.needs_layout.get()),
1229 descendant_dirt: Cell::new(self.descendant_dirt.get()),
1230 parent_data: Cell::new(self.parent_data.get()),
1231 needs_semantics: Cell::new(self.needs_semantics.get()),
1232 descendant_needs_semantics: Cell::new(self.descendant_needs_semantics.get()),
1233 semantics_reach: Cell::new(None),
1234 needs_redraw: Cell::new(self.needs_redraw.get()),
1235 needs_pointer_pass: Cell::new(self.needs_pointer_pass.get()),
1236 needs_focus_sync: Cell::new(self.needs_focus_sync.get()),
1237 parent: Cell::new(self.parent.get()),
1238 folded_parent: Cell::new(self.folded_parent.get()),
1239 id: Cell::new(None),
1240 owner_context_id: Cell::new(None),
1241 debug_modifiers: Cell::new(self.debug_modifiers.get()),
1242 is_virtual: self.is_virtual,
1243 virtual_children_count: Cell::new(self.virtual_children_count.get()),
1244 modifier_slices_snapshot: RefCell::new(Rc::default()),
1245 modifier_slices_dirty: Cell::new(true),
1246 layout_state: self.layout_state.clone(),
1247 layout_runtime_state: self.layout_runtime_state.clone(),
1248 coordinator_geometry: Rc::clone(&self.coordinator_geometry),
1249 };
1250 node.sync_modifier_chain(false);
1251 node
1252 }
1253}
1254
1255impl Node for LayoutNode {
1256 fn mount(&mut self) {
1257 let (chain, mut context) = self.modifier_chain.chain_and_context_mut();
1258 chain.repair_chain();
1259 chain.attach_nodes(&mut *context);
1260 crate::modal_nodes::reach_changed(self.id.get());
1261 }
1262
1263 fn unmount(&mut self) {
1264 self.modifier_chain.chain_mut().detach_nodes();
1265 }
1266
1267 fn set_node_id(&mut self, id: NodeId) {
1268 LayoutNode::set_node_id(self, id);
1269 }
1270
1271 fn insert_child(&mut self, child: NodeId) -> bool {
1272 if self.children.contains(&child) {
1273 return false;
1274 }
1275 if is_virtual_node(child) {
1276 let count = self.virtual_children_count.get();
1277 self.virtual_children_count.set(count + 1);
1278 }
1279 self.children.push(child);
1280 self.cache.clear();
1281 self.mark_needs_measure();
1282 self.note_semantics_layout_change();
1283 true
1284 }
1285
1286 fn remove_child(&mut self, child: NodeId) -> bool {
1287 let before = self.children.len();
1288 self.children.retain(|&id| id != child);
1289 let removed = self.children.len() < before;
1290 if removed {
1291 if is_virtual_node(child) {
1292 let count = self.virtual_children_count.get();
1293 if count > 0 {
1294 self.virtual_children_count.set(count - 1);
1295 }
1296 }
1297 self.cache.clear();
1298 self.mark_needs_measure();
1299 self.note_semantics_layout_change();
1300 }
1301 removed
1302 }
1303
1304 fn move_child(&mut self, from: usize, to: usize) {
1305 if from == to || from >= self.children.len() {
1306 return;
1307 }
1308 let child = self.children.remove(from);
1309 let target = to.min(self.children.len());
1310 self.children.insert(target, child);
1311 self.cache.clear();
1312 self.mark_needs_measure();
1313 self.note_semantics_layout_change();
1314 }
1315
1316 fn update_children(&mut self, children: &[NodeId]) {
1317 self.children.clear();
1318 self.children.extend_from_slice(children);
1319 self.cache.clear();
1320 self.mark_needs_measure();
1321 self.note_semantics_layout_change();
1322 }
1323
1324 fn collect_children_into(&self, out: &mut smallvec::SmallVec<[NodeId; 8]>) {
1325 out.clear();
1326 out.extend_from_slice(&self.children);
1327 }
1328
1329 fn owned_child_index(&self, child: NodeId) -> Option<usize> {
1330 self.children.iter().position(|&id| id == child)
1331 }
1332
1333 fn on_attached_to_parent(&mut self, parent: NodeId) {
1334 self.set_parent(parent);
1335 }
1336
1337 fn on_removed_from_parent(&mut self) {
1338 self.clear_parent();
1339 }
1340
1341 fn parent(&self) -> Option<NodeId> {
1342 self.parent.get()
1343 }
1344
1345 fn mark_needs_layout(&self) {
1346 self.needs_layout.set(true);
1347 }
1348
1349 fn needs_layout(&self) -> bool {
1350 self.needs_layout.get()
1351 }
1352
1353 fn mark_needs_measure(&self) {
1354 self.needs_measure.set(true);
1355 self.needs_layout.set(true);
1356 }
1357
1358 fn needs_measure(&self) -> bool {
1359 self.needs_measure.get()
1360 }
1361
1362 fn mark_descendant_needs_layout(&self, measure: bool) {
1363 self.descendant_dirt
1364 .set(self.descendant_dirt.get().marked(measure));
1365 if measure {
1366 self.cache.forget_intrinsics();
1367 }
1368 }
1369
1370 fn descendant_needs_layout(&self) -> bool {
1371 self.descendant_dirt.get().layout
1372 }
1373
1374 fn is_virtual(&self) -> bool {
1375 self.is_virtual
1376 }
1377
1378 fn descendant_needs_measure(&self) -> bool {
1379 self.descendant_dirt.get().measure
1380 }
1381
1382 fn mark_needs_semantics(&self) {
1383 self.needs_semantics.set(true);
1384 self.forget_semantics_reach();
1385 }
1386
1387 fn mark_descendant_needs_semantics(&self) {
1388 self.descendant_needs_semantics.set(true);
1389 }
1390
1391 fn needs_semantics(&self) -> bool {
1392 LayoutNode::needs_semantics(self)
1393 }
1394
1395 fn set_parent_for_bubbling(&mut self, parent: NodeId) {
1396 if self.parent.get().is_none() {
1397 self.parent.set(Some(parent));
1398 }
1399 }
1400
1401 fn recycle_key(&self) -> Option<TypeId> {
1402 Some(TypeId::of::<Self>())
1403 }
1404
1405 fn recycle_pool_limit(&self) -> Option<usize> {
1406 Some(RECYCLED_LAYOUT_NODE_POOL_LIMIT)
1407 }
1408
1409 fn prepare_for_recycle(&mut self) {
1410 *self = Self::new_recycled_shell(self.is_virtual);
1411 }
1412
1413 fn rehouse_for_recycle(&self) -> Option<Box<dyn cranpose_core::Node>> {
1414 Some(Box::new(Self::new_recycled_shell(self.is_virtual)))
1415 }
1416
1417 fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn cranpose_core::Node>> {
1418 let mut previous = std::mem::replace(self, Self::new_recycled_shell(self.is_virtual));
1419 let node_id = previous.id.replace(None);
1420 let parent = previous.parent.get();
1421 let folded_parent = previous.folded_parent.get();
1422 let debug_modifiers = previous.debug_modifiers.get();
1423 let needs_measure = previous.needs_measure.get();
1424 let needs_layout = previous.needs_layout.get();
1425 let needs_semantics = previous.needs_semantics.get();
1426 let descendant_needs_semantics = previous.descendant_needs_semantics.get();
1427 let needs_redraw = previous.needs_redraw.get();
1428 let needs_pointer_pass = previous.needs_pointer_pass.get();
1429 let needs_focus_sync = previous.needs_focus_sync.get();
1430 let virtual_children_count = previous.virtual_children_count.get();
1431 let children = previous.children.to_vec();
1432 let modifier = previous.modifier.rehouse_for_live_compaction();
1433 let measure_policy = previous.measure_policy.clone();
1434 let layout_state = previous.layout_state.clone();
1435 let layout_runtime_state = previous.layout_runtime_state.clone();
1436 let coordinator_geometry = Rc::clone(&previous.coordinator_geometry);
1437
1438 previous.modifier_chain.chain_mut().detach_nodes();
1439
1440 let mut compact = Self::new_with_virtual(modifier, measure_policy, previous.is_virtual);
1441 compact.children = children;
1442 #[cfg(feature = "inspection")]
1443 {
1444 compact.source_trace = previous.source_trace.clone();
1445 }
1446 compact.parent.set(parent);
1447 compact.folded_parent.set(folded_parent);
1448 compact.id.set(node_id);
1449 compact.debug_modifiers.set(debug_modifiers);
1450 compact.needs_measure.set(needs_measure);
1451 compact.needs_layout.set(needs_layout);
1452 compact.needs_semantics.set(needs_semantics);
1453 compact
1454 .descendant_needs_semantics
1455 .set(descendant_needs_semantics);
1456 compact.needs_redraw.set(needs_redraw);
1457 compact.needs_pointer_pass.set(needs_pointer_pass);
1458 compact.needs_focus_sync.set(needs_focus_sync);
1459 compact.virtual_children_count.set(virtual_children_count);
1460 compact.layout_state = layout_state;
1461 compact.layout_runtime_state = layout_runtime_state;
1462 compact.coordinator_geometry = coordinator_geometry;
1463 compact.sync_modifier_chain(false);
1464 if let Some(id) = node_id {
1465 let owner_context_id = register_layout_node(id, &compact);
1466 compact.owner_context_id.set(Some(owner_context_id));
1467 }
1468
1469 Some(Box::new(compact))
1470 }
1471}
1472
1473impl Drop for LayoutNode {
1474 fn drop(&mut self) {
1475 if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
1476 unregister_layout_node(owner_context_id, id);
1477 }
1478 }
1479}
1480
1481const MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY: usize = 128;
1482const VIRTUAL_NODE_ID_START: NodeId = 0xC0000000;
1483
1484#[cfg(test)]
1485#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1486struct LayoutNodeRegistryDebugStats {
1487 len: usize,
1488 capacity: usize,
1489}
1490
1491struct LayoutNodeRegistryEntry {
1492 parent: Option<NodeId>,
1493 modifier_child_capabilities: NodeCapabilities,
1494 modifier_locals: Option<ModifierLocalsHandle>,
1495 is_virtual: bool,
1496}
1497
1498pub(crate) struct LayoutNodeRegistryState {
1499 entries: RefCell<HashMap<NodeId, LayoutNodeRegistryEntry>>,
1500 virtual_node_id_counter: Cell<NodeId>,
1501}
1502
1503impl LayoutNodeRegistryState {
1504 pub(crate) fn new() -> Self {
1505 Self {
1506 entries: RefCell::new(HashMap::new()),
1507 virtual_node_id_counter: Cell::new(VIRTUAL_NODE_ID_START),
1508 }
1509 }
1510
1511 fn register(&self, id: NodeId, node: &LayoutNode) {
1512 self.entries.borrow_mut().insert(
1513 id,
1514 LayoutNodeRegistryEntry {
1515 parent: node.parent(),
1516 modifier_child_capabilities: node.modifier_child_capabilities(),
1517 modifier_locals: node.modifier_locals_handle(),
1518 is_virtual: node.is_virtual(),
1519 },
1520 );
1521 }
1522
1523 fn unregister(&self, id: NodeId) {
1524 let mut entries = self.entries.borrow_mut();
1525 entries.remove(&id);
1526 let should_shrink = (entries.len() <= MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY
1527 && entries.capacity() > MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1528 || entries.capacity()
1529 > entries
1530 .len()
1531 .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1532 .saturating_mul(4);
1533 if should_shrink {
1534 let retained = entries
1535 .len()
1536 .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY);
1537 let mut rebuilt = HashMap::new();
1538 rebuilt.reserve(retained);
1539 rebuilt.extend(entries.drain());
1540 *entries = rebuilt;
1541 }
1542 }
1543
1544 fn update_entry(
1545 &self,
1546 id: NodeId,
1547 parent: Option<NodeId>,
1548 modifier_child_capabilities: NodeCapabilities,
1549 modifier_locals: Option<ModifierLocalsHandle>,
1550 ) {
1551 if let Some(entry) = self.entries.borrow_mut().get_mut(&id) {
1552 entry.parent = parent;
1553 entry.modifier_child_capabilities = modifier_child_capabilities;
1554 entry.modifier_locals = modifier_locals;
1555 }
1556 }
1557
1558 #[cfg(test)]
1559 fn stats(&self) -> LayoutNodeRegistryDebugStats {
1560 let entries = self.entries.borrow();
1561 LayoutNodeRegistryDebugStats {
1562 len: entries.len(),
1563 capacity: entries.capacity(),
1564 }
1565 }
1566
1567 fn is_virtual_node(&self, id: NodeId) -> bool {
1568 self.entries
1569 .borrow()
1570 .get(&id)
1571 .is_some_and(|entry| entry.is_virtual)
1572 }
1573
1574 fn allocate_virtual_node_id(&self) -> NodeId {
1575 let id = self.virtual_node_id_counter.get();
1576 self.virtual_node_id_counter.set(id.wrapping_add(1));
1577 id
1578 }
1579
1580 fn resolve_modifier_local_from_parent_chain(
1581 &self,
1582 start: Option<NodeId>,
1583 token: &ModifierLocalToken,
1584 ) -> Option<ResolvedModifierLocal> {
1585 let mut current = start;
1586 while let Some(parent_id) = current {
1587 let (next_parent, resolved) = {
1588 let entries = self.entries.borrow();
1589 if let Some(entry) = entries.get(&parent_id) {
1590 let resolved = entry
1591 .modifier_child_capabilities
1592 .contains(NodeCapabilities::MODIFIER_LOCALS)
1593 .then_some(entry.modifier_locals.as_ref())
1594 .flatten()
1595 .and_then(|locals| locals.borrow().resolve(token))
1596 .map(|value| value.with_source(ModifierLocalSource::Ancestor));
1597 (entry.parent, resolved)
1598 } else {
1599 (None, None)
1600 }
1601 };
1602 if let Some(value) = resolved {
1603 return Some(value);
1604 }
1605 current = next_parent;
1606 }
1607 None
1608 }
1609}
1610
1611pub(crate) fn register_layout_node(
1612 id: NodeId,
1613 node: &LayoutNode,
1614) -> crate::render_state::AppContextId {
1615 let owner_context_id = crate::render_state::current_app_context_id();
1616 let _ = crate::render_state::with_layout_node_registry_by_app_context(
1617 owner_context_id,
1618 |registry| {
1619 registry.register(id, node);
1620 },
1621 );
1622 owner_context_id
1623}
1624
1625pub(crate) fn unregister_layout_node(
1626 owner_context_id: crate::render_state::AppContextId,
1627 id: NodeId,
1628) {
1629 let _ = crate::render_state::with_layout_node_registry_by_app_context(
1630 owner_context_id,
1631 |registry| {
1632 registry.unregister(id);
1633 },
1634 );
1635}
1636
1637#[cfg(test)]
1638fn layout_node_registry_stats() -> LayoutNodeRegistryDebugStats {
1639 crate::render_state::with_layout_node_registry(LayoutNodeRegistryState::stats)
1640}
1641
1642pub(crate) fn is_virtual_node(id: NodeId) -> bool {
1643 crate::render_state::with_layout_node_registry(|registry| registry.is_virtual_node(id))
1644}
1645
1646pub(crate) fn allocate_virtual_node_id() -> NodeId {
1647 crate::render_state::with_layout_node_registry(
1648 LayoutNodeRegistryState::allocate_virtual_node_id,
1649 )
1650}
1651
1652fn resolve_modifier_local_from_parent_chain(
1653 start: Option<NodeId>,
1654 token: &ModifierLocalToken,
1655) -> Option<ResolvedModifierLocal> {
1656 crate::render_state::with_layout_node_registry(|registry| {
1657 registry.resolve_modifier_local_from_parent_chain(start, token)
1658 })
1659}
1660
1661#[cfg(test)]
1662#[path = "tests/layout_node_tests.rs"]
1663mod tests;