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, NodeCapabilities, 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 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
43 .to_string_lossy()
44 .parse::<NodeId>()
45 .map(LayoutInvalidationDispatchDiag::Node)
46 .unwrap_or(LayoutInvalidationDispatchDiag::Disabled)
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={} invalidation={:?} curr_caps={:?} prev_caps={:?} modifier={}",
65 id,
66 invalidation,
67 curr_caps,
68 prev_caps,
69 modifier
70 );
71 }
72}
73
74#[derive(Clone, Debug)]
79pub struct LayoutState {
80 size: Size,
86 position: Point,
89 is_placed: bool,
91 node_id: Option<NodeId>,
94 pub measurement_constraints: Constraints,
96 pub content_offset: Point,
98}
99
100impl Default for LayoutState {
101 fn default() -> Self {
102 Self {
103 size: Size::default(),
104 position: Point::default(),
105 is_placed: false,
106 node_id: None,
107 measurement_constraints: Constraints {
108 min_width: 0.0,
109 max_width: f32::INFINITY,
110 min_height: 0.0,
111 max_height: f32::INFINITY,
112 },
113 content_offset: Point::default(),
114 }
115 }
116}
117
118impl LayoutState {
119 pub fn size(&self) -> Size {
120 self.size
121 }
122
123 pub fn position(&self) -> Point {
124 self.position
125 }
126
127 pub fn is_placed(&self) -> bool {
128 self.is_placed
129 }
130
131 pub(crate) fn set_node_id(&mut self, node_id: NodeId) {
132 self.node_id = Some(node_id);
133 }
134
135 pub fn set_size(&mut self, size: Size) {
138 if self.size != size {
139 if let Some(id) = self.node_id {
140 crate::render_state::record_geometry_scene_node(id);
141 }
142 self.size = size;
143 }
144 }
145
146 pub fn place(&mut self, position: Point) {
149 if self.position != position {
150 if let Some(id) = self.node_id {
151 crate::render_state::record_geometry_scene_node(id);
152 }
153 self.position = position;
154 }
155 self.is_placed = true;
156 }
157
158 pub fn clear_placed(&mut self) {
160 self.is_placed = false;
161 }
162}
163
164#[derive(Clone)]
165struct MeasurementCacheEntry {
166 constraints: Constraints,
167 measured: Rc<MeasuredNode>,
168}
169
170#[derive(Clone, Copy, Debug)]
171pub enum IntrinsicKind {
172 MinWidth(f32),
173 MaxWidth(f32),
174 MinHeight(f32),
175 MaxHeight(f32),
176}
177
178impl IntrinsicKind {
179 fn discriminant(&self) -> u8 {
180 match self {
181 IntrinsicKind::MinWidth(_) => 0,
182 IntrinsicKind::MaxWidth(_) => 1,
183 IntrinsicKind::MinHeight(_) => 2,
184 IntrinsicKind::MaxHeight(_) => 3,
185 }
186 }
187
188 fn value_bits(&self) -> u32 {
189 match self {
190 IntrinsicKind::MinWidth(value)
191 | IntrinsicKind::MaxWidth(value)
192 | IntrinsicKind::MinHeight(value)
193 | IntrinsicKind::MaxHeight(value) => value.to_bits(),
194 }
195 }
196}
197
198impl PartialEq for IntrinsicKind {
199 fn eq(&self, other: &Self) -> bool {
200 self.discriminant() == other.discriminant() && self.value_bits() == other.value_bits()
201 }
202}
203
204impl Eq for IntrinsicKind {}
205
206impl Hash for IntrinsicKind {
207 fn hash<H: Hasher>(&self, state: &mut H) {
208 self.discriminant().hash(state);
209 self.value_bits().hash(state);
210 }
211}
212
213#[derive(Default)]
214struct NodeCacheState {
215 epoch: u64,
216 measurements: Vec<MeasurementCacheEntry>,
217 intrinsics: Vec<(IntrinsicKind, f32)>,
218}
219
220#[derive(Clone, Default)]
221pub(crate) struct LayoutNodeCacheHandles {
222 state: Rc<RefCell<NodeCacheState>>,
223}
224
225impl LayoutNodeCacheHandles {
226 pub(crate) fn clear(&self) {
227 let mut state = self.state.borrow_mut();
228 state.measurements.clear();
229 state.intrinsics.clear();
230 state.epoch = 0;
231 }
232
233 pub(crate) fn activate(&self, epoch: u64) {
234 let mut state = self.state.borrow_mut();
235 if state.epoch != epoch {
236 state.measurements.clear();
237 state.intrinsics.clear();
238 state.epoch = epoch;
239 }
240 }
241
242 pub(crate) fn epoch(&self) -> u64 {
243 self.state.borrow().epoch
244 }
245
246 pub(crate) fn get_measurement(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
247 let state = self.state.borrow();
248 state
249 .measurements
250 .iter()
251 .find(|entry| entry.constraints == constraints)
252 .map(|entry| Rc::clone(&entry.measured))
253 }
254
255 pub(crate) fn store_measurement(&self, constraints: Constraints, measured: Rc<MeasuredNode>) {
256 let mut state = self.state.borrow_mut();
257 if let Some(entry) = state
258 .measurements
259 .iter_mut()
260 .find(|entry| entry.constraints == constraints)
261 {
262 entry.measured = measured;
263 } else {
264 state.measurements.push(MeasurementCacheEntry {
265 constraints,
266 measured,
267 });
268 }
269 }
270
271 pub(crate) fn get_intrinsic(&self, kind: &IntrinsicKind) -> Option<f32> {
272 let state = self.state.borrow();
273 state
274 .intrinsics
275 .iter()
276 .find(|(stored_kind, _)| stored_kind == kind)
277 .map(|(_, value)| *value)
278 }
279
280 pub(crate) fn store_intrinsic(&self, kind: IntrinsicKind, value: f32) {
281 let mut state = self.state.borrow_mut();
282 if let Some((_, existing)) = state
283 .intrinsics
284 .iter_mut()
285 .find(|(stored_kind, _)| stored_kind == &kind)
286 {
287 *existing = value;
288 } else {
289 state.intrinsics.push((kind, value));
290 }
291 }
292}
293
294pub struct LayoutNode {
295 pub modifier: Modifier,
296 modifier_chain: ModifierChainHandle,
297 resolved_modifiers: ResolvedModifiers,
298 modifier_capabilities: NodeCapabilities,
299 modifier_child_capabilities: NodeCapabilities,
300 pub measure_policy: Rc<dyn MeasurePolicy>,
301 density: crate::density::Density,
308 pub children: Vec<NodeId>,
310 cache: LayoutNodeCacheHandles,
311 needs_measure: Cell<bool>,
313 needs_layout: Cell<bool>,
314 needs_semantics: Cell<bool>,
315 needs_redraw: Cell<bool>,
316 needs_pointer_pass: Cell<bool>,
317 needs_focus_sync: Cell<bool>,
318 parent: Cell<Option<NodeId>>,
320 folded_parent: Cell<Option<NodeId>>,
322 id: Cell<Option<NodeId>>,
324 owner_context_id: Cell<Option<crate::render_state::AppContextId>>,
325 debug_modifiers: Cell<bool>,
326 is_virtual: bool,
329 virtual_children_count: Cell<usize>,
331
332 modifier_slices_snapshot: RefCell<Rc<ModifierNodeSlices>>,
333 modifier_slices_dirty: Cell<bool>,
334
335 layout_state: Rc<RefCell<LayoutState>>,
339 layout_runtime_state: Rc<RefCell<LayoutRuntimeState>>,
340}
341
342pub(crate) const RECYCLED_LAYOUT_NODE_POOL_LIMIT: usize = 128;
343
344thread_local! {
345 static EMPTY_MEASURE_POLICY: Rc<dyn MeasurePolicy> =
346 Rc::new(crate::layout::policies::EmptyMeasurePolicy);
347}
348
349fn empty_measure_policy() -> Rc<dyn MeasurePolicy> {
350 EMPTY_MEASURE_POLICY.with(Rc::clone)
351}
352
353impl LayoutNode {
354 pub fn new(modifier: Modifier, measure_policy: Rc<dyn MeasurePolicy>) -> Self {
355 Self::new_with_virtual(modifier, measure_policy, false)
356 }
357
358 pub fn new_virtual() -> Self {
361 Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), true)
362 }
363
364 fn new_recycled_shell(is_virtual: bool) -> Self {
365 let mut shell =
366 Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), is_virtual);
367 shell.needs_measure.set(false);
368 shell.needs_layout.set(false);
369 shell.needs_semantics.set(false);
370 shell.needs_redraw.set(false);
371 shell.needs_pointer_pass.set(false);
372 shell.needs_focus_sync.set(false);
373 shell.parent.set(None);
374 shell.folded_parent.set(None);
375 shell.id.set(None);
376 shell.owner_context_id.set(None);
377 shell.debug_modifiers.set(false);
378 shell.virtual_children_count.set(0);
379 shell.cache = LayoutNodeCacheHandles::default();
380 shell.modifier_slices_snapshot = RefCell::new(Rc::default());
381 shell.modifier_slices_dirty = Cell::new(true);
382 shell.layout_state = Rc::new(RefCell::new(LayoutState::default()));
383 shell.layout_runtime_state = Rc::new(RefCell::new(LayoutRuntimeState::default()));
384 shell
385 }
386
387 fn new_with_virtual(
388 modifier: Modifier,
389 measure_policy: Rc<dyn MeasurePolicy>,
390 is_virtual: bool,
391 ) -> Self {
392 let mut node = Self {
393 modifier,
394 modifier_chain: ModifierChainHandle::new(),
395 resolved_modifiers: ResolvedModifiers::default(),
396 modifier_capabilities: NodeCapabilities::default(),
397 modifier_child_capabilities: NodeCapabilities::default(),
398 measure_policy,
399 density: crate::density::Density::default(),
400 children: Vec::new(),
401 cache: LayoutNodeCacheHandles::default(),
402 needs_measure: Cell::new(true), needs_layout: Cell::new(true), needs_semantics: Cell::new(true), needs_redraw: Cell::new(true), needs_pointer_pass: Cell::new(false),
407 needs_focus_sync: Cell::new(false),
408 parent: Cell::new(None), folded_parent: Cell::new(None), id: Cell::new(None), owner_context_id: Cell::new(None),
412 debug_modifiers: Cell::new(false),
413 is_virtual,
414 virtual_children_count: Cell::new(0),
415 modifier_slices_snapshot: RefCell::new(Rc::default()),
416 modifier_slices_dirty: Cell::new(true),
417 layout_state: Rc::new(RefCell::new(LayoutState::default())),
418 layout_runtime_state: Rc::new(RefCell::new(LayoutRuntimeState::default())),
419 };
420 node.sync_modifier_chain();
421 node
422 }
423
424 pub fn set_modifier(&mut self, modifier: Modifier) {
425 let modifier_changed = !self.modifier.structural_eq(&modifier);
431 self.modifier = modifier;
432 self.sync_modifier_chain();
433 if modifier_changed {
434 self.cache.clear();
435 self.request_semantics_update();
436 }
437 }
438
439 fn sync_modifier_chain(&mut self) {
440 let prev_caps = self.modifier_capabilities;
441 let start_parent = self.parent();
442 let mut resolver = move |token: &ModifierLocalToken| {
443 resolve_modifier_local_from_parent_chain(start_parent, token)
444 };
445 self.modifier_chain
446 .set_debug_logging(self.debug_modifiers.get());
447 self.modifier_chain.set_node_id(self.id.get());
448 let modifier_local_invalidations = self
449 .modifier_chain
450 .update_with_resolver(&self.modifier, &mut resolver);
451 self.resolved_modifiers = self.modifier_chain.resolved_modifiers();
452 self.modifier_capabilities = self.modifier_chain.capabilities();
453 self.modifier_child_capabilities = self.modifier_chain.aggregate_child_capabilities();
454
455 self.update_modifier_slices_cache();
456
457 let mut invalidations = self.modifier_chain.take_invalidations();
458 invalidations.extend(modifier_local_invalidations);
459 self.dispatch_modifier_invalidations_with_prev(&invalidations, prev_caps);
460 self.refresh_registry_state();
461 }
462
463 fn update_modifier_slices_cache(&self) {
464 use crate::modifier::collect_modifier_slices_into;
465
466 let mut snapshot = self.modifier_slices_snapshot.borrow_mut();
467 collect_modifier_slices_into(self.modifier_chain.chain(), Rc::make_mut(&mut snapshot));
468 self.modifier_slices_dirty.set(false);
469 }
470
471 pub(crate) fn mark_modifier_slices_dirty(&self) {
472 self.modifier_slices_dirty.set(true);
473 }
474
475 #[cfg(test)]
476 fn dispatch_modifier_invalidations(&self, invalidations: &[ModifierInvalidation]) {
477 self.dispatch_modifier_invalidations_with_prev(invalidations, NodeCapabilities::empty());
478 }
479
480 fn dispatch_modifier_invalidations_with_prev(
481 &self,
482 invalidations: &[ModifierInvalidation],
483 prev_caps: NodeCapabilities,
484 ) {
485 let curr_caps = self.modifier_capabilities;
486 for invalidation in invalidations {
487 self.modifier_slices_dirty.set(true);
488 let has_capability =
489 |capability| curr_caps.contains(capability) || prev_caps.contains(capability);
490 match invalidation.kind() {
491 InvalidationKind::Layout => {
492 if has_capability(NodeCapabilities::LAYOUT) {
493 self.mark_needs_measure();
494 if let Some(id) = self.id.get() {
495 log_layout_invalidation_dispatch(
496 id,
497 invalidation,
498 curr_caps,
499 prev_caps,
500 &self.modifier,
501 );
502 let inside_composition =
503 cranpose_core::composer_context::try_with_composer(|_| ())
504 .is_some();
505 if inside_composition {
506 cranpose_core::bubble_measure_dirty_in_composer(id);
507 } else {
508 crate::schedule_layout_repass(id);
509 }
510 }
511 }
512 }
513 InvalidationKind::Draw => {
514 if has_capability(NodeCapabilities::DRAW)
515 || invalidation.capabilities().contains(NodeCapabilities::DRAW)
516 {
517 self.mark_needs_redraw();
518 }
519 }
520 InvalidationKind::PointerInput => {
521 if has_capability(NodeCapabilities::POINTER_INPUT) {
522 self.mark_needs_pointer_pass();
523 crate::request_pointer_invalidation();
524 if let Some(id) = self.id.get() {
526 crate::schedule_pointer_repass(id);
527 }
528 }
529 }
530 InvalidationKind::Semantics => {
531 self.request_semantics_update();
532 }
533 InvalidationKind::Focus => {
534 if has_capability(NodeCapabilities::FOCUS) {
535 self.mark_needs_focus_sync();
536 crate::request_focus_invalidation();
537 if let Some(id) = self.id.get() {
539 crate::schedule_focus_invalidation(id);
540 }
541 }
542 }
543 }
544 }
545 }
546
547 pub fn density(&self) -> crate::density::Density {
549 self.density
550 }
551
552 pub fn set_density(&mut self, density: crate::density::Density) {
554 if self.density != density {
555 self.density = density;
556 self.cache.clear();
557 self.mark_needs_measure();
558 }
559 }
560
561 pub fn set_measure_policy(&mut self, policy: Rc<dyn MeasurePolicy>) {
562 if !Rc::ptr_eq(&self.measure_policy, &policy) {
564 self.measure_policy = policy;
565 self.cache.clear();
566 self.mark_needs_measure();
567 if let Some(id) = self.id.get() {
568 cranpose_core::bubble_measure_dirty_in_composer(id);
569 }
570 }
571 }
572
573 pub fn mark_needs_measure(&self) {
575 self.needs_measure.set(true);
576 self.needs_layout.set(true);
577 }
578
579 pub fn mark_needs_layout(&self) {
581 self.needs_layout.set(true);
582 }
583
584 pub fn mark_needs_redraw(&self) {
586 self.needs_redraw.set(true);
587 if let Some(id) = self.id.get() {
588 crate::schedule_draw_repass(id);
589 }
590 crate::request_render_invalidation();
591 }
592
593 pub fn needs_measure(&self) -> bool {
595 self.needs_measure.get()
596 }
597
598 pub fn needs_layout(&self) -> bool {
600 self.needs_layout.get()
601 }
602
603 pub fn mark_needs_semantics(&self) {
605 self.needs_semantics.set(true);
606 }
607
608 pub(crate) fn clear_needs_semantics(&self) {
610 self.needs_semantics.set(false);
611 }
612
613 pub fn needs_semantics(&self) -> bool {
615 self.needs_semantics.get()
616 }
617
618 pub fn needs_redraw(&self) -> bool {
620 self.needs_redraw.get()
621 }
622
623 pub fn clear_needs_redraw(&self) {
624 self.needs_redraw.set(false);
625 }
626
627 fn request_semantics_update(&self) {
628 let already_dirty = self.needs_semantics.replace(true);
629 if already_dirty {
630 return;
631 }
632
633 if let Some(id) = self.id.get() {
634 cranpose_core::queue_semantics_invalidation(id);
635 }
636 }
637
638 pub(crate) fn clear_needs_measure(&self) {
640 self.needs_measure.set(false);
641 }
642
643 pub(crate) fn clear_needs_layout(&self) {
645 self.needs_layout.set(false);
646 }
647
648 pub fn mark_needs_pointer_pass(&self) {
650 self.needs_pointer_pass.set(true);
651 }
652
653 pub fn needs_pointer_pass(&self) -> bool {
655 self.needs_pointer_pass.get()
656 }
657
658 pub fn clear_needs_pointer_pass(&self) {
660 self.needs_pointer_pass.set(false);
661 }
662
663 pub fn mark_needs_focus_sync(&self) {
665 self.needs_focus_sync.set(true);
666 }
667
668 pub fn needs_focus_sync(&self) -> bool {
670 self.needs_focus_sync.get()
671 }
672
673 pub fn clear_needs_focus_sync(&self) {
675 self.needs_focus_sync.set(false);
676 }
677
678 pub fn set_node_id(&mut self, id: NodeId) {
680 if let Some(existing) = self.id.replace(Some(id))
681 && let Some(owner_context_id) = self.owner_context_id.take()
682 {
683 unregister_layout_node(owner_context_id, existing);
684 }
685 self.layout_state.borrow_mut().set_node_id(id);
686 let owner_context_id = register_layout_node(id, self);
687 self.owner_context_id.set(Some(owner_context_id));
688 self.refresh_registry_state();
689
690 self.modifier_chain.set_node_id(Some(id));
693 let invalidations = self.modifier_chain.take_invalidations();
694 self.dispatch_modifier_invalidations_with_prev(&invalidations, NodeCapabilities::empty());
695 self.update_modifier_slices_cache();
696 }
697
698 pub fn node_id(&self) -> Option<NodeId> {
700 self.id.get()
701 }
702
703 pub fn set_parent(&self, parent: NodeId) {
706 self.folded_parent.set(Some(parent));
707 self.parent.set(Some(parent));
710 self.refresh_registry_state();
711 }
712
713 pub fn clear_parent(&self) {
715 self.folded_parent.set(None);
716 self.parent.set(None);
717 self.refresh_registry_state();
718 }
719
720 pub fn parent(&self) -> Option<NodeId> {
722 self.parent.get()
723 }
724
725 pub fn folded_parent(&self) -> Option<NodeId> {
727 self.folded_parent.get()
728 }
729
730 pub fn is_virtual(&self) -> bool {
732 self.is_virtual
733 }
734
735 pub(crate) fn cache_handles(&self) -> LayoutNodeCacheHandles {
736 self.cache.clone()
737 }
738
739 pub fn resolved_modifiers(&self) -> ResolvedModifiers {
740 self.resolved_modifiers
741 }
742
743 pub fn modifier_capabilities(&self) -> NodeCapabilities {
744 self.modifier_capabilities
745 }
746
747 pub fn modifier_child_capabilities(&self) -> NodeCapabilities {
748 self.modifier_child_capabilities
749 }
750
751 pub fn set_debug_modifiers(&mut self, enabled: bool) {
752 self.debug_modifiers.set(enabled);
753 self.modifier_chain.set_debug_logging(enabled);
754 }
755
756 pub fn debug_modifiers_enabled(&self) -> bool {
757 self.debug_modifiers.get()
758 }
759
760 pub fn modifier_locals_handle(&self) -> ModifierLocalsHandle {
761 self.modifier_chain.modifier_locals_handle()
762 }
763
764 pub fn has_layout_modifier_nodes(&self) -> bool {
765 self.modifier_capabilities
766 .contains(NodeCapabilities::LAYOUT)
767 }
768
769 pub fn has_draw_modifier_nodes(&self) -> bool {
770 self.modifier_capabilities.contains(NodeCapabilities::DRAW)
771 }
772
773 pub fn has_pointer_input_modifier_nodes(&self) -> bool {
774 self.modifier_capabilities
775 .contains(NodeCapabilities::POINTER_INPUT)
776 }
777
778 pub fn has_semantics_modifier_nodes(&self) -> bool {
779 self.modifier_capabilities
780 .contains(NodeCapabilities::SEMANTICS)
781 }
782
783 pub fn has_focus_modifier_nodes(&self) -> bool {
784 self.modifier_capabilities.contains(NodeCapabilities::FOCUS)
785 }
786
787 fn refresh_registry_state(&self) {
788 if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
789 let parent = self.parent();
790 let capabilities = self.modifier_child_capabilities();
791 let modifier_locals = self.modifier_locals_handle();
792 let _ = crate::render_state::with_layout_node_registry_by_app_context(
793 owner_context_id,
794 |registry| {
795 registry.update_entry(id, parent, capabilities, modifier_locals);
796 },
797 );
798 }
799 }
800
801 pub fn modifier_slices_snapshot(&self) -> Rc<ModifierNodeSlices> {
802 if self.modifier_slices_dirty.get() {
803 self.update_modifier_slices_cache();
804 }
805 self.modifier_slices_snapshot.borrow().clone()
806 }
807
808 pub fn layout_state(&self) -> LayoutState {
814 self.layout_state.borrow().clone()
815 }
816
817 pub fn measured_size(&self) -> Size {
819 self.layout_state.borrow().size
820 }
821
822 pub fn position(&self) -> Point {
824 self.layout_state.borrow().position
825 }
826
827 pub fn is_placed(&self) -> bool {
829 self.layout_state.borrow().is_placed
830 }
831
832 pub fn set_measured_size(&self, size: Size) {
836 self.layout_state.borrow_mut().set_size(size);
837 }
838
839 pub fn set_position(&self, position: Point) {
842 self.layout_state.borrow_mut().place(position);
843 }
844
845 pub fn set_measurement_constraints(&self, constraints: Constraints) {
847 self.layout_state.borrow_mut().measurement_constraints = constraints;
848 }
849
850 pub fn set_content_offset(&self, offset: Point) {
852 self.layout_state.borrow_mut().content_offset = offset;
853 }
854
855 pub fn clear_placed(&self) {
857 self.layout_state.borrow_mut().is_placed = false;
858 }
859
860 pub fn semantics_configuration(&self) -> Option<SemanticsConfiguration> {
861 crate::modifier::collect_semantics_from_chain(self.modifier_chain.chain())
862 }
863
864 pub(crate) fn modifier_chain(&self) -> &ModifierChainHandle {
866 &self.modifier_chain
867 }
868
869 pub fn with_text_field_modifier_mut<R>(
874 &mut self,
875 f: impl FnMut(&mut crate::TextFieldModifierNode) -> R,
876 ) -> Option<R> {
877 self.modifier_chain.with_text_field_modifier_mut(f)
878 }
879
880 pub fn layout_state_handle(&self) -> Rc<RefCell<LayoutState>> {
883 self.layout_state.clone()
884 }
885
886 pub(crate) fn layout_runtime_state_handle(&self) -> Rc<RefCell<LayoutRuntimeState>> {
887 self.layout_runtime_state.clone()
888 }
889
890 #[cfg(test)]
891 pub(crate) fn layout_runtime_debug_stats(&self) -> LayoutRuntimeDebugStats {
892 self.layout_runtime_state.borrow().debug_stats()
893 }
894}
895impl Clone for LayoutNode {
896 fn clone(&self) -> Self {
897 let mut node = Self {
898 modifier: self.modifier.clone(),
899 modifier_chain: ModifierChainHandle::new(),
900 resolved_modifiers: ResolvedModifiers::default(),
901 modifier_capabilities: self.modifier_capabilities,
902 modifier_child_capabilities: self.modifier_child_capabilities,
903 measure_policy: self.measure_policy.clone(),
904 density: self.density,
905 children: self.children.clone(),
906 cache: self.cache.clone(),
907 needs_measure: Cell::new(self.needs_measure.get()),
908 needs_layout: Cell::new(self.needs_layout.get()),
909 needs_semantics: Cell::new(self.needs_semantics.get()),
910 needs_redraw: Cell::new(self.needs_redraw.get()),
911 needs_pointer_pass: Cell::new(self.needs_pointer_pass.get()),
912 needs_focus_sync: Cell::new(self.needs_focus_sync.get()),
913 parent: Cell::new(self.parent.get()),
914 folded_parent: Cell::new(self.folded_parent.get()),
915 id: Cell::new(None),
916 owner_context_id: Cell::new(None),
917 debug_modifiers: Cell::new(self.debug_modifiers.get()),
918 is_virtual: self.is_virtual,
919 virtual_children_count: Cell::new(self.virtual_children_count.get()),
920 modifier_slices_snapshot: RefCell::new(Rc::default()),
921 modifier_slices_dirty: Cell::new(true),
922 layout_state: self.layout_state.clone(),
924 layout_runtime_state: self.layout_runtime_state.clone(),
925 };
926 node.sync_modifier_chain();
927 node
928 }
929}
930
931impl Node for LayoutNode {
932 fn mount(&mut self) {
933 let (chain, mut context) = self.modifier_chain.chain_and_context_mut();
934 chain.repair_chain();
935 chain.attach_nodes(&mut *context);
936 }
937
938 fn unmount(&mut self) {
939 self.modifier_chain.chain_mut().detach_nodes();
940 }
941
942 fn set_node_id(&mut self, id: NodeId) {
943 LayoutNode::set_node_id(self, id);
945 }
946
947 fn insert_child(&mut self, child: NodeId) -> bool {
948 if self.children.contains(&child) {
949 return false;
950 }
951 if is_virtual_node(child) {
952 let count = self.virtual_children_count.get();
953 self.virtual_children_count.set(count + 1);
954 }
955 self.children.push(child);
956 self.cache.clear();
957 self.mark_needs_measure();
958 true
959 }
960
961 fn remove_child(&mut self, child: NodeId) -> bool {
962 let before = self.children.len();
963 self.children.retain(|&id| id != child);
964 let removed = self.children.len() < before;
965 if removed {
966 if is_virtual_node(child) {
967 let count = self.virtual_children_count.get();
968 if count > 0 {
969 self.virtual_children_count.set(count - 1);
970 }
971 }
972 self.cache.clear();
973 self.mark_needs_measure();
974 }
975 removed
976 }
977
978 fn move_child(&mut self, from: usize, to: usize) {
979 if from == to || from >= self.children.len() {
980 return;
981 }
982 let child = self.children.remove(from);
983 let target = to.min(self.children.len());
984 self.children.insert(target, child);
985 self.cache.clear();
986 self.mark_needs_measure();
987 }
988
989 fn update_children(&mut self, children: &[NodeId]) {
990 self.children.clear();
991 self.children.extend_from_slice(children);
992 self.cache.clear();
993 self.mark_needs_measure();
994 }
995
996 fn children(&self) -> Vec<NodeId> {
997 self.children.clone()
998 }
999
1000 fn collect_children_into(&self, out: &mut smallvec::SmallVec<[NodeId; 8]>) {
1001 out.clear();
1002 out.extend(self.children.iter().copied());
1003 }
1004
1005 fn on_attached_to_parent(&mut self, parent: NodeId) {
1006 self.set_parent(parent);
1007 }
1008
1009 fn on_removed_from_parent(&mut self) {
1010 self.clear_parent();
1011 }
1012
1013 fn parent(&self) -> Option<NodeId> {
1014 self.parent.get()
1015 }
1016
1017 fn mark_needs_layout(&self) {
1018 self.needs_layout.set(true);
1019 }
1020
1021 fn needs_layout(&self) -> bool {
1022 self.needs_layout.get()
1023 }
1024
1025 fn mark_needs_measure(&self) {
1026 self.needs_measure.set(true);
1027 self.needs_layout.set(true);
1028 }
1029
1030 fn needs_measure(&self) -> bool {
1031 self.needs_measure.get()
1032 }
1033
1034 fn mark_needs_semantics(&self) {
1035 self.needs_semantics.set(true);
1036 }
1037
1038 fn needs_semantics(&self) -> bool {
1039 self.needs_semantics.get()
1040 }
1041
1042 fn set_parent_for_bubbling(&mut self, parent: NodeId) {
1047 self.parent.set(Some(parent));
1048 }
1049
1050 fn recycle_key(&self) -> Option<TypeId> {
1051 Some(TypeId::of::<Self>())
1052 }
1053
1054 fn recycle_pool_limit(&self) -> Option<usize> {
1055 Some(RECYCLED_LAYOUT_NODE_POOL_LIMIT)
1056 }
1057
1058 fn prepare_for_recycle(&mut self) {
1059 *self = Self::new_recycled_shell(self.is_virtual);
1060 }
1061
1062 fn rehouse_for_recycle(&self) -> Option<Box<dyn cranpose_core::Node>> {
1063 Some(Box::new(Self::new_recycled_shell(self.is_virtual)))
1064 }
1065
1066 fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn cranpose_core::Node>> {
1067 let mut previous = std::mem::replace(self, Self::new_recycled_shell(self.is_virtual));
1068 let node_id = previous.id.replace(None);
1069 let parent = previous.parent.get();
1070 let folded_parent = previous.folded_parent.get();
1071 let debug_modifiers = previous.debug_modifiers.get();
1072 let needs_measure = previous.needs_measure.get();
1073 let needs_layout = previous.needs_layout.get();
1074 let needs_semantics = previous.needs_semantics.get();
1075 let needs_redraw = previous.needs_redraw.get();
1076 let needs_pointer_pass = previous.needs_pointer_pass.get();
1077 let needs_focus_sync = previous.needs_focus_sync.get();
1078 let virtual_children_count = previous.virtual_children_count.get();
1079 let children = previous.children.to_vec();
1080 let modifier = previous.modifier.rehouse_for_live_compaction();
1081 let measure_policy = previous.measure_policy.clone();
1082 let layout_state = previous.layout_state.clone();
1083 let layout_runtime_state = previous.layout_runtime_state.clone();
1084
1085 previous.modifier_chain.chain_mut().detach_nodes();
1086
1087 let mut compact = Self::new_with_virtual(modifier, measure_policy, previous.is_virtual);
1088 compact.children = children;
1089 compact.parent.set(parent);
1090 compact.folded_parent.set(folded_parent);
1091 compact.id.set(node_id);
1092 compact.debug_modifiers.set(debug_modifiers);
1093 compact.needs_measure.set(needs_measure);
1094 compact.needs_layout.set(needs_layout);
1095 compact.needs_semantics.set(needs_semantics);
1096 compact.needs_redraw.set(needs_redraw);
1097 compact.needs_pointer_pass.set(needs_pointer_pass);
1098 compact.needs_focus_sync.set(needs_focus_sync);
1099 compact.virtual_children_count.set(virtual_children_count);
1100 compact.layout_state = layout_state;
1101 compact.layout_runtime_state = layout_runtime_state;
1102 compact.sync_modifier_chain();
1103 if let Some(id) = node_id {
1104 let owner_context_id = register_layout_node(id, &compact);
1105 compact.owner_context_id.set(Some(owner_context_id));
1106 }
1107
1108 Some(Box::new(compact))
1109 }
1110}
1111
1112impl Drop for LayoutNode {
1113 fn drop(&mut self) {
1114 if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
1115 unregister_layout_node(owner_context_id, id);
1116 }
1117 }
1118}
1119
1120const MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY: usize = 128;
1121const VIRTUAL_NODE_ID_START: NodeId = 0xC0000000;
1122
1123#[cfg(test)]
1124#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1125struct LayoutNodeRegistryDebugStats {
1126 len: usize,
1127 capacity: usize,
1128}
1129
1130struct LayoutNodeRegistryEntry {
1131 parent: Option<NodeId>,
1132 modifier_child_capabilities: NodeCapabilities,
1133 modifier_locals: ModifierLocalsHandle,
1134 is_virtual: bool,
1135}
1136
1137pub(crate) struct LayoutNodeRegistryState {
1138 entries: RefCell<HashMap<NodeId, LayoutNodeRegistryEntry>>,
1139 virtual_node_id_counter: Cell<NodeId>,
1140}
1141
1142impl LayoutNodeRegistryState {
1143 pub(crate) fn new() -> Self {
1144 Self {
1145 entries: RefCell::new(HashMap::new()),
1146 virtual_node_id_counter: Cell::new(VIRTUAL_NODE_ID_START),
1147 }
1148 }
1149
1150 fn register(&self, id: NodeId, node: &LayoutNode) {
1151 self.entries.borrow_mut().insert(
1152 id,
1153 LayoutNodeRegistryEntry {
1154 parent: node.parent(),
1155 modifier_child_capabilities: node.modifier_child_capabilities(),
1156 modifier_locals: node.modifier_locals_handle(),
1157 is_virtual: node.is_virtual(),
1158 },
1159 );
1160 }
1161
1162 fn unregister(&self, id: NodeId) {
1163 let mut entries = self.entries.borrow_mut();
1164 entries.remove(&id);
1165 let should_shrink = (entries.len() <= MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY
1166 && entries.capacity() > MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1167 || entries.capacity()
1168 > entries
1169 .len()
1170 .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
1171 .saturating_mul(4);
1172 if should_shrink {
1173 let retained = entries
1174 .len()
1175 .max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY);
1176 let mut rebuilt = HashMap::new();
1177 rebuilt.reserve(retained);
1178 rebuilt.extend(entries.drain());
1179 *entries = rebuilt;
1180 }
1181 }
1182
1183 fn update_entry(
1184 &self,
1185 id: NodeId,
1186 parent: Option<NodeId>,
1187 modifier_child_capabilities: NodeCapabilities,
1188 modifier_locals: ModifierLocalsHandle,
1189 ) {
1190 if let Some(entry) = self.entries.borrow_mut().get_mut(&id) {
1191 entry.parent = parent;
1192 entry.modifier_child_capabilities = modifier_child_capabilities;
1193 entry.modifier_locals = modifier_locals;
1194 }
1195 }
1196
1197 #[cfg(test)]
1198 fn stats(&self) -> LayoutNodeRegistryDebugStats {
1199 let entries = self.entries.borrow();
1200 LayoutNodeRegistryDebugStats {
1201 len: entries.len(),
1202 capacity: entries.capacity(),
1203 }
1204 }
1205
1206 fn is_virtual_node(&self, id: NodeId) -> bool {
1207 self.entries
1208 .borrow()
1209 .get(&id)
1210 .map(|entry| entry.is_virtual)
1211 .unwrap_or(false)
1212 }
1213
1214 fn allocate_virtual_node_id(&self) -> NodeId {
1215 let id = self.virtual_node_id_counter.get();
1216 self.virtual_node_id_counter.set(id.wrapping_add(1));
1217 id
1218 }
1219
1220 fn resolve_modifier_local_from_parent_chain(
1221 &self,
1222 start: Option<NodeId>,
1223 token: &ModifierLocalToken,
1224 ) -> Option<ResolvedModifierLocal> {
1225 let mut current = start;
1226 while let Some(parent_id) = current {
1227 let (next_parent, resolved) = {
1228 let entries = self.entries.borrow();
1229 if let Some(entry) = entries.get(&parent_id) {
1230 let resolved = if entry
1231 .modifier_child_capabilities
1232 .contains(NodeCapabilities::MODIFIER_LOCALS)
1233 {
1234 entry
1235 .modifier_locals
1236 .borrow()
1237 .resolve(token)
1238 .map(|value| value.with_source(ModifierLocalSource::Ancestor))
1239 } else {
1240 None
1241 };
1242 (entry.parent, resolved)
1243 } else {
1244 (None, None)
1245 }
1246 };
1247 if let Some(value) = resolved {
1248 return Some(value);
1249 }
1250 current = next_parent;
1251 }
1252 None
1253 }
1254}
1255
1256pub(crate) fn register_layout_node(
1257 id: NodeId,
1258 node: &LayoutNode,
1259) -> crate::render_state::AppContextId {
1260 let owner_context_id = crate::render_state::current_app_context_id();
1261 let _ = crate::render_state::with_layout_node_registry_by_app_context(
1262 owner_context_id,
1263 |registry| {
1264 registry.register(id, node);
1265 },
1266 );
1267 owner_context_id
1268}
1269
1270pub(crate) fn unregister_layout_node(
1271 owner_context_id: crate::render_state::AppContextId,
1272 id: NodeId,
1273) {
1274 let _ = crate::render_state::with_layout_node_registry_by_app_context(
1275 owner_context_id,
1276 |registry| {
1277 registry.unregister(id);
1278 },
1279 );
1280}
1281
1282#[cfg(test)]
1283fn layout_node_registry_stats() -> LayoutNodeRegistryDebugStats {
1284 crate::render_state::with_layout_node_registry(|registry| registry.stats())
1285}
1286
1287pub(crate) fn is_virtual_node(id: NodeId) -> bool {
1288 crate::render_state::with_layout_node_registry(|registry| registry.is_virtual_node(id))
1289}
1290
1291pub(crate) fn allocate_virtual_node_id() -> NodeId {
1292 crate::render_state::with_layout_node_registry(|registry| registry.allocate_virtual_node_id())
1293}
1294
1295fn resolve_modifier_local_from_parent_chain(
1296 start: Option<NodeId>,
1297 token: &ModifierLocalToken,
1298) -> Option<ResolvedModifierLocal> {
1299 crate::render_state::with_layout_node_registry(|registry| {
1300 registry.resolve_modifier_local_from_parent_chain(start, token)
1301 })
1302}
1303
1304#[cfg(test)]
1305mod tests {
1306 use std::rc::Rc;
1307
1308 use cranpose_ui_graphics::Size as GeometrySize;
1309 use cranpose_ui_layout::{Measurable, MeasureResult, MeasureScope};
1310
1311 use super::*;
1312
1313 #[derive(Default)]
1314 struct TestMeasurePolicy;
1315
1316 impl MeasurePolicy for TestMeasurePolicy {
1317 fn measure(
1318 &self,
1319 _scope: &dyn MeasureScope,
1320 _measurables: &[Box<dyn Measurable>],
1321 _constraints: Constraints,
1322 ) -> MeasureResult {
1323 MeasureResult::new(
1324 GeometrySize {
1325 width: 0.0,
1326 height: 0.0,
1327 },
1328 Vec::new(),
1329 )
1330 }
1331
1332 fn min_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
1333 0.0
1334 }
1335
1336 fn max_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
1337 0.0
1338 }
1339
1340 fn min_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
1341 0.0
1342 }
1343
1344 fn max_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
1345 0.0
1346 }
1347 }
1348
1349 fn fresh_node() -> LayoutNode {
1350 LayoutNode::new(Modifier::empty(), Rc::new(TestMeasurePolicy))
1351 }
1352
1353 #[test]
1354 fn modifier_slices_cache_reuses_unique_snapshot_allocation() {
1355 let _app_context = crate::render_state::app_context_test_scope();
1356 let mut node = fresh_node();
1357 let snapshot = node.modifier_slices_snapshot();
1358 let snapshot_ptr = Rc::as_ptr(&snapshot);
1359 drop(snapshot);
1360
1361 node.set_modifier(Modifier::empty().padding(4.0));
1362
1363 let updated = node.modifier_slices_snapshot();
1364 assert_eq!(Rc::as_ptr(&updated), snapshot_ptr);
1365 }
1366
1367 #[test]
1368 fn modifier_slices_cache_preserves_live_snapshot_isolation() {
1369 let _app_context = crate::render_state::app_context_test_scope();
1370 let mut node = fresh_node();
1371 let old_snapshot = node.modifier_slices_snapshot();
1372 let old_snapshot_ptr = Rc::as_ptr(&old_snapshot);
1373
1374 node.set_modifier(Modifier::empty().padding(4.0));
1375
1376 let updated = node.modifier_slices_snapshot();
1377 assert_ne!(Rc::as_ptr(&updated), old_snapshot_ptr);
1378 assert_eq!(old_snapshot.draw_commands().len(), 0);
1379 }
1380
1381 #[test]
1382 fn layout_node_registry_retains_warm_capacity_after_large_cleanup() {
1383 let _app_context = crate::render_state::app_context_test_scope();
1384 let app_context = crate::render_state::AppContext::new_with_density(1.0);
1385 app_context.enter(|| {
1386 let nodes: Vec<_> = (0..2048)
1387 .map(|_| {
1388 let id = allocate_virtual_node_id();
1389 let node = fresh_node();
1390 let owner_context_id = register_layout_node(id, &node);
1391 (id, owner_context_id, node)
1392 })
1393 .collect();
1394
1395 for (id, owner_context_id, _) in &nodes {
1396 unregister_layout_node(*owner_context_id, *id);
1397 }
1398
1399 let stats = layout_node_registry_stats();
1400 assert_eq!(stats.len, 0);
1401 assert!(
1402 (MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY
1403 ..=MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY.saturating_mul(2))
1404 .contains(&stats.capacity),
1405 "registry warm capacity {} fell outside expected retained range {}..={}",
1406 stats.capacity,
1407 MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY,
1408 MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY.saturating_mul(2),
1409 );
1410 });
1411 }
1412
1413 #[test]
1414 fn layout_node_registry_is_scoped_by_app_context() {
1415 let _app_context = crate::render_state::app_context_test_scope();
1416 let first = crate::render_state::AppContext::new_with_density(1.0);
1417 let second = crate::render_state::AppContext::new_with_density(1.0);
1418
1419 let first_id = first.enter(allocate_virtual_node_id);
1420 let second_id = second.enter(allocate_virtual_node_id);
1421
1422 assert_eq!(first_id, VIRTUAL_NODE_ID_START);
1423 assert_eq!(second_id, VIRTUAL_NODE_ID_START);
1424
1425 let virtual_node = LayoutNode::new_virtual();
1426 let regular_node = fresh_node();
1427
1428 first.enter(|| {
1429 register_layout_node(first_id, &virtual_node);
1430 register_layout_node(101, ®ular_node);
1431 assert!(is_virtual_node(first_id));
1432 assert_eq!(layout_node_registry_stats().len, 2);
1433 });
1434
1435 second.enter(|| {
1436 assert!(!is_virtual_node(first_id));
1437 assert_eq!(layout_node_registry_stats().len, 0);
1438 assert_eq!(allocate_virtual_node_id(), VIRTUAL_NODE_ID_START + 1);
1439 });
1440
1441 first.enter(|| {
1442 let owner_context_id = crate::render_state::current_app_context_id();
1443 unregister_layout_node(owner_context_id, first_id);
1444 unregister_layout_node(owner_context_id, 101);
1445 assert_eq!(layout_node_registry_stats().len, 0);
1446 });
1447 }
1448
1449 fn invalidation(kind: InvalidationKind) -> ModifierInvalidation {
1450 ModifierInvalidation::new(kind, NodeCapabilities::for_invalidation(kind))
1451 }
1452
1453 #[test]
1454 fn layout_invalidation_requires_layout_capability() {
1455 let _app_context = crate::render_state::app_context_test_scope();
1456 let mut node = fresh_node();
1457 node.clear_needs_measure();
1458 node.clear_needs_layout();
1459 node.modifier_capabilities = NodeCapabilities::DRAW;
1460 node.modifier_child_capabilities = node.modifier_capabilities;
1461
1462 node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Layout)]);
1463
1464 assert!(!node.needs_measure());
1465 assert!(!node.needs_layout());
1466 }
1467
1468 #[test]
1469 fn semantics_configuration_reflects_modifier_state() {
1470 let _app_context = crate::render_state::app_context_test_scope();
1471 let mut node = fresh_node();
1472 node.set_modifier(Modifier::empty().semantics(|config| {
1473 config.content_description = Some("greeting".into());
1474 config.is_clickable = true;
1475 }));
1476
1477 let config = node
1478 .semantics_configuration()
1479 .expect("expected semantics configuration");
1480 assert_eq!(config.content_description.as_deref(), Some("greeting"));
1481 assert!(config.is_clickable);
1482 }
1483
1484 #[test]
1485 fn layout_invalidation_marks_flags_when_capability_present() {
1486 let _app_context = crate::render_state::app_context_test_scope();
1487 let _guard = crate::render_state::render_state_test_guard();
1488 crate::reset_render_state_for_tests();
1489 let mut node = fresh_node();
1490 node.id.set(Some(11));
1491 node.clear_needs_measure();
1492 node.clear_needs_layout();
1493 node.modifier_capabilities = NodeCapabilities::LAYOUT;
1494 node.modifier_child_capabilities = node.modifier_capabilities;
1495
1496 node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Layout)]);
1497
1498 assert!(node.needs_measure());
1499 assert!(node.needs_layout());
1500 assert_eq!(crate::take_layout_repass_nodes(), vec![11]);
1501 assert!(crate::take_layout_invalidation());
1502 }
1503
1504 #[test]
1505 fn layout_invalidation_skips_repass_while_composing() {
1506 let _app_context = crate::render_state::app_context_test_scope();
1507 let _guard = crate::render_state::render_state_test_guard();
1508 crate::reset_render_state_for_tests();
1509
1510 let node = Rc::new(RefCell::new(fresh_node()));
1511 {
1512 let mut node = node.borrow_mut();
1513 node.id.set(Some(17));
1514 node.clear_needs_measure();
1515 node.clear_needs_layout();
1516 node.modifier_capabilities = NodeCapabilities::LAYOUT;
1517 node.modifier_child_capabilities = node.modifier_capabilities;
1518 }
1519
1520 let node_for_composition = Rc::clone(&node);
1521 let _composition = crate::run_test_composition(move || {
1522 node_for_composition
1523 .borrow()
1524 .dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Layout)]);
1525 });
1526
1527 let node = node.borrow();
1528 assert!(node.needs_measure());
1529 assert!(node.needs_layout());
1530 assert!(crate::take_layout_repass_nodes().is_empty());
1531 assert!(!crate::take_layout_invalidation());
1532 }
1533
1534 #[test]
1535 fn draw_invalidation_marks_redraw_flag_when_capable() {
1536 let _app_context = crate::render_state::app_context_test_scope();
1537 let mut node = fresh_node();
1538 node.clear_needs_measure();
1539 node.clear_needs_layout();
1540 node.modifier_capabilities = NodeCapabilities::DRAW;
1541 node.modifier_child_capabilities = node.modifier_capabilities;
1542
1543 node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Draw)]);
1544
1545 assert!(node.needs_redraw());
1546 assert!(!node.needs_layout());
1547 }
1548
1549 #[test]
1550 fn draw_invalidation_capability_marks_redraw_for_layout_modifier_update() {
1551 let _app_context = crate::render_state::app_context_test_scope();
1552 let mut node = fresh_node();
1553 node.clear_needs_measure();
1554 node.clear_needs_layout();
1555 node.clear_needs_redraw();
1556 node.modifier_capabilities = NodeCapabilities::LAYOUT;
1557 node.modifier_child_capabilities = node.modifier_capabilities;
1558
1559 node.dispatch_modifier_invalidations(&[ModifierInvalidation::new(
1560 InvalidationKind::Draw,
1561 NodeCapabilities::DRAW,
1562 )]);
1563
1564 assert!(node.needs_redraw());
1565 assert!(!node.needs_measure());
1566 assert!(!node.needs_layout());
1567 }
1568
1569 #[test]
1570 fn semantics_invalidation_sets_semantics_flag_only() {
1571 let _app_context = crate::render_state::app_context_test_scope();
1572 let mut node = fresh_node();
1573 node.clear_needs_measure();
1574 node.clear_needs_layout();
1575 node.clear_needs_semantics();
1576 node.modifier_capabilities = NodeCapabilities::SEMANTICS;
1577 node.modifier_child_capabilities = node.modifier_capabilities;
1578
1579 node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Semantics)]);
1580
1581 assert!(node.needs_semantics());
1582 assert!(!node.needs_measure());
1583 assert!(!node.needs_layout());
1584 }
1585
1586 #[test]
1587 fn pointer_invalidation_requires_pointer_capability() {
1588 let _app_context = crate::render_state::app_context_test_scope();
1589 let mut node = fresh_node();
1590 node.clear_needs_pointer_pass();
1591 node.modifier_capabilities = NodeCapabilities::DRAW;
1592 node.modifier_child_capabilities = node.modifier_capabilities;
1593 node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::PointerInput)]);
1598
1599 assert!(!node.needs_pointer_pass());
1600 }
1601
1602 #[test]
1603 fn pointer_invalidation_marks_flag_and_requests_queue() {
1604 let _app_context = crate::render_state::app_context_test_scope();
1605 let mut node = fresh_node();
1606 node.clear_needs_pointer_pass();
1607 node.modifier_capabilities = NodeCapabilities::POINTER_INPUT;
1608 node.modifier_child_capabilities = node.modifier_capabilities;
1609 node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::PointerInput)]);
1614
1615 assert!(node.needs_pointer_pass());
1616 }
1617
1618 #[test]
1619 fn focus_invalidation_requires_focus_capability() {
1620 let _app_context = crate::render_state::app_context_test_scope();
1621 let mut node = fresh_node();
1622 node.clear_needs_focus_sync();
1623 node.modifier_capabilities = NodeCapabilities::DRAW;
1624 node.modifier_child_capabilities = node.modifier_capabilities;
1625 crate::take_focus_invalidation();
1626
1627 node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Focus)]);
1628
1629 assert!(!node.needs_focus_sync());
1630 assert!(!crate::take_focus_invalidation());
1631 }
1632
1633 #[test]
1634 fn focus_invalidation_marks_flag_and_requests_queue() {
1635 let _app_context = crate::render_state::app_context_test_scope();
1636 let mut node = fresh_node();
1637 node.clear_needs_focus_sync();
1638 node.modifier_capabilities = NodeCapabilities::FOCUS;
1639 node.modifier_child_capabilities = node.modifier_capabilities;
1640 crate::take_focus_invalidation();
1641
1642 node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Focus)]);
1643
1644 assert!(node.needs_focus_sync());
1645 assert!(crate::take_focus_invalidation());
1646 }
1647
1648 #[test]
1649 fn set_modifier_marks_semantics_dirty() {
1650 let _app_context = crate::render_state::app_context_test_scope();
1651 let mut node = fresh_node();
1652 node.clear_needs_semantics();
1653 node.set_modifier(Modifier::empty().semantics(|config| {
1654 config.is_clickable = true;
1655 }));
1656
1657 assert!(node.needs_semantics());
1658 }
1659
1660 #[test]
1661 fn modifier_child_capabilities_reflect_chain_head() {
1662 let _app_context = crate::render_state::app_context_test_scope();
1663 let mut node = fresh_node();
1664 node.set_modifier(Modifier::empty().padding(4.0));
1665 assert!(
1666 node.modifier_child_capabilities()
1667 .contains(NodeCapabilities::LAYOUT),
1668 "padding should introduce layout capability"
1669 );
1670 }
1671}