Skip to main content

cranpose_ui/modifier/
mod.rs

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