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