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