1use std::{fmt, rc::Rc};
9
10use cranpose_core::ProvidedValue;
11use smallvec::SmallVec;
12
13mod alignment;
14mod background;
15mod blur;
16mod border;
17mod chain;
18mod clickable;
19mod coordinator_geometry;
20mod drag_and_drop;
21mod draw_cache;
22mod fill;
23mod focus;
24mod focus_ring;
25mod graphics_layer;
26mod key_input;
27mod layout_data;
28mod local;
29mod long_press;
30mod minimum_interactive;
31mod offset;
32mod padding;
33mod pointer_icon;
34pub(crate) mod pointer_input;
35mod pointer_opaque;
36mod rotary_input;
37mod scroll;
38mod selectable;
39mod semantics;
40mod shadow;
41mod size;
42mod slices;
43mod toggleable;
44mod weight;
45mod window_root;
46mod wrap_content;
47mod z_index;
48
49pub(crate) use chain::InPlaceReach;
50pub use chain::{ModifierChainHandle, ModifierChainInspectorNode, ModifierLocalsHandle};
51pub(crate) use coordinator_geometry::{CoordinatorGeometry, CoordinatorRect};
52pub use cranpose_foundation::{
53 AnyModifierElement, DynModifierElement, FocusState, PointerEvent, PointerEventKind,
54 PointerSource, RotaryScrollEvent, SemanticsConfiguration, modifier_element,
55};
56use cranpose_foundation::{ModifierNodeElement, NodeCapabilities, ProgressBarRangeInfo};
57#[expect(unused_imports)]
58pub use cranpose_ui_graphics::{
59 BlendMode, BlurredEdgeTreatment, Brush, Color, ColorFilter, CompositingStrategy, CornerRadii,
60 CursorIcon, CustomPointerIcon, CutDirection, Dp, DpOffset, EdgeInsets, GradientCutMaskSpec,
61 GradientFadeMaskSpec, GraphicsLayer, LayerShape, Point, PointerIcon, PointerIconError, Rect,
62 RenderEffect, RoundedCornerShape, RuntimeShader, Shadow, ShadowScope, Size, TransformOrigin,
63};
64use cranpose_ui_layout::{Alignment, HorizontalAlignment, IntrinsicSize, VerticalAlignment};
65pub use drag_and_drop::{
66 DragAndDropEvent, DragAndDropOutcome, DragAndDropPayload, DragAndDropPoint, DragAndDropSource,
67 DragAndDropSourceElement, DragAndDropSourceNode, DragAndDropState, DragAndDropTarget,
68 DragAndDropTargetElement, DragAndDropTargetNode,
69};
70use focus::FocusTargetElement;
71pub use focus::{FocusDirection, FocusRequestError, FocusRequester, FocusRequesterElement};
72pub use graphics_layer::GlassMaterial;
73pub use key_input::KeyEventRoute;
74pub(crate) use local::{
75 ModifierLocalAncestorResolver, ModifierLocalSource, ModifierLocalToken, ResolvedModifierLocal,
76};
77use local::{ModifierLocalConsumerElement, ModifierLocalProviderElement};
78pub use local::{ModifierLocalKey, ModifierLocalReadScope};
79#[expect(unused_imports)]
80pub use pointer_input::{AwaitPointerEventScope, PointerInputScope};
81pub use rotary_input::RotaryInputModifierNode;
82#[cfg(test)]
83pub(crate) use scroll::lazy_scroll_semantics;
84#[cfg(feature = "test-helpers")]
85pub use scroll::{last_fling_velocity, reset_last_fling_velocity};
86use semantics::SemanticsElement;
87pub use semantics::{
88 SemanticsRequester, SemanticsRequesterElement, collect_semantics_from_chain,
89 collect_semantics_from_modifier, semantics_reach_of_chain,
90};
91pub(crate) use slices::SlicesSnapshot;
92pub use slices::{
93 ModifierNodeSlices, ModifierNodeSlicesDebugStats, SliceTextRef, collect_modifier_slices,
94 collect_slices_from_modifier,
95};
96pub use window_root::{
97 WindowRootDescriptor, WindowRootElement, WindowRootEntry, WindowRootNode, WindowRootRegistry,
98 WindowRootRoutingScratch, is_window_root, nearest_window_root, nearest_window_roots,
99 nearest_window_roots_into, window_roots, window_roots_revision,
100};
101pub use z_index::z_index_in_use;
102
103pub use crate::draw::{DrawCacheBuilder, DrawCommand};
104use crate::modifier_nodes::ClipToBoundsElement;
105
106#[derive(Clone, Debug, Default)]
107pub struct InspectorInfo {
108 properties: Vec<InspectorProperty>,
109}
110
111impl InspectorInfo {
112 pub fn new() -> Self {
113 Self::default()
114 }
115
116 pub fn add_property<V: Into<String>>(&mut self, name: &'static str, value: V) {
117 self.properties.push(InspectorProperty {
118 name,
119 value: value.into(),
120 });
121 }
122
123 pub fn properties(&self) -> &[InspectorProperty] {
124 &self.properties
125 }
126
127 pub fn is_empty(&self) -> bool {
128 self.properties.is_empty()
129 }
130
131 pub fn add_dimension(&mut self, name: &'static str, constraint: DimensionConstraint) {
132 self.add_property(name, describe_dimension(constraint));
133 }
134
135 pub fn add_offset_components(
136 &mut self,
137 x_name: &'static str,
138 y_name: &'static str,
139 offset: Point,
140 ) {
141 self.add_property(x_name, offset.x.to_string());
142 self.add_property(y_name, offset.y.to_string());
143 }
144
145 pub fn add_alignment<A>(&mut self, name: &'static str, alignment: A)
146 where
147 A: fmt::Debug,
148 {
149 self.add_property(name, format!("{alignment:?}"));
150 }
151}
152
153#[derive(Clone, Debug, PartialEq)]
154pub struct InspectorProperty {
155 pub name: &'static str,
156 pub value: String,
157}
158
159#[derive(Clone, Debug, PartialEq)]
160pub struct ModifierInspectorRecord {
161 pub name: &'static str,
162 pub properties: Vec<InspectorProperty>,
163}
164
165#[derive(Clone, Debug)]
166pub(crate) struct InspectorMetadata {
167 name: &'static str,
168 info: InspectorInfo,
169}
170
171impl InspectorMetadata {
172 pub(crate) fn new<F>(name: &'static str, recorder: F) -> Self
173 where
174 F: FnOnce(&mut InspectorInfo),
175 {
176 let mut info = InspectorInfo::new();
177 recorder(&mut info);
178 Self { name, info }
179 }
180
181 fn is_empty(&self) -> bool {
182 self.info.is_empty()
183 }
184
185 fn to_record(&self) -> ModifierInspectorRecord {
186 ModifierInspectorRecord {
187 name: self.name,
188 properties: self.info.properties().to_vec(),
189 }
190 }
191}
192
193fn describe_dimension(constraint: DimensionConstraint) -> String {
194 match constraint {
195 DimensionConstraint::Unspecified => "unspecified".to_string(),
196 DimensionConstraint::Points(value) => value.to_string(),
197 DimensionConstraint::Fraction(value) => format!("fraction({value})"),
198 DimensionConstraint::Intrinsic(size) => format!("intrinsic({size:?})"),
199 }
200}
201
202fn inspector_slice(inspector: &Option<Rc<[InspectorMetadata]>>) -> &[InspectorMetadata] {
203 inspector.as_deref().unwrap_or(&[])
204}
205
206fn merged_inspector(
209 first: &[InspectorMetadata],
210 second: &[InspectorMetadata],
211) -> Option<Rc<[InspectorMetadata]>> {
212 if first.is_empty() && second.is_empty() {
213 return None;
214 }
215 Some(first.iter().chain(second).cloned().collect())
216}
217
218pub(crate) fn inspector_metadata<F>(name: &'static str, recorder: F) -> InspectorMetadata
219where
220 F: FnOnce(&mut InspectorInfo),
221{
222 if !inspector_metadata_enabled() {
223 return InspectorMetadata::new(name, |_| {});
224 }
225 InspectorMetadata::new(name, recorder)
226}
227
228pub(crate) fn modifier_debug_enabled() -> bool {
229 #[cfg(not(target_arch = "wasm32"))]
230 {
231 cranpose_core::env_flag!("COMPOSE_DEBUG_MODIFIERS")
232 }
233 #[cfg(target_arch = "wasm32")]
234 {
235 false
236 }
237}
238
239fn inspector_metadata_enabled() -> bool {
240 cfg!(any(test, feature = "inspection")) || modifier_debug_enabled()
241}
242
243type ModifierElements = SmallVec<[DynModifierElement; 4]>;
246
247#[derive(Clone)]
252enum Elements {
253 One(DynModifierElement),
254 Many(Rc<ModifierElements>),
255}
256
257impl Elements {
258 fn from_slice(elements: &[DynModifierElement]) -> Self {
259 match elements {
260 [element] => Self::One(element.clone()),
261 _ => Self::Many(Rc::new(elements.iter().cloned().collect())),
262 }
263 }
264
265 fn extend_from(&mut self, more: &[DynModifierElement]) {
266 match self {
267 Self::Many(shared) => match Rc::get_mut(shared) {
268 Some(owned) => owned.extend(more.iter().cloned()),
269 None => *shared = Rc::new(shared.iter().chain(more).cloned().collect()),
270 },
271 Self::One(first) => {
272 *self = Self::Many(Rc::new(
273 std::iter::once(&*first).chain(more).cloned().collect(),
274 ));
275 }
276 }
277 }
278
279 fn shares_storage(&self, other: &Self) -> bool {
281 match (self, other) {
282 (Self::One(a), Self::One(b)) => Rc::ptr_eq(a, b),
283 (Self::Many(a), Self::Many(b)) => Rc::ptr_eq(a, b),
284 _ => false,
285 }
286 }
287}
288
289impl std::ops::Deref for Elements {
290 type Target = [DynModifierElement];
291
292 fn deref(&self) -> &[DynModifierElement] {
293 match self {
294 Self::One(element) => std::slice::from_ref(element),
295 Self::Many(elements) => elements.as_slice(),
296 }
297 }
298}
299
300#[derive(Clone)]
301enum ModifierKind {
302 Empty,
303 Single {
304 elements: Elements,
305 inspector: Option<Rc<[InspectorMetadata]>>,
308 },
309}
310
311pub struct ModifierElementIterator<'a> {
312 inner: std::slice::Iter<'a, DynModifierElement>,
313}
314
315impl<'a> Iterator for ModifierElementIterator<'a> {
316 type Item = &'a DynModifierElement;
317
318 #[inline]
319 fn next(&mut self) -> Option<Self::Item> {
320 self.inner.next()
321 }
322
323 #[inline]
324 fn size_hint(&self) -> (usize, Option<usize>) {
325 self.inner.size_hint()
326 }
327}
328
329impl ExactSizeIterator for ModifierElementIterator<'_> {}
330
331pub(crate) struct ModifierInspectorIterator<'a> {
332 inner: std::slice::Iter<'a, InspectorMetadata>,
333}
334
335impl<'a> Iterator for ModifierInspectorIterator<'a> {
336 type Item = &'a InspectorMetadata;
337
338 #[inline]
339 fn next(&mut self) -> Option<Self::Item> {
340 self.inner.next()
341 }
342
343 #[inline]
344 fn size_hint(&self) -> (usize, Option<usize>) {
345 self.inner.size_hint()
346 }
347}
348
349impl ExactSizeIterator for ModifierInspectorIterator<'_> {}
350
351#[derive(Clone)]
369pub struct Modifier {
370 kind: ModifierKind,
371 element_count: usize,
372 provides_composition_locals: bool,
373}
374
375impl Default for Modifier {
376 fn default() -> Self {
377 Self {
378 kind: ModifierKind::Empty,
379 element_count: 0,
380 provides_composition_locals: false,
381 }
382 }
383}
384
385impl Modifier {
386 pub fn empty() -> Self {
388 Self::default()
389 }
390
391 pub fn from_element<E>(element: E) -> Self
393 where
394 E: ModifierNodeElement,
395 {
396 Self::with_element(element)
397 }
398
399 pub fn clip_to_bounds(self) -> Self {
403 let modifier = crate::thread_state::with(|state| {
404 state
405 .clip_to_bounds
406 .get_or_init(|| Modifier::with_element(ClipToBoundsElement::new()))
407 .clone()
408 });
409 let modifier =
410 modifier.with_inspector_metadata(inspector_metadata("clipToBounds", |info| {
411 info.add_property("clipToBounds", "true");
412 }));
413 self.then(modifier)
414 }
415
416 pub fn modifier_local_provider<T, F>(self, key: ModifierLocalKey<T>, value: F) -> Self
419 where
420 T: 'static,
421 F: Fn() -> T + 'static,
422 {
423 let element = ModifierLocalProviderElement::new(key, value);
424 let modifier = Modifier::from_parts(&[modifier_element(element)]);
425 self.then(modifier)
426 }
427
428 pub fn modifier_local_consumer<F>(self, consumer: F) -> Self
431 where
432 F: for<'scope> Fn(&mut ModifierLocalReadScope<'scope>) + 'static,
433 {
434 let element = ModifierLocalConsumerElement::new(consumer);
435 let modifier = Modifier::from_parts(&[modifier_element(element)]);
436 self.then(modifier)
437 }
438
439 pub fn semantics_spec(self, spec: cranpose_foundation::SemanticsSpec) -> Self {
450 self.stable_semantics(move |config: &mut SemanticsConfiguration| config.merge(&spec))
451 }
452
453 pub fn semantics<F>(self, recorder: F) -> Self
458 where
459 F: Fn(&mut SemanticsConfiguration) + 'static,
460 {
461 self.semantics_recorder(std::rc::Rc::new(recorder), true)
462 }
463
464 pub fn stable_semantics<F>(self, recorder: F) -> Self
471 where
472 F: Fn(&mut SemanticsConfiguration) + 'static,
473 {
474 self.semantics_recorder(std::rc::Rc::new(recorder), false)
475 }
476
477 fn semantics_recorder(
478 self,
479 recorder: std::rc::Rc<dyn Fn(&mut SemanticsConfiguration)>,
480 reads_live_state: bool,
481 ) -> Self {
482 let metadata = if inspector_metadata_enabled() {
483 let mut preview = SemanticsConfiguration::default();
484 recorder(&mut preview);
485 let description = preview.content_description.clone();
486 let state_description = preview.state_description.clone();
487 let role = preview.role;
488 let is_clickable = preview.is_activatable();
489 let canvas_children = preview.canvas_children.len();
490 inspector_metadata("semantics", move |info| {
491 if let Some(desc) = &description {
492 info.add_property("contentDescription", desc.clone());
493 }
494 if let Some(state) = &state_description {
495 info.add_property("stateDescription", state.clone());
496 }
497 if let Some(role) = role {
498 info.add_property("role", format!("{role:?}"));
499 }
500 if is_clickable {
501 info.add_property("isClickable", "true");
502 }
503 if canvas_children > 0 {
504 info.add_property("canvasSemanticsChildren", canvas_children.to_string());
505 }
506 })
507 } else {
508 inspector_metadata("semantics", |_| {})
509 };
510 let element = SemanticsElement::new(recorder, reads_live_state);
511 let modifier =
512 Modifier::from_parts(&[modifier_element(element)]).with_inspector_metadata(metadata);
513 self.then(modifier)
514 }
515
516 pub fn progress_semantics(self, current: f32, start: f32, end: f32, steps: u32) -> Self {
525 let info = ProgressBarRangeInfo::new(current, start, end, steps);
526 self.stable_semantics(move |config| config.progress = Some(info))
527 }
528
529 pub fn dropdown_list(self) -> Self {
533 self.role(cranpose_foundation::SemanticsWidgetRole::DropdownList)
534 }
535
536 pub fn value_picker(self) -> Self {
539 self.role(cranpose_foundation::SemanticsWidgetRole::ValuePicker)
540 }
541
542 pub fn expand(self, action: impl Fn() -> bool + 'static) -> Self {
547 let action = cranpose_foundation::SemanticsExpand::new(action);
548 self.stable_semantics(move |config| config.expand = Some(action.clone()))
549 }
550
551 pub fn on_long_click(
561 self,
562 label: impl Into<String>,
563 action: impl Fn() -> bool + 'static,
564 ) -> Self {
565 let label = label.into();
566 let action = cranpose_foundation::SemanticsLongClick::new(action);
567 self.stable_semantics(move |config| {
568 config.on_long_click_label = Some(label.clone());
569 config.on_long_click = Some(action.clone());
570 })
571 }
572
573 pub fn on_magic_tap(
578 self,
579 label: impl Into<String>,
580 action: impl Fn() -> bool + 'static,
581 ) -> Self {
582 let label = label.into();
583 let action = cranpose_foundation::SemanticsMagicTap::new(action);
584 self.stable_semantics(move |config| {
585 config.on_magic_tap_label = Some(label.clone());
586 config.on_magic_tap = Some(action.clone());
587 })
588 }
589
590 pub fn input_labels<S: Into<String>>(self, labels: impl IntoIterator<Item = S>) -> Self {
594 let labels: Vec<String> = labels.into_iter().map(Into::into).collect();
595 self.stable_semantics(move |config| config.input_labels.clone_from(&labels))
596 }
597
598 pub fn language(self, tag: impl Into<String>) -> Self {
602 let tag = tag.into();
603 self.stable_semantics(move |config| config.language = Some(tag.clone()))
604 }
605
606 pub fn collapse(self, action: impl Fn() -> bool + 'static) -> Self {
610 let action = cranpose_foundation::SemanticsExpand::new(action);
611 self.stable_semantics(move |config| config.collapse = Some(action.clone()))
612 }
613
614 pub fn dismiss(self, action: impl Fn() -> bool + 'static) -> Self {
619 let action = cranpose_foundation::SemanticsDismiss::new(action);
620 self.stable_semantics(move |config| config.dismiss = Some(action.clone()))
621 }
622
623 pub fn scroll_to_index(self, action: impl Fn(usize) -> bool + 'static) -> Self {
629 let action = cranpose_foundation::SemanticsScrollToIndex::new(action);
630 self.stable_semantics(move |config| config.scroll_to_index = Some(action.clone()))
631 }
632
633 pub fn traversal_index(self, index: f32) -> Self {
639 self.stable_semantics(move |config| config.traversal_index = index)
640 }
641
642 pub fn password(self) -> Self {
647 self.stable_semantics(|config| config.password = true)
648 }
649
650 pub fn error(self, message: impl Into<String>) -> Self {
654 let message = message.into();
655 self.stable_semantics(move |config| config.error = Some(message.clone()))
656 }
657
658 pub fn hint_text(self, hint: impl Into<String>) -> Self {
662 let hint = hint.into();
663 self.stable_semantics(move |config| config.hint_text = Some(hint.clone()))
664 }
665
666 pub fn pane_title(self, title: impl Into<String>) -> Self {
670 let title = title.into();
671 self.stable_semantics(move |config| config.pane_title = Some(title.clone()))
672 }
673
674 pub fn selectable_group(self) -> Self {
679 self.stable_semantics(|config| config.selectable_group = true)
680 }
681
682 pub fn merge_descendants(self) -> Self {
687 self.stable_semantics(|config| config.merge_descendants = true)
688 }
689
690 pub fn hide_from_accessibility(self) -> Self {
695 self.stable_semantics(|config| config.hidden = true)
696 }
697
698 pub fn heading(self) -> Self {
703 self.role(cranpose_foundation::SemanticsWidgetRole::Header)
704 }
705
706 pub fn role(self, role: cranpose_foundation::SemanticsWidgetRole) -> Self {
711 self.stable_semantics(move |config| config.role = Some(role))
712 }
713
714 pub fn live_region(self, mode: cranpose_foundation::LiveRegionMode) -> Self {
720 self.stable_semantics(move |config| config.live_region = Some(mode))
721 }
722
723 pub fn content_description(self, description: impl Into<String>) -> Self {
728 let description = description.into();
729 self.stable_semantics(move |config| config.content_description = Some(description.clone()))
730 }
731
732 pub fn test_tag(self, tag: impl Into<String>) -> Self {
737 let tag = tag.into();
738 self.stable_semantics(move |config| config.test_tag = Some(tag.clone()))
739 }
740
741 pub fn focus_target(self) -> Self {
747 let element = FocusTargetElement::new();
748 let modifier = Modifier::from_parts(&[modifier_element(element)]);
749 self.then(modifier)
750 }
751
752 pub fn on_focus_changed<F>(self, callback: F) -> Self
757 where
758 F: Fn(FocusState) + 'static,
759 {
760 let element = FocusTargetElement::with_callback(callback);
761 let modifier = Modifier::from_parts(&[modifier_element(element)]);
762 self.then(modifier)
763 }
764
765 pub fn focus_requester(self, requester: &FocusRequester) -> Self {
772 let element = FocusRequesterElement::new(requester.clone());
773 let modifier = Modifier::from_parts(&[modifier_element(element)]);
774 self.then(modifier)
775 }
776
777 pub fn semantics_requester(self, requester: &SemanticsRequester) -> Self {
785 let element = SemanticsRequesterElement::new(requester.clone());
786 let modifier = Modifier::from_parts(&[modifier_element(element)]);
787 self.then(modifier)
788 }
789
790 pub fn debug_chain(self, tag: &'static str) -> Self {
808 use cranpose_foundation::{ModifierNode, ModifierNodeContext, NodeCapabilities, NodeState};
809
810 #[derive(Clone)]
811 struct DebugChainElement {
812 tag: &'static str,
813 }
814
815 impl fmt::Debug for DebugChainElement {
816 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
817 f.debug_struct("DebugChainElement")
818 .field("tag", &self.tag)
819 .finish()
820 }
821 }
822
823 impl PartialEq for DebugChainElement {
824 fn eq(&self, other: &Self) -> bool {
825 self.tag == other.tag
826 }
827 }
828
829 impl Eq for DebugChainElement {}
830
831 impl std::hash::Hash for DebugChainElement {
832 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
833 self.tag.hash(state);
834 }
835 }
836
837 impl ModifierNodeElement for DebugChainElement {
838 type Node = DebugChainNode;
839
840 fn create(&self) -> Self::Node {
841 DebugChainNode::new(self.tag)
842 }
843
844 fn update(&self, node: &mut Self::Node) {
845 node.tag = self.tag;
846 }
847
848 fn capabilities(&self) -> NodeCapabilities {
849 NodeCapabilities::empty()
850 }
851 }
852
853 struct DebugChainNode {
854 tag: &'static str,
855 state: NodeState,
856 }
857
858 impl DebugChainNode {
859 fn new(tag: &'static str) -> Self {
860 Self {
861 tag,
862 state: NodeState::new(),
863 }
864 }
865 }
866
867 impl ModifierNode for DebugChainNode {
868 fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {
869 eprintln!("[debug_chain:{}] Modifier chain attached", self.tag);
870 }
871
872 fn on_detach(&mut self) {
873 eprintln!("[debug_chain:{}] Modifier chain detached", self.tag);
874 }
875
876 fn on_reset(&mut self) {
877 eprintln!("[debug_chain:{}] Modifier chain reset", self.tag);
878 }
879 }
880
881 impl cranpose_foundation::DelegatableNode for DebugChainNode {
882 fn node_state(&self) -> &NodeState {
883 &self.state
884 }
885 }
886
887 let element = DebugChainElement { tag };
888 let modifier = Modifier::from_parts(&[modifier_element(element)]);
889 self.then(modifier)
890 .with_inspector_metadata(inspector_metadata("debugChain", move |info| {
891 info.add_property("tag", tag);
892 }))
893 }
894
895 pub fn then(self, next: Modifier) -> Modifier {
901 let Modifier {
902 kind,
903 element_count,
904 provides_composition_locals,
905 } = self;
906 let ModifierKind::Single {
907 mut elements,
908 inspector,
909 } = kind
910 else {
911 return next;
912 };
913 let ModifierKind::Single {
914 elements: next_elements,
915 inspector: next_inspector,
916 } = &next.kind
917 else {
918 return Modifier {
919 kind: ModifierKind::Single {
920 elements,
921 inspector,
922 },
923 element_count,
924 provides_composition_locals,
925 };
926 };
927
928 elements.extend_from(next_elements);
929 let inspector = if next_inspector.is_none() {
930 inspector
931 } else {
932 merged_inspector(inspector_slice(&inspector), inspector_slice(next_inspector))
933 };
934 Modifier {
935 kind: ModifierKind::Single {
936 elements,
937 inspector,
938 },
939 element_count: element_count + next.element_count,
940 provides_composition_locals: provides_composition_locals
941 || next.provides_composition_locals,
942 }
943 }
944
945 pub(crate) fn element_count(&self) -> usize {
947 self.element_count
948 }
949
950 pub(crate) fn iter_elements(&self) -> ModifierElementIterator<'_> {
951 match &self.kind {
952 ModifierKind::Empty => ModifierElementIterator { inner: [].iter() },
953 ModifierKind::Single { elements, .. } => ModifierElementIterator {
954 inner: elements.iter(),
955 },
956 }
957 }
958
959 pub(crate) fn provided_composition_locals(&self) -> Vec<ProvidedValue> {
960 if !self.provides_composition_locals {
961 return Vec::new();
962 }
963 self.iter_elements()
964 .flat_map(|element| element.provided_composition_locals())
965 .collect()
966 }
967
968 pub(crate) fn iter_inspector_metadata(&self) -> ModifierInspectorIterator<'_> {
969 match &self.kind {
970 ModifierKind::Empty => ModifierInspectorIterator { inner: [].iter() },
971 ModifierKind::Single { inspector, .. } => ModifierInspectorIterator {
972 inner: inspector_slice(inspector).iter(),
973 },
974 }
975 }
976
977 #[cfg(test)]
978 pub(crate) fn elements(&self) -> Vec<DynModifierElement> {
979 match &self.kind {
980 ModifierKind::Empty => Vec::new(),
981 ModifierKind::Single { elements, .. } => elements.to_vec(),
982 }
983 }
984
985 pub(crate) fn inspector_metadata(&self) -> Vec<InspectorMetadata> {
986 match &self.kind {
987 ModifierKind::Empty => Vec::new(),
988 ModifierKind::Single { inspector, .. } => {
989 inspector.as_deref().map(<[_]>::to_vec).unwrap_or_default()
990 }
991 }
992 }
993
994 pub fn total_padding(&self) -> f32 {
996 let padding = self.padding_values();
997 padding
998 .left
999 .max(padding.right)
1000 .max(padding.top)
1001 .max(padding.bottom)
1002 }
1003
1004 pub fn explicit_size(&self) -> Option<Size> {
1007 let props = self.layout_properties();
1008 match (props.width, props.height) {
1009 (DimensionConstraint::Points(width), DimensionConstraint::Points(height)) => {
1010 Some(Size { width, height })
1011 }
1012 _ => None,
1013 }
1014 }
1015
1016 pub fn padding_values(&self) -> EdgeInsets {
1018 self.resolved_modifiers().padding()
1019 }
1020
1021 pub(crate) fn layout_properties(&self) -> LayoutProperties {
1022 self.resolved_modifiers().layout_properties()
1023 }
1024
1025 pub fn box_alignment(&self) -> Option<Alignment> {
1027 self.layout_properties().box_alignment()
1028 }
1029
1030 pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
1032 self.layout_properties().column_alignment()
1033 }
1034
1035 pub fn row_alignment(&self) -> Option<VerticalAlignment> {
1037 self.layout_properties().row_alignment()
1038 }
1039
1040 pub fn draw_commands(&self) -> Vec<DrawCommand> {
1042 collect_slices_from_modifier(self).draw_commands().to_vec()
1043 }
1044
1045 pub fn clips_to_bounds(&self) -> bool {
1047 collect_slices_from_modifier(self).clip_to_bounds()
1048 }
1049
1050 pub fn collect_inspector_records(&self) -> Vec<ModifierInspectorRecord> {
1052 self.inspector_metadata()
1053 .iter()
1054 .map(InspectorMetadata::to_record)
1055 .collect()
1056 }
1057
1058 pub fn resolved_modifiers(&self) -> ResolvedModifiers {
1060 let mut handle = ModifierChainHandle::new();
1061 let _ = handle.update(self);
1062 handle.resolved_modifiers()
1063 }
1064
1065 pub fn with_element<E>(element: E) -> Self
1068 where
1069 E: ModifierNodeElement,
1070 {
1071 Self::from_parts(&[modifier_element(element)])
1072 }
1073
1074 pub(crate) fn from_parts(elements: &[DynModifierElement]) -> Self {
1076 if elements.is_empty() {
1077 Self::default()
1078 } else {
1079 let element_count = elements.len();
1080 let provides_composition_locals = elements
1081 .iter()
1082 .any(|element| element.provides_composition_locals());
1083 Self {
1084 kind: ModifierKind::Single {
1085 elements: Elements::from_slice(elements),
1086 inspector: None,
1087 },
1088 element_count,
1089 provides_composition_locals,
1090 }
1091 }
1092 }
1093
1094 pub(crate) fn with_inspector_metadata(self, metadata: InspectorMetadata) -> Self {
1095 if metadata.is_empty() {
1096 return self;
1097 }
1098 match self.kind {
1099 ModifierKind::Empty => self,
1100 ModifierKind::Single {
1101 elements,
1102 inspector,
1103 } => {
1104 let new_inspector = inspector_slice(&inspector)
1105 .iter()
1106 .cloned()
1107 .chain(std::iter::once(metadata))
1108 .collect();
1109 Self {
1110 kind: ModifierKind::Single {
1111 elements,
1112 inspector: Some(new_inspector),
1113 },
1114 element_count: self.element_count,
1115 provides_composition_locals: self.provides_composition_locals,
1116 }
1117 }
1118 }
1119 }
1120
1121 pub fn structural_eq(&self, other: &Self) -> bool {
1126 self.eq_internal(other, false)
1127 }
1128
1129 pub(crate) fn in_place_reach(&self, other: &Self) -> Option<InPlaceReach> {
1134 if self.element_count != other.element_count {
1135 return None;
1136 }
1137 let mut reach = InPlaceReach::Node;
1138 for (a, b) in self.iter_elements().zip(other.iter_elements()) {
1139 if a.requires_update() || b.requires_update() {
1140 return None;
1141 }
1142 let element_type = a.element_type();
1143 match InPlaceReach::of(element_type) {
1144 Some(InPlaceReach::Node) if b.element_type() == element_type => {}
1146 Some(InPlaceReach::Resolved) if b.element_type() == element_type => {
1147 if !a.equals_element(&**b) {
1148 reach = InPlaceReach::Resolved;
1149 }
1150 }
1151 _ => {
1152 if !a.equals_element(&**b) {
1153 return None;
1154 }
1155 }
1156 }
1157 }
1158 Some(reach)
1159 }
1160
1161 fn eq_internal(&self, other: &Self, consider_always_update: bool) -> bool {
1162 if self.element_count != other.element_count {
1163 return false;
1164 }
1165
1166 match (&self.kind, &other.kind) {
1167 (ModifierKind::Empty, ModifierKind::Empty) => true,
1168 (
1169 ModifierKind::Single {
1170 elements: e1,
1171 inspector: _,
1172 },
1173 ModifierKind::Single {
1174 elements: e2,
1175 inspector: _,
1176 },
1177 ) => {
1178 if e1.shares_storage(e2) {
1179 return true;
1180 }
1181
1182 if e1.len() != e2.len() {
1183 return false;
1184 }
1185
1186 for (a, b) in e1.iter().zip(e2.iter()) {
1187 if !consider_always_update
1188 && a.element_type() == b.element_type()
1189 && a.capabilities() == NodeCapabilities::DRAW
1190 && b.capabilities() == NodeCapabilities::DRAW
1191 {
1192 continue;
1193 }
1194
1195 if consider_always_update && (a.requires_update() || b.requires_update()) {
1196 if !Rc::ptr_eq(a, b) {
1197 return false;
1198 }
1199 continue;
1200 }
1201
1202 if !a.equals_element(&**b) {
1203 return false;
1204 }
1205 }
1206
1207 true
1208 }
1209 _ => false,
1210 }
1211 }
1212}
1213
1214impl PartialEq for Modifier {
1215 fn eq(&self, other: &Self) -> bool {
1216 self.eq_internal(other, true)
1217 }
1218}
1219
1220impl Eq for Modifier {}
1221
1222impl fmt::Display for Modifier {
1223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1224 match &self.kind {
1225 ModifierKind::Empty => write!(f, "Modifier.empty"),
1226 ModifierKind::Single { elements, .. } => {
1227 if elements.is_empty() {
1228 return write!(f, "Modifier.empty");
1229 }
1230 write!(f, "Modifier[")?;
1231 for (index, element) in elements.iter().enumerate() {
1232 if index > 0 {
1233 write!(f, ", ")?;
1234 }
1235 let name = element.inspector_name();
1236 let mut properties = Vec::new();
1237 element.record_inspector_properties(&mut |prop, value| {
1238 properties.push(format!("{prop}={value}"));
1239 });
1240 if properties.is_empty() {
1241 write!(f, "{name}")?;
1242 } else {
1243 write!(f, "{name}({})", properties.join(", "))?;
1244 }
1245 }
1246 write!(f, "]")
1247 }
1248 }
1249 }
1250}
1251
1252impl fmt::Debug for Modifier {
1253 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1254 fmt::Display::fmt(self, f)
1255 }
1256}
1257
1258#[derive(Clone, Copy, Debug, PartialEq)]
1260pub struct ResolvedBackground {
1261 color: Color,
1262 shape: Option<RoundedCornerShape>,
1263}
1264
1265impl ResolvedBackground {
1266 pub fn new(color: Color, shape: Option<RoundedCornerShape>) -> Self {
1269 Self { color, shape }
1270 }
1271
1272 pub fn color(&self) -> Color {
1274 self.color
1275 }
1276
1277 pub fn shape(&self) -> Option<RoundedCornerShape> {
1279 self.shape
1280 }
1281
1282 pub fn set_shape(&mut self, shape: Option<RoundedCornerShape>) {
1284 self.shape = shape;
1285 }
1286}
1287
1288#[derive(Clone, Copy, Debug, PartialEq, Default)]
1290pub struct ResolvedModifiers {
1291 padding: EdgeInsets,
1292 layout: LayoutProperties,
1293 offset: Point,
1294}
1295
1296impl ResolvedModifiers {
1297 pub fn padding(&self) -> EdgeInsets {
1299 self.padding
1300 }
1301
1302 pub fn layout_properties(&self) -> LayoutProperties {
1304 self.layout
1305 }
1306
1307 pub fn offset(&self) -> Point {
1309 self.offset
1310 }
1311
1312 pub(crate) fn set_padding(&mut self, padding: EdgeInsets) {
1313 self.padding = padding;
1314 }
1315
1316 pub(crate) fn set_layout_properties(&mut self, layout: LayoutProperties) {
1317 self.layout = layout;
1318 }
1319
1320 pub(crate) fn set_offset(&mut self, offset: Point) {
1321 self.offset = offset;
1322 }
1323
1324 pub(crate) fn on_device_grid(&self, density: f32) -> Self {
1327 use cranpose_ui_layout::round_to_px;
1328
1329 let round = |value: Option<f32>| value.map(|value| round_to_px(value, density));
1330 let dimension = |dimension: DimensionConstraint| match dimension {
1331 DimensionConstraint::Points(value) => {
1332 DimensionConstraint::Points(round_to_px(value, density))
1333 }
1334 other => other,
1335 };
1336 Self {
1337 padding: crate::modifier_nodes::device_padding(self.padding, density),
1338 layout: LayoutProperties {
1339 padding: crate::modifier_nodes::device_padding(self.layout.padding, density),
1340 width: dimension(self.layout.width),
1341 height: dimension(self.layout.height),
1342 min_width: round(self.layout.min_width),
1343 min_height: round(self.layout.min_height),
1344 max_width: round(self.layout.max_width),
1345 max_height: round(self.layout.max_height),
1346 ..self.layout
1347 },
1348 offset: Point {
1349 x: round_to_px(self.offset.x, density),
1350 y: round_to_px(self.offset.y, density),
1351 },
1352 }
1353 }
1354}
1355
1356#[derive(Clone, Copy, Debug, Default, PartialEq)]
1357pub enum DimensionConstraint {
1358 #[default]
1359 Unspecified,
1360 Points(f32),
1361 Fraction(f32),
1362 Intrinsic(IntrinsicSize),
1363}
1364
1365#[derive(Clone, Copy, Debug, Default, PartialEq)]
1366pub struct LayoutWeight {
1367 pub weight: f32,
1368 pub fill: bool,
1369}
1370
1371#[derive(Clone, Copy, Debug, Default, PartialEq)]
1372pub struct LayoutProperties {
1373 padding: EdgeInsets,
1374 width: DimensionConstraint,
1375 height: DimensionConstraint,
1376 min_width: Option<f32>,
1377 min_height: Option<f32>,
1378 max_width: Option<f32>,
1379 max_height: Option<f32>,
1380 weight: Option<LayoutWeight>,
1381 box_alignment: Option<Alignment>,
1382 column_alignment: Option<HorizontalAlignment>,
1383 row_alignment: Option<VerticalAlignment>,
1384 row_baseline: bool,
1385}
1386
1387impl LayoutProperties {
1388 pub fn padding(&self) -> EdgeInsets {
1389 self.padding
1390 }
1391
1392 pub fn width(&self) -> DimensionConstraint {
1393 self.width
1394 }
1395
1396 pub fn height(&self) -> DimensionConstraint {
1397 self.height
1398 }
1399
1400 pub fn min_width(&self) -> Option<f32> {
1401 self.min_width
1402 }
1403
1404 pub fn min_height(&self) -> Option<f32> {
1405 self.min_height
1406 }
1407
1408 pub fn max_width(&self) -> Option<f32> {
1409 self.max_width
1410 }
1411
1412 pub fn max_height(&self) -> Option<f32> {
1413 self.max_height
1414 }
1415
1416 pub fn weight(&self) -> Option<LayoutWeight> {
1417 self.weight
1418 }
1419
1420 pub fn box_alignment(&self) -> Option<Alignment> {
1421 self.box_alignment
1422 }
1423
1424 pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
1425 self.column_alignment
1426 }
1427
1428 pub fn row_alignment(&self) -> Option<VerticalAlignment> {
1429 self.row_alignment
1430 }
1431
1432 pub fn row_baseline(&self) -> bool {
1434 self.row_baseline
1435 }
1436}
1437
1438#[cfg(test)]
1439#[path = "tests/modifier_tests.rs"]
1440mod tests;