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        f.debug_struct("Modifier")
178            .field("key", &self.key)
179            .field("size", &self.size)
180            .field("width", &self.width)
181            .field("height", &self.height)
182            .field("fill_max", &self.fill_max)
183            .field("fill_max_w", &self.fill_max_w)
184            .field("fill_max_h", &self.fill_max_h)
185            .field("padding", &self.padding)
186            .field("padding_values", &self.padding_values)
187            .field("min_width", &self.min_width)
188            .field("min_height", &self.min_height)
189            .field("max_width", &self.max_width)
190            .field("max_height", &self.max_height)
191            .field("background", &self.background)
192            .field("border", &self.border)
193            .field("flex_grow", &self.flex_grow)
194            .field("flex_shrink", &self.flex_shrink)
195            .field("flex_basis", &self.flex_basis)
196            .field("gap", &self.gap)
197            .field("row_gap", &self.row_gap)
198            .field("column_gap", &self.column_gap)
199            .field("align_self", &self.align_self)
200            .field("justify_content", &self.justify_content)
201            .field("align_items_container", &self.align_items_container)
202            .field("align_content", &self.align_content)
203            .field("clip_rounded", &self.clip_rounded)
204            .field("z_index", &self.z_index)
205            .field("render_z_index", &self.render_z_index)
206            .field("hit_passthrough", &self.hit_passthrough)
207            .field("input_blocker", &self.input_blocker)
208            .field("repaint_boundary", &self.repaint_boundary)
209            .field("click", &self.click)
210            .field("on_scroll", &self.on_scroll.as_ref().map(|_| "..."))
211            .field(
212                "on_pointer_down",
213                &self.on_pointer_down.as_ref().map(|_| "..."),
214            )
215            .field(
216                "on_pointer_move",
217                &self.on_pointer_move.as_ref().map(|_| "..."),
218            )
219            .field("on_pointer_up", &self.on_pointer_up.as_ref().map(|_| "..."))
220            .field(
221                "on_pointer_enter",
222                &self.on_pointer_enter.as_ref().map(|_| "..."),
223            )
224            .field(
225                "on_pointer_leave",
226                &self.on_pointer_leave.as_ref().map(|_| "..."),
227            )
228            .field("semantics", &self.semantics)
229            .field("alpha", &self.alpha)
230            .field("transform", &self.transform)
231            .field("grid", &self.grid)
232            .field("grid_col_span", &self.grid_col_span)
233            .field("grid_row_span", &self.grid_row_span)
234            .field("position_type", &self.position_type)
235            .field("offset_left", &self.offset_left)
236            .field("offset_right", &self.offset_right)
237            .field("offset_top", &self.offset_top)
238            .field("offset_bottom", &self.offset_bottom)
239            .field("aspect_ratio", &self.aspect_ratio)
240            .field("painter", &self.painter.as_ref().map(|_| "..."))
241            .field("on_drag_start", &self.on_drag_start.as_ref().map(|_| "..."))
242            .field("on_drag_end", &self.on_drag_end.as_ref().map(|_| "..."))
243            .field("on_drag_enter", &self.on_drag_enter.as_ref().map(|_| "..."))
244            .field("on_drag_over", &self.on_drag_over.as_ref().map(|_| "..."))
245            .field("on_drag_leave", &self.on_drag_leave.as_ref().map(|_| "..."))
246            .field("on_drop", &self.on_drop.as_ref().map(|_| "..."))
247            .field("on_action", &self.on_action.as_ref().map(|_| "..."))
248            .finish()
249    }
250}
251
252impl_option_fields!(Modifier);
253
254impl Modifier {
255    pub fn new() -> Self {
256        Self::default()
257    }
258
259    /// Attaches a stable identity key to this view node.
260    /// Use for dynamic lists / conditional UI where index-based identity can shift.
261    pub fn key(mut self, key: u64) -> Self {
262        self.key = Some(key);
263        self
264    }
265
266    pub fn size(mut self, w: f32, h: f32) -> Self {
267        self.size = Some(Size {
268            width: w,
269            height: h,
270        });
271        self
272    }
273    pub fn width(mut self, w: f32) -> Self {
274        self.width = Some(w);
275        self
276    }
277    pub fn height(mut self, h: f32) -> Self {
278        self.height = Some(h);
279        self
280    }
281    pub fn fill_max_size(mut self) -> Self {
282        self.fill_max = true;
283        self
284    }
285    pub fn fill_max_width(mut self) -> Self {
286        self.fill_max_w = true;
287        self
288    }
289    pub fn fill_max_height(mut self) -> Self {
290        self.fill_max_h = true;
291        self
292    }
293    pub fn padding(mut self, v: f32) -> Self {
294        self.padding = Some(v);
295        self
296    }
297    pub fn padding_values(mut self, padding: PaddingValues) -> Self {
298        self.padding_values = Some(padding);
299        self
300    }
301    pub fn min_size(mut self, w: f32, h: f32) -> Self {
302        self.min_width = Some(w);
303        self.min_height = Some(h);
304        self
305    }
306    pub fn max_size(mut self, w: f32, h: f32) -> Self {
307        self.max_width = Some(w);
308        self.max_height = Some(h);
309        self
310    }
311    pub fn min_width(mut self, w: f32) -> Self {
312        self.min_width = Some(w);
313        self
314    }
315    pub fn min_height(mut self, h: f32) -> Self {
316        self.min_height = Some(h);
317        self
318    }
319    pub fn max_width(mut self, w: f32) -> Self {
320        self.max_width = Some(w);
321        self
322    }
323    pub fn max_height(mut self, h: f32) -> Self {
324        self.max_height = Some(h);
325        self
326    }
327    /// Set a solid color background.
328    pub fn background(mut self, color: Color) -> Self {
329        self.background = Some(Brush::Solid(color));
330        self
331    }
332    /// Set a brush (solid, gradient, etc.) background.
333    pub fn background_brush(mut self, brush: Brush) -> Self {
334        self.background = Some(brush);
335        self
336    }
337    pub fn border(mut self, width: f32, color: Color, radius: f32) -> Self {
338        self.border = Some(Border {
339            width,
340            color,
341            radius,
342        });
343        self
344    }
345    pub fn flex_grow(mut self, v: f32) -> Self {
346        self.flex_grow = Some(v);
347        self
348    }
349    pub fn flex_shrink(mut self, v: f32) -> Self {
350        self.flex_shrink = Some(v);
351        self
352    }
353    pub fn flex_basis(mut self, v: f32) -> Self {
354        self.flex_basis = Some(v);
355        self
356    }
357    pub fn flex_wrap(mut self, w: FlexWrap) -> Self {
358        self.flex_wrap = Some(w);
359        self
360    }
361    pub fn flex_dir(mut self, d: FlexDirection) -> Self {
362        self.flex_dir = Some(d);
363        self
364    }
365    pub fn gap(mut self, v: f32) -> Self {
366        let v = v.max(0.0);
367        self.gap = Some(v);
368        self.row_gap = Some(v);
369        self.column_gap = Some(v);
370        self
371    }
372    pub fn row_gap(mut self, v: f32) -> Self {
373        self.row_gap = Some(v.max(0.0));
374        self
375    }
376    pub fn column_gap(mut self, v: f32) -> Self {
377        self.column_gap = Some(v.max(0.0));
378        self
379    }
380    pub fn align_self(mut self, a: AlignSelf) -> Self {
381        self.align_self = Some(a);
382        self
383    }
384    pub fn align_self_center(mut self) -> Self {
385        self.align_self = Some(AlignSelf::Center);
386        self
387    }
388    pub fn justify_content(mut self, j: JustifyContent) -> Self {
389        self.justify_content = Some(j);
390        self
391    }
392    pub fn align_items(mut self, a: AlignItems) -> Self {
393        self.align_items_container = Some(a);
394        self
395    }
396    pub fn align_content(mut self, a: AlignContent) -> Self {
397        self.align_content = Some(a);
398        self
399    }
400    pub fn clip_rounded(mut self, radius: f32) -> Self {
401        self.clip_rounded = Some(radius);
402        self
403    }
404    pub fn z_index(mut self, z: f32) -> Self {
405        self.z_index = z;
406        self
407    }
408
409    /// Sets the render z-index for this view. Higher values are painted on top.
410    /// Unlike `z_index` (which only affects hit-testing), this affects visual layering.
411    pub fn render_z_index(mut self, z: f32) -> Self {
412        self.render_z_index = Some(z);
413        self
414    }
415
416    /// Prevent pointer/touch from reaching lower layers.
417    pub fn input_blocker(mut self) -> Self {
418        self.input_blocker = true;
419        self
420    }
421
422    pub fn hit_passthrough(mut self) -> Self {
423        self.hit_passthrough = true;
424        self
425    }
426    pub fn clickable(mut self) -> Self {
427        self.click = true;
428        self
429    }
430    pub fn on_scroll(mut self, f: impl Fn(Vec2) -> Vec2 + 'static) -> Self {
431        self.on_scroll = Some(Rc::new(f));
432        self
433    }
434    pub fn on_pointer_down(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
435        self.on_pointer_down = Some(Rc::new(f));
436        self
437    }
438    pub fn on_pointer_move(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
439        self.on_pointer_move = Some(Rc::new(f));
440        self
441    }
442    pub fn on_pointer_up(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
443        self.on_pointer_up = Some(Rc::new(f));
444        self
445    }
446    pub fn on_pointer_enter(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
447        self.on_pointer_enter = Some(Rc::new(f));
448        self
449    }
450    pub fn on_pointer_leave(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
451        self.on_pointer_leave = Some(Rc::new(f));
452        self
453    }
454    pub fn semantics(mut self, s: crate::Semantics) -> Self {
455        self.semantics = Some(s);
456        self
457    }
458    pub fn alpha(mut self, a: f32) -> Self {
459        self.alpha = Some(a);
460        self
461    }
462    pub fn transform(mut self, t: Transform) -> Self {
463        self.transform = Some(t);
464        self
465    }
466    pub fn grid(mut self, columns: usize, row_gap: f32, column_gap: f32) -> Self {
467        self.grid = Some(GridConfig {
468            columns,
469            row_gap,
470            column_gap,
471        });
472        self
473    }
474    pub fn grid_span(mut self, col_span: u16, row_span: u16) -> Self {
475        self.grid_col_span = Some(col_span);
476        self.grid_row_span = Some(row_span);
477        self
478    }
479    pub fn absolute(mut self) -> Self {
480        self.position_type = Some(PositionType::Absolute);
481        self
482    }
483    pub fn offset(
484        mut self,
485        left: Option<f32>,
486        top: Option<f32>,
487        right: Option<f32>,
488        bottom: Option<f32>,
489    ) -> Self {
490        self.offset_left = left;
491        self.offset_top = top;
492        self.offset_right = right;
493        self.offset_bottom = bottom;
494        self
495    }
496    pub fn offset_left(mut self, v: f32) -> Self {
497        self.offset_left = Some(v);
498        self
499    }
500    pub fn offset_right(mut self, v: f32) -> Self {
501        self.offset_right = Some(v);
502        self
503    }
504    pub fn offset_top(mut self, v: f32) -> Self {
505        self.offset_top = Some(v);
506        self
507    }
508    pub fn offset_bottom(mut self, v: f32) -> Self {
509        self.offset_bottom = Some(v);
510        self
511    }
512
513    pub fn margin(mut self, v: f32) -> Self {
514        self.margin_left = Some(v);
515        self.margin_right = Some(v);
516        self.margin_top = Some(v);
517        self.margin_bottom = Some(v);
518        self
519    }
520
521    pub fn margin_horizontal(mut self, v: f32) -> Self {
522        self.margin_left = Some(v);
523        self.margin_right = Some(v);
524        self
525    }
526
527    pub fn margin_vertical(mut self, v: f32) -> Self {
528        self.margin_top = Some(v);
529        self.margin_bottom = Some(v);
530        self
531    }
532    pub fn aspect_ratio(mut self, ratio: f32) -> Self {
533        self.aspect_ratio = Some(ratio);
534        self
535    }
536    pub fn painter(mut self, f: impl Fn(&mut crate::Scene, crate::Rect) + 'static) -> Self {
537        self.painter = Some(Rc::new(f));
538        self
539    }
540    pub fn scale(self, s: f32) -> Self {
541        self.scale2(s, s)
542    }
543    pub fn scale2(mut self, sx: f32, sy: f32) -> Self {
544        let mut t = self.transform.unwrap_or_else(Transform::identity);
545        t.scale_x *= sx;
546        t.scale_y *= sy;
547        self.transform = Some(t);
548        self
549    }
550    pub fn translate(mut self, x: f32, y: f32) -> Self {
551        let t = self.transform.unwrap_or_else(Transform::identity);
552        self.transform = Some(t.combine(&Transform::translate(x, y)));
553        self
554    }
555    pub fn rotate(mut self, radians: f32) -> Self {
556        let mut t = self.transform.unwrap_or_else(Transform::identity);
557        t.rotate += radians;
558        self.transform = Some(t);
559        self
560    }
561    pub fn weight(mut self, w: f32) -> Self {
562        let w = w.max(0.0);
563        self.flex_grow = Some(w);
564        self.flex_shrink = Some(1.0);
565        // dp units; 0 is fine.
566        self.flex_basis = Some(0.0);
567        self
568    }
569    /// Marks this view as a repaint boundary candidate.
570    ///
571    /// The engine may cache its painted output.
572    pub fn repaint_boundary(mut self) -> Self {
573        self.repaint_boundary = true;
574        self
575    }
576    pub fn on_action(mut self, f: impl Fn(crate::shortcuts::Action) -> bool + 'static) -> Self {
577        self.on_action = Some(Rc::new(f));
578        self
579    }
580
581    /// Mark this node as a drag source. Return `Some(payload)` to start dragging.
582    pub fn on_drag_start(
583        mut self,
584        f: impl Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload> + 'static,
585    ) -> Self {
586        self.on_drag_start = Some(Rc::new(f));
587        self
588    }
589
590    /// Called when a drag ends (drop accepted or canceled/ignored).
591    pub fn on_drag_end(mut self, f: impl Fn(crate::dnd::DragEnd) + 'static) -> Self {
592        self.on_drag_end = Some(Rc::new(f));
593        self
594    }
595
596    /// Called when a drag first enters this target.
597    pub fn on_drag_enter(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
598        self.on_drag_enter = Some(Rc::new(f));
599        self
600    }
601
602    /// Called on every pointer move while a drag is over this target.
603    pub fn on_drag_over(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
604        self.on_drag_over = Some(Rc::new(f));
605        self
606    }
607
608    /// Called when a drag leaves this target.
609    pub fn on_drag_leave(mut self, f: impl Fn(crate::dnd::DragOver) + 'static) -> Self {
610        self.on_drag_leave = Some(Rc::new(f));
611        self
612    }
613
614    /// Called on pointer release while a drag is over this target.
615    /// Return `true` to accept the drop.
616    pub fn on_drop(mut self, f: impl Fn(crate::dnd::DropEvent) -> bool + 'static) -> Self {
617        self.on_drop = Some(Rc::new(f));
618        self
619    }
620
621    /// Set the cursor icon hint for desktop/web runners.
622    pub fn cursor(mut self, c: crate::CursorIcon) -> Self {
623        self.cursor = Some(c);
624        self
625    }
626}