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