Skip to main content

repose_core/
modifier.rs

1use std::cell::{Cell, RefCell};
2use std::collections::HashSet;
3use std::rc::Rc;
4use std::sync::atomic::{AtomicU64, Ordering};
5
6use taffy::{AlignContent, AlignItems, AlignSelf, FlexDirection, FlexWrap, JustifyContent};
7
8use crate::animation::AnimationSpec;
9use crate::indication::IndicationNodeFactory;
10use crate::{Brush, Color, PointerEvent, Size, Transform, Vec2};
11
12/// State-driven colors. Priority: disabled > dragged > pressed > focused > hovered > default.
13///
14/// Priority (paint): disabled > dragged > pressed > focused > hovered > default.
15#[derive(Clone, Copy, Debug)]
16pub struct StateColors {
17    pub default: Color,
18    pub hovered: Color,
19    /// Color while focused.
20    pub focused: Color,
21    pub pressed: Color,
22    pub disabled: Color,
23    /// While dragged (overrides hover/press/focus).
24    pub dragged: Color,
25}
26
27/// State-driven elevation. Priority: disabled > dragged > pressed > focused > hovered > default.
28#[derive(Clone, Copy, Debug)]
29pub struct StateElevation {
30    pub default: f32,
31    pub hovered: f32,
32    /// Applied between pressed and hovered in the paint priority order.
33    pub focused: f32,
34    pub pressed: f32,
35    pub disabled: f32,
36    /// Elevation while the component is being dragged (preferred over hovered/pressed/focused).
37    pub dragged: f32,
38}
39
40impl StateColors {
41    pub const fn transparent() -> Self {
42        Self {
43            default: Color::TRANSPARENT,
44            hovered: Color::TRANSPARENT,
45            focused: Color::TRANSPARENT,
46            pressed: Color::TRANSPARENT,
47            disabled: Color::TRANSPARENT,
48            dragged: Color::TRANSPARENT,
49        }
50    }
51}
52
53impl StateElevation {
54    pub const fn zero() -> Self {
55        Self {
56            default: 0.0,
57            hovered: 0.0,
58            focused: 0.0,
59            pressed: 0.0,
60            disabled: 0.0,
61            dragged: 0.0,
62        }
63    }
64}
65
66macro_rules! merge_opts {
67    ($dst:ident, $src:ident; $($f:ident),+ $(,)?) => {
68        $( $dst.$f = $src.$f.or($dst.$f); )+
69    };
70}
71macro_rules! merge_flags {
72    ($dst:ident, $src:ident; $($f:ident),+ $(,)?) => {
73        $( $dst.$f |= $src.$f; )+
74    };
75}
76
77macro_rules! impl_option_fields {
78    ($ty:ty, $fn:ident) => {
79        impl $ty {
80            $fn!(replace);
81        }
82    };
83    ($ty:ident) => {
84        impl $ty {
85            /// Chain another modifier's settings onto this one.
86            /// Useful for creating reusable modifier templates.
87            pub fn then(mut self, other: Self) -> Self {
88                merge_opts!(self, other;
89                    key, size, width, height, required_size,
90                    padding, padding_values,
91                    min_width, min_height, max_width, max_height,
92                    required_min_width, required_max_width,
93                    required_min_height, required_max_height,
94                    default_min_width, default_min_height,
95                    fill_max, fill_max_w, fill_max_h,
96                    background, state_colors, state_elevation, border,
97                    flex_grow, flex_shrink, flex_basis, flex_wrap, flex_dir,
98                    gap, row_gap, column_gap,
99                    align_self, justify_content, align_items_container, align_content,
100                    clip_rounded, clip_rect, overflow, render_z_index,
101                    on_scroll,
102                    nested_scroll_connection,
103                    scroll,
104                    on_pointer_down, on_pointer_move, on_pointer_up,
105                    on_pointer_enter, on_pointer_leave,
106                    on_click, on_double_click, on_long_click,
107                    semantics, alpha, transform,
108                    grid, grid_col_span, grid_row_span,
109                    position_type,
110                    offset_left, offset_right, offset_top, offset_bottom,
111                    margin_left, margin_right, margin_top, margin_bottom,
112                    aspect_ratio, intrinsic_width, intrinsic_height,
113                    painter,
114                    on_drag_start, on_drag_end, on_drag_enter, on_drag_over, on_drag_leave, on_drop,
115                    drag_preview,
116                    on_action, cursor, animate_content_size, focus_requester, on_focus_changed,
117                    interaction_source, text_input,
118                );
119                        merge_flags!(self, other;
120                    hit_passthrough, input_blocker, repaint_boundary, click, disabled,
121                    propagate_min, focus_group,
122                );
123
124                if let Some(f) = other.focusable {
125                    self.focusable = Some(f);
126                }
127                if other.indication.is_some() {
128                    self.indication = other.indication;
129                }
130                if other.z_index != 0.0 {
131                    self.z_index = other.z_index;
132                }
133                self
134            }
135        }
136    };
137}
138
139#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
140pub enum ClipOp {
141    /// Keep content inside the clip rect (default).
142    #[default]
143    Intersect,
144    /// Remove content inside the clip rect (cutout).
145    Difference,
146}
147
148/// Controls whether child content is clipped to the parent bounds.
149///
150/// Analogous to CSS `overflow`:
151/// - `Clip` (default): content extending beyond the parent is hidden.
152/// - `Visible`: content is allowed to overflow the parent bounds.
153#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
154pub enum Overflow {
155    #[default]
156    Clip,
157    Visible,
158}
159
160/// Rectangular clip with a clipping operation.
161/// The rect is relative to the element bounds, in dp.
162#[derive(Clone, Copy, Debug)]
163pub struct ClipRect {
164    pub left: f32,
165    pub top: f32,
166    pub right: f32,
167    pub bottom: f32,
168    pub op: ClipOp,
169}
170
171#[derive(Clone, Debug)]
172pub struct Border {
173    pub width: f32,
174    pub color: Color,
175    pub radius: [f32; 4],
176}
177
178#[derive(Clone, Copy, Debug, Default)]
179pub struct PaddingValues {
180    pub left: f32,
181    pub right: f32,
182    pub top: f32,
183    pub bottom: f32,
184}
185
186#[derive(Clone, Debug)]
187pub struct GridConfig {
188    pub columns: usize,
189    pub row_gap: f32,
190    pub column_gap: f32,
191}
192
193/// Edge treatment for `Modifier::blur` -> controls how pixels at the edges
194/// of the blurred region are handled.
195#[derive(Clone, Copy, Debug, PartialEq)]
196pub enum BlurredEdgeTreatment {
197    /// Clip the blur to the element's bounds and clamp edge pixels
198    /// (extend the outermost pixels). This is the Compose default.
199    Rectangle,
200    /// Allow the blur to extend beyond the element's bounds.
201    /// Edge pixels are treated as transparent (decal).
202    Unbounded,
203}
204
205/// Gaussian blur parameters for `Modifier::blur`.
206#[derive(Clone, Copy, Debug)]
207pub struct BlurStyle {
208    /// Horizontal blur radius in dp.
209    pub radius_x: f32,
210    /// Vertical blur radius in dp.
211    pub radius_y: f32,
212    /// Controls edge pixel behavior.
213    pub edge_treatment: BlurredEdgeTreatment,
214}
215
216/// Constraints passed to the `Modifier::layout` callback.
217/// Mirrors Compose's `Constraints` -> the element's size must fall within
218/// `[min_width, max_width]` × `[min_height, max_height]`.
219/// A dimension with `INFINITY` max means unbounded in that direction.
220#[derive(Clone, Copy, Debug)]
221pub struct LayoutConstraints {
222    pub min_width: f32,
223    pub max_width: f32,
224    pub min_height: f32,
225    pub max_height: f32,
226}
227
228/// Drop-shadow parameters applied to a graphics layer.
229///
230/// `blur_radius` is the Gaussian blur radius in dp (1.0 = subtle, 8.0 = soft,
231/// 16.0 = very diffuse). `offset_y` is the vertical offset of the shadow in dp
232/// (positive = below the layer). `color` is the shadow color (premultiplied
233/// alpha controls shadow darkness).
234#[derive(Clone, Copy, Debug)]
235pub struct ShadowSpec {
236    pub blur_radius: f32,
237    pub offset_y: f32,
238    pub color: Color,
239}
240
241#[derive(Clone, Copy, Debug)]
242#[non_exhaustive]
243pub enum PositionType {
244    Relative,
245    Absolute,
246}
247
248/// Configuration for a text input field.
249#[derive(Clone)]
250pub struct TextInputConfig {
251    pub hint: String,
252    pub multiline: bool,
253    pub on_change: Option<Rc<dyn Fn(String)>>,
254    pub on_submit: Option<Rc<dyn Fn(String)>>,
255    pub focus_tracker: Option<Rc<Cell<bool>>>,
256    pub value: String,
257    pub visual_transformation: Option<Rc<dyn crate::text::VisualTransformation>>,
258    pub keyboard_type: crate::text::KeyboardType,
259    pub capitalization: crate::text::KeyboardCapitalization,
260    pub ime_action: crate::text::ImeAction,
261    /// Platform keyboard auto-correct hint. `None` = follow platform default
262    /// (except password keyboards, which never auto-correct).
263    pub auto_correct_enabled: Option<bool>,
264    /// When false, the text field is not editable, not focusable, and input is not selectable.
265    pub enabled: bool,
266    /// When true, the text field can be focused and text can be selected/copied, but not modified.
267    pub read_only: bool,
268    /// Maximum visible lines. Only effective when `multiline` is true.
269    pub max_lines: Option<usize>,
270    /// Minimum visible lines. Only effective when `multiline` is true.
271    pub min_lines: usize,
272    /// Override the cursor color. When None, uses the theme's `on_surface`.
273    pub cursor_color: Option<Color>,
274    /// Callback invoked after each text layout computation, providing layout details
275    /// such as line count and content size.
276    pub on_text_layout: Option<Rc<dyn Fn(&crate::text::TextLayoutResult)>>,
277    /// Style for the text content (font size, color, weight, etc.).
278    /// None = use defaults (16dp, theme color, NORMAL weight).
279    pub text_style: Option<crate::text::TextStyle>,
280    /// Per-action IME callbacks (onDone, onGo, onNext, etc.).
281    /// None = use `on_submit` for all actions.
282    pub keyboard_actions: Option<crate::text::KeyboardActions>,
283    /// Interaction source for tracking focus/press/hover state.
284    pub interaction_source: Option<InteractionSource>,
285    /// Line limits (SingleLine or MultiLine). Overrides `multiline`/`max_lines`/`min_lines`.
286    pub line_limits: Option<crate::text::TextFieldLineLimits>,
287}
288
289impl Default for TextInputConfig {
290    fn default() -> Self {
291        Self {
292            hint: String::new(),
293            multiline: false,
294            on_change: None,
295            on_submit: None,
296            focus_tracker: None,
297            value: String::new(),
298            visual_transformation: None,
299            keyboard_type: crate::text::KeyboardType::default(),
300            capitalization: crate::text::KeyboardCapitalization::default(),
301            ime_action: crate::text::ImeAction::default(),
302            auto_correct_enabled: None,
303            enabled: true,
304            read_only: false,
305            max_lines: None,
306            min_lines: 1,
307            cursor_color: None,
308            on_text_layout: None,
309            text_style: None,
310            keyboard_actions: None,
311            interaction_source: None,
312            line_limits: None,
313        }
314    }
315}
316
317impl std::fmt::Debug for TextInputConfig {
318    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
319        let mut s = f.debug_struct("TextInputConfig");
320        s.field("hint", &self.hint);
321        s.field("multiline", &self.multiline);
322        if self.on_change.is_some() {
323            s.field("on_change", &"…");
324        }
325        if self.on_submit.is_some() {
326            s.field("on_submit", &"…");
327        }
328        if self.focus_tracker.is_some() {
329            s.field("focus_tracker", &"…");
330        }
331        s.field("value", &self.value);
332        if self.visual_transformation.is_some() {
333            s.field("visual_transformation", &"…");
334        }
335        s.field("keyboard_type", &self.keyboard_type);
336        s.field("capitalization", &self.capitalization);
337        s.field("ime_action", &self.ime_action);
338        s.field("auto_correct_enabled", &self.auto_correct_enabled);
339        s.field("enabled", &self.enabled);
340        s.field("read_only", &self.read_only);
341        s.field("max_lines", &self.max_lines);
342        s.field("min_lines", &self.min_lines);
343        s.field("cursor_color", &self.cursor_color);
344        if self.on_text_layout.is_some() {
345            s.field("on_text_layout", &"…");
346        }
347        s.finish()
348    }
349}
350
351/// Intrinsic sizing mode for [`Modifier::intrinsic_width`] and [`Modifier::intrinsic_height`].
352/// When set, the node sizes itself to the intrinsic content size in that dimension.
353#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
354pub enum IntrinsicSize {
355    Min,
356    Max,
357}
358
359static PRESS_COUNTER: AtomicU64 = AtomicU64::new(1);
360
361/// A press identifier for linking Press -> Release/Cancel pairs.
362pub type PressId = u64;
363
364/// An interaction event that can be emitted by a [`MutableInteractionSource`].
365///
366/// Compose-like per-interaction-type hierarchy:
367/// - `PressInteraction.Press(position)` / `Release(press)` / `Cancel(press)`
368/// - `HoverInteraction.Enter` / `Exit`
369/// - `FocusInteraction.Focus` / `Unfocus`
370/// - `DragInteraction.Start` / `Stop` / `Cancel`
371#[derive(Clone, Copy, Debug, PartialEq)]
372pub enum Interaction {
373    /// A press started at the given position (in local coords).
374    /// Carries a unique `PressId` so Release/Cancel can identify which press to end.
375    Press(PressId, Vec2),
376    /// The press with the given `PressId` was released.
377    Release(PressId),
378    /// The press with the given `PressId` was cancelled
379    /// (e.g. gesture disambiguation, pointer leave during press).
380    Cancel(PressId),
381    HoverEnter,
382    HoverLeave,
383    Focus,
384    Unfocus,
385    DragStart,
386    DragStop,
387    DragCancel,
388}
389
390impl Interaction {
391    /// Create a new `Press` with a fresh unique ID and the given position.
392    #[inline]
393    pub fn new_press(position: Vec2) -> Self {
394        Interaction::Press(PRESS_COUNTER.fetch_add(1, Ordering::Relaxed), position)
395    }
396}
397
398/// Read-only handle to a shared interaction state.
399///
400/// Use [`MutableInteractionSource::source`] to obtain a read handle, or
401/// [`MutableInteractionSource::new`] + `.source()` to create a new source pair.
402///
403/// Multiple clones share the same underlying state.
404#[derive(Clone)]
405pub struct InteractionSource {
406    pub(crate) state: Rc<RefCell<InteractionState>>,
407}
408
409impl InteractionSource {
410    pub fn collect_is_pressed(&self) -> bool {
411        !self.state.borrow().active_presses.is_empty()
412    }
413    pub fn collect_is_hovered(&self) -> bool {
414        self.state.borrow().hovered
415    }
416    pub fn collect_is_focused(&self) -> bool {
417        self.state.borrow().focused
418    }
419
420    /// Focused and keyboard/D-pad input mode (Compose `:focus-visible`).
421    pub fn collect_is_focus_visible(&self) -> bool {
422        crate::input::is_focus_visible(self.collect_is_focused())
423    }
424
425    pub fn collect_is_dragged(&self) -> bool {
426        self.state.borrow().dragged > 0
427    }
428    pub fn collect_last_press_position(&self) -> Option<Vec2> {
429        self.state.borrow().last_press_position
430    }
431    pub fn collect_last_press_id(&self) -> Option<PressId> {
432        self.state.borrow().last_press_id
433    }
434    /// Stable identity: the pointer of the shared state Rc.
435    pub fn stable_id(&self) -> *const () {
436        Rc::as_ptr(&self.state) as *const ()
437    }
438    /// Get a mutable handle to the same underlying state.
439    /// Both handles share the same `Rc<RefCell<..>>`, so mutations via
440    /// the returned `MutableInteractionSource` are reflected here.
441    pub fn to_mutable(&self) -> MutableInteractionSource {
442        MutableInteractionSource {
443            state: self.state.clone(),
444        }
445    }
446
447    /// Convenience: hard-reset via read handle (same Rc).
448    pub fn reset(&self) {
449        self.to_mutable().reset();
450    }
451
452    /// Convenience: clear hover only via read handle (same Rc).
453    pub fn reset_hover(&self) {
454        self.to_mutable().reset_hover();
455    }
456}
457
458/// Mutable handle to a shared interaction state.
459///
460/// Create one via [`MutableInteractionSource::new`], then pass the read-only
461/// [`InteractionSource`] to modifiers via `.interaction_source(&source)`.
462///
463/// ```ignore
464/// let src = remember(MutableInteractionSource::new);
465/// m = m.clickable().interaction_source(&src).state_colors(...);
466/// ```
467#[derive(Clone)]
468pub struct MutableInteractionSource {
469    pub(crate) state: Rc<RefCell<InteractionState>>,
470}
471
472impl std::fmt::Debug for MutableInteractionSource {
473    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
474        f.debug_struct("MutableInteractionSource")
475            .finish_non_exhaustive()
476    }
477}
478
479impl MutableInteractionSource {
480    pub fn new() -> Self {
481        Self {
482            state: Rc::new(RefCell::new(InteractionState::default())),
483        }
484    }
485
486    /// Emit an interaction event, updating the shared state.
487    pub fn emit(&self, interaction: Interaction) {
488        let changed = {
489            let mut s = self.state.borrow_mut();
490            match interaction {
491                Interaction::Press(id, pos) => {
492                    let inserted = s.active_presses.insert(id);
493                    s.last_press_id = Some(id);
494                    s.last_press_position = Some(pos);
495                    inserted
496                }
497                Interaction::Release(id) | Interaction::Cancel(id) => {
498                    if s.active_presses.remove(&id) {
499                        true
500                    } else if id == 0 {
501                        if let Some(any) = s.active_presses.iter().next().copied() {
502                            s.active_presses.remove(&any);
503                            true
504                        } else {
505                            false
506                        }
507                    } else {
508                        false
509                    }
510                }
511                Interaction::HoverEnter => {
512                    let changed = !s.hovered;
513                    s.hovered = true;
514                    changed
515                }
516                Interaction::HoverLeave => {
517                    let changed = s.hovered;
518                    s.hovered = false;
519                    // Leaving while pressed cancels all presses (Compose-like).
520                    if !s.active_presses.is_empty() {
521                        s.active_presses.clear();
522                        true
523                    } else {
524                        changed
525                    }
526                }
527                Interaction::Focus => {
528                    let changed = !s.focused;
529                    s.focused = true;
530                    changed
531                }
532                Interaction::Unfocus => {
533                    let changed = s.focused;
534                    s.focused = false;
535                    changed
536                }
537                Interaction::DragStart => {
538                    let changed = s.dragged == 0;
539                    s.dragged = s.dragged.saturating_add(1);
540                    changed
541                }
542                Interaction::DragStop | Interaction::DragCancel => {
543                    let was = s.dragged;
544                    s.dragged = s.dragged.saturating_sub(1);
545                    was != s.dragged
546                }
547            }
548        };
549        if changed {
550            // So source-driven paint (ripple, state layers) cannot stick stale.
551            crate::frame_clock::request_frame();
552        }
553    }
554
555    /// Get a read-only handle to the shared state.
556    pub fn source(&self) -> InteractionSource {
557        InteractionSource {
558            state: self.state.clone(),
559        }
560    }
561
562    /// Hard-reset all interaction flags.
563    pub fn reset(&self) {
564        let mut s = self.state.borrow_mut();
565        *s = InteractionState::default();
566        crate::frame_clock::request_frame();
567    }
568
569    /// Clear hover only (keep press/focus/drag).
570    pub fn reset_hover(&self) {
571        let mut s = self.state.borrow_mut();
572        if s.hovered {
573            s.hovered = false;
574            crate::frame_clock::request_frame();
575        }
576    }
577}
578
579impl Default for MutableInteractionSource {
580    fn default() -> Self {
581        Self::new()
582    }
583}
584
585#[derive(Clone, Default)]
586pub(crate) struct InteractionState {
587    /// Active press IDs (Press → Release/Cancel pairing).
588    active_presses: HashSet<PressId>,
589    hovered: bool,
590    focused: bool,
591    dragged: u32,
592    /// Most recent press position (used by ripple for origin).
593    pub(crate) last_press_position: Option<Vec2>,
594    /// Most recent press ID.
595    pub(crate) last_press_id: Option<PressId>,
596}
597
598#[derive(Clone, Default)]
599pub struct Modifier {
600    /// Optional stable identity key for this view node.
601    ///
602    /// If set, `layout_and_paint` will prefer this over child index when assigning stable ViewIds.
603    /// This is the “escape hatch” for dynamic lists / conditional UI where index-based identity
604    /// would otherwise shift.
605    pub key: Option<u64>,
606
607    pub size: Option<Size>,
608    pub width: Option<f32>,
609    pub height: Option<f32>,
610    pub required_size: Option<Size>,
611    pub fill_max: Option<f32>,
612    pub fill_max_w: Option<f32>,
613    pub fill_max_h: Option<f32>,
614    pub padding: Option<f32>,
615    pub padding_values: Option<PaddingValues>,
616    pub min_width: Option<f32>,
617    pub min_height: Option<f32>,
618    pub max_width: Option<f32>,
619    pub max_height: Option<f32>,
620    /// Like [`required_size`] but only for min width. Overrides parent min constraints.
621    pub required_min_width: Option<f32>,
622    /// Like [`required_size`] but only for max width. Overrides parent max constraints.
623    pub required_max_width: Option<f32>,
624    /// Like [`required_size`] but only for min height. Overrides parent min constraints.
625    pub required_min_height: Option<f32>,
626    /// Like [`required_size`] but only for max height. Overrides parent max constraints.
627    pub required_max_height: Option<f32>,
628    /// Minimum size that only applies when the incoming constraint is 0 (unconstrained).
629    /// Use [`min_width`] for an unconditional minimum.
630    pub default_min_width: Option<f32>,
631    pub default_min_height: Option<f32>,
632    pub background: Option<Brush>,
633    pub state_colors: Option<StateColors>,
634    pub state_elevation: Option<StateElevation>,
635
636    pub border: Option<Border>,
637    pub flex_grow: Option<f32>,
638    pub flex_shrink: Option<f32>,
639    pub flex_basis: Option<f32>,
640    pub flex_wrap: Option<FlexWrap>,
641    pub flex_dir: Option<FlexDirection>,
642    pub gap: Option<f32>,
643    pub row_gap: Option<f32>,
644    pub column_gap: Option<f32>,
645    pub align_self: Option<AlignSelf>,
646    pub justify_content: Option<JustifyContent>,
647    pub align_items_container: Option<AlignItems>,
648    pub align_content: Option<AlignContent>,
649    pub clip_rounded: Option<[f32; 4]>,
650    /// Rectangular clip with a clipping operation (Intersect or Difference).
651    /// The rect is relative to the element bounds, in dp.
652    pub clip_rect: Option<ClipRect>,
653    /// Controls whether child content is clipped to the parent bounds.
654    ///
655    /// Defaults to `Clip`. When set to `Visible`, children can overflow
656    /// the parent's rounded rect or clip rect boundary.
657    pub overflow: Option<Overflow>,
658    /// Z-index for hit-testing order (higher = receives events first).
659    pub z_index: f32,
660    /// Z-index for render order (higher = painted on top). If None, uses tree order.
661    pub render_z_index: Option<f32>,
662    /// If true, this view does not create hit regions.
663    pub hit_passthrough: bool,
664    /// If true, this view blocks pointer/touch input for hits below it.
665    pub input_blocker: bool,
666    pub repaint_boundary: bool,
667    pub click: bool,
668    /// When true, the component ignores pointer events and appears disabled.
669    pub disabled: bool,
670    /// When Some(true), the component can receive keyboard focus regardless of interactivity.
671    /// When Some(false), the component cannot receive focus even if interactive.
672    /// When None, focusability is determined implicitly by interactivity (click/pointer/dnd handlers).
673    pub focusable: Option<bool>,
674    /// When true, the Box passes its min constraints to children instead of removing them.
675    pub propagate_min: bool,
676    /// When true, this node and its children form a focus group: focus cycles within
677    /// the group before moving outside it.
678    pub focus_group: bool,
679    pub on_scroll: Option<Rc<dyn Fn(Vec2) -> Vec2>>,
680    /// Scroll modifier binding. When set, the layout engine treats this view as
681    /// a scroll container, applying clipping and offset to children.
682    ///
683    /// Use `Modifier::vertical_scroll()`, `Modifier::horizontal_scroll()`, or
684    /// `Modifier::scrollable()` to set this.
685    pub scroll: Option<crate::scroll::ScrollBinding>,
686    /// Nested scroll connection for coordinated scrolling between this element
687    /// and its scrollable descendants.
688    ///
689    /// When set on an ancestor of a scroll container (e.g. `ScrollArea`,
690    /// `LazyColumn`), the scroll container automatically discovers this
691    /// connection and dispatches pre/post scroll events to it during layout.
692    ///
693    /// Mirrors Compose's `Modifier.nestedScroll(NestedScrollConnection)`.
694    pub nested_scroll_connection: Option<crate::nested_scroll::NestedScrollConnection>,
695    pub on_pointer_down: Option<Rc<dyn Fn(PointerEvent)>>,
696    pub on_pointer_move: Option<Rc<dyn Fn(PointerEvent)>>,
697    pub on_pointer_up: Option<Rc<dyn Fn(PointerEvent)>>,
698    pub on_pointer_cancel: Option<Rc<dyn Fn(PointerEvent)>>,
699    pub on_pointer_enter: Option<Rc<dyn Fn(PointerEvent)>>,
700    pub on_pointer_leave: Option<Rc<dyn Fn(PointerEvent)>>,
701    /// Called when the element is clicked (pointer down then up within bounds).
702    pub on_click: Option<Rc<dyn Fn()>>,
703    /// Called when the element is double-clicked/tapped.
704    pub on_double_click: Option<Rc<dyn Fn()>>,
705    /// Called when the element is long-pressed.
706    pub on_long_click: Option<Rc<dyn Fn()>>,
707    /// Called when the element's global position changes after layout.
708    /// Provides the element's rect in window coordinates.
709    pub on_globally_positioned: Option<Rc<dyn Fn(crate::Rect)>>,
710    /// Called when the element's size changes after layout.
711    /// Provides the new size.
712    pub on_size_changed: Option<Rc<dyn Fn(crate::Vec2)>>,
713    /// Called when a key event is received while this element is focused.
714    /// Return `true` to consume the event. This is the normal handler.
715    pub on_key_event: Option<Rc<dyn Fn(crate::input::KeyEvent) -> bool>>,
716    /// Called before `on_key_event` -> if the preview handler returns `true`,
717    /// the event is consumed and `on_key_event` is NOT called.
718    pub on_preview_key_event: Option<Rc<dyn Fn(crate::input::KeyEvent) -> bool>>,
719    /// Apply a gaussian blur to this element's rendered content.
720    /// When set, `graphics_layer` is auto-enabled if not already set.
721    /// Use `Modifier::blur(radius)` for uniform blur, or
722    /// `Modifier::blur_with_edge(rx, ry, edge)` for per-axis control.
723    pub blur: Option<BlurStyle>,
724    /// Custom layout callback. When set, the element's measurement is delegated
725    /// to this function instead of the default Taffy-based layout.
726    /// The callback receives `LayoutConstraints` (min/max width/height in dp).
727    /// Returns `(width, height)` for this element.
728    pub layout: Option<Rc<dyn Fn(LayoutConstraints) -> (f32, f32)>>,
729    pub semantics: Option<crate::Semantics>,
730    pub alpha: Option<f32>,
731    pub graphics_layer: Option<f32>,
732    pub shadow: Option<ShadowSpec>,
733    pub transform: Option<Transform>,
734    pub grid: Option<GridConfig>,
735    pub grid_col_span: Option<u16>,
736    pub grid_row_span: Option<u16>,
737    pub position_type: Option<PositionType>,
738    pub offset_left: Option<f32>,
739    pub offset_right: Option<f32>,
740    pub offset_top: Option<f32>,
741    pub offset_bottom: Option<f32>,
742
743    pub margin_left: Option<f32>,
744    pub margin_right: Option<f32>,
745    pub margin_top: Option<f32>,
746    pub margin_bottom: Option<f32>,
747    pub aspect_ratio: Option<f32>,
748    /// Size this node's width to its min or max intrinsic content size.
749    pub intrinsic_width: Option<IntrinsicSize>,
750    /// Size this node's height to its min or max intrinsic content size.
751    pub intrinsic_height: Option<IntrinsicSize>,
752    pub painter: Option<Rc<dyn Fn(&mut crate::Scene, crate::Rect, f32)>>,
753
754    // Drag-drop (internal)
755    pub on_drag_start: Option<Rc<dyn Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload>>>,
756    pub on_drag_end: Option<Rc<dyn Fn(crate::dnd::DragEnd)>>,
757    pub on_drag_enter: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
758    pub on_drag_over: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
759    pub on_drag_leave: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
760    pub on_drop: Option<Rc<dyn Fn(crate::dnd::DropEvent) -> bool>>,
761    /// Compose-like `drawDragDecoration`: paints the floating preview while dragging.
762    pub drag_preview: Option<crate::dnd::DragPreview>,
763
764    pub on_action: Option<Rc<dyn Fn(crate::shortcuts::Action) -> bool>>,
765
766    /// Cursor icon hint for desktop/web runners.
767    pub cursor: Option<crate::CursorIcon>,
768
769    /// If set, the size of this node will smoothly animate to its target size
770    /// whenever content size changes. Uses the provided animation spec.
771    pub animate_content_size: Option<AnimationSpec>,
772
773    /// A `FocusRequester` handle that will be associated with this view.
774    /// When the requester's `request_focus()` is called, keyboard focus will
775    /// move to this view.
776    pub focus_requester: Option<crate::runtime::FocusRequester>,
777
778    /// Called when this view gains or loses focus. The boolean parameter is
779    /// `true` when focused, `false` when unfocused.
780    pub on_focus_changed: Option<Rc<dyn Fn(bool)>>,
781
782    /// If set, this view reads its interaction state (hover/press) from this source
783    /// in addition to the implicit view-ID-based matching. The source state is OR'd
784    /// with the implicit state, enabling programmatic override of hover/press visuals.
785    ///
786    /// When set, the layout engine also auto-wires the source to emit PointerDown/Up
787    /// and HoverEnter/Leave events into the source via the hit region's callbacks.
788    pub interaction_source: Option<InteractionSource>,
789
790    /// Text input configuration. When set, this box acts as a text input field.
791    pub text_input: Option<TextInputConfig>,
792
793    /// Indication (ripple/overlay) factory for visual feedback on interaction.
794    pub indication: Option<Rc<dyn IndicationNodeFactory>>,
795}
796
797impl std::fmt::Debug for Modifier {
798    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
799        let mut s = f.debug_struct("Modifier");
800
801        macro_rules! opt_val {
802            ($($name:ident),+ $(,)?) => {
803                $( if self.$name.is_some() { s.field(stringify!($name), &self.$name); } )+
804            };
805        }
806        if self.indication.is_some() {
807            s.field("indication", &"…");
808        }
809
810        opt_val!(
811            key,
812            size,
813            width,
814            height,
815            required_size,
816            padding,
817            padding_values,
818            min_width,
819            min_height,
820            max_width,
821            max_height,
822            required_min_width,
823            required_max_width,
824            required_min_height,
825            required_max_height,
826            default_min_width,
827            default_min_height,
828            fill_max,
829            fill_max_w,
830            fill_max_h,
831            background,
832            state_colors,
833            state_elevation,
834            border,
835            flex_grow,
836            flex_shrink,
837            flex_basis,
838            flex_wrap,
839            flex_dir,
840            gap,
841            row_gap,
842            column_gap,
843            align_self,
844            justify_content,
845            align_items_container,
846            align_content,
847            clip_rounded,
848            clip_rect,
849            render_z_index,
850            semantics,
851            alpha,
852            transform,
853            grid,
854            grid_col_span,
855            grid_row_span,
856            position_type,
857            offset_left,
858            offset_right,
859            offset_top,
860            offset_bottom,
861            margin_left,
862            margin_right,
863            margin_top,
864            margin_bottom,
865            aspect_ratio,
866            intrinsic_width,
867            intrinsic_height,
868            cursor,
869            animate_content_size,
870            blur,
871        );
872
873        macro_rules! opt_cb {
874            ($($name:ident),+ $(,)?) => {
875                $( if self.$name.is_some() { s.field(stringify!($name), &"…"); } )+
876            };
877        }
878        opt_cb!(
879            on_scroll,
880            scroll,
881            nested_scroll_connection,
882            on_pointer_down,
883            on_pointer_move,
884            on_pointer_up,
885            on_pointer_cancel,
886            on_pointer_enter,
887            on_pointer_leave,
888            on_click,
889            on_double_click,
890            on_long_click,
891            on_globally_positioned,
892            on_size_changed,
893            on_key_event,
894            on_preview_key_event,
895            painter,
896            on_drag_start,
897            on_drag_end,
898            on_drag_enter,
899            on_drag_over,
900            on_drag_leave,
901            on_drop,
902            drag_preview,
903            on_action,
904            on_focus_changed,
905            interaction_source,
906            text_input,
907            layout,
908        );
909
910        macro_rules! flag {
911            ($($name:ident),+ $(,)?) => {
912                $( if self.$name { s.field(stringify!($name), &true); } )+
913            };
914        }
915        flag!(
916            hit_passthrough,
917            input_blocker,
918            repaint_boundary,
919            click,
920            disabled,
921            propagate_min,
922            focus_group,
923        );
924
925        if let Some(f) = self.focusable {
926            s.field("focusable", &f);
927        }
928        if self.z_index != 0.0 {
929            s.field("z_index", &self.z_index);
930        }
931
932        s.finish()
933    }
934}
935
936impl_option_fields!(Modifier);
937
938/// Content alignment for a container, applied as the flexbox cross-axis
939/// (`align_items`) and main-axis (`justify_content`) pair. Mirrors Compose's
940/// `Alignment` for `Box(contentAlignment = ...)`.
941#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
942pub enum Alignment {
943    TopStart,
944    TopCenter,
945    TopEnd,
946    CenterStart,
947    #[default]
948    Center,
949    CenterEnd,
950    BottomStart,
951    BottomCenter,
952    BottomEnd,
953}
954
955impl Alignment {
956    /// The corresponding (`align_items`, `justify_content`) pair for flexbox layout.
957    pub fn to_flex(self) -> (AlignItems, JustifyContent) {
958        use AlignItems as AI;
959        use JustifyContent as JC;
960        match self {
961            Self::TopStart => (AI::START, JC::START),
962            Self::TopCenter => (AI::START, JC::CENTER),
963            Self::TopEnd => (AI::START, JC::END),
964            Self::CenterStart => (AI::CENTER, JC::START),
965            Self::Center => (AI::CENTER, JC::CENTER),
966            Self::CenterEnd => (AI::CENTER, JC::END),
967            Self::BottomStart => (AI::END, JC::START),
968            Self::BottomCenter => (AI::END, JC::CENTER),
969            Self::BottomEnd => (AI::END, JC::END),
970        }
971    }
972}
973
974impl Modifier {
975    pub fn new() -> Self {
976        Self::default()
977    }
978
979    /// Attaches a stable identity key to this view node.
980    /// Use for dynamic lists / conditional UI where index-based identity can shift.
981    pub fn key(mut self, key: u64) -> Self {
982        self.key = Some(key);
983        self
984    }
985
986    pub fn size(mut self, w: f32, h: f32) -> Self {
987        self.size = Some(Size {
988            width: w,
989            height: h,
990        });
991        self
992    }
993    pub fn width(mut self, w: f32) -> Self {
994        self.width = Some(w);
995        self
996    }
997    pub fn height(mut self, h: f32) -> Self {
998        self.height = Some(h);
999        self
1000    }
1001    /// Set a fixed size that overrides parent constraints.
1002    /// Unlike `size()` which is bounded by the parent's max constraints,
1003    /// `required_size()` forces the node to this size regardless of the parent,
1004    /// acting as both min and max.
1005    pub fn required_size(mut self, w: f32, h: f32) -> Self {
1006        self.required_size = Some(Size {
1007            width: w,
1008            height: h,
1009        });
1010        self
1011    }
1012    pub fn required_width_in(mut self, min: f32, max: f32) -> Self {
1013        self.required_min_width = Some(min.max(0.0));
1014        self.required_max_width = Some(max.max(0.0));
1015        self
1016    }
1017    pub fn required_height_in(mut self, min: f32, max: f32) -> Self {
1018        self.required_min_height = Some(min.max(0.0));
1019        self.required_max_height = Some(max.max(0.0));
1020        self
1021    }
1022    pub fn required_min_width(mut self, w: f32) -> Self {
1023        self.required_min_width = Some(w.max(0.0));
1024        self
1025    }
1026    pub fn required_max_width(mut self, w: f32) -> Self {
1027        self.required_max_width = Some(w.max(0.0));
1028        self
1029    }
1030    pub fn required_min_height(mut self, h: f32) -> Self {
1031        self.required_min_height = Some(h.max(0.0));
1032        self
1033    }
1034    pub fn required_max_height(mut self, h: f32) -> Self {
1035        self.required_max_height = Some(h.max(0.0));
1036        self
1037    }
1038    /// Minimum size that only takes effect when the incoming constraint is 0 (unconstrained).
1039    pub fn default_min_size(mut self, w: f32, h: f32) -> Self {
1040        self.default_min_width = Some(w.max(0.0));
1041        self.default_min_height = Some(h.max(0.0));
1042        self
1043    }
1044    /// Fill the available space in both dimensions.
1045    /// By default fills 100% (fraction = 1.0). Pass a fraction to fill partially.
1046    pub fn fill_max_size(mut self) -> Self {
1047        self.fill_max = Some(1.0);
1048        self
1049    }
1050    pub fn fill_max_size_frac(mut self, fraction: f32) -> Self {
1051        self.fill_max = Some(fraction.clamp(0.0, 1.0));
1052        self
1053    }
1054    /// Fill the available width. By default fills 100%.
1055    pub fn fill_max_width(mut self) -> Self {
1056        self.fill_max_w = Some(1.0);
1057        self
1058    }
1059    pub fn fill_max_width_frac(mut self, fraction: f32) -> Self {
1060        self.fill_max_w = Some(fraction.clamp(0.0, 1.0));
1061        self
1062    }
1063    /// Fill the available height. By default fills 100%.
1064    pub fn fill_max_height(mut self) -> Self {
1065        self.fill_max_h = Some(1.0);
1066        self
1067    }
1068    pub fn fill_max_height_frac(mut self, fraction: f32) -> Self {
1069        self.fill_max_h = Some(fraction.clamp(0.0, 1.0));
1070        self
1071    }
1072    pub fn padding(mut self, v: f32) -> Self {
1073        self.padding = Some(v);
1074        self
1075    }
1076    pub fn padding_values(mut self, padding: PaddingValues) -> Self {
1077        self.padding_values = Some(padding);
1078        self
1079    }
1080    /// Add padding equal to the current IME (soft keyboard) bottom inset.
1081    /// Combine with `system_bars_padding()` to handle both system bars and keyboard.
1082    pub fn ime_padding(mut self) -> Self {
1083        let insets = crate::locals::window_insets();
1084        let scale = crate::locals::effective_density_scale();
1085        let mut p = self.padding_values.unwrap_or_default();
1086        p.bottom += insets.ime_bottom / scale;
1087        self.padding_values = Some(p);
1088        self
1089    }
1090    /// Add padding equal to the current system bar insets (status bar top, nav bar bottom).
1091    pub fn system_bars_padding(mut self) -> Self {
1092        let insets = crate::locals::window_insets();
1093        let scale = crate::locals::effective_density_scale();
1094        let mut p = self.padding_values.unwrap_or_default();
1095        p.top += insets.top / scale;
1096        p.bottom += insets.bottom / scale;
1097        self.padding_values = Some(p);
1098        self
1099    }
1100    /// Add status bar inset as top padding.
1101    pub fn status_bars_padding(mut self) -> Self {
1102        let insets = crate::locals::window_insets();
1103        let scale = crate::locals::effective_density_scale();
1104        let mut p = self.padding_values.unwrap_or_default();
1105        p.top += insets.top / scale;
1106        self.padding_values = Some(p);
1107        self
1108    }
1109    /// Add navigation bar inset as bottom padding.
1110    pub fn navigation_bars_padding(mut self) -> Self {
1111        let insets = crate::locals::window_insets();
1112        let scale = crate::locals::effective_density_scale();
1113        let mut p = self.padding_values.unwrap_or_default();
1114        p.bottom += insets.bottom / scale;
1115        self.padding_values = Some(p);
1116        self
1117    }
1118    pub fn min_size(mut self, w: f32, h: f32) -> Self {
1119        self.min_width = Some(w);
1120        self.min_height = Some(h);
1121        self
1122    }
1123    pub fn max_size(mut self, w: f32, h: f32) -> Self {
1124        self.max_width = Some(w);
1125        self.max_height = Some(h);
1126        self
1127    }
1128    pub fn min_width(mut self, w: f32) -> Self {
1129        self.min_width = Some(w);
1130        self
1131    }
1132    pub fn min_height(mut self, h: f32) -> Self {
1133        self.min_height = Some(h);
1134        self
1135    }
1136    pub fn max_width(mut self, w: f32) -> Self {
1137        self.max_width = Some(w);
1138        self
1139    }
1140    pub fn max_height(mut self, h: f32) -> Self {
1141        self.max_height = Some(h);
1142        self
1143    }
1144    /// Set a solid color background.
1145    pub fn background(mut self, color: Color) -> Self {
1146        self.background = Some(Brush::Solid(color));
1147        self
1148    }
1149    /// Set a brush (solid, gradient, etc.) background.
1150    pub fn background_brush(mut self, brush: Brush) -> Self {
1151        self.background = Some(brush);
1152        self
1153    }
1154    pub fn border(mut self, width: f32, color: Color, radius: f32) -> Self {
1155        self.border = Some(Border {
1156            width,
1157            color,
1158            radius: [radius; 4],
1159        });
1160        self
1161    }
1162    pub fn border_radii(mut self, width: f32, color: Color, radii: [f32; 4]) -> Self {
1163        self.border = Some(Border {
1164            width,
1165            color,
1166            radius: radii,
1167        });
1168        self
1169    }
1170    pub fn flex_grow(mut self, v: f32) -> Self {
1171        self.flex_grow = Some(v);
1172        self
1173    }
1174    pub fn flex_shrink(mut self, v: f32) -> Self {
1175        self.flex_shrink = Some(v);
1176        self
1177    }
1178    pub fn flex_basis(mut self, v: f32) -> Self {
1179        self.flex_basis = Some(v);
1180        self
1181    }
1182    pub fn flex_wrap(mut self, w: FlexWrap) -> Self {
1183        self.flex_wrap = Some(w);
1184        self
1185    }
1186    pub fn flex_dir(mut self, d: FlexDirection) -> Self {
1187        self.flex_dir = Some(d);
1188        self
1189    }
1190    pub fn gap(mut self, v: f32) -> Self {
1191        let v = v.max(0.0);
1192        self.gap = Some(v);
1193        self.row_gap = Some(v);
1194        self.column_gap = Some(v);
1195        self
1196    }
1197    pub fn row_gap(mut self, v: f32) -> Self {
1198        self.row_gap = Some(v.max(0.0));
1199        self
1200    }
1201    pub fn column_gap(mut self, v: f32) -> Self {
1202        self.column_gap = Some(v.max(0.0));
1203        self
1204    }
1205    pub fn align_self(mut self, a: AlignSelf) -> Self {
1206        self.align_self = Some(a);
1207        self
1208    }
1209    pub fn align_self_center(mut self) -> Self {
1210        self.align_self = Some(AlignSelf::CENTER);
1211        self
1212    }
1213    pub fn justify_content(mut self, j: JustifyContent) -> Self {
1214        self.justify_content = Some(j);
1215        self
1216    }
1217    pub fn align_items(mut self, a: AlignItems) -> Self {
1218        self.align_items_container = Some(a);
1219        self
1220    }
1221    /// Compose-like content alignment (sets both `align_items` and
1222    /// `justify_content` in one call).
1223    pub fn content_alignment(self, alignment: Alignment) -> Self {
1224        let (ai, jc) = alignment.to_flex();
1225        self.align_items(ai).justify_content(jc)
1226    }
1227    pub fn align_content(mut self, a: AlignContent) -> Self {
1228        self.align_content = Some(a);
1229        self
1230    }
1231    pub fn clip_rounded(mut self, radius: f32) -> Self {
1232        self.clip_rounded = Some([radius; 4]);
1233        self
1234    }
1235    pub fn clip_rounded_radii(mut self, radii: [f32; 4]) -> Self {
1236        self.clip_rounded = Some(radii);
1237        self
1238    }
1239    /// Clip a rectangular region from this element using the given operation.
1240    /// `left`, `top`, `right`, `bottom` are relative to the element bounds, in dp.
1241    pub fn clip_rect(mut self, left: f32, top: f32, right: f32, bottom: f32, op: ClipOp) -> Self {
1242        self.clip_rect = Some(ClipRect {
1243            left,
1244            top,
1245            right,
1246            bottom,
1247            op,
1248        });
1249        self
1250    }
1251    pub fn overflow(mut self, overflow: Overflow) -> Self {
1252        self.overflow = Some(overflow);
1253        self
1254    }
1255    pub fn z_index(mut self, z: f32) -> Self {
1256        self.z_index = z;
1257        self
1258    }
1259
1260    /// Sets the render z-index for this view. Higher values are painted on top.
1261    /// Unlike `z_index` (which only affects hit-testing), this affects visual layering.
1262    pub fn render_z_index(mut self, z: f32) -> Self {
1263        self.render_z_index = Some(z);
1264        self
1265    }
1266
1267    /// Prevent pointer/touch from reaching lower layers.
1268    pub fn input_blocker(mut self) -> Self {
1269        self.input_blocker = true;
1270        self
1271    }
1272
1273    pub fn hit_passthrough(mut self) -> Self {
1274        self.hit_passthrough = true;
1275        self
1276    }
1277    pub fn clickable(mut self) -> Self {
1278        self.click = true;
1279        if self.indication.is_none() {
1280            self.indication = crate::locals::local_indication();
1281        }
1282        self
1283    }
1284    /// Make this element clickable and attach an [`InteractionSource`] for state tracking.
1285    /// Combines `.clickable()` and `.interaction_source(&source)` in one call.
1286    pub fn clickable_with_source(mut self, source: &MutableInteractionSource) -> Self {
1287        self.click = true;
1288        self.interaction_source = Some(source.source());
1289        if self.indication.is_none() {
1290            self.indication = crate::locals::local_indication();
1291        }
1292        self
1293    }
1294    /// Set state-driven background colors for hover, press, disabled states.
1295    /// The layout engine automatically selects and animates between these based on interaction.
1296    pub fn state_colors(mut self, colors: StateColors) -> Self {
1297        self.state_colors = Some(colors);
1298        self
1299    }
1300    /// Set state-driven elevation values for hover, press, disabled states.
1301    pub fn state_elevation(mut self, elev: StateElevation) -> Self {
1302        self.state_elevation = Some(elev);
1303        self
1304    }
1305    /// Mark this component as disabled - it won't respond to pointer events.
1306    pub fn disabled(mut self) -> Self {
1307        self.disabled = true;
1308        self
1309    }
1310    /// Mark this component as enabled or disabled.
1311    pub fn enabled(mut self, enabled: bool) -> Self {
1312        self.disabled = !enabled;
1313        self
1314    }
1315    /// Set explicit focusability for this component.
1316    /// When `true`, the component can receive keyboard focus even without
1317    /// explicit click/pointer/dnd handlers. When `false`, focus is suppressed
1318    /// even for interactive components.
1319    pub fn focusable(mut self, focusable: bool) -> Self {
1320        self.focusable = Some(focusable);
1321        self
1322    }
1323    /// Mark this node as a focus group: focus cycles within this group before
1324    /// moving to siblings outside it.
1325    pub fn focus_group(mut self) -> Self {
1326        self.focus_group = true;
1327        self
1328    }
1329    /// Attach an [`InteractionSource`] to this view. The source provides shared
1330    /// interaction state (hover/press/focus/drag) that supplements the implicit
1331    /// view-ID-based state. The layout engine auto-wires pointer events
1332    /// (press/hover), keyboard activation (Space/Enter press parity), focus
1333    /// transitions (Focus/Unfocus) and DnD drag start/end into the source so it
1334    /// stays in sync with user interaction.
1335    ///
1336    /// Use this when you need programmatic control of interaction state (e.g.,
1337    /// showing pressed state during an async operation) or to share interaction
1338    /// state between components.
1339    pub fn interaction_source(mut self, source: &MutableInteractionSource) -> Self {
1340        self.interaction_source = Some(source.source());
1341        self
1342    }
1343    /// Convenience: register hover enter/leave callbacks.
1344    /// Shorthand for setting `on_pointer_enter` and `on_pointer_leave`.
1345    pub fn hoverable(
1346        mut self,
1347        on_enter: impl Fn() + 'static,
1348        on_leave: impl Fn() + 'static,
1349    ) -> Self {
1350        self.on_pointer_enter = Some(Rc::new(move |_| on_enter()));
1351        self.on_pointer_leave = Some(Rc::new(move |_| on_leave()));
1352        self
1353    }
1354    /// Attach an [`InteractionSource`] to track hover state without explicit callbacks.
1355    /// The source automatically receives HoverEnter/Leave events from the layout engine's
1356    /// auto-wiring, so you can use it with `collect_is_hovered()` for custom visuals.
1357    pub fn hoverable_with_source(mut self, source: &MutableInteractionSource) -> Self {
1358        self.interaction_source = Some(source.source());
1359        self
1360    }
1361    /// When true, Box passes min-width/min-height constraints to its children
1362    /// instead of allowing them to shrink below the parent's min constraints.
1363    pub fn propagate_min_constraints(mut self, propagate: bool) -> Self {
1364        self.propagate_min = propagate;
1365        self
1366    }
1367    pub fn on_scroll(mut self, f: impl Fn(Vec2) -> Vec2 + 'static) -> Self {
1368        self.on_scroll = Some(Rc::new(f));
1369        self
1370    }
1371    /// Attach a vertical scroll binding to this modifier.
1372    /// The binding provides callbacks for scroll handling, viewport tracking, etc.
1373    /// Use `ScrollState::to_binding()` to create one from a scroll state.
1374    pub fn vertical_scroll(mut self, binding: crate::scroll::ScrollAxisBinding) -> Self {
1375        self.scroll = Some(crate::scroll::ScrollBinding::Vertical(binding));
1376        self
1377    }
1378    /// Attach a horizontal scroll binding to this modifier.
1379    pub fn horizontal_scroll(mut self, binding: crate::scroll::ScrollAxisBinding) -> Self {
1380        self.scroll = Some(crate::scroll::ScrollBinding::Horizontal(binding));
1381        self
1382    }
1383    /// Attach a 2D scroll binding to this modifier.
1384    pub fn scrollable(mut self, binding: crate::scroll::ScrollBothBinding) -> Self {
1385        self.scroll = Some(crate::scroll::ScrollBinding::Both(binding));
1386        self
1387    }
1388    /// Attach a nested scroll connection that descendant scrollable containers
1389    /// will discover during layout. Mirrors Compose's `Modifier.nestedScroll`.
1390    ///
1391    /// The connection receives pre/post scroll and pre/post fling callbacks
1392    /// when a scrollable child dispatches events, enabling coordinated scrolling
1393    /// patterns like collapsing toolbars and pull-to-refresh.
1394    pub fn nested_scroll(mut self, conn: crate::nested_scroll::NestedScrollConnection) -> Self {
1395        self.nested_scroll_connection = Some(conn);
1396        self
1397    }
1398    pub fn on_pointer_down(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1399        self.on_pointer_down = Some(Rc::new(f));
1400        self
1401    }
1402    pub fn on_pointer_move(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1403        self.on_pointer_move = Some(Rc::new(f));
1404        self
1405    }
1406    pub fn on_pointer_up(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1407        self.on_pointer_up = Some(Rc::new(f));
1408        self
1409    }
1410    pub fn on_pointer_cancel(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1411        self.on_pointer_cancel = Some(Rc::new(f));
1412        self
1413    }
1414    pub fn on_pointer_enter(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1415        self.on_pointer_enter = Some(Rc::new(f));
1416        self
1417    }
1418    pub fn on_pointer_leave(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1419        self.on_pointer_leave = Some(Rc::new(f));
1420        self
1421    }
1422    pub fn on_click(mut self, f: impl Fn() + 'static) -> Self {
1423        self.on_click = Some(Rc::new(f));
1424        self.click = true;
1425        if self.semantics.is_none() {
1426            self.semantics = Some(crate::Semantics::new(crate::Role::Button));
1427        }
1428        self
1429    }
1430    pub fn on_double_click(mut self, f: impl Fn() + 'static) -> Self {
1431        self.on_double_click = Some(Rc::new(f));
1432        self.click = true;
1433        self
1434    }
1435    pub fn on_long_click(mut self, f: impl Fn() + 'static) -> Self {
1436        self.on_long_click = Some(Rc::new(f));
1437        self.click = true;
1438        self
1439    }
1440    pub fn clickable_ext(
1441        mut self,
1442        enabled: bool,
1443        on_click_label: Option<String>,
1444        role: Option<crate::semantics::Role>,
1445        on_click: impl Fn() + 'static,
1446    ) -> Self {
1447        if !enabled {
1448            let mut s = self.semantics.clone().unwrap_or_else(|| {
1449                crate::semantics::Semantics::new(role.unwrap_or(crate::semantics::Role::Button))
1450            });
1451            s.enabled = false;
1452            if let Some(r) = role {
1453                s.role = r;
1454            }
1455            if let Some(l) = on_click_label {
1456                s.label = Some(l);
1457            }
1458            return self
1459                .clickable()
1460                .enabled(false)
1461                .default_min_size(48.0, 48.0)
1462                .semantics(s);
1463        }
1464        self = self.clickable().on_click(on_click);
1465        if role.is_some() || on_click_label.is_some() {
1466            let mut s = self.semantics.clone().unwrap_or_else(|| {
1467                crate::semantics::Semantics::new(role.unwrap_or(crate::semantics::Role::Button))
1468            });
1469            s.enabled = true;
1470            if let Some(r) = role {
1471                s.role = r;
1472            }
1473            if let Some(l) = on_click_label {
1474                s.label = Some(l);
1475            }
1476            self = self.semantics(s);
1477        }
1478        self.default_min_size(48.0, 48.0)
1479    }
1480    pub fn combined_clickable(
1481        mut self,
1482        enabled: bool,
1483        on_click_label: Option<String>,
1484        role: Option<crate::semantics::Role>,
1485        on_long_click_label: Option<String>,
1486        on_click: impl Fn() + 'static,
1487        on_long_click: Option<impl Fn() + 'static>,
1488        on_double_click: Option<impl Fn() + 'static>,
1489    ) -> Self {
1490        let _ = on_long_click_label;
1491        if !enabled {
1492            return self.clickable_ext(false, on_click_label, role, || {});
1493        }
1494        self = self.clickable_ext(true, on_click_label, role, on_click);
1495        if let Some(f) = on_long_click {
1496            self = self.on_long_click(f);
1497        }
1498        if let Some(f) = on_double_click {
1499            self = self.on_double_click(f);
1500        }
1501        self.default_min_size(48.0, 48.0)
1502    }
1503    pub fn semantics(mut self, s: crate::Semantics) -> Self {
1504        self.semantics = Some(s);
1505        self
1506    }
1507    pub fn alpha(mut self, a: f32) -> Self {
1508        self.alpha = Some(a);
1509        self
1510    }
1511    /// Render this subtree into an offscreen texture, then composite it
1512    /// back into the parent with the given group `alpha` (0.0..=1.0).
1513    /// Allows correct blending when children overlap inside the layer, and
1514    /// sets up the architecture for future layer effects (shadow, blur, clip).
1515    pub fn graphics_layer(mut self, alpha: f32) -> Self {
1516        self.graphics_layer = Some(alpha.clamp(0.0, 1.0));
1517        self
1518    }
1519    /// Drop shadow with the given `blur_radius` (dp) and vertical `offset_y` (dp).
1520    /// The shadow color defaults to black with alpha 64 (~25%). Combines with
1521    /// [`Modifier::graphics_layer`] to draw a shadow underneath the layer.
1522    pub fn shadow(mut self, blur_radius: f32, offset_y: f32) -> Self {
1523        self.shadow = Some(ShadowSpec {
1524            blur_radius: blur_radius.max(0.0),
1525            offset_y,
1526            color: Color(0, 0, 0, 64),
1527        });
1528        self
1529    }
1530    /// Drop shadow with a custom color. Alpha 0..=255.
1531    pub fn shadow_with_color(mut self, blur_radius: f32, offset_y: f32, color: Color) -> Self {
1532        self.shadow = Some(ShadowSpec {
1533            blur_radius: blur_radius.max(0.0),
1534            offset_y,
1535            color,
1536        });
1537        self
1538    }
1539    /// Material-style elevation. Auto-scales blur and offset by `level` (dp)
1540    /// and uses a default shadow color. Level 0 = no shadow. 4 = subtle;
1541    /// 16 = strong. Requires [`Modifier::graphics_layer`] to take effect.
1542    pub fn elevation(mut self, level: f32) -> Self {
1543        if level <= 0.0 {
1544            self.shadow = None;
1545            return self;
1546        }
1547        self.shadow = Some(ShadowSpec {
1548            blur_radius: level * 2.0,
1549            offset_y: level * 0.5,
1550            color: Color(0, 0, 0, (level * 8.0).clamp(8.0, 80.0) as u8),
1551        });
1552        self
1553    }
1554    pub fn transform(mut self, t: Transform) -> Self {
1555        self.transform = Some(t);
1556        self
1557    }
1558    pub fn grid(mut self, columns: usize, row_gap: f32, column_gap: f32) -> Self {
1559        self.grid = Some(GridConfig {
1560            columns,
1561            row_gap,
1562            column_gap,
1563        });
1564        self
1565    }
1566    pub fn grid_span(mut self, col_span: u16, row_span: u16) -> Self {
1567        self.grid_col_span = Some(col_span);
1568        self.grid_row_span = Some(row_span);
1569        self
1570    }
1571    pub fn absolute(mut self) -> Self {
1572        self.position_type = Some(PositionType::Absolute);
1573        self
1574    }
1575    pub fn offset(
1576        mut self,
1577        left: Option<f32>,
1578        top: Option<f32>,
1579        right: Option<f32>,
1580        bottom: Option<f32>,
1581    ) -> Self {
1582        self.offset_left = left;
1583        self.offset_top = top;
1584        self.offset_right = right;
1585        self.offset_bottom = bottom;
1586        self
1587    }
1588    pub fn offset_left(mut self, v: f32) -> Self {
1589        self.offset_left = Some(v);
1590        self
1591    }
1592    pub fn offset_right(mut self, v: f32) -> Self {
1593        self.offset_right = Some(v);
1594        self
1595    }
1596    pub fn offset_top(mut self, v: f32) -> Self {
1597        self.offset_top = Some(v);
1598        self
1599    }
1600    pub fn offset_bottom(mut self, v: f32) -> Self {
1601        self.offset_bottom = Some(v);
1602        self
1603    }
1604    pub fn margin(mut self, v: f32) -> Self {
1605        self.margin_left = Some(v);
1606        self.margin_right = Some(v);
1607        self.margin_top = Some(v);
1608        self.margin_bottom = Some(v);
1609        self
1610    }
1611
1612    pub fn margin_horizontal(mut self, v: f32) -> Self {
1613        self.margin_left = Some(v);
1614        self.margin_right = Some(v);
1615        self
1616    }
1617
1618    pub fn margin_vertical(mut self, v: f32) -> Self {
1619        self.margin_top = Some(v);
1620        self.margin_bottom = Some(v);
1621        self
1622    }
1623    pub fn aspect_ratio(mut self, ratio: f32) -> Self {
1624        self.aspect_ratio = Some(ratio);
1625        self
1626    }
1627    /// Size this node's width to its min or max intrinsic content size.
1628    pub fn intrinsic_width(mut self, mode: IntrinsicSize) -> Self {
1629        self.intrinsic_width = Some(mode);
1630        self
1631    }
1632    /// Size this node's height to its min or max intrinsic content size.
1633    pub fn intrinsic_height(mut self, mode: IntrinsicSize) -> Self {
1634        self.intrinsic_height = Some(mode);
1635        self
1636    }
1637    pub fn painter(mut self, f: impl Fn(&mut crate::Scene, crate::Rect, f32) + 'static) -> Self {
1638        self.painter = Some(Rc::new(f));
1639        self
1640    }
1641    pub fn scale(self, s: f32) -> Self {
1642        self.scale2(s, s)
1643    }
1644    pub fn scale2(mut self, sx: f32, sy: f32) -> Self {
1645        let mut t = self.transform.unwrap_or_else(Transform::identity);
1646        t.scale_x *= sx;
1647        t.scale_y *= sy;
1648        self.transform = Some(t);
1649        self
1650    }
1651    pub fn translate(mut self, x: f32, y: f32) -> Self {
1652        let t = self.transform.unwrap_or_else(Transform::identity);
1653        self.transform = Some(t.combine(&Transform::translate(x, y)));
1654        self
1655    }
1656    pub fn translate_vec2(self, v: Vec2) -> Self {
1657        self.translate(v.x, v.y)
1658    }
1659    pub fn rotate(mut self, radians: f32) -> Self {
1660        let mut t = self.transform.unwrap_or_else(Transform::identity);
1661        t.rotate += radians;
1662        self.transform = Some(t);
1663        self
1664    }
1665    pub fn transform_origin(mut self, x: f32, y: f32) -> Self {
1666        let mut t = self.transform.unwrap_or_else(Transform::identity);
1667        t.origin_x = x;
1668        t.origin_y = y;
1669        self.transform = Some(t);
1670        self
1671    }
1672    pub fn weight(mut self, w: f32) -> Self {
1673        let w = w.max(0.0);
1674        self.flex_grow = Some(w);
1675        self.flex_shrink = Some(1.0);
1676        // dp units; 0 is fine.
1677        self.flex_basis = Some(0.0);
1678        self
1679    }
1680    /// Marks this view as a repaint boundary candidate.
1681    ///
1682    /// The engine may cache its painted output.
1683    pub fn repaint_boundary(mut self) -> Self {
1684        self.repaint_boundary = true;
1685        self
1686    }
1687    pub fn on_action(mut self, f: impl Fn(crate::shortcuts::Action) -> bool + 'static) -> Self {
1688        self.on_action = Some(Rc::new(f));
1689        self
1690    }
1691
1692    /// Mark this node as a drag source. Return `Some(payload)` to start dragging.
1693    pub fn on_drag_start(
1694        mut self,
1695        f: impl Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload> + 'static,
1696    ) -> Self {
1697        self.on_drag_start = Some(Rc::new(f));
1698        self
1699    }
1700
1701    /// Called when a drag ends (drop accepted or canceled/ignored).
1702    pub fn on_drag_end(mut self, f: impl Fn(crate::dnd::DragEnd) + 'static) -> Self {
1703        self.on_drag_end = Some(Rc::new(f));
1704        self
1705    }
1706
1707    /// Called when a drag first enters this target.
1708    pub fn on_drag_enter(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
1709        self.on_drag_enter = Some(Rc::new(f));
1710        self
1711    }
1712
1713    /// Called on every pointer move while a drag is over this target.
1714    pub fn on_drag_over(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
1715        self.on_drag_over = Some(Rc::new(f));
1716        self
1717    }
1718
1719    /// Called when a drag leaves this target.
1720    pub fn on_drag_leave(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
1721        self.on_drag_leave = Some(Rc::new(f));
1722        self
1723    }
1724
1725    /// Called on pointer release while a drag is over this target.
1726    /// Return `true` to accept the drop.
1727    pub fn on_drop(mut self, f: impl Fn(crate::dnd::DropEvent) -> bool + 'static) -> Self {
1728        self.on_drop = Some(Rc::new(f));
1729        self
1730    }
1731
1732    /// Custom drag preview decoration (Compose `drawDragDecoration`).
1733    ///
1734    /// Called every frame while this node is the active drag source.
1735    /// Coordinates are screen px; see [`crate::dnd::DragPreviewCtx`].
1736    pub fn draw_drag_decoration(
1737        mut self,
1738        f: impl Fn(&mut crate::Scene, &crate::dnd::DragPreviewCtx) + 'static,
1739    ) -> Self {
1740        self.drag_preview = Some(Rc::new(f));
1741        self
1742    }
1743
1744    /// Same as [`Self::draw_drag_decoration`] but takes an existing [`crate::dnd::DragPreview`] Rc.
1745    pub fn draw_drag_decoration_rc(mut self, preview: crate::dnd::DragPreview) -> Self {
1746        self.drag_preview = Some(preview);
1747        self
1748    }
1749
1750    /// Convenience: floating label chip as the drag preview.
1751    pub fn drag_preview_label(self, label: impl Into<String>, accent: crate::Color) -> Self {
1752        self.draw_drag_decoration_rc(crate::dnd::drag_preview_label(label, accent))
1753    }
1754
1755    /// Convenience: elevated chip preview.
1756    pub fn drag_preview_chip(self, label: impl Into<String>, accent: crate::Color) -> Self {
1757        self.draw_drag_decoration_rc(crate::dnd::drag_preview_chip(label, accent))
1758    }
1759
1760    /// Set the cursor icon hint for desktop/web runners.
1761    pub fn cursor(mut self, c: crate::CursorIcon) -> Self {
1762        self.cursor = Some(c);
1763        self
1764    }
1765
1766    /// Animate size changes smoothly when the content's natural size changes.
1767    /// Uses the provided `AnimationSpec` for the transition.
1768    /// The content will be clipped to the animated size during transitions.
1769    pub fn animate_content_size(mut self, spec: AnimationSpec) -> Self {
1770        self.animate_content_size = Some(spec);
1771        self
1772    }
1773
1774    /// Attach a `FocusRequester` to this view. The requester will be associated
1775    /// with the view's focusable element, allowing programmatic focus requests.
1776    pub fn focus_requester(mut self, fr: crate::runtime::FocusRequester) -> Self {
1777        self.focus_requester = Some(fr);
1778        self
1779    }
1780
1781    /// Make this composable a focus target (`.focusable(true)`).
1782    /// Corresponds to Compose's `Modifier.focusTarget()`.
1783    pub fn focus_target(mut self) -> Self {
1784        self.focusable = Some(true);
1785        self
1786    }
1787
1788    /// Register a callback that fires when this view gains or loses keyboard focus.
1789    /// The argument is `true` when the view receives focus, `false` when it loses it.
1790    pub fn on_focus_changed(mut self, f: impl Fn(bool) + 'static) -> Self {
1791        self.on_focus_changed = Some(Rc::new(f));
1792        self
1793    }
1794
1795    /// Called after layout when this element's position changes.
1796    /// The callback receives the element's rect in dp (device-independent pixels).
1797    /// Fires whenever the rect changes, including on the initial layout.
1798    pub fn on_globally_positioned(mut self, f: impl Fn(crate::Rect) + 'static) -> Self {
1799        self.on_globally_positioned = Some(Rc::new(f));
1800        self
1801    }
1802
1803    /// Called after layout when this element's size changes.
1804    /// Provides the new (width, height) in dp.
1805    pub fn on_size_changed(mut self, f: impl Fn(crate::Vec2) + 'static) -> Self {
1806        self.on_size_changed = Some(Rc::new(f));
1807        self
1808    }
1809
1810    /// Called when a key event is received while this element is focused.
1811    /// Return `true` to indicate the event was consumed and should not
1812    /// propagate further (e.g. to text input handling or shortcut dispatch).
1813    pub fn on_key_event(mut self, f: impl Fn(crate::input::KeyEvent) -> bool + 'static) -> Self {
1814        self.on_key_event = Some(Rc::new(f));
1815        self
1816    }
1817
1818    /// Preview variant of `on_key_event`. Called before `on_key_event`;
1819    /// if the preview handler returns `true`, the event is consumed
1820    /// and `on_key_event` is NOT called.
1821    pub fn on_preview_key_event(
1822        mut self,
1823        f: impl Fn(crate::input::KeyEvent) -> bool + 'static,
1824    ) -> Self {
1825        self.on_preview_key_event = Some(Rc::new(f));
1826        self
1827    }
1828
1829    /// Apply a gaussian blur to this element's rendered content.
1830    /// `radius_dp` is the uniform blur radius in device-independent pixels.
1831    /// Larger values produce a stronger blur.
1832    /// Uses `Rectangle` edge treatment (clip to bounds).
1833    ///
1834    /// Requires `graphics_layer` to be enabled (set automatically if not).
1835    pub fn blur(mut self, radius_dp: f32) -> Self {
1836        self.blur = Some(BlurStyle {
1837            radius_x: radius_dp.max(0.0),
1838            radius_y: radius_dp.max(0.0),
1839            edge_treatment: BlurredEdgeTreatment::Rectangle,
1840        });
1841        self
1842    }
1843
1844    /// Apply a gaussian blur with separate horizontal/vertical radii.
1845    /// `edge_treatment` controls how edge pixels are handled.
1846    ///
1847    /// Requires `graphics_layer` to be enabled (set automatically if not).
1848    pub fn blur_with_edge(
1849        mut self,
1850        radius_x: f32,
1851        radius_y: f32,
1852        edge_treatment: BlurredEdgeTreatment,
1853    ) -> Self {
1854        self.blur = Some(BlurStyle {
1855            radius_x: radius_x.max(0.0),
1856            radius_y: radius_y.max(0.0),
1857            edge_treatment,
1858        });
1859        self
1860    }
1861
1862    /// Override this element's measured size with a custom callback.
1863    /// The callback receives `LayoutConstraints` (min/max width/height in dp),
1864    /// where `max_width`/`max_height` may be `f32::INFINITY` if unbounded.
1865    /// Returns `(width, height)` for this element.
1866    ///
1867    /// Child placement is handled by the parent layout (same as Compose's
1868    /// `Modifier.size` family).
1869    pub fn layout(mut self, f: impl Fn(LayoutConstraints) -> (f32, f32) + 'static) -> Self {
1870        self.layout = Some(Rc::new(f));
1871        self
1872    }
1873
1874    /// Mark this Box as a text input field with the given configuration.
1875    pub fn text_input(mut self, config: TextInputConfig) -> Self {
1876        self.text_input = Some(config);
1877        self
1878    }
1879
1880    /// Attach an indication (ripple/highlight) factory for visual feedback.
1881    /// The factory is paired with an `InteractionSource` (via `.interaction_source(...)`)
1882    /// to draw press/hover/focus visual feedback.
1883    pub fn indication(mut self, factory: Rc<dyn IndicationNodeFactory>) -> Self {
1884        self.indication = Some(factory);
1885        self
1886    }
1887}