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