Skip to main content

cranpose_ui/modifier/
mod.rs

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