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