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