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