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, InPlaceUpdate};
50pub use chain::{ModifierChainHandle, ModifierChainInspectorNode, ModifierLocalsHandle};
51pub(crate) use coordinator_geometry::{CoordinatorGeometry, CoordinatorRect};
52pub use cranpose_foundation::{
53 AnyModifierElement, DynModifierElement, FocusState, PointerButton, PointerButtons,
54 PointerEvent, PointerEventKind, PointerSource, RotaryScrollEvent, SemanticsConfiguration,
55 modifier_element,
56};
57use cranpose_foundation::{ModifierNodeElement, NodeCapabilities, ProgressBarRangeInfo};
58#[expect(unused_imports)]
59pub use cranpose_ui_graphics::{
60 BlendMode, BlurredEdgeTreatment, Brush, Color, ColorFilter, CompositingStrategy, CornerRadii,
61 CursorIcon, CustomPointerIcon, CutDirection, Dp, DpOffset, EdgeInsets, GradientCutMaskSpec,
62 GradientFadeMaskSpec, GraphicsLayer, LayerShape, Point, PointerIcon, PointerIconError, Rect,
63 RenderEffect, RoundedCornerShape, RuntimeShader, Shadow, ShadowScope, Size, TransformOrigin,
64};
65use cranpose_ui_layout::{Alignment, HorizontalAlignment, IntrinsicSize, VerticalAlignment};
66pub use drag_and_drop::{
67 DragAndDropEvent, DragAndDropOutcome, DragAndDropPayload, DragAndDropPoint, DragAndDropSource,
68 DragAndDropSourceElement, DragAndDropSourceNode, DragAndDropState, DragAndDropTarget,
69 DragAndDropTargetElement, DragAndDropTargetNode,
70};
71use focus::FocusTargetElement;
72pub use focus::{FocusDirection, FocusRequestError, FocusRequester, FocusRequesterElement};
73pub use graphics_layer::GlassMaterial;
74pub use key_input::KeyEventRoute;
75pub(crate) use local::{
76 ModifierLocalAncestorResolver, ModifierLocalSource, ModifierLocalToken, ResolvedModifierLocal,
77};
78use local::{ModifierLocalConsumerElement, ModifierLocalProviderElement};
79pub use local::{ModifierLocalKey, ModifierLocalReadScope};
80#[expect(unused_imports)]
81pub use pointer_input::{AwaitPointerEventScope, PointerInputScope};
82pub use rotary_input::RotaryInputModifierNode;
83#[cfg(test)]
84pub(crate) use scroll::lazy_scroll_semantics;
85#[cfg(feature = "test-helpers")]
86pub use scroll::{last_fling_velocity, reset_last_fling_velocity};
87use semantics::SemanticsElement;
88pub use semantics::{
89 SemanticsRequester, SemanticsRequesterElement, collect_semantics_from_chain,
90 collect_semantics_from_modifier, semantics_reach_of_chain,
91};
92pub(crate) use slices::SlicesSnapshot;
93pub use slices::{
94 ModifierNodeSlices, ModifierNodeSlicesDebugStats, SliceTextRef, collect_modifier_slices,
95 collect_slices_from_modifier,
96};
97pub use window_root::{
98 WindowRootDescriptor, WindowRootElement, WindowRootEntry, WindowRootNode, WindowRootRegistry,
99 WindowRootRoutingScratch, is_window_root, nearest_window_root, nearest_window_roots,
100 nearest_window_roots_into, window_roots, window_roots_revision,
101};
102pub use z_index::z_index_in_use;
103
104pub use crate::draw::{DrawCacheBuilder, DrawCommand};
105use crate::modifier_nodes::ClipToBoundsElement;
106
107#[derive(Clone, Debug, Default)]
108pub struct InspectorInfo {
109 properties: Vec<InspectorProperty>,
110}
111
112impl InspectorInfo {
113 pub fn new() -> Self {
114 Self::default()
115 }
116
117 pub fn add_property<V: Into<String>>(&mut self, name: &'static str, value: V) {
118 self.properties.push(InspectorProperty {
119 name,
120 value: value.into(),
121 });
122 }
123
124 pub fn properties(&self) -> &[InspectorProperty] {
125 &self.properties
126 }
127
128 pub fn is_empty(&self) -> bool {
129 self.properties.is_empty()
130 }
131
132 pub fn add_dimension(&mut self, name: &'static str, constraint: DimensionConstraint) {
133 self.add_property(name, describe_dimension(constraint));
134 }
135
136 pub fn add_offset_components(
137 &mut self,
138 x_name: &'static str,
139 y_name: &'static str,
140 offset: Point,
141 ) {
142 self.add_property(x_name, offset.x.to_string());
143 self.add_property(y_name, offset.y.to_string());
144 }
145
146 pub fn add_alignment<A>(&mut self, name: &'static str, alignment: A)
147 where
148 A: fmt::Debug,
149 {
150 self.add_property(name, format!("{alignment:?}"));
151 }
152}
153
154#[derive(Clone, Debug, PartialEq)]
155pub struct InspectorProperty {
156 pub name: &'static str,
157 pub value: String,
158}
159
160#[derive(Clone, Debug, PartialEq)]
161pub struct ModifierInspectorRecord {
162 pub name: &'static str,
163 pub properties: Vec<InspectorProperty>,
164}
165
166#[derive(Clone, Debug)]
167pub(crate) struct InspectorMetadata {
168 name: &'static str,
169 info: InspectorInfo,
170}
171
172impl InspectorMetadata {
173 pub(crate) fn new<F>(name: &'static str, recorder: F) -> Self
174 where
175 F: FnOnce(&mut InspectorInfo),
176 {
177 let mut info = InspectorInfo::new();
178 recorder(&mut info);
179 Self { name, info }
180 }
181
182 fn is_empty(&self) -> bool {
183 self.info.is_empty()
184 }
185
186 fn to_record(&self) -> ModifierInspectorRecord {
187 ModifierInspectorRecord {
188 name: self.name,
189 properties: self.info.properties().to_vec(),
190 }
191 }
192}
193
194fn describe_dimension(constraint: DimensionConstraint) -> String {
195 match constraint {
196 DimensionConstraint::Unspecified => "unspecified".to_string(),
197 DimensionConstraint::Points(value) => value.to_string(),
198 DimensionConstraint::Fraction(value) => format!("fraction({value})"),
199 DimensionConstraint::Intrinsic(size) => format!("intrinsic({size:?})"),
200 }
201}
202
203fn inspector_slice(inspector: &Option<Rc<[InspectorMetadata]>>) -> &[InspectorMetadata] {
204 inspector.as_deref().unwrap_or(&[])
205}
206
207fn merged_inspector(
210 first: &[InspectorMetadata],
211 second: &[InspectorMetadata],
212) -> Option<Rc<[InspectorMetadata]>> {
213 if first.is_empty() && second.is_empty() {
214 return None;
215 }
216 Some(first.iter().chain(second).cloned().collect())
217}
218
219fn same_element(a: &DynModifierElement, b: &DynModifierElement) -> bool {
222 Rc::ptr_eq(a, b) || a.equals_element(&**b)
223}
224
225pub(crate) fn inspector_metadata<F>(name: &'static str, recorder: F) -> InspectorMetadata
226where
227 F: FnOnce(&mut InspectorInfo),
228{
229 if !inspector_metadata_enabled() {
230 return InspectorMetadata::new(name, |_| {});
231 }
232 InspectorMetadata::new(name, recorder)
233}
234
235pub(crate) fn modifier_debug_enabled() -> bool {
236 #[cfg(not(target_arch = "wasm32"))]
237 {
238 cranpose_core::env_flag!("COMPOSE_DEBUG_MODIFIERS")
239 }
240 #[cfg(target_arch = "wasm32")]
241 {
242 false
243 }
244}
245
246fn inspector_metadata_enabled() -> bool {
247 cfg!(any(test, feature = "inspection")) || modifier_debug_enabled()
248}
249
250type ModifierElements = SmallVec<[DynModifierElement; 4]>;
253
254#[derive(Clone)]
259enum Elements {
260 One(DynModifierElement),
261 Many(Rc<ModifierElements>),
262}
263
264impl Elements {
265 fn joined(self, more: Self) -> Self {
268 let mut chain = match self {
269 Self::One(first) => {
270 let mut chain = ModifierElements::new();
271 chain.push(first);
272 Rc::new(chain)
273 }
274 Self::Many(chain) => chain,
275 };
276 match Rc::get_mut(&mut chain) {
277 Some(owned) => more.move_into(owned),
278 None => {
279 let mut copy: ModifierElements = chain.iter().cloned().collect();
280 more.move_into(&mut copy);
281 chain = Rc::new(copy);
282 }
283 }
284 Self::Many(chain)
285 }
286
287 fn move_into(self, chain: &mut ModifierElements) {
288 match self {
289 Self::One(element) => chain.push(element),
290 Self::Many(shared) => match Rc::try_unwrap(shared) {
291 Ok(owned) => chain.extend(owned),
292 Err(shared) => chain.extend(shared.iter().cloned()),
293 },
294 }
295 }
296
297 fn shares_storage(&self, other: &Self) -> bool {
299 match (self, other) {
300 (Self::One(a), Self::One(b)) => Rc::ptr_eq(a, b),
301 (Self::Many(a), Self::Many(b)) => Rc::ptr_eq(a, b),
302 _ => false,
303 }
304 }
305}
306
307impl std::ops::Deref for Elements {
308 type Target = [DynModifierElement];
309
310 fn deref(&self) -> &[DynModifierElement] {
311 match self {
312 Self::One(element) => std::slice::from_ref(element),
313 Self::Many(elements) => elements.as_slice(),
314 }
315 }
316}
317
318#[derive(Clone)]
319enum ModifierKind {
320 Empty,
321 Single {
322 elements: Elements,
323 inspector: Option<Rc<[InspectorMetadata]>>,
326 },
327}
328
329pub struct ModifierElementIterator<'a> {
330 inner: std::slice::Iter<'a, DynModifierElement>,
331}
332
333impl<'a> Iterator for ModifierElementIterator<'a> {
334 type Item = &'a DynModifierElement;
335
336 #[inline]
337 fn next(&mut self) -> Option<Self::Item> {
338 self.inner.next()
339 }
340
341 #[inline]
342 fn size_hint(&self) -> (usize, Option<usize>) {
343 self.inner.size_hint()
344 }
345}
346
347impl ExactSizeIterator for ModifierElementIterator<'_> {}
348
349pub(crate) struct ModifierInspectorIterator<'a> {
350 inner: std::slice::Iter<'a, InspectorMetadata>,
351}
352
353impl<'a> Iterator for ModifierInspectorIterator<'a> {
354 type Item = &'a InspectorMetadata;
355
356 #[inline]
357 fn next(&mut self) -> Option<Self::Item> {
358 self.inner.next()
359 }
360
361 #[inline]
362 fn size_hint(&self) -> (usize, Option<usize>) {
363 self.inner.size_hint()
364 }
365}
366
367impl ExactSizeIterator for ModifierInspectorIterator<'_> {}
368
369#[derive(Clone)]
387pub struct Modifier {
388 kind: ModifierKind,
389 element_count: usize,
390 provides_composition_locals: bool,
391}
392
393impl Default for Modifier {
394 fn default() -> Self {
395 Self {
396 kind: ModifierKind::Empty,
397 element_count: 0,
398 provides_composition_locals: false,
399 }
400 }
401}
402
403impl Modifier {
404 pub fn empty() -> Self {
406 Self::default()
407 }
408
409 pub fn from_element<E>(element: E) -> Self
411 where
412 E: ModifierNodeElement,
413 {
414 Self::with_element(element)
415 }
416
417 pub fn clip_to_bounds(self) -> Self {
421 let modifier = crate::thread_state::with(|state| {
422 state
423 .clip_to_bounds
424 .get_or_init(|| Modifier::with_element(ClipToBoundsElement::new()))
425 .clone()
426 });
427 let modifier =
428 modifier.with_inspector_metadata(inspector_metadata("clipToBounds", |info| {
429 info.add_property("clipToBounds", "true");
430 }));
431 self.then(modifier)
432 }
433
434 pub fn modifier_local_provider<T, F>(self, key: ModifierLocalKey<T>, value: F) -> Self
437 where
438 T: 'static,
439 F: Fn() -> T + 'static,
440 {
441 let element = ModifierLocalProviderElement::new(key, value);
442 let modifier = Modifier::from_dyn_element(modifier_element(element));
443 self.then(modifier)
444 }
445
446 pub fn modifier_local_consumer<F>(self, consumer: F) -> Self
449 where
450 F: for<'scope> Fn(&mut ModifierLocalReadScope<'scope>) + 'static,
451 {
452 let element = ModifierLocalConsumerElement::new(consumer);
453 let modifier = Modifier::from_dyn_element(modifier_element(element));
454 self.then(modifier)
455 }
456
457 pub fn semantics_spec(self, spec: cranpose_foundation::SemanticsSpec) -> Self {
468 self.stable_semantics(move |config: &mut SemanticsConfiguration| config.merge(&spec))
469 }
470
471 pub fn semantics<F>(self, recorder: F) -> Self
476 where
477 F: Fn(&mut SemanticsConfiguration) + 'static,
478 {
479 self.semantics_recorder(std::rc::Rc::new(recorder), true)
480 }
481
482 pub fn stable_semantics<F>(self, recorder: F) -> Self
489 where
490 F: Fn(&mut SemanticsConfiguration) + 'static,
491 {
492 self.semantics_recorder(std::rc::Rc::new(recorder), false)
493 }
494
495 fn semantics_recorder(
496 self,
497 recorder: std::rc::Rc<dyn Fn(&mut SemanticsConfiguration)>,
498 reads_live_state: bool,
499 ) -> Self {
500 let metadata = if inspector_metadata_enabled() {
501 let mut preview = SemanticsConfiguration::default();
502 recorder(&mut preview);
503 let description = preview.content_description.clone();
504 let state_description = preview.state_description.clone();
505 let role = preview.role;
506 let is_clickable = preview.is_activatable();
507 let canvas_children = preview.canvas_children.len();
508 inspector_metadata("semantics", move |info| {
509 if let Some(desc) = &description {
510 info.add_property("contentDescription", desc.clone());
511 }
512 if let Some(state) = &state_description {
513 info.add_property("stateDescription", state.clone());
514 }
515 if let Some(role) = role {
516 info.add_property("role", format!("{role:?}"));
517 }
518 if is_clickable {
519 info.add_property("isClickable", "true");
520 }
521 if canvas_children > 0 {
522 info.add_property("canvasSemanticsChildren", canvas_children.to_string());
523 }
524 })
525 } else {
526 inspector_metadata("semantics", |_| {})
527 };
528 let element = SemanticsElement::new(recorder, reads_live_state);
529 let modifier =
530 Modifier::from_dyn_element(modifier_element(element)).with_inspector_metadata(metadata);
531 self.then(modifier)
532 }
533
534 pub fn progress_semantics(self, current: f32, start: f32, end: f32, steps: u32) -> Self {
543 let info = ProgressBarRangeInfo::new(current, start, end, steps);
544 self.stable_semantics(move |config| config.progress = Some(info))
545 }
546
547 pub fn dropdown_list(self) -> Self {
551 self.role(cranpose_foundation::SemanticsWidgetRole::DropdownList)
552 }
553
554 pub fn value_picker(self) -> Self {
557 self.role(cranpose_foundation::SemanticsWidgetRole::ValuePicker)
558 }
559
560 pub fn expand(self, action: impl Fn() -> bool + 'static) -> Self {
565 let action = cranpose_foundation::SemanticsExpand::new(action);
566 self.stable_semantics(move |config| config.expand = Some(action.clone()))
567 }
568
569 pub fn on_long_click(
579 self,
580 label: impl Into<String>,
581 action: impl Fn() -> bool + 'static,
582 ) -> Self {
583 let label = label.into();
584 let action = cranpose_foundation::SemanticsLongClick::new(action);
585 self.stable_semantics(move |config| {
586 config.on_long_click_label = Some(label.clone());
587 config.on_long_click = Some(action.clone());
588 })
589 }
590
591 pub fn on_magic_tap(
596 self,
597 label: impl Into<String>,
598 action: impl Fn() -> bool + 'static,
599 ) -> Self {
600 let label = label.into();
601 let action = cranpose_foundation::SemanticsMagicTap::new(action);
602 self.stable_semantics(move |config| {
603 config.on_magic_tap_label = Some(label.clone());
604 config.on_magic_tap = Some(action.clone());
605 })
606 }
607
608 pub fn input_labels<S: Into<String>>(self, labels: impl IntoIterator<Item = S>) -> Self {
612 let labels: Vec<String> = labels.into_iter().map(Into::into).collect();
613 self.stable_semantics(move |config| config.input_labels.clone_from(&labels))
614 }
615
616 pub fn language(self, tag: impl Into<String>) -> Self {
620 let tag = tag.into();
621 self.stable_semantics(move |config| config.language = Some(tag.clone()))
622 }
623
624 pub fn collapse(self, action: impl Fn() -> bool + 'static) -> Self {
628 let action = cranpose_foundation::SemanticsExpand::new(action);
629 self.stable_semantics(move |config| config.collapse = Some(action.clone()))
630 }
631
632 pub fn dismiss(self, action: impl Fn() -> bool + 'static) -> Self {
637 let action = cranpose_foundation::SemanticsDismiss::new(action);
638 self.stable_semantics(move |config| config.dismiss = Some(action.clone()))
639 }
640
641 pub fn scroll_to_index(self, action: impl Fn(usize) -> bool + 'static) -> Self {
647 let action = cranpose_foundation::SemanticsScrollToIndex::new(action);
648 self.stable_semantics(move |config| config.scroll_to_index = Some(action.clone()))
649 }
650
651 pub fn traversal_index(self, index: f32) -> Self {
657 self.stable_semantics(move |config| config.traversal_index = index)
658 }
659
660 pub fn password(self) -> Self {
665 self.stable_semantics(|config| config.password = true)
666 }
667
668 pub fn error(self, message: impl Into<String>) -> Self {
672 let message = message.into();
673 self.stable_semantics(move |config| config.error = Some(message.clone()))
674 }
675
676 pub fn hint_text(self, hint: impl Into<String>) -> Self {
680 let hint = hint.into();
681 self.stable_semantics(move |config| config.hint_text = Some(hint.clone()))
682 }
683
684 pub fn pane_title(self, title: impl Into<String>) -> Self {
688 let title = title.into();
689 self.stable_semantics(move |config| config.pane_title = Some(title.clone()))
690 }
691
692 pub fn selectable_group(self) -> Self {
697 self.stable_semantics(|config| config.selectable_group = true)
698 }
699
700 pub fn merge_descendants(self) -> Self {
705 self.stable_semantics(|config| config.merge_descendants = true)
706 }
707
708 pub fn hide_from_accessibility(self) -> Self {
713 self.stable_semantics(|config| config.hidden = true)
714 }
715
716 pub fn heading(self) -> Self {
721 self.role(cranpose_foundation::SemanticsWidgetRole::Header)
722 }
723
724 pub fn role(self, role: cranpose_foundation::SemanticsWidgetRole) -> Self {
729 self.stable_semantics(move |config| config.role = Some(role))
730 }
731
732 pub fn live_region(self, mode: cranpose_foundation::LiveRegionMode) -> Self {
738 self.stable_semantics(move |config| config.live_region = Some(mode))
739 }
740
741 pub fn content_description(self, description: impl Into<String>) -> Self {
746 let description = description.into();
747 self.stable_semantics(move |config| config.content_description = Some(description.clone()))
748 }
749
750 pub fn test_tag(self, tag: impl Into<String>) -> Self {
755 let tag = tag.into();
756 self.stable_semantics(move |config| config.test_tag = Some(tag.clone()))
757 }
758
759 pub fn focus_target(self) -> Self {
765 let element = FocusTargetElement::new();
766 let modifier = Modifier::from_dyn_element(modifier_element(element));
767 self.then(modifier)
768 }
769
770 pub fn on_focus_changed<F>(self, callback: F) -> Self
775 where
776 F: Fn(FocusState) + 'static,
777 {
778 let element = FocusTargetElement::with_callback(callback);
779 let modifier = Modifier::from_dyn_element(modifier_element(element));
780 self.then(modifier)
781 }
782
783 pub fn focus_requester(self, requester: &FocusRequester) -> Self {
790 let element = FocusRequesterElement::new(requester.clone());
791 let modifier = Modifier::from_dyn_element(modifier_element(element));
792 self.then(modifier)
793 }
794
795 pub fn semantics_requester(self, requester: &SemanticsRequester) -> Self {
803 let element = SemanticsRequesterElement::new(requester.clone());
804 let modifier = Modifier::from_dyn_element(modifier_element(element));
805 self.then(modifier)
806 }
807
808 pub fn debug_chain(self, tag: &'static str) -> Self {
826 use cranpose_foundation::{ModifierNode, ModifierNodeContext, NodeCapabilities, NodeState};
827
828 #[derive(Clone)]
829 struct DebugChainElement {
830 tag: &'static str,
831 }
832
833 impl fmt::Debug for DebugChainElement {
834 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
835 f.debug_struct("DebugChainElement")
836 .field("tag", &self.tag)
837 .finish()
838 }
839 }
840
841 impl PartialEq for DebugChainElement {
842 fn eq(&self, other: &Self) -> bool {
843 self.tag == other.tag
844 }
845 }
846
847 impl Eq for DebugChainElement {}
848
849 impl std::hash::Hash for DebugChainElement {
850 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
851 self.tag.hash(state);
852 }
853 }
854
855 impl ModifierNodeElement for DebugChainElement {
856 type Node = DebugChainNode;
857
858 fn create(&self) -> Self::Node {
859 DebugChainNode::new(self.tag)
860 }
861
862 fn update(&self, node: &mut Self::Node) {
863 node.tag = self.tag;
864 }
865
866 fn capabilities(&self) -> NodeCapabilities {
867 NodeCapabilities::empty()
868 }
869 }
870
871 struct DebugChainNode {
872 tag: &'static str,
873 state: NodeState,
874 }
875
876 impl DebugChainNode {
877 fn new(tag: &'static str) -> Self {
878 Self {
879 tag,
880 state: NodeState::new(),
881 }
882 }
883 }
884
885 impl ModifierNode for DebugChainNode {
886 fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {
887 eprintln!("[debug_chain:{}] Modifier chain attached", self.tag);
888 }
889
890 fn on_detach(&mut self) {
891 eprintln!("[debug_chain:{}] Modifier chain detached", self.tag);
892 }
893
894 fn on_reset(&mut self) {
895 eprintln!("[debug_chain:{}] Modifier chain reset", self.tag);
896 }
897 }
898
899 impl cranpose_foundation::DelegatableNode for DebugChainNode {
900 fn node_state(&self) -> &NodeState {
901 &self.state
902 }
903 }
904
905 let element = DebugChainElement { tag };
906 let modifier = Modifier::from_dyn_element(modifier_element(element));
907 self.then(modifier)
908 .with_inspector_metadata(inspector_metadata("debugChain", move |info| {
909 info.add_property("tag", tag);
910 }))
911 }
912
913 pub fn then(self, next: Modifier) -> Modifier {
919 let ModifierKind::Single {
920 elements,
921 inspector,
922 } = self.kind
923 else {
924 return next;
925 };
926 let ModifierKind::Single {
927 elements: next_elements,
928 inspector: next_inspector,
929 } = next.kind
930 else {
931 return Modifier {
932 kind: ModifierKind::Single {
933 elements,
934 inspector,
935 },
936 ..self
937 };
938 };
939
940 let inspector = if next_inspector.is_none() {
941 inspector
942 } else {
943 merged_inspector(
944 inspector_slice(&inspector),
945 inspector_slice(&next_inspector),
946 )
947 };
948 Modifier {
949 kind: ModifierKind::Single {
950 elements: elements.joined(next_elements),
951 inspector,
952 },
953 element_count: self.element_count + next.element_count,
954 provides_composition_locals: self.provides_composition_locals
955 || next.provides_composition_locals,
956 }
957 }
958
959 pub(crate) fn element_count(&self) -> usize {
961 self.element_count
962 }
963
964 pub(crate) fn iter_elements(&self) -> ModifierElementIterator<'_> {
965 match &self.kind {
966 ModifierKind::Empty => ModifierElementIterator { inner: [].iter() },
967 ModifierKind::Single { elements, .. } => ModifierElementIterator {
968 inner: elements.iter(),
969 },
970 }
971 }
972
973 pub(crate) fn provided_composition_locals(&self) -> Vec<ProvidedValue> {
974 if !self.provides_composition_locals {
975 return Vec::new();
976 }
977 self.iter_elements()
978 .flat_map(|element| element.provided_composition_locals())
979 .collect()
980 }
981
982 pub(crate) fn iter_inspector_metadata(&self) -> ModifierInspectorIterator<'_> {
983 match &self.kind {
984 ModifierKind::Empty => ModifierInspectorIterator { inner: [].iter() },
985 ModifierKind::Single { inspector, .. } => ModifierInspectorIterator {
986 inner: inspector_slice(inspector).iter(),
987 },
988 }
989 }
990
991 #[cfg(test)]
992 pub(crate) fn elements(&self) -> Vec<DynModifierElement> {
993 match &self.kind {
994 ModifierKind::Empty => Vec::new(),
995 ModifierKind::Single { elements, .. } => elements.to_vec(),
996 }
997 }
998
999 pub(crate) fn inspector_metadata(&self) -> Vec<InspectorMetadata> {
1000 match &self.kind {
1001 ModifierKind::Empty => Vec::new(),
1002 ModifierKind::Single { inspector, .. } => {
1003 inspector.as_deref().map(<[_]>::to_vec).unwrap_or_default()
1004 }
1005 }
1006 }
1007
1008 pub fn total_padding(&self) -> f32 {
1010 let padding = self.padding_values();
1011 padding
1012 .left
1013 .max(padding.right)
1014 .max(padding.top)
1015 .max(padding.bottom)
1016 }
1017
1018 pub fn explicit_size(&self) -> Option<Size> {
1021 let props = self.layout_properties();
1022 match (props.width, props.height) {
1023 (DimensionConstraint::Points(width), DimensionConstraint::Points(height)) => {
1024 Some(Size { width, height })
1025 }
1026 _ => None,
1027 }
1028 }
1029
1030 pub fn padding_values(&self) -> EdgeInsets {
1032 self.resolved_modifiers().padding()
1033 }
1034
1035 pub(crate) fn layout_properties(&self) -> LayoutProperties {
1036 self.resolved_modifiers().layout_properties()
1037 }
1038
1039 pub fn box_alignment(&self) -> Option<Alignment> {
1041 self.layout_properties().box_alignment()
1042 }
1043
1044 pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
1046 self.layout_properties().column_alignment()
1047 }
1048
1049 pub fn row_alignment(&self) -> Option<VerticalAlignment> {
1051 self.layout_properties().row_alignment()
1052 }
1053
1054 pub fn draw_commands(&self) -> Vec<DrawCommand> {
1056 collect_slices_from_modifier(self).draw_commands().to_vec()
1057 }
1058
1059 pub fn clips_to_bounds(&self) -> bool {
1061 collect_slices_from_modifier(self).clip_to_bounds()
1062 }
1063
1064 pub fn collect_inspector_records(&self) -> Vec<ModifierInspectorRecord> {
1066 self.inspector_metadata()
1067 .iter()
1068 .map(InspectorMetadata::to_record)
1069 .collect()
1070 }
1071
1072 pub fn resolved_modifiers(&self) -> ResolvedModifiers {
1074 let mut handle = ModifierChainHandle::new();
1075 let _ = handle.update(self);
1076 handle.resolved_modifiers()
1077 }
1078
1079 pub fn with_element<E>(element: E) -> Self
1082 where
1083 E: ModifierNodeElement,
1084 {
1085 Self::from_dyn_element(modifier_element(element))
1086 }
1087
1088 pub(crate) fn from_dyn_element(element: DynModifierElement) -> Self {
1090 Self {
1091 provides_composition_locals: element.provides_composition_locals(),
1092 kind: ModifierKind::Single {
1093 elements: Elements::One(element),
1094 inspector: None,
1095 },
1096 element_count: 1,
1097 }
1098 }
1099
1100 pub(crate) fn with_inspector_metadata(self, metadata: InspectorMetadata) -> Self {
1101 if metadata.is_empty() {
1102 return self;
1103 }
1104 match self.kind {
1105 ModifierKind::Empty => self,
1106 ModifierKind::Single {
1107 elements,
1108 inspector,
1109 } => {
1110 let new_inspector = inspector_slice(&inspector)
1111 .iter()
1112 .cloned()
1113 .chain(std::iter::once(metadata))
1114 .collect();
1115 Self {
1116 kind: ModifierKind::Single {
1117 elements,
1118 inspector: Some(new_inspector),
1119 },
1120 element_count: self.element_count,
1121 provides_composition_locals: self.provides_composition_locals,
1122 }
1123 }
1124 }
1125 }
1126
1127 pub fn structural_eq(&self, other: &Self) -> bool {
1132 self.eq_internal(other, false)
1133 }
1134
1135 pub(crate) fn in_place_update(&self, other: &Self) -> Option<InPlaceUpdate> {
1142 if self.element_count != other.element_count || self.element_count > u64::BITS as usize {
1143 return None;
1144 }
1145 let mut update = InPlaceUpdate {
1146 reach: InPlaceReach::Node,
1147 elements: 0,
1148 };
1149 for (index, (a, b)) in self.iter_elements().zip(other.iter_elements()).enumerate() {
1150 if a.requires_update() || b.requires_update() {
1151 return None;
1152 }
1153 let element_type = a.element_type();
1154 match InPlaceReach::of(element_type) {
1155 Some(InPlaceReach::Node) if b.element_type() == element_type => {
1157 update.elements |= 1 << index;
1158 }
1159 Some(InPlaceReach::Resolved) if b.element_type() == element_type => {
1160 if !same_element(a, b) {
1161 update.reach = InPlaceReach::Resolved;
1162 update.elements |= 1 << index;
1163 }
1164 }
1165 _ => {
1166 if !same_element(a, b) {
1167 return None;
1168 }
1169 }
1170 }
1171 }
1172 Some(update)
1173 }
1174
1175 fn eq_internal(&self, other: &Self, consider_always_update: bool) -> bool {
1176 if self.element_count != other.element_count {
1177 return false;
1178 }
1179
1180 match (&self.kind, &other.kind) {
1181 (ModifierKind::Empty, ModifierKind::Empty) => true,
1182 (
1183 ModifierKind::Single {
1184 elements: e1,
1185 inspector: _,
1186 },
1187 ModifierKind::Single {
1188 elements: e2,
1189 inspector: _,
1190 },
1191 ) => {
1192 if e1.shares_storage(e2) {
1193 return true;
1194 }
1195
1196 if e1.len() != e2.len() {
1197 return false;
1198 }
1199
1200 for (a, b) in e1.iter().zip(e2.iter()) {
1201 if Rc::ptr_eq(a, b) {
1203 continue;
1204 }
1205 if !consider_always_update
1206 && a.element_type() == b.element_type()
1207 && a.capabilities() == NodeCapabilities::DRAW
1208 && b.capabilities() == NodeCapabilities::DRAW
1209 {
1210 continue;
1211 }
1212
1213 if consider_always_update && (a.requires_update() || b.requires_update()) {
1214 if !Rc::ptr_eq(a, b) {
1215 return false;
1216 }
1217 continue;
1218 }
1219
1220 if !a.equals_element(&**b) {
1221 return false;
1222 }
1223 }
1224
1225 true
1226 }
1227 _ => false,
1228 }
1229 }
1230}
1231
1232impl PartialEq for Modifier {
1233 fn eq(&self, other: &Self) -> bool {
1234 self.eq_internal(other, true)
1235 }
1236}
1237
1238impl Eq for Modifier {}
1239
1240impl fmt::Display for Modifier {
1241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1242 match &self.kind {
1243 ModifierKind::Empty => write!(f, "Modifier.empty"),
1244 ModifierKind::Single { elements, .. } => {
1245 if elements.is_empty() {
1246 return write!(f, "Modifier.empty");
1247 }
1248 write!(f, "Modifier[")?;
1249 for (index, element) in elements.iter().enumerate() {
1250 if index > 0 {
1251 write!(f, ", ")?;
1252 }
1253 let name = element.inspector_name();
1254 let mut properties = Vec::new();
1255 element.record_inspector_properties(&mut |prop, value| {
1256 properties.push(format!("{prop}={value}"));
1257 });
1258 if properties.is_empty() {
1259 write!(f, "{name}")?;
1260 } else {
1261 write!(f, "{name}({})", properties.join(", "))?;
1262 }
1263 }
1264 write!(f, "]")
1265 }
1266 }
1267 }
1268}
1269
1270impl fmt::Debug for Modifier {
1271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1272 fmt::Display::fmt(self, f)
1273 }
1274}
1275
1276#[derive(Clone, Copy, Debug, PartialEq)]
1278pub struct ResolvedBackground {
1279 color: Color,
1280 shape: Option<RoundedCornerShape>,
1281}
1282
1283impl ResolvedBackground {
1284 pub fn new(color: Color, shape: Option<RoundedCornerShape>) -> Self {
1287 Self { color, shape }
1288 }
1289
1290 pub fn color(&self) -> Color {
1292 self.color
1293 }
1294
1295 pub fn shape(&self) -> Option<RoundedCornerShape> {
1297 self.shape
1298 }
1299
1300 pub fn set_shape(&mut self, shape: Option<RoundedCornerShape>) {
1302 self.shape = shape;
1303 }
1304}
1305
1306#[derive(Clone, Copy, Debug, PartialEq, Default)]
1308pub struct ResolvedModifiers {
1309 padding: EdgeInsets,
1310 layout: LayoutProperties,
1311 offset: Point,
1312}
1313
1314impl ResolvedModifiers {
1315 pub fn padding(&self) -> EdgeInsets {
1317 self.padding
1318 }
1319
1320 pub fn layout_properties(&self) -> LayoutProperties {
1322 self.layout
1323 }
1324
1325 pub fn offset(&self) -> Point {
1327 self.offset
1328 }
1329
1330 pub(crate) fn set_padding(&mut self, padding: EdgeInsets) {
1331 self.padding = padding;
1332 }
1333
1334 pub(crate) fn set_layout_properties(&mut self, layout: LayoutProperties) {
1335 self.layout = layout;
1336 }
1337
1338 pub(crate) fn set_offset(&mut self, offset: Point) {
1339 self.offset = offset;
1340 }
1341
1342 pub(crate) fn on_device_grid(&self, density: f32) -> Self {
1345 use cranpose_ui_layout::round_to_px;
1346
1347 let round = |value: Option<f32>| value.map(|value| round_to_px(value, density));
1348 let dimension = |dimension: DimensionConstraint| match dimension {
1349 DimensionConstraint::Points(value) => {
1350 DimensionConstraint::Points(round_to_px(value, density))
1351 }
1352 other => other,
1353 };
1354 Self {
1355 padding: crate::modifier_nodes::device_padding(self.padding, density),
1356 layout: LayoutProperties {
1357 padding: crate::modifier_nodes::device_padding(self.layout.padding, density),
1358 width: dimension(self.layout.width),
1359 height: dimension(self.layout.height),
1360 min_width: round(self.layout.min_width),
1361 min_height: round(self.layout.min_height),
1362 max_width: round(self.layout.max_width),
1363 max_height: round(self.layout.max_height),
1364 ..self.layout
1365 },
1366 offset: Point {
1367 x: round_to_px(self.offset.x, density),
1368 y: round_to_px(self.offset.y, density),
1369 },
1370 }
1371 }
1372}
1373
1374#[derive(Clone, Copy, Debug, Default, PartialEq)]
1375pub enum DimensionConstraint {
1376 #[default]
1377 Unspecified,
1378 Points(f32),
1379 Fraction(f32),
1380 Intrinsic(IntrinsicSize),
1381}
1382
1383#[derive(Clone, Copy, Debug, Default, PartialEq)]
1384pub struct LayoutWeight {
1385 pub weight: f32,
1386 pub fill: bool,
1387}
1388
1389#[derive(Clone, Copy, Debug, Default, PartialEq)]
1390pub struct LayoutProperties {
1391 padding: EdgeInsets,
1392 width: DimensionConstraint,
1393 height: DimensionConstraint,
1394 min_width: Option<f32>,
1395 min_height: Option<f32>,
1396 max_width: Option<f32>,
1397 max_height: Option<f32>,
1398 weight: Option<LayoutWeight>,
1399 box_alignment: Option<Alignment>,
1400 column_alignment: Option<HorizontalAlignment>,
1401 row_alignment: Option<VerticalAlignment>,
1402 row_baseline: bool,
1403}
1404
1405impl LayoutProperties {
1406 pub fn padding(&self) -> EdgeInsets {
1407 self.padding
1408 }
1409
1410 pub fn width(&self) -> DimensionConstraint {
1411 self.width
1412 }
1413
1414 pub fn height(&self) -> DimensionConstraint {
1415 self.height
1416 }
1417
1418 pub fn min_width(&self) -> Option<f32> {
1419 self.min_width
1420 }
1421
1422 pub fn min_height(&self) -> Option<f32> {
1423 self.min_height
1424 }
1425
1426 pub fn max_width(&self) -> Option<f32> {
1427 self.max_width
1428 }
1429
1430 pub fn max_height(&self) -> Option<f32> {
1431 self.max_height
1432 }
1433
1434 pub fn weight(&self) -> Option<LayoutWeight> {
1435 self.weight
1436 }
1437
1438 pub fn box_alignment(&self) -> Option<Alignment> {
1439 self.box_alignment
1440 }
1441
1442 pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
1443 self.column_alignment
1444 }
1445
1446 pub fn row_alignment(&self) -> Option<VerticalAlignment> {
1447 self.row_alignment
1448 }
1449
1450 pub fn row_baseline(&self) -> bool {
1452 self.row_baseline
1453 }
1454}
1455
1456#[cfg(test)]
1457#[path = "tests/modifier_tests.rs"]
1458mod tests;