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