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
8#![allow(non_snake_case)]
9
10use std::fmt;
11use std::rc::Rc;
12
13mod alignment;
14mod background;
15mod chain;
16mod clickable;
17mod draw_cache;
18mod fill;
19mod focus;
20mod graphics_layer;
21mod local;
22mod offset;
23mod padding;
24mod pointer_input;
25mod scroll;
26mod semantics;
27mod size;
28mod slices;
29mod weight;
30
31pub use crate::draw::{DrawCacheBuilder, DrawCommand};
32#[allow(unused_imports)]
33pub use chain::{ModifierChainHandle, ModifierChainInspectorNode, ModifierLocalsHandle};
34use cranpose_foundation::ModifierNodeElement;
35pub use cranpose_foundation::{
36    modifier_element, AnyModifierElement, DynModifierElement, FocusState, PointerEvent,
37    PointerEventKind, SemanticsConfiguration,
38};
39pub use cranpose_ui_graphics::{
40    Brush, Color, CornerRadii, EdgeInsets, GraphicsLayer, Point, Rect, RoundedCornerShape, Size,
41};
42use cranpose_ui_layout::{Alignment, HorizontalAlignment, IntrinsicSize, VerticalAlignment};
43#[allow(unused_imports)]
44pub use focus::{FocusDirection, FocusRequester};
45pub(crate) use local::{
46    ModifierLocalAncestorResolver, ModifierLocalSource, ModifierLocalToken, ResolvedModifierLocal,
47};
48#[allow(unused_imports)]
49pub use local::{ModifierLocalKey, ModifierLocalReadScope};
50#[allow(unused_imports)]
51pub use pointer_input::{AwaitPointerEventScope, PointerInputScope};
52pub use semantics::{collect_semantics_from_chain, collect_semantics_from_modifier};
53pub use slices::{collect_modifier_slices, collect_slices_from_modifier, ModifierNodeSlices};
54// Test accessibility for fling velocity (only with test-helpers feature)
55#[cfg(feature = "test-helpers")]
56pub use scroll::{last_fling_velocity, reset_last_fling_velocity};
57
58use crate::modifier_nodes::ClipToBoundsElement;
59use focus::{FocusRequesterElement, FocusTargetElement};
60use local::{ModifierLocalConsumerElement, ModifierLocalProviderElement};
61use semantics::SemanticsElement;
62
63/// Minimal inspector metadata storage.
64#[derive(Clone, Debug, Default)]
65pub struct InspectorInfo {
66    properties: Vec<InspectorProperty>,
67}
68
69impl InspectorInfo {
70    pub fn new() -> Self {
71        Self::default()
72    }
73
74    pub fn add_property<V: Into<String>>(&mut self, name: &'static str, value: V) {
75        self.properties.push(InspectorProperty {
76            name,
77            value: value.into(),
78        });
79    }
80
81    pub fn properties(&self) -> &[InspectorProperty] {
82        &self.properties
83    }
84
85    pub fn is_empty(&self) -> bool {
86        self.properties.is_empty()
87    }
88
89    pub fn add_dimension(&mut self, name: &'static str, constraint: DimensionConstraint) {
90        self.add_property(name, describe_dimension(constraint));
91    }
92
93    pub fn add_offset_components(
94        &mut self,
95        x_name: &'static str,
96        y_name: &'static str,
97        offset: Point,
98    ) {
99        self.add_property(x_name, offset.x.to_string());
100        self.add_property(y_name, offset.y.to_string());
101    }
102
103    pub fn add_alignment<A>(&mut self, name: &'static str, alignment: A)
104    where
105        A: fmt::Debug,
106    {
107        self.add_property(name, format!("{alignment:?}"));
108    }
109
110    #[allow(dead_code)] // use for debugging
111    pub fn debug_properties(&self) -> Vec<(&'static str, String)> {
112        self.properties
113            .iter()
114            .map(|property| (property.name, property.value.clone()))
115            .collect()
116    }
117
118    #[allow(dead_code)] // use for debugging
119    pub fn describe(&self) -> String {
120        self.properties
121            .iter()
122            .map(|property| format!("{}={}", property.name, property.value))
123            .collect::<Vec<_>>()
124            .join(", ")
125    }
126}
127
128/// Single inspector entry recording a property exposed by a modifier.
129#[derive(Clone, Debug, PartialEq)]
130pub struct InspectorProperty {
131    pub name: &'static str,
132    pub value: String,
133}
134
135/// Structured inspector payload describing a modifier element.
136#[derive(Clone, Debug, PartialEq)]
137pub struct ModifierInspectorRecord {
138    pub name: &'static str,
139    pub properties: Vec<InspectorProperty>,
140}
141
142/// Helper describing the metadata contributed by a modifier factory.
143#[derive(Clone, Debug)]
144pub(crate) struct InspectorMetadata {
145    name: &'static str,
146    info: InspectorInfo,
147}
148
149impl InspectorMetadata {
150    pub(crate) fn new<F>(name: &'static str, recorder: F) -> Self
151    where
152        F: FnOnce(&mut InspectorInfo),
153    {
154        let mut info = InspectorInfo::new();
155        recorder(&mut info);
156        Self { name, info }
157    }
158
159    fn is_empty(&self) -> bool {
160        self.info.is_empty()
161    }
162
163    fn to_record(&self) -> ModifierInspectorRecord {
164        ModifierInspectorRecord {
165            name: self.name,
166            properties: self.info.properties().to_vec(),
167        }
168    }
169}
170
171fn describe_dimension(constraint: DimensionConstraint) -> String {
172    match constraint {
173        DimensionConstraint::Unspecified => "unspecified".to_string(),
174        DimensionConstraint::Points(value) => value.to_string(),
175        DimensionConstraint::Fraction(value) => format!("fraction({value})"),
176        DimensionConstraint::Intrinsic(size) => format!("intrinsic({size:?})"),
177    }
178}
179
180pub(crate) fn inspector_metadata<F>(name: &'static str, recorder: F) -> InspectorMetadata
181where
182    F: FnOnce(&mut InspectorInfo),
183{
184    InspectorMetadata::new(name, recorder)
185}
186
187/// Internal representation of modifier composition structure.
188/// This mirrors Jetpack Compose's CombinedModifier pattern where modifiers
189/// form a persistent tree structure instead of eagerly flattening into vectors.
190#[derive(Clone)]
191enum ModifierKind {
192    /// Empty modifier (like Modifier.companion in Kotlin)
193    Empty,
194    /// Single modifier with elements and inspector metadata
195    Single {
196        elements: Rc<Vec<DynModifierElement>>,
197        inspector: Rc<Vec<InspectorMetadata>>,
198    },
199    /// Combined modifier tree node (like CombinedModifier in Kotlin)
200    Combined {
201        outer: Rc<Modifier>,
202        inner: Rc<Modifier>,
203    },
204}
205
206/// Iterator over modifier elements that traverses the tree without allocation.
207///
208/// This avoids the O(N) allocation of `Modifier::elements()` by using a stack-based
209/// traversal of the `ModifierKind::Combined` tree structure.
210pub struct ModifierElementIterator<'a> {
211    /// Stack of modifiers to visit (right-to-left for in-order traversal)
212    stack: Vec<&'a Modifier>,
213    /// Current position within a Single modifier's elements
214    current_elements: Option<(&'a [DynModifierElement], usize)>,
215}
216
217impl<'a> ModifierElementIterator<'a> {
218    fn new(modifier: &'a Modifier) -> Self {
219        let mut iter = Self {
220            stack: Vec::new(),
221            current_elements: None,
222        };
223        iter.push_modifier(modifier);
224        iter
225    }
226
227    fn push_modifier(&mut self, modifier: &'a Modifier) {
228        match &modifier.kind {
229            ModifierKind::Empty => {}
230            ModifierKind::Single { elements, .. } => {
231                if !elements.is_empty() {
232                    self.current_elements = Some((elements.as_slice(), 0));
233                }
234            }
235            ModifierKind::Combined { outer, inner } => {
236                // Push inner first so outer is processed first (stack is LIFO)
237                self.stack.push(inner.as_ref());
238                self.push_modifier(outer.as_ref());
239            }
240        }
241    }
242}
243
244impl<'a> Iterator for ModifierElementIterator<'a> {
245    type Item = &'a DynModifierElement;
246
247    fn next(&mut self) -> Option<Self::Item> {
248        loop {
249            // First, try to yield from current elements
250            if let Some((elements, index)) = &mut self.current_elements {
251                if *index < elements.len() {
252                    let element = &elements[*index];
253                    *index += 1;
254                    return Some(element);
255                }
256                self.current_elements = None;
257            }
258
259            // Pop next modifier from stack
260            let next_modifier = self.stack.pop()?;
261            self.push_modifier(next_modifier);
262        }
263    }
264}
265
266/// A modifier chain that can be applied to composable elements.
267/// Modifiers form a persistent tree structure (via CombinedModifier pattern)
268/// to enable O(1) composition and structural sharing during recomposition.
269#[derive(Clone)]
270pub struct Modifier {
271    kind: ModifierKind,
272}
273
274impl Default for Modifier {
275    fn default() -> Self {
276        Self {
277            kind: ModifierKind::Empty,
278        }
279    }
280}
281
282impl Modifier {
283    pub fn empty() -> Self {
284        Self::default()
285    }
286
287    /// Clip the content to the bounds of this modifier.
288    ///
289    /// Example: `Modifier::empty().clip_to_bounds()`
290    pub fn clip_to_bounds(self) -> Self {
291        let modifier = Self::with_element(ClipToBoundsElement::new()).with_inspector_metadata(
292            inspector_metadata("clipToBounds", |info| {
293                info.add_property("clipToBounds", "true");
294            }),
295        );
296        self.then(modifier)
297    }
298
299    pub fn modifier_local_provider<T, F>(self, key: ModifierLocalKey<T>, value: F) -> Self
300    where
301        T: 'static,
302        F: Fn() -> T + 'static,
303    {
304        let element = ModifierLocalProviderElement::new(key, value);
305        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
306        self.then(modifier)
307    }
308
309    pub fn modifier_local_consumer<F>(self, consumer: F) -> Self
310    where
311        F: for<'scope> Fn(&mut ModifierLocalReadScope<'scope>) + 'static,
312    {
313        let element = ModifierLocalConsumerElement::new(consumer);
314        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
315        self.then(modifier)
316    }
317
318    pub fn semantics<F>(self, recorder: F) -> Self
319    where
320        F: Fn(&mut SemanticsConfiguration) + 'static,
321    {
322        let mut preview = SemanticsConfiguration::default();
323        recorder(&mut preview);
324        let description = preview.content_description.clone();
325        let is_button = preview.is_button;
326        let is_clickable = preview.is_clickable;
327        let metadata = inspector_metadata("semantics", move |info| {
328            if let Some(desc) = &description {
329                info.add_property("contentDescription", desc.clone());
330            }
331            if is_button {
332                info.add_property("isButton", "true");
333            }
334            if is_clickable {
335                info.add_property("isClickable", "true");
336            }
337        });
338        let element = SemanticsElement::new(recorder);
339        let modifier =
340            Modifier::from_parts(vec![modifier_element(element)]).with_inspector_metadata(metadata);
341        self.then(modifier)
342    }
343
344    /// Makes this component focusable.
345    ///
346    /// This adds a focus target node that can receive focus and participate
347    /// in focus traversal. The component will be included in tab order and
348    /// can be focused programmatically.
349    pub fn focus_target(self) -> Self {
350        let element = FocusTargetElement::new();
351        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
352        self.then(modifier)
353    }
354
355    /// Makes this component focusable with a callback for focus changes.
356    ///
357    /// The callback is invoked whenever the focus state changes, allowing
358    /// components to react to gaining or losing focus.
359    pub fn on_focus_changed<F>(self, callback: F) -> Self
360    where
361        F: Fn(FocusState) + 'static,
362    {
363        let element = FocusTargetElement::with_callback(callback);
364        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
365        self.then(modifier)
366    }
367
368    /// Attaches a focus requester to this component.
369    ///
370    /// The requester can be used to programmatically request focus for
371    /// this component from application code.
372    pub fn focus_requester(self, requester: &FocusRequester) -> Self {
373        let element = FocusRequesterElement::new(requester.id());
374        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
375        self.then(modifier)
376    }
377
378    /// Enables debug logging for this modifier chain.
379    ///
380    /// When enabled, logs the entire modifier chain structure including:
381    /// - Element types and their properties
382    /// - Inspector metadata
383    /// - Capability flags
384    ///
385    /// This is useful for debugging modifier composition issues and understanding
386    /// how the modifier chain is structured at runtime.
387    ///
388    /// Example:
389    /// ```text
390    /// Modifier::empty()
391    ///     .padding(8.0)
392    ///     .background(Color(1.0, 0.0, 0.0, 1.0))
393    ///     .debug_chain("MyWidget")
394    /// ```
395    pub fn debug_chain(self, tag: &'static str) -> Self {
396        use cranpose_foundation::{ModifierNode, ModifierNodeContext, NodeCapabilities, NodeState};
397
398        #[derive(Clone)]
399        struct DebugChainElement {
400            tag: &'static str,
401        }
402
403        impl fmt::Debug for DebugChainElement {
404            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405                f.debug_struct("DebugChainElement")
406                    .field("tag", &self.tag)
407                    .finish()
408            }
409        }
410
411        impl PartialEq for DebugChainElement {
412            fn eq(&self, other: &Self) -> bool {
413                self.tag == other.tag
414            }
415        }
416
417        impl Eq for DebugChainElement {}
418
419        impl std::hash::Hash for DebugChainElement {
420            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
421                self.tag.hash(state);
422            }
423        }
424
425        impl ModifierNodeElement for DebugChainElement {
426            type Node = DebugChainNode;
427
428            fn create(&self) -> Self::Node {
429                DebugChainNode::new(self.tag)
430            }
431
432            fn update(&self, node: &mut Self::Node) {
433                node.tag = self.tag;
434            }
435
436            fn capabilities(&self) -> NodeCapabilities {
437                NodeCapabilities::empty()
438            }
439        }
440
441        struct DebugChainNode {
442            tag: &'static str,
443            state: NodeState,
444        }
445
446        impl DebugChainNode {
447            fn new(tag: &'static str) -> Self {
448                Self {
449                    tag,
450                    state: NodeState::new(),
451                }
452            }
453        }
454
455        impl ModifierNode for DebugChainNode {
456            fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {
457                eprintln!("[debug_chain:{}] Modifier chain attached", self.tag);
458            }
459
460            fn on_detach(&mut self) {
461                eprintln!("[debug_chain:{}] Modifier chain detached", self.tag);
462            }
463
464            fn on_reset(&mut self) {
465                eprintln!("[debug_chain:{}] Modifier chain reset", self.tag);
466            }
467        }
468
469        impl cranpose_foundation::DelegatableNode for DebugChainNode {
470            fn node_state(&self) -> &NodeState {
471                &self.state
472            }
473        }
474
475        let element = DebugChainElement { tag };
476        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
477        self.then(modifier)
478            .with_inspector_metadata(inspector_metadata("debugChain", move |info| {
479                info.add_property("tag", tag);
480            }))
481    }
482
483    /// Concatenates this modifier with another.
484    ///
485    /// This creates a persistent tree structure (CombinedModifier pattern) rather than
486    /// eagerly flattening into a vector, enabling O(1) composition and structural sharing.
487    ///
488    /// Mirrors Jetpack Compose: `infix fun then(other: Modifier): Modifier =
489    ///     if (other === Modifier) this else CombinedModifier(this, other)`
490    pub fn then(&self, next: Modifier) -> Modifier {
491        if self.is_trivially_empty() {
492            return next;
493        }
494        if next.is_trivially_empty() {
495            return self.clone();
496        }
497        Modifier {
498            kind: ModifierKind::Combined {
499                outer: Rc::new(self.clone()),
500                inner: Rc::new(next),
501            },
502        }
503    }
504
505    /// Returns an iterator over the modifier elements without allocation.
506    ///
507    /// This is the preferred method for traversing modifier elements as it avoids
508    /// the O(N) allocation of `elements()`. The iterator traverses the tree structure
509    /// in-place using a stack-based approach.
510    pub(crate) fn iter_elements(&self) -> ModifierElementIterator<'_> {
511        ModifierElementIterator::new(self)
512    }
513
514    /// Returns the flattened list of elements in this modifier chain.
515    ///
516    /// **Note:** Consider using `iter_elements()` instead to avoid allocation.
517    /// This method flattens the tree structure on-demand, allocating a new Vec.
518    pub(crate) fn elements(&self) -> Vec<DynModifierElement> {
519        match &self.kind {
520            ModifierKind::Empty => Vec::new(),
521            ModifierKind::Single { elements, .. } => elements.as_ref().clone(),
522            ModifierKind::Combined { outer, inner } => {
523                let mut result = outer.elements();
524                result.extend(inner.elements());
525                result
526            }
527        }
528    }
529
530    /// Returns the flattened list of inspector metadata in this modifier chain.
531    /// For backward compatibility, this flattens the tree structure on-demand.
532    /// Note: This allocates a new Vec for Combined modifiers.
533    pub(crate) fn inspector_metadata(&self) -> Vec<InspectorMetadata> {
534        match &self.kind {
535            ModifierKind::Empty => Vec::new(),
536            ModifierKind::Single { inspector, .. } => inspector.as_ref().clone(),
537            ModifierKind::Combined { outer, inner } => {
538                let mut result = outer.inspector_metadata();
539                result.extend(inner.inspector_metadata());
540                result
541            }
542        }
543    }
544
545    pub fn total_padding(&self) -> f32 {
546        let padding = self.padding_values();
547        padding
548            .left
549            .max(padding.right)
550            .max(padding.top)
551            .max(padding.bottom)
552    }
553
554    pub fn explicit_size(&self) -> Option<Size> {
555        let props = self.layout_properties();
556        match (props.width, props.height) {
557            (DimensionConstraint::Points(width), DimensionConstraint::Points(height)) => {
558                Some(Size { width, height })
559            }
560            _ => None,
561        }
562    }
563
564    pub fn padding_values(&self) -> EdgeInsets {
565        self.resolved_modifiers().padding()
566    }
567
568    pub(crate) fn layout_properties(&self) -> LayoutProperties {
569        self.resolved_modifiers().layout_properties()
570    }
571
572    pub fn box_alignment(&self) -> Option<Alignment> {
573        self.layout_properties().box_alignment()
574    }
575
576    pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
577        self.layout_properties().column_alignment()
578    }
579
580    pub fn row_alignment(&self) -> Option<VerticalAlignment> {
581        self.layout_properties().row_alignment()
582    }
583
584    pub fn draw_commands(&self) -> Vec<DrawCommand> {
585        collect_slices_from_modifier(self).draw_commands().to_vec()
586    }
587
588    pub fn clips_to_bounds(&self) -> bool {
589        collect_slices_from_modifier(self).clip_to_bounds()
590    }
591
592    /// Returns structured inspector records for each modifier element.
593    pub fn collect_inspector_records(&self) -> Vec<ModifierInspectorRecord> {
594        self.inspector_metadata()
595            .iter()
596            .map(|metadata| metadata.to_record())
597            .collect()
598    }
599
600    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
601        let mut handle = ModifierChainHandle::new();
602        let _ = handle.update(self);
603        handle.resolved_modifiers()
604    }
605
606    fn with_element<E>(element: E) -> Self
607    where
608        E: ModifierNodeElement,
609    {
610        let dyn_element = modifier_element(element);
611        Self::from_parts(vec![dyn_element])
612    }
613
614    pub(crate) fn from_parts(elements: Vec<DynModifierElement>) -> Self {
615        if elements.is_empty() {
616            Self {
617                kind: ModifierKind::Empty,
618            }
619        } else {
620            Self {
621                kind: ModifierKind::Single {
622                    elements: Rc::new(elements),
623                    inspector: Rc::new(Vec::new()),
624                },
625            }
626        }
627    }
628
629    fn is_trivially_empty(&self) -> bool {
630        matches!(self.kind, ModifierKind::Empty)
631    }
632
633    pub(crate) fn with_inspector_metadata(self, metadata: InspectorMetadata) -> Self {
634        if metadata.is_empty() {
635            return self;
636        }
637        match self.kind {
638            ModifierKind::Empty => self,
639            ModifierKind::Single {
640                elements,
641                inspector,
642            } => {
643                let mut new_inspector = inspector.as_ref().clone();
644                new_inspector.push(metadata);
645                Self {
646                    kind: ModifierKind::Single {
647                        elements,
648                        inspector: Rc::new(new_inspector),
649                    },
650                }
651            }
652            ModifierKind::Combined { .. } => {
653                // Combined modifiers shouldn't have inspector metadata added directly
654                // This should only be called on freshly created modifiers
655                panic!("Cannot add inspector metadata to a combined modifier")
656            }
657        }
658    }
659}
660
661impl PartialEq for Modifier {
662    fn eq(&self, other: &Self) -> bool {
663        match (&self.kind, &other.kind) {
664            (ModifierKind::Empty, ModifierKind::Empty) => true,
665            (
666                ModifierKind::Single {
667                    elements: e1,
668                    inspector: _,
669                },
670                ModifierKind::Single {
671                    elements: e2,
672                    inspector: _,
673                },
674            ) => {
675                // Fast path: if they share the same Rc, they're definitely equal
676                if Rc::ptr_eq(e1, e2) {
677                    return true;
678                }
679
680                // Slow path: compare elements by value
681                if e1.len() != e2.len() {
682                    return false;
683                }
684
685                for (a, b) in e1.iter().zip(e2.iter()) {
686                    if !a.equals_element(&**b) {
687                        return false;
688                    }
689                }
690                true
691            }
692            (
693                ModifierKind::Combined {
694                    outer: o1,
695                    inner: i1,
696                },
697                ModifierKind::Combined {
698                    outer: o2,
699                    inner: i2,
700                },
701            ) => {
702                // Fast path: if they share the same Rc pointers, they're definitely equal
703                if Rc::ptr_eq(o1, o2) && Rc::ptr_eq(i1, i2) {
704                    return true;
705                }
706                // Recursive comparison
707                o1 == o2 && i1 == i2
708            }
709            _ => false,
710        }
711    }
712}
713
714impl Eq for Modifier {}
715
716impl fmt::Display for Modifier {
717    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
718        match &self.kind {
719            ModifierKind::Empty => write!(f, "Modifier.empty"),
720            ModifierKind::Single { elements, .. } => {
721                if elements.is_empty() {
722                    return write!(f, "Modifier.empty");
723                }
724                write!(f, "Modifier[")?;
725                for (index, element) in elements.iter().enumerate() {
726                    if index > 0 {
727                        write!(f, ", ")?;
728                    }
729                    let name = element.inspector_name();
730                    let mut properties = Vec::new();
731                    element.record_inspector_properties(&mut |prop, value| {
732                        properties.push(format!("{prop}={value}"));
733                    });
734                    if properties.is_empty() {
735                        write!(f, "{name}")?;
736                    } else {
737                        write!(f, "{name}({})", properties.join(", "))?;
738                    }
739                }
740                write!(f, "]")
741            }
742            ModifierKind::Combined { outer: _, inner: _ } => {
743                // Flatten the representation for display
744                // This matches Kotlin's CombinedModifier toString behavior
745                write!(f, "[")?;
746                let elements = self.elements();
747                for (index, element) in elements.iter().enumerate() {
748                    if index > 0 {
749                        write!(f, ", ")?;
750                    }
751                    let name = element.inspector_name();
752                    let mut properties = Vec::new();
753                    element.record_inspector_properties(&mut |prop, value| {
754                        properties.push(format!("{prop}={value}"));
755                    });
756                    if properties.is_empty() {
757                        write!(f, "{name}")?;
758                    } else {
759                        write!(f, "{name}({})", properties.join(", "))?;
760                    }
761                }
762                write!(f, "]")
763            }
764        }
765    }
766}
767
768impl fmt::Debug for Modifier {
769    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
770        fmt::Display::fmt(self, f)
771    }
772}
773
774#[derive(Clone, Copy, Debug, PartialEq)]
775pub struct ResolvedBackground {
776    color: Color,
777    shape: Option<RoundedCornerShape>,
778}
779
780impl ResolvedBackground {
781    pub fn new(color: Color, shape: Option<RoundedCornerShape>) -> Self {
782        Self { color, shape }
783    }
784
785    pub fn color(&self) -> Color {
786        self.color
787    }
788
789    pub fn shape(&self) -> Option<RoundedCornerShape> {
790        self.shape
791    }
792
793    pub fn set_shape(&mut self, shape: Option<RoundedCornerShape>) {
794        self.shape = shape;
795    }
796}
797
798#[derive(Clone, Copy, Debug, PartialEq, Default)]
799pub struct ResolvedModifiers {
800    padding: EdgeInsets,
801    layout: LayoutProperties,
802    offset: Point,
803}
804
805impl ResolvedModifiers {
806    pub fn padding(&self) -> EdgeInsets {
807        self.padding
808    }
809
810    pub fn layout_properties(&self) -> LayoutProperties {
811        self.layout
812    }
813
814    pub fn offset(&self) -> Point {
815        self.offset
816    }
817
818    pub(crate) fn set_padding(&mut self, padding: EdgeInsets) {
819        self.padding = padding;
820    }
821
822    pub(crate) fn set_layout_properties(&mut self, layout: LayoutProperties) {
823        self.layout = layout;
824    }
825
826    pub(crate) fn set_offset(&mut self, offset: Point) {
827        self.offset = offset;
828    }
829}
830
831#[derive(Clone, Copy, Debug, Default, PartialEq)]
832pub enum DimensionConstraint {
833    #[default]
834    Unspecified,
835    Points(f32),
836    Fraction(f32),
837    Intrinsic(IntrinsicSize),
838}
839
840#[derive(Clone, Copy, Debug, Default, PartialEq)]
841pub struct LayoutWeight {
842    pub weight: f32,
843    pub fill: bool,
844}
845
846#[derive(Clone, Copy, Debug, Default, PartialEq)]
847pub struct LayoutProperties {
848    padding: EdgeInsets,
849    width: DimensionConstraint,
850    height: DimensionConstraint,
851    min_width: Option<f32>,
852    min_height: Option<f32>,
853    max_width: Option<f32>,
854    max_height: Option<f32>,
855    weight: Option<LayoutWeight>,
856    box_alignment: Option<Alignment>,
857    column_alignment: Option<HorizontalAlignment>,
858    row_alignment: Option<VerticalAlignment>,
859}
860
861impl LayoutProperties {
862    pub fn padding(&self) -> EdgeInsets {
863        self.padding
864    }
865
866    pub fn width(&self) -> DimensionConstraint {
867        self.width
868    }
869
870    pub fn height(&self) -> DimensionConstraint {
871        self.height
872    }
873
874    pub fn min_width(&self) -> Option<f32> {
875        self.min_width
876    }
877
878    pub fn min_height(&self) -> Option<f32> {
879        self.min_height
880    }
881
882    pub fn max_width(&self) -> Option<f32> {
883        self.max_width
884    }
885
886    pub fn max_height(&self) -> Option<f32> {
887        self.max_height
888    }
889
890    pub fn weight(&self) -> Option<LayoutWeight> {
891        self.weight
892    }
893
894    pub fn box_alignment(&self) -> Option<Alignment> {
895        self.box_alignment
896    }
897
898    pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
899        self.column_alignment
900    }
901
902    pub fn row_alignment(&self) -> Option<VerticalAlignment> {
903        self.row_alignment
904    }
905}
906
907#[cfg(test)]
908#[path = "tests/modifier_tests.rs"]
909mod tests;