Skip to main content

repose_core/
modifier.rs

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