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