Skip to main content

cranpose_ui/modifier/
mod.rs

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