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/// Modifiers form a persistent tree structure (via CombinedModifier pattern)
324/// to enable O(1) composition and structural sharing during recomposition.
325#[derive(Clone)]
326pub struct Modifier {
327    kind: ModifierKind,
328}
329
330impl Default for Modifier {
331    fn default() -> Self {
332        Self {
333            kind: ModifierKind::Empty,
334        }
335    }
336}
337
338impl Modifier {
339    pub fn empty() -> Self {
340        Self::default()
341    }
342
343    /// Clip the content to the bounds of this modifier.
344    ///
345    /// Example: `Modifier::empty().clip_to_bounds()`
346    pub fn clip_to_bounds(self) -> Self {
347        let modifier = Self::with_element(ClipToBoundsElement::new()).with_inspector_metadata(
348            inspector_metadata("clipToBounds", |info| {
349                info.add_property("clipToBounds", "true");
350            }),
351        );
352        self.then(modifier)
353    }
354
355    pub fn modifier_local_provider<T, F>(self, key: ModifierLocalKey<T>, value: F) -> Self
356    where
357        T: 'static,
358        F: Fn() -> T + 'static,
359    {
360        let element = ModifierLocalProviderElement::new(key, value);
361        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
362        self.then(modifier)
363    }
364
365    pub fn modifier_local_consumer<F>(self, consumer: F) -> Self
366    where
367        F: for<'scope> Fn(&mut ModifierLocalReadScope<'scope>) + 'static,
368    {
369        let element = ModifierLocalConsumerElement::new(consumer);
370        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
371        self.then(modifier)
372    }
373
374    pub fn semantics<F>(self, recorder: F) -> Self
375    where
376        F: Fn(&mut SemanticsConfiguration) + 'static,
377    {
378        let mut preview = SemanticsConfiguration::default();
379        recorder(&mut preview);
380        let description = preview.content_description.clone();
381        let is_button = preview.is_button;
382        let is_clickable = preview.is_clickable;
383        let metadata = inspector_metadata("semantics", move |info| {
384            if let Some(desc) = &description {
385                info.add_property("contentDescription", desc.clone());
386            }
387            if is_button {
388                info.add_property("isButton", "true");
389            }
390            if is_clickable {
391                info.add_property("isClickable", "true");
392            }
393        });
394        let element = SemanticsElement::new(recorder);
395        let modifier =
396            Modifier::from_parts(vec![modifier_element(element)]).with_inspector_metadata(metadata);
397        self.then(modifier)
398    }
399
400    /// Makes this component focusable.
401    ///
402    /// This adds a focus target node that can receive focus and participate
403    /// in focus traversal. The component will be included in tab order and
404    /// can be focused programmatically.
405    pub fn focus_target(self) -> Self {
406        let element = FocusTargetElement::new();
407        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
408        self.then(modifier)
409    }
410
411    /// Makes this component focusable with a callback for focus changes.
412    ///
413    /// The callback is invoked whenever the focus state changes, allowing
414    /// components to react to gaining or losing focus.
415    pub fn on_focus_changed<F>(self, callback: F) -> Self
416    where
417        F: Fn(FocusState) + 'static,
418    {
419        let element = FocusTargetElement::with_callback(callback);
420        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
421        self.then(modifier)
422    }
423
424    /// Attaches a focus requester to this component.
425    ///
426    /// The requester can be used to programmatically request focus for
427    /// this component from application code.
428    pub fn focus_requester(self, requester: &FocusRequester) -> Self {
429        let element = FocusRequesterElement::new(requester.id());
430        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
431        self.then(modifier)
432    }
433
434    /// Enables debug logging for this modifier chain.
435    ///
436    /// When enabled, logs the entire modifier chain structure including:
437    /// - Element types and their properties
438    /// - Inspector metadata
439    /// - Capability flags
440    ///
441    /// This is useful for debugging modifier composition issues and understanding
442    /// how the modifier chain is structured at runtime.
443    ///
444    /// Example:
445    /// ```text
446    /// Modifier::empty()
447    ///     .padding(8.0)
448    ///     .background(Color(1.0, 0.0, 0.0, 1.0))
449    ///     .debug_chain("MyWidget")
450    /// ```
451    pub fn debug_chain(self, tag: &'static str) -> Self {
452        use cranpose_foundation::{ModifierNode, ModifierNodeContext, NodeCapabilities, NodeState};
453
454        #[derive(Clone)]
455        struct DebugChainElement {
456            tag: &'static str,
457        }
458
459        impl fmt::Debug for DebugChainElement {
460            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
461                f.debug_struct("DebugChainElement")
462                    .field("tag", &self.tag)
463                    .finish()
464            }
465        }
466
467        impl PartialEq for DebugChainElement {
468            fn eq(&self, other: &Self) -> bool {
469                self.tag == other.tag
470            }
471        }
472
473        impl Eq for DebugChainElement {}
474
475        impl std::hash::Hash for DebugChainElement {
476            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
477                self.tag.hash(state);
478            }
479        }
480
481        impl ModifierNodeElement for DebugChainElement {
482            type Node = DebugChainNode;
483
484            fn create(&self) -> Self::Node {
485                DebugChainNode::new(self.tag)
486            }
487
488            fn update(&self, node: &mut Self::Node) {
489                node.tag = self.tag;
490            }
491
492            fn capabilities(&self) -> NodeCapabilities {
493                NodeCapabilities::empty()
494            }
495        }
496
497        struct DebugChainNode {
498            tag: &'static str,
499            state: NodeState,
500        }
501
502        impl DebugChainNode {
503            fn new(tag: &'static str) -> Self {
504                Self {
505                    tag,
506                    state: NodeState::new(),
507                }
508            }
509        }
510
511        impl ModifierNode for DebugChainNode {
512            fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {
513                eprintln!("[debug_chain:{}] Modifier chain attached", self.tag);
514            }
515
516            fn on_detach(&mut self) {
517                eprintln!("[debug_chain:{}] Modifier chain detached", self.tag);
518            }
519
520            fn on_reset(&mut self) {
521                eprintln!("[debug_chain:{}] Modifier chain reset", self.tag);
522            }
523        }
524
525        impl cranpose_foundation::DelegatableNode for DebugChainNode {
526            fn node_state(&self) -> &NodeState {
527                &self.state
528            }
529        }
530
531        let element = DebugChainElement { tag };
532        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
533        self.then(modifier)
534            .with_inspector_metadata(inspector_metadata("debugChain", move |info| {
535                info.add_property("tag", tag);
536            }))
537    }
538
539    /// Concatenates this modifier with another.
540    ///
541    /// This creates a persistent tree structure (CombinedModifier pattern) rather than
542    /// eagerly flattening into a vector, enabling O(1) composition and structural sharing.
543    ///
544    /// Mirrors Jetpack Compose: `infix fun then(other: Modifier): Modifier =
545    ///     if (other === Modifier) this else CombinedModifier(this, other)`
546    pub fn then(&self, next: Modifier) -> Modifier {
547        if self.is_trivially_empty() {
548            return next;
549        }
550        if next.is_trivially_empty() {
551            return self.clone();
552        }
553        Modifier {
554            kind: ModifierKind::Combined {
555                outer: Rc::new(self.clone()),
556                inner: Rc::new(next),
557            },
558        }
559    }
560
561    /// Returns an iterator over the modifier elements without allocation.
562    ///
563    /// This is the preferred method for traversing modifier elements as it avoids
564    /// the O(N) allocation of `elements()`. The iterator traverses the tree structure
565    /// in-place using a stack-based approach.
566    pub(crate) fn iter_elements(&self) -> ModifierElementIterator<'_> {
567        ModifierElementIterator::new(self)
568    }
569
570    pub(crate) fn iter_inspector_metadata(&self) -> ModifierInspectorIterator<'_> {
571        ModifierInspectorIterator::new(self)
572    }
573
574    /// Returns the flattened list of elements in this modifier chain.
575    ///
576    /// **Note:** Consider using `iter_elements()` instead to avoid allocation.
577    /// This method flattens the tree structure on-demand, allocating a new Vec.
578    pub(crate) fn elements(&self) -> Vec<DynModifierElement> {
579        match &self.kind {
580            ModifierKind::Empty => Vec::new(),
581            ModifierKind::Single { elements, .. } => elements.as_ref().clone(),
582            ModifierKind::Combined { outer, inner } => {
583                let mut result = outer.elements();
584                result.extend(inner.elements());
585                result
586            }
587        }
588    }
589
590    /// Returns the flattened list of inspector metadata in this modifier chain.
591    /// For backward compatibility, this flattens the tree structure on-demand.
592    /// Note: This allocates a new Vec for Combined modifiers.
593    pub(crate) fn inspector_metadata(&self) -> Vec<InspectorMetadata> {
594        match &self.kind {
595            ModifierKind::Empty => Vec::new(),
596            ModifierKind::Single { inspector, .. } => inspector.as_ref().clone(),
597            ModifierKind::Combined { outer, inner } => {
598                let mut result = outer.inspector_metadata();
599                result.extend(inner.inspector_metadata());
600                result
601            }
602        }
603    }
604
605    pub fn total_padding(&self) -> f32 {
606        let padding = self.padding_values();
607        padding
608            .left
609            .max(padding.right)
610            .max(padding.top)
611            .max(padding.bottom)
612    }
613
614    pub fn explicit_size(&self) -> Option<Size> {
615        let props = self.layout_properties();
616        match (props.width, props.height) {
617            (DimensionConstraint::Points(width), DimensionConstraint::Points(height)) => {
618                Some(Size { width, height })
619            }
620            _ => None,
621        }
622    }
623
624    pub fn padding_values(&self) -> EdgeInsets {
625        self.resolved_modifiers().padding()
626    }
627
628    pub(crate) fn layout_properties(&self) -> LayoutProperties {
629        self.resolved_modifiers().layout_properties()
630    }
631
632    pub fn box_alignment(&self) -> Option<Alignment> {
633        self.layout_properties().box_alignment()
634    }
635
636    pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
637        self.layout_properties().column_alignment()
638    }
639
640    pub fn row_alignment(&self) -> Option<VerticalAlignment> {
641        self.layout_properties().row_alignment()
642    }
643
644    pub fn draw_commands(&self) -> Vec<DrawCommand> {
645        collect_slices_from_modifier(self).draw_commands().to_vec()
646    }
647
648    pub fn clips_to_bounds(&self) -> bool {
649        collect_slices_from_modifier(self).clip_to_bounds()
650    }
651
652    /// Returns structured inspector records for each modifier element.
653    pub fn collect_inspector_records(&self) -> Vec<ModifierInspectorRecord> {
654        self.inspector_metadata()
655            .iter()
656            .map(|metadata| metadata.to_record())
657            .collect()
658    }
659
660    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
661        let mut handle = ModifierChainHandle::new();
662        let _ = handle.update(self);
663        handle.resolved_modifiers()
664    }
665
666    fn with_element<E>(element: E) -> Self
667    where
668        E: ModifierNodeElement,
669    {
670        let dyn_element = modifier_element(element);
671        Self::from_parts(vec![dyn_element])
672    }
673
674    pub(crate) fn from_parts(elements: Vec<DynModifierElement>) -> Self {
675        if elements.is_empty() {
676            Self {
677                kind: ModifierKind::Empty,
678            }
679        } else {
680            Self {
681                kind: ModifierKind::Single {
682                    elements: Rc::new(elements),
683                    inspector: Rc::new(Vec::new()),
684                },
685            }
686        }
687    }
688
689    fn is_trivially_empty(&self) -> bool {
690        matches!(self.kind, ModifierKind::Empty)
691    }
692
693    pub(crate) fn with_inspector_metadata(self, metadata: InspectorMetadata) -> Self {
694        if metadata.is_empty() {
695            return self;
696        }
697        match self.kind {
698            ModifierKind::Empty => self,
699            ModifierKind::Single {
700                elements,
701                inspector,
702            } => {
703                let mut new_inspector = inspector.as_ref().clone();
704                new_inspector.push(metadata);
705                Self {
706                    kind: ModifierKind::Single {
707                        elements,
708                        inspector: Rc::new(new_inspector),
709                    },
710                }
711            }
712            ModifierKind::Combined { .. } => {
713                // Combined modifiers shouldn't have inspector metadata added directly
714                // This should only be called on freshly created modifiers
715                panic!("Cannot add inspector metadata to a combined modifier")
716            }
717        }
718    }
719
720    /// Checks whether two modifiers are structurally equivalent for layout decisions.
721    ///
722    /// This ignores identity-sensitive modifier elements (e.g., draw closures) so
723    /// draw-only updates do not force measure/layout invalidation.
724    pub fn structural_eq(&self, other: &Self) -> bool {
725        self.eq_internal(other, false)
726    }
727
728    fn eq_internal(&self, other: &Self, consider_always_update: bool) -> bool {
729        match (&self.kind, &other.kind) {
730            (ModifierKind::Empty, ModifierKind::Empty) => true,
731            (
732                ModifierKind::Single {
733                    elements: e1,
734                    inspector: _,
735                },
736                ModifierKind::Single {
737                    elements: e2,
738                    inspector: _,
739                },
740            ) => {
741                if Rc::ptr_eq(e1, e2) {
742                    return true;
743                }
744
745                if e1.len() != e2.len() {
746                    return false;
747                }
748
749                for (a, b) in e1.iter().zip(e2.iter()) {
750                    if consider_always_update && (a.requires_update() || b.requires_update()) {
751                        if !Rc::ptr_eq(a, b) {
752                            return false;
753                        }
754                        continue;
755                    }
756
757                    if !a.equals_element(&**b) {
758                        return false;
759                    }
760                }
761
762                true
763            }
764            (
765                ModifierKind::Combined {
766                    outer: o1,
767                    inner: i1,
768                },
769                ModifierKind::Combined {
770                    outer: o2,
771                    inner: i2,
772                },
773            ) => {
774                if Rc::ptr_eq(o1, o2) && Rc::ptr_eq(i1, i2) {
775                    return true;
776                }
777                o1.as_ref().eq_internal(o2.as_ref(), consider_always_update)
778                    && i1.as_ref().eq_internal(i2.as_ref(), consider_always_update)
779            }
780            _ => false,
781        }
782    }
783}
784
785impl PartialEq for Modifier {
786    fn eq(&self, other: &Self) -> bool {
787        self.eq_internal(other, true)
788    }
789}
790
791impl Eq for Modifier {}
792
793impl fmt::Display for Modifier {
794    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
795        match &self.kind {
796            ModifierKind::Empty => write!(f, "Modifier.empty"),
797            ModifierKind::Single { elements, .. } => {
798                if elements.is_empty() {
799                    return write!(f, "Modifier.empty");
800                }
801                write!(f, "Modifier[")?;
802                for (index, element) in elements.iter().enumerate() {
803                    if index > 0 {
804                        write!(f, ", ")?;
805                    }
806                    let name = element.inspector_name();
807                    let mut properties = Vec::new();
808                    element.record_inspector_properties(&mut |prop, value| {
809                        properties.push(format!("{prop}={value}"));
810                    });
811                    if properties.is_empty() {
812                        write!(f, "{name}")?;
813                    } else {
814                        write!(f, "{name}({})", properties.join(", "))?;
815                    }
816                }
817                write!(f, "]")
818            }
819            ModifierKind::Combined { outer: _, inner: _ } => {
820                // Flatten the representation for display
821                // This matches Kotlin's CombinedModifier toString behavior
822                write!(f, "[")?;
823                let elements = self.elements();
824                for (index, element) in elements.iter().enumerate() {
825                    if index > 0 {
826                        write!(f, ", ")?;
827                    }
828                    let name = element.inspector_name();
829                    let mut properties = Vec::new();
830                    element.record_inspector_properties(&mut |prop, value| {
831                        properties.push(format!("{prop}={value}"));
832                    });
833                    if properties.is_empty() {
834                        write!(f, "{name}")?;
835                    } else {
836                        write!(f, "{name}({})", properties.join(", "))?;
837                    }
838                }
839                write!(f, "]")
840            }
841        }
842    }
843}
844
845impl fmt::Debug for Modifier {
846    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
847        fmt::Display::fmt(self, f)
848    }
849}
850
851#[derive(Clone, Copy, Debug, PartialEq)]
852pub struct ResolvedBackground {
853    color: Color,
854    shape: Option<RoundedCornerShape>,
855}
856
857impl ResolvedBackground {
858    pub fn new(color: Color, shape: Option<RoundedCornerShape>) -> Self {
859        Self { color, shape }
860    }
861
862    pub fn color(&self) -> Color {
863        self.color
864    }
865
866    pub fn shape(&self) -> Option<RoundedCornerShape> {
867        self.shape
868    }
869
870    pub fn set_shape(&mut self, shape: Option<RoundedCornerShape>) {
871        self.shape = shape;
872    }
873}
874
875#[derive(Clone, Copy, Debug, PartialEq, Default)]
876pub struct ResolvedModifiers {
877    padding: EdgeInsets,
878    layout: LayoutProperties,
879    offset: Point,
880}
881
882impl ResolvedModifiers {
883    pub fn padding(&self) -> EdgeInsets {
884        self.padding
885    }
886
887    pub fn layout_properties(&self) -> LayoutProperties {
888        self.layout
889    }
890
891    pub fn offset(&self) -> Point {
892        self.offset
893    }
894
895    pub(crate) fn set_padding(&mut self, padding: EdgeInsets) {
896        self.padding = padding;
897    }
898
899    pub(crate) fn set_layout_properties(&mut self, layout: LayoutProperties) {
900        self.layout = layout;
901    }
902
903    pub(crate) fn set_offset(&mut self, offset: Point) {
904        self.offset = offset;
905    }
906}
907
908#[derive(Clone, Copy, Debug, Default, PartialEq)]
909pub enum DimensionConstraint {
910    #[default]
911    Unspecified,
912    Points(f32),
913    Fraction(f32),
914    Intrinsic(IntrinsicSize),
915}
916
917#[derive(Clone, Copy, Debug, Default, PartialEq)]
918pub struct LayoutWeight {
919    pub weight: f32,
920    pub fill: bool,
921}
922
923#[derive(Clone, Copy, Debug, Default, PartialEq)]
924pub struct LayoutProperties {
925    padding: EdgeInsets,
926    width: DimensionConstraint,
927    height: DimensionConstraint,
928    min_width: Option<f32>,
929    min_height: Option<f32>,
930    max_width: Option<f32>,
931    max_height: Option<f32>,
932    weight: Option<LayoutWeight>,
933    box_alignment: Option<Alignment>,
934    column_alignment: Option<HorizontalAlignment>,
935    row_alignment: Option<VerticalAlignment>,
936}
937
938impl LayoutProperties {
939    pub fn padding(&self) -> EdgeInsets {
940        self.padding
941    }
942
943    pub fn width(&self) -> DimensionConstraint {
944        self.width
945    }
946
947    pub fn height(&self) -> DimensionConstraint {
948        self.height
949    }
950
951    pub fn min_width(&self) -> Option<f32> {
952        self.min_width
953    }
954
955    pub fn min_height(&self) -> Option<f32> {
956        self.min_height
957    }
958
959    pub fn max_width(&self) -> Option<f32> {
960        self.max_width
961    }
962
963    pub fn max_height(&self) -> Option<f32> {
964        self.max_height
965    }
966
967    pub fn weight(&self) -> Option<LayoutWeight> {
968        self.weight
969    }
970
971    pub fn box_alignment(&self) -> Option<Alignment> {
972        self.box_alignment
973    }
974
975    pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
976        self.column_alignment
977    }
978
979    pub fn row_alignment(&self) -> Option<VerticalAlignment> {
980        self.row_alignment
981    }
982}
983
984#[cfg(test)]
985#[path = "tests/modifier_tests.rs"]
986mod tests;