1use std::{
2 any::TypeId,
3 cell::{Cell, RefCell},
4 hash::{Hash, Hasher},
5 rc::Rc,
6};
7
8use cranpose_core::{Node, NodeId, collections::map::HashMap};
9use cranpose_foundation::{
10 InvalidationKind, ModifierInvalidation, ModifierInvalidations, NodeCapabilities,
11 SemanticsConfiguration,
12};
13use cranpose_ui_layout::{Constraints, MeasurePolicy};
14
15#[cfg(test)]
16use crate::layout::LayoutRuntimeDebugStats;
17use crate::{
18 layout::{LayoutRuntimeState, MeasuredNode},
19 modifier::{
20 InPlaceUpdate, Modifier, ModifierChainHandle, ModifierLocalSource, ModifierLocalToken,
21 ModifierLocalsHandle, ModifierNodeSlices, Point, ResolvedModifierLocal, ResolvedModifiers,
22 Size,
23 },
24 render_state::AppContext,
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
69pub(crate) struct PlacementPass {
73 id: Cell<u64>,
74 unplaced: Cell<usize>,
75}
76
77impl PlacementPass {
78 pub(crate) const fn new() -> Self {
79 Self {
80 id: Cell::new(1),
81 unplaced: Cell::new(0),
82 }
83 }
84
85 pub(crate) fn begin(&self) {
87 self.id.set(self.id.get() + 1);
88 self.unplaced.set(0);
89 }
90
91 pub(crate) fn with_unplaced_nodes(&self) -> Option<u64> {
93 (self.unplaced.get() > 0).then(|| self.id.get())
94 }
95
96 fn clear(&self) -> u64 {
98 self.unplaced.set(self.unplaced.get() + 1);
99 self.id.get()
100 }
101
102 fn restore(&self, cleared_in: u64) -> bool {
105 let restored = cleared_in == self.id.get();
106 if restored {
107 self.unplaced.set(self.unplaced.get().saturating_sub(1));
108 }
109 restored
110 }
111}
112
113#[derive(Clone, Copy, Default)]
115pub(crate) struct DescendantDirt {
116 pub(crate) layout: bool,
117 pub(crate) measure: bool,
118}
119
120impl DescendantDirt {
121 pub(crate) fn marked(self, measure: bool) -> Self {
122 Self {
123 layout: true,
124 measure: self.measure || measure,
125 }
126 }
127}
128
129#[derive(Clone, Copy)]
131pub(crate) struct LayoutDirt {
132 pub(crate) measure: bool,
133 pub(crate) layout: bool,
134 pub(crate) descendant: DescendantDirt,
135}
136
137impl LayoutDirt {
138 pub(crate) const CLEAN: Self = Self {
140 measure: false,
141 layout: false,
142 descendant: DescendantDirt {
143 layout: false,
144 measure: false,
145 },
146 };
147
148 pub(crate) fn any(self) -> bool {
150 self.measure || self.layout || self.descendant.layout
151 }
152}
153
154impl Default for LayoutDirt {
157 fn default() -> Self {
158 Self {
159 measure: true,
160 layout: true,
161 descendant: DescendantDirt::default(),
162 }
163 }
164}
165
166#[derive(Clone, Debug, Default)]
168pub struct LayoutState {
169 size: Size,
170 position: Point,
171 is_placed: bool,
172 node_id: Option<NodeId>,
173 cleared_in_pass: u64,
175 content_offset: Point,
176 semantics_stamp: u64,
177 moved_noted_in: u64,
179 reshaped_noted_in: u64,
181}
182
183impl LayoutState {
184 pub fn size(&self) -> Size {
186 self.size
187 }
188
189 pub fn position(&self) -> Point {
191 self.position
192 }
193
194 pub fn content_offset(&self) -> Point {
197 self.content_offset
198 }
199
200 pub fn set_content_offset(&mut self, offset: Point) {
203 if self.content_offset != offset {
204 self.content_offset = offset;
205 self.note_semantics_change();
206 }
207 }
208
209 pub fn at_origin(mut self) -> Self {
213 self.position = Point::default();
214 self
215 }
216
217 pub fn is_placed(&self) -> bool {
219 self.is_placed
220 }
221
222 pub(crate) fn set_node_id(&mut self, node_id: NodeId) {
223 self.node_id = Some(node_id);
224 }
225
226 pub fn set_size(&mut self, size: Size, context: &AppContext) {
229 if self.size != size {
230 if let Some(id) = self.node_id {
231 context.note_geometry_scene_node(id, &mut self.reshaped_noted_in);
232 }
233 self.size = size;
234 self.note_semantics_change();
235 }
236 }
237
238 pub fn place(&mut self, position: Point, placer: Option<NodeId>, context: &AppContext) {
245 let newly_placed =
248 !self.is_placed && !context.placement_pass().restore(self.cleared_in_pass);
249 let moved = self.position != position;
250 if let Some(id) = self.node_id {
251 if newly_placed {
252 context.note_geometry_scene_node(id, &mut self.reshaped_noted_in);
253 } else if moved {
254 context.record_moved_scene_node(id, placer, &mut self.moved_noted_in);
255 }
256 }
257 self.position = position;
258 if moved || !self.is_placed {
259 self.is_placed = true;
260 self.note_semantics_change();
261 }
262 }
263
264 pub fn clear_placed(&mut self, context: &AppContext) {
268 if !self.is_placed {
269 return;
270 }
271 self.is_placed = false;
272 self.cleared_in_pass = context.placement_pass().clear();
273 self.note_semantics_change();
274 }
275
276 pub(crate) fn unplaced_in(&self, pass: u64) -> bool {
278 !self.is_placed && self.cleared_in_pass == pass
279 }
280
281 #[inline]
282 pub(crate) fn note_semantics_change(&mut self) {
283 if let Some(id) = self.node_id {
284 crate::semantics_layout_log::record_layout_change(id, &mut self.semantics_stamp);
285 }
286 }
287}
288
289#[derive(Clone)]
290struct MeasurementCacheEntry {
291 constraints: Constraints,
292 measured: Rc<MeasuredNode>,
293 hold: Option<cranpose_ui_layout::ConstraintsHold>,
295 ignores_child_sizes: bool,
298}
299
300#[derive(Clone, Copy, Debug)]
302pub enum IntrinsicKind {
303 MinWidth(f32),
305 MaxWidth(f32),
307 MinHeight(f32),
309 MaxHeight(f32),
311}
312
313impl IntrinsicKind {
314 fn discriminant(&self) -> u8 {
315 match self {
316 IntrinsicKind::MinWidth(_) => 0,
317 IntrinsicKind::MaxWidth(_) => 1,
318 IntrinsicKind::MinHeight(_) => 2,
319 IntrinsicKind::MaxHeight(_) => 3,
320 }
321 }
322
323 fn value_bits(&self) -> u32 {
324 match self {
325 IntrinsicKind::MinWidth(value)
326 | IntrinsicKind::MaxWidth(value)
327 | IntrinsicKind::MinHeight(value)
328 | IntrinsicKind::MaxHeight(value) => value.to_bits(),
329 }
330 }
331}
332
333impl PartialEq for IntrinsicKind {
334 fn eq(&self, other: &Self) -> bool {
335 self.discriminant() == other.discriminant() && self.value_bits() == other.value_bits()
336 }
337}
338
339impl Eq for IntrinsicKind {}
340
341impl Hash for IntrinsicKind {
342 fn hash<H: Hasher>(&self, state: &mut H) {
343 self.discriminant().hash(state);
344 self.value_bits().hash(state);
345 }
346}
347
348#[derive(Default)]
349struct NodeCacheState {
350 epoch: u64,
351 measurement: Option<MeasurementCacheEntry>,
352 spare: Option<Rc<MeasuredNode>>,
356 intrinsics: Vec<(IntrinsicKind, f32)>,
357 intrinsics_read: bool,
359}
360
361impl NodeCacheState {
362 fn retire_measurement(&mut self) {
363 if let Some(entry) = self.measurement.take() {
364 self.spare = Some(entry.measured);
365 }
366 }
367}
368
369#[derive(Default)]
373struct CacheShared {
374 cache: RefCell<NodeCacheState>,
375 dirt: Cell<LayoutDirt>,
376}
377
378#[derive(Default)]
379pub(crate) struct LayoutNodeCacheHandles {
380 state: Rc<CacheShared>,
381}
382
383impl Clone for LayoutNodeCacheHandles {
384 fn clone(&self) -> Self {
385 Self {
386 state: Rc::clone(&self.state),
387 }
388 }
389
390 fn clone_from(&mut self, source: &Self) {
391 if !Rc::ptr_eq(&self.state, &source.state) {
392 self.state = Rc::clone(&source.state);
393 }
394 }
395}
396
397impl LayoutNodeCacheHandles {
398 pub(crate) fn dirt(&self) -> LayoutDirt {
401 self.state.dirt.get()
402 }
403
404 fn set_dirt(&self, dirt: LayoutDirt) {
405 self.state.dirt.set(dirt);
406 }
407
408 pub(crate) fn take_layout_dirt(&self) -> LayoutDirt {
411 let dirt = self.dirt();
412 self.clear_layout_dirt();
413 dirt
414 }
415
416 pub(crate) fn restore_dirt(&self, dirt: LayoutDirt) {
418 self.set_dirt(dirt);
419 }
420
421 pub(crate) fn clear_layout_dirt(&self) {
424 self.set_dirt(LayoutDirt {
425 layout: false,
426 descendant: DescendantDirt::default(),
427 ..self.dirt()
428 });
429 }
430
431 pub(crate) fn clear(&self) {
432 let mut state = self.state.cache.borrow_mut();
433 state.retire_measurement();
434 state.intrinsics.clear();
435 state.intrinsics_read = false;
436 state.epoch = 0;
437 }
438
439 pub(crate) fn activate(&self, epoch: u64) {
440 let mut state = self.state.cache.borrow_mut();
441 if state.epoch != epoch {
442 state.retire_measurement();
443 state.intrinsics.clear();
444 state.intrinsics_read = false;
445 state.epoch = epoch;
446 }
447 }
448
449 pub(crate) fn held_measurement(&self) -> Option<Rc<MeasuredNode>> {
452 let state = self.state.cache.borrow();
453 state
454 .measurement
455 .as_ref()
456 .map(|entry| &entry.measured)
457 .or(state.spare.as_ref())
458 .cloned()
459 }
460
461 pub(crate) fn take_previous_measurement(&self) -> Option<Rc<MeasuredNode>> {
464 let mut state = self.state.cache.borrow_mut();
465 state
466 .spare
467 .take()
468 .or_else(|| state.measurement.take().map(|entry| entry.measured))
469 }
470
471 pub(crate) fn forget_intrinsics(&self) {
475 self.state.cache.borrow_mut().intrinsics.clear();
476 }
477
478 pub(crate) fn epoch(&self) -> u64 {
479 self.state.cache.borrow().epoch
480 }
481
482 pub(crate) fn get_measurement(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
486 let state = self.state.cache.borrow();
487 state
488 .measurement
489 .as_ref()
490 .filter(|entry| {
491 entry.constraints == constraints
492 || entry.hold.is_some_and(|hold| hold.contains(constraints))
493 })
494 .map(|entry| Rc::clone(&entry.measured))
495 }
496
497 pub(crate) fn measured_constraints(&self) -> Option<Constraints> {
499 self.state
500 .cache
501 .borrow()
502 .measurement
503 .as_ref()
504 .map(|entry| entry.constraints)
505 }
506
507 pub(crate) fn has_intrinsics(&self) -> bool {
509 self.state.cache.borrow().intrinsics_read
510 }
511
512 pub(crate) fn hold_of(
514 &self,
515 measured: &Rc<MeasuredNode>,
516 ) -> Option<cranpose_ui_layout::ConstraintsHold> {
517 self.state
518 .cache
519 .borrow()
520 .measurement
521 .as_ref()
522 .filter(|entry| Rc::ptr_eq(&entry.measured, measured))
523 .and_then(|entry| entry.hold)
524 }
525
526 pub(crate) fn store_measurement(&self, constraints: Constraints, measured: Rc<MeasuredNode>) {
530 let mut state = self.state.cache.borrow_mut();
531 let (hold, ignores_child_sizes) = state
532 .measurement
533 .as_ref()
534 .filter(|entry| Rc::ptr_eq(&entry.measured, &measured))
535 .map_or((None, false), |entry| {
536 (entry.hold, entry.ignores_child_sizes)
537 });
538 state.measurement = Some(MeasurementCacheEntry {
539 constraints,
540 measured,
541 hold,
542 ignores_child_sizes,
543 });
544 }
545
546 pub(crate) fn store_held_measurement(
549 &self,
550 constraints: Constraints,
551 measured: Rc<MeasuredNode>,
552 hold: Option<cranpose_ui_layout::ConstraintsHold>,
553 ignores_child_sizes: bool,
554 ) {
555 self.state.cache.borrow_mut().measurement = Some(MeasurementCacheEntry {
556 constraints,
557 measured,
558 hold,
559 ignores_child_sizes,
560 });
561 }
562
563 pub(crate) fn store_resized_measurement(
567 &self,
568 constraints: Constraints,
569 measured: Rc<MeasuredNode>,
570 ) {
571 self.store_held_measurement(constraints, measured, None, true);
572 }
573
574 pub(crate) fn read_measurement<R>(
577 &self,
578 read: impl FnOnce(&MeasuredNode, bool) -> R,
579 ) -> Option<R> {
580 let state = self.state.cache.borrow();
581 state
582 .measurement
583 .as_ref()
584 .map(|entry| read(&entry.measured, entry.ignores_child_sizes))
585 }
586
587 pub(crate) fn ignores_child_sizes_of(&self, measured: &Rc<MeasuredNode>) -> bool {
590 self.state
591 .cache
592 .borrow()
593 .measurement
594 .as_ref()
595 .is_some_and(|entry| entry.ignores_child_sizes && Rc::ptr_eq(&entry.measured, measured))
596 }
597
598 pub(crate) fn get_intrinsic(&self, kind: &IntrinsicKind) -> Option<f32> {
599 let state = self.state.cache.borrow();
600 state
601 .intrinsics
602 .iter()
603 .find(|(stored_kind, _)| stored_kind == kind)
604 .map(|(_, value)| *value)
605 }
606
607 pub(crate) fn store_intrinsic(&self, kind: IntrinsicKind, value: f32) {
608 let mut state = self.state.cache.borrow_mut();
609 state.intrinsics_read = true;
610 if let Some((_, existing)) = state
611 .intrinsics
612 .iter_mut()
613 .find(|(stored_kind, _)| stored_kind == &kind)
614 {
615 *existing = value;
616 } else {
617 state.intrinsics.push((kind, value));
618 }
619 }
620}
621
622struct NodeIdCell(Cell<NodeId>);
626
627const NO_NODE_ID: NodeId = NodeId::MAX;
630
631impl NodeIdCell {
632 fn new(id: Option<NodeId>) -> Self {
633 Self(Cell::new(id.unwrap_or(NO_NODE_ID)))
634 }
635
636 fn get(&self) -> Option<NodeId> {
637 let id = self.0.get();
638 (id != NO_NODE_ID).then_some(id)
639 }
640
641 fn set(&self, id: Option<NodeId>) {
642 debug_assert_ne!(id, Some(NO_NODE_ID), "no node takes the none id");
643 self.0.set(id.unwrap_or(NO_NODE_ID));
644 }
645
646 fn replace(&self, id: Option<NodeId>) -> Option<NodeId> {
647 let previous = self.get();
648 self.set(id);
649 previous
650 }
651}
652
653#[repr(C)]
660pub struct LayoutNode {
661 cache: LayoutNodeCacheHandles,
662 layout_state: Rc<RefCell<LayoutState>>,
663 layout_runtime_state: Rc<RefCell<LayoutRuntimeState>>,
664 pub children: Vec<NodeId>,
666
667 pub measure_policy: Rc<dyn MeasurePolicy>,
670 coordinator_geometry: Rc<crate::modifier::CoordinatorGeometry>,
673 density: crate::density::Density,
674 is_virtual: bool,
675 needs_semantics: Cell<bool>,
676 descendant_needs_semantics: Cell<bool>,
680 needs_redraw: Cell<bool>,
681 needs_pointer_pass: Cell<bool>,
682 needs_focus_sync: Cell<bool>,
683 modifier_slices_dirty: Cell<bool>,
684 debug_modifiers: Cell<bool>,
685
686 id: NodeIdCell,
687 parent: NodeIdCell,
688 folded_parent: NodeIdCell,
689 modifier_slices_snapshot: crate::modifier::SlicesSnapshot,
690
691 semantics_reach: Cell<Option<cranpose_foundation::SemanticsReach>>,
695 owner_context_id: Cell<Option<crate::render_state::AppContextId>>,
696 modifier_chain: ModifierChainHandle,
697 pub modifier: Modifier,
700 #[cfg(feature = "inspection")]
702 pub source_trace: Rc<[cranpose_core::source_trace::SourceLocation]>,
703}
704
705pub(crate) const RECYCLED_LAYOUT_NODE_POOL_LIMIT: usize = 128;
706
707pub(crate) fn empty_measure_policy() -> Rc<dyn MeasurePolicy> {
708 crate::thread_state::with(|state| {
709 Rc::clone(
710 state
711 .empty_measure_policy
712 .get_or_init(|| Rc::new(crate::layout::policies::EmptyMeasurePolicy)),
713 )
714 })
715}
716
717impl LayoutNode {
718 pub fn new(modifier: Modifier, measure_policy: Rc<dyn MeasurePolicy>) -> Self {
720 Self::new_with_virtual(modifier, measure_policy, false)
721 }
722
723 pub fn new_virtual() -> Self {
726 Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), true)
727 }
728
729 fn new_recycled_shell(is_virtual: bool) -> Self {
730 let mut shell =
731 Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), is_virtual);
732 shell.cache.set_dirt(LayoutDirt::CLEAN);
733 shell.needs_semantics.set(false);
734 shell.descendant_needs_semantics.set(false);
735 shell.needs_redraw.set(false);
736 shell.needs_pointer_pass.set(false);
737 shell.needs_focus_sync.set(false);
738 shell.parent.set(None);
739 shell.folded_parent.set(None);
740 shell.id.set(None);
741 shell.owner_context_id.set(None);
742 shell.debug_modifiers.set(false);
743 shell.modifier_slices_dirty = Cell::new(true);
744 shell
745 }
746
747 fn new_with_virtual(
748 modifier: Modifier,
749 measure_policy: Rc<dyn MeasurePolicy>,
750 is_virtual: bool,
751 ) -> Self {
752 let layout_runtime_state = Rc::new(RefCell::new(LayoutRuntimeState::new(Rc::clone(
753 &measure_policy,
754 ))));
755 let mut node = Self {
756 #[cfg(feature = "inspection")]
757 source_trace: cranpose_core::source_trace::current_source_trace(),
758 modifier,
759 modifier_chain: ModifierChainHandle::detached(),
760 measure_policy,
761 density: crate::density::Density::default(),
762 children: Vec::new(),
763 cache: LayoutNodeCacheHandles::default(),
764 needs_semantics: Cell::new(true),
765 descendant_needs_semantics: Cell::new(false),
766 semantics_reach: Cell::new(None),
767 needs_redraw: Cell::new(true),
768 needs_pointer_pass: Cell::new(false),
769 needs_focus_sync: Cell::new(false),
770 parent: NodeIdCell::new(None),
771 folded_parent: NodeIdCell::new(None),
772 id: NodeIdCell::new(None),
773 owner_context_id: Cell::new(None),
774 debug_modifiers: Cell::new(false),
775 is_virtual,
776 modifier_slices_snapshot: crate::modifier::SlicesSnapshot::default(),
777 modifier_slices_dirty: Cell::new(true),
778 layout_state: Rc::new(RefCell::new(LayoutState::default())),
779 layout_runtime_state,
780 coordinator_geometry: Rc::default(),
781 };
782 node.sync_modifier_chain(None);
783 node
784 }
785
786 pub fn set_modifier(&mut self, modifier: Modifier) {
789 #[cfg(feature = "inspection")]
790 {
791 let trace = cranpose_core::source_trace::current_source_trace();
792 if !trace.is_empty() {
793 self.source_trace = trace;
794 }
795 }
796 if self.modifier == modifier
800 && !self
801 .modifier_capabilities()
802 .contains(NodeCapabilities::MODIFIER_LOCALS)
803 {
804 return;
805 }
806 let update = self.modifier.in_place_update(&modifier);
809 let modifier_changed = update.is_some() || !self.modifier.structural_eq(&modifier);
810 self.modifier = modifier;
811 self.sync_modifier_chain(update);
812 if modifier_changed {
813 self.cache.clear();
814 self.request_semantics_update();
815 }
816 }
817
818 fn sync_modifier_chain(&mut self, in_place: Option<InPlaceUpdate>) {
822 let prev_caps = self.modifier_capabilities();
823 let start_parent = self.parent();
824 let mut resolver = move |token: &ModifierLocalToken| {
825 resolve_modifier_local_from_parent_chain(start_parent, token)
826 };
827 self.modifier_chain
828 .set_debug_logging(self.debug_modifiers.get());
829 let mut context = self.modifier_chain.context(self.id.get());
830 let in_place = in_place.is_some_and(|update| {
831 self.modifier_chain
832 .update_elements_in_place(&self.modifier, update, &mut context)
833 });
834 let modifier_local_invalidations = if in_place {
835 ModifierInvalidations::new()
836 } else {
837 self.modifier_chain
838 .update_with_resolver(&self.modifier, &mut resolver, &mut context)
839 };
840 if prev_caps.contains(NodeCapabilities::WINDOW_ROOT)
841 != self
842 .modifier_capabilities()
843 .contains(NodeCapabilities::WINDOW_ROOT)
844 {
845 self.note_semantics_layout_change();
846 }
847 if !in_place {
851 self.forget_semantics_reach();
852 }
853 let keep_slices = in_place && !self.modifier_slices_dirty.get();
854 if !keep_slices {
855 self.modifier_slices_dirty.set(true);
856 }
857
858 let mut invalidations = context.take_invalidations();
859 invalidations.extend(modifier_local_invalidations);
860 self.dispatch_modifier_invalidations_with_prev(&invalidations, prev_caps, keep_slices);
861 if !in_place {
864 self.refresh_registry_state();
865 }
866 }
867
868 fn update_modifier_slices_cache(&self) {
869 self.modifier_slices_snapshot.collect(
870 self.modifier_chain.chain(),
871 &self.coordinator_geometry,
872 self.density.density(),
873 );
874 self.modifier_slices_dirty.set(false);
875 }
876
877 #[cfg(test)]
879 pub(crate) fn modifier_slices_ready(&self) -> bool {
880 !self.modifier_slices_dirty.get()
881 }
882
883 pub(crate) fn mark_modifier_slices_dirty(&self) {
884 self.modifier_slices_dirty.set(true);
885 }
886
887 #[cfg(test)]
888 fn dispatch_modifier_invalidations(&self, invalidations: &[ModifierInvalidation]) {
889 self.dispatch_modifier_invalidations_with_prev(
890 invalidations,
891 NodeCapabilities::empty(),
892 false,
893 );
894 }
895
896 fn dispatch_modifier_invalidations_with_prev(
899 &self,
900 invalidations: &[ModifierInvalidation],
901 prev_caps: NodeCapabilities,
902 keep_slices: bool,
903 ) {
904 let curr_caps = self.modifier_capabilities();
905 for invalidation in invalidations {
906 if !keep_slices {
907 self.modifier_slices_dirty.set(true);
908 }
909 let has_capability =
910 |capability| curr_caps.contains(capability) || prev_caps.contains(capability);
911 match invalidation.kind() {
912 InvalidationKind::Layout => {
913 if has_capability(NodeCapabilities::LAYOUT) {
914 self.mark_needs_measure();
915 if let Some(id) = self.id.get() {
916 log_layout_invalidation_dispatch(
917 id,
918 invalidation,
919 curr_caps,
920 prev_caps,
921 &self.modifier,
922 );
923 let inside_composition =
924 cranpose_core::composer_context::try_with_composer(|_| ())
925 .is_some();
926 if inside_composition {
927 cranpose_core::bubble_measure_dirty_in_composer(id);
928 } else {
929 crate::schedule_layout_repass(id);
930 }
931 }
932 }
933 }
934 InvalidationKind::Draw => {
935 if has_capability(NodeCapabilities::DRAW)
936 || invalidation.capabilities().contains(NodeCapabilities::DRAW)
937 {
938 self.mark_needs_redraw();
939 }
940 }
941 InvalidationKind::PointerInput => {
942 if has_capability(NodeCapabilities::POINTER_INPUT) {
943 self.mark_needs_pointer_pass();
944 crate::request_pointer_invalidation();
945 if let Some(id) = self.id.get() {
946 crate::schedule_pointer_repass(id);
947 }
948 }
949 }
950 InvalidationKind::Semantics => {
951 self.request_semantics_update();
952 }
953 InvalidationKind::Focus => {
954 if has_capability(NodeCapabilities::FOCUS) {
955 self.mark_needs_focus_sync();
956 crate::request_focus_invalidation();
957 if let Some(id) = self.id.get() {
958 crate::schedule_focus_invalidation(id);
959 }
960 }
961 }
962 }
963 }
964 }
965
966 pub fn density(&self) -> crate::density::Density {
968 self.density
969 }
970
971 pub fn set_density(&mut self, density: crate::density::Density) {
973 if self.density != density {
974 self.density = density;
975 self.cache.clear();
976 self.modifier_slices_dirty.set(true);
977 self.mark_needs_measure();
978 }
979 }
980
981 pub fn set_layout_direction(&mut self, direction: crate::LayoutDirection) {
983 if self.modifier_chain.set_layout_direction(direction) {
984 self.cache.clear();
985 self.modifier_slices_dirty.set(true);
986 self.mark_needs_measure();
987 }
988 }
989
990 pub(crate) fn update_measure_policy<P: MeasurePolicy + PartialEq>(&mut self, policy: P) {
992 let current: &dyn std::any::Any = &*self.measure_policy;
993 if current.downcast_ref::<P>() != Some(&policy) {
994 self.set_measure_policy(Rc::new(policy));
995 }
996 }
997
998 pub fn set_measure_policy(&mut self, policy: Rc<dyn MeasurePolicy>) {
1001 if !Rc::ptr_eq(&self.measure_policy, &policy) {
1002 self.measure_policy = policy;
1003 self.cache.clear();
1004 self.mark_needs_measure();
1005 if let Some(id) = self.id.get() {
1006 cranpose_core::bubble_measure_dirty_in_composer(id);
1007 }
1008 }
1009 }
1010
1011 pub fn mark_needs_measure(&self) {
1013 self.update_dirt(|dirt| LayoutDirt {
1014 measure: true,
1015 layout: true,
1016 ..dirt
1017 });
1018 }
1019
1020 pub fn mark_needs_layout(&self) {
1022 self.update_dirt(|dirt| LayoutDirt {
1023 layout: true,
1024 ..dirt
1025 });
1026 }
1027
1028 fn update_dirt(&self, update: impl FnOnce(LayoutDirt) -> LayoutDirt) {
1029 self.cache.set_dirt(update(self.cache.dirt()));
1030 }
1031
1032 pub fn mark_needs_redraw(&self) {
1034 self.needs_redraw.set(true);
1035 if let Some(id) = self.id.get() {
1036 crate::schedule_draw_repass(id);
1037 }
1038 crate::request_render_invalidation();
1039 }
1040
1041 pub fn needs_measure(&self) -> bool {
1043 self.cache.dirt().measure
1044 }
1045
1046 pub fn needs_layout(&self) -> bool {
1048 self.cache.dirt().layout
1049 }
1050
1051 pub fn mark_needs_semantics(&self) {
1053 self.needs_semantics.set(true);
1054 self.forget_semantics_reach();
1055 }
1056
1057 pub(crate) fn clear_needs_semantics(&self) {
1058 self.needs_semantics.set(false);
1059 self.descendant_needs_semantics.set(false);
1060 }
1061
1062 pub fn needs_semantics(&self) -> bool {
1065 self.needs_semantics.get() || self.descendant_needs_semantics.get()
1066 }
1067
1068 pub(crate) fn semantics_changed(&self) -> bool {
1071 self.needs_semantics.get()
1072 }
1073
1074 pub fn needs_redraw(&self) -> bool {
1076 self.needs_redraw.get()
1077 }
1078
1079 pub fn clear_needs_redraw(&self) {
1081 self.needs_redraw.set(false);
1082 }
1083
1084 fn request_semantics_update(&self) {
1085 let already_dirty = self.needs_semantics.replace(true);
1086 if already_dirty {
1087 return;
1088 }
1089
1090 if let Some(id) = self.id.get() {
1091 cranpose_core::queue_semantics_invalidation(id);
1092 }
1093 }
1094
1095 pub(crate) fn clear_needs_measure(&self) {
1096 self.update_dirt(|dirt| LayoutDirt {
1097 measure: false,
1098 ..dirt
1099 });
1100 }
1101
1102 pub(crate) fn clear_needs_layout(&self) {
1105 self.cache.clear_layout_dirt();
1106 }
1107
1108 pub fn mark_needs_pointer_pass(&self) {
1110 self.needs_pointer_pass.set(true);
1111 }
1112
1113 pub fn needs_pointer_pass(&self) -> bool {
1115 self.needs_pointer_pass.get()
1116 }
1117
1118 pub fn clear_needs_pointer_pass(&self) {
1120 self.needs_pointer_pass.set(false);
1121 }
1122
1123 pub fn mark_needs_focus_sync(&self) {
1125 self.needs_focus_sync.set(true);
1126 }
1127
1128 pub fn needs_focus_sync(&self) -> bool {
1130 self.needs_focus_sync.get()
1131 }
1132
1133 pub fn clear_needs_focus_sync(&self) {
1135 self.needs_focus_sync.set(false);
1136 }
1137
1138 pub fn set_node_id(&mut self, id: NodeId) {
1140 if self.needs_redraw.get() {
1143 crate::render_state::note_retained_redraw(id);
1144 }
1145 let previous = self.id.replace(Some(id));
1146 if let Some(existing) = previous
1147 && let Some(owner_context_id) = self.owner_context_id.take()
1148 {
1149 unregister_layout_node(owner_context_id, existing);
1150 }
1151 self.layout_state.borrow_mut().set_node_id(id);
1152 let owner_context_id = register_layout_node(id, self);
1155 self.owner_context_id.set(Some(owner_context_id));
1156
1157 let mut context = self.modifier_chain.context(Some(id));
1158 self.modifier_chain.move_to_node(previous, &mut context);
1159 let invalidations = context.take_invalidations();
1160 self.dispatch_modifier_invalidations_with_prev(
1161 &invalidations,
1162 NodeCapabilities::empty(),
1163 false,
1164 );
1165 self.modifier_slices_dirty.set(true);
1167 }
1168
1169 pub fn node_id(&self) -> Option<NodeId> {
1171 self.id.get()
1172 }
1173
1174 pub fn set_parent(&self, parent: NodeId) {
1177 if self.parent.get() == Some(parent) && self.folded_parent.get() == Some(parent) {
1180 return;
1181 }
1182 self.folded_parent.set(Some(parent));
1183 self.parent.set(Some(parent));
1184 self.refresh_registry_state();
1185 }
1186
1187 pub fn clear_parent(&self) {
1189 self.folded_parent.set(None);
1190 self.parent.set(None);
1191 self.refresh_registry_state();
1192 }
1193
1194 pub fn parent(&self) -> Option<NodeId> {
1196 self.parent.get()
1197 }
1198
1199 pub fn folded_parent(&self) -> Option<NodeId> {
1201 self.folded_parent.get()
1202 }
1203
1204 pub(crate) fn cache_handles(&self) -> &LayoutNodeCacheHandles {
1205 &self.cache
1206 }
1207
1208 pub fn resolved_modifiers(&self) -> ResolvedModifiers {
1210 self.modifier_chain.resolved_modifiers()
1211 }
1212
1213 pub(crate) fn parent_data(&self) -> cranpose_ui_layout::ParentData {
1215 self.modifier_chain.parent_data()
1216 }
1217
1218 pub fn modifier_capabilities(&self) -> NodeCapabilities {
1220 self.modifier_chain.capabilities()
1221 }
1222
1223 pub fn is_window_root(&self) -> bool {
1227 self.modifier_capabilities()
1228 .contains(NodeCapabilities::WINDOW_ROOT)
1229 }
1230
1231 pub fn modifier_child_capabilities(&self) -> NodeCapabilities {
1234 self.modifier_chain.aggregate_child_capabilities()
1235 }
1236
1237 pub fn set_debug_modifiers(&mut self, enabled: bool) {
1239 self.debug_modifiers.set(enabled);
1240 self.modifier_chain.set_debug_logging(enabled);
1241 }
1242
1243 pub fn debug_modifiers_enabled(&self) -> bool {
1245 self.debug_modifiers.get()
1246 }
1247
1248 pub fn modifier_locals_handle(&self) -> Option<ModifierLocalsHandle> {
1251 self.modifier_chain.modifier_locals_handle()
1252 }
1253
1254 pub fn has_layout_modifier_nodes(&self) -> bool {
1256 self.modifier_capabilities()
1257 .contains(NodeCapabilities::LAYOUT)
1258 }
1259
1260 pub fn has_draw_modifier_nodes(&self) -> bool {
1262 self.modifier_capabilities()
1263 .contains(NodeCapabilities::DRAW)
1264 }
1265
1266 pub fn has_pointer_input_modifier_nodes(&self) -> bool {
1268 self.modifier_capabilities()
1269 .contains(NodeCapabilities::POINTER_INPUT)
1270 }
1271
1272 pub fn has_semantics_modifier_nodes(&self) -> bool {
1274 self.modifier_capabilities()
1275 .contains(NodeCapabilities::SEMANTICS)
1276 }
1277
1278 pub fn has_focus_modifier_nodes(&self) -> bool {
1280 self.modifier_capabilities()
1281 .contains(NodeCapabilities::FOCUS)
1282 }
1283
1284 fn refresh_registry_state(&self) {
1285 if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
1286 let parent = self.parent();
1287 let capabilities = self.modifier_child_capabilities();
1288 let modifier_locals = self.modifier_locals_handle();
1289 let _ = crate::render_state::with_layout_node_registry_by_app_context(
1290 owner_context_id,
1291 |registry| {
1292 registry.update_entry(id, parent, capabilities, modifier_locals);
1293 },
1294 );
1295 }
1296 }
1297
1298 pub fn modifier_slices_snapshot(&self) -> Rc<ModifierNodeSlices> {
1301 if self.modifier_slices_dirty.get() {
1302 self.update_modifier_slices_cache();
1303 }
1304 self.modifier_slices_snapshot.get()
1305 }
1306
1307 pub fn layout_state(&self) -> LayoutState {
1309 self.layout_state.borrow().clone()
1310 }
1311
1312 pub fn measured_size(&self) -> Size {
1314 self.layout_state.borrow().size
1315 }
1316
1317 pub fn position(&self) -> Point {
1319 self.layout_state.borrow().position
1320 }
1321
1322 pub fn is_placed(&self) -> bool {
1324 self.layout_state.borrow().is_placed
1325 }
1326
1327 pub fn set_measured_size(&self, size: Size, context: &AppContext) {
1331 self.layout_state.borrow_mut().set_size(size, context);
1332 }
1333
1334 pub(crate) fn note_geometry_change(&self, node_id: NodeId, context: &AppContext) {
1337 context.note_geometry_scene_node(
1338 node_id,
1339 &mut self.layout_state.borrow_mut().reshaped_noted_in,
1340 );
1341 }
1342
1343 pub fn set_position(&self, position: Point, placer: Option<NodeId>, context: &AppContext) {
1347 self.layout_state
1348 .borrow_mut()
1349 .place(position, placer, context);
1350 }
1351
1352 pub fn set_content_offset(&self, offset: Point) {
1354 self.layout_state.borrow_mut().set_content_offset(offset);
1355 }
1356
1357 pub fn clear_placed(&self, context: &AppContext) {
1359 self.layout_state.borrow_mut().clear_placed(context);
1360 }
1361
1362 fn note_semantics_layout_change(&self) {
1363 if let Ok(mut state) = self.layout_state.try_borrow_mut() {
1364 state.note_semantics_change();
1365 }
1366 }
1367
1368 pub fn semantics_configuration(&self) -> Option<SemanticsConfiguration> {
1370 crate::modifier::collect_semantics_from_chain(self.modifier_chain.chain())
1371 }
1372
1373 fn forget_semantics_reach(&self) {
1375 self.semantics_reach.set(None);
1376 crate::modal_nodes::reach_changed(self.id.get());
1377 }
1378
1379 pub fn semantics_reach(&self) -> cranpose_foundation::SemanticsReach {
1381 if !self
1383 .modifier_capabilities()
1384 .contains(NodeCapabilities::SEMANTICS)
1385 {
1386 return cranpose_foundation::SemanticsReach::default();
1387 }
1388 if let Some(reach) = self.semantics_reach.get() {
1389 return reach;
1390 }
1391 let reach = crate::modifier::semantics_reach_of_chain(self.modifier_chain.chain());
1392 self.semantics_reach.set(Some(reach));
1393 reach
1394 }
1395
1396 pub(crate) fn modifier_chain(&self) -> &ModifierChainHandle {
1397 &self.modifier_chain
1398 }
1399
1400 pub fn layout_state_handle(&self) -> &Rc<RefCell<LayoutState>> {
1403 &self.layout_state
1404 }
1405
1406 pub(crate) fn coordinator_geometry(&self) -> &crate::modifier::CoordinatorGeometry {
1407 &self.coordinator_geometry
1408 }
1409
1410 pub(crate) fn layout_runtime_state_handle(&self) -> Rc<RefCell<LayoutRuntimeState>> {
1411 self.layout_runtime_state.clone()
1412 }
1413
1414 pub(crate) fn layout_runtime_state(&self) -> &RefCell<LayoutRuntimeState> {
1417 &self.layout_runtime_state
1418 }
1419
1420 #[cfg(test)]
1421 pub(crate) fn layout_runtime_debug_stats(&self) -> LayoutRuntimeDebugStats {
1422 self.layout_runtime_state.borrow().debug_stats()
1423 }
1424}
1425impl Clone for LayoutNode {
1426 fn clone(&self) -> Self {
1427 let mut node = Self {
1428 #[cfg(feature = "inspection")]
1429 source_trace: self.source_trace.clone(),
1430 modifier: self.modifier.clone(),
1431 modifier_chain: ModifierChainHandle::for_layout_direction(
1432 self.modifier_chain.layout_direction(),
1433 ),
1434 measure_policy: self.measure_policy.clone(),
1435 density: self.density,
1436 children: self.children.clone(),
1437 cache: self.cache.clone(),
1438 needs_semantics: Cell::new(self.needs_semantics.get()),
1439 descendant_needs_semantics: Cell::new(self.descendant_needs_semantics.get()),
1440 semantics_reach: Cell::new(None),
1441 needs_redraw: Cell::new(self.needs_redraw.get()),
1442 needs_pointer_pass: Cell::new(self.needs_pointer_pass.get()),
1443 needs_focus_sync: Cell::new(self.needs_focus_sync.get()),
1444 parent: NodeIdCell::new(self.parent.get()),
1445 folded_parent: NodeIdCell::new(self.folded_parent.get()),
1446 id: NodeIdCell::new(None),
1447 owner_context_id: Cell::new(None),
1448 debug_modifiers: Cell::new(self.debug_modifiers.get()),
1449 is_virtual: self.is_virtual,
1450 modifier_slices_snapshot: crate::modifier::SlicesSnapshot::default(),
1451 modifier_slices_dirty: Cell::new(true),
1452 layout_state: self.layout_state.clone(),
1453 layout_runtime_state: self.layout_runtime_state.clone(),
1454 coordinator_geometry: Rc::clone(&self.coordinator_geometry),
1455 };
1456 node.sync_modifier_chain(None);
1457 node
1458 }
1459}
1460
1461impl Node for LayoutNode {
1462 fn mount(&mut self) {
1463 self.modifier_chain.remount(self.id.get());
1464 crate::modal_nodes::reach_changed(self.id.get());
1465 }
1466
1467 fn unmount(&mut self) {
1468 self.modifier_chain.chain_mut().detach_nodes();
1469 }
1470
1471 fn set_node_id(&mut self, id: NodeId) {
1472 LayoutNode::set_node_id(self, id);
1473 }
1474
1475 fn insert_child(&mut self, child: NodeId) -> bool {
1476 if self.children.contains(&child) {
1477 return false;
1478 }
1479 self.children.push(child);
1480 self.cache.clear();
1481 self.mark_needs_measure();
1482 self.note_semantics_layout_change();
1483 true
1484 }
1485
1486 fn remove_child(&mut self, child: NodeId) -> bool {
1487 let before = self.children.len();
1488 self.children.retain(|&id| id != child);
1489 let removed = self.children.len() < before;
1490 if removed {
1491 self.cache.clear();
1492 self.mark_needs_measure();
1493 self.note_semantics_layout_change();
1494 }
1495 removed
1496 }
1497
1498 fn move_child(&mut self, from: usize, to: usize) {
1499 if from == to || from >= self.children.len() {
1500 return;
1501 }
1502 let child = self.children.remove(from);
1503 let target = to.min(self.children.len());
1504 self.children.insert(target, child);
1505 self.cache.clear();
1506 self.mark_needs_measure();
1507 self.note_semantics_layout_change();
1508 }
1509
1510 fn update_children(&mut self, children: &[NodeId]) {
1511 self.children.clear();
1512 self.children.extend_from_slice(children);
1513 self.cache.clear();
1514 self.mark_needs_measure();
1515 self.note_semantics_layout_change();
1516 }
1517
1518 fn collect_children_into(&self, out: &mut smallvec::SmallVec<[NodeId; 8]>) {
1519 out.clear();
1520 out.extend_from_slice(&self.children);
1521 }
1522
1523 fn for_each_child(&self, f: &mut dyn FnMut(NodeId)) {
1524 for &child in &self.children {
1525 f(child);
1526 }
1527 }
1528
1529 fn owned_child_index(&self, child: NodeId) -> Option<usize> {
1530 self.children.iter().position(|&id| id == child)
1531 }
1532
1533 fn on_attached_to_parent(&mut self, parent: NodeId) {
1534 self.set_parent(parent);
1535 }
1536
1537 fn on_removed_from_parent(&mut self) {
1538 self.clear_parent();
1539 }
1540
1541 fn parent(&self) -> Option<NodeId> {
1542 self.parent.get()
1543 }
1544
1545 fn mark_needs_layout(&self) {
1546 LayoutNode::mark_needs_layout(self);
1547 }
1548
1549 fn needs_layout(&self) -> bool {
1550 LayoutNode::needs_layout(self)
1551 }
1552
1553 fn mark_needs_measure(&self) {
1554 LayoutNode::mark_needs_measure(self);
1555 }
1556
1557 fn needs_measure(&self) -> bool {
1558 LayoutNode::needs_measure(self)
1559 }
1560
1561 fn mark_descendant_needs_layout(&self, measure: bool) {
1562 self.update_dirt(|dirt| LayoutDirt {
1563 descendant: dirt.descendant.marked(measure),
1564 ..dirt
1565 });
1566 if measure {
1567 self.cache.forget_intrinsics();
1568 }
1569 }
1570
1571 fn descendant_needs_layout(&self) -> bool {
1572 self.cache.dirt().descendant.layout
1573 }
1574
1575 fn is_virtual(&self) -> bool {
1576 self.is_virtual
1577 }
1578
1579 fn descendant_needs_measure(&self) -> bool {
1580 self.cache.dirt().descendant.measure
1581 }
1582
1583 fn mark_needs_semantics(&self) {
1584 self.needs_semantics.set(true);
1585 self.forget_semantics_reach();
1586 }
1587
1588 fn mark_descendant_needs_semantics(&self) {
1589 self.descendant_needs_semantics.set(true);
1590 }
1591
1592 fn needs_semantics(&self) -> bool {
1593 LayoutNode::needs_semantics(self)
1594 }
1595
1596 fn set_parent_for_bubbling(&mut self, parent: NodeId) {
1597 if self.parent.get().is_none() {
1598 self.parent.set(Some(parent));
1599 }
1600 }
1601
1602 fn recycle_key(&self) -> Option<TypeId> {
1603 Some(TypeId::of::<Self>())
1604 }
1605
1606 fn recycle_pool_limit(&self) -> Option<usize> {
1607 Some(RECYCLED_LAYOUT_NODE_POOL_LIMIT)
1608 }
1609
1610 fn prepare_for_recycle(&mut self) {
1611 *self = Self::new_recycled_shell(self.is_virtual);
1612 }
1613
1614 fn rehouse_for_recycle(&self) -> Option<Box<dyn cranpose_core::Node>> {
1615 Some(Box::new(Self::new_recycled_shell(self.is_virtual)))
1616 }
1617
1618 fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn cranpose_core::Node>> {
1619 let mut node = std::mem::replace(self, Self::new_recycled_shell(self.is_virtual));
1625 node.children.shrink_to_fit();
1626 Some(Box::new(node))
1627 }
1628}
1629
1630impl Drop for LayoutNode {
1631 fn drop(&mut self) {
1632 if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
1633 unregister_layout_node(owner_context_id, id);
1634 }
1635 }
1636}
1637
1638const MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY: usize = 128;
1639
1640#[cfg(test)]
1641#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1642struct LayoutNodeRegistryDebugStats {
1643 len: usize,
1644 capacity: usize,
1645}
1646
1647struct LayoutNodeRegistryEntry {
1648 parent: Option<NodeId>,
1649 modifier_child_capabilities: NodeCapabilities,
1650 modifier_locals: Option<ModifierLocalsHandle>,
1651}
1652
1653pub(crate) struct LayoutNodeRegistryState {
1654 entries: RefCell<HashMap<NodeId, LayoutNodeRegistryEntry>>,
1655}
1656
1657impl LayoutNodeRegistryState {
1658 pub(crate) fn new() -> Self {
1659 Self {
1660 entries: RefCell::new(HashMap::default()),
1661 }
1662 }
1663
1664 fn register(&self, id: NodeId, node: &LayoutNode) {
1665 self.entries.borrow_mut().insert(
1666 id,
1667 LayoutNodeRegistryEntry {
1668 parent: node.parent(),
1669 modifier_child_capabilities: node.modifier_child_capabilities(),
1670 modifier_locals: node.modifier_locals_handle(),
1671 },
1672 );
1673 }
1674
1675 fn unregister(&self, id: NodeId) {
1676 let mut entries = self.entries.borrow_mut();
1677 entries.remove(&id);
1678 let should_shrink = (entries.len() <= MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY
1679 && entries.capacity() > MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1680 || entries.capacity()
1681 > entries
1682 .len()
1683 .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1684 .saturating_mul(4);
1685 if should_shrink {
1686 let retained = entries
1687 .len()
1688 .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY);
1689 let mut rebuilt = HashMap::default();
1690 rebuilt.reserve(retained);
1691 rebuilt.extend(entries.drain());
1692 *entries = rebuilt;
1693 }
1694 }
1695
1696 fn update_entry(
1697 &self,
1698 id: NodeId,
1699 parent: Option<NodeId>,
1700 modifier_child_capabilities: NodeCapabilities,
1701 modifier_locals: Option<ModifierLocalsHandle>,
1702 ) {
1703 if let Some(entry) = self.entries.borrow_mut().get_mut(&id) {
1704 entry.parent = parent;
1705 entry.modifier_child_capabilities = modifier_child_capabilities;
1706 entry.modifier_locals = modifier_locals;
1707 }
1708 }
1709
1710 #[cfg(test)]
1711 fn stats(&self) -> LayoutNodeRegistryDebugStats {
1712 let entries = self.entries.borrow();
1713 LayoutNodeRegistryDebugStats {
1714 len: entries.len(),
1715 capacity: entries.capacity(),
1716 }
1717 }
1718
1719 fn resolve_modifier_local_from_parent_chain(
1720 &self,
1721 start: Option<NodeId>,
1722 token: &ModifierLocalToken,
1723 ) -> Option<ResolvedModifierLocal> {
1724 let mut current = start;
1725 while let Some(parent_id) = current {
1726 let (next_parent, resolved) = {
1727 let entries = self.entries.borrow();
1728 if let Some(entry) = entries.get(&parent_id) {
1729 let resolved = entry
1730 .modifier_child_capabilities
1731 .contains(NodeCapabilities::MODIFIER_LOCALS)
1732 .then_some(entry.modifier_locals.as_ref())
1733 .flatten()
1734 .and_then(|locals| locals.borrow().resolve(token))
1735 .map(|value| value.with_source(ModifierLocalSource::Ancestor));
1736 (entry.parent, resolved)
1737 } else {
1738 (None, None)
1739 }
1740 };
1741 if let Some(value) = resolved {
1742 return Some(value);
1743 }
1744 current = next_parent;
1745 }
1746 None
1747 }
1748}
1749
1750pub(crate) fn register_layout_node(
1751 id: NodeId,
1752 node: &LayoutNode,
1753) -> crate::render_state::AppContextId {
1754 crate::render_state::with_current_layout_node_registry(|registry| registry.register(id, node))
1755}
1756
1757pub(crate) fn unregister_layout_node(
1758 owner_context_id: crate::render_state::AppContextId,
1759 id: NodeId,
1760) {
1761 let _ = crate::render_state::with_layout_node_registry_by_app_context(
1762 owner_context_id,
1763 |registry| {
1764 registry.unregister(id);
1765 },
1766 );
1767}
1768
1769#[cfg(test)]
1770fn layout_node_registry_stats() -> LayoutNodeRegistryDebugStats {
1771 crate::render_state::with_layout_node_registry(LayoutNodeRegistryState::stats)
1772}
1773
1774fn resolve_modifier_local_from_parent_chain(
1775 start: Option<NodeId>,
1776 token: &ModifierLocalToken,
1777) -> Option<ResolvedModifierLocal> {
1778 crate::render_state::with_layout_node_registry(|registry| {
1779 registry.resolve_modifier_local_from_parent_chain(start, token)
1780 })
1781}
1782
1783#[cfg(test)]
1784#[path = "tests/layout_node_tests.rs"]
1785mod tests;