Skip to main content

cranpose_ui/modifier/
mod.rs

1//! Modifier system for Cranpose
2//!
3//! This module now acts as a thin builder around modifier elements. Each
4//! [`Modifier`] stores the element chain required by the modifier node system
5//! together with inspector metadata while resolved state is computed directly
6//! from the modifier nodes.
7
8use 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
199/// The inspector metadata of two modifiers joined, first `first`'s, or
200/// `None` when neither has any.
201fn 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
236/// A modifier's elements: a chain of up to four lives inline in its one
237/// shared allocation.
238type ModifierElements = SmallVec<[DynModifierElement; 4]>;
239
240/// The elements of a non-empty modifier. A lone element, as each link of a
241/// builder chain starts out, is held as it is; joining it to more moves the
242/// chain into one shared allocation, which the chain's later links extend
243/// in place while it is the only owner.
244#[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    /// Whether both are the same storage, so equal without comparing.
273    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        /// `None` when no element records inspector metadata, which is
299        /// always outside tests and modifier debugging: no allocation.
300        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/// A modifier chain that can be applied to composable elements.
450///
451/// Modifiers allow you to decorate or augment a composable. Common operations include:
452/// - Adjusting layout (e.g., `padding`, `fill_max_size`)
453/// - Adding behavior (e.g., `clickable`, `scrollable`)
454/// - Drawing (e.g., `background`, `border`)
455///
456/// Modifiers are immutable and form a chain using the builder pattern.
457/// The order of modifiers matters: previous modifiers wrap subsequent ones.
458///
459/// # Example
460///
461/// ```rust,ignore
462/// Modifier::padding(16.0)     // Applied first (outer)
463///     .background(Color::Red) // Applied second
464///     .clickable(|| println!("Clicked")) // Applied last (inner)
465/// ```
466#[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    /// Creates a modifier from a custom modifier node element.
494    pub fn from_element<E>(element: E) -> Self
495    where
496        E: ModifierNodeElement,
497    {
498        Self::with_element(element)
499    }
500
501    /// Clip the content to the bounds of this modifier.
502    ///
503    /// Example: `Modifier::empty().clip_to_bounds()`
504    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    /// Says what a screen reader reads for this node, as one spec value.
533    ///
534    /// This is [`Modifier::semantics`] with a value in place of a closure.
535    /// Compose has only the closure form, because Kotlin's receiver lambda
536    /// makes `semantics { contentDescription = "Save" }` read well; Rust has
537    /// none, so a spec value reads better, costs one chain element rather
538    /// than one per property, and can be compared with another.
539    ///
540    /// Example:
541    /// `Modifier::empty().semantics_spec(SemanticsSpec::new().content_description("Amount").error("needs a number"))`
542    pub fn semantics_spec(self, spec: cranpose_foundation::SemanticsSpec) -> Self {
543        self.stable_semantics(move |config: &mut SemanticsConfiguration| config.merge(&spec))
544    }
545
546    /// Records what a screen reader reads for this node. The recorder may
547    /// read live state, so every semantics update runs it again; one whose
548    /// answer changes without the node changing can say so with a
549    /// [`SemanticsRequester`](crate::SemanticsRequester).
550    pub fn semantics<F>(self, recorder: F) -> Self
551    where
552        F: Fn(&mut SemanticsConfiguration) + 'static,
553    {
554        self.semantics_recorder(std::rc::Rc::new(recorder), true)
555    }
556
557    /// [`Modifier::semantics`] for a recorder that reads nothing but what it
558    /// captured. Its answer changes only when the modifier is set again, which
559    /// invalidates the node's semantics, so a semantics update keeps what it
560    /// reported instead of running it.
561    ///
562    /// Example: `Modifier::empty().stable_semantics(move |config| config.content_description = Some(label.clone()))`
563    pub fn stable_semantics<F>(self, recorder: F) -> Self
564    where
565        F: Fn(&mut SemanticsConfiguration) + 'static,
566    {
567        self.semantics_recorder(std::rc::Rc::new(recorder), false)
568    }
569
570    fn semantics_recorder(
571        self,
572        recorder: std::rc::Rc<dyn Fn(&mut SemanticsConfiguration)>,
573        reads_live_state: bool,
574    ) -> Self {
575        let metadata = if inspector_metadata_enabled() {
576            let mut preview = SemanticsConfiguration::default();
577            recorder(&mut preview);
578            let description = preview.content_description.clone();
579            let state_description = preview.state_description.clone();
580            let role = preview.role;
581            let is_clickable = preview.is_activatable();
582            let canvas_children = preview.canvas_children.len();
583            inspector_metadata("semantics", move |info| {
584                if let Some(desc) = &description {
585                    info.add_property("contentDescription", desc.clone());
586                }
587                if let Some(state) = &state_description {
588                    info.add_property("stateDescription", state.clone());
589                }
590                if let Some(role) = role {
591                    info.add_property("role", format!("{role:?}"));
592                }
593                if is_clickable {
594                    info.add_property("isClickable", "true");
595                }
596                if canvas_children > 0 {
597                    info.add_property("canvasSemanticsChildren", canvas_children.to_string());
598                }
599            })
600        } else {
601            inspector_metadata("semantics", |_| {})
602        };
603        let element = SemanticsElement::new(recorder, reads_live_state);
604        let modifier =
605            Modifier::from_parts(&[modifier_element(element)]).with_inspector_metadata(metadata);
606        self.then(modifier)
607    }
608
609    /// Tells a screen reader the value this control holds inside a range, so
610    /// it reads the value and offers its own way to change it.
611    ///
612    /// This is Compose's `Modifier.progressSemantics(value, valueRange,
613    /// steps)`. Without it a slider reads as text and a person who cannot see
614    /// the screen has no way to move it.
615    ///
616    /// Example: `Modifier::empty().progress_semantics(0.35, 0.0, 1.0, 0)`
617    pub fn progress_semantics(self, current: f32, start: f32, end: f32, steps: u32) -> Self {
618        let info = ProgressBarRangeInfo::new(current, start, end, steps);
619        self.stable_semantics(move |config| config.progress = Some(info))
620    }
621
622    /// Marks this control as one that opens a list of choices, so a reader
623    /// says "combo box" rather than "button" and knows to look for the
624    /// choice it holds. Compose's `Role.DropdownList`.
625    pub fn dropdown_list(self) -> Self {
626        self.role(cranpose_foundation::SemanticsWidgetRole::DropdownList)
627    }
628
629    /// Marks this control as one that holds one value out of an ordered set,
630    /// so a reader steps through them. Compose's `Role.ValuePicker`.
631    pub fn value_picker(self) -> Self {
632        self.role(cranpose_foundation::SemanticsWidgetRole::ValuePicker)
633    }
634
635    /// Says what this control does when a screen reader asks it to open, and
636    /// marks it as closed right now: a reader offers "expand" and says the
637    /// control is collapsed. Compose's
638    /// `Modifier.semantics { expand { … } }`.
639    pub fn expand(self, action: impl Fn() -> bool + 'static) -> Self {
640        let action = cranpose_foundation::SemanticsExpand::new(action);
641        self.stable_semantics(move |config| config.expand = Some(action.clone()))
642    }
643
644    /// Says what a long press on this control does, so a screen reader can
645    /// ask for it and read the label out first: "Remove receipt". Compose's
646    /// `Modifier.semantics { onLongClick("Remove receipt") { … } }`, which
647    /// `Modifier.combinedClickable(onLongClickLabel = …)` fills in for a
648    /// control that takes a long press from a finger too.
649    ///
650    /// The label is asked for, not optional as in Compose: Android is the one
651    /// platform of the four with a long press of its own, and the other three
652    /// list the action by name, so a nameless one reads as nothing.
653    pub fn on_long_click(
654        self,
655        label: impl Into<String>,
656        action: impl Fn() -> bool + 'static,
657    ) -> Self {
658        let label = label.into();
659        let action = cranpose_foundation::SemanticsLongClick::new(action);
660        self.stable_semantics(move |config| {
661            config.on_long_click_label = Some(label.clone());
662            config.on_long_click = Some(action.clone());
663        })
664    }
665
666    /// Says what this control does on VoiceOver's magic tap, the two finger
667    /// double tap for the main action of a screen, and the verb phrase the
668    /// other platforms list it under: "Take the photo". SwiftUI's
669    /// `accessibilityAction(.magicTap)`.
670    pub fn on_magic_tap(
671        self,
672        label: impl Into<String>,
673        action: impl Fn() -> bool + 'static,
674    ) -> Self {
675        let label = label.into();
676        let action = cranpose_foundation::SemanticsMagicTap::new(action);
677        self.stable_semantics(move |config| {
678            config.on_magic_tap_label = Some(label.clone());
679            config.on_magic_tap = Some(action.clone());
680        })
681    }
682
683    /// The short names a person says to Voice Control to reach this control,
684    /// when the name a reader hears is too long to say. SwiftUI's
685    /// `accessibilityInputLabels`.
686    pub fn input_labels<S: Into<String>>(self, labels: impl IntoIterator<Item = S>) -> Self {
687        let labels: Vec<String> = labels.into_iter().map(Into::into).collect();
688        self.stable_semantics(move |config| config.input_labels.clone_from(&labels))
689    }
690
691    /// The language of this control's text, as a BCP 47 tag such as "de" or
692    /// "pt-BR", so a reader picks the right voice. SwiftUI's
693    /// `accessibilityLanguage`, ARIA's `lang`.
694    pub fn language(self, tag: impl Into<String>) -> Self {
695        let tag = tag.into();
696        self.stable_semantics(move |config| config.language = Some(tag.clone()))
697    }
698
699    /// Says what this control does when a screen reader asks it to close, and
700    /// marks it as open right now. Compose's
701    /// `Modifier.semantics { collapse { … } }`.
702    pub fn collapse(self, action: impl Fn() -> bool + 'static) -> Self {
703        let action = cranpose_foundation::SemanticsExpand::new(action);
704        self.stable_semantics(move |config| config.collapse = Some(action.clone()))
705    }
706
707    /// Says what this control does when a screen reader asks to send it away:
708    /// a row a sighted person swipes off, a sheet a sighted person taps
709    /// outside of. A reader that cannot make the gesture gets the same way
710    /// out. Compose's `Modifier.semantics { dismiss { … } }`.
711    pub fn dismiss(self, action: impl Fn() -> bool + 'static) -> Self {
712        let action = cranpose_foundation::SemanticsDismiss::new(action);
713        self.stable_semantics(move |config| config.dismiss = Some(action.clone()))
714    }
715
716    /// Says what this list does when a screen reader asks for the row at an
717    /// index, so a reader reaches row 300 of a long list at once instead of
718    /// paging to it. The index counts rows from zero and the answer says
719    /// whether the list moved. `LazyColumn` and `LazyRow` declare it on their
720    /// own. Compose's `Modifier.semantics { scrollToIndex { … } }`.
721    pub fn scroll_to_index(self, action: impl Fn(usize) -> bool + 'static) -> Self {
722        let action = cranpose_foundation::SemanticsScrollToIndex::new(action);
723        self.stable_semantics(move |config| config.scroll_to_index = Some(action.clone()))
724    }
725
726    /// Moves this node in the order a screen reader visits the nodes beside
727    /// it: a smaller number comes first, and a node left alone keeps the
728    /// order the app laid it out in. A search field drawn last but meant to
729    /// be read first takes a negative number. Compose's
730    /// `Modifier.semantics { traversalIndex = -1f }`.
731    pub fn traversal_index(self, index: f32) -> Self {
732        self.stable_semantics(move |config| config.traversal_index = index)
733    }
734
735    /// Marks a field as one that holds a secret, so no screen reader reads
736    /// its text out: a reader hears the name the app gave the field, and
737    /// "password" in place of the text. Compose's
738    /// `Modifier.semantics { password() }`.
739    pub fn password(self) -> Self {
740        self.stable_semantics(|config| config.password = true)
741    }
742
743    /// Says why the control's content is wrong, so a screen reader reads
744    /// "invalid, the amount needs a number" after the control's state.
745    /// Compose's `Modifier.semantics { error("...") }`.
746    pub fn error(self, message: impl Into<String>) -> Self {
747        let message = message.into();
748        self.stable_semantics(move |config| config.error = Some(message.clone()))
749    }
750
751    /// Names the screen or pane this node is the root of, so a screen reader
752    /// hears where it is when the app moves on: "Library" as the library
753    /// opens. Compose's `Modifier.semantics { paneTitle = "..." }`.
754    pub fn pane_title(self, title: impl Into<String>) -> Self {
755        let title = title.into();
756        self.stable_semantics(move |config| config.pane_title = Some(title.clone()))
757    }
758
759    /// Makes the selectable controls under this node one group, so a screen
760    /// reader says which of how many a tab or a radio button is: "Library,
761    /// tab, 2 of 5". `LiquidTabBar` declares it on its own. Compose's
762    /// `Modifier.selectableGroup()`.
763    pub fn selectable_group(self) -> Self {
764        self.stable_semantics(|config| config.selectable_group = true)
765    }
766
767    /// Makes a screen reader take this node and the text under it as one
768    /// stop, the way it does for a button: a row whose name, count and price
769    /// belong together reads as "Milk, 2, 3.40" and not as three stops.
770    /// Compose's `Modifier.semantics(mergeDescendants = true) {}`.
771    pub fn merge_descendants(self) -> Self {
772        self.stable_semantics(|config| config.merge_descendants = true)
773    }
774
775    /// Takes this node and everything under it out of what a screen reader
776    /// sees: a decorative image, or a placeholder drawn under a field that
777    /// carries the same words as its name. Compose's
778    /// `semantics { hideFromAccessibility() }`.
779    pub fn hide_from_accessibility(self) -> Self {
780        self.stable_semantics(|config| config.hidden = true)
781    }
782
783    /// Marks this component as a heading, so a screen reader lists it among
784    /// the headings of the screen and a person can jump between them.
785    ///
786    /// This is Compose's `Modifier.semantics { heading() }`.
787    pub fn heading(self) -> Self {
788        self.role(cranpose_foundation::SemanticsWidgetRole::Header)
789    }
790
791    /// Tells a screen reader what kind of control this is, when the widget
792    /// does not say so on its own.
793    ///
794    /// This is Compose's `Modifier.semantics { role = Role.Button }`.
795    pub fn role(self, role: cranpose_foundation::SemanticsWidgetRole) -> Self {
796        self.stable_semantics(move |config| config.role = Some(role))
797    }
798
799    /// Makes a screen reader read this component's text out whenever it
800    /// changes, without the reader's cursor on it: a status line, a toast, a
801    /// count that moves.
802    ///
803    /// This is Compose's `Modifier.semantics { liveRegion = LiveRegionMode.Polite }`.
804    pub fn live_region(self, mode: cranpose_foundation::LiveRegionMode) -> Self {
805        self.stable_semantics(move |config| config.live_region = Some(mode))
806    }
807
808    /// Gives this component the text a screen reader reads for it, for a
809    /// drawing, an icon or a control with no text of its own.
810    ///
811    /// This is Compose's `Modifier.semantics { contentDescription = "..." }`.
812    pub fn content_description(self, description: impl Into<String>) -> Self {
813        let description = description.into();
814        self.stable_semantics(move |config| config.content_description = Some(description.clone()))
815    }
816
817    /// Makes this component focusable.
818    ///
819    /// This adds a focus target node that can receive focus and participate
820    /// in focus traversal. The component will be included in tab order and
821    /// can be focused programmatically.
822    pub fn focus_target(self) -> Self {
823        let element = FocusTargetElement::new();
824        let modifier = Modifier::from_parts(&[modifier_element(element)]);
825        self.then(modifier)
826    }
827
828    /// Makes this component focusable with a callback for focus changes.
829    ///
830    /// The callback is invoked whenever the focus state changes, allowing
831    /// components to react to gaining or losing focus.
832    pub fn on_focus_changed<F>(self, callback: F) -> Self
833    where
834        F: Fn(FocusState) + 'static,
835    {
836        let element = FocusTargetElement::with_callback(callback);
837        let modifier = Modifier::from_parts(&[modifier_element(element)]);
838        self.then(modifier)
839    }
840
841    /// Binds a [`FocusRequester`] to this node, so an app can move focus onto
842    /// it imperatively with [`FocusRequester::request_focus`].
843    ///
844    /// Pair it with [`focus_target`](Self::focus_target) (or
845    /// [`on_focus_changed`](Self::on_focus_changed)) on the same node —
846    /// `request_focus` moves whichever focus targets are attached there.
847    pub fn focus_requester(self, requester: &FocusRequester) -> Self {
848        let element = FocusRequesterElement::new(requester.clone());
849        let modifier = Modifier::from_parts(&[modifier_element(element)]);
850        self.then(modifier)
851    }
852
853    /// Binds a [`SemanticsRequester`] to this node, so an app can mark the
854    /// node's semantics for re-collection without recomposing or laying out.
855    ///
856    /// Pair it with [`semantics`](Self::semantics) on the same node when the
857    /// recorder reads state the composition does not observe — app state behind
858    /// a `RefCell`, a game's own model — which is the case a recorder cannot
859    /// signal for itself.
860    pub fn semantics_requester(self, requester: &SemanticsRequester) -> Self {
861        let element = SemanticsRequesterElement::new(requester.clone());
862        let modifier = Modifier::from_parts(&[modifier_element(element)]);
863        self.then(modifier)
864    }
865
866    /// Enables debug logging for this modifier chain.
867    ///
868    /// When enabled, logs the entire modifier chain structure including:
869    /// - Element types and their properties
870    /// - Inspector metadata
871    /// - Capability flags
872    ///
873    /// This is useful for debugging modifier composition issues and understanding
874    /// how the modifier chain is structured at runtime.
875    ///
876    /// Example:
877    /// ```text
878    /// Modifier::empty()
879    ///     .padding(8.0)
880    ///     .background(Color(1.0, 0.0, 0.0, 1.0))
881    ///     .debug_chain("MyWidget")
882    /// ```
883    pub fn debug_chain(self, tag: &'static str) -> Self {
884        use cranpose_foundation::{ModifierNode, ModifierNodeContext, NodeCapabilities, NodeState};
885
886        #[derive(Clone)]
887        struct DebugChainElement {
888            tag: &'static str,
889        }
890
891        impl fmt::Debug for DebugChainElement {
892            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
893                f.debug_struct("DebugChainElement")
894                    .field("tag", &self.tag)
895                    .finish()
896            }
897        }
898
899        impl PartialEq for DebugChainElement {
900            fn eq(&self, other: &Self) -> bool {
901                self.tag == other.tag
902            }
903        }
904
905        impl Eq for DebugChainElement {}
906
907        impl std::hash::Hash for DebugChainElement {
908            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
909                self.tag.hash(state);
910            }
911        }
912
913        impl ModifierNodeElement for DebugChainElement {
914            type Node = DebugChainNode;
915
916            fn create(&self) -> Self::Node {
917                DebugChainNode::new(self.tag)
918            }
919
920            fn update(&self, node: &mut Self::Node) {
921                node.tag = self.tag;
922            }
923
924            fn capabilities(&self) -> NodeCapabilities {
925                NodeCapabilities::empty()
926            }
927        }
928
929        struct DebugChainNode {
930            tag: &'static str,
931            state: NodeState,
932        }
933
934        impl DebugChainNode {
935            fn new(tag: &'static str) -> Self {
936                Self {
937                    tag,
938                    state: NodeState::new(),
939                }
940            }
941        }
942
943        impl ModifierNode for DebugChainNode {
944            fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {
945                eprintln!("[debug_chain:{}] Modifier chain attached", self.tag);
946            }
947
948            fn on_detach(&mut self) {
949                eprintln!("[debug_chain:{}] Modifier chain detached", self.tag);
950            }
951
952            fn on_reset(&mut self) {
953                eprintln!("[debug_chain:{}] Modifier chain reset", self.tag);
954            }
955        }
956
957        impl cranpose_foundation::DelegatableNode for DebugChainNode {
958            fn node_state(&self) -> &NodeState {
959                &self.state
960            }
961        }
962
963        let element = DebugChainElement { tag };
964        let modifier = Modifier::from_parts(&[modifier_element(element)]);
965        self.then(modifier)
966            .with_inspector_metadata(inspector_metadata("debugChain", move |info| {
967                info.add_property("tag", tag);
968            }))
969    }
970
971    /// This modifier followed by `next`.
972    ///
973    /// It takes `self`: a modifier built link by link owns its elements
974    /// alone, so each link joins them in place instead of copying the chain.
975    /// Clone a modifier first to keep using it; the join then copies it.
976    pub fn then(self, next: Modifier) -> Modifier {
977        let Modifier {
978            kind,
979            strict_fingerprint,
980            structural_fingerprint,
981            element_count,
982            provides_composition_locals,
983        } = self;
984        let ModifierKind::Single {
985            mut elements,
986            inspector,
987        } = kind
988        else {
989            return next;
990        };
991        let ModifierKind::Single {
992            elements: next_elements,
993            inspector: next_inspector,
994        } = &next.kind
995        else {
996            return Modifier {
997                kind: ModifierKind::Single {
998                    elements,
999                    inspector,
1000                },
1001                strict_fingerprint,
1002                structural_fingerprint,
1003                element_count,
1004                provides_composition_locals,
1005            };
1006        };
1007
1008        let fingerprints = append_fingerprints(
1009            ModifierFingerprints {
1010                strict: strict_fingerprint,
1011                structural: structural_fingerprint,
1012            },
1013            next_elements,
1014        );
1015        elements.extend_from(next_elements);
1016        let inspector = if next_inspector.is_none() {
1017            inspector
1018        } else {
1019            merged_inspector(inspector_slice(&inspector), inspector_slice(next_inspector))
1020        };
1021        Modifier {
1022            kind: ModifierKind::Single {
1023                elements,
1024                inspector,
1025            },
1026            strict_fingerprint: fingerprints.strict,
1027            structural_fingerprint: fingerprints.structural,
1028            element_count: element_count + next.element_count,
1029            provides_composition_locals: provides_composition_locals
1030                || next.provides_composition_locals,
1031        }
1032    }
1033
1034    pub(crate) fn iter_elements(&self) -> ModifierElementIterator<'_> {
1035        match &self.kind {
1036            ModifierKind::Empty => ModifierElementIterator { inner: [].iter() },
1037            ModifierKind::Single { elements, .. } => ModifierElementIterator {
1038                inner: elements.iter(),
1039            },
1040        }
1041    }
1042
1043    pub(crate) fn provided_composition_locals(&self) -> Vec<ProvidedValue> {
1044        if !self.provides_composition_locals {
1045            return Vec::new();
1046        }
1047        self.iter_elements()
1048            .flat_map(|element| element.provided_composition_locals())
1049            .collect()
1050    }
1051
1052    pub(crate) fn iter_inspector_metadata(&self) -> ModifierInspectorIterator<'_> {
1053        match &self.kind {
1054            ModifierKind::Empty => ModifierInspectorIterator { inner: [].iter() },
1055            ModifierKind::Single { inspector, .. } => ModifierInspectorIterator {
1056                inner: inspector_slice(inspector).iter(),
1057            },
1058        }
1059    }
1060
1061    #[cfg(test)]
1062    pub(crate) fn elements(&self) -> Vec<DynModifierElement> {
1063        match &self.kind {
1064            ModifierKind::Empty => Vec::new(),
1065            ModifierKind::Single { elements, .. } => elements.to_vec(),
1066        }
1067    }
1068
1069    pub(crate) fn inspector_metadata(&self) -> Vec<InspectorMetadata> {
1070        match &self.kind {
1071            ModifierKind::Empty => Vec::new(),
1072            ModifierKind::Single { inspector, .. } => {
1073                inspector.as_deref().map(<[_]>::to_vec).unwrap_or_default()
1074            }
1075        }
1076    }
1077
1078    pub(crate) fn rehouse_for_live_compaction(&self) -> Self {
1079        match &self.kind {
1080            ModifierKind::Empty => Self::default(),
1081            ModifierKind::Single {
1082                elements,
1083                inspector,
1084            } => Self {
1085                kind: ModifierKind::Single {
1086                    elements: Elements::from_slice(elements),
1087                    inspector: inspector
1088                        .as_ref()
1089                        .map(|inspector| inspector.iter().cloned().collect()),
1090                },
1091                strict_fingerprint: self.strict_fingerprint,
1092                structural_fingerprint: self.structural_fingerprint,
1093                element_count: self.element_count,
1094                provides_composition_locals: self.provides_composition_locals,
1095            },
1096        }
1097    }
1098
1099    pub fn total_padding(&self) -> f32 {
1100        let padding = self.padding_values();
1101        padding
1102            .left
1103            .max(padding.right)
1104            .max(padding.top)
1105            .max(padding.bottom)
1106    }
1107
1108    pub fn explicit_size(&self) -> Option<Size> {
1109        let props = self.layout_properties();
1110        match (props.width, props.height) {
1111            (DimensionConstraint::Points(width), DimensionConstraint::Points(height)) => {
1112                Some(Size { width, height })
1113            }
1114            _ => None,
1115        }
1116    }
1117
1118    pub fn padding_values(&self) -> EdgeInsets {
1119        self.resolved_modifiers().padding()
1120    }
1121
1122    pub(crate) fn layout_properties(&self) -> LayoutProperties {
1123        self.resolved_modifiers().layout_properties()
1124    }
1125
1126    pub fn box_alignment(&self) -> Option<Alignment> {
1127        self.layout_properties().box_alignment()
1128    }
1129
1130    pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
1131        self.layout_properties().column_alignment()
1132    }
1133
1134    pub fn row_alignment(&self) -> Option<VerticalAlignment> {
1135        self.layout_properties().row_alignment()
1136    }
1137
1138    pub fn draw_commands(&self) -> Vec<DrawCommand> {
1139        collect_slices_from_modifier(self).draw_commands().to_vec()
1140    }
1141
1142    pub fn clips_to_bounds(&self) -> bool {
1143        collect_slices_from_modifier(self).clip_to_bounds()
1144    }
1145
1146    /// Returns structured inspector records for each modifier element.
1147    pub fn collect_inspector_records(&self) -> Vec<ModifierInspectorRecord> {
1148        self.inspector_metadata()
1149            .iter()
1150            .map(InspectorMetadata::to_record)
1151            .collect()
1152    }
1153
1154    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
1155        let mut handle = ModifierChainHandle::new();
1156        let _ = handle.update(self);
1157        handle.resolved_modifiers()
1158    }
1159
1160    /// A modifier of the one `element`. Platform crates build their own
1161    /// modifiers on it, the way [`Modifier::window_root`] is built.
1162    pub fn with_element<E>(element: E) -> Self
1163    where
1164        E: ModifierNodeElement,
1165    {
1166        Self::from_parts(&[modifier_element(element)])
1167    }
1168
1169    /// A modifier of `elements`, held in one shared allocation.
1170    pub(crate) fn from_parts(elements: &[DynModifierElement]) -> Self {
1171        if elements.is_empty() {
1172            Self::default()
1173        } else {
1174            let element_count = elements.len();
1175            let provides_composition_locals = elements
1176                .iter()
1177                .any(|element| element.provides_composition_locals());
1178            let fingerprints = single_fingerprints(elements);
1179            Self {
1180                kind: ModifierKind::Single {
1181                    elements: Elements::from_slice(elements),
1182                    inspector: None,
1183                },
1184                strict_fingerprint: fingerprints.strict,
1185                structural_fingerprint: fingerprints.structural,
1186                element_count,
1187                provides_composition_locals,
1188            }
1189        }
1190    }
1191
1192    pub(crate) fn with_inspector_metadata(self, metadata: InspectorMetadata) -> Self {
1193        if metadata.is_empty() {
1194            return self;
1195        }
1196        match self.kind {
1197            ModifierKind::Empty => self,
1198            ModifierKind::Single {
1199                elements,
1200                inspector,
1201            } => {
1202                let new_inspector = inspector_slice(&inspector)
1203                    .iter()
1204                    .cloned()
1205                    .chain(std::iter::once(metadata))
1206                    .collect();
1207                Self {
1208                    kind: ModifierKind::Single {
1209                        elements,
1210                        inspector: Some(new_inspector),
1211                    },
1212                    strict_fingerprint: self.strict_fingerprint,
1213                    structural_fingerprint: self.structural_fingerprint,
1214                    element_count: self.element_count,
1215                    provides_composition_locals: self.provides_composition_locals,
1216                }
1217            }
1218        }
1219    }
1220
1221    /// Checks whether two modifiers are structurally equivalent for layout decisions.
1222    ///
1223    /// This ignores identity-sensitive modifier elements (e.g., draw closures) so
1224    /// draw-only updates do not force measure/layout invalidation.
1225    pub fn structural_eq(&self, other: &Self) -> bool {
1226        self.eq_internal(other, false)
1227    }
1228
1229    fn eq_internal(&self, other: &Self, consider_always_update: bool) -> bool {
1230        if self.element_count != other.element_count {
1231            return false;
1232        }
1233        if consider_always_update {
1234            if self.strict_fingerprint != other.strict_fingerprint {
1235                return false;
1236            }
1237        } else if self.structural_fingerprint != other.structural_fingerprint {
1238            return false;
1239        }
1240
1241        match (&self.kind, &other.kind) {
1242            (ModifierKind::Empty, ModifierKind::Empty) => true,
1243            (
1244                ModifierKind::Single {
1245                    elements: e1,
1246                    inspector: _,
1247                },
1248                ModifierKind::Single {
1249                    elements: e2,
1250                    inspector: _,
1251                },
1252            ) => {
1253                if e1.shares_storage(e2) {
1254                    return true;
1255                }
1256
1257                if e1.len() != e2.len() {
1258                    return false;
1259                }
1260
1261                for (a, b) in e1.iter().zip(e2.iter()) {
1262                    if !consider_always_update
1263                        && a.element_type() == b.element_type()
1264                        && a.capabilities() == NodeCapabilities::DRAW
1265                        && b.capabilities() == NodeCapabilities::DRAW
1266                    {
1267                        continue;
1268                    }
1269
1270                    if consider_always_update && (a.requires_update() || b.requires_update()) {
1271                        if !Rc::ptr_eq(a, b) {
1272                            return false;
1273                        }
1274                        continue;
1275                    }
1276
1277                    if !a.equals_element(&**b) {
1278                        return false;
1279                    }
1280                }
1281
1282                true
1283            }
1284            _ => false,
1285        }
1286    }
1287}
1288
1289impl PartialEq for Modifier {
1290    fn eq(&self, other: &Self) -> bool {
1291        self.eq_internal(other, true)
1292    }
1293}
1294
1295impl Eq for Modifier {}
1296
1297impl fmt::Display for Modifier {
1298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1299        match &self.kind {
1300            ModifierKind::Empty => write!(f, "Modifier.empty"),
1301            ModifierKind::Single { elements, .. } => {
1302                if elements.is_empty() {
1303                    return write!(f, "Modifier.empty");
1304                }
1305                write!(f, "Modifier[")?;
1306                for (index, element) in elements.iter().enumerate() {
1307                    if index > 0 {
1308                        write!(f, ", ")?;
1309                    }
1310                    let name = element.inspector_name();
1311                    let mut properties = Vec::new();
1312                    element.record_inspector_properties(&mut |prop, value| {
1313                        properties.push(format!("{prop}={value}"));
1314                    });
1315                    if properties.is_empty() {
1316                        write!(f, "{name}")?;
1317                    } else {
1318                        write!(f, "{name}({})", properties.join(", "))?;
1319                    }
1320                }
1321                write!(f, "]")
1322            }
1323        }
1324    }
1325}
1326
1327impl fmt::Debug for Modifier {
1328    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1329        fmt::Display::fmt(self, f)
1330    }
1331}
1332
1333#[derive(Clone, Copy, Debug, PartialEq)]
1334pub struct ResolvedBackground {
1335    color: Color,
1336    shape: Option<RoundedCornerShape>,
1337}
1338
1339impl ResolvedBackground {
1340    pub fn new(color: Color, shape: Option<RoundedCornerShape>) -> Self {
1341        Self { color, shape }
1342    }
1343
1344    pub fn color(&self) -> Color {
1345        self.color
1346    }
1347
1348    pub fn shape(&self) -> Option<RoundedCornerShape> {
1349        self.shape
1350    }
1351
1352    pub fn set_shape(&mut self, shape: Option<RoundedCornerShape>) {
1353        self.shape = shape;
1354    }
1355}
1356
1357#[derive(Clone, Copy, Debug, PartialEq, Default)]
1358pub struct ResolvedModifiers {
1359    padding: EdgeInsets,
1360    layout: LayoutProperties,
1361    offset: Point,
1362}
1363
1364impl ResolvedModifiers {
1365    pub fn padding(&self) -> EdgeInsets {
1366        self.padding
1367    }
1368
1369    pub fn layout_properties(&self) -> LayoutProperties {
1370        self.layout
1371    }
1372
1373    pub fn offset(&self) -> Point {
1374        self.offset
1375    }
1376
1377    pub(crate) fn set_padding(&mut self, padding: EdgeInsets) {
1378        self.padding = padding;
1379    }
1380
1381    pub(crate) fn set_layout_properties(&mut self, layout: LayoutProperties) {
1382        self.layout = layout;
1383    }
1384
1385    pub(crate) fn set_offset(&mut self, offset: Point) {
1386        self.offset = offset;
1387    }
1388
1389    /// These modifiers with every length on a whole device pixel of
1390    /// `density`, as Compose's `roundToPx` puts padding, sizes and offsets.
1391    pub(crate) fn on_device_grid(&self, density: f32) -> Self {
1392        use cranpose_ui_layout::round_to_px;
1393
1394        let round = |value: Option<f32>| value.map(|value| round_to_px(value, density));
1395        let dimension = |dimension: DimensionConstraint| match dimension {
1396            DimensionConstraint::Points(value) => {
1397                DimensionConstraint::Points(round_to_px(value, density))
1398            }
1399            other => other,
1400        };
1401        Self {
1402            padding: crate::modifier_nodes::device_padding(self.padding, density),
1403            layout: LayoutProperties {
1404                padding: crate::modifier_nodes::device_padding(self.layout.padding, density),
1405                width: dimension(self.layout.width),
1406                height: dimension(self.layout.height),
1407                min_width: round(self.layout.min_width),
1408                min_height: round(self.layout.min_height),
1409                max_width: round(self.layout.max_width),
1410                max_height: round(self.layout.max_height),
1411                ..self.layout
1412            },
1413            offset: Point {
1414                x: round_to_px(self.offset.x, density),
1415                y: round_to_px(self.offset.y, density),
1416            },
1417        }
1418    }
1419}
1420
1421#[derive(Clone, Copy, Debug, Default, PartialEq)]
1422pub enum DimensionConstraint {
1423    #[default]
1424    Unspecified,
1425    Points(f32),
1426    Fraction(f32),
1427    Intrinsic(IntrinsicSize),
1428}
1429
1430#[derive(Clone, Copy, Debug, Default, PartialEq)]
1431pub struct LayoutWeight {
1432    pub weight: f32,
1433    pub fill: bool,
1434}
1435
1436#[derive(Clone, Copy, Debug, Default, PartialEq)]
1437pub struct LayoutProperties {
1438    padding: EdgeInsets,
1439    width: DimensionConstraint,
1440    height: DimensionConstraint,
1441    min_width: Option<f32>,
1442    min_height: Option<f32>,
1443    max_width: Option<f32>,
1444    max_height: Option<f32>,
1445    weight: Option<LayoutWeight>,
1446    box_alignment: Option<Alignment>,
1447    column_alignment: Option<HorizontalAlignment>,
1448    row_alignment: Option<VerticalAlignment>,
1449}
1450
1451impl LayoutProperties {
1452    pub fn padding(&self) -> EdgeInsets {
1453        self.padding
1454    }
1455
1456    pub fn width(&self) -> DimensionConstraint {
1457        self.width
1458    }
1459
1460    pub fn height(&self) -> DimensionConstraint {
1461        self.height
1462    }
1463
1464    pub fn min_width(&self) -> Option<f32> {
1465        self.min_width
1466    }
1467
1468    pub fn min_height(&self) -> Option<f32> {
1469        self.min_height
1470    }
1471
1472    pub fn max_width(&self) -> Option<f32> {
1473        self.max_width
1474    }
1475
1476    pub fn max_height(&self) -> Option<f32> {
1477        self.max_height
1478    }
1479
1480    pub fn weight(&self) -> Option<LayoutWeight> {
1481        self.weight
1482    }
1483
1484    pub fn box_alignment(&self) -> Option<Alignment> {
1485        self.box_alignment
1486    }
1487
1488    pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
1489        self.column_alignment
1490    }
1491
1492    pub fn row_alignment(&self) -> Option<VerticalAlignment> {
1493        self.row_alignment
1494    }
1495}
1496
1497#[cfg(test)]
1498#[path = "tests/modifier_tests.rs"]
1499mod tests;