Skip to main content

repose_core/
modifier.rs

1use std::rc::Rc;
2
3use taffy::{AlignContent, AlignItems, AlignSelf, FlexDirection, FlexWrap, JustifyContent};
4
5use crate::{Brush, Color, PointerEvent, Size, Transform, Vec2};
6
7macro_rules! merge_opts {
8    ($dst:ident, $src:ident; $($f:ident),+ $(,)?) => {
9        $( $dst.$f = $src.$f.or($dst.$f); )+
10    };
11}
12macro_rules! merge_flags {
13    ($dst:ident, $src:ident; $($f:ident),+ $(,)?) => {
14        $( $dst.$f |= $src.$f; )+
15    };
16}
17
18macro_rules! impl_option_fields {
19    ($ty:ty, $fn:ident) => {
20        impl $ty {
21            $fn!(replace);
22        }
23    };
24    ($ty:ident) => {
25        impl $ty {
26            /// Chain another modifier's settings onto this one.
27            /// Useful for creating reusable modifier templates.
28            pub fn then(mut self, other: Self) -> Self {
29                merge_opts!(self, other;
30                    key, size, width, height,
31                    padding, padding_values,
32                    min_width, min_height, max_width, max_height,
33                    background, border,
34                    flex_grow, flex_shrink, flex_basis, flex_wrap, flex_dir,
35                    gap, row_gap, column_gap,
36                    align_self, justify_content, align_items_container, align_content,
37                    clip_rounded, render_z_index,
38                    on_scroll,
39                    on_pointer_down, on_pointer_move, on_pointer_up,
40                    on_pointer_enter, on_pointer_leave,
41                    semantics, alpha, transform,
42                    grid, grid_col_span, grid_row_span,
43                    position_type,
44                    offset_left, offset_right, offset_top, offset_bottom,
45                    margin_left, margin_right, margin_top, margin_bottom,
46                    aspect_ratio, painter,
47                    on_drag_start, on_drag_end, on_drag_enter, on_drag_over, on_drag_leave, on_drop,
48                    on_action, cursor,
49                );
50                merge_flags!(self, other;
51                    fill_max, fill_max_w, fill_max_h,
52                    hit_passthrough, input_blocker, repaint_boundary, click,
53                );
54                if other.z_index != 0.0 {
55                    self.z_index = other.z_index;
56                }
57                self
58            }
59        }
60    };
61}
62
63#[derive(Clone, Debug)]
64pub struct Border {
65    pub width: f32,
66    pub color: Color,
67    pub radius: f32,
68}
69
70#[derive(Clone, Copy, Debug, Default)]
71pub struct PaddingValues {
72    pub left: f32,
73    pub right: f32,
74    pub top: f32,
75    pub bottom: f32,
76}
77
78#[derive(Clone, Debug)]
79pub struct GridConfig {
80    pub columns: usize,
81    pub row_gap: f32,
82    pub column_gap: f32,
83}
84
85#[derive(Clone, Copy, Debug)]
86pub enum PositionType {
87    Relative,
88    Absolute,
89}
90
91#[derive(Clone, Default)]
92pub struct Modifier {
93    /// Optional stable identity key for this view node.
94    ///
95    /// If set, `layout_and_paint` will prefer this over child index when assigning stable ViewIds.
96    /// This is the “escape hatch” for dynamic lists / conditional UI where index-based identity
97    /// would otherwise shift.
98    pub key: Option<u64>,
99
100    pub size: Option<Size>,
101    pub width: Option<f32>,
102    pub height: Option<f32>,
103    pub fill_max: bool,
104    pub fill_max_w: bool,
105    pub fill_max_h: bool,
106    pub padding: Option<f32>,
107    pub padding_values: Option<PaddingValues>,
108    pub min_width: Option<f32>,
109    pub min_height: Option<f32>,
110    pub max_width: Option<f32>,
111    pub max_height: Option<f32>,
112    pub background: Option<Brush>,
113    pub border: Option<Border>,
114    pub flex_grow: Option<f32>,
115    pub flex_shrink: Option<f32>,
116    pub flex_basis: Option<f32>,
117    pub flex_wrap: Option<FlexWrap>,
118    pub flex_dir: Option<FlexDirection>,
119    pub gap: Option<f32>,
120    pub row_gap: Option<f32>,
121    pub column_gap: Option<f32>,
122    pub align_self: Option<AlignSelf>,
123    pub justify_content: Option<JustifyContent>,
124    pub align_items_container: Option<AlignItems>,
125    pub align_content: Option<AlignContent>,
126    pub clip_rounded: Option<f32>,
127    /// Z-index for hit-testing order (higher = receives events first).
128    pub z_index: f32,
129    /// Z-index for render order (higher = painted on top). If None, uses tree order.
130    pub render_z_index: Option<f32>,
131    /// If true, this view does not create hit regions.
132    pub hit_passthrough: bool,
133    /// If true, this view blocks pointer/touch input for hits below it.
134    pub input_blocker: bool,
135    pub repaint_boundary: bool,
136    pub click: bool,
137    pub on_scroll: Option<Rc<dyn Fn(Vec2) -> Vec2>>,
138    pub on_pointer_down: Option<Rc<dyn Fn(PointerEvent)>>,
139    pub on_pointer_move: Option<Rc<dyn Fn(PointerEvent)>>,
140    pub on_pointer_up: Option<Rc<dyn Fn(PointerEvent)>>,
141    pub on_pointer_enter: Option<Rc<dyn Fn(PointerEvent)>>,
142    pub on_pointer_leave: Option<Rc<dyn Fn(PointerEvent)>>,
143    pub semantics: Option<crate::Semantics>,
144    pub alpha: Option<f32>,
145    pub transform: Option<Transform>,
146    pub grid: Option<GridConfig>,
147    pub grid_col_span: Option<u16>,
148    pub grid_row_span: Option<u16>,
149    pub position_type: Option<PositionType>,
150    pub offset_left: Option<f32>,
151    pub offset_right: Option<f32>,
152    pub offset_top: Option<f32>,
153    pub offset_bottom: Option<f32>,
154    pub margin_left: Option<f32>,
155    pub margin_right: Option<f32>,
156    pub margin_top: Option<f32>,
157    pub margin_bottom: Option<f32>,
158    pub aspect_ratio: Option<f32>,
159    pub painter: Option<Rc<dyn Fn(&mut crate::Scene, crate::Rect)>>,
160
161    // Drag-drop (internal)
162    pub on_drag_start: Option<Rc<dyn Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload>>>,
163    pub on_drag_end: Option<Rc<dyn Fn(crate::dnd::DragEnd)>>,
164    pub on_drag_enter: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
165    pub on_drag_over: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
166    pub on_drag_leave: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
167    pub on_drop: Option<Rc<dyn Fn(crate::dnd::DropEvent) -> bool>>,
168
169    pub on_action: Option<Rc<dyn Fn(crate::shortcuts::Action) -> bool>>,
170
171    /// Cursor icon hint for desktop/web runners.
172    pub cursor: Option<crate::CursorIcon>,
173}
174
175impl std::fmt::Debug for Modifier {
176    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177        let mut s = f.debug_struct("Modifier");
178
179        macro_rules! opt_val {
180            ($($name:ident),+ $(,)?) => {
181                $( if self.$name.is_some() { s.field(stringify!($name), &self.$name); } )+
182            };
183        }
184        opt_val!(
185            key,
186            size,
187            width,
188            height,
189            padding,
190            padding_values,
191            min_width,
192            min_height,
193            max_width,
194            max_height,
195            background,
196            border,
197            flex_grow,
198            flex_shrink,
199            flex_basis,
200            flex_wrap,
201            flex_dir,
202            gap,
203            row_gap,
204            column_gap,
205            align_self,
206            justify_content,
207            align_items_container,
208            align_content,
209            clip_rounded,
210            render_z_index,
211            semantics,
212            alpha,
213            transform,
214            grid,
215            grid_col_span,
216            grid_row_span,
217            position_type,
218            offset_left,
219            offset_right,
220            offset_top,
221            offset_bottom,
222            margin_left,
223            margin_right,
224            margin_top,
225            margin_bottom,
226            aspect_ratio,
227            cursor,
228        );
229
230        macro_rules! opt_cb {
231            ($($name:ident),+ $(,)?) => {
232                $( if self.$name.is_some() { s.field(stringify!($name), &"…"); } )+
233            };
234        }
235        opt_cb!(
236            on_scroll,
237            on_pointer_down,
238            on_pointer_move,
239            on_pointer_up,
240            on_pointer_enter,
241            on_pointer_leave,
242            painter,
243            on_drag_start,
244            on_drag_end,
245            on_drag_enter,
246            on_drag_over,
247            on_drag_leave,
248            on_drop,
249            on_action,
250        );
251
252        macro_rules! flag {
253            ($($name:ident),+ $(,)?) => {
254                $( if self.$name { s.field(stringify!($name), &true); } )+
255            };
256        }
257        flag!(
258            fill_max,
259            fill_max_w,
260            fill_max_h,
261            hit_passthrough,
262            input_blocker,
263            repaint_boundary,
264            click,
265        );
266
267        if self.z_index != 0.0 {
268            s.field("z_index", &self.z_index);
269        }
270
271        s.finish()
272    }
273}
274
275impl_option_fields!(Modifier);
276
277impl Modifier {
278    pub fn new() -> Self {
279        Self::default()
280    }
281
282    /// Attaches a stable identity key to this view node.
283    /// Use for dynamic lists / conditional UI where index-based identity can shift.
284    pub fn key(mut self, key: u64) -> Self {
285        self.key = Some(key);
286        self
287    }
288
289    pub fn size(mut self, w: f32, h: f32) -> Self {
290        self.size = Some(Size {
291            width: w,
292            height: h,
293        });
294        self
295    }
296    pub fn width(mut self, w: f32) -> Self {
297        self.width = Some(w);
298        self
299    }
300    pub fn height(mut self, h: f32) -> Self {
301        self.height = Some(h);
302        self
303    }
304    pub fn fill_max_size(mut self) -> Self {
305        self.fill_max = true;
306        self
307    }
308    pub fn fill_max_width(mut self) -> Self {
309        self.fill_max_w = true;
310        self
311    }
312    pub fn fill_max_height(mut self) -> Self {
313        self.fill_max_h = true;
314        self
315    }
316    pub fn padding(mut self, v: f32) -> Self {
317        self.padding = Some(v);
318        self
319    }
320    pub fn padding_values(mut self, padding: PaddingValues) -> Self {
321        self.padding_values = Some(padding);
322        self
323    }
324    pub fn min_size(mut self, w: f32, h: f32) -> Self {
325        self.min_width = Some(w);
326        self.min_height = Some(h);
327        self
328    }
329    pub fn max_size(mut self, w: f32, h: f32) -> Self {
330        self.max_width = Some(w);
331        self.max_height = Some(h);
332        self
333    }
334    pub fn min_width(mut self, w: f32) -> Self {
335        self.min_width = Some(w);
336        self
337    }
338    pub fn min_height(mut self, h: f32) -> Self {
339        self.min_height = Some(h);
340        self
341    }
342    pub fn max_width(mut self, w: f32) -> Self {
343        self.max_width = Some(w);
344        self
345    }
346    pub fn max_height(mut self, h: f32) -> Self {
347        self.max_height = Some(h);
348        self
349    }
350    /// Set a solid color background.
351    pub fn background(mut self, color: Color) -> Self {
352        self.background = Some(Brush::Solid(color));
353        self
354    }
355    /// Set a brush (solid, gradient, etc.) background.
356    pub fn background_brush(mut self, brush: Brush) -> Self {
357        self.background = Some(brush);
358        self
359    }
360    pub fn border(mut self, width: f32, color: Color, radius: f32) -> Self {
361        self.border = Some(Border {
362            width,
363            color,
364            radius,
365        });
366        self
367    }
368    pub fn flex_grow(mut self, v: f32) -> Self {
369        self.flex_grow = Some(v);
370        self
371    }
372    pub fn flex_shrink(mut self, v: f32) -> Self {
373        self.flex_shrink = Some(v);
374        self
375    }
376    pub fn flex_basis(mut self, v: f32) -> Self {
377        self.flex_basis = Some(v);
378        self
379    }
380    pub fn flex_wrap(mut self, w: FlexWrap) -> Self {
381        self.flex_wrap = Some(w);
382        self
383    }
384    pub fn flex_dir(mut self, d: FlexDirection) -> Self {
385        self.flex_dir = Some(d);
386        self
387    }
388    pub fn gap(mut self, v: f32) -> Self {
389        let v = v.max(0.0);
390        self.gap = Some(v);
391        self.row_gap = Some(v);
392        self.column_gap = Some(v);
393        self
394    }
395    pub fn row_gap(mut self, v: f32) -> Self {
396        self.row_gap = Some(v.max(0.0));
397        self
398    }
399    pub fn column_gap(mut self, v: f32) -> Self {
400        self.column_gap = Some(v.max(0.0));
401        self
402    }
403    pub fn align_self(mut self, a: AlignSelf) -> Self {
404        self.align_self = Some(a);
405        self
406    }
407    pub fn align_self_center(mut self) -> Self {
408        self.align_self = Some(AlignSelf::Center);
409        self
410    }
411    pub fn justify_content(mut self, j: JustifyContent) -> Self {
412        self.justify_content = Some(j);
413        self
414    }
415    pub fn align_items(mut self, a: AlignItems) -> Self {
416        self.align_items_container = Some(a);
417        self
418    }
419    pub fn align_content(mut self, a: AlignContent) -> Self {
420        self.align_content = Some(a);
421        self
422    }
423    pub fn clip_rounded(mut self, radius: f32) -> Self {
424        self.clip_rounded = Some(radius);
425        self
426    }
427    pub fn z_index(mut self, z: f32) -> Self {
428        self.z_index = z;
429        self
430    }
431
432    /// Sets the render z-index for this view. Higher values are painted on top.
433    /// Unlike `z_index` (which only affects hit-testing), this affects visual layering.
434    pub fn render_z_index(mut self, z: f32) -> Self {
435        self.render_z_index = Some(z);
436        self
437    }
438
439    /// Prevent pointer/touch from reaching lower layers.
440    pub fn input_blocker(mut self) -> Self {
441        self.input_blocker = true;
442        self
443    }
444
445    pub fn hit_passthrough(mut self) -> Self {
446        self.hit_passthrough = true;
447        self
448    }
449    pub fn clickable(mut self) -> Self {
450        self.click = true;
451        self
452    }
453    pub fn on_scroll(mut self, f: impl Fn(Vec2) -> Vec2 + 'static) -> Self {
454        self.on_scroll = Some(Rc::new(f));
455        self
456    }
457    pub fn on_pointer_down(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
458        self.on_pointer_down = Some(Rc::new(f));
459        self
460    }
461    pub fn on_pointer_move(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
462        self.on_pointer_move = Some(Rc::new(f));
463        self
464    }
465    pub fn on_pointer_up(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
466        self.on_pointer_up = Some(Rc::new(f));
467        self
468    }
469    pub fn on_pointer_enter(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
470        self.on_pointer_enter = Some(Rc::new(f));
471        self
472    }
473    pub fn on_pointer_leave(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
474        self.on_pointer_leave = Some(Rc::new(f));
475        self
476    }
477    pub fn semantics(mut self, s: crate::Semantics) -> Self {
478        self.semantics = Some(s);
479        self
480    }
481    pub fn alpha(mut self, a: f32) -> Self {
482        self.alpha = Some(a);
483        self
484    }
485    pub fn transform(mut self, t: Transform) -> Self {
486        self.transform = Some(t);
487        self
488    }
489    pub fn grid(mut self, columns: usize, row_gap: f32, column_gap: f32) -> Self {
490        self.grid = Some(GridConfig {
491            columns,
492            row_gap,
493            column_gap,
494        });
495        self
496    }
497    pub fn grid_span(mut self, col_span: u16, row_span: u16) -> Self {
498        self.grid_col_span = Some(col_span);
499        self.grid_row_span = Some(row_span);
500        self
501    }
502    pub fn absolute(mut self) -> Self {
503        self.position_type = Some(PositionType::Absolute);
504        self
505    }
506    pub fn offset(
507        mut self,
508        left: Option<f32>,
509        top: Option<f32>,
510        right: Option<f32>,
511        bottom: Option<f32>,
512    ) -> Self {
513        self.offset_left = left;
514        self.offset_top = top;
515        self.offset_right = right;
516        self.offset_bottom = bottom;
517        self
518    }
519    pub fn offset_left(mut self, v: f32) -> Self {
520        self.offset_left = Some(v);
521        self
522    }
523    pub fn offset_right(mut self, v: f32) -> Self {
524        self.offset_right = Some(v);
525        self
526    }
527    pub fn offset_top(mut self, v: f32) -> Self {
528        self.offset_top = Some(v);
529        self
530    }
531    pub fn offset_bottom(mut self, v: f32) -> Self {
532        self.offset_bottom = Some(v);
533        self
534    }
535
536    pub fn margin(mut self, v: f32) -> Self {
537        self.margin_left = Some(v);
538        self.margin_right = Some(v);
539        self.margin_top = Some(v);
540        self.margin_bottom = Some(v);
541        self
542    }
543
544    pub fn margin_horizontal(mut self, v: f32) -> Self {
545        self.margin_left = Some(v);
546        self.margin_right = Some(v);
547        self
548    }
549
550    pub fn margin_vertical(mut self, v: f32) -> Self {
551        self.margin_top = Some(v);
552        self.margin_bottom = Some(v);
553        self
554    }
555    pub fn aspect_ratio(mut self, ratio: f32) -> Self {
556        self.aspect_ratio = Some(ratio);
557        self
558    }
559    pub fn painter(mut self, f: impl Fn(&mut crate::Scene, crate::Rect) + 'static) -> Self {
560        self.painter = Some(Rc::new(f));
561        self
562    }
563    pub fn scale(self, s: f32) -> Self {
564        self.scale2(s, s)
565    }
566    pub fn scale2(mut self, sx: f32, sy: f32) -> Self {
567        let mut t = self.transform.unwrap_or_else(Transform::identity);
568        t.scale_x *= sx;
569        t.scale_y *= sy;
570        self.transform = Some(t);
571        self
572    }
573    pub fn translate(mut self, x: f32, y: f32) -> Self {
574        let t = self.transform.unwrap_or_else(Transform::identity);
575        self.transform = Some(t.combine(&Transform::translate(x, y)));
576        self
577    }
578    pub fn rotate(mut self, radians: f32) -> Self {
579        let mut t = self.transform.unwrap_or_else(Transform::identity);
580        t.rotate += radians;
581        self.transform = Some(t);
582        self
583    }
584    pub fn weight(mut self, w: f32) -> Self {
585        let w = w.max(0.0);
586        self.flex_grow = Some(w);
587        self.flex_shrink = Some(1.0);
588        // dp units; 0 is fine.
589        self.flex_basis = Some(0.0);
590        self
591    }
592    /// Marks this view as a repaint boundary candidate.
593    ///
594    /// The engine may cache its painted output.
595    pub fn repaint_boundary(mut self) -> Self {
596        self.repaint_boundary = true;
597        self
598    }
599    pub fn on_action(mut self, f: impl Fn(crate::shortcuts::Action) -> bool + 'static) -> Self {
600        self.on_action = Some(Rc::new(f));
601        self
602    }
603
604    /// Mark this node as a drag source. Return `Some(payload)` to start dragging.
605    pub fn on_drag_start(
606        mut self,
607        f: impl Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload> + 'static,
608    ) -> Self {
609        self.on_drag_start = Some(Rc::new(f));
610        self
611    }
612
613    /// Called when a drag ends (drop accepted or canceled/ignored).
614    pub fn on_drag_end(mut self, f: impl Fn(crate::dnd::DragEnd) + 'static) -> Self {
615        self.on_drag_end = Some(Rc::new(f));
616        self
617    }
618
619    /// Called when a drag first enters this target.
620    pub fn on_drag_enter(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
621        self.on_drag_enter = Some(Rc::new(f));
622        self
623    }
624
625    /// Called on every pointer move while a drag is over this target.
626    pub fn on_drag_over(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
627        self.on_drag_over = Some(Rc::new(f));
628        self
629    }
630
631    /// Called when a drag leaves this target.
632    pub fn on_drag_leave(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
633        self.on_drag_leave = Some(Rc::new(f));
634        self
635    }
636
637    /// Called on pointer release while a drag is over this target.
638    /// Return `true` to accept the drop.
639    pub fn on_drop(mut self, f: impl Fn(crate::dnd::DropEvent) -> bool + 'static) -> Self {
640        self.on_drop = Some(Rc::new(f));
641        self
642    }
643
644    /// Set the cursor icon hint for desktop/web runners.
645    pub fn cursor(mut self, c: crate::CursorIcon) -> Self {
646        self.cursor = Some(c);
647        self
648    }
649}