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