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