Skip to main content

cranpose_ui/modifier/
mod.rs

1//! Modifier system for Cranpose
2//!
3//! This module now acts as a thin builder around modifier elements. Each
4//! [`Modifier`] stores the element chain required by the modifier node system
5//! together with inspector metadata while resolved state is computed directly
6//! from the modifier nodes.
7
8#![allow(non_snake_case)]
9
10use std::{
11    fmt,
12    hash::{Hash, Hasher},
13    rc::Rc,
14};
15
16use cranpose_core::hash::default;
17
18mod alignment;
19mod background;
20mod blur;
21mod chain;
22mod clickable;
23mod draw_cache;
24mod fill;
25mod focus;
26mod focus_ring;
27mod graphics_layer;
28mod local;
29mod minimum_interactive;
30mod offset;
31mod padding;
32mod pointer_icon;
33pub(crate) mod pointer_input;
34mod rotary_input;
35mod scroll;
36mod selectable;
37mod semantics;
38mod shadow;
39mod size;
40mod slices;
41mod toggleable;
42mod weight;
43
44#[allow(unused_imports)]
45pub use chain::{ModifierChainHandle, ModifierChainInspectorNode, ModifierLocalsHandle};
46pub use cranpose_foundation::{
47    AnyModifierElement, DynModifierElement, FocusState, PointerEvent, PointerEventKind,
48    PointerSource, RotaryScrollEvent, SemanticsConfiguration, modifier_element,
49};
50use cranpose_foundation::{ModifierNodeElement, NodeCapabilities, ProgressBarRangeInfo};
51#[allow(unused_imports)]
52pub use cranpose_ui_graphics::{
53    BlendMode, BlurredEdgeTreatment, Brush, Color, ColorFilter, CompositingStrategy, CornerRadii,
54    CursorIcon, CustomPointerIcon, CutDirection, Dp, DpOffset, EdgeInsets, GradientCutMaskSpec,
55    GradientFadeMaskSpec, GraphicsLayer, LayerShape, Point, PointerIcon, PointerIconError, Rect,
56    RenderEffect, RoundedCornerShape, RuntimeShader, Shadow, ShadowScope, Size, TransformOrigin,
57};
58use cranpose_ui_layout::{Alignment, HorizontalAlignment, IntrinsicSize, VerticalAlignment};
59use focus::FocusTargetElement;
60#[allow(unused_imports)]
61pub use focus::{FocusDirection, FocusRequestError, FocusRequester, FocusRequesterElement};
62pub use graphics_layer::GlassMaterial;
63pub(crate) use local::{
64    ModifierLocalAncestorResolver, ModifierLocalSource, ModifierLocalToken, ResolvedModifierLocal,
65};
66use local::{ModifierLocalConsumerElement, ModifierLocalProviderElement};
67#[allow(unused_imports)]
68pub use local::{ModifierLocalKey, ModifierLocalReadScope};
69#[allow(unused_imports)]
70pub use pointer_input::{AwaitPointerEventScope, PointerInputScope};
71pub use rotary_input::RotaryInputModifierNode;
72#[cfg(test)]
73pub(crate) use scroll::lazy_scroll_semantics;
74#[cfg(feature = "test-helpers")]
75pub use scroll::{last_fling_velocity, reset_last_fling_velocity};
76use semantics::SemanticsElement;
77pub use semantics::{
78    SemanticsRequester, SemanticsRequesterElement, collect_semantics_from_chain,
79    collect_semantics_from_modifier,
80};
81pub use slices::{
82    ModifierNodeSlices, ModifierNodeSlicesDebugStats, collect_modifier_slices,
83    collect_modifier_slices_into, collect_slices_from_modifier,
84};
85
86pub use crate::draw::{DrawCacheBuilder, DrawCommand};
87use crate::modifier_nodes::ClipToBoundsElement;
88
89#[derive(Clone, Debug, Default)]
90pub struct InspectorInfo {
91    properties: Vec<InspectorProperty>,
92}
93
94impl InspectorInfo {
95    pub fn new() -> Self {
96        Self::default()
97    }
98
99    pub fn add_property<V: Into<String>>(&mut self, name: &'static str, value: V) {
100        self.properties.push(InspectorProperty {
101            name,
102            value: value.into(),
103        });
104    }
105
106    pub fn properties(&self) -> &[InspectorProperty] {
107        &self.properties
108    }
109
110    pub fn is_empty(&self) -> bool {
111        self.properties.is_empty()
112    }
113
114    pub fn add_dimension(&mut self, name: &'static str, constraint: DimensionConstraint) {
115        self.add_property(name, describe_dimension(constraint));
116    }
117
118    pub fn add_offset_components(
119        &mut self,
120        x_name: &'static str,
121        y_name: &'static str,
122        offset: Point,
123    ) {
124        self.add_property(x_name, offset.x.to_string());
125        self.add_property(y_name, offset.y.to_string());
126    }
127
128    pub fn add_alignment<A>(&mut self, name: &'static str, alignment: A)
129    where
130        A: fmt::Debug,
131    {
132        self.add_property(name, format!("{alignment:?}"));
133    }
134}
135
136#[derive(Clone, Debug, PartialEq)]
137pub struct InspectorProperty {
138    pub name: &'static str,
139    pub value: String,
140}
141
142#[derive(Clone, Debug, PartialEq)]
143pub struct ModifierInspectorRecord {
144    pub name: &'static str,
145    pub properties: Vec<InspectorProperty>,
146}
147
148#[derive(Clone, Debug)]
149pub(crate) struct InspectorMetadata {
150    name: &'static str,
151    info: InspectorInfo,
152}
153
154impl InspectorMetadata {
155    pub(crate) fn new<F>(name: &'static str, recorder: F) -> Self
156    where
157        F: FnOnce(&mut InspectorInfo),
158    {
159        let mut info = InspectorInfo::new();
160        recorder(&mut info);
161        Self { name, info }
162    }
163
164    fn is_empty(&self) -> bool {
165        self.info.is_empty()
166    }
167
168    fn to_record(&self) -> ModifierInspectorRecord {
169        ModifierInspectorRecord {
170            name: self.name,
171            properties: self.info.properties().to_vec(),
172        }
173    }
174}
175
176fn describe_dimension(constraint: DimensionConstraint) -> String {
177    match constraint {
178        DimensionConstraint::Unspecified => "unspecified".to_string(),
179        DimensionConstraint::Points(value) => value.to_string(),
180        DimensionConstraint::Fraction(value) => format!("fraction({value})"),
181        DimensionConstraint::Intrinsic(size) => format!("intrinsic({size:?})"),
182    }
183}
184
185pub(crate) fn inspector_metadata<F>(name: &'static str, recorder: F) -> InspectorMetadata
186where
187    F: FnOnce(&mut InspectorInfo),
188{
189    if !inspector_metadata_enabled() {
190        return InspectorMetadata::new(name, |_| {});
191    }
192    InspectorMetadata::new(name, recorder)
193}
194
195pub(crate) fn modifier_debug_enabled() -> bool {
196    #[cfg(not(target_arch = "wasm32"))]
197    {
198        cranpose_core::env_flag!("COMPOSE_DEBUG_MODIFIERS")
199    }
200    #[cfg(target_arch = "wasm32")]
201    {
202        false
203    }
204}
205
206fn inspector_metadata_enabled() -> bool {
207    cfg!(test) || modifier_debug_enabled()
208}
209
210#[derive(Clone)]
211enum ModifierKind {
212    Empty,
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
324pub struct ModifierElementIterator<'a> {
325    inner: std::slice::Iter<'a, DynModifierElement>,
326}
327
328impl<'a> Iterator for ModifierElementIterator<'a> {
329    type Item = &'a DynModifierElement;
330
331    #[inline]
332    fn next(&mut self) -> Option<Self::Item> {
333        self.inner.next()
334    }
335
336    #[inline]
337    fn size_hint(&self) -> (usize, Option<usize>) {
338        self.inner.size_hint()
339    }
340}
341
342impl ExactSizeIterator for ModifierElementIterator<'_> {}
343
344pub(crate) struct ModifierInspectorIterator<'a> {
345    inner: std::slice::Iter<'a, InspectorMetadata>,
346}
347
348impl<'a> Iterator for ModifierInspectorIterator<'a> {
349    type Item = &'a InspectorMetadata;
350
351    #[inline]
352    fn next(&mut self) -> Option<Self::Item> {
353        self.inner.next()
354    }
355
356    #[inline]
357    fn size_hint(&self) -> (usize, Option<usize>) {
358        self.inner.size_hint()
359    }
360}
361
362impl ExactSizeIterator for ModifierInspectorIterator<'_> {}
363
364/// A modifier chain that can be applied to composable elements.
365///
366/// Modifiers allow you to decorate or augment a composable. Common operations include:
367/// - Adjusting layout (e.g., `padding`, `fill_max_size`)
368/// - Adding behavior (e.g., `clickable`, `scrollable`)
369/// - Drawing (e.g., `background`, `border`)
370///
371/// Modifiers are immutable and form a chain using the builder pattern.
372/// The order of modifiers matters: previous modifiers wrap subsequent ones.
373///
374/// # Example
375///
376/// ```rust,ignore
377/// Modifier::padding(16.0)     // Applied first (outer)
378///     .background(Color::Red) // Applied second
379///     .clickable(|| println!("Clicked")) // Applied last (inner)
380/// ```
381#[derive(Clone)]
382pub struct Modifier {
383    kind: ModifierKind,
384    strict_fingerprint: u64,
385    structural_fingerprint: u64,
386    element_count: usize,
387}
388
389impl Default for Modifier {
390    fn default() -> Self {
391        let fingerprints = empty_fingerprints();
392        Self {
393            kind: ModifierKind::Empty,
394            strict_fingerprint: fingerprints.strict,
395            structural_fingerprint: fingerprints.structural,
396            element_count: 0,
397        }
398    }
399}
400
401impl Modifier {
402    pub fn empty() -> Self {
403        Self::default()
404    }
405
406    /// Creates a modifier from a custom modifier node element.
407    pub fn from_element<E>(element: E) -> Self
408    where
409        E: ModifierNodeElement,
410    {
411        Self::with_element(element)
412    }
413
414    /// Clip the content to the bounds of this modifier.
415    ///
416    /// Example: `Modifier::empty().clip_to_bounds()`
417    pub fn clip_to_bounds(self) -> Self {
418        let modifier = Self::with_element(ClipToBoundsElement::new()).with_inspector_metadata(
419            inspector_metadata("clipToBounds", |info| {
420                info.add_property("clipToBounds", "true");
421            }),
422        );
423        self.then(modifier)
424    }
425
426    pub fn modifier_local_provider<T, F>(self, key: ModifierLocalKey<T>, value: F) -> Self
427    where
428        T: 'static,
429        F: Fn() -> T + 'static,
430    {
431        let element = ModifierLocalProviderElement::new(key, value);
432        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
433        self.then(modifier)
434    }
435
436    pub fn modifier_local_consumer<F>(self, consumer: F) -> Self
437    where
438        F: for<'scope> Fn(&mut ModifierLocalReadScope<'scope>) + 'static,
439    {
440        let element = ModifierLocalConsumerElement::new(consumer);
441        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
442        self.then(modifier)
443    }
444
445    /// Says what a screen reader reads for this node, as one spec value.
446    ///
447    /// This is [`Modifier::semantics`] with a value in place of a closure.
448    /// Compose has only the closure form, because Kotlin's receiver lambda
449    /// makes `semantics { contentDescription = "Save" }` read well; Rust has
450    /// none, so a spec value reads better, costs one chain element rather
451    /// than one per property, and can be compared with another.
452    ///
453    /// Example:
454    /// `Modifier::empty().semantics_spec(SemanticsSpec::new().content_description("Amount").error("needs a number"))`
455    pub fn semantics_spec(self, spec: cranpose_foundation::SemanticsSpec) -> Self {
456        self.semantics(move |config: &mut SemanticsConfiguration| config.merge(&spec))
457    }
458    pub fn semantics<F>(self, recorder: F) -> Self
459    where
460        F: Fn(&mut SemanticsConfiguration) + 'static,
461    {
462        let recorder: std::rc::Rc<dyn Fn(&mut SemanticsConfiguration)> = std::rc::Rc::new(recorder);
463        let metadata = if inspector_metadata_enabled() {
464            let mut preview = SemanticsConfiguration::default();
465            recorder(&mut preview);
466            let description = preview.content_description.clone();
467            let state_description = preview.state_description.clone();
468            let role = preview.role;
469            let is_clickable = preview.is_activatable();
470            let canvas_children = preview.canvas_children.len();
471            inspector_metadata("semantics", move |info| {
472                if let Some(desc) = &description {
473                    info.add_property("contentDescription", desc.clone());
474                }
475                if let Some(state) = &state_description {
476                    info.add_property("stateDescription", state.clone());
477                }
478                if let Some(role) = role {
479                    info.add_property("role", format!("{role:?}"));
480                }
481                if is_clickable {
482                    info.add_property("isClickable", "true");
483                }
484                if canvas_children > 0 {
485                    info.add_property("canvasSemanticsChildren", canvas_children.to_string());
486                }
487            })
488        } else {
489            inspector_metadata("semantics", |_| {})
490        };
491        let element = SemanticsElement::new(recorder);
492        let modifier =
493            Modifier::from_parts(vec![modifier_element(element)]).with_inspector_metadata(metadata);
494        self.then(modifier)
495    }
496
497    /// Tells a screen reader the value this control holds inside a range, so
498    /// it reads the value and offers its own way to change it.
499    ///
500    /// This is Compose's `Modifier.progressSemantics(value, valueRange,
501    /// steps)`. Without it a slider reads as text and a person who cannot see
502    /// the screen has no way to move it.
503    ///
504    /// Example: `Modifier::empty().progress_semantics(0.35, 0.0, 1.0, 0)`
505    pub fn progress_semantics(self, current: f32, start: f32, end: f32, steps: u32) -> Self {
506        let info = ProgressBarRangeInfo::new(current, start, end, steps);
507        self.semantics(move |config| config.progress = Some(info))
508    }
509
510    /// Marks this control as one that opens a list of choices, so a reader
511    /// says "combo box" rather than "button" and knows to look for the
512    /// choice it holds. Compose's `Role.DropdownList`.
513    pub fn dropdown_list(self) -> Self {
514        self.role(cranpose_foundation::SemanticsWidgetRole::DropdownList)
515    }
516
517    /// Marks this control as one that holds one value out of an ordered set,
518    /// so a reader steps through them. Compose's `Role.ValuePicker`.
519    pub fn value_picker(self) -> Self {
520        self.role(cranpose_foundation::SemanticsWidgetRole::ValuePicker)
521    }
522
523    /// Says what this control does when a screen reader asks it to open, and
524    /// marks it as closed right now: a reader offers "expand" and says the
525    /// control is collapsed. Compose's
526    /// `Modifier.semantics { expand { … } }`.
527    pub fn expand(self, action: impl Fn() -> bool + 'static) -> Self {
528        let action = cranpose_foundation::SemanticsExpand::new(action);
529        self.semantics(move |config| config.expand = Some(action.clone()))
530    }
531
532    /// Says what a long press on this control does, so a screen reader can
533    /// ask for it and read the label out first: "Remove receipt". Compose's
534    /// `Modifier.semantics { onLongClick("Remove receipt") { … } }`, which
535    /// `Modifier.combinedClickable(onLongClickLabel = …)` fills in for a
536    /// control that takes a long press from a finger too.
537    ///
538    /// The label is asked for, not optional as in Compose: Android is the one
539    /// platform of the four with a long press of its own, and the other three
540    /// list the action by name, so a nameless one reads as nothing.
541    pub fn on_long_click(
542        self,
543        label: impl Into<String>,
544        action: impl Fn() -> bool + 'static,
545    ) -> Self {
546        let label = label.into();
547        let action = cranpose_foundation::SemanticsLongClick::new(action);
548        self.semantics(move |config| {
549            config.on_long_click_label = Some(label.clone());
550            config.on_long_click = Some(action.clone());
551        })
552    }
553
554    /// Says what this control does on VoiceOver's magic tap, the two finger
555    /// double tap for the main action of a screen, and the verb phrase the
556    /// other platforms list it under: "Take the photo". SwiftUI's
557    /// `accessibilityAction(.magicTap)`.
558    pub fn on_magic_tap(
559        self,
560        label: impl Into<String>,
561        action: impl Fn() -> bool + 'static,
562    ) -> Self {
563        let label = label.into();
564        let action = cranpose_foundation::SemanticsMagicTap::new(action);
565        self.semantics(move |config| {
566            config.on_magic_tap_label = Some(label.clone());
567            config.on_magic_tap = Some(action.clone());
568        })
569    }
570
571    /// The short names a person says to Voice Control to reach this control,
572    /// when the name a reader hears is too long to say. SwiftUI's
573    /// `accessibilityInputLabels`.
574    pub fn input_labels<S: Into<String>>(self, labels: impl IntoIterator<Item = S>) -> Self {
575        let labels: Vec<String> = labels.into_iter().map(Into::into).collect();
576        self.semantics(move |config| config.input_labels.clone_from(&labels))
577    }
578
579    /// The language of this control's text, as a BCP 47 tag such as "de" or
580    /// "pt-BR", so a reader picks the right voice. SwiftUI's
581    /// `accessibilityLanguage`, ARIA's `lang`.
582    pub fn language(self, tag: impl Into<String>) -> Self {
583        let tag = tag.into();
584        self.semantics(move |config| config.language = Some(tag.clone()))
585    }
586
587    /// Says what this control does when a screen reader asks it to close, and
588    /// marks it as open right now. Compose's
589    /// `Modifier.semantics { collapse { … } }`.
590    pub fn collapse(self, action: impl Fn() -> bool + 'static) -> Self {
591        let action = cranpose_foundation::SemanticsExpand::new(action);
592        self.semantics(move |config| config.collapse = Some(action.clone()))
593    }
594
595    /// Says what this control does when a screen reader asks to send it away:
596    /// a row a sighted person swipes off, a sheet a sighted person taps
597    /// outside of. A reader that cannot make the gesture gets the same way
598    /// out. Compose's `Modifier.semantics { dismiss { … } }`.
599    pub fn dismiss(self, action: impl Fn() -> bool + 'static) -> Self {
600        let action = cranpose_foundation::SemanticsDismiss::new(action);
601        self.semantics(move |config| config.dismiss = Some(action.clone()))
602    }
603
604    /// Says what this list does when a screen reader asks for the row at an
605    /// index, so a reader reaches row 300 of a long list at once instead of
606    /// paging to it. The index counts rows from zero and the answer says
607    /// whether the list moved. `LazyColumn` and `LazyRow` declare it on their
608    /// own. Compose's `Modifier.semantics { scrollToIndex { … } }`.
609    pub fn scroll_to_index(self, action: impl Fn(usize) -> bool + 'static) -> Self {
610        let action = cranpose_foundation::SemanticsScrollToIndex::new(action);
611        self.semantics(move |config| config.scroll_to_index = Some(action.clone()))
612    }
613
614    /// Moves this node in the order a screen reader visits the nodes beside
615    /// it: a smaller number comes first, and a node left alone keeps the
616    /// order the app laid it out in. A search field drawn last but meant to
617    /// be read first takes a negative number. Compose's
618    /// `Modifier.semantics { traversalIndex = -1f }`.
619    pub fn traversal_index(self, index: f32) -> Self {
620        self.semantics(move |config| config.traversal_index = index)
621    }
622
623    /// Marks a field as one that holds a secret, so no screen reader reads
624    /// its text out: a reader hears the name the app gave the field, and
625    /// "password" in place of the text. Compose's
626    /// `Modifier.semantics { password() }`.
627    pub fn password(self) -> Self {
628        self.semantics(|config| config.password = true)
629    }
630
631    /// Says why the control's content is wrong, so a screen reader reads
632    /// "invalid, the amount needs a number" after the control's state.
633    /// Compose's `Modifier.semantics { error("...") }`.
634    pub fn error(self, message: impl Into<String>) -> Self {
635        let message = message.into();
636        self.semantics(move |config| config.error = Some(message.clone()))
637    }
638
639    /// Names the screen or pane this node is the root of, so a screen reader
640    /// hears where it is when the app moves on: "Library" as the library
641    /// opens. Compose's `Modifier.semantics { paneTitle = "..." }`.
642    pub fn pane_title(self, title: impl Into<String>) -> Self {
643        let title = title.into();
644        self.semantics(move |config| config.pane_title = Some(title.clone()))
645    }
646
647    /// Makes the selectable controls under this node one group, so a screen
648    /// reader says which of how many a tab or a radio button is: "Library,
649    /// tab, 2 of 5". `LiquidTabBar` declares it on its own. Compose's
650    /// `Modifier.selectableGroup()`.
651    pub fn selectable_group(self) -> Self {
652        self.semantics(|config| config.selectable_group = true)
653    }
654
655    /// Makes a screen reader take this node and the text under it as one
656    /// stop, the way it does for a button: a row whose name, count and price
657    /// belong together reads as "Milk, 2, 3.40" and not as three stops.
658    /// Compose's `Modifier.semantics(mergeDescendants = true) {}`.
659    pub fn merge_descendants(self) -> Self {
660        self.semantics(|config| config.merge_descendants = true)
661    }
662
663    /// Takes this node and everything under it out of what a screen reader
664    /// sees: a decorative image, or a placeholder drawn under a field that
665    /// carries the same words as its name. Compose's
666    /// `semantics { hideFromAccessibility() }`.
667    pub fn hide_from_accessibility(self) -> Self {
668        self.semantics(|config| config.hidden = true)
669    }
670
671    /// Marks this component as a heading, so a screen reader lists it among
672    /// the headings of the screen and a person can jump between them.
673    ///
674    /// This is Compose's `Modifier.semantics { heading() }`.
675    pub fn heading(self) -> Self {
676        self.role(cranpose_foundation::SemanticsWidgetRole::Header)
677    }
678
679    /// Tells a screen reader what kind of control this is, when the widget
680    /// does not say so on its own.
681    ///
682    /// This is Compose's `Modifier.semantics { role = Role.Button }`.
683    pub fn role(self, role: cranpose_foundation::SemanticsWidgetRole) -> Self {
684        self.semantics(move |config| config.role = Some(role))
685    }
686
687    /// Makes a screen reader read this component's text out whenever it
688    /// changes, without the reader's cursor on it: a status line, a toast, a
689    /// count that moves.
690    ///
691    /// This is Compose's `Modifier.semantics { liveRegion = LiveRegionMode.Polite }`.
692    pub fn live_region(self, mode: cranpose_foundation::LiveRegionMode) -> Self {
693        self.semantics(move |config| config.live_region = Some(mode))
694    }
695
696    /// Gives this component the text a screen reader reads for it, for a
697    /// drawing, an icon or a control with no text of its own.
698    ///
699    /// This is Compose's `Modifier.semantics { contentDescription = "..." }`.
700    pub fn content_description(self, description: impl Into<String>) -> Self {
701        let description = description.into();
702        self.semantics(move |config| config.content_description = Some(description.clone()))
703    }
704
705    /// Makes this component focusable.
706    ///
707    /// This adds a focus target node that can receive focus and participate
708    /// in focus traversal. The component will be included in tab order and
709    /// can be focused programmatically.
710    pub fn focus_target(self) -> Self {
711        let element = FocusTargetElement::new();
712        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
713        self.then(modifier)
714    }
715
716    /// Makes this component focusable with a callback for focus changes.
717    ///
718    /// The callback is invoked whenever the focus state changes, allowing
719    /// components to react to gaining or losing focus.
720    pub fn on_focus_changed<F>(self, callback: F) -> Self
721    where
722        F: Fn(FocusState) + 'static,
723    {
724        let element = FocusTargetElement::with_callback(callback);
725        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
726        self.then(modifier)
727    }
728
729    /// Binds a [`FocusRequester`] to this node, so an app can move focus onto
730    /// it imperatively with [`FocusRequester::request_focus`].
731    ///
732    /// Pair it with [`focus_target`](Self::focus_target) (or
733    /// [`on_focus_changed`](Self::on_focus_changed)) on the same node —
734    /// `request_focus` moves whichever focus targets are attached there.
735    pub fn focus_requester(self, requester: &FocusRequester) -> Self {
736        let element = FocusRequesterElement::new(requester.clone());
737        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
738        self.then(modifier)
739    }
740
741    /// Binds a [`SemanticsRequester`] to this node, so an app can mark the
742    /// node's semantics for re-collection without recomposing or laying out.
743    ///
744    /// Pair it with [`semantics`](Self::semantics) on the same node when the
745    /// recorder reads state the composition does not observe — app state behind
746    /// a `RefCell`, a game's own model — which is the case a recorder cannot
747    /// signal for itself.
748    pub fn semantics_requester(self, requester: &SemanticsRequester) -> Self {
749        let element = SemanticsRequesterElement::new(requester.clone());
750        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
751        self.then(modifier)
752    }
753
754    /// Enables debug logging for this modifier chain.
755    ///
756    /// When enabled, logs the entire modifier chain structure including:
757    /// - Element types and their properties
758    /// - Inspector metadata
759    /// - Capability flags
760    ///
761    /// This is useful for debugging modifier composition issues and understanding
762    /// how the modifier chain is structured at runtime.
763    ///
764    /// Example:
765    /// ```text
766    /// Modifier::empty()
767    ///     .padding(8.0)
768    ///     .background(Color(1.0, 0.0, 0.0, 1.0))
769    ///     .debug_chain("MyWidget")
770    /// ```
771    pub fn debug_chain(self, tag: &'static str) -> Self {
772        use cranpose_foundation::{ModifierNode, ModifierNodeContext, NodeCapabilities, NodeState};
773
774        #[derive(Clone)]
775        struct DebugChainElement {
776            tag: &'static str,
777        }
778
779        impl fmt::Debug for DebugChainElement {
780            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
781                f.debug_struct("DebugChainElement")
782                    .field("tag", &self.tag)
783                    .finish()
784            }
785        }
786
787        impl PartialEq for DebugChainElement {
788            fn eq(&self, other: &Self) -> bool {
789                self.tag == other.tag
790            }
791        }
792
793        impl Eq for DebugChainElement {}
794
795        impl std::hash::Hash for DebugChainElement {
796            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
797                self.tag.hash(state);
798            }
799        }
800
801        impl ModifierNodeElement for DebugChainElement {
802            type Node = DebugChainNode;
803
804            fn create(&self) -> Self::Node {
805                DebugChainNode::new(self.tag)
806            }
807
808            fn update(&self, node: &mut Self::Node) {
809                node.tag = self.tag;
810            }
811
812            fn capabilities(&self) -> NodeCapabilities {
813                NodeCapabilities::empty()
814            }
815        }
816
817        struct DebugChainNode {
818            tag: &'static str,
819            state: NodeState,
820        }
821
822        impl DebugChainNode {
823            fn new(tag: &'static str) -> Self {
824                Self {
825                    tag,
826                    state: NodeState::new(),
827                }
828            }
829        }
830
831        impl ModifierNode for DebugChainNode {
832            fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {
833                eprintln!("[debug_chain:{}] Modifier chain attached", self.tag);
834            }
835
836            fn on_detach(&mut self) {
837                eprintln!("[debug_chain:{}] Modifier chain detached", self.tag);
838            }
839
840            fn on_reset(&mut self) {
841                eprintln!("[debug_chain:{}] Modifier chain reset", self.tag);
842            }
843        }
844
845        impl cranpose_foundation::DelegatableNode for DebugChainNode {
846            fn node_state(&self) -> &NodeState {
847                &self.state
848            }
849        }
850
851        let element = DebugChainElement { tag };
852        let modifier = Modifier::from_parts(vec![modifier_element(element)]);
853        self.then(modifier)
854            .with_inspector_metadata(inspector_metadata("debugChain", move |info| {
855                info.add_property("tag", tag);
856            }))
857    }
858
859    /// Concatenates this modifier with another.
860    ///
861    /// Eagerly concatenates both element vectors into a single flat `Single`
862    /// variant, avoiding recursive Rc tree overhead on drop and comparison.
863    pub fn then(&self, next: Modifier) -> Modifier {
864        if self.is_trivially_empty() {
865            return next;
866        }
867        if next.is_trivially_empty() {
868            return self.clone();
869        }
870
871        let Some((self_elements, self_inspector)) = self.single_parts() else {
872            return next;
873        };
874        let Some((next_elements, next_inspector)) = next.single_parts() else {
875            return self.clone();
876        };
877
878        let mut merged_elements = Vec::with_capacity(self_elements.len() + next_elements.len());
879        merged_elements.extend_from_slice(self_elements);
880        merged_elements.extend_from_slice(next_elements);
881
882        let mut merged_inspector = Vec::with_capacity(self_inspector.len() + next_inspector.len());
883        merged_inspector.extend_from_slice(self_inspector);
884        merged_inspector.extend_from_slice(next_inspector);
885
886        let fingerprints = append_fingerprints(
887            ModifierFingerprints {
888                strict: self.strict_fingerprint,
889                structural: self.structural_fingerprint,
890            },
891            next_elements,
892        );
893        Modifier {
894            kind: ModifierKind::Single {
895                elements: Rc::new(merged_elements),
896                inspector: Rc::new(merged_inspector),
897            },
898            strict_fingerprint: fingerprints.strict,
899            structural_fingerprint: fingerprints.structural,
900            element_count: self.element_count + next.element_count,
901        }
902    }
903
904    pub(crate) fn iter_elements(&self) -> ModifierElementIterator<'_> {
905        match &self.kind {
906            ModifierKind::Empty => ModifierElementIterator { inner: [].iter() },
907            ModifierKind::Single { elements, .. } => ModifierElementIterator {
908                inner: elements.iter(),
909            },
910        }
911    }
912
913    pub(crate) fn iter_inspector_metadata(&self) -> ModifierInspectorIterator<'_> {
914        match &self.kind {
915            ModifierKind::Empty => ModifierInspectorIterator { inner: [].iter() },
916            ModifierKind::Single { inspector, .. } => ModifierInspectorIterator {
917                inner: inspector.iter(),
918            },
919        }
920    }
921
922    #[cfg(test)]
923    pub(crate) fn elements(&self) -> Vec<DynModifierElement> {
924        match &self.kind {
925            ModifierKind::Empty => Vec::new(),
926            ModifierKind::Single { elements, .. } => elements.as_ref().clone(),
927        }
928    }
929
930    pub(crate) fn inspector_metadata(&self) -> Vec<InspectorMetadata> {
931        match &self.kind {
932            ModifierKind::Empty => Vec::new(),
933            ModifierKind::Single { inspector, .. } => inspector.as_ref().clone(),
934        }
935    }
936
937    pub(crate) fn rehouse_for_live_compaction(&self) -> Self {
938        match &self.kind {
939            ModifierKind::Empty => Self::default(),
940            ModifierKind::Single {
941                elements,
942                inspector,
943            } => Self {
944                kind: ModifierKind::Single {
945                    elements: Rc::new(elements.iter().cloned().collect()),
946                    inspector: Rc::new(inspector.as_ref().clone()),
947                },
948                strict_fingerprint: self.strict_fingerprint,
949                structural_fingerprint: self.structural_fingerprint,
950                element_count: self.element_count,
951            },
952        }
953    }
954
955    pub fn total_padding(&self) -> f32 {
956        let padding = self.padding_values();
957        padding
958            .left
959            .max(padding.right)
960            .max(padding.top)
961            .max(padding.bottom)
962    }
963
964    pub fn explicit_size(&self) -> Option<Size> {
965        let props = self.layout_properties();
966        match (props.width, props.height) {
967            (DimensionConstraint::Points(width), DimensionConstraint::Points(height)) => {
968                Some(Size { width, height })
969            }
970            _ => None,
971        }
972    }
973
974    pub fn padding_values(&self) -> EdgeInsets {
975        self.resolved_modifiers().padding()
976    }
977
978    pub(crate) fn layout_properties(&self) -> LayoutProperties {
979        self.resolved_modifiers().layout_properties()
980    }
981
982    pub fn box_alignment(&self) -> Option<Alignment> {
983        self.layout_properties().box_alignment()
984    }
985
986    pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
987        self.layout_properties().column_alignment()
988    }
989
990    pub fn row_alignment(&self) -> Option<VerticalAlignment> {
991        self.layout_properties().row_alignment()
992    }
993
994    pub fn draw_commands(&self) -> Vec<DrawCommand> {
995        collect_slices_from_modifier(self).draw_commands().to_vec()
996    }
997
998    pub fn clips_to_bounds(&self) -> bool {
999        collect_slices_from_modifier(self).clip_to_bounds()
1000    }
1001
1002    /// Returns structured inspector records for each modifier element.
1003    pub fn collect_inspector_records(&self) -> Vec<ModifierInspectorRecord> {
1004        self.inspector_metadata()
1005            .iter()
1006            .map(|metadata| metadata.to_record())
1007            .collect()
1008    }
1009
1010    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
1011        let mut handle = ModifierChainHandle::new();
1012        let _ = handle.update(self);
1013        handle.resolved_modifiers()
1014    }
1015
1016    pub(crate) fn with_element<E>(element: E) -> Self
1017    where
1018        E: ModifierNodeElement,
1019    {
1020        let dyn_element = modifier_element(element);
1021        Self::from_parts(vec![dyn_element])
1022    }
1023
1024    pub(crate) fn from_parts(elements: Vec<DynModifierElement>) -> Self {
1025        if elements.is_empty() {
1026            Self::default()
1027        } else {
1028            let element_count = elements.len();
1029            let fingerprints = single_fingerprints(elements.as_slice());
1030            Self {
1031                kind: ModifierKind::Single {
1032                    elements: Rc::new(elements),
1033                    inspector: Rc::new(Vec::new()),
1034                },
1035                strict_fingerprint: fingerprints.strict,
1036                structural_fingerprint: fingerprints.structural,
1037                element_count,
1038            }
1039        }
1040    }
1041
1042    fn is_trivially_empty(&self) -> bool {
1043        matches!(self.kind, ModifierKind::Empty)
1044    }
1045
1046    fn single_parts(&self) -> Option<(&[DynModifierElement], &[InspectorMetadata])> {
1047        match &self.kind {
1048            ModifierKind::Empty => None,
1049            ModifierKind::Single {
1050                elements,
1051                inspector,
1052            } => Some((elements.as_slice(), inspector.as_slice())),
1053        }
1054    }
1055
1056    pub(crate) fn with_inspector_metadata(self, metadata: InspectorMetadata) -> Self {
1057        if metadata.is_empty() {
1058            return self;
1059        }
1060        match self.kind {
1061            ModifierKind::Empty => self,
1062            ModifierKind::Single {
1063                elements,
1064                inspector,
1065            } => {
1066                let mut new_inspector = inspector.as_ref().clone();
1067                new_inspector.push(metadata);
1068                Self {
1069                    kind: ModifierKind::Single {
1070                        elements,
1071                        inspector: Rc::new(new_inspector),
1072                    },
1073                    strict_fingerprint: self.strict_fingerprint,
1074                    structural_fingerprint: self.structural_fingerprint,
1075                    element_count: self.element_count,
1076                }
1077            }
1078        }
1079    }
1080
1081    /// Checks whether two modifiers are structurally equivalent for layout decisions.
1082    ///
1083    /// This ignores identity-sensitive modifier elements (e.g., draw closures) so
1084    /// draw-only updates do not force measure/layout invalidation.
1085    pub fn structural_eq(&self, other: &Self) -> bool {
1086        self.eq_internal(other, false)
1087    }
1088
1089    fn eq_internal(&self, other: &Self, consider_always_update: bool) -> bool {
1090        if self.element_count != other.element_count {
1091            return false;
1092        }
1093        if consider_always_update {
1094            if self.strict_fingerprint != other.strict_fingerprint {
1095                return false;
1096            }
1097        } else if self.structural_fingerprint != other.structural_fingerprint {
1098            return false;
1099        }
1100
1101        match (&self.kind, &other.kind) {
1102            (ModifierKind::Empty, ModifierKind::Empty) => true,
1103            (
1104                ModifierKind::Single {
1105                    elements: e1,
1106                    inspector: _,
1107                },
1108                ModifierKind::Single {
1109                    elements: e2,
1110                    inspector: _,
1111                },
1112            ) => {
1113                if Rc::ptr_eq(e1, e2) {
1114                    return true;
1115                }
1116
1117                if e1.len() != e2.len() {
1118                    return false;
1119                }
1120
1121                for (a, b) in e1.iter().zip(e2.iter()) {
1122                    if !consider_always_update
1123                        && a.element_type() == b.element_type()
1124                        && a.capabilities() == NodeCapabilities::DRAW
1125                        && b.capabilities() == NodeCapabilities::DRAW
1126                    {
1127                        continue;
1128                    }
1129
1130                    if consider_always_update && (a.requires_update() || b.requires_update()) {
1131                        if !Rc::ptr_eq(a, b) {
1132                            return false;
1133                        }
1134                        continue;
1135                    }
1136
1137                    if !a.equals_element(&**b) {
1138                        return false;
1139                    }
1140                }
1141
1142                true
1143            }
1144            _ => false,
1145        }
1146    }
1147}
1148
1149impl PartialEq for Modifier {
1150    fn eq(&self, other: &Self) -> bool {
1151        self.eq_internal(other, true)
1152    }
1153}
1154
1155impl Eq for Modifier {}
1156
1157impl fmt::Display for Modifier {
1158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1159        match &self.kind {
1160            ModifierKind::Empty => write!(f, "Modifier.empty"),
1161            ModifierKind::Single { elements, .. } => {
1162                if elements.is_empty() {
1163                    return write!(f, "Modifier.empty");
1164                }
1165                write!(f, "Modifier[")?;
1166                for (index, element) in elements.iter().enumerate() {
1167                    if index > 0 {
1168                        write!(f, ", ")?;
1169                    }
1170                    let name = element.inspector_name();
1171                    let mut properties = Vec::new();
1172                    element.record_inspector_properties(&mut |prop, value| {
1173                        properties.push(format!("{prop}={value}"));
1174                    });
1175                    if properties.is_empty() {
1176                        write!(f, "{name}")?;
1177                    } else {
1178                        write!(f, "{name}({})", properties.join(", "))?;
1179                    }
1180                }
1181                write!(f, "]")
1182            }
1183        }
1184    }
1185}
1186
1187impl fmt::Debug for Modifier {
1188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1189        fmt::Display::fmt(self, f)
1190    }
1191}
1192
1193#[derive(Clone, Copy, Debug, PartialEq)]
1194pub struct ResolvedBackground {
1195    color: Color,
1196    shape: Option<RoundedCornerShape>,
1197}
1198
1199impl ResolvedBackground {
1200    pub fn new(color: Color, shape: Option<RoundedCornerShape>) -> Self {
1201        Self { color, shape }
1202    }
1203
1204    pub fn color(&self) -> Color {
1205        self.color
1206    }
1207
1208    pub fn shape(&self) -> Option<RoundedCornerShape> {
1209        self.shape
1210    }
1211
1212    pub fn set_shape(&mut self, shape: Option<RoundedCornerShape>) {
1213        self.shape = shape;
1214    }
1215}
1216
1217#[derive(Clone, Copy, Debug, PartialEq, Default)]
1218pub struct ResolvedModifiers {
1219    padding: EdgeInsets,
1220    layout: LayoutProperties,
1221    offset: Point,
1222}
1223
1224impl ResolvedModifiers {
1225    pub fn padding(&self) -> EdgeInsets {
1226        self.padding
1227    }
1228
1229    pub fn layout_properties(&self) -> LayoutProperties {
1230        self.layout
1231    }
1232
1233    pub fn offset(&self) -> Point {
1234        self.offset
1235    }
1236
1237    pub(crate) fn set_padding(&mut self, padding: EdgeInsets) {
1238        self.padding = padding;
1239    }
1240
1241    pub(crate) fn set_layout_properties(&mut self, layout: LayoutProperties) {
1242        self.layout = layout;
1243    }
1244
1245    pub(crate) fn set_offset(&mut self, offset: Point) {
1246        self.offset = offset;
1247    }
1248}
1249
1250#[derive(Clone, Copy, Debug, Default, PartialEq)]
1251pub enum DimensionConstraint {
1252    #[default]
1253    Unspecified,
1254    Points(f32),
1255    Fraction(f32),
1256    Intrinsic(IntrinsicSize),
1257}
1258
1259#[derive(Clone, Copy, Debug, Default, PartialEq)]
1260pub struct LayoutWeight {
1261    pub weight: f32,
1262    pub fill: bool,
1263}
1264
1265#[derive(Clone, Copy, Debug, Default, PartialEq)]
1266pub struct LayoutProperties {
1267    padding: EdgeInsets,
1268    width: DimensionConstraint,
1269    height: DimensionConstraint,
1270    min_width: Option<f32>,
1271    min_height: Option<f32>,
1272    max_width: Option<f32>,
1273    max_height: Option<f32>,
1274    weight: Option<LayoutWeight>,
1275    box_alignment: Option<Alignment>,
1276    column_alignment: Option<HorizontalAlignment>,
1277    row_alignment: Option<VerticalAlignment>,
1278}
1279
1280impl LayoutProperties {
1281    pub fn padding(&self) -> EdgeInsets {
1282        self.padding
1283    }
1284
1285    pub fn width(&self) -> DimensionConstraint {
1286        self.width
1287    }
1288
1289    pub fn height(&self) -> DimensionConstraint {
1290        self.height
1291    }
1292
1293    pub fn min_width(&self) -> Option<f32> {
1294        self.min_width
1295    }
1296
1297    pub fn min_height(&self) -> Option<f32> {
1298        self.min_height
1299    }
1300
1301    pub fn max_width(&self) -> Option<f32> {
1302        self.max_width
1303    }
1304
1305    pub fn max_height(&self) -> Option<f32> {
1306        self.max_height
1307    }
1308
1309    pub fn weight(&self) -> Option<LayoutWeight> {
1310        self.weight
1311    }
1312
1313    pub fn box_alignment(&self) -> Option<Alignment> {
1314        self.box_alignment
1315    }
1316
1317    pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
1318        self.column_alignment
1319    }
1320
1321    pub fn row_alignment(&self) -> Option<VerticalAlignment> {
1322        self.row_alignment
1323    }
1324}
1325
1326#[cfg(test)]
1327#[path = "tests/modifier_tests.rs"]
1328mod tests;