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