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