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