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