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