Skip to main content

cranpose_ui/modifier/
mod.rs

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