Skip to main content

repose_core/
modifier.rs

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