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