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    /// Checks whether two modifiers are structurally equivalent for layout decisions.
661    ///
662    /// This ignores identity-sensitive modifier elements (e.g., draw closures) so
663    /// draw-only updates do not force measure/layout invalidation.
664    pub fn structural_eq(&self, other: &Self) -> bool {
665        self.eq_internal(other, false)
666    }
667
668    fn eq_internal(&self, other: &Self, consider_always_update: bool) -> bool {
669        match (&self.kind, &other.kind) {
670            (ModifierKind::Empty, ModifierKind::Empty) => true,
671            (
672                ModifierKind::Single {
673                    elements: e1,
674                    inspector: _,
675                },
676                ModifierKind::Single {
677                    elements: e2,
678                    inspector: _,
679                },
680            ) => {
681                if Rc::ptr_eq(e1, e2) {
682                    return true;
683                }
684
685                if e1.len() != e2.len() {
686                    return false;
687                }
688
689                for (a, b) in e1.iter().zip(e2.iter()) {
690                    if consider_always_update && (a.requires_update() || b.requires_update()) {
691                        if !Rc::ptr_eq(a, b) {
692                            return false;
693                        }
694                        continue;
695                    }
696
697                    if !a.equals_element(&**b) {
698                        return false;
699                    }
700                }
701
702                true
703            }
704            (
705                ModifierKind::Combined {
706                    outer: o1,
707                    inner: i1,
708                },
709                ModifierKind::Combined {
710                    outer: o2,
711                    inner: i2,
712                },
713            ) => {
714                if Rc::ptr_eq(o1, o2) && Rc::ptr_eq(i1, i2) {
715                    return true;
716                }
717                o1.as_ref().eq_internal(o2.as_ref(), consider_always_update)
718                    && i1.as_ref().eq_internal(i2.as_ref(), consider_always_update)
719            }
720            _ => false,
721        }
722    }
723}
724
725impl PartialEq for Modifier {
726    fn eq(&self, other: &Self) -> bool {
727        self.eq_internal(other, true)
728    }
729}
730
731impl Eq for Modifier {}
732
733impl fmt::Display for Modifier {
734    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
735        match &self.kind {
736            ModifierKind::Empty => write!(f, "Modifier.empty"),
737            ModifierKind::Single { elements, .. } => {
738                if elements.is_empty() {
739                    return write!(f, "Modifier.empty");
740                }
741                write!(f, "Modifier[")?;
742                for (index, element) in elements.iter().enumerate() {
743                    if index > 0 {
744                        write!(f, ", ")?;
745                    }
746                    let name = element.inspector_name();
747                    let mut properties = Vec::new();
748                    element.record_inspector_properties(&mut |prop, value| {
749                        properties.push(format!("{prop}={value}"));
750                    });
751                    if properties.is_empty() {
752                        write!(f, "{name}")?;
753                    } else {
754                        write!(f, "{name}({})", properties.join(", "))?;
755                    }
756                }
757                write!(f, "]")
758            }
759            ModifierKind::Combined { outer: _, inner: _ } => {
760                // Flatten the representation for display
761                // This matches Kotlin's CombinedModifier toString behavior
762                write!(f, "[")?;
763                let elements = self.elements();
764                for (index, element) in elements.iter().enumerate() {
765                    if index > 0 {
766                        write!(f, ", ")?;
767                    }
768                    let name = element.inspector_name();
769                    let mut properties = Vec::new();
770                    element.record_inspector_properties(&mut |prop, value| {
771                        properties.push(format!("{prop}={value}"));
772                    });
773                    if properties.is_empty() {
774                        write!(f, "{name}")?;
775                    } else {
776                        write!(f, "{name}({})", properties.join(", "))?;
777                    }
778                }
779                write!(f, "]")
780            }
781        }
782    }
783}
784
785impl fmt::Debug for Modifier {
786    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
787        fmt::Display::fmt(self, f)
788    }
789}
790
791#[derive(Clone, Copy, Debug, PartialEq)]
792pub struct ResolvedBackground {
793    color: Color,
794    shape: Option<RoundedCornerShape>,
795}
796
797impl ResolvedBackground {
798    pub fn new(color: Color, shape: Option<RoundedCornerShape>) -> Self {
799        Self { color, shape }
800    }
801
802    pub fn color(&self) -> Color {
803        self.color
804    }
805
806    pub fn shape(&self) -> Option<RoundedCornerShape> {
807        self.shape
808    }
809
810    pub fn set_shape(&mut self, shape: Option<RoundedCornerShape>) {
811        self.shape = shape;
812    }
813}
814
815#[derive(Clone, Copy, Debug, PartialEq, Default)]
816pub struct ResolvedModifiers {
817    padding: EdgeInsets,
818    layout: LayoutProperties,
819    offset: Point,
820}
821
822impl ResolvedModifiers {
823    pub fn padding(&self) -> EdgeInsets {
824        self.padding
825    }
826
827    pub fn layout_properties(&self) -> LayoutProperties {
828        self.layout
829    }
830
831    pub fn offset(&self) -> Point {
832        self.offset
833    }
834
835    pub(crate) fn set_padding(&mut self, padding: EdgeInsets) {
836        self.padding = padding;
837    }
838
839    pub(crate) fn set_layout_properties(&mut self, layout: LayoutProperties) {
840        self.layout = layout;
841    }
842
843    pub(crate) fn set_offset(&mut self, offset: Point) {
844        self.offset = offset;
845    }
846}
847
848#[derive(Clone, Copy, Debug, Default, PartialEq)]
849pub enum DimensionConstraint {
850    #[default]
851    Unspecified,
852    Points(f32),
853    Fraction(f32),
854    Intrinsic(IntrinsicSize),
855}
856
857#[derive(Clone, Copy, Debug, Default, PartialEq)]
858pub struct LayoutWeight {
859    pub weight: f32,
860    pub fill: bool,
861}
862
863#[derive(Clone, Copy, Debug, Default, PartialEq)]
864pub struct LayoutProperties {
865    padding: EdgeInsets,
866    width: DimensionConstraint,
867    height: DimensionConstraint,
868    min_width: Option<f32>,
869    min_height: Option<f32>,
870    max_width: Option<f32>,
871    max_height: Option<f32>,
872    weight: Option<LayoutWeight>,
873    box_alignment: Option<Alignment>,
874    column_alignment: Option<HorizontalAlignment>,
875    row_alignment: Option<VerticalAlignment>,
876}
877
878impl LayoutProperties {
879    pub fn padding(&self) -> EdgeInsets {
880        self.padding
881    }
882
883    pub fn width(&self) -> DimensionConstraint {
884        self.width
885    }
886
887    pub fn height(&self) -> DimensionConstraint {
888        self.height
889    }
890
891    pub fn min_width(&self) -> Option<f32> {
892        self.min_width
893    }
894
895    pub fn min_height(&self) -> Option<f32> {
896        self.min_height
897    }
898
899    pub fn max_width(&self) -> Option<f32> {
900        self.max_width
901    }
902
903    pub fn max_height(&self) -> Option<f32> {
904        self.max_height
905    }
906
907    pub fn weight(&self) -> Option<LayoutWeight> {
908        self.weight
909    }
910
911    pub fn box_alignment(&self) -> Option<Alignment> {
912        self.box_alignment
913    }
914
915    pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
916        self.column_alignment
917    }
918
919    pub fn row_alignment(&self) -> Option<VerticalAlignment> {
920        self.row_alignment
921    }
922}
923
924#[cfg(test)]
925#[path = "tests/modifier_tests.rs"]
926mod tests;