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