Skip to main content

repose_core/
modifier.rs

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