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