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