Skip to main content

repose_core/
modifier.rs

1use std::cell::{Cell, RefCell};
2use std::rc::Rc;
3use std::sync::atomic::{AtomicU64, Ordering};
4
5use taffy::{AlignContent, AlignItems, AlignSelf, FlexDirection, FlexWrap, JustifyContent};
6
7use crate::animation::AnimationSpec;
8use crate::indication::IndicationNodeFactory;
9use crate::{Brush, Color, PointerEvent, Size, Transform, Vec2};
10
11/// State-driven colors for interactive components.
12/// The layout engine selects the appropriate color based on hover/press/disabled state
13/// and animates transitions between them.
14#[derive(Clone, Copy, Debug)]
15pub struct StateColors {
16    pub default: Color,
17    pub hovered: Color,
18    pub pressed: Color,
19    pub disabled: Color,
20}
21
22/// State-driven elevation for interactive components.
23#[derive(Clone, Copy, Debug)]
24pub struct StateElevation {
25    pub default: f32,
26    pub hovered: f32,
27    pub pressed: f32,
28    pub disabled: f32,
29}
30
31macro_rules! merge_opts {
32    ($dst:ident, $src:ident; $($f:ident),+ $(,)?) => {
33        $( $dst.$f = $src.$f.or($dst.$f); )+
34    };
35}
36macro_rules! merge_flags {
37    ($dst:ident, $src:ident; $($f:ident),+ $(,)?) => {
38        $( $dst.$f |= $src.$f; )+
39    };
40}
41
42macro_rules! impl_option_fields {
43    ($ty:ty, $fn:ident) => {
44        impl $ty {
45            $fn!(replace);
46        }
47    };
48    ($ty:ident) => {
49        impl $ty {
50            /// Chain another modifier's settings onto this one.
51            /// Useful for creating reusable modifier templates.
52            pub fn then(mut self, other: Self) -> Self {
53                merge_opts!(self, other;
54                    key, size, width, height, required_size,
55                    padding, padding_values,
56                    min_width, min_height, max_width, max_height,
57                    required_min_width, required_max_width,
58                    required_min_height, required_max_height,
59                    default_min_width, default_min_height,
60                    fill_max, fill_max_w, fill_max_h,
61                    background, state_colors, state_elevation, border,
62                    flex_grow, flex_shrink, flex_basis, flex_wrap, flex_dir,
63                    gap, row_gap, column_gap,
64                    align_self, justify_content, align_items_container, align_content,
65                    clip_rounded, render_z_index,
66                    on_scroll,
67                    on_pointer_down, on_pointer_move, on_pointer_up,
68                    on_pointer_enter, on_pointer_leave,
69                    on_double_click, on_long_click,
70                    semantics, alpha, transform,
71                    grid, grid_col_span, grid_row_span,
72                    position_type,
73                    offset_left, offset_right, offset_top, offset_bottom,
74                    margin_left, margin_right, margin_top, margin_bottom,
75                    aspect_ratio, intrinsic_width, intrinsic_height,
76                    painter,
77                    on_drag_start, on_drag_end, on_drag_enter, on_drag_over, on_drag_leave, on_drop,
78                    on_action, cursor, animate_content_size, focus_requester, on_focus_changed,
79                    interaction_source, text_input,
80                );
81                        merge_flags!(self, other;
82                    hit_passthrough, input_blocker, repaint_boundary, click, disabled,
83                    propagate_min, focus_group,
84                );
85
86                if let Some(f) = other.focusable {
87                    self.focusable = Some(f);
88                }
89                if other.indication.is_some() {
90                    self.indication = other.indication;
91                }
92                if other.z_index != 0.0 {
93                    self.z_index = other.z_index;
94                }
95                self
96            }
97        }
98    };
99}
100
101#[derive(Clone, Debug)]
102pub struct Border {
103    pub width: f32,
104    pub color: Color,
105    pub radius: [f32; 4],
106}
107
108#[derive(Clone, Copy, Debug, Default)]
109pub struct PaddingValues {
110    pub left: f32,
111    pub right: f32,
112    pub top: f32,
113    pub bottom: f32,
114}
115
116#[derive(Clone, Debug)]
117pub struct GridConfig {
118    pub columns: usize,
119    pub row_gap: f32,
120    pub column_gap: f32,
121}
122
123/// Drop-shadow parameters applied to a graphics layer.
124///
125/// `blur_radius` is the Gaussian blur radius in dp (1.0 = subtle, 8.0 = soft,
126/// 16.0 = very diffuse). `offset_y` is the vertical offset of the shadow in dp
127/// (positive = below the layer). `color` is the shadow color (premultiplied
128/// alpha controls shadow darkness).
129#[derive(Clone, Copy, Debug)]
130pub struct ShadowSpec {
131    pub blur_radius: f32,
132    pub offset_y: f32,
133    pub color: Color,
134}
135
136#[derive(Clone, Copy, Debug)]
137#[non_exhaustive]
138pub enum PositionType {
139    Relative,
140    Absolute,
141}
142
143/// Configuration for a text input field.
144#[derive(Clone)]
145pub struct TextInputConfig {
146    pub hint: String,
147    pub multiline: bool,
148    pub on_change: Option<Rc<dyn Fn(String)>>,
149    pub on_submit: Option<Rc<dyn Fn(String)>>,
150    pub focus_tracker: Option<Rc<Cell<bool>>>,
151    pub value: String,
152    pub visual_transformation: Option<Rc<dyn crate::text::VisualTransformation>>,
153    pub keyboard_type: Option<crate::text::KeyboardType>,
154    pub ime_action: Option<crate::text::ImeAction>,
155    /// When false, the text field is not editable, not focusable, and input is not selectable.
156    pub enabled: bool,
157    /// When true, the text field can be focused and text can be selected/copied, but not modified.
158    pub read_only: bool,
159    /// Maximum visible lines. Only effective when `multiline` is true.
160    pub max_lines: Option<usize>,
161    /// Minimum visible lines. Only effective when `multiline` is true.
162    pub min_lines: Option<usize>,
163    /// Override the cursor color. When None, uses the theme's `on_surface`.
164    pub cursor_color: Option<Color>,
165    /// Callback invoked after each text layout computation, providing layout details
166    /// such as line count and content size.
167    pub on_text_layout: Option<Rc<dyn Fn(&crate::text::TextLayoutResult)>>,
168}
169
170impl std::fmt::Debug for TextInputConfig {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        let mut s = f.debug_struct("TextInputConfig");
173        s.field("hint", &self.hint);
174        s.field("multiline", &self.multiline);
175        if self.on_change.is_some() {
176            s.field("on_change", &"…");
177        }
178        if self.on_submit.is_some() {
179            s.field("on_submit", &"…");
180        }
181        if self.focus_tracker.is_some() {
182            s.field("focus_tracker", &"…");
183        }
184        s.field("value", &self.value);
185        if self.visual_transformation.is_some() {
186            s.field("visual_transformation", &"…");
187        }
188        s.field("keyboard_type", &self.keyboard_type);
189        s.field("ime_action", &self.ime_action);
190        s.field("enabled", &self.enabled);
191        s.field("read_only", &self.read_only);
192        s.field("max_lines", &self.max_lines);
193        s.field("min_lines", &self.min_lines);
194        s.field("cursor_color", &self.cursor_color);
195        if self.on_text_layout.is_some() {
196            s.field("on_text_layout", &"…");
197        }
198        s.finish()
199    }
200}
201
202/// Intrinsic sizing mode for [`Modifier::intrinsic_width`] and [`Modifier::intrinsic_height`].
203/// When set, the node sizes itself to the intrinsic content size in that dimension.
204#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
205pub enum IntrinsicSize {
206    Min,
207    Max,
208}
209
210static PRESS_COUNTER: AtomicU64 = AtomicU64::new(1);
211
212/// A press identifier for linking Press ↔ Release/Cancel pairs.
213pub type PressId = u64;
214
215/// An interaction event that can be emitted by a [`MutableInteractionSource`].
216///
217/// Compose-like per-interaction-type hierarchy:
218/// - `PressInteraction.Press(position)` / `Release(press)` / `Cancel(press)`
219/// - `HoverInteraction.Enter` / `Exit`
220/// - `FocusInteraction.Focus` / `Unfocus`
221/// - `DragInteraction.Start` / `Stop` / `Cancel`
222#[derive(Clone, Copy, Debug, PartialEq)]
223pub enum Interaction {
224    /// A press started at the given position (in local coords).
225    /// Carries a unique `PressId` so Release/Cancel can identify which press to end.
226    Press(PressId, Vec2),
227    /// The press with the given `PressId` was released.
228    Release(PressId),
229    /// The press with the given `PressId` was cancelled
230    /// (e.g. gesture disambiguation, pointer leave during press).
231    Cancel(PressId),
232    HoverEnter,
233    HoverLeave,
234    Focus,
235    Unfocus,
236    DragStart,
237    DragStop,
238    DragCancel,
239}
240
241impl Interaction {
242    /// Create a new `Press` with a fresh unique ID and the given position.
243    #[inline]
244    pub fn new_press(position: Vec2) -> Self {
245        Interaction::Press(PRESS_COUNTER.fetch_add(1, Ordering::Relaxed), position)
246    }
247}
248
249/// Read-only handle to a shared interaction state.
250///
251/// Use [`MutableInteractionSource::source`] to obtain a read handle, or
252/// [`MutableInteractionSource::new`] + `.source()` to create a new source pair.
253///
254/// Multiple clones share the same underlying state.
255#[derive(Clone)]
256pub struct InteractionSource {
257    pub(crate) state: Rc<RefCell<InteractionState>>,
258}
259
260impl InteractionSource {
261    pub fn collect_is_pressed(&self) -> bool {
262        self.state.borrow().pressed > 0
263    }
264    pub fn collect_is_hovered(&self) -> bool {
265        self.state.borrow().hovered
266    }
267    pub fn collect_is_focused(&self) -> bool {
268        self.state.borrow().focused
269    }
270    pub fn collect_is_dragged(&self) -> bool {
271        self.state.borrow().dragged > 0
272    }
273    pub fn collect_last_press_position(&self) -> Option<Vec2> {
274        self.state.borrow().last_press_position
275    }
276    pub fn collect_last_press_id(&self) -> Option<PressId> {
277        self.state.borrow().last_press_id
278    }
279    /// Stable identity: the pointer of the shared state Rc.
280    pub fn stable_id(&self) -> *const () {
281        Rc::as_ptr(&self.state) as *const ()
282    }
283    /// Get a mutable handle to the same underlying state.
284    /// Both handles share the same `Rc<RefCell<..>>`, so mutations via
285    /// the returned `MutableInteractionSource` are reflected here.
286    pub fn to_mutable(&self) -> MutableInteractionSource {
287        MutableInteractionSource {
288            state: self.state.clone(),
289        }
290    }
291}
292
293/// Mutable handle to a shared interaction state.
294///
295/// Create one via [`MutableInteractionSource::new`], then pass the read-only
296/// [`InteractionSource`] to modifiers via `.interaction_source(&source)`.
297///
298/// ```ignore
299/// let src = remember(MutableInteractionSource::new);
300/// m = m.clickable().interaction_source(&src).state_colors(...);
301/// ```
302#[derive(Clone)]
303pub struct MutableInteractionSource {
304    pub(crate) state: Rc<RefCell<InteractionState>>,
305}
306
307impl MutableInteractionSource {
308    pub fn new() -> Self {
309        Self {
310            state: Rc::new(RefCell::new(InteractionState::default())),
311        }
312    }
313
314    /// Emit an interaction event, updating the shared state.
315    pub fn emit(&self, interaction: Interaction) {
316        let mut s = self.state.borrow_mut();
317        match interaction {
318            Interaction::Press(id, pos) => {
319                s.pressed = s.pressed.saturating_add(1);
320                s.last_press_id = Some(id);
321                s.last_press_position = Some(pos);
322            }
323            Interaction::Release(_) | Interaction::Cancel(_) => {
324                s.pressed = s.pressed.saturating_sub(1);
325            }
326            Interaction::HoverEnter => s.hovered = true,
327            Interaction::HoverLeave => s.hovered = false,
328            Interaction::Focus => s.focused = true,
329            Interaction::Unfocus => s.focused = false,
330            Interaction::DragStart => s.dragged = s.dragged.saturating_add(1),
331            Interaction::DragStop | Interaction::DragCancel => {
332                s.dragged = s.dragged.saturating_sub(1);
333            }
334        }
335    }
336
337    /// Get a read-only handle to the shared state.
338    pub fn source(&self) -> InteractionSource {
339        InteractionSource {
340            state: self.state.clone(),
341        }
342    }
343}
344
345impl Default for MutableInteractionSource {
346    fn default() -> Self {
347        Self::new()
348    }
349}
350
351#[derive(Clone, Default)]
352pub(crate) struct InteractionState {
353    pressed: u32,
354    hovered: bool,
355    focused: bool,
356    dragged: u32,
357    /// Most recent press position (used by ripple for origin).
358    pub(crate) last_press_position: Option<Vec2>,
359    /// Most recent press ID.
360    pub(crate) last_press_id: Option<PressId>,
361}
362
363#[derive(Clone, Default)]
364pub struct Modifier {
365    /// Optional stable identity key for this view node.
366    ///
367    /// If set, `layout_and_paint` will prefer this over child index when assigning stable ViewIds.
368    /// This is the “escape hatch” for dynamic lists / conditional UI where index-based identity
369    /// would otherwise shift.
370    pub key: Option<u64>,
371
372    pub size: Option<Size>,
373    pub width: Option<f32>,
374    pub height: Option<f32>,
375    pub required_size: Option<Size>,
376    pub fill_max: Option<f32>,
377    pub fill_max_w: Option<f32>,
378    pub fill_max_h: Option<f32>,
379    pub padding: Option<f32>,
380    pub padding_values: Option<PaddingValues>,
381    pub min_width: Option<f32>,
382    pub min_height: Option<f32>,
383    pub max_width: Option<f32>,
384    pub max_height: Option<f32>,
385    /// Like [`required_size`] but only for min width. Overrides parent min constraints.
386    pub required_min_width: Option<f32>,
387    /// Like [`required_size`] but only for max width. Overrides parent max constraints.
388    pub required_max_width: Option<f32>,
389    /// Like [`required_size`] but only for min height. Overrides parent min constraints.
390    pub required_min_height: Option<f32>,
391    /// Like [`required_size`] but only for max height. Overrides parent max constraints.
392    pub required_max_height: Option<f32>,
393    /// Minimum size that only applies when the incoming constraint is 0 (unconstrained).
394    /// Use [`min_width`] for an unconditional minimum.
395    pub default_min_width: Option<f32>,
396    pub default_min_height: Option<f32>,
397    pub background: Option<Brush>,
398    pub state_colors: Option<StateColors>,
399    pub state_elevation: Option<StateElevation>,
400
401    pub border: Option<Border>,
402    pub flex_grow: Option<f32>,
403    pub flex_shrink: Option<f32>,
404    pub flex_basis: Option<f32>,
405    pub flex_wrap: Option<FlexWrap>,
406    pub flex_dir: Option<FlexDirection>,
407    pub gap: Option<f32>,
408    pub row_gap: Option<f32>,
409    pub column_gap: Option<f32>,
410    pub align_self: Option<AlignSelf>,
411    pub justify_content: Option<JustifyContent>,
412    pub align_items_container: Option<AlignItems>,
413    pub align_content: Option<AlignContent>,
414    pub clip_rounded: Option<[f32; 4]>,
415    /// Z-index for hit-testing order (higher = receives events first).
416    pub z_index: f32,
417    /// Z-index for render order (higher = painted on top). If None, uses tree order.
418    pub render_z_index: Option<f32>,
419    /// If true, this view does not create hit regions.
420    pub hit_passthrough: bool,
421    /// If true, this view blocks pointer/touch input for hits below it.
422    pub input_blocker: bool,
423    pub repaint_boundary: bool,
424    pub click: bool,
425    /// When true, the component ignores pointer events and appears disabled.
426    pub disabled: bool,
427    /// When Some(true), the component can receive keyboard focus regardless of interactivity.
428    /// When Some(false), the component cannot receive focus even if interactive.
429    /// When None, focusability is determined implicitly by interactivity (click/pointer/dnd handlers).
430    pub focusable: Option<bool>,
431    /// When true, the Box passes its min constraints to children instead of removing them.
432    pub propagate_min: bool,
433    /// When true, this node and its children form a focus group: focus cycles within
434    /// the group before moving outside it.
435    pub focus_group: bool,
436    pub on_scroll: Option<Rc<dyn Fn(Vec2) -> Vec2>>,
437    pub on_pointer_down: Option<Rc<dyn Fn(PointerEvent)>>,
438    pub on_pointer_move: Option<Rc<dyn Fn(PointerEvent)>>,
439    pub on_pointer_up: Option<Rc<dyn Fn(PointerEvent)>>,
440    pub on_pointer_cancel: Option<Rc<dyn Fn(PointerEvent)>>,
441    pub on_pointer_enter: Option<Rc<dyn Fn(PointerEvent)>>,
442    pub on_pointer_leave: Option<Rc<dyn Fn(PointerEvent)>>,
443    /// Called when the element is double-clicked/tapped.
444    pub on_double_click: Option<Rc<dyn Fn()>>,
445    /// Called when the element is long-pressed.
446    pub on_long_click: Option<Rc<dyn Fn()>>,
447    pub semantics: Option<crate::Semantics>,
448    pub alpha: Option<f32>,
449    pub graphics_layer: Option<f32>,
450    pub shadow: Option<ShadowSpec>,
451    pub transform: Option<Transform>,
452    pub grid: Option<GridConfig>,
453    pub grid_col_span: Option<u16>,
454    pub grid_row_span: Option<u16>,
455    pub position_type: Option<PositionType>,
456    pub offset_left: Option<f32>,
457    pub offset_right: Option<f32>,
458    pub offset_top: Option<f32>,
459    pub offset_bottom: Option<f32>,
460
461    pub margin_left: Option<f32>,
462    pub margin_right: Option<f32>,
463    pub margin_top: Option<f32>,
464    pub margin_bottom: Option<f32>,
465    pub aspect_ratio: Option<f32>,
466    /// Size this node's width to its min or max intrinsic content size.
467    pub intrinsic_width: Option<IntrinsicSize>,
468    /// Size this node's height to its min or max intrinsic content size.
469    pub intrinsic_height: Option<IntrinsicSize>,
470    pub painter: Option<Rc<dyn Fn(&mut crate::Scene, crate::Rect, f32)>>,
471
472    // Drag-drop (internal)
473    pub on_drag_start: Option<Rc<dyn Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload>>>,
474    pub on_drag_end: Option<Rc<dyn Fn(crate::dnd::DragEnd)>>,
475    pub on_drag_enter: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
476    pub on_drag_over: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
477    pub on_drag_leave: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
478    pub on_drop: Option<Rc<dyn Fn(crate::dnd::DropEvent) -> bool>>,
479
480    pub on_action: Option<Rc<dyn Fn(crate::shortcuts::Action) -> bool>>,
481
482    /// Cursor icon hint for desktop/web runners.
483    pub cursor: Option<crate::CursorIcon>,
484
485    /// If set, the size of this node will smoothly animate to its target size
486    /// whenever content size changes. Uses the provided animation spec.
487    pub animate_content_size: Option<AnimationSpec>,
488
489    /// A `FocusRequester` handle that will be associated with this view.
490    /// When the requester's `request_focus()` is called, keyboard focus will
491    /// move to this view.
492    pub focus_requester: Option<crate::runtime::FocusRequester>,
493
494    /// Called when this view gains or loses focus. The boolean parameter is
495    /// `true` when focused, `false` when unfocused.
496    pub on_focus_changed: Option<Rc<dyn Fn(bool)>>,
497
498    /// If set, this view reads its interaction state (hover/press) from this source
499    /// in addition to the implicit view-ID-based matching. The source state is OR'd
500    /// with the implicit state, enabling programmatic override of hover/press visuals.
501    ///
502    /// When set, the layout engine also auto-wires the source to emit PointerDown/Up
503    /// and HoverEnter/Leave events into the source via the hit region's callbacks.
504    pub interaction_source: Option<InteractionSource>,
505
506    /// Text input configuration. When set, this box acts as a text input field.
507    pub text_input: Option<TextInputConfig>,
508
509    /// Indication (ripple/overlay) factory for visual feedback on interaction.
510    pub indication: Option<Rc<dyn IndicationNodeFactory>>,
511}
512
513impl std::fmt::Debug for Modifier {
514    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
515        let mut s = f.debug_struct("Modifier");
516
517        macro_rules! opt_val {
518            ($($name:ident),+ $(,)?) => {
519                $( if self.$name.is_some() { s.field(stringify!($name), &self.$name); } )+
520            };
521        }
522        if self.indication.is_some() {
523            s.field("indication", &"…");
524        }
525
526        opt_val!(
527            key,
528            size,
529            width,
530            height,
531            required_size,
532            padding,
533            padding_values,
534            min_width,
535            min_height,
536            max_width,
537            max_height,
538            required_min_width,
539            required_max_width,
540            required_min_height,
541            required_max_height,
542            default_min_width,
543            default_min_height,
544            fill_max,
545            fill_max_w,
546            fill_max_h,
547            background,
548            state_colors,
549            state_elevation,
550            border,
551            flex_grow,
552            flex_shrink,
553            flex_basis,
554            flex_wrap,
555            flex_dir,
556            gap,
557            row_gap,
558            column_gap,
559            align_self,
560            justify_content,
561            align_items_container,
562            align_content,
563            clip_rounded,
564            render_z_index,
565            semantics,
566            alpha,
567            transform,
568            grid,
569            grid_col_span,
570            grid_row_span,
571            position_type,
572            offset_left,
573            offset_right,
574            offset_top,
575            offset_bottom,
576            margin_left,
577            margin_right,
578            margin_top,
579            margin_bottom,
580            aspect_ratio,
581            intrinsic_width,
582            intrinsic_height,
583            cursor,
584            animate_content_size,
585        );
586
587        macro_rules! opt_cb {
588            ($($name:ident),+ $(,)?) => {
589                $( if self.$name.is_some() { s.field(stringify!($name), &"…"); } )+
590            };
591        }
592        opt_cb!(
593            on_scroll,
594            on_pointer_down,
595            on_pointer_move,
596            on_pointer_up,
597            on_pointer_cancel,
598            on_pointer_enter,
599            on_pointer_leave,
600            on_double_click,
601            on_long_click,
602            painter,
603            on_drag_start,
604            on_drag_end,
605            on_drag_enter,
606            on_drag_over,
607            on_drag_leave,
608            on_drop,
609            on_action,
610            on_focus_changed,
611            interaction_source,
612            text_input,
613        );
614
615        macro_rules! flag {
616            ($($name:ident),+ $(,)?) => {
617                $( if self.$name { s.field(stringify!($name), &true); } )+
618            };
619        }
620        flag!(
621            hit_passthrough,
622            input_blocker,
623            repaint_boundary,
624            click,
625            disabled,
626            propagate_min,
627            focus_group,
628        );
629
630        if let Some(f) = self.focusable {
631            s.field("focusable", &f);
632        }
633        if self.z_index != 0.0 {
634            s.field("z_index", &self.z_index);
635        }
636
637        s.finish()
638    }
639}
640
641impl_option_fields!(Modifier);
642
643impl Modifier {
644    pub fn new() -> Self {
645        Self::default()
646    }
647
648    /// Attaches a stable identity key to this view node.
649    /// Use for dynamic lists / conditional UI where index-based identity can shift.
650    pub fn key(mut self, key: u64) -> Self {
651        self.key = Some(key);
652        self
653    }
654
655    pub fn size(mut self, w: f32, h: f32) -> Self {
656        self.size = Some(Size {
657            width: w,
658            height: h,
659        });
660        self
661    }
662    pub fn width(mut self, w: f32) -> Self {
663        self.width = Some(w);
664        self
665    }
666    pub fn height(mut self, h: f32) -> Self {
667        self.height = Some(h);
668        self
669    }
670    /// Set a fixed size that overrides parent constraints.
671    /// Unlike `size()` which is bounded by the parent's max constraints,
672    /// `required_size()` forces the node to this size regardless of the parent,
673    /// acting as both min and max.
674    pub fn required_size(mut self, w: f32, h: f32) -> Self {
675        self.required_size = Some(Size {
676            width: w,
677            height: h,
678        });
679        self
680    }
681    pub fn required_width_in(mut self, min: f32, max: f32) -> Self {
682        self.required_min_width = Some(min.max(0.0));
683        self.required_max_width = Some(max.max(0.0));
684        self
685    }
686    pub fn required_height_in(mut self, min: f32, max: f32) -> Self {
687        self.required_min_height = Some(min.max(0.0));
688        self.required_max_height = Some(max.max(0.0));
689        self
690    }
691    pub fn required_min_width(mut self, w: f32) -> Self {
692        self.required_min_width = Some(w.max(0.0));
693        self
694    }
695    pub fn required_max_width(mut self, w: f32) -> Self {
696        self.required_max_width = Some(w.max(0.0));
697        self
698    }
699    pub fn required_min_height(mut self, h: f32) -> Self {
700        self.required_min_height = Some(h.max(0.0));
701        self
702    }
703    pub fn required_max_height(mut self, h: f32) -> Self {
704        self.required_max_height = Some(h.max(0.0));
705        self
706    }
707    /// Minimum size that only takes effect when the incoming constraint is 0 (unconstrained).
708    pub fn default_min_size(mut self, w: f32, h: f32) -> Self {
709        self.default_min_width = Some(w.max(0.0));
710        self.default_min_height = Some(h.max(0.0));
711        self
712    }
713    /// Fill the available space in both dimensions.
714    /// By default fills 100% (fraction = 1.0). Pass a fraction to fill partially.
715    pub fn fill_max_size(mut self) -> Self {
716        self.fill_max = Some(1.0);
717        self
718    }
719    pub fn fill_max_size_frac(mut self, fraction: f32) -> Self {
720        self.fill_max = Some(fraction.clamp(0.0, 1.0));
721        self
722    }
723    /// Fill the available width. By default fills 100%.
724    pub fn fill_max_width(mut self) -> Self {
725        self.fill_max_w = Some(1.0);
726        self
727    }
728    pub fn fill_max_width_frac(mut self, fraction: f32) -> Self {
729        self.fill_max_w = Some(fraction.clamp(0.0, 1.0));
730        self
731    }
732    /// Fill the available height. By default fills 100%.
733    pub fn fill_max_height(mut self) -> Self {
734        self.fill_max_h = Some(1.0);
735        self
736    }
737    pub fn fill_max_height_frac(mut self, fraction: f32) -> Self {
738        self.fill_max_h = Some(fraction.clamp(0.0, 1.0));
739        self
740    }
741    pub fn padding(mut self, v: f32) -> Self {
742        self.padding = Some(v);
743        self
744    }
745    pub fn padding_values(mut self, padding: PaddingValues) -> Self {
746        self.padding_values = Some(padding);
747        self
748    }
749    /// Add padding equal to the current IME (soft keyboard) bottom inset.
750    /// Combine with `system_bars_padding()` to handle both system bars and keyboard.
751    pub fn ime_padding(mut self) -> Self {
752        let insets = crate::locals::window_insets();
753        let scale = crate::locals::density().scale * crate::locals::ui_scale().0;
754        let mut p = self.padding_values.unwrap_or_default();
755        p.bottom += insets.ime_bottom / scale;
756        self.padding_values = Some(p);
757        self
758    }
759    /// Add padding equal to the current system bar insets (status bar top, nav bar bottom).
760    pub fn system_bars_padding(mut self) -> Self {
761        let insets = crate::locals::window_insets();
762        let scale = crate::locals::density().scale * crate::locals::ui_scale().0;
763        let mut p = self.padding_values.unwrap_or_default();
764        p.top += insets.top / scale;
765        p.bottom += insets.bottom / scale;
766        self.padding_values = Some(p);
767        self
768    }
769    /// Add status bar inset as top padding.
770    pub fn status_bars_padding(mut self) -> Self {
771        let insets = crate::locals::window_insets();
772        let scale = crate::locals::density().scale * crate::locals::ui_scale().0;
773        let mut p = self.padding_values.unwrap_or_default();
774        p.top += insets.top / scale;
775        self.padding_values = Some(p);
776        self
777    }
778    /// Add navigation bar inset as bottom padding.
779    pub fn navigation_bars_padding(mut self) -> Self {
780        let insets = crate::locals::window_insets();
781        let scale = crate::locals::density().scale * crate::locals::ui_scale().0;
782        let mut p = self.padding_values.unwrap_or_default();
783        p.bottom += insets.bottom / scale;
784        self.padding_values = Some(p);
785        self
786    }
787    pub fn min_size(mut self, w: f32, h: f32) -> Self {
788        self.min_width = Some(w);
789        self.min_height = Some(h);
790        self
791    }
792    pub fn max_size(mut self, w: f32, h: f32) -> Self {
793        self.max_width = Some(w);
794        self.max_height = Some(h);
795        self
796    }
797    pub fn min_width(mut self, w: f32) -> Self {
798        self.min_width = Some(w);
799        self
800    }
801    pub fn min_height(mut self, h: f32) -> Self {
802        self.min_height = Some(h);
803        self
804    }
805    pub fn max_width(mut self, w: f32) -> Self {
806        self.max_width = Some(w);
807        self
808    }
809    pub fn max_height(mut self, h: f32) -> Self {
810        self.max_height = Some(h);
811        self
812    }
813    /// Set a solid color background.
814    pub fn background(mut self, color: Color) -> Self {
815        self.background = Some(Brush::Solid(color));
816        self
817    }
818    /// Set a brush (solid, gradient, etc.) background.
819    pub fn background_brush(mut self, brush: Brush) -> Self {
820        self.background = Some(brush);
821        self
822    }
823    pub fn border(mut self, width: f32, color: Color, radius: f32) -> Self {
824        self.border = Some(Border {
825            width,
826            color,
827            radius: [radius; 4],
828        });
829        self
830    }
831    pub fn border_radii(mut self, width: f32, color: Color, radii: [f32; 4]) -> Self {
832        self.border = Some(Border {
833            width,
834            color,
835            radius: radii,
836        });
837        self
838    }
839    pub fn flex_grow(mut self, v: f32) -> Self {
840        self.flex_grow = Some(v);
841        self
842    }
843    pub fn flex_shrink(mut self, v: f32) -> Self {
844        self.flex_shrink = Some(v);
845        self
846    }
847    pub fn flex_basis(mut self, v: f32) -> Self {
848        self.flex_basis = Some(v);
849        self
850    }
851    pub fn flex_wrap(mut self, w: FlexWrap) -> Self {
852        self.flex_wrap = Some(w);
853        self
854    }
855    pub fn flex_dir(mut self, d: FlexDirection) -> Self {
856        self.flex_dir = Some(d);
857        self
858    }
859    pub fn gap(mut self, v: f32) -> Self {
860        let v = v.max(0.0);
861        self.gap = Some(v);
862        self.row_gap = Some(v);
863        self.column_gap = Some(v);
864        self
865    }
866    pub fn row_gap(mut self, v: f32) -> Self {
867        self.row_gap = Some(v.max(0.0));
868        self
869    }
870    pub fn column_gap(mut self, v: f32) -> Self {
871        self.column_gap = Some(v.max(0.0));
872        self
873    }
874    pub fn align_self(mut self, a: AlignSelf) -> Self {
875        self.align_self = Some(a);
876        self
877    }
878    pub fn align_self_center(mut self) -> Self {
879        self.align_self = Some(AlignSelf::Center);
880        self
881    }
882    pub fn justify_content(mut self, j: JustifyContent) -> Self {
883        self.justify_content = Some(j);
884        self
885    }
886    pub fn align_items(mut self, a: AlignItems) -> Self {
887        self.align_items_container = Some(a);
888        self
889    }
890    pub fn align_content(mut self, a: AlignContent) -> Self {
891        self.align_content = Some(a);
892        self
893    }
894    pub fn clip_rounded(mut self, radius: f32) -> Self {
895        self.clip_rounded = Some([radius; 4]);
896        self
897    }
898    pub fn clip_rounded_radii(mut self, radii: [f32; 4]) -> Self {
899        self.clip_rounded = Some(radii);
900        self
901    }
902    pub fn z_index(mut self, z: f32) -> Self {
903        self.z_index = z;
904        self
905    }
906
907    /// Sets the render z-index for this view. Higher values are painted on top.
908    /// Unlike `z_index` (which only affects hit-testing), this affects visual layering.
909    pub fn render_z_index(mut self, z: f32) -> Self {
910        self.render_z_index = Some(z);
911        self
912    }
913
914    /// Prevent pointer/touch from reaching lower layers.
915    pub fn input_blocker(mut self) -> Self {
916        self.input_blocker = true;
917        self
918    }
919
920    pub fn hit_passthrough(mut self) -> Self {
921        self.hit_passthrough = true;
922        self
923    }
924    pub fn clickable(mut self) -> Self {
925        self.click = true;
926        self
927    }
928    /// Make this element clickable and attach an [`InteractionSource`] for state tracking.
929    /// Combines `.clickable()` and `.interaction_source(&source)` in one call.
930    pub fn clickable_with_source(mut self, source: &MutableInteractionSource) -> Self {
931        self.click = true;
932        self.interaction_source = Some(source.source());
933        self
934    }
935    /// Set state-driven background colors for hover, press, disabled states.
936    /// The layout engine automatically selects and animates between these based on interaction.
937    pub fn state_colors(mut self, colors: StateColors) -> Self {
938        self.state_colors = Some(colors);
939        self
940    }
941    /// Set state-driven elevation values for hover, press, disabled states.
942    pub fn state_elevation(mut self, elev: StateElevation) -> Self {
943        self.state_elevation = Some(elev);
944        self
945    }
946    /// Mark this component as disabled - it won't respond to pointer events.
947    pub fn disabled(mut self) -> Self {
948        self.disabled = true;
949        self
950    }
951    /// Mark this component as enabled or disabled.
952    pub fn enabled(mut self, enabled: bool) -> Self {
953        self.disabled = !enabled;
954        self
955    }
956    /// Set explicit focusability for this component.
957    /// When `true`, the component can receive keyboard focus even without
958    /// explicit click/pointer/dnd handlers. When `false`, focus is suppressed
959    /// even for interactive components.
960    pub fn focusable(mut self, focusable: bool) -> Self {
961        self.focusable = Some(focusable);
962        self
963    }
964    /// Mark this node as a focus group: focus cycles within this group before
965    /// moving to siblings outside it.
966    pub fn focus_group(mut self) -> Self {
967        self.focus_group = true;
968        self
969    }
970    /// Attach an [`InteractionSource`] to this view. The source provides shared
971    /// interaction state (hover/press) that supplements the implicit view-ID-based
972    /// state. The layout engine auto-wires pointer events into the source so it
973    /// stays in sync with user interaction.
974    ///
975    /// Use this when you need programmatic control of interaction state (e.g.,
976    /// showing pressed state during an async operation) or to share interaction
977    /// state between components.
978    pub fn interaction_source(mut self, source: &MutableInteractionSource) -> Self {
979        self.interaction_source = Some(source.source());
980        self
981    }
982    /// Convenience: register hover enter/leave callbacks.
983    /// Shorthand for setting `on_pointer_enter` and `on_pointer_leave`.
984    pub fn hoverable(
985        mut self,
986        on_enter: impl Fn() + 'static,
987        on_leave: impl Fn() + 'static,
988    ) -> Self {
989        self.on_pointer_enter = Some(Rc::new(move |_| on_enter()));
990        self.on_pointer_leave = Some(Rc::new(move |_| on_leave()));
991        self
992    }
993    /// Attach an [`InteractionSource`] to track hover state without explicit callbacks.
994    /// The source automatically receives HoverEnter/Leave events from the layout engine's
995    /// auto-wiring, so you can use it with `collect_is_hovered()` for custom visuals.
996    pub fn hoverable_with_source(mut self, source: &MutableInteractionSource) -> Self {
997        self.interaction_source = Some(source.source());
998        self
999    }
1000    /// When true, Box passes min-width/min-height constraints to its children
1001    /// instead of allowing them to shrink below the parent's min constraints.
1002    pub fn propagate_min_constraints(mut self, propagate: bool) -> Self {
1003        self.propagate_min = propagate;
1004        self
1005    }
1006    pub fn on_scroll(mut self, f: impl Fn(Vec2) -> Vec2 + 'static) -> Self {
1007        self.on_scroll = Some(Rc::new(f));
1008        self
1009    }
1010    pub fn on_pointer_down(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1011        self.on_pointer_down = Some(Rc::new(f));
1012        self
1013    }
1014    pub fn on_pointer_move(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1015        self.on_pointer_move = Some(Rc::new(f));
1016        self
1017    }
1018    pub fn on_pointer_up(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1019        self.on_pointer_up = Some(Rc::new(f));
1020        self
1021    }
1022    pub fn on_pointer_cancel(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1023        self.on_pointer_cancel = Some(Rc::new(f));
1024        self
1025    }
1026    pub fn on_pointer_enter(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1027        self.on_pointer_enter = Some(Rc::new(f));
1028        self
1029    }
1030    pub fn on_pointer_leave(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
1031        self.on_pointer_leave = Some(Rc::new(f));
1032        self
1033    }
1034    pub fn on_double_click(mut self, f: impl Fn() + 'static) -> Self {
1035        self.on_double_click = Some(Rc::new(f));
1036        self
1037    }
1038    pub fn on_long_click(mut self, f: impl Fn() + 'static) -> Self {
1039        self.on_long_click = Some(Rc::new(f));
1040        self
1041    }
1042    pub fn semantics(mut self, s: crate::Semantics) -> Self {
1043        self.semantics = Some(s);
1044        self
1045    }
1046    pub fn alpha(mut self, a: f32) -> Self {
1047        self.alpha = Some(a);
1048        self
1049    }
1050    /// Render this subtree into an offscreen texture, then composite it
1051    /// back into the parent with the given group `alpha` (0.0..=1.0).
1052    /// Allows correct blending when children overlap inside the layer, and
1053    /// sets up the architecture for future layer effects (shadow, blur, clip).
1054    pub fn graphics_layer(mut self, alpha: f32) -> Self {
1055        self.graphics_layer = Some(alpha.clamp(0.0, 1.0));
1056        self
1057    }
1058    /// Drop shadow with the given `blur_radius` (dp) and vertical `offset_y` (dp).
1059    /// The shadow color defaults to black with alpha 64 (~25%). Combines with
1060    /// [`Modifier::graphics_layer`] to draw a shadow underneath the layer.
1061    pub fn shadow(mut self, blur_radius: f32, offset_y: f32) -> Self {
1062        self.shadow = Some(ShadowSpec {
1063            blur_radius: blur_radius.max(0.0),
1064            offset_y,
1065            color: Color(0, 0, 0, 64),
1066        });
1067        self
1068    }
1069    /// Drop shadow with a custom color. Alpha 0..=255.
1070    pub fn shadow_with_color(mut self, blur_radius: f32, offset_y: f32, color: Color) -> Self {
1071        self.shadow = Some(ShadowSpec {
1072            blur_radius: blur_radius.max(0.0),
1073            offset_y,
1074            color,
1075        });
1076        self
1077    }
1078    /// Material-style elevation. Auto-scales blur and offset by `level` (dp)
1079    /// and uses a default shadow color. Level 0 = no shadow; 4 = subtle;
1080    /// 16 = strong. Requires [`Modifier::graphics_layer`] to take effect.
1081    pub fn elevation(mut self, level: f32) -> Self {
1082        if level <= 0.0 {
1083            self.shadow = None;
1084            return self;
1085        }
1086        self.shadow = Some(ShadowSpec {
1087            blur_radius: level * 2.0,
1088            offset_y: level * 0.5,
1089            color: Color(0, 0, 0, (level * 8.0).clamp(8.0, 80.0) as u8),
1090        });
1091        self
1092    }
1093    pub fn transform(mut self, t: Transform) -> Self {
1094        self.transform = Some(t);
1095        self
1096    }
1097    pub fn grid(mut self, columns: usize, row_gap: f32, column_gap: f32) -> Self {
1098        self.grid = Some(GridConfig {
1099            columns,
1100            row_gap,
1101            column_gap,
1102        });
1103        self
1104    }
1105    pub fn grid_span(mut self, col_span: u16, row_span: u16) -> Self {
1106        self.grid_col_span = Some(col_span);
1107        self.grid_row_span = Some(row_span);
1108        self
1109    }
1110    pub fn absolute(mut self) -> Self {
1111        self.position_type = Some(PositionType::Absolute);
1112        self
1113    }
1114    pub fn offset(
1115        mut self,
1116        left: Option<f32>,
1117        top: Option<f32>,
1118        right: Option<f32>,
1119        bottom: Option<f32>,
1120    ) -> Self {
1121        self.offset_left = left;
1122        self.offset_top = top;
1123        self.offset_right = right;
1124        self.offset_bottom = bottom;
1125        self
1126    }
1127    pub fn offset_left(mut self, v: f32) -> Self {
1128        self.offset_left = Some(v);
1129        self
1130    }
1131    pub fn offset_right(mut self, v: f32) -> Self {
1132        self.offset_right = Some(v);
1133        self
1134    }
1135    pub fn offset_top(mut self, v: f32) -> Self {
1136        self.offset_top = Some(v);
1137        self
1138    }
1139    pub fn offset_bottom(mut self, v: f32) -> Self {
1140        self.offset_bottom = Some(v);
1141        self
1142    }
1143    pub fn margin(mut self, v: f32) -> Self {
1144        self.margin_left = Some(v);
1145        self.margin_right = Some(v);
1146        self.margin_top = Some(v);
1147        self.margin_bottom = Some(v);
1148        self
1149    }
1150
1151    pub fn margin_horizontal(mut self, v: f32) -> Self {
1152        self.margin_left = Some(v);
1153        self.margin_right = Some(v);
1154        self
1155    }
1156
1157    pub fn margin_vertical(mut self, v: f32) -> Self {
1158        self.margin_top = Some(v);
1159        self.margin_bottom = Some(v);
1160        self
1161    }
1162    pub fn aspect_ratio(mut self, ratio: f32) -> Self {
1163        self.aspect_ratio = Some(ratio);
1164        self
1165    }
1166    /// Size this node's width to its min or max intrinsic content size.
1167    pub fn intrinsic_width(mut self, mode: IntrinsicSize) -> Self {
1168        self.intrinsic_width = Some(mode);
1169        self
1170    }
1171    /// Size this node's height to its min or max intrinsic content size.
1172    pub fn intrinsic_height(mut self, mode: IntrinsicSize) -> Self {
1173        self.intrinsic_height = Some(mode);
1174        self
1175    }
1176    pub fn painter(mut self, f: impl Fn(&mut crate::Scene, crate::Rect, f32) + 'static) -> Self {
1177        self.painter = Some(Rc::new(f));
1178        self
1179    }
1180    pub fn scale(self, s: f32) -> Self {
1181        self.scale2(s, s)
1182    }
1183    pub fn scale2(mut self, sx: f32, sy: f32) -> Self {
1184        let mut t = self.transform.unwrap_or_else(Transform::identity);
1185        t.scale_x *= sx;
1186        t.scale_y *= sy;
1187        self.transform = Some(t);
1188        self
1189    }
1190    pub fn translate(mut self, x: f32, y: f32) -> Self {
1191        let t = self.transform.unwrap_or_else(Transform::identity);
1192        self.transform = Some(t.combine(&Transform::translate(x, y)));
1193        self
1194    }
1195    pub fn translate_vec2(self, v: Vec2) -> Self {
1196        self.translate(v.x, v.y)
1197    }
1198    pub fn rotate(mut self, radians: f32) -> Self {
1199        let mut t = self.transform.unwrap_or_else(Transform::identity);
1200        t.rotate += radians;
1201        self.transform = Some(t);
1202        self
1203    }
1204    pub fn weight(mut self, w: f32) -> Self {
1205        let w = w.max(0.0);
1206        self.flex_grow = Some(w);
1207        self.flex_shrink = Some(1.0);
1208        // dp units; 0 is fine.
1209        self.flex_basis = Some(0.0);
1210        self
1211    }
1212    /// Marks this view as a repaint boundary candidate.
1213    ///
1214    /// The engine may cache its painted output.
1215    pub fn repaint_boundary(mut self) -> Self {
1216        self.repaint_boundary = true;
1217        self
1218    }
1219    pub fn on_action(mut self, f: impl Fn(crate::shortcuts::Action) -> bool + 'static) -> Self {
1220        self.on_action = Some(Rc::new(f));
1221        self
1222    }
1223
1224    /// Mark this node as a drag source. Return `Some(payload)` to start dragging.
1225    pub fn on_drag_start(
1226        mut self,
1227        f: impl Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload> + 'static,
1228    ) -> Self {
1229        self.on_drag_start = Some(Rc::new(f));
1230        self
1231    }
1232
1233    /// Called when a drag ends (drop accepted or canceled/ignored).
1234    pub fn on_drag_end(mut self, f: impl Fn(crate::dnd::DragEnd) + 'static) -> Self {
1235        self.on_drag_end = Some(Rc::new(f));
1236        self
1237    }
1238
1239    /// Called when a drag first enters this target.
1240    pub fn on_drag_enter(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
1241        self.on_drag_enter = Some(Rc::new(f));
1242        self
1243    }
1244
1245    /// Called on every pointer move while a drag is over this target.
1246    pub fn on_drag_over(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
1247        self.on_drag_over = Some(Rc::new(f));
1248        self
1249    }
1250
1251    /// Called when a drag leaves this target.
1252    pub fn on_drag_leave(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
1253        self.on_drag_leave = Some(Rc::new(f));
1254        self
1255    }
1256
1257    /// Called on pointer release while a drag is over this target.
1258    /// Return `true` to accept the drop.
1259    pub fn on_drop(mut self, f: impl Fn(crate::dnd::DropEvent) -> bool + 'static) -> Self {
1260        self.on_drop = Some(Rc::new(f));
1261        self
1262    }
1263
1264    /// Set the cursor icon hint for desktop/web runners.
1265    pub fn cursor(mut self, c: crate::CursorIcon) -> Self {
1266        self.cursor = Some(c);
1267        self
1268    }
1269
1270    /// Animate size changes smoothly when the content's natural size changes.
1271    /// Uses the provided `AnimationSpec` for the transition.
1272    /// The content will be clipped to the animated size during transitions.
1273    pub fn animate_content_size(mut self, spec: AnimationSpec) -> Self {
1274        self.animate_content_size = Some(spec);
1275        self
1276    }
1277
1278    /// Attach a `FocusRequester` to this view. The requester will be associated
1279    /// with the view's focusable element, allowing programmatic focus requests.
1280    pub fn focus_requester(mut self, fr: crate::runtime::FocusRequester) -> Self {
1281        self.focus_requester = Some(fr);
1282        self
1283    }
1284
1285    /// Register a callback that fires when this view gains or loses keyboard focus.
1286    /// The argument is `true` when the view receives focus, `false` when it loses it.
1287    pub fn on_focus_changed(mut self, f: impl Fn(bool) + 'static) -> Self {
1288        self.on_focus_changed = Some(Rc::new(f));
1289        self
1290    }
1291
1292    /// Mark this Box as a text input field with the given configuration.
1293    pub fn text_input(mut self, config: TextInputConfig) -> Self {
1294        self.text_input = Some(config);
1295        self
1296    }
1297
1298    /// Attach an indication (ripple/highlight) factory for visual feedback.
1299    /// The factory is paired with an `InteractionSource` (via `.interaction_source(...)`)
1300    /// to draw press/hover/focus visual feedback.
1301    pub fn indication(mut self, factory: Rc<dyn IndicationNodeFactory>) -> Self {
1302        self.indication = Some(factory);
1303        self
1304    }
1305}