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