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