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