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