Skip to main content

repose_core/
modifier.rs

1use std::cell::Cell;
2use std::rc::Rc;
3
4use taffy::{AlignContent, AlignItems, AlignSelf, FlexDirection, FlexWrap, JustifyContent};
5
6use crate::animation::AnimationSpec;
7use crate::{Brush, Color, PointerEvent, Size, Transform, Vec2};
8
9/// State-driven colors for interactive components.
10/// The layout engine selects the appropriate color based on hover/press/disabled state
11/// and animates transitions between them.
12#[derive(Clone, Copy, Debug)]
13pub struct StateColors {
14    pub default: Color,
15    pub hovered: Color,
16    pub pressed: Color,
17    pub disabled: Color,
18}
19
20/// State-driven elevation for interactive components.
21#[derive(Clone, Copy, Debug)]
22pub struct StateElevation {
23    pub default: f32,
24    pub hovered: f32,
25    pub pressed: f32,
26    pub disabled: f32,
27}
28
29macro_rules! merge_opts {
30    ($dst:ident, $src:ident; $($f:ident),+ $(,)?) => {
31        $( $dst.$f = $src.$f.or($dst.$f); )+
32    };
33}
34macro_rules! merge_flags {
35    ($dst:ident, $src:ident; $($f:ident),+ $(,)?) => {
36        $( $dst.$f |= $src.$f; )+
37    };
38}
39
40macro_rules! impl_option_fields {
41    ($ty:ty, $fn:ident) => {
42        impl $ty {
43            $fn!(replace);
44        }
45    };
46    ($ty:ident) => {
47        impl $ty {
48            /// Chain another modifier's settings onto this one.
49            /// Useful for creating reusable modifier templates.
50            pub fn then(mut self, other: Self) -> Self {
51                merge_opts!(self, other;
52                    key, size, width, height,
53                    padding, padding_values,
54                    min_width, min_height, max_width, max_height,
55                    background, state_colors, state_elevation, border,
56                    flex_grow, flex_shrink, flex_basis, flex_wrap, flex_dir,
57                    gap, row_gap, column_gap,
58                    align_self, justify_content, align_items_container, align_content,
59                    clip_rounded, render_z_index,
60                    on_scroll,
61                    on_pointer_down, on_pointer_move, on_pointer_up,
62                    on_pointer_enter, on_pointer_leave,
63                    semantics, alpha, transform,
64                    grid, grid_col_span, grid_row_span,
65                    position_type,
66                    offset_left, offset_right, offset_top, offset_bottom,
67                    margin_left, margin_right, margin_top, margin_bottom,
68                    aspect_ratio, painter,
69                    on_drag_start, on_drag_end, on_drag_enter, on_drag_over, on_drag_leave, on_drop,
70                    on_action, cursor, animate_content_size, focus_requester, on_focus_changed,
71                    text_input,
72                );
73                merge_flags!(self, other;
74                    fill_max, fill_max_w, fill_max_h,
75                    hit_passthrough, input_blocker, repaint_boundary, click, disabled,
76                );
77                if other.z_index != 0.0 {
78                    self.z_index = other.z_index;
79                }
80                self
81            }
82        }
83    };
84}
85
86#[derive(Clone, Debug)]
87pub struct Border {
88    pub width: f32,
89    pub color: Color,
90    pub radius: f32,
91}
92
93#[derive(Clone, Copy, Debug, Default)]
94pub struct PaddingValues {
95    pub left: f32,
96    pub right: f32,
97    pub top: f32,
98    pub bottom: f32,
99}
100
101#[derive(Clone, Debug)]
102pub struct GridConfig {
103    pub columns: usize,
104    pub row_gap: f32,
105    pub column_gap: f32,
106}
107
108/// Drop-shadow parameters applied to a graphics layer.
109///
110/// `blur_radius` is the Gaussian blur radius in dp (1.0 = subtle, 8.0 = soft,
111/// 16.0 = very diffuse). `offset_y` is the vertical offset of the shadow in dp
112/// (positive = below the layer). `color` is the shadow color (premultiplied
113/// alpha controls shadow darkness).
114#[derive(Clone, Copy, Debug)]
115pub struct ShadowSpec {
116    pub blur_radius: f32,
117    pub offset_y: f32,
118    pub color: Color,
119}
120
121#[derive(Clone, Copy, Debug)]
122#[non_exhaustive]
123pub enum PositionType {
124    Relative,
125    Absolute,
126}
127
128/// Configuration for a text input field.
129#[derive(Clone)]
130pub struct TextInputConfig {
131    pub hint: String,
132    pub multiline: bool,
133    pub on_change: Option<Rc<dyn Fn(String)>>,
134    pub on_submit: Option<Rc<dyn Fn(String)>>,
135    pub focus_tracker: Option<Rc<Cell<bool>>>,
136    pub value: String,
137    pub visual_transformation: Option<Rc<dyn crate::text::VisualTransformation>>,
138    pub keyboard_type: Option<crate::text::KeyboardType>,
139    pub ime_action: Option<crate::text::ImeAction>,
140}
141
142impl std::fmt::Debug for TextInputConfig {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        let mut s = f.debug_struct("TextInputConfig");
145        s.field("hint", &self.hint);
146        s.field("multiline", &self.multiline);
147        if self.on_change.is_some() {
148            s.field("on_change", &"…");
149        }
150        if self.on_submit.is_some() {
151            s.field("on_submit", &"…");
152        }
153        if self.focus_tracker.is_some() {
154            s.field("focus_tracker", &"…");
155        }
156        s.field("value", &self.value);
157        if self.visual_transformation.is_some() {
158            s.field("visual_transformation", &"…");
159        }
160        s.field("keyboard_type", &self.keyboard_type);
161        s.field("ime_action", &self.ime_action);
162        s.finish()
163    }
164}
165
166#[derive(Clone, Default)]
167pub struct Modifier {
168    /// Optional stable identity key for this view node.
169    ///
170    /// If set, `layout_and_paint` will prefer this over child index when assigning stable ViewIds.
171    /// This is the “escape hatch” for dynamic lists / conditional UI where index-based identity
172    /// would otherwise shift.
173    pub key: Option<u64>,
174
175    pub size: Option<Size>,
176    pub width: Option<f32>,
177    pub height: Option<f32>,
178    pub fill_max: bool,
179    pub fill_max_w: bool,
180    pub fill_max_h: bool,
181    pub padding: Option<f32>,
182    pub padding_values: Option<PaddingValues>,
183    pub min_width: Option<f32>,
184    pub min_height: Option<f32>,
185    pub max_width: Option<f32>,
186    pub max_height: Option<f32>,
187    pub background: Option<Brush>,
188    pub state_colors: Option<StateColors>,
189    pub state_elevation: Option<StateElevation>,
190
191    pub border: Option<Border>,
192    pub flex_grow: Option<f32>,
193    pub flex_shrink: Option<f32>,
194    pub flex_basis: Option<f32>,
195    pub flex_wrap: Option<FlexWrap>,
196    pub flex_dir: Option<FlexDirection>,
197    pub gap: Option<f32>,
198    pub row_gap: Option<f32>,
199    pub column_gap: Option<f32>,
200    pub align_self: Option<AlignSelf>,
201    pub justify_content: Option<JustifyContent>,
202    pub align_items_container: Option<AlignItems>,
203    pub align_content: Option<AlignContent>,
204    pub clip_rounded: Option<f32>,
205    /// Z-index for hit-testing order (higher = receives events first).
206    pub z_index: f32,
207    /// Z-index for render order (higher = painted on top). If None, uses tree order.
208    pub render_z_index: Option<f32>,
209    /// If true, this view does not create hit regions.
210    pub hit_passthrough: bool,
211    /// If true, this view blocks pointer/touch input for hits below it.
212    pub input_blocker: bool,
213    pub repaint_boundary: bool,
214    pub click: bool,
215    /// When true, the component ignores pointer events and appears disabled.
216    pub disabled: bool,
217    pub on_scroll: Option<Rc<dyn Fn(Vec2) -> Vec2>>,
218    pub on_pointer_down: Option<Rc<dyn Fn(PointerEvent)>>,
219    pub on_pointer_move: Option<Rc<dyn Fn(PointerEvent)>>,
220    pub on_pointer_up: Option<Rc<dyn Fn(PointerEvent)>>,
221    pub on_pointer_enter: Option<Rc<dyn Fn(PointerEvent)>>,
222    pub on_pointer_leave: Option<Rc<dyn Fn(PointerEvent)>>,
223    pub semantics: Option<crate::Semantics>,
224    pub alpha: Option<f32>,
225    pub graphics_layer: Option<f32>,
226    pub shadow: Option<ShadowSpec>,
227    pub transform: Option<Transform>,
228    pub grid: Option<GridConfig>,
229    pub grid_col_span: Option<u16>,
230    pub grid_row_span: Option<u16>,
231    pub position_type: Option<PositionType>,
232    pub offset_left: Option<f32>,
233    pub offset_right: Option<f32>,
234    pub offset_top: Option<f32>,
235    pub offset_bottom: Option<f32>,
236    pub margin_left: Option<f32>,
237    pub margin_right: Option<f32>,
238    pub margin_top: Option<f32>,
239    pub margin_bottom: Option<f32>,
240    pub aspect_ratio: Option<f32>,
241    pub painter: Option<Rc<dyn Fn(&mut crate::Scene, crate::Rect, f32)>>,
242
243    // Drag-drop (internal)
244    pub on_drag_start: Option<Rc<dyn Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload>>>,
245    pub on_drag_end: Option<Rc<dyn Fn(crate::dnd::DragEnd)>>,
246    pub on_drag_enter: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
247    pub on_drag_over: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
248    pub on_drag_leave: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
249    pub on_drop: Option<Rc<dyn Fn(crate::dnd::DropEvent) -> bool>>,
250
251    pub on_action: Option<Rc<dyn Fn(crate::shortcuts::Action) -> bool>>,
252
253    /// Cursor icon hint for desktop/web runners.
254    pub cursor: Option<crate::CursorIcon>,
255
256    /// If set, the size of this node will smoothly animate to its target size
257    /// whenever content size changes. Uses the provided animation spec.
258    pub animate_content_size: Option<AnimationSpec>,
259
260    /// A `FocusRequester` handle that will be associated with this view.
261    /// When the requester's `request_focus()` is called, keyboard focus will
262    /// move to this view.
263    pub focus_requester: Option<crate::runtime::FocusRequester>,
264
265    /// Called when this view gains or loses focus. The boolean parameter is
266    /// `true` when focused, `false` when unfocused.
267    pub on_focus_changed: Option<Rc<dyn Fn(bool)>>,
268
269    /// Text input configuration. When set, this box acts as a text input field.
270    pub text_input: Option<TextInputConfig>,
271}
272
273impl std::fmt::Debug for Modifier {
274    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275        let mut s = f.debug_struct("Modifier");
276
277        macro_rules! opt_val {
278            ($($name:ident),+ $(,)?) => {
279                $( if self.$name.is_some() { s.field(stringify!($name), &self.$name); } )+
280            };
281        }
282        opt_val!(
283            key,
284            size,
285            width,
286            height,
287            padding,
288            padding_values,
289            min_width,
290            min_height,
291            max_width,
292            max_height,
293            background,
294            state_colors,
295            state_elevation,
296            border,
297            flex_grow,
298            flex_shrink,
299            flex_basis,
300            flex_wrap,
301            flex_dir,
302            gap,
303            row_gap,
304            column_gap,
305            align_self,
306            justify_content,
307            align_items_container,
308            align_content,
309            clip_rounded,
310            render_z_index,
311            semantics,
312            alpha,
313            transform,
314            grid,
315            grid_col_span,
316            grid_row_span,
317            position_type,
318            offset_left,
319            offset_right,
320            offset_top,
321            offset_bottom,
322            margin_left,
323            margin_right,
324            margin_top,
325            margin_bottom,
326            aspect_ratio,
327            cursor,
328            animate_content_size,
329        );
330
331        macro_rules! opt_cb {
332            ($($name:ident),+ $(,)?) => {
333                $( if self.$name.is_some() { s.field(stringify!($name), &"…"); } )+
334            };
335        }
336        opt_cb!(
337            on_scroll,
338            on_pointer_down,
339            on_pointer_move,
340            on_pointer_up,
341            on_pointer_enter,
342            on_pointer_leave,
343            painter,
344            on_drag_start,
345            on_drag_end,
346            on_drag_enter,
347            on_drag_over,
348            on_drag_leave,
349            on_drop,
350            on_action,
351            on_focus_changed,
352            text_input,
353        );
354
355        macro_rules! flag {
356            ($($name:ident),+ $(,)?) => {
357                $( if self.$name { s.field(stringify!($name), &true); } )+
358            };
359        }
360        flag!(
361            fill_max,
362            fill_max_w,
363            fill_max_h,
364            hit_passthrough,
365            input_blocker,
366            repaint_boundary,
367            click,
368            disabled,
369        );
370
371        if self.z_index != 0.0 {
372            s.field("z_index", &self.z_index);
373        }
374
375        s.finish()
376    }
377}
378
379impl_option_fields!(Modifier);
380
381impl Modifier {
382    pub fn new() -> Self {
383        Self::default()
384    }
385
386    /// Attaches a stable identity key to this view node.
387    /// Use for dynamic lists / conditional UI where index-based identity can shift.
388    pub fn key(mut self, key: u64) -> Self {
389        self.key = Some(key);
390        self
391    }
392
393    pub fn size(mut self, w: f32, h: f32) -> Self {
394        self.size = Some(Size {
395            width: w,
396            height: h,
397        });
398        self
399    }
400    pub fn width(mut self, w: f32) -> Self {
401        self.width = Some(w);
402        self
403    }
404    pub fn height(mut self, h: f32) -> Self {
405        self.height = Some(h);
406        self
407    }
408    pub fn fill_max_size(mut self) -> Self {
409        self.fill_max = true;
410        self
411    }
412    pub fn fill_max_width(mut self) -> Self {
413        self.fill_max_w = true;
414        self
415    }
416    pub fn fill_max_height(mut self) -> Self {
417        self.fill_max_h = true;
418        self
419    }
420    pub fn padding(mut self, v: f32) -> Self {
421        self.padding = Some(v);
422        self
423    }
424    pub fn padding_values(mut self, padding: PaddingValues) -> Self {
425        self.padding_values = Some(padding);
426        self
427    }
428    /// Add padding equal to the current IME (soft keyboard) bottom inset.
429    /// Combine with `system_bars_padding()` to handle both system bars and keyboard.
430    pub fn ime_padding(mut self) -> Self {
431        let insets = crate::locals::window_insets();
432        let scale = crate::locals::density().scale * crate::locals::ui_scale().0;
433        let mut p = self.padding_values.unwrap_or_default();
434        p.bottom += insets.ime_bottom / scale;
435        self.padding_values = Some(p);
436        self
437    }
438    /// Add padding equal to the current system bar insets (status bar top, nav bar bottom).
439    pub fn system_bars_padding(mut self) -> Self {
440        let insets = crate::locals::window_insets();
441        let scale = crate::locals::density().scale * crate::locals::ui_scale().0;
442        let mut p = self.padding_values.unwrap_or_default();
443        p.top += insets.top / scale;
444        p.bottom += insets.bottom / scale;
445        self.padding_values = Some(p);
446        self
447    }
448    /// Add status bar inset as top padding.
449    pub fn status_bars_padding(mut self) -> Self {
450        let insets = crate::locals::window_insets();
451        let scale = crate::locals::density().scale * crate::locals::ui_scale().0;
452        let mut p = self.padding_values.unwrap_or_default();
453        p.top += insets.top / scale;
454        self.padding_values = Some(p);
455        self
456    }
457    /// Add navigation bar inset as bottom padding.
458    pub fn navigation_bars_padding(mut self) -> Self {
459        let insets = crate::locals::window_insets();
460        let scale = crate::locals::density().scale * crate::locals::ui_scale().0;
461        let mut p = self.padding_values.unwrap_or_default();
462        p.bottom += insets.bottom / scale;
463        self.padding_values = Some(p);
464        self
465    }
466    pub fn min_size(mut self, w: f32, h: f32) -> Self {
467        self.min_width = Some(w);
468        self.min_height = Some(h);
469        self
470    }
471    pub fn max_size(mut self, w: f32, h: f32) -> Self {
472        self.max_width = Some(w);
473        self.max_height = Some(h);
474        self
475    }
476    pub fn min_width(mut self, w: f32) -> Self {
477        self.min_width = Some(w);
478        self
479    }
480    pub fn min_height(mut self, h: f32) -> Self {
481        self.min_height = Some(h);
482        self
483    }
484    pub fn max_width(mut self, w: f32) -> Self {
485        self.max_width = Some(w);
486        self
487    }
488    pub fn max_height(mut self, h: f32) -> Self {
489        self.max_height = Some(h);
490        self
491    }
492    /// Set a solid color background.
493    pub fn background(mut self, color: Color) -> Self {
494        self.background = Some(Brush::Solid(color));
495        self
496    }
497    /// Set a brush (solid, gradient, etc.) background.
498    pub fn background_brush(mut self, brush: Brush) -> Self {
499        self.background = Some(brush);
500        self
501    }
502    pub fn border(mut self, width: f32, color: Color, radius: f32) -> Self {
503        self.border = Some(Border {
504            width,
505            color,
506            radius,
507        });
508        self
509    }
510    pub fn flex_grow(mut self, v: f32) -> Self {
511        self.flex_grow = Some(v);
512        self
513    }
514    pub fn flex_shrink(mut self, v: f32) -> Self {
515        self.flex_shrink = Some(v);
516        self
517    }
518    pub fn flex_basis(mut self, v: f32) -> Self {
519        self.flex_basis = Some(v);
520        self
521    }
522    pub fn flex_wrap(mut self, w: FlexWrap) -> Self {
523        self.flex_wrap = Some(w);
524        self
525    }
526    pub fn flex_dir(mut self, d: FlexDirection) -> Self {
527        self.flex_dir = Some(d);
528        self
529    }
530    pub fn gap(mut self, v: f32) -> Self {
531        let v = v.max(0.0);
532        self.gap = Some(v);
533        self.row_gap = Some(v);
534        self.column_gap = Some(v);
535        self
536    }
537    pub fn row_gap(mut self, v: f32) -> Self {
538        self.row_gap = Some(v.max(0.0));
539        self
540    }
541    pub fn column_gap(mut self, v: f32) -> Self {
542        self.column_gap = Some(v.max(0.0));
543        self
544    }
545    pub fn align_self(mut self, a: AlignSelf) -> Self {
546        self.align_self = Some(a);
547        self
548    }
549    pub fn align_self_center(mut self) -> Self {
550        self.align_self = Some(AlignSelf::Center);
551        self
552    }
553    pub fn justify_content(mut self, j: JustifyContent) -> Self {
554        self.justify_content = Some(j);
555        self
556    }
557    pub fn align_items(mut self, a: AlignItems) -> Self {
558        self.align_items_container = Some(a);
559        self
560    }
561    pub fn align_content(mut self, a: AlignContent) -> Self {
562        self.align_content = Some(a);
563        self
564    }
565    pub fn clip_rounded(mut self, radius: f32) -> Self {
566        self.clip_rounded = Some(radius);
567        self
568    }
569    pub fn z_index(mut self, z: f32) -> Self {
570        self.z_index = z;
571        self
572    }
573
574    /// Sets the render z-index for this view. Higher values are painted on top.
575    /// Unlike `z_index` (which only affects hit-testing), this affects visual layering.
576    pub fn render_z_index(mut self, z: f32) -> Self {
577        self.render_z_index = Some(z);
578        self
579    }
580
581    /// Prevent pointer/touch from reaching lower layers.
582    pub fn input_blocker(mut self) -> Self {
583        self.input_blocker = true;
584        self
585    }
586
587    pub fn hit_passthrough(mut self) -> Self {
588        self.hit_passthrough = true;
589        self
590    }
591    pub fn clickable(mut self) -> Self {
592        self.click = true;
593        self
594    }
595    /// Set state-driven background colors for hover, press, disabled states.
596    /// The layout engine automatically selects and animates between these based on interaction.
597    pub fn state_colors(mut self, colors: StateColors) -> Self {
598        self.state_colors = Some(colors);
599        self
600    }
601    /// Set state-driven elevation values for hover, press, disabled states.
602    pub fn state_elevation(mut self, elev: StateElevation) -> Self {
603        self.state_elevation = Some(elev);
604        self
605    }
606    /// Mark this component as disabled - it won't respond to pointer events.
607    pub fn disabled(mut self) -> Self {
608        self.disabled = true;
609        self
610    }
611    /// Mark this component as enabled or disabled.
612    pub fn enabled(mut self, enabled: bool) -> Self {
613        self.disabled = !enabled;
614        self
615    }
616    pub fn on_scroll(mut self, f: impl Fn(Vec2) -> Vec2 + 'static) -> Self {
617        self.on_scroll = Some(Rc::new(f));
618        self
619    }
620    pub fn on_pointer_down(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
621        self.on_pointer_down = Some(Rc::new(f));
622        self
623    }
624    pub fn on_pointer_move(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
625        self.on_pointer_move = Some(Rc::new(f));
626        self
627    }
628    pub fn on_pointer_up(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
629        self.on_pointer_up = Some(Rc::new(f));
630        self
631    }
632    pub fn on_pointer_enter(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
633        self.on_pointer_enter = Some(Rc::new(f));
634        self
635    }
636    pub fn on_pointer_leave(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
637        self.on_pointer_leave = Some(Rc::new(f));
638        self
639    }
640    pub fn semantics(mut self, s: crate::Semantics) -> Self {
641        self.semantics = Some(s);
642        self
643    }
644    pub fn alpha(mut self, a: f32) -> Self {
645        self.alpha = Some(a);
646        self
647    }
648    /// Render this subtree into an offscreen texture, then composite it
649    /// back into the parent with the given group `alpha` (0.0..=1.0).
650    /// Allows correct blending when children overlap inside the layer, and
651    /// sets up the architecture for future layer effects (shadow, blur, clip).
652    pub fn graphics_layer(mut self, alpha: f32) -> Self {
653        self.graphics_layer = Some(alpha.clamp(0.0, 1.0));
654        self
655    }
656    /// Drop shadow with the given `blur_radius` (dp) and vertical `offset_y` (dp).
657    /// The shadow color defaults to black with alpha 64 (~25%). Combines with
658    /// [`Modifier::graphics_layer`] to draw a shadow underneath the layer.
659    pub fn shadow(mut self, blur_radius: f32, offset_y: f32) -> Self {
660        self.shadow = Some(ShadowSpec {
661            blur_radius: blur_radius.max(0.0),
662            offset_y,
663            color: Color(0, 0, 0, 64),
664        });
665        self
666    }
667    /// Drop shadow with a custom color. Alpha 0..=255.
668    pub fn shadow_with_color(mut self, blur_radius: f32, offset_y: f32, color: Color) -> Self {
669        self.shadow = Some(ShadowSpec {
670            blur_radius: blur_radius.max(0.0),
671            offset_y,
672            color,
673        });
674        self
675    }
676    /// Material-style elevation. Auto-scales blur and offset by `level` (dp)
677    /// and uses a default shadow color. Level 0 = no shadow; 4 = subtle;
678    /// 16 = strong. Requires [`Modifier::graphics_layer`] to take effect.
679    pub fn elevation(mut self, level: f32) -> Self {
680        if level <= 0.0 {
681            self.shadow = None;
682            return self;
683        }
684        self.shadow = Some(ShadowSpec {
685            blur_radius: level * 2.0,
686            offset_y: level * 0.5,
687            color: Color(0, 0, 0, (level * 8.0).clamp(8.0, 80.0) as u8),
688        });
689        self
690    }
691    pub fn transform(mut self, t: Transform) -> Self {
692        self.transform = Some(t);
693        self
694    }
695    pub fn grid(mut self, columns: usize, row_gap: f32, column_gap: f32) -> Self {
696        self.grid = Some(GridConfig {
697            columns,
698            row_gap,
699            column_gap,
700        });
701        self
702    }
703    pub fn grid_span(mut self, col_span: u16, row_span: u16) -> Self {
704        self.grid_col_span = Some(col_span);
705        self.grid_row_span = Some(row_span);
706        self
707    }
708    pub fn absolute(mut self) -> Self {
709        self.position_type = Some(PositionType::Absolute);
710        self
711    }
712    pub fn offset(
713        mut self,
714        left: Option<f32>,
715        top: Option<f32>,
716        right: Option<f32>,
717        bottom: Option<f32>,
718    ) -> Self {
719        self.offset_left = left;
720        self.offset_top = top;
721        self.offset_right = right;
722        self.offset_bottom = bottom;
723        self
724    }
725    pub fn offset_left(mut self, v: f32) -> Self {
726        self.offset_left = Some(v);
727        self
728    }
729    pub fn offset_right(mut self, v: f32) -> Self {
730        self.offset_right = Some(v);
731        self
732    }
733    pub fn offset_top(mut self, v: f32) -> Self {
734        self.offset_top = Some(v);
735        self
736    }
737    pub fn offset_bottom(mut self, v: f32) -> Self {
738        self.offset_bottom = Some(v);
739        self
740    }
741
742    pub fn margin(mut self, v: f32) -> Self {
743        self.margin_left = Some(v);
744        self.margin_right = Some(v);
745        self.margin_top = Some(v);
746        self.margin_bottom = Some(v);
747        self
748    }
749
750    pub fn margin_horizontal(mut self, v: f32) -> Self {
751        self.margin_left = Some(v);
752        self.margin_right = Some(v);
753        self
754    }
755
756    pub fn margin_vertical(mut self, v: f32) -> Self {
757        self.margin_top = Some(v);
758        self.margin_bottom = Some(v);
759        self
760    }
761    pub fn aspect_ratio(mut self, ratio: f32) -> Self {
762        self.aspect_ratio = Some(ratio);
763        self
764    }
765    pub fn painter(mut self, f: impl Fn(&mut crate::Scene, crate::Rect, f32) + 'static) -> Self {
766        self.painter = Some(Rc::new(f));
767        self
768    }
769    pub fn scale(self, s: f32) -> Self {
770        self.scale2(s, s)
771    }
772    pub fn scale2(mut self, sx: f32, sy: f32) -> Self {
773        let mut t = self.transform.unwrap_or_else(Transform::identity);
774        t.scale_x *= sx;
775        t.scale_y *= sy;
776        self.transform = Some(t);
777        self
778    }
779    pub fn translate(mut self, x: f32, y: f32) -> Self {
780        let t = self.transform.unwrap_or_else(Transform::identity);
781        self.transform = Some(t.combine(&Transform::translate(x, y)));
782        self
783    }
784    pub fn translate_vec2(self, v: Vec2) -> Self {
785        self.translate(v.x, v.y)
786    }
787    pub fn rotate(mut self, radians: f32) -> Self {
788        let mut t = self.transform.unwrap_or_else(Transform::identity);
789        t.rotate += radians;
790        self.transform = Some(t);
791        self
792    }
793    pub fn weight(mut self, w: f32) -> Self {
794        let w = w.max(0.0);
795        self.flex_grow = Some(w);
796        self.flex_shrink = Some(1.0);
797        // dp units; 0 is fine.
798        self.flex_basis = Some(0.0);
799        self
800    }
801    /// Marks this view as a repaint boundary candidate.
802    ///
803    /// The engine may cache its painted output.
804    pub fn repaint_boundary(mut self) -> Self {
805        self.repaint_boundary = true;
806        self
807    }
808    pub fn on_action(mut self, f: impl Fn(crate::shortcuts::Action) -> bool + 'static) -> Self {
809        self.on_action = Some(Rc::new(f));
810        self
811    }
812
813    /// Mark this node as a drag source. Return `Some(payload)` to start dragging.
814    pub fn on_drag_start(
815        mut self,
816        f: impl Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload> + 'static,
817    ) -> Self {
818        self.on_drag_start = Some(Rc::new(f));
819        self
820    }
821
822    /// Called when a drag ends (drop accepted or canceled/ignored).
823    pub fn on_drag_end(mut self, f: impl Fn(crate::dnd::DragEnd) + 'static) -> Self {
824        self.on_drag_end = Some(Rc::new(f));
825        self
826    }
827
828    /// Called when a drag first enters this target.
829    pub fn on_drag_enter(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
830        self.on_drag_enter = Some(Rc::new(f));
831        self
832    }
833
834    /// Called on every pointer move while a drag is over this target.
835    pub fn on_drag_over(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
836        self.on_drag_over = Some(Rc::new(f));
837        self
838    }
839
840    /// Called when a drag leaves this target.
841    pub fn on_drag_leave(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
842        self.on_drag_leave = Some(Rc::new(f));
843        self
844    }
845
846    /// Called on pointer release while a drag is over this target.
847    /// Return `true` to accept the drop.
848    pub fn on_drop(mut self, f: impl Fn(crate::dnd::DropEvent) -> bool + 'static) -> Self {
849        self.on_drop = Some(Rc::new(f));
850        self
851    }
852
853    /// Set the cursor icon hint for desktop/web runners.
854    pub fn cursor(mut self, c: crate::CursorIcon) -> Self {
855        self.cursor = Some(c);
856        self
857    }
858
859    /// Animate size changes smoothly when the content's natural size changes.
860    /// Uses the provided `AnimationSpec` for the transition.
861    /// The content will be clipped to the animated size during transitions.
862    pub fn animate_content_size(mut self, spec: AnimationSpec) -> Self {
863        self.animate_content_size = Some(spec);
864        self
865    }
866
867    /// Attach a `FocusRequester` to this view. The requester will be associated
868    /// with the view's focusable element, allowing programmatic focus requests.
869    pub fn focus_requester(mut self, fr: crate::runtime::FocusRequester) -> Self {
870        self.focus_requester = Some(fr);
871        self
872    }
873
874    /// Register a callback that fires when this view gains or loses keyboard focus.
875    /// The argument is `true` when the view receives focus, `false` when it loses it.
876    pub fn on_focus_changed(mut self, f: impl Fn(bool) + 'static) -> Self {
877        self.on_focus_changed = Some(Rc::new(f));
878        self
879    }
880
881    /// Mark this Box as a text input field with the given configuration.
882    pub fn text_input(mut self, config: TextInputConfig) -> Self {
883        self.text_input = Some(config);
884        self
885    }
886}