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