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