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 border;
21mod chain;
22mod clickable;
23mod coordinator_geometry;
24mod drag_and_drop;
25mod draw_cache;
26mod fill;
27mod focus;
28mod focus_ring;
29mod graphics_layer;
30mod key_input;
31mod local;
32mod minimum_interactive;
33mod offset;
34mod padding;
35mod pointer_icon;
36pub(crate) mod pointer_input;
37mod rotary_input;
38mod scroll;
39mod selectable;
40mod semantics;
41mod shadow;
42mod size;
43mod slices;
44mod toggleable;
45mod weight;
46mod window_root;
47mod wrap_content;
48
49pub use chain::{ModifierChainHandle, ModifierChainInspectorNode, ModifierLocalsHandle};
50pub(crate) use coordinator_geometry::{CoordinatorGeometry, CoordinatorRect};
51pub use cranpose_foundation::{
52    AnyModifierElement, DynModifierElement, FocusState, PointerEvent, PointerEventKind,
53    PointerSource, RotaryScrollEvent, SemanticsConfiguration, modifier_element,
54};
55use cranpose_foundation::{ModifierNodeElement, NodeCapabilities, ProgressBarRangeInfo};
56#[expect(unused_imports)]
57pub use cranpose_ui_graphics::{
58    BlendMode, BlurredEdgeTreatment, Brush, Color, ColorFilter, CompositingStrategy, CornerRadii,
59    CursorIcon, CustomPointerIcon, CutDirection, Dp, DpOffset, EdgeInsets, GradientCutMaskSpec,
60    GradientFadeMaskSpec, GraphicsLayer, LayerShape, Point, PointerIcon, PointerIconError, Rect,
61    RenderEffect, RoundedCornerShape, RuntimeShader, Shadow, ShadowScope, Size, TransformOrigin,
62};
63use cranpose_ui_layout::{Alignment, HorizontalAlignment, IntrinsicSize, VerticalAlignment};
64pub use drag_and_drop::{
65    DragAndDropEvent, DragAndDropOutcome, DragAndDropPayload, DragAndDropPoint, DragAndDropSource,
66    DragAndDropSourceElement, DragAndDropSourceNode, DragAndDropState, DragAndDropTarget,
67    DragAndDropTargetElement, DragAndDropTargetNode,
68};
69use focus::FocusTargetElement;
70pub use focus::{FocusDirection, FocusRequestError, FocusRequester, FocusRequesterElement};
71pub use graphics_layer::GlassMaterial;
72pub use key_input::KeyEventRoute;
73pub(crate) use local::{
74    ModifierLocalAncestorResolver, ModifierLocalSource, ModifierLocalToken, ResolvedModifierLocal,
75};
76use local::{ModifierLocalConsumerElement, ModifierLocalProviderElement};
77pub use local::{ModifierLocalKey, ModifierLocalReadScope};
78#[expect(unused_imports)]
79pub use pointer_input::{AwaitPointerEventScope, PointerInputScope};
80pub use rotary_input::RotaryInputModifierNode;
81#[cfg(test)]
82pub(crate) use scroll::lazy_scroll_semantics;
83#[cfg(feature = "test-helpers")]
84pub use scroll::{last_fling_velocity, reset_last_fling_velocity};
85use semantics::SemanticsElement;
86pub use semantics::{
87    SemanticsRequester, SemanticsRequesterElement, collect_semantics_from_chain,
88    collect_semantics_from_modifier, semantics_reach_of_chain,
89};
90pub(crate) use slices::collect_modifier_slices_into_shared;
91pub use slices::{
92    ModifierNodeSlices, ModifierNodeSlicesDebugStats, collect_modifier_slices,
93    collect_slices_from_modifier,
94};
95pub use window_root::{
96    WindowRootDescriptor, WindowRootElement, WindowRootEntry, WindowRootNode, WindowRootRegistry,
97    is_window_root, nearest_window_root, nearest_window_roots, window_roots, window_roots_revision,
98};
99
100pub use crate::draw::{DrawCacheBuilder, DrawCommand};
101use crate::modifier_nodes::ClipToBoundsElement;
102
103#[derive(Clone, Debug, Default)]
104pub struct InspectorInfo {
105    properties: Vec<InspectorProperty>,
106}
107
108impl InspectorInfo {
109    pub fn new() -> Self {
110        Self::default()
111    }
112
113    pub fn add_property<V: Into<String>>(&mut self, name: &'static str, value: V) {
114        self.properties.push(InspectorProperty {
115            name,
116            value: value.into(),
117        });
118    }
119
120    pub fn properties(&self) -> &[InspectorProperty] {
121        &self.properties
122    }
123
124    pub fn is_empty(&self) -> bool {
125        self.properties.is_empty()
126    }
127
128    pub fn add_dimension(&mut self, name: &'static str, constraint: DimensionConstraint) {
129        self.add_property(name, describe_dimension(constraint));
130    }
131
132    pub fn add_offset_components(
133        &mut self,
134        x_name: &'static str,
135        y_name: &'static str,
136        offset: Point,
137    ) {
138        self.add_property(x_name, offset.x.to_string());
139        self.add_property(y_name, offset.y.to_string());
140    }
141
142    pub fn add_alignment<A>(&mut self, name: &'static str, alignment: A)
143    where
144        A: fmt::Debug,
145    {
146        self.add_property(name, format!("{alignment:?}"));
147    }
148}
149
150#[derive(Clone, Debug, PartialEq)]
151pub struct InspectorProperty {
152    pub name: &'static str,
153    pub value: String,
154}
155
156#[derive(Clone, Debug, PartialEq)]
157pub struct ModifierInspectorRecord {
158    pub name: &'static str,
159    pub properties: Vec<InspectorProperty>,
160}
161
162#[derive(Clone, Debug)]
163pub(crate) struct InspectorMetadata {
164    name: &'static str,
165    info: InspectorInfo,
166}
167
168impl InspectorMetadata {
169    pub(crate) fn new<F>(name: &'static str, recorder: F) -> Self
170    where
171        F: FnOnce(&mut InspectorInfo),
172    {
173        let mut info = InspectorInfo::new();
174        recorder(&mut info);
175        Self { name, info }
176    }
177
178    fn is_empty(&self) -> bool {
179        self.info.is_empty()
180    }
181
182    fn to_record(&self) -> ModifierInspectorRecord {
183        ModifierInspectorRecord {
184            name: self.name,
185            properties: self.info.properties().to_vec(),
186        }
187    }
188}
189
190fn describe_dimension(constraint: DimensionConstraint) -> String {
191    match constraint {
192        DimensionConstraint::Unspecified => "unspecified".to_string(),
193        DimensionConstraint::Points(value) => value.to_string(),
194        DimensionConstraint::Fraction(value) => format!("fraction({value})"),
195        DimensionConstraint::Intrinsic(size) => format!("intrinsic({size:?})"),
196    }
197}
198
199fn inspector_slice(inspector: &Option<Rc<[InspectorMetadata]>>) -> &[InspectorMetadata] {
200    inspector.as_deref().unwrap_or(&[])
201}
202
203/// The inspector metadata of two modifiers joined, first `first`'s, or
204/// `None` when neither has any.
205fn merged_inspector(
206    first: &[InspectorMetadata],
207    second: &[InspectorMetadata],
208) -> Option<Rc<[InspectorMetadata]>> {
209    if first.is_empty() && second.is_empty() {
210        return None;
211    }
212    Some(first.iter().chain(second).cloned().collect())
213}
214
215pub(crate) fn inspector_metadata<F>(name: &'static str, recorder: F) -> InspectorMetadata
216where
217    F: FnOnce(&mut InspectorInfo),
218{
219    if !inspector_metadata_enabled() {
220        return InspectorMetadata::new(name, |_| {});
221    }
222    InspectorMetadata::new(name, recorder)
223}
224
225pub(crate) fn modifier_debug_enabled() -> bool {
226    #[cfg(not(target_arch = "wasm32"))]
227    {
228        cranpose_core::env_flag!("COMPOSE_DEBUG_MODIFIERS")
229    }
230    #[cfg(target_arch = "wasm32")]
231    {
232        false
233    }
234}
235
236fn inspector_metadata_enabled() -> bool {
237    cfg!(any(test, feature = "inspection")) || modifier_debug_enabled()
238}
239
240/// A modifier's elements: a chain of up to four lives inline in its one
241/// shared allocation.
242type ModifierElements = SmallVec<[DynModifierElement; 4]>;
243
244/// The elements of a non-empty modifier. A lone element, as each link of a
245/// builder chain starts out, is held as it is; joining it to more moves the
246/// chain into one shared allocation, which the chain's later links extend
247/// in place while it is the only owner.
248#[derive(Clone)]
249enum Elements {
250    One(DynModifierElement),
251    Many(Rc<ModifierElements>),
252}
253
254impl Elements {
255    fn from_slice(elements: &[DynModifierElement]) -> Self {
256        match elements {
257            [element] => Self::One(element.clone()),
258            _ => Self::Many(Rc::new(elements.iter().cloned().collect())),
259        }
260    }
261
262    fn extend_from(&mut self, more: &[DynModifierElement]) {
263        match self {
264            Self::Many(shared) => match Rc::get_mut(shared) {
265                Some(owned) => owned.extend(more.iter().cloned()),
266                None => *shared = Rc::new(shared.iter().chain(more).cloned().collect()),
267            },
268            Self::One(first) => {
269                *self = Self::Many(Rc::new(
270                    std::iter::once(&*first).chain(more).cloned().collect(),
271                ));
272            }
273        }
274    }
275
276    /// Whether both are the same storage, so equal without comparing.
277    fn shares_storage(&self, other: &Self) -> bool {
278        match (self, other) {
279            (Self::One(a), Self::One(b)) => Rc::ptr_eq(a, b),
280            (Self::Many(a), Self::Many(b)) => Rc::ptr_eq(a, b),
281            _ => false,
282        }
283    }
284}
285
286impl std::ops::Deref for Elements {
287    type Target = [DynModifierElement];
288
289    fn deref(&self) -> &[DynModifierElement] {
290        match self {
291            Self::One(element) => std::slice::from_ref(element),
292            Self::Many(elements) => elements.as_slice(),
293        }
294    }
295}
296
297#[derive(Clone)]
298enum ModifierKind {
299    Empty,
300    Single {
301        elements: Elements,
302        /// `None` when no element records inspector metadata, which is
303        /// always outside tests and modifier debugging: no allocation.
304        inspector: Option<Rc<[InspectorMetadata]>>,
305    },
306}
307
308const FINGERPRINT_KIND_EMPTY: u8 = 0;
309const FINGERPRINT_KIND_SINGLE: u8 = 1;
310
311const FINGERPRINT_EMPTY_STRICT_SEED: u64 = 0x243f_6a88_85a3_08d3;
312const FINGERPRINT_EMPTY_STRUCTURAL_SEED: u64 = 0x1319_8a2e_0370_7344;
313const FINGERPRINT_SINGLE_STRICT_SEED: u64 = 0xa409_3822_299f_31d0;
314const FINGERPRINT_SINGLE_STRUCTURAL_SEED: u64 = 0x082e_fa98_ec4e_6c89;
315const FINGERPRINT_SEQUENCE_MUL: u64 = 0x9e37_79b1_85eb_ca87;
316const FINGERPRINT_STRICT_UPDATE_TAG: u64 = 0xdbe6_d5d5_fe4c_ce2f;
317const FINGERPRINT_STRUCTURAL_DRAW_ONLY_TAG: u64 = 0x94d0_49bb_1331_11eb;
318
319#[derive(Clone, Copy, Debug, PartialEq, Eq)]
320struct ModifierFingerprints {
321    strict: u64,
322    structural: u64,
323}
324
325#[inline]
326fn mix_fingerprint_bits(mut value: u64) -> u64 {
327    value ^= value >> 33;
328    value = value.wrapping_mul(0xff51_afd7_ed55_8ccd);
329    value ^= value >> 33;
330    value = value.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
331    value ^ (value >> 33)
332}
333
334#[inline]
335fn fold_fingerprint(state: u64, value: u64) -> u64 {
336    mix_fingerprint_bits(state ^ value.wrapping_add(FINGERPRINT_SEQUENCE_MUL))
337        .wrapping_mul(FINGERPRINT_SEQUENCE_MUL)
338}
339
340#[inline]
341fn empty_fingerprints() -> ModifierFingerprints {
342    ModifierFingerprints {
343        strict: fold_fingerprint(FINGERPRINT_EMPTY_STRICT_SEED, FINGERPRINT_KIND_EMPTY as u64),
344        structural: fold_fingerprint(
345            FINGERPRINT_EMPTY_STRUCTURAL_SEED,
346            FINGERPRINT_KIND_EMPTY as u64,
347        ),
348    }
349}
350
351#[inline]
352fn single_fingerprint_seed() -> ModifierFingerprints {
353    let strict = fold_fingerprint(
354        FINGERPRINT_SINGLE_STRICT_SEED,
355        FINGERPRINT_KIND_SINGLE as u64,
356    );
357    let structural = fold_fingerprint(
358        FINGERPRINT_SINGLE_STRUCTURAL_SEED,
359        FINGERPRINT_KIND_SINGLE as u64,
360    );
361    ModifierFingerprints { strict, structural }
362}
363
364#[inline]
365fn element_common_fingerprint(element: &DynModifierElement) -> u64 {
366    let mut hasher = default::new();
367    element.element_type().hash(&mut hasher);
368    element.capabilities().bits().hash(&mut hasher);
369    hasher.finish()
370}
371
372#[inline]
373fn element_fingerprints(element: &DynModifierElement) -> ModifierFingerprints {
374    let common = element_common_fingerprint(element);
375    let requires_update = element.requires_update();
376    let strict_payload = if requires_update {
377        let element_ptr = Rc::as_ptr(element) as *const () as usize as u64;
378        element_ptr ^ FINGERPRINT_STRICT_UPDATE_TAG
379    } else {
380        element.hash_code()
381    };
382    let strict = mix_fingerprint_bits(common ^ strict_payload);
383
384    let is_draw_only = element.capabilities() == NodeCapabilities::DRAW;
385    let structural_payload = if is_draw_only {
386        FINGERPRINT_STRUCTURAL_DRAW_ONLY_TAG
387    } else {
388        element.hash_code()
389    };
390    let structural = mix_fingerprint_bits(common ^ structural_payload);
391
392    ModifierFingerprints { strict, structural }
393}
394
395#[inline]
396fn append_fingerprints(
397    mut fingerprints: ModifierFingerprints,
398    elements: &[DynModifierElement],
399) -> ModifierFingerprints {
400    for element in elements {
401        let element_fingerprints = element_fingerprints(element);
402        fingerprints.strict = fold_fingerprint(fingerprints.strict, element_fingerprints.strict);
403        fingerprints.structural =
404            fold_fingerprint(fingerprints.structural, element_fingerprints.structural);
405    }
406    fingerprints
407}
408
409fn single_fingerprints(elements: &[DynModifierElement]) -> ModifierFingerprints {
410    append_fingerprints(single_fingerprint_seed(), elements)
411}
412
413pub struct ModifierElementIterator<'a> {
414    inner: std::slice::Iter<'a, DynModifierElement>,
415}
416
417impl<'a> Iterator for ModifierElementIterator<'a> {
418    type Item = &'a DynModifierElement;
419
420    #[inline]
421    fn next(&mut self) -> Option<Self::Item> {
422        self.inner.next()
423    }
424
425    #[inline]
426    fn size_hint(&self) -> (usize, Option<usize>) {
427        self.inner.size_hint()
428    }
429}
430
431impl ExactSizeIterator for ModifierElementIterator<'_> {}
432
433pub(crate) struct ModifierInspectorIterator<'a> {
434    inner: std::slice::Iter<'a, InspectorMetadata>,
435}
436
437impl<'a> Iterator for ModifierInspectorIterator<'a> {
438    type Item = &'a InspectorMetadata;
439
440    #[inline]
441    fn next(&mut self) -> Option<Self::Item> {
442        self.inner.next()
443    }
444
445    #[inline]
446    fn size_hint(&self) -> (usize, Option<usize>) {
447        self.inner.size_hint()
448    }
449}
450
451impl ExactSizeIterator for ModifierInspectorIterator<'_> {}
452
453/// A modifier chain that can be applied to composable elements.
454///
455/// Modifiers allow you to decorate or augment a composable. Common operations include:
456/// - Adjusting layout (e.g., `padding`, `fill_max_size`)
457/// - Adding behavior (e.g., `clickable`, `scrollable`)
458/// - Drawing (e.g., `background`, `border`)
459///
460/// Modifiers are immutable and form a chain using the builder pattern.
461/// The order of modifiers matters: previous modifiers wrap subsequent ones.
462///
463/// # Example
464///
465/// ```rust,ignore
466/// Modifier::padding(16.0)     // Applied first (outer)
467///     .background(Color::Red) // Applied second
468///     .clickable(|| println!("Clicked")) // Applied last (inner)
469/// ```
470#[derive(Clone)]
471pub struct Modifier {
472    kind: ModifierKind,
473    strict_fingerprint: u64,
474    structural_fingerprint: u64,
475    element_count: usize,
476    provides_composition_locals: bool,
477}
478
479impl Default for Modifier {
480    fn default() -> Self {
481        let fingerprints = empty_fingerprints();
482        Self {
483            kind: ModifierKind::Empty,
484            strict_fingerprint: fingerprints.strict,
485            structural_fingerprint: fingerprints.structural,
486            element_count: 0,
487            provides_composition_locals: false,
488        }
489    }
490}
491
492impl Modifier {
493    pub fn empty() -> Self {
494        Self::default()
495    }
496
497    /// Creates a modifier from a custom modifier node element.
498    pub fn from_element<E>(element: E) -> Self
499    where
500        E: ModifierNodeElement,
501    {
502        Self::with_element(element)
503    }
504
505    /// Clip the content to the bounds of this modifier.
506    ///
507    /// Example: `Modifier::empty().clip_to_bounds()`
508    pub fn clip_to_bounds(self) -> Self {
509        thread_local! {
510            static CLIP_TO_BOUNDS: Modifier = Modifier::with_element(ClipToBoundsElement::new());
511        }
512        let modifier = CLIP_TO_BOUNDS.with(Clone::clone);
513        let modifier =
514            modifier.with_inspector_metadata(inspector_metadata("clipToBounds", |info| {
515                info.add_property("clipToBounds", "true");
516            }));
517        self.then(modifier)
518    }
519
520    pub fn modifier_local_provider<T, F>(self, key: ModifierLocalKey<T>, value: F) -> Self
521    where
522        T: 'static,
523        F: Fn() -> T + 'static,
524    {
525        let element = ModifierLocalProviderElement::new(key, value);
526        let modifier = Modifier::from_parts(&[modifier_element(element)]);
527        self.then(modifier)
528    }
529
530    pub fn modifier_local_consumer<F>(self, consumer: F) -> Self
531    where
532        F: for<'scope> Fn(&mut ModifierLocalReadScope<'scope>) + 'static,
533    {
534        let element = ModifierLocalConsumerElement::new(consumer);
535        let modifier = Modifier::from_parts(&[modifier_element(element)]);
536        self.then(modifier)
537    }
538
539    /// Says what a screen reader reads for this node, as one spec value.
540    ///
541    /// This is [`Modifier::semantics`] with a value in place of a closure.
542    /// Compose has only the closure form, because Kotlin's receiver lambda
543    /// makes `semantics { contentDescription = "Save" }` read well; Rust has
544    /// none, so a spec value reads better, costs one chain element rather
545    /// than one per property, and can be compared with another.
546    ///
547    /// Example:
548    /// `Modifier::empty().semantics_spec(SemanticsSpec::new().content_description("Amount").error("needs a number"))`
549    pub fn semantics_spec(self, spec: cranpose_foundation::SemanticsSpec) -> Self {
550        self.stable_semantics(move |config: &mut SemanticsConfiguration| config.merge(&spec))
551    }
552
553    /// Records what a screen reader reads for this node. The recorder may
554    /// read live state, so every semantics update runs it again; one whose
555    /// answer changes without the node changing can say so with a
556    /// [`SemanticsRequester`](crate::SemanticsRequester).
557    pub fn semantics<F>(self, recorder: F) -> Self
558    where
559        F: Fn(&mut SemanticsConfiguration) + 'static,
560    {
561        self.semantics_recorder(std::rc::Rc::new(recorder), true)
562    }
563
564    /// [`Modifier::semantics`] for a recorder that reads nothing but what it
565    /// captured. Its answer changes only when the modifier is set again, which
566    /// invalidates the node's semantics, so a semantics update keeps what it
567    /// reported instead of running it.
568    ///
569    /// Example: `Modifier::empty().stable_semantics(move |config| config.content_description = Some(label.clone()))`
570    pub fn stable_semantics<F>(self, recorder: F) -> Self
571    where
572        F: Fn(&mut SemanticsConfiguration) + 'static,
573    {
574        self.semantics_recorder(std::rc::Rc::new(recorder), false)
575    }
576
577    fn semantics_recorder(
578        self,
579        recorder: std::rc::Rc<dyn Fn(&mut SemanticsConfiguration)>,
580        reads_live_state: bool,
581    ) -> Self {
582        let metadata = if inspector_metadata_enabled() {
583            let mut preview = SemanticsConfiguration::default();
584            recorder(&mut preview);
585            let description = preview.content_description.clone();
586            let state_description = preview.state_description.clone();
587            let role = preview.role;
588            let is_clickable = preview.is_activatable();
589            let canvas_children = preview.canvas_children.len();
590            inspector_metadata("semantics", move |info| {
591                if let Some(desc) = &description {
592                    info.add_property("contentDescription", desc.clone());
593                }
594                if let Some(state) = &state_description {
595                    info.add_property("stateDescription", state.clone());
596                }
597                if let Some(role) = role {
598                    info.add_property("role", format!("{role:?}"));
599                }
600                if is_clickable {
601                    info.add_property("isClickable", "true");
602                }
603                if canvas_children > 0 {
604                    info.add_property("canvasSemanticsChildren", canvas_children.to_string());
605                }
606            })
607        } else {
608            inspector_metadata("semantics", |_| {})
609        };
610        let element = SemanticsElement::new(recorder, reads_live_state);
611        let modifier =
612            Modifier::from_parts(&[modifier_element(element)]).with_inspector_metadata(metadata);
613        self.then(modifier)
614    }
615
616    /// Tells a screen reader the value this control holds inside a range, so
617    /// it reads the value and offers its own way to change it.
618    ///
619    /// This is Compose's `Modifier.progressSemantics(value, valueRange,
620    /// steps)`. Without it a slider reads as text and a person who cannot see
621    /// the screen has no way to move it.
622    ///
623    /// Example: `Modifier::empty().progress_semantics(0.35, 0.0, 1.0, 0)`
624    pub fn progress_semantics(self, current: f32, start: f32, end: f32, steps: u32) -> Self {
625        let info = ProgressBarRangeInfo::new(current, start, end, steps);
626        self.stable_semantics(move |config| config.progress = Some(info))
627    }
628
629    /// Marks this control as one that opens a list of choices, so a reader
630    /// says "combo box" rather than "button" and knows to look for the
631    /// choice it holds. Compose's `Role.DropdownList`.
632    pub fn dropdown_list(self) -> Self {
633        self.role(cranpose_foundation::SemanticsWidgetRole::DropdownList)
634    }
635
636    /// Marks this control as one that holds one value out of an ordered set,
637    /// so a reader steps through them. Compose's `Role.ValuePicker`.
638    pub fn value_picker(self) -> Self {
639        self.role(cranpose_foundation::SemanticsWidgetRole::ValuePicker)
640    }
641
642    /// Says what this control does when a screen reader asks it to open, and
643    /// marks it as closed right now: a reader offers "expand" and says the
644    /// control is collapsed. Compose's
645    /// `Modifier.semantics { expand { … } }`.
646    pub fn expand(self, action: impl Fn() -> bool + 'static) -> Self {
647        let action = cranpose_foundation::SemanticsExpand::new(action);
648        self.stable_semantics(move |config| config.expand = Some(action.clone()))
649    }
650
651    /// Says what a long press on this control does, so a screen reader can
652    /// ask for it and read the label out first: "Remove receipt". Compose's
653    /// `Modifier.semantics { onLongClick("Remove receipt") { … } }`, which
654    /// `Modifier.combinedClickable(onLongClickLabel = …)` fills in for a
655    /// control that takes a long press from a finger too.
656    ///
657    /// The label is asked for, not optional as in Compose: Android is the one
658    /// platform of the four with a long press of its own, and the other three
659    /// list the action by name, so a nameless one reads as nothing.
660    pub fn on_long_click(
661        self,
662        label: impl Into<String>,
663        action: impl Fn() -> bool + 'static,
664    ) -> Self {
665        let label = label.into();
666        let action = cranpose_foundation::SemanticsLongClick::new(action);
667        self.stable_semantics(move |config| {
668            config.on_long_click_label = Some(label.clone());
669            config.on_long_click = Some(action.clone());
670        })
671    }
672
673    /// Says what this control does on VoiceOver's magic tap, the two finger
674    /// double tap for the main action of a screen, and the verb phrase the
675    /// other platforms list it under: "Take the photo". SwiftUI's
676    /// `accessibilityAction(.magicTap)`.
677    pub fn on_magic_tap(
678        self,
679        label: impl Into<String>,
680        action: impl Fn() -> bool + 'static,
681    ) -> Self {
682        let label = label.into();
683        let action = cranpose_foundation::SemanticsMagicTap::new(action);
684        self.stable_semantics(move |config| {
685            config.on_magic_tap_label = Some(label.clone());
686            config.on_magic_tap = Some(action.clone());
687        })
688    }
689
690    /// The short names a person says to Voice Control to reach this control,
691    /// when the name a reader hears is too long to say. SwiftUI's
692    /// `accessibilityInputLabels`.
693    pub fn input_labels<S: Into<String>>(self, labels: impl IntoIterator<Item = S>) -> Self {
694        let labels: Vec<String> = labels.into_iter().map(Into::into).collect();
695        self.stable_semantics(move |config| config.input_labels.clone_from(&labels))
696    }
697
698    /// The language of this control's text, as a BCP 47 tag such as "de" or
699    /// "pt-BR", so a reader picks the right voice. SwiftUI's
700    /// `accessibilityLanguage`, ARIA's `lang`.
701    pub fn language(self, tag: impl Into<String>) -> Self {
702        let tag = tag.into();
703        self.stable_semantics(move |config| config.language = Some(tag.clone()))
704    }
705
706    /// Says what this control does when a screen reader asks it to close, and
707    /// marks it as open right now. Compose's
708    /// `Modifier.semantics { collapse { … } }`.
709    pub fn collapse(self, action: impl Fn() -> bool + 'static) -> Self {
710        let action = cranpose_foundation::SemanticsExpand::new(action);
711        self.stable_semantics(move |config| config.collapse = Some(action.clone()))
712    }
713
714    /// Says what this control does when a screen reader asks to send it away:
715    /// a row a sighted person swipes off, a sheet a sighted person taps
716    /// outside of. A reader that cannot make the gesture gets the same way
717    /// out. Compose's `Modifier.semantics { dismiss { … } }`.
718    pub fn dismiss(self, action: impl Fn() -> bool + 'static) -> Self {
719        let action = cranpose_foundation::SemanticsDismiss::new(action);
720        self.stable_semantics(move |config| config.dismiss = Some(action.clone()))
721    }
722
723    /// Says what this list does when a screen reader asks for the row at an
724    /// index, so a reader reaches row 300 of a long list at once instead of
725    /// paging to it. The index counts rows from zero and the answer says
726    /// whether the list moved. `LazyColumn` and `LazyRow` declare it on their
727    /// own. Compose's `Modifier.semantics { scrollToIndex { … } }`.
728    pub fn scroll_to_index(self, action: impl Fn(usize) -> bool + 'static) -> Self {
729        let action = cranpose_foundation::SemanticsScrollToIndex::new(action);
730        self.stable_semantics(move |config| config.scroll_to_index = Some(action.clone()))
731    }
732
733    /// Moves this node in the order a screen reader visits the nodes beside
734    /// it: a smaller number comes first, and a node left alone keeps the
735    /// order the app laid it out in. A search field drawn last but meant to
736    /// be read first takes a negative number. Compose's
737    /// `Modifier.semantics { traversalIndex = -1f }`.
738    pub fn traversal_index(self, index: f32) -> Self {
739        self.stable_semantics(move |config| config.traversal_index = index)
740    }
741
742    /// Marks a field as one that holds a secret, so no screen reader reads
743    /// its text out: a reader hears the name the app gave the field, and
744    /// "password" in place of the text. Compose's
745    /// `Modifier.semantics { password() }`.
746    pub fn password(self) -> Self {
747        self.stable_semantics(|config| config.password = true)
748    }
749
750    /// Says why the control's content is wrong, so a screen reader reads
751    /// "invalid, the amount needs a number" after the control's state.
752    /// Compose's `Modifier.semantics { error("...") }`.
753    pub fn error(self, message: impl Into<String>) -> Self {
754        let message = message.into();
755        self.stable_semantics(move |config| config.error = Some(message.clone()))
756    }
757
758    /// Names the screen or pane this node is the root of, so a screen reader
759    /// hears where it is when the app moves on: "Library" as the library
760    /// opens. Compose's `Modifier.semantics { paneTitle = "..." }`.
761    pub fn pane_title(self, title: impl Into<String>) -> Self {
762        let title = title.into();
763        self.stable_semantics(move |config| config.pane_title = Some(title.clone()))
764    }
765
766    /// Makes the selectable controls under this node one group, so a screen
767    /// reader says which of how many a tab or a radio button is: "Library,
768    /// tab, 2 of 5". `LiquidTabBar` declares it on its own. Compose's
769    /// `Modifier.selectableGroup()`.
770    pub fn selectable_group(self) -> Self {
771        self.stable_semantics(|config| config.selectable_group = true)
772    }
773
774    /// Makes a screen reader take this node and the text under it as one
775    /// stop, the way it does for a button: a row whose name, count and price
776    /// belong together reads as "Milk, 2, 3.40" and not as three stops.
777    /// Compose's `Modifier.semantics(mergeDescendants = true) {}`.
778    pub fn merge_descendants(self) -> Self {
779        self.stable_semantics(|config| config.merge_descendants = true)
780    }
781
782    /// Takes this node and everything under it out of what a screen reader
783    /// sees: a decorative image, or a placeholder drawn under a field that
784    /// carries the same words as its name. Compose's
785    /// `semantics { hideFromAccessibility() }`.
786    pub fn hide_from_accessibility(self) -> Self {
787        self.stable_semantics(|config| config.hidden = true)
788    }
789
790    /// Marks this component as a heading, so a screen reader lists it among
791    /// the headings of the screen and a person can jump between them.
792    ///
793    /// This is Compose's `Modifier.semantics { heading() }`.
794    pub fn heading(self) -> Self {
795        self.role(cranpose_foundation::SemanticsWidgetRole::Header)
796    }
797
798    /// Tells a screen reader what kind of control this is, when the widget
799    /// does not say so on its own.
800    ///
801    /// This is Compose's `Modifier.semantics { role = Role.Button }`.
802    pub fn role(self, role: cranpose_foundation::SemanticsWidgetRole) -> Self {
803        self.stable_semantics(move |config| config.role = Some(role))
804    }
805
806    /// Makes a screen reader read this component's text out whenever it
807    /// changes, without the reader's cursor on it: a status line, a toast, a
808    /// count that moves.
809    ///
810    /// This is Compose's `Modifier.semantics { liveRegion = LiveRegionMode.Polite }`.
811    pub fn live_region(self, mode: cranpose_foundation::LiveRegionMode) -> Self {
812        self.stable_semantics(move |config| config.live_region = Some(mode))
813    }
814
815    /// Gives this component the text a screen reader reads for it, for a
816    /// drawing, an icon or a control with no text of its own.
817    ///
818    /// This is Compose's `Modifier.semantics { contentDescription = "..." }`.
819    pub fn content_description(self, description: impl Into<String>) -> Self {
820        let description = description.into();
821        self.stable_semantics(move |config| config.content_description = Some(description.clone()))
822    }
823
824    /// Makes this component focusable.
825    ///
826    /// This adds a focus target node that can receive focus and participate
827    /// in focus traversal. The component will be included in tab order and
828    /// can be focused programmatically.
829    pub fn focus_target(self) -> Self {
830        let element = FocusTargetElement::new();
831        let modifier = Modifier::from_parts(&[modifier_element(element)]);
832        self.then(modifier)
833    }
834
835    /// Makes this component focusable with a callback for focus changes.
836    ///
837    /// The callback is invoked whenever the focus state changes, allowing
838    /// components to react to gaining or losing focus.
839    pub fn on_focus_changed<F>(self, callback: F) -> Self
840    where
841        F: Fn(FocusState) + 'static,
842    {
843        let element = FocusTargetElement::with_callback(callback);
844        let modifier = Modifier::from_parts(&[modifier_element(element)]);
845        self.then(modifier)
846    }
847
848    /// Binds a [`FocusRequester`] to this node, so an app can move focus onto
849    /// it imperatively with [`FocusRequester::request_focus`].
850    ///
851    /// Pair it with [`focus_target`](Self::focus_target) (or
852    /// [`on_focus_changed`](Self::on_focus_changed)) on the same node —
853    /// `request_focus` moves whichever focus targets are attached there.
854    pub fn focus_requester(self, requester: &FocusRequester) -> Self {
855        let element = FocusRequesterElement::new(requester.clone());
856        let modifier = Modifier::from_parts(&[modifier_element(element)]);
857        self.then(modifier)
858    }
859
860    /// Binds a [`SemanticsRequester`] to this node, so an app can mark the
861    /// node's semantics for re-collection without recomposing or laying out.
862    ///
863    /// Pair it with [`semantics`](Self::semantics) on the same node when the
864    /// recorder reads state the composition does not observe — app state behind
865    /// a `RefCell`, a game's own model — which is the case a recorder cannot
866    /// signal for itself.
867    pub fn semantics_requester(self, requester: &SemanticsRequester) -> Self {
868        let element = SemanticsRequesterElement::new(requester.clone());
869        let modifier = Modifier::from_parts(&[modifier_element(element)]);
870        self.then(modifier)
871    }
872
873    /// Enables debug logging for this modifier chain.
874    ///
875    /// When enabled, logs the entire modifier chain structure including:
876    /// - Element types and their properties
877    /// - Inspector metadata
878    /// - Capability flags
879    ///
880    /// This is useful for debugging modifier composition issues and understanding
881    /// how the modifier chain is structured at runtime.
882    ///
883    /// Example:
884    /// ```text
885    /// Modifier::empty()
886    ///     .padding(8.0)
887    ///     .background(Color(1.0, 0.0, 0.0, 1.0))
888    ///     .debug_chain("MyWidget")
889    /// ```
890    pub fn debug_chain(self, tag: &'static str) -> Self {
891        use cranpose_foundation::{ModifierNode, ModifierNodeContext, NodeCapabilities, NodeState};
892
893        #[derive(Clone)]
894        struct DebugChainElement {
895            tag: &'static str,
896        }
897
898        impl fmt::Debug for DebugChainElement {
899            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
900                f.debug_struct("DebugChainElement")
901                    .field("tag", &self.tag)
902                    .finish()
903            }
904        }
905
906        impl PartialEq for DebugChainElement {
907            fn eq(&self, other: &Self) -> bool {
908                self.tag == other.tag
909            }
910        }
911
912        impl Eq for DebugChainElement {}
913
914        impl std::hash::Hash for DebugChainElement {
915            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
916                self.tag.hash(state);
917            }
918        }
919
920        impl ModifierNodeElement for DebugChainElement {
921            type Node = DebugChainNode;
922
923            fn create(&self) -> Self::Node {
924                DebugChainNode::new(self.tag)
925            }
926
927            fn update(&self, node: &mut Self::Node) {
928                node.tag = self.tag;
929            }
930
931            fn capabilities(&self) -> NodeCapabilities {
932                NodeCapabilities::empty()
933            }
934        }
935
936        struct DebugChainNode {
937            tag: &'static str,
938            state: NodeState,
939        }
940
941        impl DebugChainNode {
942            fn new(tag: &'static str) -> Self {
943                Self {
944                    tag,
945                    state: NodeState::new(),
946                }
947            }
948        }
949
950        impl ModifierNode for DebugChainNode {
951            fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {
952                eprintln!("[debug_chain:{}] Modifier chain attached", self.tag);
953            }
954
955            fn on_detach(&mut self) {
956                eprintln!("[debug_chain:{}] Modifier chain detached", self.tag);
957            }
958
959            fn on_reset(&mut self) {
960                eprintln!("[debug_chain:{}] Modifier chain reset", self.tag);
961            }
962        }
963
964        impl cranpose_foundation::DelegatableNode for DebugChainNode {
965            fn node_state(&self) -> &NodeState {
966                &self.state
967            }
968        }
969
970        let element = DebugChainElement { tag };
971        let modifier = Modifier::from_parts(&[modifier_element(element)]);
972        self.then(modifier)
973            .with_inspector_metadata(inspector_metadata("debugChain", move |info| {
974                info.add_property("tag", tag);
975            }))
976    }
977
978    /// This modifier followed by `next`.
979    ///
980    /// It takes `self`: a modifier built link by link owns its elements
981    /// alone, so each link joins them in place instead of copying the chain.
982    /// Clone a modifier first to keep using it; the join then copies it.
983    pub fn then(self, next: Modifier) -> Modifier {
984        let Modifier {
985            kind,
986            strict_fingerprint,
987            structural_fingerprint,
988            element_count,
989            provides_composition_locals,
990        } = self;
991        let ModifierKind::Single {
992            mut elements,
993            inspector,
994        } = kind
995        else {
996            return next;
997        };
998        let ModifierKind::Single {
999            elements: next_elements,
1000            inspector: next_inspector,
1001        } = &next.kind
1002        else {
1003            return Modifier {
1004                kind: ModifierKind::Single {
1005                    elements,
1006                    inspector,
1007                },
1008                strict_fingerprint,
1009                structural_fingerprint,
1010                element_count,
1011                provides_composition_locals,
1012            };
1013        };
1014
1015        let fingerprints = append_fingerprints(
1016            ModifierFingerprints {
1017                strict: strict_fingerprint,
1018                structural: structural_fingerprint,
1019            },
1020            next_elements,
1021        );
1022        elements.extend_from(next_elements);
1023        let inspector = if next_inspector.is_none() {
1024            inspector
1025        } else {
1026            merged_inspector(inspector_slice(&inspector), inspector_slice(next_inspector))
1027        };
1028        Modifier {
1029            kind: ModifierKind::Single {
1030                elements,
1031                inspector,
1032            },
1033            strict_fingerprint: fingerprints.strict,
1034            structural_fingerprint: fingerprints.structural,
1035            element_count: element_count + next.element_count,
1036            provides_composition_locals: provides_composition_locals
1037                || next.provides_composition_locals,
1038        }
1039    }
1040
1041    pub(crate) fn iter_elements(&self) -> ModifierElementIterator<'_> {
1042        match &self.kind {
1043            ModifierKind::Empty => ModifierElementIterator { inner: [].iter() },
1044            ModifierKind::Single { elements, .. } => ModifierElementIterator {
1045                inner: elements.iter(),
1046            },
1047        }
1048    }
1049
1050    pub(crate) fn provided_composition_locals(&self) -> Vec<ProvidedValue> {
1051        if !self.provides_composition_locals {
1052            return Vec::new();
1053        }
1054        self.iter_elements()
1055            .flat_map(|element| element.provided_composition_locals())
1056            .collect()
1057    }
1058
1059    pub(crate) fn iter_inspector_metadata(&self) -> ModifierInspectorIterator<'_> {
1060        match &self.kind {
1061            ModifierKind::Empty => ModifierInspectorIterator { inner: [].iter() },
1062            ModifierKind::Single { inspector, .. } => ModifierInspectorIterator {
1063                inner: inspector_slice(inspector).iter(),
1064            },
1065        }
1066    }
1067
1068    #[cfg(test)]
1069    pub(crate) fn elements(&self) -> Vec<DynModifierElement> {
1070        match &self.kind {
1071            ModifierKind::Empty => Vec::new(),
1072            ModifierKind::Single { elements, .. } => elements.to_vec(),
1073        }
1074    }
1075
1076    pub(crate) fn inspector_metadata(&self) -> Vec<InspectorMetadata> {
1077        match &self.kind {
1078            ModifierKind::Empty => Vec::new(),
1079            ModifierKind::Single { inspector, .. } => {
1080                inspector.as_deref().map(<[_]>::to_vec).unwrap_or_default()
1081            }
1082        }
1083    }
1084
1085    pub(crate) fn rehouse_for_live_compaction(&self) -> Self {
1086        match &self.kind {
1087            ModifierKind::Empty => Self::default(),
1088            ModifierKind::Single {
1089                elements,
1090                inspector,
1091            } => Self {
1092                kind: ModifierKind::Single {
1093                    elements: Elements::from_slice(elements),
1094                    inspector: inspector
1095                        .as_ref()
1096                        .map(|inspector| inspector.iter().cloned().collect()),
1097                },
1098                strict_fingerprint: self.strict_fingerprint,
1099                structural_fingerprint: self.structural_fingerprint,
1100                element_count: self.element_count,
1101                provides_composition_locals: self.provides_composition_locals,
1102            },
1103        }
1104    }
1105
1106    pub fn total_padding(&self) -> f32 {
1107        let padding = self.padding_values();
1108        padding
1109            .left
1110            .max(padding.right)
1111            .max(padding.top)
1112            .max(padding.bottom)
1113    }
1114
1115    pub fn explicit_size(&self) -> Option<Size> {
1116        let props = self.layout_properties();
1117        match (props.width, props.height) {
1118            (DimensionConstraint::Points(width), DimensionConstraint::Points(height)) => {
1119                Some(Size { width, height })
1120            }
1121            _ => None,
1122        }
1123    }
1124
1125    pub fn padding_values(&self) -> EdgeInsets {
1126        self.resolved_modifiers().padding()
1127    }
1128
1129    pub(crate) fn layout_properties(&self) -> LayoutProperties {
1130        self.resolved_modifiers().layout_properties()
1131    }
1132
1133    pub fn box_alignment(&self) -> Option<Alignment> {
1134        self.layout_properties().box_alignment()
1135    }
1136
1137    pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
1138        self.layout_properties().column_alignment()
1139    }
1140
1141    pub fn row_alignment(&self) -> Option<VerticalAlignment> {
1142        self.layout_properties().row_alignment()
1143    }
1144
1145    pub fn draw_commands(&self) -> Vec<DrawCommand> {
1146        collect_slices_from_modifier(self).draw_commands().to_vec()
1147    }
1148
1149    pub fn clips_to_bounds(&self) -> bool {
1150        collect_slices_from_modifier(self).clip_to_bounds()
1151    }
1152
1153    /// Returns structured inspector records for each modifier element.
1154    pub fn collect_inspector_records(&self) -> Vec<ModifierInspectorRecord> {
1155        self.inspector_metadata()
1156            .iter()
1157            .map(InspectorMetadata::to_record)
1158            .collect()
1159    }
1160
1161    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
1162        let mut handle = ModifierChainHandle::new();
1163        let _ = handle.update(self);
1164        handle.resolved_modifiers()
1165    }
1166
1167    /// A modifier of the one `element`. Platform crates build their own
1168    /// modifiers on it, the way [`Modifier::window_root`] is built.
1169    pub fn with_element<E>(element: E) -> Self
1170    where
1171        E: ModifierNodeElement,
1172    {
1173        Self::from_parts(&[modifier_element(element)])
1174    }
1175
1176    /// A modifier of `elements`, held in one shared allocation.
1177    pub(crate) fn from_parts(elements: &[DynModifierElement]) -> Self {
1178        if elements.is_empty() {
1179            Self::default()
1180        } else {
1181            let element_count = elements.len();
1182            let provides_composition_locals = elements
1183                .iter()
1184                .any(|element| element.provides_composition_locals());
1185            let fingerprints = single_fingerprints(elements);
1186            Self {
1187                kind: ModifierKind::Single {
1188                    elements: Elements::from_slice(elements),
1189                    inspector: None,
1190                },
1191                strict_fingerprint: fingerprints.strict,
1192                structural_fingerprint: fingerprints.structural,
1193                element_count,
1194                provides_composition_locals,
1195            }
1196        }
1197    }
1198
1199    pub(crate) fn with_inspector_metadata(self, metadata: InspectorMetadata) -> Self {
1200        if metadata.is_empty() {
1201            return self;
1202        }
1203        match self.kind {
1204            ModifierKind::Empty => self,
1205            ModifierKind::Single {
1206                elements,
1207                inspector,
1208            } => {
1209                let new_inspector = inspector_slice(&inspector)
1210                    .iter()
1211                    .cloned()
1212                    .chain(std::iter::once(metadata))
1213                    .collect();
1214                Self {
1215                    kind: ModifierKind::Single {
1216                        elements,
1217                        inspector: Some(new_inspector),
1218                    },
1219                    strict_fingerprint: self.strict_fingerprint,
1220                    structural_fingerprint: self.structural_fingerprint,
1221                    element_count: self.element_count,
1222                    provides_composition_locals: self.provides_composition_locals,
1223                }
1224            }
1225        }
1226    }
1227
1228    /// Checks whether two modifiers are structurally equivalent for layout decisions.
1229    ///
1230    /// This ignores identity-sensitive modifier elements (e.g., draw closures) so
1231    /// draw-only updates do not force measure/layout invalidation.
1232    pub fn structural_eq(&self, other: &Self) -> bool {
1233        self.eq_internal(other, false)
1234    }
1235
1236    fn eq_internal(&self, other: &Self, consider_always_update: bool) -> bool {
1237        if self.element_count != other.element_count {
1238            return false;
1239        }
1240        if consider_always_update {
1241            if self.strict_fingerprint != other.strict_fingerprint {
1242                return false;
1243            }
1244        } else if self.structural_fingerprint != other.structural_fingerprint {
1245            return false;
1246        }
1247
1248        match (&self.kind, &other.kind) {
1249            (ModifierKind::Empty, ModifierKind::Empty) => true,
1250            (
1251                ModifierKind::Single {
1252                    elements: e1,
1253                    inspector: _,
1254                },
1255                ModifierKind::Single {
1256                    elements: e2,
1257                    inspector: _,
1258                },
1259            ) => {
1260                if e1.shares_storage(e2) {
1261                    return true;
1262                }
1263
1264                if e1.len() != e2.len() {
1265                    return false;
1266                }
1267
1268                for (a, b) in e1.iter().zip(e2.iter()) {
1269                    if !consider_always_update
1270                        && a.element_type() == b.element_type()
1271                        && a.capabilities() == NodeCapabilities::DRAW
1272                        && b.capabilities() == NodeCapabilities::DRAW
1273                    {
1274                        continue;
1275                    }
1276
1277                    if consider_always_update && (a.requires_update() || b.requires_update()) {
1278                        if !Rc::ptr_eq(a, b) {
1279                            return false;
1280                        }
1281                        continue;
1282                    }
1283
1284                    if !a.equals_element(&**b) {
1285                        return false;
1286                    }
1287                }
1288
1289                true
1290            }
1291            _ => false,
1292        }
1293    }
1294}
1295
1296impl PartialEq for Modifier {
1297    fn eq(&self, other: &Self) -> bool {
1298        self.eq_internal(other, true)
1299    }
1300}
1301
1302impl Eq for Modifier {}
1303
1304impl fmt::Display for Modifier {
1305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1306        match &self.kind {
1307            ModifierKind::Empty => write!(f, "Modifier.empty"),
1308            ModifierKind::Single { elements, .. } => {
1309                if elements.is_empty() {
1310                    return write!(f, "Modifier.empty");
1311                }
1312                write!(f, "Modifier[")?;
1313                for (index, element) in elements.iter().enumerate() {
1314                    if index > 0 {
1315                        write!(f, ", ")?;
1316                    }
1317                    let name = element.inspector_name();
1318                    let mut properties = Vec::new();
1319                    element.record_inspector_properties(&mut |prop, value| {
1320                        properties.push(format!("{prop}={value}"));
1321                    });
1322                    if properties.is_empty() {
1323                        write!(f, "{name}")?;
1324                    } else {
1325                        write!(f, "{name}({})", properties.join(", "))?;
1326                    }
1327                }
1328                write!(f, "]")
1329            }
1330        }
1331    }
1332}
1333
1334impl fmt::Debug for Modifier {
1335    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1336        fmt::Display::fmt(self, f)
1337    }
1338}
1339
1340#[derive(Clone, Copy, Debug, PartialEq)]
1341pub struct ResolvedBackground {
1342    color: Color,
1343    shape: Option<RoundedCornerShape>,
1344}
1345
1346impl ResolvedBackground {
1347    pub fn new(color: Color, shape: Option<RoundedCornerShape>) -> Self {
1348        Self { color, shape }
1349    }
1350
1351    pub fn color(&self) -> Color {
1352        self.color
1353    }
1354
1355    pub fn shape(&self) -> Option<RoundedCornerShape> {
1356        self.shape
1357    }
1358
1359    pub fn set_shape(&mut self, shape: Option<RoundedCornerShape>) {
1360        self.shape = shape;
1361    }
1362}
1363
1364#[derive(Clone, Copy, Debug, PartialEq, Default)]
1365pub struct ResolvedModifiers {
1366    padding: EdgeInsets,
1367    layout: LayoutProperties,
1368    offset: Point,
1369}
1370
1371impl ResolvedModifiers {
1372    pub fn padding(&self) -> EdgeInsets {
1373        self.padding
1374    }
1375
1376    pub fn layout_properties(&self) -> LayoutProperties {
1377        self.layout
1378    }
1379
1380    pub fn offset(&self) -> Point {
1381        self.offset
1382    }
1383
1384    pub(crate) fn set_padding(&mut self, padding: EdgeInsets) {
1385        self.padding = padding;
1386    }
1387
1388    pub(crate) fn set_layout_properties(&mut self, layout: LayoutProperties) {
1389        self.layout = layout;
1390    }
1391
1392    pub(crate) fn set_offset(&mut self, offset: Point) {
1393        self.offset = offset;
1394    }
1395
1396    /// These modifiers with every length on a whole device pixel of
1397    /// `density`, as Compose's `roundToPx` puts padding, sizes and offsets.
1398    pub(crate) fn on_device_grid(&self, density: f32) -> Self {
1399        use cranpose_ui_layout::round_to_px;
1400
1401        let round = |value: Option<f32>| value.map(|value| round_to_px(value, density));
1402        let dimension = |dimension: DimensionConstraint| match dimension {
1403            DimensionConstraint::Points(value) => {
1404                DimensionConstraint::Points(round_to_px(value, density))
1405            }
1406            other => other,
1407        };
1408        Self {
1409            padding: crate::modifier_nodes::device_padding(self.padding, density),
1410            layout: LayoutProperties {
1411                padding: crate::modifier_nodes::device_padding(self.layout.padding, density),
1412                width: dimension(self.layout.width),
1413                height: dimension(self.layout.height),
1414                min_width: round(self.layout.min_width),
1415                min_height: round(self.layout.min_height),
1416                max_width: round(self.layout.max_width),
1417                max_height: round(self.layout.max_height),
1418                ..self.layout
1419            },
1420            offset: Point {
1421                x: round_to_px(self.offset.x, density),
1422                y: round_to_px(self.offset.y, density),
1423            },
1424        }
1425    }
1426}
1427
1428#[derive(Clone, Copy, Debug, Default, PartialEq)]
1429pub enum DimensionConstraint {
1430    #[default]
1431    Unspecified,
1432    Points(f32),
1433    Fraction(f32),
1434    Intrinsic(IntrinsicSize),
1435}
1436
1437#[derive(Clone, Copy, Debug, Default, PartialEq)]
1438pub struct LayoutWeight {
1439    pub weight: f32,
1440    pub fill: bool,
1441}
1442
1443#[derive(Clone, Copy, Debug, Default, PartialEq)]
1444pub struct LayoutProperties {
1445    padding: EdgeInsets,
1446    width: DimensionConstraint,
1447    height: DimensionConstraint,
1448    min_width: Option<f32>,
1449    min_height: Option<f32>,
1450    max_width: Option<f32>,
1451    max_height: Option<f32>,
1452    weight: Option<LayoutWeight>,
1453    box_alignment: Option<Alignment>,
1454    column_alignment: Option<HorizontalAlignment>,
1455    row_alignment: Option<VerticalAlignment>,
1456    row_baseline: bool,
1457}
1458
1459impl LayoutProperties {
1460    pub fn padding(&self) -> EdgeInsets {
1461        self.padding
1462    }
1463
1464    pub fn width(&self) -> DimensionConstraint {
1465        self.width
1466    }
1467
1468    pub fn height(&self) -> DimensionConstraint {
1469        self.height
1470    }
1471
1472    pub fn min_width(&self) -> Option<f32> {
1473        self.min_width
1474    }
1475
1476    pub fn min_height(&self) -> Option<f32> {
1477        self.min_height
1478    }
1479
1480    pub fn max_width(&self) -> Option<f32> {
1481        self.max_width
1482    }
1483
1484    pub fn max_height(&self) -> Option<f32> {
1485        self.max_height
1486    }
1487
1488    pub fn weight(&self) -> Option<LayoutWeight> {
1489        self.weight
1490    }
1491
1492    pub fn box_alignment(&self) -> Option<Alignment> {
1493        self.box_alignment
1494    }
1495
1496    pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
1497        self.column_alignment
1498    }
1499
1500    pub fn row_alignment(&self) -> Option<VerticalAlignment> {
1501        self.row_alignment
1502    }
1503
1504    /// Whether the parent Row aligns this child's first baseline with its siblings.
1505    pub fn row_baseline(&self) -> bool {
1506        self.row_baseline
1507    }
1508}
1509
1510#[cfg(test)]
1511#[path = "tests/modifier_tests.rs"]
1512mod tests;