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