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