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