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