1use std::{
9 fmt,
10 hash::{Hash, Hasher},
11 rc::Rc,
12};
13
14use cranpose_core::{ProvidedValue, hash::default};
15
16mod alignment;
17mod background;
18mod blur;
19mod chain;
20mod clickable;
21mod coordinator_geometry;
22mod drag_and_drop;
23mod draw_cache;
24mod fill;
25mod focus;
26mod focus_ring;
27mod graphics_layer;
28mod local;
29mod minimum_interactive;
30mod offset;
31mod padding;
32mod pointer_icon;
33pub(crate) mod pointer_input;
34mod rotary_input;
35mod scroll;
36mod selectable;
37mod semantics;
38mod shadow;
39mod size;
40mod slices;
41mod toggleable;
42mod weight;
43mod window_root;
44
45pub use chain::{ModifierChainHandle, ModifierChainInspectorNode, ModifierLocalsHandle};
46pub(crate) use coordinator_geometry::{CoordinatorGeometry, CoordinatorRect};
47pub use cranpose_foundation::{
48 AnyModifierElement, DynModifierElement, FocusState, PointerEvent, PointerEventKind,
49 PointerSource, RotaryScrollEvent, SemanticsConfiguration, modifier_element,
50};
51use cranpose_foundation::{ModifierNodeElement, NodeCapabilities, ProgressBarRangeInfo};
52#[expect(unused_imports)]
53pub use cranpose_ui_graphics::{
54 BlendMode, BlurredEdgeTreatment, Brush, Color, ColorFilter, CompositingStrategy, CornerRadii,
55 CursorIcon, CustomPointerIcon, CutDirection, Dp, DpOffset, EdgeInsets, GradientCutMaskSpec,
56 GradientFadeMaskSpec, GraphicsLayer, LayerShape, Point, PointerIcon, PointerIconError, Rect,
57 RenderEffect, RoundedCornerShape, RuntimeShader, Shadow, ShadowScope, Size, TransformOrigin,
58};
59use cranpose_ui_layout::{Alignment, HorizontalAlignment, IntrinsicSize, VerticalAlignment};
60pub use drag_and_drop::{
61 DragAndDropEvent, DragAndDropOutcome, DragAndDropPayload, DragAndDropPoint, DragAndDropSource,
62 DragAndDropSourceElement, DragAndDropSourceNode, DragAndDropState, DragAndDropTarget,
63 DragAndDropTargetElement, DragAndDropTargetNode,
64};
65use focus::FocusTargetElement;
66pub use focus::{FocusDirection, FocusRequestError, FocusRequester, FocusRequesterElement};
67pub use graphics_layer::GlassMaterial;
68pub(crate) use local::{
69 ModifierLocalAncestorResolver, ModifierLocalSource, ModifierLocalToken, ResolvedModifierLocal,
70};
71use local::{ModifierLocalConsumerElement, ModifierLocalProviderElement};
72pub use local::{ModifierLocalKey, ModifierLocalReadScope};
73#[expect(unused_imports)]
74pub use pointer_input::{AwaitPointerEventScope, PointerInputScope};
75pub use rotary_input::RotaryInputModifierNode;
76#[cfg(test)]
77pub(crate) use scroll::lazy_scroll_semantics;
78#[cfg(feature = "test-helpers")]
79pub use scroll::{last_fling_velocity, reset_last_fling_velocity};
80use semantics::SemanticsElement;
81pub use semantics::{
82 SemanticsRequester, SemanticsRequesterElement, collect_semantics_from_chain,
83 collect_semantics_from_modifier, semantics_reach_of_chain,
84};
85pub(crate) use slices::collect_modifier_slices_into_shared;
86pub use slices::{
87 ModifierNodeSlices, ModifierNodeSlicesDebugStats, collect_modifier_slices,
88 collect_slices_from_modifier,
89};
90pub use window_root::{
91 WindowRootDescriptor, WindowRootElement, WindowRootEntry, WindowRootNode, WindowRootRegistry,
92 is_window_root, nearest_window_root, nearest_window_roots, window_roots, window_roots_revision,
93};
94
95pub use crate::draw::{DrawCacheBuilder, DrawCommand};
96use crate::modifier_nodes::ClipToBoundsElement;
97
98#[derive(Clone, Debug, Default)]
99pub struct InspectorInfo {
100 properties: Vec<InspectorProperty>,
101}
102
103impl InspectorInfo {
104 pub fn new() -> Self {
105 Self::default()
106 }
107
108 pub fn add_property<V: Into<String>>(&mut self, name: &'static str, value: V) {
109 self.properties.push(InspectorProperty {
110 name,
111 value: value.into(),
112 });
113 }
114
115 pub fn properties(&self) -> &[InspectorProperty] {
116 &self.properties
117 }
118
119 pub fn is_empty(&self) -> bool {
120 self.properties.is_empty()
121 }
122
123 pub fn add_dimension(&mut self, name: &'static str, constraint: DimensionConstraint) {
124 self.add_property(name, describe_dimension(constraint));
125 }
126
127 pub fn add_offset_components(
128 &mut self,
129 x_name: &'static str,
130 y_name: &'static str,
131 offset: Point,
132 ) {
133 self.add_property(x_name, offset.x.to_string());
134 self.add_property(y_name, offset.y.to_string());
135 }
136
137 pub fn add_alignment<A>(&mut self, name: &'static str, alignment: A)
138 where
139 A: fmt::Debug,
140 {
141 self.add_property(name, format!("{alignment:?}"));
142 }
143}
144
145#[derive(Clone, Debug, PartialEq)]
146pub struct InspectorProperty {
147 pub name: &'static str,
148 pub value: String,
149}
150
151#[derive(Clone, Debug, PartialEq)]
152pub struct ModifierInspectorRecord {
153 pub name: &'static str,
154 pub properties: Vec<InspectorProperty>,
155}
156
157#[derive(Clone, Debug)]
158pub(crate) struct InspectorMetadata {
159 name: &'static str,
160 info: InspectorInfo,
161}
162
163impl InspectorMetadata {
164 pub(crate) fn new<F>(name: &'static str, recorder: F) -> Self
165 where
166 F: FnOnce(&mut InspectorInfo),
167 {
168 let mut info = InspectorInfo::new();
169 recorder(&mut info);
170 Self { name, info }
171 }
172
173 fn is_empty(&self) -> bool {
174 self.info.is_empty()
175 }
176
177 fn to_record(&self) -> ModifierInspectorRecord {
178 ModifierInspectorRecord {
179 name: self.name,
180 properties: self.info.properties().to_vec(),
181 }
182 }
183}
184
185fn describe_dimension(constraint: DimensionConstraint) -> String {
186 match constraint {
187 DimensionConstraint::Unspecified => "unspecified".to_string(),
188 DimensionConstraint::Points(value) => value.to_string(),
189 DimensionConstraint::Fraction(value) => format!("fraction({value})"),
190 DimensionConstraint::Intrinsic(size) => format!("intrinsic({size:?})"),
191 }
192}
193
194fn inspector_slice(inspector: &Option<Rc<Vec<InspectorMetadata>>>) -> &[InspectorMetadata] {
195 inspector.as_deref().map_or(&[], Vec::as_slice)
196}
197
198fn merged_inspector(
201 first: &[InspectorMetadata],
202 second: &[InspectorMetadata],
203) -> Option<Rc<Vec<InspectorMetadata>>> {
204 if first.is_empty() && second.is_empty() {
205 return None;
206 }
207 let mut merged = Vec::with_capacity(first.len() + second.len());
208 merged.extend_from_slice(first);
209 merged.extend_from_slice(second);
210 Some(Rc::new(merged))
211}
212
213pub(crate) fn inspector_metadata<F>(name: &'static str, recorder: F) -> InspectorMetadata
214where
215 F: FnOnce(&mut InspectorInfo),
216{
217 if !inspector_metadata_enabled() {
218 return InspectorMetadata::new(name, |_| {});
219 }
220 InspectorMetadata::new(name, recorder)
221}
222
223pub(crate) fn modifier_debug_enabled() -> bool {
224 #[cfg(not(target_arch = "wasm32"))]
225 {
226 cranpose_core::env_flag!("COMPOSE_DEBUG_MODIFIERS")
227 }
228 #[cfg(target_arch = "wasm32")]
229 {
230 false
231 }
232}
233
234fn inspector_metadata_enabled() -> bool {
235 cfg!(any(test, feature = "inspection")) || modifier_debug_enabled()
236}
237
238#[derive(Clone)]
239enum ModifierKind {
240 Empty,
241 Single {
242 elements: Rc<Vec<DynModifierElement>>,
243 inspector: Option<Rc<Vec<InspectorMetadata>>>,
246 },
247}
248
249const FINGERPRINT_KIND_EMPTY: u8 = 0;
250const FINGERPRINT_KIND_SINGLE: u8 = 1;
251
252const FINGERPRINT_EMPTY_STRICT_SEED: u64 = 0x243f_6a88_85a3_08d3;
253const FINGERPRINT_EMPTY_STRUCTURAL_SEED: u64 = 0x1319_8a2e_0370_7344;
254const FINGERPRINT_SINGLE_STRICT_SEED: u64 = 0xa409_3822_299f_31d0;
255const FINGERPRINT_SINGLE_STRUCTURAL_SEED: u64 = 0x082e_fa98_ec4e_6c89;
256const FINGERPRINT_SEQUENCE_MUL: u64 = 0x9e37_79b1_85eb_ca87;
257const FINGERPRINT_STRICT_UPDATE_TAG: u64 = 0xdbe6_d5d5_fe4c_ce2f;
258const FINGERPRINT_STRUCTURAL_DRAW_ONLY_TAG: u64 = 0x94d0_49bb_1331_11eb;
259
260#[derive(Clone, Copy, Debug, PartialEq, Eq)]
261struct ModifierFingerprints {
262 strict: u64,
263 structural: u64,
264}
265
266#[inline]
267fn mix_fingerprint_bits(mut value: u64) -> u64 {
268 value ^= value >> 33;
269 value = value.wrapping_mul(0xff51_afd7_ed55_8ccd);
270 value ^= value >> 33;
271 value = value.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
272 value ^ (value >> 33)
273}
274
275#[inline]
276fn fold_fingerprint(state: u64, value: u64) -> u64 {
277 mix_fingerprint_bits(state ^ value.wrapping_add(FINGERPRINT_SEQUENCE_MUL))
278 .wrapping_mul(FINGERPRINT_SEQUENCE_MUL)
279}
280
281#[inline]
282fn empty_fingerprints() -> ModifierFingerprints {
283 ModifierFingerprints {
284 strict: fold_fingerprint(FINGERPRINT_EMPTY_STRICT_SEED, FINGERPRINT_KIND_EMPTY as u64),
285 structural: fold_fingerprint(
286 FINGERPRINT_EMPTY_STRUCTURAL_SEED,
287 FINGERPRINT_KIND_EMPTY as u64,
288 ),
289 }
290}
291
292#[inline]
293fn single_fingerprint_seed() -> ModifierFingerprints {
294 let strict = fold_fingerprint(
295 FINGERPRINT_SINGLE_STRICT_SEED,
296 FINGERPRINT_KIND_SINGLE as u64,
297 );
298 let structural = fold_fingerprint(
299 FINGERPRINT_SINGLE_STRUCTURAL_SEED,
300 FINGERPRINT_KIND_SINGLE as u64,
301 );
302 ModifierFingerprints { strict, structural }
303}
304
305#[inline]
306fn element_common_fingerprint(element: &DynModifierElement) -> u64 {
307 let mut hasher = default::new();
308 element.element_type().hash(&mut hasher);
309 element.capabilities().bits().hash(&mut hasher);
310 hasher.finish()
311}
312
313#[inline]
314fn element_fingerprints(element: &DynModifierElement) -> ModifierFingerprints {
315 let common = element_common_fingerprint(element);
316 let requires_update = element.requires_update();
317 let strict_payload = if requires_update {
318 let element_ptr = Rc::as_ptr(element) as *const () as usize as u64;
319 element_ptr ^ FINGERPRINT_STRICT_UPDATE_TAG
320 } else {
321 element.hash_code()
322 };
323 let strict = mix_fingerprint_bits(common ^ strict_payload);
324
325 let is_draw_only = element.capabilities() == NodeCapabilities::DRAW;
326 let structural_payload = if is_draw_only {
327 FINGERPRINT_STRUCTURAL_DRAW_ONLY_TAG
328 } else {
329 element.hash_code()
330 };
331 let structural = mix_fingerprint_bits(common ^ structural_payload);
332
333 ModifierFingerprints { strict, structural }
334}
335
336#[inline]
337fn append_fingerprints(
338 mut fingerprints: ModifierFingerprints,
339 elements: &[DynModifierElement],
340) -> ModifierFingerprints {
341 for element in elements {
342 let element_fingerprints = element_fingerprints(element);
343 fingerprints.strict = fold_fingerprint(fingerprints.strict, element_fingerprints.strict);
344 fingerprints.structural =
345 fold_fingerprint(fingerprints.structural, element_fingerprints.structural);
346 }
347 fingerprints
348}
349
350fn single_fingerprints(elements: &[DynModifierElement]) -> ModifierFingerprints {
351 append_fingerprints(single_fingerprint_seed(), elements)
352}
353
354pub struct ModifierElementIterator<'a> {
355 inner: std::slice::Iter<'a, DynModifierElement>,
356}
357
358impl<'a> Iterator for ModifierElementIterator<'a> {
359 type Item = &'a DynModifierElement;
360
361 #[inline]
362 fn next(&mut self) -> Option<Self::Item> {
363 self.inner.next()
364 }
365
366 #[inline]
367 fn size_hint(&self) -> (usize, Option<usize>) {
368 self.inner.size_hint()
369 }
370}
371
372impl ExactSizeIterator for ModifierElementIterator<'_> {}
373
374pub(crate) struct ModifierInspectorIterator<'a> {
375 inner: std::slice::Iter<'a, InspectorMetadata>,
376}
377
378impl<'a> Iterator for ModifierInspectorIterator<'a> {
379 type Item = &'a InspectorMetadata;
380
381 #[inline]
382 fn next(&mut self) -> Option<Self::Item> {
383 self.inner.next()
384 }
385
386 #[inline]
387 fn size_hint(&self) -> (usize, Option<usize>) {
388 self.inner.size_hint()
389 }
390}
391
392impl ExactSizeIterator for ModifierInspectorIterator<'_> {}
393
394#[derive(Clone)]
412pub struct Modifier {
413 kind: ModifierKind,
414 strict_fingerprint: u64,
415 structural_fingerprint: u64,
416 element_count: usize,
417 provides_composition_locals: bool,
418}
419
420impl Default for Modifier {
421 fn default() -> Self {
422 let fingerprints = empty_fingerprints();
423 Self {
424 kind: ModifierKind::Empty,
425 strict_fingerprint: fingerprints.strict,
426 structural_fingerprint: fingerprints.structural,
427 element_count: 0,
428 provides_composition_locals: false,
429 }
430 }
431}
432
433impl Modifier {
434 pub fn empty() -> Self {
435 Self::default()
436 }
437
438 pub fn from_element<E>(element: E) -> Self
440 where
441 E: ModifierNodeElement,
442 {
443 Self::with_element(element)
444 }
445
446 pub fn clip_to_bounds(self) -> Self {
450 let modifier = Self::with_element(ClipToBoundsElement::new()).with_inspector_metadata(
451 inspector_metadata("clipToBounds", |info| {
452 info.add_property("clipToBounds", "true");
453 }),
454 );
455 self.then(modifier)
456 }
457
458 pub fn modifier_local_provider<T, F>(self, key: ModifierLocalKey<T>, value: F) -> Self
459 where
460 T: 'static,
461 F: Fn() -> T + 'static,
462 {
463 let element = ModifierLocalProviderElement::new(key, value);
464 let modifier = Modifier::from_parts(vec![modifier_element(element)]);
465 self.then(modifier)
466 }
467
468 pub fn modifier_local_consumer<F>(self, consumer: F) -> Self
469 where
470 F: for<'scope> Fn(&mut ModifierLocalReadScope<'scope>) + 'static,
471 {
472 let element = ModifierLocalConsumerElement::new(consumer);
473 let modifier = Modifier::from_parts(vec![modifier_element(element)]);
474 self.then(modifier)
475 }
476
477 pub fn semantics_spec(self, spec: cranpose_foundation::SemanticsSpec) -> Self {
488 self.semantics(move |config: &mut SemanticsConfiguration| config.merge(&spec))
489 }
490 pub fn semantics<F>(self, recorder: F) -> Self
491 where
492 F: Fn(&mut SemanticsConfiguration) + 'static,
493 {
494 let recorder: std::rc::Rc<dyn Fn(&mut SemanticsConfiguration)> = std::rc::Rc::new(recorder);
495 let metadata = if inspector_metadata_enabled() {
496 let mut preview = SemanticsConfiguration::default();
497 recorder(&mut preview);
498 let description = preview.content_description.clone();
499 let state_description = preview.state_description.clone();
500 let role = preview.role;
501 let is_clickable = preview.is_activatable();
502 let canvas_children = preview.canvas_children.len();
503 inspector_metadata("semantics", move |info| {
504 if let Some(desc) = &description {
505 info.add_property("contentDescription", desc.clone());
506 }
507 if let Some(state) = &state_description {
508 info.add_property("stateDescription", state.clone());
509 }
510 if let Some(role) = role {
511 info.add_property("role", format!("{role:?}"));
512 }
513 if is_clickable {
514 info.add_property("isClickable", "true");
515 }
516 if canvas_children > 0 {
517 info.add_property("canvasSemanticsChildren", canvas_children.to_string());
518 }
519 })
520 } else {
521 inspector_metadata("semantics", |_| {})
522 };
523 let element = SemanticsElement::new(recorder);
524 let modifier =
525 Modifier::from_parts(vec![modifier_element(element)]).with_inspector_metadata(metadata);
526 self.then(modifier)
527 }
528
529 pub fn progress_semantics(self, current: f32, start: f32, end: f32, steps: u32) -> Self {
538 let info = ProgressBarRangeInfo::new(current, start, end, steps);
539 self.semantics(move |config| config.progress = Some(info))
540 }
541
542 pub fn dropdown_list(self) -> Self {
546 self.role(cranpose_foundation::SemanticsWidgetRole::DropdownList)
547 }
548
549 pub fn value_picker(self) -> Self {
552 self.role(cranpose_foundation::SemanticsWidgetRole::ValuePicker)
553 }
554
555 pub fn expand(self, action: impl Fn() -> bool + 'static) -> Self {
560 let action = cranpose_foundation::SemanticsExpand::new(action);
561 self.semantics(move |config| config.expand = Some(action.clone()))
562 }
563
564 pub fn on_long_click(
574 self,
575 label: impl Into<String>,
576 action: impl Fn() -> bool + 'static,
577 ) -> Self {
578 let label = label.into();
579 let action = cranpose_foundation::SemanticsLongClick::new(action);
580 self.semantics(move |config| {
581 config.on_long_click_label = Some(label.clone());
582 config.on_long_click = Some(action.clone());
583 })
584 }
585
586 pub fn on_magic_tap(
591 self,
592 label: impl Into<String>,
593 action: impl Fn() -> bool + 'static,
594 ) -> Self {
595 let label = label.into();
596 let action = cranpose_foundation::SemanticsMagicTap::new(action);
597 self.semantics(move |config| {
598 config.on_magic_tap_label = Some(label.clone());
599 config.on_magic_tap = Some(action.clone());
600 })
601 }
602
603 pub fn input_labels<S: Into<String>>(self, labels: impl IntoIterator<Item = S>) -> Self {
607 let labels: Vec<String> = labels.into_iter().map(Into::into).collect();
608 self.semantics(move |config| config.input_labels.clone_from(&labels))
609 }
610
611 pub fn language(self, tag: impl Into<String>) -> Self {
615 let tag = tag.into();
616 self.semantics(move |config| config.language = Some(tag.clone()))
617 }
618
619 pub fn collapse(self, action: impl Fn() -> bool + 'static) -> Self {
623 let action = cranpose_foundation::SemanticsExpand::new(action);
624 self.semantics(move |config| config.collapse = Some(action.clone()))
625 }
626
627 pub fn dismiss(self, action: impl Fn() -> bool + 'static) -> Self {
632 let action = cranpose_foundation::SemanticsDismiss::new(action);
633 self.semantics(move |config| config.dismiss = Some(action.clone()))
634 }
635
636 pub fn scroll_to_index(self, action: impl Fn(usize) -> bool + 'static) -> Self {
642 let action = cranpose_foundation::SemanticsScrollToIndex::new(action);
643 self.semantics(move |config| config.scroll_to_index = Some(action.clone()))
644 }
645
646 pub fn traversal_index(self, index: f32) -> Self {
652 self.semantics(move |config| config.traversal_index = index)
653 }
654
655 pub fn password(self) -> Self {
660 self.semantics(|config| config.password = true)
661 }
662
663 pub fn error(self, message: impl Into<String>) -> Self {
667 let message = message.into();
668 self.semantics(move |config| config.error = Some(message.clone()))
669 }
670
671 pub fn pane_title(self, title: impl Into<String>) -> Self {
675 let title = title.into();
676 self.semantics(move |config| config.pane_title = Some(title.clone()))
677 }
678
679 pub fn selectable_group(self) -> Self {
684 self.semantics(|config| config.selectable_group = true)
685 }
686
687 pub fn merge_descendants(self) -> Self {
692 self.semantics(|config| config.merge_descendants = true)
693 }
694
695 pub fn hide_from_accessibility(self) -> Self {
700 self.semantics(|config| config.hidden = true)
701 }
702
703 pub fn heading(self) -> Self {
708 self.role(cranpose_foundation::SemanticsWidgetRole::Header)
709 }
710
711 pub fn role(self, role: cranpose_foundation::SemanticsWidgetRole) -> Self {
716 self.semantics(move |config| config.role = Some(role))
717 }
718
719 pub fn live_region(self, mode: cranpose_foundation::LiveRegionMode) -> Self {
725 self.semantics(move |config| config.live_region = Some(mode))
726 }
727
728 pub fn content_description(self, description: impl Into<String>) -> Self {
733 let description = description.into();
734 self.semantics(move |config| config.content_description = Some(description.clone()))
735 }
736
737 pub fn focus_target(self) -> Self {
743 let element = FocusTargetElement::new();
744 let modifier = Modifier::from_parts(vec![modifier_element(element)]);
745 self.then(modifier)
746 }
747
748 pub fn on_focus_changed<F>(self, callback: F) -> Self
753 where
754 F: Fn(FocusState) + 'static,
755 {
756 let element = FocusTargetElement::with_callback(callback);
757 let modifier = Modifier::from_parts(vec![modifier_element(element)]);
758 self.then(modifier)
759 }
760
761 pub fn focus_requester(self, requester: &FocusRequester) -> Self {
768 let element = FocusRequesterElement::new(requester.clone());
769 let modifier = Modifier::from_parts(vec![modifier_element(element)]);
770 self.then(modifier)
771 }
772
773 pub fn semantics_requester(self, requester: &SemanticsRequester) -> Self {
781 let element = SemanticsRequesterElement::new(requester.clone());
782 let modifier = Modifier::from_parts(vec![modifier_element(element)]);
783 self.then(modifier)
784 }
785
786 pub fn debug_chain(self, tag: &'static str) -> Self {
804 use cranpose_foundation::{ModifierNode, ModifierNodeContext, NodeCapabilities, NodeState};
805
806 #[derive(Clone)]
807 struct DebugChainElement {
808 tag: &'static str,
809 }
810
811 impl fmt::Debug for DebugChainElement {
812 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
813 f.debug_struct("DebugChainElement")
814 .field("tag", &self.tag)
815 .finish()
816 }
817 }
818
819 impl PartialEq for DebugChainElement {
820 fn eq(&self, other: &Self) -> bool {
821 self.tag == other.tag
822 }
823 }
824
825 impl Eq for DebugChainElement {}
826
827 impl std::hash::Hash for DebugChainElement {
828 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
829 self.tag.hash(state);
830 }
831 }
832
833 impl ModifierNodeElement for DebugChainElement {
834 type Node = DebugChainNode;
835
836 fn create(&self) -> Self::Node {
837 DebugChainNode::new(self.tag)
838 }
839
840 fn update(&self, node: &mut Self::Node) {
841 node.tag = self.tag;
842 }
843
844 fn capabilities(&self) -> NodeCapabilities {
845 NodeCapabilities::empty()
846 }
847 }
848
849 struct DebugChainNode {
850 tag: &'static str,
851 state: NodeState,
852 }
853
854 impl DebugChainNode {
855 fn new(tag: &'static str) -> Self {
856 Self {
857 tag,
858 state: NodeState::new(),
859 }
860 }
861 }
862
863 impl ModifierNode for DebugChainNode {
864 fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {
865 eprintln!("[debug_chain:{}] Modifier chain attached", self.tag);
866 }
867
868 fn on_detach(&mut self) {
869 eprintln!("[debug_chain:{}] Modifier chain detached", self.tag);
870 }
871
872 fn on_reset(&mut self) {
873 eprintln!("[debug_chain:{}] Modifier chain reset", self.tag);
874 }
875 }
876
877 impl cranpose_foundation::DelegatableNode for DebugChainNode {
878 fn node_state(&self) -> &NodeState {
879 &self.state
880 }
881 }
882
883 let element = DebugChainElement { tag };
884 let modifier = Modifier::from_parts(vec![modifier_element(element)]);
885 self.then(modifier)
886 .with_inspector_metadata(inspector_metadata("debugChain", move |info| {
887 info.add_property("tag", tag);
888 }))
889 }
890
891 pub fn then(&self, next: Modifier) -> Modifier {
896 if self.is_trivially_empty() {
897 return next;
898 }
899 if next.is_trivially_empty() {
900 return self.clone();
901 }
902
903 let Some((self_elements, self_inspector)) = self.single_parts() else {
904 return next;
905 };
906 let Some((next_elements, next_inspector)) = next.single_parts() else {
907 return self.clone();
908 };
909
910 let mut merged_elements = Vec::with_capacity(self_elements.len() + next_elements.len());
911 merged_elements.extend_from_slice(self_elements);
912 merged_elements.extend_from_slice(next_elements);
913
914 let merged_inspector = merged_inspector(self_inspector, next_inspector);
915
916 let fingerprints = append_fingerprints(
917 ModifierFingerprints {
918 strict: self.strict_fingerprint,
919 structural: self.structural_fingerprint,
920 },
921 next_elements,
922 );
923 Modifier {
924 kind: ModifierKind::Single {
925 elements: Rc::new(merged_elements),
926 inspector: merged_inspector,
927 },
928 strict_fingerprint: fingerprints.strict,
929 structural_fingerprint: fingerprints.structural,
930 element_count: self.element_count + next.element_count,
931 provides_composition_locals: self.provides_composition_locals
932 || next.provides_composition_locals,
933 }
934 }
935
936 pub(crate) fn iter_elements(&self) -> ModifierElementIterator<'_> {
937 match &self.kind {
938 ModifierKind::Empty => ModifierElementIterator { inner: [].iter() },
939 ModifierKind::Single { elements, .. } => ModifierElementIterator {
940 inner: elements.iter(),
941 },
942 }
943 }
944
945 pub(crate) fn provided_composition_locals(&self) -> Vec<ProvidedValue> {
946 if !self.provides_composition_locals {
947 return Vec::new();
948 }
949 self.iter_elements()
950 .flat_map(|element| element.provided_composition_locals())
951 .collect()
952 }
953
954 pub(crate) fn iter_inspector_metadata(&self) -> ModifierInspectorIterator<'_> {
955 match &self.kind {
956 ModifierKind::Empty => ModifierInspectorIterator { inner: [].iter() },
957 ModifierKind::Single { inspector, .. } => ModifierInspectorIterator {
958 inner: inspector_slice(inspector).iter(),
959 },
960 }
961 }
962
963 #[cfg(test)]
964 pub(crate) fn elements(&self) -> Vec<DynModifierElement> {
965 match &self.kind {
966 ModifierKind::Empty => Vec::new(),
967 ModifierKind::Single { elements, .. } => elements.as_ref().clone(),
968 }
969 }
970
971 pub(crate) fn inspector_metadata(&self) -> Vec<InspectorMetadata> {
972 match &self.kind {
973 ModifierKind::Empty => Vec::new(),
974 ModifierKind::Single { inspector, .. } => {
975 inspector.as_deref().cloned().unwrap_or_default()
976 }
977 }
978 }
979
980 pub(crate) fn rehouse_for_live_compaction(&self) -> Self {
981 match &self.kind {
982 ModifierKind::Empty => Self::default(),
983 ModifierKind::Single {
984 elements,
985 inspector,
986 } => Self {
987 kind: ModifierKind::Single {
988 elements: Rc::new(elements.iter().cloned().collect()),
989 inspector: inspector
990 .as_ref()
991 .map(|inspector| Rc::new(inspector.as_ref().clone())),
992 },
993 strict_fingerprint: self.strict_fingerprint,
994 structural_fingerprint: self.structural_fingerprint,
995 element_count: self.element_count,
996 provides_composition_locals: self.provides_composition_locals,
997 },
998 }
999 }
1000
1001 pub fn total_padding(&self) -> f32 {
1002 let padding = self.padding_values();
1003 padding
1004 .left
1005 .max(padding.right)
1006 .max(padding.top)
1007 .max(padding.bottom)
1008 }
1009
1010 pub fn explicit_size(&self) -> Option<Size> {
1011 let props = self.layout_properties();
1012 match (props.width, props.height) {
1013 (DimensionConstraint::Points(width), DimensionConstraint::Points(height)) => {
1014 Some(Size { width, height })
1015 }
1016 _ => None,
1017 }
1018 }
1019
1020 pub fn padding_values(&self) -> EdgeInsets {
1021 self.resolved_modifiers().padding()
1022 }
1023
1024 pub(crate) fn layout_properties(&self) -> LayoutProperties {
1025 self.resolved_modifiers().layout_properties()
1026 }
1027
1028 pub fn box_alignment(&self) -> Option<Alignment> {
1029 self.layout_properties().box_alignment()
1030 }
1031
1032 pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
1033 self.layout_properties().column_alignment()
1034 }
1035
1036 pub fn row_alignment(&self) -> Option<VerticalAlignment> {
1037 self.layout_properties().row_alignment()
1038 }
1039
1040 pub fn draw_commands(&self) -> Vec<DrawCommand> {
1041 collect_slices_from_modifier(self).draw_commands().to_vec()
1042 }
1043
1044 pub fn clips_to_bounds(&self) -> bool {
1045 collect_slices_from_modifier(self).clip_to_bounds()
1046 }
1047
1048 pub fn collect_inspector_records(&self) -> Vec<ModifierInspectorRecord> {
1050 self.inspector_metadata()
1051 .iter()
1052 .map(InspectorMetadata::to_record)
1053 .collect()
1054 }
1055
1056 pub fn resolved_modifiers(&self) -> ResolvedModifiers {
1057 let mut handle = ModifierChainHandle::new();
1058 let _ = handle.update(self);
1059 handle.resolved_modifiers()
1060 }
1061
1062 pub fn with_element<E>(element: E) -> Self
1065 where
1066 E: ModifierNodeElement,
1067 {
1068 let dyn_element = modifier_element(element);
1069 Self::from_parts(vec![dyn_element])
1070 }
1071
1072 pub(crate) fn from_parts(elements: Vec<DynModifierElement>) -> Self {
1073 if elements.is_empty() {
1074 Self::default()
1075 } else {
1076 let element_count = elements.len();
1077 let provides_composition_locals = elements
1078 .iter()
1079 .any(|element| element.provides_composition_locals());
1080 let fingerprints = single_fingerprints(elements.as_slice());
1081 Self {
1082 kind: ModifierKind::Single {
1083 elements: Rc::new(elements),
1084 inspector: None,
1085 },
1086 strict_fingerprint: fingerprints.strict,
1087 structural_fingerprint: fingerprints.structural,
1088 element_count,
1089 provides_composition_locals,
1090 }
1091 }
1092 }
1093
1094 fn is_trivially_empty(&self) -> bool {
1095 matches!(self.kind, ModifierKind::Empty)
1096 }
1097
1098 fn single_parts(&self) -> Option<(&[DynModifierElement], &[InspectorMetadata])> {
1099 match &self.kind {
1100 ModifierKind::Empty => None,
1101 ModifierKind::Single {
1102 elements,
1103 inspector,
1104 } => Some((elements.as_slice(), inspector_slice(inspector))),
1105 }
1106 }
1107
1108 pub(crate) fn with_inspector_metadata(self, metadata: InspectorMetadata) -> Self {
1109 if metadata.is_empty() {
1110 return self;
1111 }
1112 match self.kind {
1113 ModifierKind::Empty => self,
1114 ModifierKind::Single {
1115 elements,
1116 inspector,
1117 } => {
1118 let mut new_inspector = inspector.as_deref().cloned().unwrap_or_default();
1119 new_inspector.push(metadata);
1120 Self {
1121 kind: ModifierKind::Single {
1122 elements,
1123 inspector: Some(Rc::new(new_inspector)),
1124 },
1125 strict_fingerprint: self.strict_fingerprint,
1126 structural_fingerprint: self.structural_fingerprint,
1127 element_count: self.element_count,
1128 provides_composition_locals: self.provides_composition_locals,
1129 }
1130 }
1131 }
1132 }
1133
1134 pub fn structural_eq(&self, other: &Self) -> bool {
1139 self.eq_internal(other, false)
1140 }
1141
1142 fn eq_internal(&self, other: &Self, consider_always_update: bool) -> bool {
1143 if self.element_count != other.element_count {
1144 return false;
1145 }
1146 if consider_always_update {
1147 if self.strict_fingerprint != other.strict_fingerprint {
1148 return false;
1149 }
1150 } else if self.structural_fingerprint != other.structural_fingerprint {
1151 return false;
1152 }
1153
1154 match (&self.kind, &other.kind) {
1155 (ModifierKind::Empty, ModifierKind::Empty) => true,
1156 (
1157 ModifierKind::Single {
1158 elements: e1,
1159 inspector: _,
1160 },
1161 ModifierKind::Single {
1162 elements: e2,
1163 inspector: _,
1164 },
1165 ) => {
1166 if Rc::ptr_eq(e1, e2) {
1167 return true;
1168 }
1169
1170 if e1.len() != e2.len() {
1171 return false;
1172 }
1173
1174 for (a, b) in e1.iter().zip(e2.iter()) {
1175 if !consider_always_update
1176 && a.element_type() == b.element_type()
1177 && a.capabilities() == NodeCapabilities::DRAW
1178 && b.capabilities() == NodeCapabilities::DRAW
1179 {
1180 continue;
1181 }
1182
1183 if consider_always_update && (a.requires_update() || b.requires_update()) {
1184 if !Rc::ptr_eq(a, b) {
1185 return false;
1186 }
1187 continue;
1188 }
1189
1190 if !a.equals_element(&**b) {
1191 return false;
1192 }
1193 }
1194
1195 true
1196 }
1197 _ => false,
1198 }
1199 }
1200}
1201
1202impl PartialEq for Modifier {
1203 fn eq(&self, other: &Self) -> bool {
1204 self.eq_internal(other, true)
1205 }
1206}
1207
1208impl Eq for Modifier {}
1209
1210impl fmt::Display for Modifier {
1211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1212 match &self.kind {
1213 ModifierKind::Empty => write!(f, "Modifier.empty"),
1214 ModifierKind::Single { elements, .. } => {
1215 if elements.is_empty() {
1216 return write!(f, "Modifier.empty");
1217 }
1218 write!(f, "Modifier[")?;
1219 for (index, element) in elements.iter().enumerate() {
1220 if index > 0 {
1221 write!(f, ", ")?;
1222 }
1223 let name = element.inspector_name();
1224 let mut properties = Vec::new();
1225 element.record_inspector_properties(&mut |prop, value| {
1226 properties.push(format!("{prop}={value}"));
1227 });
1228 if properties.is_empty() {
1229 write!(f, "{name}")?;
1230 } else {
1231 write!(f, "{name}({})", properties.join(", "))?;
1232 }
1233 }
1234 write!(f, "]")
1235 }
1236 }
1237 }
1238}
1239
1240impl fmt::Debug for Modifier {
1241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1242 fmt::Display::fmt(self, f)
1243 }
1244}
1245
1246#[derive(Clone, Copy, Debug, PartialEq)]
1247pub struct ResolvedBackground {
1248 color: Color,
1249 shape: Option<RoundedCornerShape>,
1250}
1251
1252impl ResolvedBackground {
1253 pub fn new(color: Color, shape: Option<RoundedCornerShape>) -> Self {
1254 Self { color, shape }
1255 }
1256
1257 pub fn color(&self) -> Color {
1258 self.color
1259 }
1260
1261 pub fn shape(&self) -> Option<RoundedCornerShape> {
1262 self.shape
1263 }
1264
1265 pub fn set_shape(&mut self, shape: Option<RoundedCornerShape>) {
1266 self.shape = shape;
1267 }
1268}
1269
1270#[derive(Clone, Copy, Debug, PartialEq, Default)]
1271pub struct ResolvedModifiers {
1272 padding: EdgeInsets,
1273 layout: LayoutProperties,
1274 offset: Point,
1275}
1276
1277impl ResolvedModifiers {
1278 pub fn padding(&self) -> EdgeInsets {
1279 self.padding
1280 }
1281
1282 pub fn layout_properties(&self) -> LayoutProperties {
1283 self.layout
1284 }
1285
1286 pub fn offset(&self) -> Point {
1287 self.offset
1288 }
1289
1290 pub(crate) fn set_padding(&mut self, padding: EdgeInsets) {
1291 self.padding = padding;
1292 }
1293
1294 pub(crate) fn set_layout_properties(&mut self, layout: LayoutProperties) {
1295 self.layout = layout;
1296 }
1297
1298 pub(crate) fn set_offset(&mut self, offset: Point) {
1299 self.offset = offset;
1300 }
1301
1302 pub(crate) fn on_device_grid(&self, density: f32) -> Self {
1305 use cranpose_ui_layout::round_to_px;
1306
1307 let round = |value: Option<f32>| value.map(|value| round_to_px(value, density));
1308 let dimension = |dimension: DimensionConstraint| match dimension {
1309 DimensionConstraint::Points(value) => {
1310 DimensionConstraint::Points(round_to_px(value, density))
1311 }
1312 other => other,
1313 };
1314 Self {
1315 padding: crate::modifier_nodes::device_padding(self.padding, density),
1316 layout: LayoutProperties {
1317 padding: crate::modifier_nodes::device_padding(self.layout.padding, density),
1318 width: dimension(self.layout.width),
1319 height: dimension(self.layout.height),
1320 min_width: round(self.layout.min_width),
1321 min_height: round(self.layout.min_height),
1322 max_width: round(self.layout.max_width),
1323 max_height: round(self.layout.max_height),
1324 ..self.layout
1325 },
1326 offset: Point {
1327 x: round_to_px(self.offset.x, density),
1328 y: round_to_px(self.offset.y, density),
1329 },
1330 }
1331 }
1332}
1333
1334#[derive(Clone, Copy, Debug, Default, PartialEq)]
1335pub enum DimensionConstraint {
1336 #[default]
1337 Unspecified,
1338 Points(f32),
1339 Fraction(f32),
1340 Intrinsic(IntrinsicSize),
1341}
1342
1343#[derive(Clone, Copy, Debug, Default, PartialEq)]
1344pub struct LayoutWeight {
1345 pub weight: f32,
1346 pub fill: bool,
1347}
1348
1349#[derive(Clone, Copy, Debug, Default, PartialEq)]
1350pub struct LayoutProperties {
1351 padding: EdgeInsets,
1352 width: DimensionConstraint,
1353 height: DimensionConstraint,
1354 min_width: Option<f32>,
1355 min_height: Option<f32>,
1356 max_width: Option<f32>,
1357 max_height: Option<f32>,
1358 weight: Option<LayoutWeight>,
1359 box_alignment: Option<Alignment>,
1360 column_alignment: Option<HorizontalAlignment>,
1361 row_alignment: Option<VerticalAlignment>,
1362}
1363
1364impl LayoutProperties {
1365 pub fn padding(&self) -> EdgeInsets {
1366 self.padding
1367 }
1368
1369 pub fn width(&self) -> DimensionConstraint {
1370 self.width
1371 }
1372
1373 pub fn height(&self) -> DimensionConstraint {
1374 self.height
1375 }
1376
1377 pub fn min_width(&self) -> Option<f32> {
1378 self.min_width
1379 }
1380
1381 pub fn min_height(&self) -> Option<f32> {
1382 self.min_height
1383 }
1384
1385 pub fn max_width(&self) -> Option<f32> {
1386 self.max_width
1387 }
1388
1389 pub fn max_height(&self) -> Option<f32> {
1390 self.max_height
1391 }
1392
1393 pub fn weight(&self) -> Option<LayoutWeight> {
1394 self.weight
1395 }
1396
1397 pub fn box_alignment(&self) -> Option<Alignment> {
1398 self.box_alignment
1399 }
1400
1401 pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
1402 self.column_alignment
1403 }
1404
1405 pub fn row_alignment(&self) -> Option<VerticalAlignment> {
1406 self.row_alignment
1407 }
1408}
1409
1410#[cfg(test)]
1411#[path = "tests/modifier_tests.rs"]
1412mod tests;