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