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    /// Works for hit-testing only, draw order is not changed.
69    pub z_index: f32,
70    pub click: bool,
71    pub on_scroll: Option<Rc<dyn Fn(Vec2) -> Vec2>>,
72    pub on_pointer_down: Option<Rc<dyn Fn(PointerEvent)>>,
73    pub on_pointer_move: Option<Rc<dyn Fn(PointerEvent)>>,
74    pub on_pointer_up: Option<Rc<dyn Fn(PointerEvent)>>,
75    pub on_pointer_enter: Option<Rc<dyn Fn(PointerEvent)>>,
76    pub on_pointer_leave: Option<Rc<dyn Fn(PointerEvent)>>,
77    pub semantics: Option<crate::Semantics>,
78    pub alpha: Option<f32>,
79    pub transform: Option<Transform>,
80    pub grid: Option<GridConfig>,
81    pub grid_col_span: Option<u16>,
82    pub grid_row_span: Option<u16>,
83    pub position_type: Option<PositionType>,
84    pub offset_left: Option<f32>,
85    pub offset_right: Option<f32>,
86    pub offset_top: Option<f32>,
87    pub offset_bottom: Option<f32>,
88    pub margin_left: Option<f32>,
89    pub margin_right: Option<f32>,
90    pub margin_top: Option<f32>,
91    pub margin_bottom: Option<f32>,
92    pub aspect_ratio: Option<f32>,
93    pub painter: Option<Rc<dyn Fn(&mut crate::Scene, crate::Rect)>>,
94}
95
96impl std::fmt::Debug for Modifier {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        f.debug_struct("Modifier")
99            .field("key", &self.key)
100            .field("size", &self.size)
101            .field("width", &self.width)
102            .field("height", &self.height)
103            .field("fill_max", &self.fill_max)
104            .field("fill_max_w", &self.fill_max_w)
105            .field("fill_max_h", &self.fill_max_h)
106            .field("padding", &self.padding)
107            .field("padding_values", &self.padding_values)
108            .field("min_width", &self.min_width)
109            .field("min_height", &self.min_height)
110            .field("max_width", &self.max_width)
111            .field("max_height", &self.max_height)
112            .field("background", &self.background)
113            .field("border", &self.border)
114            .field("flex_grow", &self.flex_grow)
115            .field("flex_shrink", &self.flex_shrink)
116            .field("flex_basis", &self.flex_basis)
117            .field("align_self", &self.align_self)
118            .field("justify_content", &self.justify_content)
119            .field("align_items_container", &self.align_items_container)
120            .field("align_content", &self.align_content)
121            .field("clip_rounded", &self.clip_rounded)
122            .field("z_index", &self.z_index)
123            .field("click", &self.click)
124            .field("on_scroll", &self.on_scroll.as_ref().map(|_| "..."))
125            .field(
126                "on_pointer_down",
127                &self.on_pointer_down.as_ref().map(|_| "..."),
128            )
129            .field(
130                "on_pointer_move",
131                &self.on_pointer_move.as_ref().map(|_| "..."),
132            )
133            .field("on_pointer_up", &self.on_pointer_up.as_ref().map(|_| "..."))
134            .field(
135                "on_pointer_enter",
136                &self.on_pointer_enter.as_ref().map(|_| "..."),
137            )
138            .field(
139                "on_pointer_leave",
140                &self.on_pointer_leave.as_ref().map(|_| "..."),
141            )
142            .field("semantics", &self.semantics)
143            .field("alpha", &self.alpha)
144            .field("transform", &self.transform)
145            .field("grid", &self.grid)
146            .field("grid_col_span", &self.grid_col_span)
147            .field("grid_row_span", &self.grid_row_span)
148            .field("position_type", &self.position_type)
149            .field("offset_left", &self.offset_left)
150            .field("offset_right", &self.offset_right)
151            .field("offset_top", &self.offset_top)
152            .field("offset_bottom", &self.offset_bottom)
153            .field("aspect_ratio", &self.aspect_ratio)
154            .field("painter", &self.painter.as_ref().map(|_| "..."))
155            .finish()
156    }
157}
158
159impl Modifier {
160    pub fn new() -> Self {
161        Self::default()
162    }
163
164    /// Attaches a stable identity key to this view node.
165    /// Use for dynamic lists / conditional UI where index-based identity can shift.
166    pub fn key(mut self, key: u64) -> Self {
167        self.key = Some(key);
168        self
169    }
170
171    pub fn size(mut self, w: f32, h: f32) -> Self {
172        self.size = Some(Size {
173            width: w,
174            height: h,
175        });
176        self
177    }
178    pub fn width(mut self, w: f32) -> Self {
179        self.width = Some(w);
180        self
181    }
182    pub fn height(mut self, h: f32) -> Self {
183        self.height = Some(h);
184        self
185    }
186    pub fn fill_max_size(mut self) -> Self {
187        self.fill_max = true;
188        self
189    }
190    pub fn fill_max_width(mut self) -> Self {
191        self.fill_max_w = true;
192        self
193    }
194    pub fn fill_max_height(mut self) -> Self {
195        self.fill_max_h = true;
196        self
197    }
198    pub fn padding(mut self, v: f32) -> Self {
199        self.padding = Some(v);
200        self
201    }
202    pub fn padding_values(mut self, padding: PaddingValues) -> Self {
203        self.padding_values = Some(padding);
204        self
205    }
206    pub fn min_size(mut self, w: f32, h: f32) -> Self {
207        self.min_width = Some(w);
208        self.min_height = Some(h);
209        self
210    }
211    pub fn max_size(mut self, w: f32, h: f32) -> Self {
212        self.max_width = Some(w);
213        self.max_height = Some(h);
214        self
215    }
216    pub fn min_width(mut self, w: f32) -> Self {
217        self.min_width = Some(w);
218        self
219    }
220    pub fn min_height(mut self, h: f32) -> Self {
221        self.min_height = Some(h);
222        self
223    }
224    pub fn max_width(mut self, w: f32) -> Self {
225        self.max_width = Some(w);
226        self
227    }
228    pub fn max_height(mut self, h: f32) -> Self {
229        self.max_height = Some(h);
230        self
231    }
232    /// Set a solid color background.
233    pub fn background(mut self, color: Color) -> Self {
234        self.background = Some(Brush::Solid(color));
235        self
236    }
237    /// Set a brush (solid, gradient, etc.) background.
238    pub fn background_brush(mut self, brush: Brush) -> Self {
239        self.background = Some(brush);
240        self
241    }
242    pub fn border(mut self, width: f32, color: Color, radius: f32) -> Self {
243        self.border = Some(Border {
244            width,
245            color,
246            radius,
247        });
248        self
249    }
250    pub fn flex_grow(mut self, v: f32) -> Self {
251        self.flex_grow = Some(v);
252        self
253    }
254    pub fn flex_shrink(mut self, v: f32) -> Self {
255        self.flex_shrink = Some(v);
256        self
257    }
258    pub fn flex_basis(mut self, v: f32) -> Self {
259        self.flex_basis = Some(v);
260        self
261    }
262    pub fn flex_wrap(mut self, w: FlexWrap) -> Self {
263        self.flex_wrap = Some(w);
264        self
265    }
266    pub fn flex_dir(mut self, d: FlexDirection) -> Self {
267        self.flex_dir = Some(d);
268        self
269    }
270    pub fn align_self(mut self, a: AlignSelf) -> Self {
271        self.align_self = Some(a);
272        self
273    }
274    pub fn align_self_center(mut self) -> Self {
275        self.align_self = Some(AlignSelf::Center);
276        self
277    }
278    pub fn justify_content(mut self, j: JustifyContent) -> Self {
279        self.justify_content = Some(j);
280        self
281    }
282    pub fn align_items(mut self, a: AlignItems) -> Self {
283        self.align_items_container = Some(a);
284        self
285    }
286    pub fn align_content(mut self, a: AlignContent) -> Self {
287        self.align_content = Some(a);
288        self
289    }
290    pub fn clip_rounded(mut self, radius: f32) -> Self {
291        self.clip_rounded = Some(radius);
292        self
293    }
294    pub fn z_index(mut self, z: f32) -> Self {
295        self.z_index = z;
296        self
297    }
298    pub fn clickable(mut self) -> Self {
299        self.click = true;
300        self
301    }
302    pub fn on_scroll(mut self, f: impl Fn(Vec2) -> Vec2 + 'static) -> Self {
303        self.on_scroll = Some(Rc::new(f));
304        self
305    }
306    pub fn on_pointer_down(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
307        self.on_pointer_down = Some(Rc::new(f));
308        self
309    }
310    pub fn on_pointer_move(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
311        self.on_pointer_move = Some(Rc::new(f));
312        self
313    }
314    pub fn on_pointer_up(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
315        self.on_pointer_up = Some(Rc::new(f));
316        self
317    }
318    pub fn on_pointer_enter(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
319        self.on_pointer_enter = Some(Rc::new(f));
320        self
321    }
322    pub fn on_pointer_leave(mut self, f: impl Fn(PointerEvent) + 'static) -> Self {
323        self.on_pointer_leave = Some(Rc::new(f));
324        self
325    }
326    pub fn semantics(mut self, s: crate::Semantics) -> Self {
327        self.semantics = Some(s);
328        self
329    }
330    pub fn alpha(mut self, a: f32) -> Self {
331        self.alpha = Some(a);
332        self
333    }
334    pub fn transform(mut self, t: Transform) -> Self {
335        self.transform = Some(t);
336        self
337    }
338    pub fn grid(mut self, columns: usize, row_gap: f32, column_gap: f32) -> Self {
339        self.grid = Some(GridConfig {
340            columns,
341            row_gap,
342            column_gap,
343        });
344        self
345    }
346    pub fn grid_span(mut self, col_span: u16, row_span: u16) -> Self {
347        self.grid_col_span = Some(col_span);
348        self.grid_row_span = Some(row_span);
349        self
350    }
351    pub fn absolute(mut self) -> Self {
352        self.position_type = Some(PositionType::Absolute);
353        self
354    }
355    pub fn offset(
356        mut self,
357        left: Option<f32>,
358        top: Option<f32>,
359        right: Option<f32>,
360        bottom: Option<f32>,
361    ) -> Self {
362        self.offset_left = left;
363        self.offset_top = top;
364        self.offset_right = right;
365        self.offset_bottom = bottom;
366        self
367    }
368    pub fn offset_left(mut self, v: f32) -> Self {
369        self.offset_left = Some(v);
370        self
371    }
372    pub fn offset_right(mut self, v: f32) -> Self {
373        self.offset_right = Some(v);
374        self
375    }
376    pub fn offset_top(mut self, v: f32) -> Self {
377        self.offset_top = Some(v);
378        self
379    }
380    pub fn offset_bottom(mut self, v: f32) -> Self {
381        self.offset_bottom = Some(v);
382        self
383    }
384
385    pub fn margin(mut self, v: f32) -> Self {
386        self.margin_left = Some(v);
387        self.margin_right = Some(v);
388        self.margin_top = Some(v);
389        self.margin_bottom = Some(v);
390        self
391    }
392
393    pub fn margin_horizontal(mut self, v: f32) -> Self {
394        self.margin_left = Some(v);
395        self.margin_right = Some(v);
396        self
397    }
398
399    pub fn margin_vertical(mut self, v: f32) -> Self {
400        self.margin_top = Some(v);
401        self.margin_bottom = Some(v);
402        self
403    }
404    pub fn aspect_ratio(mut self, ratio: f32) -> Self {
405        self.aspect_ratio = Some(ratio);
406        self
407    }
408    pub fn painter(mut self, f: impl Fn(&mut crate::Scene, crate::Rect) + 'static) -> Self {
409        self.painter = Some(Rc::new(f));
410        self
411    }
412    pub fn scale(self, s: f32) -> Self {
413        self.scale2(s, s)
414    }
415    pub fn scale2(mut self, sx: f32, sy: f32) -> Self {
416        let mut t = self.transform.unwrap_or_else(Transform::identity);
417        t.scale_x *= sx;
418        t.scale_y *= sy;
419        self.transform = Some(t);
420        self
421    }
422    pub fn translate(mut self, x: f32, y: f32) -> Self {
423        let t = self.transform.unwrap_or_else(Transform::identity);
424        self.transform = Some(t.combine(&Transform::translate(x, y)));
425        self
426    }
427    pub fn rotate(mut self, radians: f32) -> Self {
428        let mut t = self.transform.unwrap_or_else(Transform::identity);
429        t.rotate += radians;
430        self.transform = Some(t);
431        self
432    }
433    pub fn weight(mut self, w: f32) -> Self {
434        let w = w.max(0.0);
435        self.flex_grow = Some(w);
436        self.flex_shrink = Some(1.0);
437        // dp units; 0 is fine.
438        self.flex_basis = Some(0.0);
439        self
440    }
441}