Skip to main content

repose_core/
modifier.rs

1use std::rc::Rc;
2
3use taffy::{AlignContent, AlignItems, AlignSelf, FlexDirection, FlexWrap, JustifyContent};
4
5use crate::animation::AnimationSpec;
6use crate::{Brush, Color, PointerEvent, Size, Transform, Vec2};
7
8/// State-driven colors for interactive components.
9/// The layout engine selects the appropriate color based on hover/press/disabled state
10/// and animates transitions between them.
11#[derive(Clone, Copy, Debug)]
12pub struct StateColors {
13    pub default: Color,
14    pub hovered: Color,
15    pub pressed: Color,
16    pub disabled: Color,
17}
18
19/// State-driven elevation for interactive components.
20/// The layout engine animates elevation changes on interaction.
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,
71                );
72                merge_flags!(self, other;
73                    fill_max, fill_max_w, fill_max_h,
74                    hit_passthrough, input_blocker, repaint_boundary, click, disabled,
75                );
76                if other.z_index != 0.0 {
77                    self.z_index = other.z_index;
78                }
79                self
80            }
81        }
82    };
83}
84
85#[derive(Clone, Debug)]
86pub struct Border {
87    pub width: f32,
88    pub color: Color,
89    pub radius: f32,
90}
91
92#[derive(Clone, Copy, Debug, Default)]
93pub struct PaddingValues {
94    pub left: f32,
95    pub right: f32,
96    pub top: f32,
97    pub bottom: f32,
98}
99
100#[derive(Clone, Debug)]
101pub struct GridConfig {
102    pub columns: usize,
103    pub row_gap: f32,
104    pub column_gap: f32,
105}
106
107#[derive(Clone, Copy, Debug)]
108pub enum PositionType {
109    Relative,
110    Absolute,
111}
112
113#[derive(Clone, Default)]
114pub struct Modifier {
115    /// Optional stable identity key for this view node.
116    ///
117    /// If set, `layout_and_paint` will prefer this over child index when assigning stable ViewIds.
118    /// This is the “escape hatch” for dynamic lists / conditional UI where index-based identity
119    /// would otherwise shift.
120    pub key: Option<u64>,
121
122    pub size: Option<Size>,
123    pub width: Option<f32>,
124    pub height: Option<f32>,
125    pub fill_max: bool,
126    pub fill_max_w: bool,
127    pub fill_max_h: bool,
128    pub padding: Option<f32>,
129    pub padding_values: Option<PaddingValues>,
130    pub min_width: Option<f32>,
131    pub min_height: Option<f32>,
132    pub max_width: Option<f32>,
133    pub max_height: Option<f32>,
134    pub background: Option<Brush>,
135    pub state_colors: Option<StateColors>,
136    pub state_elevation: Option<StateElevation>,
137
138    pub border: Option<Border>,
139    pub flex_grow: Option<f32>,
140    pub flex_shrink: Option<f32>,
141    pub flex_basis: Option<f32>,
142    pub flex_wrap: Option<FlexWrap>,
143    pub flex_dir: Option<FlexDirection>,
144    pub gap: Option<f32>,
145    pub row_gap: Option<f32>,
146    pub column_gap: Option<f32>,
147    pub align_self: Option<AlignSelf>,
148    pub justify_content: Option<JustifyContent>,
149    pub align_items_container: Option<AlignItems>,
150    pub align_content: Option<AlignContent>,
151    pub clip_rounded: Option<f32>,
152    /// Z-index for hit-testing order (higher = receives events first).
153    pub z_index: f32,
154    /// Z-index for render order (higher = painted on top). If None, uses tree order.
155    pub render_z_index: Option<f32>,
156    /// If true, this view does not create hit regions.
157    pub hit_passthrough: bool,
158    /// If true, this view blocks pointer/touch input for hits below it.
159    pub input_blocker: bool,
160    pub repaint_boundary: bool,
161    pub click: bool,
162    /// When true, the component ignores pointer events and appears disabled.
163    pub disabled: bool,
164    pub on_scroll: Option<Rc<dyn Fn(Vec2) -> Vec2>>,
165    pub on_pointer_down: Option<Rc<dyn Fn(PointerEvent)>>,
166    pub on_pointer_move: Option<Rc<dyn Fn(PointerEvent)>>,
167    pub on_pointer_up: Option<Rc<dyn Fn(PointerEvent)>>,
168    pub on_pointer_enter: Option<Rc<dyn Fn(PointerEvent)>>,
169    pub on_pointer_leave: Option<Rc<dyn Fn(PointerEvent)>>,
170    pub semantics: Option<crate::Semantics>,
171    pub alpha: Option<f32>,
172    pub transform: Option<Transform>,
173    pub grid: Option<GridConfig>,
174    pub grid_col_span: Option<u16>,
175    pub grid_row_span: Option<u16>,
176    pub position_type: Option<PositionType>,
177    pub offset_left: Option<f32>,
178    pub offset_right: Option<f32>,
179    pub offset_top: Option<f32>,
180    pub offset_bottom: Option<f32>,
181    pub margin_left: Option<f32>,
182    pub margin_right: Option<f32>,
183    pub margin_top: Option<f32>,
184    pub margin_bottom: Option<f32>,
185    pub aspect_ratio: Option<f32>,
186    pub painter: Option<Rc<dyn Fn(&mut crate::Scene, crate::Rect)>>,
187
188    // Drag-drop (internal)
189    pub on_drag_start: Option<Rc<dyn Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload>>>,
190    pub on_drag_end: Option<Rc<dyn Fn(crate::dnd::DragEnd)>>,
191    pub on_drag_enter: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
192    pub on_drag_over: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
193    pub on_drag_leave: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
194    pub on_drop: Option<Rc<dyn Fn(crate::dnd::DropEvent) -> bool>>,
195
196    pub on_action: Option<Rc<dyn Fn(crate::shortcuts::Action) -> bool>>,
197
198    /// Cursor icon hint for desktop/web runners.
199    pub cursor: Option<crate::CursorIcon>,
200
201    /// If set, the size of this node will smoothly animate to its target size
202    /// whenever content size changes. Uses the provided animation spec.
203    pub animate_content_size: Option<AnimationSpec>,
204
205    /// A `FocusRequester` handle that will be associated with this view.
206    /// When the requester's `request_focus()` is called, keyboard focus will
207    /// move to this view.
208    pub focus_requester: Option<crate::runtime::FocusRequester>,
209}
210
211impl std::fmt::Debug for Modifier {
212    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213        let mut s = f.debug_struct("Modifier");
214
215        macro_rules! opt_val {
216            ($($name:ident),+ $(,)?) => {
217                $( if self.$name.is_some() { s.field(stringify!($name), &self.$name); } )+
218            };
219        }
220        opt_val!(
221            key,
222            size,
223            width,
224            height,
225            padding,
226            padding_values,
227            min_width,
228            min_height,
229            max_width,
230            max_height,
231            background,
232            state_colors,
233            state_elevation,
234            border,
235            flex_grow,
236            flex_shrink,
237            flex_basis,
238            flex_wrap,
239            flex_dir,
240            gap,
241            row_gap,
242            column_gap,
243            align_self,
244            justify_content,
245            align_items_container,
246            align_content,
247            clip_rounded,
248            render_z_index,
249            semantics,
250            alpha,
251            transform,
252            grid,
253            grid_col_span,
254            grid_row_span,
255            position_type,
256            offset_left,
257            offset_right,
258            offset_top,
259            offset_bottom,
260            margin_left,
261            margin_right,
262            margin_top,
263            margin_bottom,
264            aspect_ratio,
265            cursor,
266            animate_content_size,
267        );
268
269        macro_rules! opt_cb {
270            ($($name:ident),+ $(,)?) => {
271                $( if self.$name.is_some() { s.field(stringify!($name), &"…"); } )+
272            };
273        }
274        opt_cb!(
275            on_scroll,
276            on_pointer_down,
277            on_pointer_move,
278            on_pointer_up,
279            on_pointer_enter,
280            on_pointer_leave,
281            painter,
282            on_drag_start,
283            on_drag_end,
284            on_drag_enter,
285            on_drag_over,
286            on_drag_leave,
287            on_drop,
288            on_action,
289        );
290
291        macro_rules! flag {
292            ($($name:ident),+ $(,)?) => {
293                $( if self.$name { s.field(stringify!($name), &true); } )+
294            };
295        }
296        flag!(
297            fill_max,
298            fill_max_w,
299            fill_max_h,
300            hit_passthrough,
301            input_blocker,
302            repaint_boundary,
303            click,
304            disabled,
305        );
306
307        if self.z_index != 0.0 {
308            s.field("z_index", &self.z_index);
309        }
310
311        s.finish()
312    }
313}
314
315impl_option_fields!(Modifier);
316
317impl Modifier {
318    pub fn new() -> Self {
319        Self::default()
320    }
321
322    /// Attaches a stable identity key to this view node.
323    /// Use for dynamic lists / conditional UI where index-based identity can shift.
324    pub fn key(mut self, key: u64) -> Self {
325        self.key = Some(key);
326        self
327    }
328
329    pub fn size(mut self, w: f32, h: f32) -> Self {
330        self.size = Some(Size {
331            width: w,
332            height: h,
333        });
334        self
335    }
336    pub fn width(mut self, w: f32) -> Self {
337        self.width = Some(w);
338        self
339    }
340    pub fn height(mut self, h: f32) -> Self {
341        self.height = Some(h);
342        self
343    }
344    pub fn fill_max_size(mut self) -> Self {
345        self.fill_max = true;
346        self
347    }
348    pub fn fill_max_width(mut self) -> Self {
349        self.fill_max_w = true;
350        self
351    }
352    pub fn fill_max_height(mut self) -> Self {
353        self.fill_max_h = true;
354        self
355    }
356    pub fn padding(mut self, v: f32) -> Self {
357        self.padding = Some(v);
358        self
359    }
360    pub fn padding_values(mut self, padding: PaddingValues) -> Self {
361        self.padding_values = Some(padding);
362        self
363    }
364    /// Add padding equal to the current IME (soft keyboard) bottom inset.
365    /// Combine with `system_bars_padding()` to handle both system bars and keyboard.
366    pub fn ime_padding(mut self) -> Self {
367        let insets = crate::locals::window_insets();
368        let mut p = self.padding_values.unwrap_or_default();
369        p.bottom += insets.ime_bottom;
370        self.padding_values = Some(p);
371        self
372    }
373    /// Add padding equal to the current system bar insets (status bar top, nav bar bottom).
374    pub fn system_bars_padding(mut self) -> Self {
375        let insets = crate::locals::window_insets();
376        let mut p = self.padding_values.unwrap_or_default();
377        p.top += insets.top;
378        p.bottom += insets.bottom;
379        self.padding_values = Some(p);
380        self
381    }
382    /// Add status bar inset as top padding.
383    pub fn status_bars_padding(mut self) -> Self {
384        let insets = crate::locals::window_insets();
385        let mut p = self.padding_values.unwrap_or_default();
386        p.top += insets.top;
387        self.padding_values = Some(p);
388        self
389    }
390    /// Add navigation bar inset as bottom padding.
391    pub fn navigation_bars_padding(mut self) -> Self {
392        let insets = crate::locals::window_insets();
393        let mut p = self.padding_values.unwrap_or_default();
394        p.bottom += insets.bottom;
395        self.padding_values = Some(p);
396        self
397    }
398    pub fn min_size(mut self, w: f32, h: f32) -> Self {
399        self.min_width = Some(w);
400        self.min_height = Some(h);
401        self
402    }
403    pub fn max_size(mut self, w: f32, h: f32) -> Self {
404        self.max_width = Some(w);
405        self.max_height = Some(h);
406        self
407    }
408    pub fn min_width(mut self, w: f32) -> Self {
409        self.min_width = Some(w);
410        self
411    }
412    pub fn min_height(mut self, h: f32) -> Self {
413        self.min_height = Some(h);
414        self
415    }
416    pub fn max_width(mut self, w: f32) -> Self {
417        self.max_width = Some(w);
418        self
419    }
420    pub fn max_height(mut self, h: f32) -> Self {
421        self.max_height = Some(h);
422        self
423    }
424    /// Set a solid color background.
425    pub fn background(mut self, color: Color) -> Self {
426        self.background = Some(Brush::Solid(color));
427        self
428    }
429    /// Set a brush (solid, gradient, etc.) background.
430    pub fn background_brush(mut self, brush: Brush) -> Self {
431        self.background = Some(brush);
432        self
433    }
434    pub fn border(mut self, width: f32, color: Color, radius: f32) -> Self {
435        self.border = Some(Border {
436            width,
437            color,
438            radius,
439        });
440        self
441    }
442    pub fn flex_grow(mut self, v: f32) -> Self {
443        self.flex_grow = Some(v);
444        self
445    }
446    pub fn flex_shrink(mut self, v: f32) -> Self {
447        self.flex_shrink = Some(v);
448        self
449    }
450    pub fn flex_basis(mut self, v: f32) -> Self {
451        self.flex_basis = Some(v);
452        self
453    }
454    pub fn flex_wrap(mut self, w: FlexWrap) -> Self {
455        self.flex_wrap = Some(w);
456        self
457    }
458    pub fn flex_dir(mut self, d: FlexDirection) -> Self {
459        self.flex_dir = Some(d);
460        self
461    }
462    pub fn gap(mut self, v: f32) -> Self {
463        let v = v.max(0.0);
464        self.gap = Some(v);
465        self.row_gap = Some(v);
466        self.column_gap = Some(v);
467        self
468    }
469    pub fn row_gap(mut self, v: f32) -> Self {
470        self.row_gap = Some(v.max(0.0));
471        self
472    }
473    pub fn column_gap(mut self, v: f32) -> Self {
474        self.column_gap = Some(v.max(0.0));
475        self
476    }
477    pub fn align_self(mut self, a: AlignSelf) -> Self {
478        self.align_self = Some(a);
479        self
480    }
481    pub fn align_self_center(mut self) -> Self {
482        self.align_self = Some(AlignSelf::Center);
483        self
484    }
485    pub fn justify_content(mut self, j: JustifyContent) -> Self {
486        self.justify_content = Some(j);
487        self
488    }
489    pub fn align_items(mut self, a: AlignItems) -> Self {
490        self.align_items_container = Some(a);
491        self
492    }
493    pub fn align_content(mut self, a: AlignContent) -> Self {
494        self.align_content = Some(a);
495        self
496    }
497    pub fn clip_rounded(mut self, radius: f32) -> Self {
498        self.clip_rounded = Some(radius);
499        self
500    }
501    pub fn z_index(mut self, z: f32) -> Self {
502        self.z_index = z;
503        self
504    }
505
506    /// Sets the render z-index for this view. Higher values are painted on top.
507    /// Unlike `z_index` (which only affects hit-testing), this affects visual layering.
508    pub fn render_z_index(mut self, z: f32) -> Self {
509        self.render_z_index = Some(z);
510        self
511    }
512
513    /// Prevent pointer/touch from reaching lower layers.
514    pub fn input_blocker(mut self) -> Self {
515        self.input_blocker = true;
516        self
517    }
518
519    pub fn hit_passthrough(mut self) -> Self {
520        self.hit_passthrough = true;
521        self
522    }
523    pub fn clickable(mut self) -> Self {
524        self.click = true;
525        self
526    }
527    /// Set state-driven background colors for hover, press, disabled states.
528    /// The layout engine automatically selects and animates between these based on interaction.
529    pub fn state_colors(mut self, colors: StateColors) -> Self {
530        self.state_colors = Some(colors);
531        self
532    }
533    /// Set state-driven elevation values for hover, press, disabled states.
534    pub fn state_elevation(mut self, elev: StateElevation) -> Self {
535        self.state_elevation = Some(elev);
536        self
537    }
538    /// Mark this component as disabled - it won't respond to pointer events.
539    pub fn disabled(mut self) -> Self {
540        self.disabled = true;
541        self
542    }
543    /// Mark this component as enabled or disabled.
544    pub fn enabled(mut self, enabled: bool) -> Self {
545        self.disabled = !enabled;
546        self
547    }
548    pub fn on_scroll(mut self, f: impl Fn(Vec2) -> Vec2 + 'static) -> Self {
549        self.on_scroll = Some(Rc::new(f));
550        self
551    }
552    pub fn on_pointer_down(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
553        self.on_pointer_down = Some(Rc::new(f));
554        self
555    }
556    pub fn on_pointer_move(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
557        self.on_pointer_move = Some(Rc::new(f));
558        self
559    }
560    pub fn on_pointer_up(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
561        self.on_pointer_up = Some(Rc::new(f));
562        self
563    }
564    pub fn on_pointer_enter(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
565        self.on_pointer_enter = Some(Rc::new(f));
566        self
567    }
568    pub fn on_pointer_leave(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
569        self.on_pointer_leave = Some(Rc::new(f));
570        self
571    }
572    pub fn semantics(mut self, s: crate::Semantics) -> Self {
573        self.semantics = Some(s);
574        self
575    }
576    pub fn alpha(mut self, a: f32) -> Self {
577        self.alpha = Some(a);
578        self
579    }
580    pub fn transform(mut self, t: Transform) -> Self {
581        self.transform = Some(t);
582        self
583    }
584    pub fn grid(mut self, columns: usize, row_gap: f32, column_gap: f32) -> Self {
585        self.grid = Some(GridConfig {
586            columns,
587            row_gap,
588            column_gap,
589        });
590        self
591    }
592    pub fn grid_span(mut self, col_span: u16, row_span: u16) -> Self {
593        self.grid_col_span = Some(col_span);
594        self.grid_row_span = Some(row_span);
595        self
596    }
597    pub fn absolute(mut self) -> Self {
598        self.position_type = Some(PositionType::Absolute);
599        self
600    }
601    pub fn offset(
602        mut self,
603        left: Option<f32>,
604        top: Option<f32>,
605        right: Option<f32>,
606        bottom: Option<f32>,
607    ) -> Self {
608        self.offset_left = left;
609        self.offset_top = top;
610        self.offset_right = right;
611        self.offset_bottom = bottom;
612        self
613    }
614    pub fn offset_left(mut self, v: f32) -> Self {
615        self.offset_left = Some(v);
616        self
617    }
618    pub fn offset_right(mut self, v: f32) -> Self {
619        self.offset_right = Some(v);
620        self
621    }
622    pub fn offset_top(mut self, v: f32) -> Self {
623        self.offset_top = Some(v);
624        self
625    }
626    pub fn offset_bottom(mut self, v: f32) -> Self {
627        self.offset_bottom = Some(v);
628        self
629    }
630
631    pub fn margin(mut self, v: f32) -> Self {
632        self.margin_left = Some(v);
633        self.margin_right = Some(v);
634        self.margin_top = Some(v);
635        self.margin_bottom = Some(v);
636        self
637    }
638
639    pub fn margin_horizontal(mut self, v: f32) -> Self {
640        self.margin_left = Some(v);
641        self.margin_right = Some(v);
642        self
643    }
644
645    pub fn margin_vertical(mut self, v: f32) -> Self {
646        self.margin_top = Some(v);
647        self.margin_bottom = Some(v);
648        self
649    }
650    pub fn aspect_ratio(mut self, ratio: f32) -> Self {
651        self.aspect_ratio = Some(ratio);
652        self
653    }
654    pub fn painter(mut self, f: impl Fn(&mut crate::Scene, crate::Rect) + 'static) -> Self {
655        self.painter = Some(Rc::new(f));
656        self
657    }
658    pub fn scale(self, s: f32) -> Self {
659        self.scale2(s, s)
660    }
661    pub fn scale2(mut self, sx: f32, sy: f32) -> Self {
662        let mut t = self.transform.unwrap_or_else(Transform::identity);
663        t.scale_x *= sx;
664        t.scale_y *= sy;
665        self.transform = Some(t);
666        self
667    }
668    pub fn translate(mut self, x: f32, y: f32) -> Self {
669        let t = self.transform.unwrap_or_else(Transform::identity);
670        self.transform = Some(t.combine(&Transform::translate(x, y)));
671        self
672    }
673    pub fn translate_vec2(mut self, v: Vec2) -> Self {
674        self.translate(v.x, v.y)
675    }
676    pub fn rotate(mut self, radians: f32) -> Self {
677        let mut t = self.transform.unwrap_or_else(Transform::identity);
678        t.rotate += radians;
679        self.transform = Some(t);
680        self
681    }
682    pub fn weight(mut self, w: f32) -> Self {
683        let w = w.max(0.0);
684        self.flex_grow = Some(w);
685        self.flex_shrink = Some(1.0);
686        // dp units; 0 is fine.
687        self.flex_basis = Some(0.0);
688        self
689    }
690    /// Marks this view as a repaint boundary candidate.
691    ///
692    /// The engine may cache its painted output.
693    pub fn repaint_boundary(mut self) -> Self {
694        self.repaint_boundary = true;
695        self
696    }
697    pub fn on_action(mut self, f: impl Fn(crate::shortcuts::Action) -> bool + 'static) -> Self {
698        self.on_action = Some(Rc::new(f));
699        self
700    }
701
702    /// Mark this node as a drag source. Return `Some(payload)` to start dragging.
703    pub fn on_drag_start(
704        mut self,
705        f: impl Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload> + 'static,
706    ) -> Self {
707        self.on_drag_start = Some(Rc::new(f));
708        self
709    }
710
711    /// Called when a drag ends (drop accepted or canceled/ignored).
712    pub fn on_drag_end(mut self, f: impl Fn(crate::dnd::DragEnd) + 'static) -> Self {
713        self.on_drag_end = Some(Rc::new(f));
714        self
715    }
716
717    /// Called when a drag first enters this target.
718    pub fn on_drag_enter(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
719        self.on_drag_enter = Some(Rc::new(f));
720        self
721    }
722
723    /// Called on every pointer move while a drag is over this target.
724    pub fn on_drag_over(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
725        self.on_drag_over = Some(Rc::new(f));
726        self
727    }
728
729    /// Called when a drag leaves this target.
730    pub fn on_drag_leave(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
731        self.on_drag_leave = Some(Rc::new(f));
732        self
733    }
734
735    /// Called on pointer release while a drag is over this target.
736    /// Return `true` to accept the drop.
737    pub fn on_drop(mut self, f: impl Fn(crate::dnd::DropEvent) -> bool + 'static) -> Self {
738        self.on_drop = Some(Rc::new(f));
739        self
740    }
741
742    /// Set the cursor icon hint for desktop/web runners.
743    pub fn cursor(mut self, c: crate::CursorIcon) -> Self {
744        self.cursor = Some(c);
745        self
746    }
747
748    /// Animate size changes smoothly when the content's natural size changes.
749    /// Uses the provided `AnimationSpec` for the transition.
750    /// The content will be clipped to the animated size during transitions.
751    pub fn animate_content_size(mut self, spec: AnimationSpec) -> Self {
752        self.animate_content_size = Some(spec);
753        self
754    }
755
756    /// Attach a `FocusRequester` to this view. The requester will be associated
757    /// with the view's focusable element, allowing programmatic focus requests.
758    pub fn focus_requester(mut self, fr: crate::runtime::FocusRequester) -> Self {
759        self.focus_requester = Some(fr);
760        self
761    }
762}