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