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