Skip to main content

sequoia_elements/
container.rs

1use std::cell::Cell;
2use std::rc::Rc;
3
4use sequoia_core::{Color, Edges, Length};
5use sequoia_input::{CursorIcon, Event, EventPhase, Key, PointerButton, PointerEvent};
6use sequoia_layout::{Align, Inset, LayoutStyle, Overflow, Position};
7use sequoia_reactive::{Environment, Signal};
8use sequoia_style::{BoxStyle, Theme, VisualState};
9use sequoia_view::{AnyView, EventCtx, PaintCtx, Prop, Unit, View};
10
11type EnvAction = Rc<dyn Fn(&Environment)>;
12
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
14#[non_exhaustive]
15pub enum ClickTrigger {
16    #[default]
17    Release,
18
19    Press,
20}
21
22#[derive(Clone)]
23pub struct Container {
24    child: Option<AnyView>,
25    deco: BoxStyle,
26    style: LayoutStyle,
27    clip: bool,
28    on_click: Option<Rc<dyn Fn()>>,
29    on_click_env: Option<EnvAction>,
30    trigger: ClickTrigger,
31    disabled: bool,
32    hover: Option<Color>,
33    pressed: Option<Color>,
34    disabled_background: Option<Color>,
35
36    blend_rate: f32,
37    hovered: Option<Signal<bool>>,
38    pressed_flag: Rc<Cell<bool>>,
39}
40
41impl Default for Container {
42    fn default() -> Self {
43        Self {
44            child: None,
45            deco: BoxStyle::new(),
46            clip: false,
47            style: LayoutStyle {
48                width: Length::Fit,
49                height: Length::Fit,
50                padding: Edges::ZERO,
51                ..LayoutStyle::default()
52            },
53            on_click: None,
54            on_click_env: None,
55            trigger: ClickTrigger::default(),
56            disabled: false,
57            hover: None,
58            pressed: None,
59            disabled_background: None,
60            blend_rate: 0.0,
61            hovered: None,
62            pressed_flag: Rc::new(Cell::new(false)),
63        }
64    }
65}
66
67impl Container {
68    pub fn new() -> Self {
69        Self::default()
70    }
71
72    pub fn fill() -> Self {
73        Self {
74            child: None,
75            deco: BoxStyle::new(),
76            clip: false,
77            style: LayoutStyle {
78                width: Length::Fill(1.0),
79                height: Length::Fill(1.0),
80                padding: Edges::ZERO,
81                ..LayoutStyle::default()
82            },
83            on_click: None,
84            on_click_env: None,
85            trigger: ClickTrigger::default(),
86            disabled: false,
87            hover: None,
88            pressed: None,
89            disabled_background: None,
90            blend_rate: 0.0,
91            hovered: None,
92            pressed_flag: Rc::new(Cell::new(false)),
93        }
94    }
95
96    pub fn hug() -> Self {
97        Self::new()
98    }
99
100    pub fn child<V: View + 'static>(mut self, child: V) -> Self {
101        debug_assert!(
102            self.child.is_none(),
103            "Container takes a single child - a second `.child()` would overwrite the first. Use Row/Column for many."
104        );
105        self.child = Some(AnyView::new(child));
106        self
107    }
108
109    pub fn content<V: View + 'static>(self, child: V) -> Self {
110        self.child(child)
111    }
112
113    pub fn child_if<V: View + 'static>(mut self, cond: bool, make: impl FnOnce() -> V) -> Self {
114        if cond {
115            self.child = Some(AnyView::new(make()));
116        }
117        self
118    }
119
120    pub fn child_opt<V: View + 'static>(mut self, child: Option<V>) -> Self {
121        if let Some(v) = child {
122            self.child = Some(AnyView::new(v));
123        }
124        self
125    }
126
127    pub fn background(mut self, color: Color) -> Self {
128        self.deco = self.deco.background(color);
129        self
130    }
131
132    pub fn border(mut self, width: f32, color: Color) -> Self {
133        self.deco = self.deco.border(width, color);
134        self
135    }
136
137    pub fn border_left(mut self, width: f32, color: Color) -> Self {
138        self.deco = self.deco.border_left(width, color);
139        self
140    }
141
142    pub fn border_right(mut self, width: f32, color: Color) -> Self {
143        self.deco = self.deco.border_right(width, color);
144        self
145    }
146
147    pub fn border_top(mut self, width: f32, color: Color) -> Self {
148        self.deco = self.deco.border_top(width, color);
149        self
150    }
151
152    pub fn border_bottom(mut self, width: f32, color: Color) -> Self {
153        self.deco = self.deco.border_bottom(width, color);
154        self
155    }
156
157    pub fn border_x(mut self, width: f32, color: Color) -> Self {
158        self.deco = self.deco.border_x(width, color);
159        self
160    }
161
162    pub fn border_y(mut self, width: f32, color: Color) -> Self {
163        self.deco = self.deco.border_y(width, color);
164        self
165    }
166
167    pub fn box_style(mut self, style: BoxStyle) -> Self {
168        self.deco = style;
169        self
170    }
171
172    pub fn style(self, style: BoxStyle) -> Self {
173        self.box_style(style)
174    }
175
176    pub fn radius(mut self, radius: f32) -> Self {
177        self.deco = self.deco.radius(radius);
178        self
179    }
180
181    pub fn padding(mut self, padding: impl Into<Edges>) -> Self {
182        self.style = self.style.padding(padding);
183        self
184    }
185
186    pub fn pad(self, padding: impl Into<Edges>) -> Self {
187        self.padding(padding)
188    }
189
190    pub fn padding_xy(mut self, x: f32, y: f32) -> Self {
191        self.style = self.style.padding_xy(x, y);
192        self
193    }
194
195    pub fn padding_x(mut self, x: f32) -> Self {
196        self.style = self.style.padding_x(x);
197        self
198    }
199
200    pub fn padding_y(mut self, y: f32) -> Self {
201        self.style = self.style.padding_y(y);
202        self
203    }
204
205    pub fn padding_left(mut self, v: f32) -> Self {
206        self.style = self.style.padding_left(v);
207        self
208    }
209
210    pub fn padding_right(mut self, v: f32) -> Self {
211        self.style = self.style.padding_right(v);
212        self
213    }
214
215    pub fn padding_top(mut self, v: f32) -> Self {
216        self.style = self.style.padding_top(v);
217        self
218    }
219
220    pub fn padding_bottom(mut self, v: f32) -> Self {
221        self.style = self.style.padding_bottom(v);
222        self
223    }
224
225    pub fn width(mut self, width: impl Into<Length>) -> Self {
226        self.style = self.style.width(width.into());
227        self
228    }
229
230    pub fn height(mut self, height: impl Into<Length>) -> Self {
231        self.style = self.style.height(height.into());
232        self
233    }
234
235    pub fn size(mut self, width: impl Into<Length>, height: impl Into<Length>) -> Self {
236        self.style = self.style.width(width.into()).height(height.into());
237        self
238    }
239
240    pub fn square(mut self, side: impl Into<Length>) -> Self {
241        let side = side.into();
242        self.style = self.style.width(side).height(side);
243        self
244    }
245
246    pub fn min_width(mut self, v: f32) -> Self {
247        self.style = self.style.min_width(v);
248        self
249    }
250
251    pub fn max_width(mut self, v: f32) -> Self {
252        self.style = self.style.max_width(v);
253        self
254    }
255
256    pub fn min_height(mut self, v: f32) -> Self {
257        self.style = self.style.min_height(v);
258        self
259    }
260
261    pub fn max_height(mut self, v: f32) -> Self {
262        self.style = self.style.max_height(v);
263        self
264    }
265
266    pub fn overflow(mut self, overflow: Overflow) -> Self {
267        self.style = self.style.overflow(overflow);
268        self
269    }
270
271    pub fn margin(mut self, margin: impl Into<Edges>) -> Self {
272        self.style = self.style.margin(margin);
273        self
274    }
275
276    pub fn margin_xy(mut self, x: f32, y: f32) -> Self {
277        self.style = self.style.margin_xy(x, y);
278        self
279    }
280
281    pub fn margin_x(mut self, x: f32) -> Self {
282        self.style = self.style.margin_x(x);
283        self
284    }
285
286    pub fn margin_y(mut self, y: f32) -> Self {
287        self.style = self.style.margin_y(y);
288        self
289    }
290
291    pub fn margin_left(mut self, v: f32) -> Self {
292        self.style = self.style.margin_left(v);
293        self
294    }
295
296    pub fn margin_right(mut self, v: f32) -> Self {
297        self.style = self.style.margin_right(v);
298        self
299    }
300
301    pub fn margin_top(mut self, v: f32) -> Self {
302        self.style = self.style.margin_top(v);
303        self
304    }
305
306    pub fn margin_bottom(mut self, v: f32) -> Self {
307        self.style = self.style.margin_bottom(v);
308        self
309    }
310
311    pub fn flex(mut self, weight: f32) -> Self {
312        self.style = self.style.flex(weight);
313        self
314    }
315
316    pub fn flex_shrink(mut self, shrink: f32) -> Self {
317        self.style = self.style.flex_shrink(shrink);
318        self
319    }
320
321    pub fn flex_basis(mut self, basis: Option<Length>) -> Self {
322        self.style = self.style.flex_basis(basis);
323        self
324    }
325
326    pub fn align_self(mut self, align: Option<Align>) -> Self {
327        self.style = self.style.align_self(align);
328        self
329    }
330
331    pub fn aspect(mut self, ratio: f32) -> Self {
332        self.style = self.style.aspect(ratio);
333        self
334    }
335
336    pub fn position(mut self, position: Position) -> Self {
337        self.style = self.style.position(position);
338        self
339    }
340
341    pub fn absolute(mut self) -> Self {
342        self.style = self.style.absolute();
343        self
344    }
345
346    pub fn absolute_at(mut self, left: f32, top: f32) -> Self {
347        self.style = self.style.absolute_at(left, top);
348        self
349    }
350
351    pub fn absolute_inset(mut self, inset: Inset) -> Self {
352        self.style = self.style.absolute_inset(inset);
353        self
354    }
355
356    pub fn expand(self) -> Self {
357        self.flex(1.0)
358    }
359
360    pub fn fill_width(mut self) -> Self {
361        self.style = self.style.width(Length::Fill(1.0));
362        self
363    }
364
365    pub fn fill_height(mut self) -> Self {
366        self.style = self.style.height(Length::Fill(1.0));
367        self
368    }
369
370    pub fn fill_size(self) -> Self {
371        self.fill_width().fill_height()
372    }
373
374    pub fn fit(mut self) -> Self {
375        self.style = self.style.fit();
376        self
377    }
378
379    pub fn hit_testable(mut self, on: bool) -> Self {
380        self.style = self.style.hit_testable(on);
381        self
382    }
383
384    pub fn clip(mut self, clip: bool) -> Self {
385        self.clip = clip;
386        self
387    }
388
389    pub fn clip_children(self) -> Self {
390        self.clip(true)
391    }
392
393    fn is_interactive(&self) -> bool {
394        self.on_click.is_some() || self.on_click_env.is_some()
395    }
396
397    pub fn on_click(mut self, on_click: impl Fn() + 'static) -> Self {
398        self.on_click = Some(Rc::new(on_click));
399        self
400    }
401
402    pub fn on_press(mut self, on_press: impl Fn() + 'static) -> Self {
403        self.on_click = Some(Rc::new(on_press));
404        self.trigger = ClickTrigger::Press;
405        self
406    }
407
408    pub fn trigger(mut self, trigger: ClickTrigger) -> Self {
409        self.trigger = trigger;
410        self
411    }
412
413    pub fn on_click_env(mut self, on_click: impl Fn(&Environment) + 'static) -> Self {
414        self.on_click_env = Some(Rc::new(on_click));
415        self
416    }
417
418    pub fn hovered(mut self, signal: Signal<bool>) -> Self {
419        self.hovered = Some(signal);
420        self
421    }
422
423    pub fn on_hover(mut self, hovered: Signal<bool>) -> Self {
424        self.hovered = Some(hovered);
425        self
426    }
427
428    pub fn disabled(mut self, disabled: bool) -> Self {
429        self.disabled = disabled;
430        self
431    }
432
433    pub fn hover(mut self, color: Color) -> Self {
434        self.hover = Some(color);
435        self
436    }
437
438    pub fn pressed(mut self, color: Color) -> Self {
439        self.pressed = Some(color);
440        self
441    }
442
443    pub fn disabled_background(mut self, color: Color) -> Self {
444        self.disabled_background = Some(color);
445        self
446    }
447
448    pub fn blend_rate(mut self, rate: f32) -> Self {
449        self.blend_rate = rate;
450        self
451    }
452
453    #[cfg(test)]
454    pub(crate) fn effective_blend_rate(&self) -> f32 {
455        self.blend_rate
456    }
457
458    pub fn target_background(&self, theme: &Theme, state: VisualState) -> Color {
459        let mut state = state;
460        if self.disabled {
461            state.disabled = true;
462        }
463        let idle = self.deco.background;
464        if state.disabled {
465            self.disabled_background.unwrap_or(theme.disabled)
466        } else if state.pressed {
467            self.pressed.unwrap_or(idle)
468        } else if state.hovered {
469            self.hover.unwrap_or(idle)
470        } else {
471            idle
472        }
473    }
474
475    fn fire(&self, env: &Environment) {
476        if let Some(f) = &self.on_click {
477            f();
478        }
479        if let Some(f) = &self.on_click_env {
480            f(env);
481        }
482    }
483}
484
485impl View for Container {
486    fn body(&self) -> impl View + 'static {
487        Unit
488    }
489    fn clip_self(&self) -> bool {
490        self.clip
491    }
492    fn clip_radius(&self) -> f32 {
493        if self.clip { self.deco.radius } else { 0.0 }
494    }
495    fn children_override(&self) -> Option<Vec<AnyView>> {
496        self.child.clone().map(|c| vec![c])
497    }
498    fn layout_style(&self) -> LayoutStyle {
499        let mut style = self.style;
500        let edges = self.deco.border_edges();
501        style.padding.left += edges.left;
502        style.padding.top += edges.top;
503        style.padding.right += edges.right;
504        style.padding.bottom += edges.bottom;
505        style
506    }
507    fn paint_self(&self, ctx: &mut PaintCtx) {
508        if let Some(sig) = &self.hovered {
509            let hovered = ctx.hovered_subtree;
510            if sig.get_untracked() != hovered {
511                sig.set(hovered);
512            }
513        }
514        if !self.is_interactive() {
515            ctx.paint_box(self.deco);
516            return;
517        }
518        let target = self.target_background(&ctx.theme(), ctx.state);
519        let bg = if self.blend_rate <= 0.0 {
520            target
521        } else {
522            ctx.live_color_exp(Prop::BACKGROUND, target, self.blend_rate)
523        };
524        let mut deco = self.deco;
525        deco.background = bg;
526        ctx.paint_box(deco);
527    }
528
529    fn has_visual_state(&self) -> bool {
530        self.is_interactive()
531    }
532
533    fn focusable(&self) -> bool {
534        self.is_interactive() && !self.disabled
535    }
536
537    fn cursor_icon(&self, _state: VisualState) -> CursorIcon {
538        if self.is_interactive() && !self.disabled {
539            CursorIcon::Pointer
540        } else {
541            CursorIcon::Default
542        }
543    }
544
545    fn on_event(&self, event: &Event, ctx: &mut EventCtx) {
546        if self.disabled || !self.is_interactive() {
547            return;
548        }
549        match event {
550            Event::Pointer(pe) => match pe {
551                PointerEvent::Down {
552                    button: PointerButton::Left,
553                    pos,
554                    ..
555                } => {
556                    let inside = ctx.rect.contains(*pos);
557                    self.pressed_flag.set(inside);
558                    if self.trigger == ClickTrigger::Press
559                        && ctx.phase != EventPhase::Capture
560                        && inside
561                    {
562                        self.pressed_flag.set(false);
563                        self.fire(ctx.environment);
564                        ctx.handled();
565                    }
566                }
567                PointerEvent::Up {
568                    button: PointerButton::Left,
569                    pos,
570                    ..
571                } if ctx.phase != EventPhase::Capture
572                    && self.pressed_flag.take()
573                    && ctx.rect.contains(*pos) =>
574                {
575                    self.fire(ctx.environment);
576                    ctx.handled();
577                }
578                _ => {}
579            },
580
581            Event::Key(key)
582                if ctx.phase == EventPhase::Target
583                    && ctx.state.focused
584                    && (key.key == Key::ENTER || key.key == Key::SPACE) =>
585            {
586                self.fire(ctx.environment);
587                ctx.handled();
588            }
589            _ => {}
590        }
591    }
592}