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