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