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