Skip to main content

repose_core/
modifier.rs

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