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,
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 modifier binding. When set, the layout engine treats this view as
739    /// a scroll container, applying clipping and offset to children.
740    ///
741    /// Use `Modifier::vertical_scroll()`, `Modifier::horizontal_scroll()`, or
742    /// `Modifier::scrollable()` to set this.
743    pub scroll: Option<crate::scroll::ScrollBinding>,
744    pub scrollbar_style: Option<crate::scroll::ScrollbarStyle>,
745    /// Nested scroll connection for coordinated scrolling between this element
746    /// and its scrollable descendants.
747    ///
748    /// When set on an ancestor of a scroll container (e.g. `ScrollArea`,
749    /// `LazyColumn`), the scroll container automatically discovers this
750    /// connection and dispatches pre/post scroll events to it during layout.
751    ///
752    /// Mirrors Compose's `Modifier.nestedScroll(NestedScrollConnection)`.
753    pub nested_scroll_connection: Option<crate::nested_scroll::NestedScrollConnection>,
754    pub on_pointer_down: Option<Rc<dyn Fn(PointerEvent)>>,
755    pub on_pointer_move: Option<Rc<dyn Fn(PointerEvent)>>,
756    pub on_pointer_up: Option<Rc<dyn Fn(PointerEvent)>>,
757    pub on_pointer_cancel: Option<Rc<dyn Fn(PointerEvent)>>,
758    pub on_pointer_enter: Option<Rc<dyn Fn(PointerEvent)>>,
759    pub on_pointer_leave: Option<Rc<dyn Fn(PointerEvent)>>,
760    /// Called when the element is clicked (pointer down then up within bounds).
761    pub on_click: Option<Rc<dyn Fn()>>,
762    /// Called when the element is double-clicked/tapped.
763    pub on_double_click: Option<Rc<dyn Fn()>>,
764    /// Called when the element is long-pressed.
765    pub on_long_click: Option<Rc<dyn Fn()>>,
766    /// Called when the element's global position changes after layout.
767    /// Provides the element's rect in window coordinates.
768    pub on_globally_positioned: Option<Rc<dyn Fn(crate::Rect)>>,
769    /// Called when the element's size changes after layout.
770    /// Provides the new size.
771    pub on_size_changed: Option<Rc<dyn Fn(crate::Vec2)>>,
772    /// Called when a key event is received while this element is focused.
773    /// Return `true` to consume the event. This is the normal handler.
774    pub on_key_event: Option<Rc<dyn Fn(crate::input::KeyEvent) -> bool>>,
775    /// Called before `on_key_event` -> if the preview handler returns `true`,
776    /// the event is consumed and `on_key_event` is NOT called.
777    pub on_preview_key_event: Option<Rc<dyn Fn(crate::input::KeyEvent) -> bool>>,
778    /// Called when an IME event (composition update/commit) is received
779    /// while this element is focused. Return `true` to consume the event.
780    pub on_ime: Option<Rc<dyn Fn(crate::input::ImeEvent) -> bool>>,
781    /// Apply a gaussian blur to this element's rendered content.
782    /// When set, `graphics_layer` is auto-enabled if not already set.
783    /// Use `Modifier::blur(radius)` for uniform blur, or
784    /// `Modifier::blur_with_edge(rx, ry, edge)` for per-axis control.
785    pub blur: Option<BlurStyle>,
786    /// Custom layout callback. When set, the element's measurement is delegated
787    /// to this function instead of the default Taffy-based layout.
788    /// The callback receives `LayoutConstraints` (min/max width/height in [`Dp`]).
789    /// Returns `(width, height)` for this element, in [`Dp`].
790    pub layout: Option<Rc<dyn Fn(LayoutConstraints) -> (Dp, Dp)>>,
791    pub semantics: Option<crate::Semantics>,
792    pub alpha: Option<f32>,
793    pub graphics_layer: Option<f32>,
794    pub shadow: Option<ShadowSpec>,
795    /// Corner radii the shadow silhouette is shaped to, independent of what the
796    /// layer's own alpha contains. Mirrors Compose's `Modifier.shadow(shape)`,
797    /// so a square card can still cast a rounded shadow. When `None` the
798    /// silhouette follows the layer alpha as-is.
799    pub shadow_shape: Option<[Dp; 4]>,
800    pub transform: Option<Transform>,
801    pub grid: Option<GridConfig>,
802    pub grid_col_span: Option<u16>,
803    pub grid_row_span: Option<u16>,
804    pub position_type: Option<PositionType>,
805    pub offset_left: Option<Dp>,
806    pub offset_right: Option<Dp>,
807    pub offset_top: Option<Dp>,
808    pub offset_bottom: Option<Dp>,
809
810    pub margin_left: Option<Dp>,
811    pub margin_right: Option<Dp>,
812    pub margin_top: Option<Dp>,
813    pub margin_bottom: Option<Dp>,
814    pub aspect_ratio: Option<f32>,
815    /// Size this node's width to its min or max intrinsic content size.
816    /// Wired to taffy's `min-content`/`max-content` sizing keywords.
817    pub intrinsic_width: Option<IntrinsicSize>,
818    /// Size this node's height to its min or max intrinsic content size.
819    /// Wired to taffy's `min-content`/`max-content` sizing keywords.
820    pub intrinsic_height: Option<IntrinsicSize>,
821    /// `fit-content(limit)` width in [`Dp`]: `max(min-content, min(max-content, limit))`.
822    /// Applies when no explicit [`width`](Self::width) is set.
823    pub fit_content_width: Option<Dp>,
824    /// `fit-content(limit)` height in [`Dp`]. Applies when no explicit
825    /// [`height`](Self::height) is set.
826    pub fit_content_height: Option<Dp>,
827    /// Baseline alignment request for a `Row` child. See [`BaselineAlign`].
828    /// Set via `RowScope` methods; inert under non-`Row` parents.
829    pub baseline_align: Option<BaselineAlign>,
830    /// CSS containment (`contain: layout` / `paint` / `content`, taffy 0.14+).
831    /// Establishes an independent formatting context; useful with
832    /// `repaint_boundary`-style subtrees and virtualized lists.
833    /// Note: `contain: size` is not supported by taffy.
834    pub contain: Option<Contain>,
835    pub painter: Option<Rc<dyn Fn(&mut crate::Scene, crate::Rect, f32)>>,
836    pub paint_callback: Option<crate::PaintCallbackPayload>,
837
838    // Drag-drop (internal)
839    pub on_drag_start: Option<Rc<dyn Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload>>>,
840    pub on_drag_end: Option<Rc<dyn Fn(crate::dnd::DragEnd)>>,
841    pub on_drag_enter: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
842    pub on_drag_over: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
843    pub on_drag_leave: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
844    pub on_drop: Option<Rc<dyn Fn(crate::dnd::DropEvent) -> bool>>,
845    /// Compose-like `drawDragDecoration`: paints the floating preview while dragging.
846    pub drag_preview: Option<crate::dnd::DragPreview>,
847
848    pub on_action: Option<Rc<dyn Fn(crate::shortcuts::Action) -> bool>>,
849
850    /// Cursor icon hint for desktop/web runners.
851    pub cursor: Option<crate::CursorIcon>,
852
853    /// If set, the size of this node will smoothly animate to its target size
854    /// whenever content size changes. Uses the provided animation spec.
855    pub animate_content_size: Option<AnimationSpec>,
856
857    /// A `FocusRequester` handle that will be associated with this view.
858    /// When the requester's `request_focus()` is called, keyboard focus will
859    /// move to this view.
860    pub focus_requester: Option<crate::runtime::FocusRequester>,
861
862    /// Called when this view gains or loses focus. The boolean parameter is
863    /// `true` when focused, `false` when unfocused.
864    pub on_focus_changed: Option<Rc<dyn Fn(bool)>>,
865
866    /// If set, this view reads its interaction state (hover/press) from this source
867    /// in addition to the implicit view-ID-based matching. The source state is OR'd
868    /// with the implicit state, enabling programmatic override of hover/press visuals.
869    ///
870    /// When set, the layout engine also auto-wires the source to emit PointerDown/Up
871    /// and HoverEnter/Leave events into the source via the hit region's callbacks.
872    pub interaction_source: Option<InteractionSource>,
873
874    /// Text input configuration. When set, this box acts as a text input field.
875    pub text_input: Option<TextInputConfig>,
876
877    /// Selected range for a `Text` node. The layout engine paints the highlight
878    /// behind the glyphs from the node's real text layout.
879    pub text_selection: Option<crate::text::TextSelection>,
880
881    /// Indication (ripple/overlay) factory for visual feedback on interaction.
882    pub indication: Option<Rc<dyn IndicationNodeFactory>>,
883}
884
885impl std::fmt::Debug for Modifier {
886    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
887        let mut s = f.debug_struct("Modifier");
888
889        macro_rules! opt_val {
890            ($($name:ident),+ $(,)?) => {
891                $( if self.$name.is_some() { s.field(stringify!($name), &self.$name); } )+
892            };
893        }
894        if self.indication.is_some() {
895            s.field("indication", &"…");
896        }
897        s.field("scrollbar_style", &self.scrollbar_style);
898
899        opt_val!(
900            key,
901            size,
902            width,
903            height,
904            required_size,
905            padding,
906            padding_values,
907            min_width,
908            min_height,
909            max_width,
910            max_height,
911            required_min_width,
912            required_max_width,
913            required_min_height,
914            required_max_height,
915            default_min_width,
916            default_min_height,
917            fill_max,
918            fill_max_w,
919            fill_max_h,
920            background,
921            state_colors,
922            state_elevation,
923            border,
924            flex_grow,
925            flex_shrink,
926            flex_basis,
927            flex_wrap,
928            flex_dir,
929            gap,
930            row_gap,
931            column_gap,
932            align_self,
933            justify_content,
934            align_items_container,
935            align_content,
936            clip_rounded,
937            clip_rect,
938            render_z_index,
939            semantics,
940            alpha,
941            transform,
942            grid,
943            grid_col_span,
944            grid_row_span,
945            position_type,
946            offset_left,
947            offset_right,
948            offset_top,
949            offset_bottom,
950            margin_left,
951            margin_right,
952            margin_top,
953            margin_bottom,
954            aspect_ratio,
955            intrinsic_width,
956            intrinsic_height,
957            cursor,
958            animate_content_size,
959            blur,
960        );
961
962        macro_rules! opt_cb {
963            ($($name:ident),+ $(,)?) => {
964                $( if self.$name.is_some() { s.field(stringify!($name), &"…"); } )+
965            };
966        }
967        opt_cb!(
968            on_scroll,
969            scroll,
970            nested_scroll_connection,
971            on_pointer_down,
972            on_pointer_move,
973            on_pointer_up,
974            on_pointer_cancel,
975            on_pointer_enter,
976            on_pointer_leave,
977            on_click,
978            on_double_click,
979            on_long_click,
980            on_globally_positioned,
981            on_size_changed,
982            on_key_event,
983            on_preview_key_event,
984            on_ime,
985            painter,
986            paint_callback,
987            on_drag_start,
988            on_drag_end,
989            on_drag_enter,
990            on_drag_over,
991            on_drag_leave,
992            on_drop,
993            drag_preview,
994            on_action,
995            on_focus_changed,
996            interaction_source,
997            text_input,
998            text_selection,
999            layout,
1000        );
1001
1002        macro_rules! flag {
1003            ($($name:ident),+ $(,)?) => {
1004                $( if self.$name { s.field(stringify!($name), &true); } )+
1005            };
1006        }
1007        flag!(
1008            hit_passthrough,
1009            input_blocker,
1010            repaint_boundary,
1011            click,
1012            disabled,
1013            propagate_min,
1014            focus_group,
1015        );
1016
1017        if let Some(f) = self.focusable {
1018            s.field("focusable", &f);
1019        }
1020        if self.z_index != 0.0 {
1021            s.field("z_index", &self.z_index);
1022        }
1023
1024        s.finish()
1025    }
1026}
1027
1028impl_option_fields!(Modifier);
1029
1030/// Content alignment for a container, applied as the flexbox cross-axis
1031/// (`align_items`) and main-axis (`justify_content`) pair. Mirrors Compose's
1032/// `Alignment` for `Box(contentAlignment = ...)`.
1033#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1034pub enum Alignment {
1035    TopStart,
1036    TopCenter,
1037    TopEnd,
1038    CenterStart,
1039    #[default]
1040    Center,
1041    CenterEnd,
1042    BottomStart,
1043    BottomCenter,
1044    BottomEnd,
1045}
1046
1047impl Alignment {
1048    /// The corresponding (`align_items`, `justify_content`) pair for flexbox layout.
1049    pub fn to_flex(self) -> (AlignItems, JustifyContent) {
1050        use AlignItems as AI;
1051        use JustifyContent as JC;
1052        match self {
1053            Self::TopStart => (AI::START, JC::START),
1054            Self::TopCenter => (AI::START, JC::CENTER),
1055            Self::TopEnd => (AI::START, JC::END),
1056            Self::CenterStart => (AI::CENTER, JC::START),
1057            Self::Center => (AI::CENTER, JC::CENTER),
1058            Self::CenterEnd => (AI::CENTER, JC::END),
1059            Self::BottomStart => (AI::END, JC::START),
1060            Self::BottomCenter => (AI::END, JC::CENTER),
1061            Self::BottomEnd => (AI::END, JC::END),
1062        }
1063    }
1064
1065    /// Like [`to_flex`](Self::to_flex) but with CSS *safe* alignment:
1066    /// overflowing content is aligned from the start edge instead of
1067    /// overflowing both edges, so it stays reachable inside scroll
1068    /// containers (taffy 0.11+).
1069    pub fn to_flex_safe(self) -> (AlignItems, JustifyContent) {
1070        use AlignItems as AI;
1071        use JustifyContent as JC;
1072        match self {
1073            Self::TopStart => (AI::SAFE_START, JC::SAFE_START),
1074            Self::TopCenter => (AI::SAFE_START, JC::SAFE_CENTER),
1075            Self::TopEnd => (AI::SAFE_START, JC::SAFE_END),
1076            Self::CenterStart => (AI::SAFE_CENTER, JC::SAFE_START),
1077            Self::Center => (AI::SAFE_CENTER, JC::SAFE_CENTER),
1078            Self::CenterEnd => (AI::SAFE_CENTER, JC::SAFE_END),
1079            Self::BottomStart => (AI::SAFE_END, JC::SAFE_START),
1080            Self::BottomCenter => (AI::SAFE_END, JC::SAFE_CENTER),
1081            Self::BottomEnd => (AI::SAFE_END, JC::SAFE_END),
1082        }
1083    }
1084}
1085
1086impl Modifier {
1087    pub fn new() -> Self {
1088        Self::default()
1089    }
1090
1091    /// Attaches a stable identity key to this view node.
1092    /// Use for dynamic lists / conditional UI where index-based identity can shift.
1093    pub fn key(mut self, key: u64) -> Self {
1094        self.key = Some(key);
1095        self
1096    }
1097
1098    pub fn size(mut self, w: Dp, h: Dp) -> Self {
1099        self.size = Some(DpSize::new(w, h));
1100        self
1101    }
1102    pub fn width(mut self, w: Dp) -> Self {
1103        self.width = Some(w);
1104        self
1105    }
1106    pub fn height(mut self, h: Dp) -> Self {
1107        self.height = Some(h);
1108        self
1109    }
1110    /// Set a fixed size that overrides parent constraints.
1111    /// Unlike `size()` which is bounded by the parent's max constraints,
1112    /// `required_size()` forces the node to this size regardless of the parent,
1113    /// acting as both min and max.
1114    pub fn required_size(mut self, w: Dp, h: Dp) -> Self {
1115        self.required_size = Some(DpSize::new(w, h));
1116        self
1117    }
1118    pub fn required_width_in(mut self, min: Dp, max: Dp) -> Self {
1119        self.required_min_width = Some(Dp(min.0.max(0.0)));
1120        self.required_max_width = Some(Dp(max.0.max(0.0)));
1121        self
1122    }
1123    pub fn required_height_in(mut self, min: Dp, max: Dp) -> Self {
1124        self.required_min_height = Some(Dp(min.0.max(0.0)));
1125        self.required_max_height = Some(Dp(max.0.max(0.0)));
1126        self
1127    }
1128    pub fn required_min_width(mut self, w: Dp) -> Self {
1129        self.required_min_width = Some(Dp(w.0.max(0.0)));
1130        self
1131    }
1132    pub fn required_max_width(mut self, w: Dp) -> Self {
1133        self.required_max_width = Some(Dp(w.0.max(0.0)));
1134        self
1135    }
1136    pub fn required_min_height(mut self, h: Dp) -> Self {
1137        self.required_min_height = Some(Dp(h.0.max(0.0)));
1138        self
1139    }
1140    pub fn required_max_height(mut self, h: Dp) -> Self {
1141        self.required_max_height = Some(Dp(h.0.max(0.0)));
1142        self
1143    }
1144    /// Minimum size that only takes effect when the incoming constraint is 0 (unconstrained).
1145    pub fn default_min_size(mut self, w: Dp, h: Dp) -> Self {
1146        self.default_min_width = Some(Dp(w.0.max(0.0)));
1147        self.default_min_height = Some(Dp(h.0.max(0.0)));
1148        self
1149    }
1150    /// Fill the available space in both dimensions.
1151    /// By default fills 100% (fraction = 1.0). Pass a fraction to fill partially.
1152    pub fn fill_max_size(mut self) -> Self {
1153        self.fill_max = Some(1.0);
1154        self
1155    }
1156    pub fn fill_max_size_frac(mut self, fraction: f32) -> Self {
1157        self.fill_max = Some(fraction.clamp(0.0, 1.0));
1158        self
1159    }
1160    /// Fill the available width. By default fills 100%.
1161    pub fn fill_max_width(mut self) -> Self {
1162        self.fill_max_w = Some(1.0);
1163        self
1164    }
1165    pub fn fill_max_width_frac(mut self, fraction: f32) -> Self {
1166        self.fill_max_w = Some(fraction.clamp(0.0, 1.0));
1167        self
1168    }
1169    /// Fill the available height. By default fills 100%.
1170    pub fn fill_max_height(mut self) -> Self {
1171        self.fill_max_h = Some(1.0);
1172        self
1173    }
1174    pub fn fill_max_height_frac(mut self, fraction: f32) -> Self {
1175        self.fill_max_h = Some(fraction.clamp(0.0, 1.0));
1176        self
1177    }
1178    pub fn padding(mut self, v: Dp) -> Self {
1179        self.padding = Some(v);
1180        self
1181    }
1182    pub fn padding_values(mut self, padding: PaddingValues) -> Self {
1183        self.padding_values = Some(padding);
1184        self
1185    }
1186    /// Add padding equal to the current IME (soft keyboard) bottom inset.
1187    /// Combine with `system_bars_padding()` to handle both system bars and keyboard.
1188    /// Window insets are stored in px and converted to [`Dp`] here.
1189    pub fn ime_padding(mut self) -> Self {
1190        use crate::units::Px;
1191        let insets = crate::locals::window_insets();
1192        let mut p = self.padding_values.unwrap_or_default();
1193        p.bottom = p.bottom + Px(insets.ime_bottom).to_dp();
1194        self.padding_values = Some(p);
1195        self
1196    }
1197    /// Add padding equal to the current system bar insets (status bar top, nav bar bottom).
1198    pub fn system_bars_padding(mut self) -> Self {
1199        use crate::units::Px;
1200        let insets = crate::locals::window_insets();
1201        let mut p = self.padding_values.unwrap_or_default();
1202        p.top = p.top + Px(insets.top).to_dp();
1203        p.bottom = p.bottom + Px(insets.bottom).to_dp();
1204        self.padding_values = Some(p);
1205        self
1206    }
1207    /// Add status bar inset as top padding.
1208    pub fn status_bars_padding(mut self) -> Self {
1209        use crate::units::Px;
1210        let insets = crate::locals::window_insets();
1211        let mut p = self.padding_values.unwrap_or_default();
1212        p.top = p.top + Px(insets.top).to_dp();
1213        self.padding_values = Some(p);
1214        self
1215    }
1216    /// Add navigation bar inset as bottom padding.
1217    pub fn navigation_bars_padding(mut self) -> Self {
1218        let insets = crate::locals::window_insets();
1219        use crate::units::Px;
1220        let mut p = self.padding_values.unwrap_or_default();
1221        p.bottom = p.bottom + Px(insets.bottom).to_dp();
1222        self.padding_values = Some(p);
1223        self
1224    }
1225    pub fn min_size(mut self, w: Dp, h: Dp) -> Self {
1226        self.min_width = Some(w);
1227        self.min_height = Some(h);
1228        self
1229    }
1230    pub fn max_size(mut self, w: Dp, h: Dp) -> Self {
1231        self.max_width = Some(w);
1232        self.max_height = Some(h);
1233        self
1234    }
1235    pub fn min_width(mut self, w: Dp) -> Self {
1236        self.min_width = Some(w);
1237        self
1238    }
1239    pub fn min_height(mut self, h: Dp) -> Self {
1240        self.min_height = Some(h);
1241        self
1242    }
1243    pub fn max_width(mut self, w: Dp) -> Self {
1244        self.max_width = Some(w);
1245        self
1246    }
1247    pub fn max_height(mut self, h: Dp) -> Self {
1248        self.max_height = Some(h);
1249        self
1250    }
1251    /// Set a solid color background.
1252    pub fn background(mut self, color: Color) -> Self {
1253        self.background = Some(Brush::Solid(color));
1254        self
1255    }
1256    /// Set a brush (solid, gradient, etc.) background.
1257    pub fn background_brush(mut self, brush: Brush) -> Self {
1258        self.background = Some(brush);
1259        self
1260    }
1261    pub fn border(mut self, width: Dp, color: Color, radius: Dp) -> Self {
1262        self.border = Some(Border {
1263            width,
1264            brush: Brush::Solid(color),
1265            radius: [radius; 4],
1266        });
1267        self
1268    }
1269    /// Brush border (gradient, etc.). Mirrors Compose
1270    /// `Modifier.border(width: Dp, brush: Brush, shape: Shape)`.
1271    pub fn border_brush(mut self, width: Dp, brush: Brush, radius: Dp) -> Self {
1272        self.border = Some(Border {
1273            width,
1274            brush,
1275            radius: [radius; 4],
1276        });
1277        self
1278    }
1279    pub fn border_radii(mut self, width: Dp, color: Color, radii: [Dp; 4]) -> Self {
1280        self.border = Some(Border {
1281            width,
1282            brush: Brush::Solid(color),
1283            radius: radii,
1284        });
1285        self
1286    }
1287    /// Brush border with per-corner radii.
1288    pub fn border_brush_radii(mut self, width: Dp, brush: Brush, radii: [Dp; 4]) -> Self {
1289        self.border = Some(Border {
1290            width,
1291            brush,
1292            radius: radii,
1293        });
1294        self
1295    }
1296    pub fn flex_grow(mut self, v: f32) -> Self {
1297        self.flex_grow = Some(v);
1298        self
1299    }
1300    pub fn flex_shrink(mut self, v: f32) -> Self {
1301        self.flex_shrink = Some(v);
1302        self
1303    }
1304    pub fn flex_basis(mut self, v: Dp) -> Self {
1305        self.flex_basis = Some(v);
1306        self
1307    }
1308    pub fn flex_wrap(mut self, w: FlexWrap) -> Self {
1309        self.flex_wrap = Some(w);
1310        self
1311    }
1312    /// `flex-basis: content` - size from content, ignoring any
1313    /// [`flex_basis`](Self::flex_basis) value.
1314    pub fn flex_basis_content(mut self) -> Self {
1315        self.flex_basis_content = true;
1316        self
1317    }
1318    pub fn flex_dir(mut self, d: FlexDirection) -> Self {
1319        self.flex_dir = Some(d);
1320        self
1321    }
1322    pub fn gap(mut self, v: Dp) -> Self {
1323        let v = Dp(v.0.max(0.0));
1324        self.gap = Some(v);
1325        self.row_gap = Some(v);
1326        self.column_gap = Some(v);
1327        self
1328    }
1329    pub fn row_gap(mut self, v: Dp) -> Self {
1330        self.row_gap = Some(Dp(v.0.max(0.0)));
1331        self
1332    }
1333    pub fn column_gap(mut self, v: Dp) -> Self {
1334        self.column_gap = Some(Dp(v.0.max(0.0)));
1335        self
1336    }
1337    pub fn align_self(mut self, a: AlignSelf) -> Self {
1338        self.align_self = Some(a);
1339        self
1340    }
1341    pub fn align_self_center(mut self) -> Self {
1342        self.align_self = Some(AlignSelf::CENTER);
1343        self
1344    }
1345    pub fn justify_content(mut self, j: JustifyContent) -> Self {
1346        self.justify_content = Some(j);
1347        self
1348    }
1349    pub fn align_items(mut self, a: AlignItems) -> Self {
1350        self.align_items_container = Some(a);
1351        self
1352    }
1353    /// Compose-like content alignment (sets both `align_items` and
1354    /// `justify_content` in one call).
1355    pub fn content_alignment(self, alignment: Alignment) -> Self {
1356        let (ai, jc) = alignment.to_flex();
1357        self.align_items(ai).justify_content(jc)
1358    }
1359    /// Like [`content_alignment`](Self::content_alignment) but with CSS
1360    /// *safe* alignment, so overflowing content stays reachable inside
1361    /// scroll containers. Prefer this for centered dialogs and scroll
1362    /// content that may exceed the viewport.
1363    pub fn content_alignment_safe(self, alignment: Alignment) -> Self {
1364        let (ai, jc) = alignment.to_flex_safe();
1365        self.align_items(ai).justify_content(jc)
1366    }
1367    pub fn align_content(mut self, a: AlignContent) -> Self {
1368        self.align_content = Some(a);
1369        self
1370    }
1371    pub fn clip_rounded(mut self, radius: Dp) -> Self {
1372        self.clip_rounded = Some([radius; 4]);
1373        self
1374    }
1375    pub fn clip_rounded_radii(mut self, radii: [Dp; 4]) -> Self {
1376        self.clip_rounded = Some(radii);
1377        self
1378    }
1379    /// Clip a rectangular region from this element using the given operation.
1380    /// `left`, `top`, `right`, `bottom` are relative to the element bounds, in [`Dp`].
1381    pub fn clip_rect(mut self, left: Dp, top: Dp, right: Dp, bottom: Dp, op: ClipOp) -> Self {
1382        self.clip_rect = Some(ClipRect {
1383            left,
1384            top,
1385            right,
1386            bottom,
1387            op,
1388        });
1389        self
1390    }
1391    pub fn overflow(mut self, overflow: Overflow) -> Self {
1392        self.overflow = Some(overflow);
1393        self
1394    }
1395    pub fn z_index(mut self, z: f32) -> Self {
1396        self.z_index = z;
1397        self
1398    }
1399
1400    /// Sets the render z-index for this view. Higher values are painted on top.
1401    /// Unlike `z_index` (which only affects hit-testing), this affects visual layering.
1402    pub fn render_z_index(mut self, z: f32) -> Self {
1403        self.render_z_index = Some(z);
1404        self
1405    }
1406
1407    /// Prevent pointer/touch from reaching lower layers.
1408    pub fn input_blocker(mut self) -> Self {
1409        self.input_blocker = true;
1410        self
1411    }
1412
1413    pub fn hit_passthrough(mut self) -> Self {
1414        self.hit_passthrough = true;
1415        self
1416    }
1417    pub fn clickable(mut self) -> Self {
1418        self.click = true;
1419        if self.indication.is_none() {
1420            self.indication = crate::locals::local_indication();
1421        }
1422        self
1423    }
1424    /// Make this element clickable and attach an [`InteractionSource`] for state tracking.
1425    /// Combines `.clickable()` and `.interaction_source(&source)` in one call.
1426    pub fn clickable_with_source(mut self, source: &MutableInteractionSource) -> Self {
1427        self.click = true;
1428        self.interaction_source = Some(source.source());
1429        if self.indication.is_none() {
1430            self.indication = crate::locals::local_indication();
1431        }
1432        self
1433    }
1434    /// Set state-driven background colors for hover, press, disabled states.
1435    /// The layout engine automatically selects and animates between these based on interaction.
1436    pub fn state_colors(mut self, colors: StateColors) -> Self {
1437        self.state_colors = Some(colors);
1438        self
1439    }
1440    /// Set state-driven elevation values for hover, press, disabled states.
1441    pub fn state_elevation(mut self, elev: StateElevation) -> Self {
1442        self.state_elevation = Some(elev);
1443        self
1444    }
1445    /// Mark this component as disabled - it won't respond to pointer events.
1446    pub fn disabled(mut self) -> Self {
1447        self.disabled = true;
1448        self
1449    }
1450    /// Mark this component as enabled or disabled.
1451    pub fn enabled(mut self, enabled: bool) -> Self {
1452        self.disabled = !enabled;
1453        self
1454    }
1455    /// Set explicit focusability for this component.
1456    /// When `true`, the component can receive keyboard focus even without
1457    /// explicit click/pointer/dnd handlers. When `false`, focus is suppressed
1458    /// even for interactive components.
1459    pub fn focusable(mut self, focusable: bool) -> Self {
1460        self.focusable = Some(focusable);
1461        self
1462    }
1463    /// Mark this node as a focus group: focus cycles within this group before
1464    /// moving to siblings outside it.
1465    pub fn focus_group(mut self) -> Self {
1466        self.focus_group = true;
1467        self
1468    }
1469    /// Attach an [`InteractionSource`] to this view. The source provides shared
1470    /// interaction state (hover/press/focus/drag) that supplements the implicit
1471    /// view-ID-based state. The layout engine auto-wires pointer events
1472    /// (press/hover), keyboard activation (Space/Enter press parity), focus
1473    /// transitions (Focus/Unfocus) and DnD drag start/end into the source so it
1474    /// stays in sync with user interaction.
1475    ///
1476    /// Use this when you need programmatic control of interaction state (e.g.,
1477    /// showing pressed state during an async operation) or to share interaction
1478    /// state between components.
1479    pub fn interaction_source(mut self, source: &MutableInteractionSource) -> Self {
1480        self.interaction_source = Some(source.source());
1481        self
1482    }
1483    /// Convenience: register hover enter/leave callbacks.
1484    /// Shorthand for setting `on_pointer_enter` and `on_pointer_leave`.
1485    pub fn hoverable(
1486        mut self,
1487        on_enter: impl Fn() + 'static,
1488        on_leave: impl Fn() + 'static,
1489    ) -> Self {
1490        self.on_pointer_enter = Some(Rc::new(move |_| on_enter()));
1491        self.on_pointer_leave = Some(Rc::new(move |_| on_leave()));
1492        self
1493    }
1494    /// Attach an [`InteractionSource`] to track hover state without explicit callbacks.
1495    /// The source automatically receives HoverEnter/Leave events from the layout engine's
1496    /// auto-wiring, so you can use it with `collect_is_hovered()` for custom visuals.
1497    pub fn hoverable_with_source(mut self, source: &MutableInteractionSource) -> Self {
1498        self.interaction_source = Some(source.source());
1499        self
1500    }
1501    /// When true, Box passes min-width/min-height constraints to its children
1502    /// instead of allowing them to shrink below the parent's min constraints.
1503    pub fn propagate_min_constraints(mut self, propagate: bool) -> Self {
1504        self.propagate_min = propagate;
1505        self
1506    }
1507    pub fn on_scroll(mut self, f: impl Fn(Vec2) -> Vec2 + 'static) -> Self {
1508        self.on_scroll = Some(Rc::new(f));
1509        self
1510    }
1511    /// Attach a vertical scroll binding to this modifier.
1512    /// The binding provides callbacks for scroll handling, viewport tracking, etc.
1513    /// Use `ScrollState::to_binding()` to create one from a scroll state.
1514    pub fn vertical_scroll(mut self, binding: crate::scroll::ScrollAxisBinding) -> Self {
1515        self.scroll = Some(crate::scroll::ScrollBinding::Vertical(binding));
1516        self
1517    }
1518    /// Attach a horizontal scroll binding to this modifier.
1519    pub fn horizontal_scroll(mut self, binding: crate::scroll::ScrollAxisBinding) -> Self {
1520        self.scroll = Some(crate::scroll::ScrollBinding::Horizontal(binding));
1521        self
1522    }
1523    /// Attach a 2D scroll binding to this modifier.
1524    pub fn scrollable(mut self, binding: crate::scroll::ScrollBothBinding) -> Self {
1525        self.scroll = Some(crate::scroll::ScrollBinding::Both(binding));
1526        self
1527    }
1528    pub fn scrollbar_style(mut self, style: crate::scroll::ScrollbarStyle) -> Self {
1529        self.scrollbar_style = Some(style);
1530        self
1531    }
1532    pub fn scrollbar_style_or_default(&self) -> crate::scroll::ScrollbarStyle {
1533        self.scrollbar_style.clone().unwrap_or_default()
1534    }
1535    /// Attach a nested scroll connection that descendant scrollable containers
1536    /// will discover during layout. Mirrors Compose's `Modifier.nestedScroll`.
1537    ///
1538    /// The connection receives pre/post scroll and pre/post fling callbacks
1539    /// when a scrollable child dispatches events, enabling coordinated scrolling
1540    /// patterns like collapsing toolbars and pull-to-refresh.
1541    pub fn nested_scroll(mut self, conn: crate::nested_scroll::NestedScrollConnection) -> Self {
1542        self.nested_scroll_connection = Some(conn);
1543        self
1544    }
1545    pub fn on_pointer_down(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1546        self.on_pointer_down = Some(Rc::new(f));
1547        self
1548    }
1549    pub fn on_pointer_move(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1550        self.on_pointer_move = Some(Rc::new(f));
1551        self
1552    }
1553    pub fn on_pointer_up(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1554        self.on_pointer_up = Some(Rc::new(f));
1555        self
1556    }
1557    pub fn on_pointer_cancel(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1558        self.on_pointer_cancel = Some(Rc::new(f));
1559        self
1560    }
1561
1562    pub fn draggable(self, on_drag: impl Fn(Vec2) + 'static) -> Self {
1563        self.draggable_with_end(on_drag, || {})
1564    }
1565
1566    /// Like [`draggable`](Self::draggable), but also reports when the gesture
1567    /// ends (pointer up or cancel) so the consumer can settle to a rest state.
1568    pub fn draggable_with_end(
1569        self,
1570        on_drag: impl Fn(Vec2) + 'static,
1571        on_drag_end: impl Fn() + 'static,
1572    ) -> Self {
1573        let drag_pos = crate::state::remember_mutable(Vec2::default);
1574        let is_dragging = crate::state::remember_mutable(|| false);
1575        let on_drag = Rc::new(on_drag);
1576        let on_drag_end = Rc::new(on_drag_end);
1577        self.on_pointer_down({
1578            let drag_pos = drag_pos.clone();
1579            let is_dragging = is_dragging.clone();
1580            move |ev| {
1581                is_dragging.set(true);
1582                drag_pos.set(ev.position);
1583            }
1584        })
1585        .on_pointer_up({
1586            let is_dragging = is_dragging.clone();
1587            let on_drag_end = on_drag_end.clone();
1588            move |_| {
1589                if *is_dragging.get() {
1590                    is_dragging.set(false);
1591                    on_drag_end();
1592                }
1593            }
1594        })
1595        .on_pointer_cancel({
1596            let is_dragging = is_dragging.clone();
1597            let on_drag_end = on_drag_end.clone();
1598            move |_| {
1599                if *is_dragging.get() {
1600                    is_dragging.set(false);
1601                    on_drag_end();
1602                }
1603            }
1604        })
1605        .on_pointer_move({
1606            let drag_pos = drag_pos.clone();
1607            let is_dragging = is_dragging.clone();
1608            let on_drag = on_drag.clone();
1609            move |ev| {
1610                if !*is_dragging.get() {
1611                    return;
1612                }
1613                let prev = *drag_pos.get();
1614                let cur = ev.position;
1615                let delta = Vec2 {
1616                    x: cur.x - prev.x,
1617                    y: cur.y - prev.y,
1618                };
1619                drag_pos.set(cur);
1620                on_drag(delta);
1621                crate::frame_clock::request_frame();
1622            }
1623        })
1624    }
1625    pub fn on_pointer_enter(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1626        self.on_pointer_enter = Some(Rc::new(f));
1627        self
1628    }
1629    pub fn on_pointer_leave(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1630        self.on_pointer_leave = Some(Rc::new(f));
1631        self
1632    }
1633    pub fn on_click(mut self, f: impl Fn() + 'static) -> Self {
1634        self.on_click = Some(Rc::new(f));
1635        self.click = true;
1636        if self.semantics.is_none() {
1637            self.semantics = Some(crate::Semantics::new(crate::Role::Button));
1638        }
1639        self
1640    }
1641    pub fn on_double_click(mut self, f: impl Fn() + 'static) -> Self {
1642        self.on_double_click = Some(Rc::new(f));
1643        self.click = true;
1644        self
1645    }
1646    pub fn on_long_click(mut self, f: impl Fn() + 'static) -> Self {
1647        self.on_long_click = Some(Rc::new(f));
1648        self.click = true;
1649        self
1650    }
1651    pub fn clickable_ext(
1652        mut self,
1653        enabled: bool,
1654        on_click_label: Option<String>,
1655        role: Option<crate::semantics::Role>,
1656        on_click: impl Fn() + 'static,
1657    ) -> Self {
1658        if !enabled {
1659            let mut s = self.semantics.clone().unwrap_or_else(|| {
1660                crate::semantics::Semantics::new(role.unwrap_or(crate::semantics::Role::Button))
1661            });
1662            s.enabled = false;
1663            if let Some(r) = role {
1664                s.role = r;
1665            }
1666            if let Some(l) = on_click_label {
1667                s.label = Some(l);
1668            }
1669            return self
1670                .clickable()
1671                .enabled(false)
1672                .default_min_size(Dp(48.0), Dp(48.0))
1673                .semantics(s);
1674        }
1675        self = self.clickable().on_click(on_click);
1676        if role.is_some() || on_click_label.is_some() {
1677            let mut s = self.semantics.clone().unwrap_or_else(|| {
1678                crate::semantics::Semantics::new(role.unwrap_or(crate::semantics::Role::Button))
1679            });
1680            s.enabled = true;
1681            if let Some(r) = role {
1682                s.role = r;
1683            }
1684            if let Some(l) = on_click_label {
1685                s.label = Some(l);
1686            }
1687            self = self.semantics(s);
1688        }
1689        self.default_min_size(Dp(48.0), Dp(48.0))
1690    }
1691    pub fn combined_clickable(
1692        mut self,
1693        enabled: bool,
1694        on_click_label: Option<String>,
1695        role: Option<crate::semantics::Role>,
1696        on_long_click_label: Option<String>,
1697        on_click: impl Fn() + 'static,
1698        on_long_click: Option<impl Fn() + 'static>,
1699        on_double_click: Option<impl Fn() + 'static>,
1700    ) -> Self {
1701        let _ = on_long_click_label;
1702        if !enabled {
1703            return self.clickable_ext(false, on_click_label, role, || {});
1704        }
1705        self = self.clickable_ext(true, on_click_label, role, on_click);
1706        if let Some(f) = on_long_click {
1707            self = self.on_long_click(f);
1708        }
1709        if let Some(f) = on_double_click {
1710            self = self.on_double_click(f);
1711        }
1712        self.default_min_size(Dp(48.0), Dp(48.0))
1713    }
1714    pub fn semantics(mut self, s: crate::Semantics) -> Self {
1715        self.semantics = Some(s);
1716        self
1717    }
1718    pub fn alpha(mut self, a: f32) -> Self {
1719        self.alpha = Some(a);
1720        self
1721    }
1722    /// Render this subtree into an offscreen texture, then composite it
1723    /// back into the parent with the given group `alpha` (0.0..=1.0).
1724    /// Allows correct blending when children overlap inside the layer, and
1725    /// sets up the architecture for future layer effects (shadow, blur, clip).
1726    pub fn graphics_layer(mut self, alpha: f32) -> Self {
1727        self.graphics_layer = Some(alpha.clamp(0.0, 1.0));
1728        self
1729    }
1730    /// Drop shadow with the given `blur_radius` ([`Dp`]) and vertical `offset_y` ([`Dp`]).
1731    /// The shadow color defaults to black with alpha 64 (~25%). Combines with
1732    /// [`Modifier::graphics_layer`] to draw a shadow underneath the layer.
1733    pub fn shadow(mut self, blur_radius: Dp, offset_y: Dp) -> Self {
1734        self.shadow = Some(ShadowSpec {
1735            blur_radius: Dp(blur_radius.0.max(0.0)),
1736            offset_y,
1737            color: Color(0, 0, 0, 64),
1738        });
1739        self
1740    }
1741    /// Drop shadow with a custom color. Alpha 0..=255.
1742    pub fn shadow_with_color(mut self, blur_radius: Dp, offset_y: Dp, color: Color) -> Self {
1743        self.shadow = Some(ShadowSpec {
1744            blur_radius: Dp(blur_radius.0.max(0.0)),
1745            offset_y,
1746            color,
1747        });
1748        self
1749    }
1750    /// Material-style elevation. Auto-scales blur and offset by `level` ([`Dp`])
1751    /// and uses a default shadow color. Level 0 = no shadow. 4 = subtle;
1752    /// 16 = strong. Requires [`Modifier::graphics_layer`] to take effect.
1753    pub fn elevation(mut self, level: Dp) -> Self {
1754        if level.0 <= 0.0 {
1755            self.shadow = None;
1756            return self;
1757        }
1758        self.shadow = Some(ShadowSpec {
1759            blur_radius: Dp(level.0 * 2.0),
1760            offset_y: Dp(level.0 * 0.5),
1761            color: Color(0, 0, 0, (level.0 * 8.0).clamp(8.0, 80.0) as u8),
1762        });
1763        self
1764    }
1765    /// [`Modifier::elevation`] with an explicit shadow color, mirroring Compose's
1766    /// `Modifier.shadow(elevation, ambientColor, spotColor)`. Alpha 0..=255.
1767    pub fn elevation_with_color(mut self, level: Dp, color: Color) -> Self {
1768        if level.0 <= 0.0 {
1769            self.shadow = None;
1770            return self;
1771        }
1772        self.shadow = Some(ShadowSpec {
1773            blur_radius: Dp(level.0 * 2.0),
1774            offset_y: Dp(level.0 * 0.5),
1775            color,
1776        });
1777        self
1778    }
1779    /// Shape the shadow to a uniform corner radius, independent of the layer's
1780    /// own alpha. Mirrors Compose's `Modifier.shadow(shape = RoundedCornerShape)`,
1781    /// so unclipped square content still casts a rounded shadow. Requires a
1782    /// [`Modifier::shadow`] or [`Modifier::elevation`] to take effect.
1783    pub fn shadow_rounded(mut self, radius: Dp) -> Self {
1784        self.shadow_shape = Some([radius; 4]);
1785        self
1786    }
1787    /// Per-corner radii for the shadow silhouette, like
1788    /// [`Modifier::clip_rounded_radii`].
1789    pub fn shadow_rounded_radii(mut self, radii: [Dp; 4]) -> Self {
1790        self.shadow_shape = Some(radii);
1791        self
1792    }
1793    pub fn transform(mut self, t: Transform) -> Self {
1794        self.transform = Some(t);
1795        self
1796    }
1797    pub fn grid(mut self, columns: usize, row_gap: Dp, column_gap: Dp) -> Self {
1798        self.grid = Some(GridConfig {
1799            columns,
1800            row_gap,
1801            column_gap,
1802        });
1803        self
1804    }
1805    pub fn grid_span(mut self, col_span: u16, row_span: u16) -> Self {
1806        self.grid_col_span = Some(col_span);
1807        self.grid_row_span = Some(row_span);
1808        self
1809    }
1810    /// Take this node out of flow: siblings lay out as if it were absent
1811    /// and this node is positioned by [`offset`](Self::offset) instead.
1812    ///
1813    /// Load-bearing for hand-placed overlays: without it taffy ignores the
1814    /// offsets and every absolutely-placed sibling stacks at the same
1815    /// in-flow spot. Pair with `.size(...)` + `.offset(Some(x), Some(y),
1816    /// None, None)` to pin a box at an exact point (e.g. world-anchored
1817    /// actor surfaces over a viewport).
1818    pub fn absolute(mut self) -> Self {
1819        self.position_type = Some(PositionType::Absolute);
1820        self
1821    }
1822    /// In-flow offsets are ignored unless paired with
1823    /// [`absolute`](Self::absolute): taffy only applies these to
1824    /// out-of-flow nodes. Each side is independent; `None` leaves that
1825    /// edge to default flow placement.
1826    pub fn offset(
1827        mut self,
1828        left: Option<Dp>,
1829        top: Option<Dp>,
1830        right: Option<Dp>,
1831        bottom: Option<Dp>,
1832    ) -> Self {
1833        self.offset_left = left;
1834        self.offset_top = top;
1835        self.offset_right = right;
1836        self.offset_bottom = bottom;
1837        self
1838    }
1839    pub fn offset_left(mut self, v: Dp) -> Self {
1840        self.offset_left = Some(v);
1841        self
1842    }
1843    pub fn offset_right(mut self, v: Dp) -> Self {
1844        self.offset_right = Some(v);
1845        self
1846    }
1847    pub fn offset_top(mut self, v: Dp) -> Self {
1848        self.offset_top = Some(v);
1849        self
1850    }
1851    pub fn offset_bottom(mut self, v: Dp) -> Self {
1852        self.offset_bottom = Some(v);
1853        self
1854    }
1855    pub fn margin(mut self, v: Dp) -> Self {
1856        self.margin_left = Some(v);
1857        self.margin_right = Some(v);
1858        self.margin_top = Some(v);
1859        self.margin_bottom = Some(v);
1860        self
1861    }
1862
1863    pub fn margin_horizontal(mut self, v: Dp) -> Self {
1864        self.margin_left = Some(v);
1865        self.margin_right = Some(v);
1866        self
1867    }
1868
1869    pub fn margin_vertical(mut self, v: Dp) -> Self {
1870        self.margin_top = Some(v);
1871        self.margin_bottom = Some(v);
1872        self
1873    }
1874    pub fn aspect_ratio(mut self, ratio: f32) -> Self {
1875        self.aspect_ratio = Some(ratio);
1876        self
1877    }
1878    /// `fit-content(limit)` width: shrink-wrap content, clamped to `limit` [`Dp`].
1879    pub fn fit_content_width(mut self, limit: Dp) -> Self {
1880        self.fit_content_width = Some(Dp(limit.0.max(0.0)));
1881        self
1882    }
1883    /// `fit-content(limit)` height: shrink-wrap content, clamped to `limit` [`Dp`].
1884    pub fn fit_content_height(mut self, limit: Dp) -> Self {
1885        self.fit_content_height = Some(Dp(limit.0.max(0.0)));
1886        self
1887    }
1888    /// CSS containment. See [`contain`](Self::contain).
1889    pub fn contain(mut self, c: Contain) -> Self {
1890        self.contain = Some(c);
1891        self
1892    }
1893    /// `contain: layout` - independent formatting context, no baseline.
1894    pub fn contain_layout(mut self) -> Self {
1895        self.contain = Some(Contain::LAYOUT);
1896        self
1897    }
1898    /// `contain: paint` - independent formatting context.
1899    pub fn contain_paint(mut self) -> Self {
1900        self.contain = Some(Contain::PAINT);
1901        self
1902    }
1903    /// `contain: content` (`layout` + `paint`).
1904    pub fn contain_content(mut self) -> Self {
1905        self.contain = Some(Contain::CONTENT);
1906        self
1907    }
1908    /// Size this node's width to its min or max intrinsic content size.
1909    pub fn intrinsic_width(mut self, mode: IntrinsicSize) -> Self {
1910        self.intrinsic_width = Some(mode);
1911        self
1912    }
1913    /// Size this node's height to its min or max intrinsic content size.
1914    pub fn intrinsic_height(mut self, mode: IntrinsicSize) -> Self {
1915        self.intrinsic_height = Some(mode);
1916        self
1917    }
1918    pub fn painter(mut self, f: impl Fn(&mut crate::Scene, crate::Rect, f32) + 'static) -> Self {
1919        self.painter = Some(Rc::new(f));
1920        self
1921    }
1922    /// Attach a custom GPU callback (like `egui::PaintCallback`). The payload
1923    /// is type-erased (`Arc<dyn Any>`) so the core crate stays backend-agnostic.
1924    /// Prefer `repose_render_wgpu::Callback::new` + `repose_ui::Embedded` or
1925    /// `repose_render_wgpu::Callback::embedded_view` over calling this directly.
1926    /// `rect` for the callback comes from layout (`SceneNode::Callback.rect`).
1927    pub fn paint_callback(mut self, payload: crate::PaintCallbackPayload) -> Self {
1928        self.paint_callback = Some(payload);
1929        self
1930    }
1931    pub fn scale(self, s: f32) -> Self {
1932        self.scale2(s, s)
1933    }
1934    pub fn scale2(mut self, sx: f32, sy: f32) -> Self {
1935        let mut t = self.transform.unwrap_or_else(Transform::identity);
1936        t.scale_x *= sx;
1937        t.scale_y *= sy;
1938        self.transform = Some(t);
1939        self
1940    }
1941    pub fn translate(mut self, x: f32, y: f32) -> Self {
1942        let t = self.transform.unwrap_or_else(Transform::identity);
1943        self.transform = Some(t.combine(&Transform::translate(x, y)));
1944        self
1945    }
1946    pub fn translate_vec2(self, v: Vec2) -> Self {
1947        self.translate(v.x, v.y)
1948    }
1949    pub fn rotate(mut self, radians: f32) -> Self {
1950        let mut t = self.transform.unwrap_or_else(Transform::identity);
1951        t.rotate += radians;
1952        self.transform = Some(t);
1953        self
1954    }
1955    pub fn transform_origin(mut self, x: f32, y: f32) -> Self {
1956        let mut t = self.transform.unwrap_or_else(Transform::identity);
1957        t.origin_x = x;
1958        t.origin_y = y;
1959        self.transform = Some(t);
1960        self
1961    }
1962    pub fn weight(mut self, w: f32) -> Self {
1963        let w = w.max(0.0);
1964        self.flex_grow = Some(w);
1965        self.flex_shrink = Some(1.0);
1966        self.flex_basis = Some(Dp::ZERO);
1967        self.flex_basis_content = false;
1968        // Zero the automatic min size so the item may shrink below its content
1969        // width (the flexbox `min-width: auto` trap). Callers that need a floor
1970        // must set it *after* `weight`.
1971        self.min_width = Some(Dp::ZERO);
1972        self.min_height = Some(Dp::ZERO);
1973        self
1974    }
1975    /// Marks this view as a repaint boundary candidate.
1976    ///
1977    /// The engine may cache its painted output.
1978    pub fn repaint_boundary(mut self) -> Self {
1979        self.repaint_boundary = true;
1980        self
1981    }
1982    pub fn on_action(mut self, f: impl Fn(crate::shortcuts::Action) -> bool + 'static) -> Self {
1983        self.on_action = Some(Rc::new(f));
1984        self
1985    }
1986
1987    /// Mark this node as a drag source. Return `Some(payload)` to start dragging.
1988    pub fn on_drag_start(
1989        mut self,
1990        f: impl Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload> + 'static,
1991    ) -> Self {
1992        self.on_drag_start = Some(Rc::new(f));
1993        self
1994    }
1995
1996    /// Called when a drag ends (drop accepted or canceled/ignored).
1997    pub fn on_drag_end(mut self, f: impl Fn(crate::dnd::DragEnd) + 'static) -> Self {
1998        self.on_drag_end = Some(Rc::new(f));
1999        self
2000    }
2001
2002    /// Called when a drag first enters this target.
2003    pub fn on_drag_enter(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
2004        self.on_drag_enter = Some(Rc::new(f));
2005        self
2006    }
2007
2008    /// Called on every pointer move while a drag is over this target.
2009    pub fn on_drag_over(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
2010        self.on_drag_over = Some(Rc::new(f));
2011        self
2012    }
2013
2014    /// Called when a drag leaves this target.
2015    pub fn on_drag_leave(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
2016        self.on_drag_leave = Some(Rc::new(f));
2017        self
2018    }
2019
2020    /// Called on pointer release while a drag is over this target.
2021    /// Return `true` to accept the drop.
2022    pub fn on_drop(mut self, f: impl Fn(crate::dnd::DropEvent) -> bool + 'static) -> Self {
2023        self.on_drop = Some(Rc::new(f));
2024        self
2025    }
2026
2027    /// Custom drag preview decoration (Compose `drawDragDecoration`).
2028    ///
2029    /// Called every frame while this node is the active drag source.
2030    /// Coordinates are screen px; see [`crate::dnd::DragPreviewCtx`].
2031    pub fn draw_drag_decoration(
2032        mut self,
2033        f: impl Fn(&mut crate::Scene, &crate::dnd::DragPreviewCtx) + 'static,
2034    ) -> Self {
2035        self.drag_preview = Some(Rc::new(f));
2036        self
2037    }
2038
2039    /// Same as [`Self::draw_drag_decoration`] but takes an existing [`crate::dnd::DragPreview`] Rc.
2040    pub fn draw_drag_decoration_rc(mut self, preview: crate::dnd::DragPreview) -> Self {
2041        self.drag_preview = Some(preview);
2042        self
2043    }
2044
2045    /// Convenience: floating label chip as the drag preview.
2046    pub fn drag_preview_label(self, label: impl Into<String>, accent: crate::Color) -> Self {
2047        self.draw_drag_decoration_rc(crate::dnd::drag_preview_label(label, accent))
2048    }
2049
2050    /// Convenience: elevated chip preview.
2051    pub fn drag_preview_chip(self, label: impl Into<String>, accent: crate::Color) -> Self {
2052        self.draw_drag_decoration_rc(crate::dnd::drag_preview_chip(label, accent))
2053    }
2054
2055    /// Set the cursor icon hint for desktop/web runners.
2056    pub fn cursor(mut self, c: crate::CursorIcon) -> Self {
2057        self.cursor = Some(c);
2058        self
2059    }
2060
2061    /// Animate size changes smoothly when the content's natural size changes.
2062    /// Uses the provided `AnimationSpec` for the transition.
2063    /// The content will be clipped to the animated size during transitions.
2064    pub fn animate_content_size(mut self, spec: AnimationSpec) -> Self {
2065        self.animate_content_size = Some(spec);
2066        self
2067    }
2068
2069    /// Attach a `FocusRequester` to this view. The requester will be associated
2070    /// with the view's focusable element, allowing programmatic focus requests.
2071    pub fn focus_requester(mut self, fr: crate::runtime::FocusRequester) -> Self {
2072        self.focus_requester = Some(fr);
2073        self
2074    }
2075
2076    /// Make this composable a focus target (`.focusable(true)`).
2077    /// Corresponds to Compose's `Modifier.focusTarget()`.
2078    pub fn focus_target(mut self) -> Self {
2079        self.focusable = Some(true);
2080        self
2081    }
2082
2083    /// Register a callback that fires when this view gains or loses keyboard focus.
2084    /// The argument is `true` when the view receives focus, `false` when it loses it.
2085    pub fn on_focus_changed(mut self, f: impl Fn(bool) + 'static) -> Self {
2086        self.on_focus_changed = Some(Rc::new(f));
2087        self
2088    }
2089
2090    /// Called after layout when this element's position changes.
2091    /// The callback receives the element's rect with [`Dp`] magnitudes
2092    /// (device-independent pixels).
2093    /// Fires whenever the rect changes, including on the initial layout.
2094    pub fn on_globally_positioned(mut self, f: impl Fn(crate::Rect) + 'static) -> Self {
2095        self.on_globally_positioned = Some(Rc::new(f));
2096        self
2097    }
2098
2099    /// Called after layout when this element's size changes.
2100    /// Provides the new (width, height) with [`Dp`] magnitudes.
2101    pub fn on_size_changed(mut self, f: impl Fn(crate::Vec2) + 'static) -> Self {
2102        self.on_size_changed = Some(Rc::new(f));
2103        self
2104    }
2105
2106    /// Called when a key event is received while this element is focused.
2107    /// Return `true` to indicate the event was consumed and should not
2108    /// propagate further (e.g. to text input handling or shortcut dispatch).
2109    pub fn on_key_event(mut self, f: impl Fn(crate::input::KeyEvent) -> bool + 'static) -> Self {
2110        self.on_key_event = Some(Rc::new(f));
2111        self
2112    }
2113
2114    /// Called when an IME event is received while this element is focused.
2115    /// Return `true` to indicate the event was consumed.
2116    pub fn on_ime(mut self, f: impl Fn(crate::input::ImeEvent) -> bool + 'static) -> Self {
2117        self.on_ime = Some(Rc::new(f));
2118        self
2119    }
2120
2121    /// Preview variant of `on_key_event`. Called before `on_key_event`;
2122    /// if the preview handler returns `true`, the event is consumed
2123    /// and `on_key_event` is NOT called.
2124    pub fn on_preview_key_event(
2125        mut self,
2126        f: impl Fn(crate::input::KeyEvent) -> bool + 'static,
2127    ) -> Self {
2128        self.on_preview_key_event = Some(Rc::new(f));
2129        self
2130    }
2131
2132    /// Apply a gaussian blur to this element's rendered content.
2133    /// `radius` is the uniform blur radius in [`Dp`].
2134    /// Larger values produce a stronger blur.
2135    /// Uses `Rectangle` edge treatment (clip to bounds).
2136    ///
2137    /// Requires `graphics_layer` to be enabled (set automatically if not).
2138    pub fn blur(mut self, radius: Dp) -> Self {
2139        self.blur = Some(BlurStyle {
2140            radius_x: Dp(radius.0.max(0.0)),
2141            radius_y: Dp(radius.0.max(0.0)),
2142            edge_treatment: BlurredEdgeTreatment::Rectangle,
2143        });
2144        self
2145    }
2146
2147    /// Apply a gaussian blur with separate horizontal/vertical radii ([`Dp`]).
2148    /// `edge_treatment` controls how edge pixels are handled.
2149    ///
2150    /// Requires `graphics_layer` to be enabled (set automatically if not).
2151    pub fn blur_with_edge(
2152        mut self,
2153        radius_x: Dp,
2154        radius_y: Dp,
2155        edge_treatment: BlurredEdgeTreatment,
2156    ) -> Self {
2157        self.blur = Some(BlurStyle {
2158            radius_x: Dp(radius_x.0.max(0.0)),
2159            radius_y: Dp(radius_y.0.max(0.0)),
2160            edge_treatment,
2161        });
2162        self
2163    }
2164
2165    /// Override this element's measured size with a custom callback.
2166    /// The callback receives `LayoutConstraints` (min/max width/height in [`Dp`]),
2167    /// where `max_width`/`max_height` may be `Dp::INFINITY` if unbounded.
2168    /// Returns `(width, height)` for this element, in [`Dp`].
2169    ///
2170    /// Child placement is handled by the parent layout (same as Compose's
2171    /// `Modifier.size` family).
2172    pub fn layout(mut self, f: impl Fn(LayoutConstraints) -> (Dp, Dp) + 'static) -> Self {
2173        self.layout = Some(Rc::new(f));
2174        self
2175    }
2176
2177    /// Mark this Box as a text input field with the given configuration.
2178    pub fn text_input(mut self, config: TextInputConfig) -> Self {
2179        self.text_input = Some(config);
2180        self
2181    }
2182
2183    /// Attach a text selection to this `Text` node. The layout engine paints the
2184    /// highlight behind the glyphs, using this node's real text layout.
2185    pub fn text_selection(mut self, selection: crate::text::TextSelection) -> Self {
2186        self.text_selection = Some(selection);
2187        self
2188    }
2189
2190    /// Attach an indication (ripple/highlight) factory for visual feedback.
2191    /// The factory is paired with an `InteractionSource` (via `.interaction_source(...)`)
2192    /// to draw press/hover/focus visual feedback.
2193    pub fn indication(mut self, factory: Rc<dyn IndicationNodeFactory>) -> Self {
2194        self.indication = Some(factory);
2195        self
2196    }
2197}