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