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, side: impl Into<Length>) -> Self {
236        self.square(side)
237    }
238
239    pub fn square(mut self, side: impl Into<Length>) -> Self {
240        let side = side.into();
241        self.style = self.style.width(side).height(side);
242        self
243    }
244
245    pub fn min_width(mut self, v: f32) -> Self {
246        self.style = self.style.min_width(v);
247        self
248    }
249
250    pub fn max_width(mut self, v: f32) -> Self {
251        self.style = self.style.max_width(v);
252        self
253    }
254
255    pub fn min_height(mut self, v: f32) -> Self {
256        self.style = self.style.min_height(v);
257        self
258    }
259
260    pub fn max_height(mut self, v: f32) -> Self {
261        self.style = self.style.max_height(v);
262        self
263    }
264
265    pub fn overflow(mut self, overflow: Overflow) -> Self {
266        self.style = self.style.overflow(overflow);
267        self
268    }
269
270    pub fn margin(mut self, margin: impl Into<Edges>) -> Self {
271        self.style = self.style.margin(margin);
272        self
273    }
274
275    pub fn margin_xy(mut self, x: f32, y: f32) -> Self {
276        self.style = self.style.margin_xy(x, y);
277        self
278    }
279
280    pub fn margin_x(mut self, x: f32) -> Self {
281        self.style = self.style.margin_x(x);
282        self
283    }
284
285    pub fn margin_y(mut self, y: f32) -> Self {
286        self.style = self.style.margin_y(y);
287        self
288    }
289
290    pub fn margin_left(mut self, v: f32) -> Self {
291        self.style = self.style.margin_left(v);
292        self
293    }
294
295    pub fn margin_right(mut self, v: f32) -> Self {
296        self.style = self.style.margin_right(v);
297        self
298    }
299
300    pub fn margin_top(mut self, v: f32) -> Self {
301        self.style = self.style.margin_top(v);
302        self
303    }
304
305    pub fn margin_bottom(mut self, v: f32) -> Self {
306        self.style = self.style.margin_bottom(v);
307        self
308    }
309
310    pub fn flex(mut self, weight: f32) -> Self {
311        self.style = self.style.flex(weight);
312        self
313    }
314
315    pub fn flex_shrink(mut self, shrink: f32) -> Self {
316        self.style = self.style.flex_shrink(shrink);
317        self
318    }
319
320    pub fn flex_basis(mut self, basis: Option<Length>) -> Self {
321        self.style = self.style.flex_basis(basis);
322        self
323    }
324
325    pub fn align_self(mut self, align: Option<Align>) -> Self {
326        self.style = self.style.align_self(align);
327        self
328    }
329
330    pub fn aspect(mut self, ratio: f32) -> Self {
331        self.style = self.style.aspect(ratio);
332        self
333    }
334
335    pub fn position(mut self, position: Position) -> Self {
336        self.style = self.style.position(position);
337        self
338    }
339
340    pub fn absolute(mut self) -> Self {
341        self.style = self.style.absolute();
342        self
343    }
344
345    pub fn absolute_at(mut self, left: f32, top: f32) -> Self {
346        self.style = self.style.absolute_at(left, top);
347        self
348    }
349
350    pub fn absolute_inset(mut self, inset: Inset) -> Self {
351        self.style = self.style.absolute_inset(inset);
352        self
353    }
354
355    pub fn expand(self) -> Self {
356        self.flex(1.0)
357    }
358
359    pub fn fill_width(mut self) -> Self {
360        self.style = self.style.width(Length::Fill(1.0));
361        self
362    }
363
364    pub fn fill_height(mut self) -> Self {
365        self.style = self.style.height(Length::Fill(1.0));
366        self
367    }
368
369    pub fn fill_size(self) -> Self {
370        self.fill_width().fill_height()
371    }
372
373    pub fn fit(mut self) -> Self {
374        self.style = self.style.fit();
375        self
376    }
377
378    pub fn hit_testable(mut self, on: bool) -> Self {
379        self.style = self.style.hit_testable(on);
380        self
381    }
382
383    pub fn clip(mut self, clip: bool) -> Self {
384        self.clip = clip;
385        self
386    }
387
388    pub fn clip_children(self) -> Self {
389        self.clip(true)
390    }
391
392    fn is_interactive(&self) -> bool {
393        self.on_click.is_some() || self.on_click_env.is_some()
394    }
395
396    pub fn on_click(mut self, on_click: impl Fn() + 'static) -> Self {
397        self.on_click = Some(Rc::new(on_click));
398        self
399    }
400
401    pub fn on_press(mut self, on_press: impl Fn() + 'static) -> Self {
402        self.on_click = Some(Rc::new(on_press));
403        self.trigger = ClickTrigger::Press;
404        self
405    }
406
407    pub fn trigger(mut self, trigger: ClickTrigger) -> Self {
408        self.trigger = trigger;
409        self
410    }
411
412    pub fn on_click_env(mut self, on_click: impl Fn(&Environment) + 'static) -> Self {
413        self.on_click_env = Some(Rc::new(on_click));
414        self
415    }
416
417    pub fn hovered(mut self, signal: Signal<bool>) -> Self {
418        self.hovered = Some(signal);
419        self
420    }
421
422    pub fn on_hover(mut self, hovered: Signal<bool>) -> Self {
423        self.hovered = Some(hovered);
424        self
425    }
426
427    pub fn disabled(mut self, disabled: bool) -> Self {
428        self.disabled = disabled;
429        self
430    }
431
432    pub fn hover(mut self, color: Color) -> Self {
433        self.hover = Some(color);
434        self
435    }
436
437    pub fn pressed(mut self, color: Color) -> Self {
438        self.pressed = Some(color);
439        self
440    }
441
442    pub fn disabled_background(mut self, color: Color) -> Self {
443        self.disabled_background = Some(color);
444        self
445    }
446
447    pub fn blend_rate(mut self, rate: f32) -> Self {
448        self.blend_rate = rate;
449        self
450    }
451
452    #[cfg(test)]
453    pub(crate) fn effective_blend_rate(&self) -> f32 {
454        self.blend_rate
455    }
456
457    pub fn target_background(&self, theme: &Theme, state: VisualState) -> Color {
458        let mut state = state;
459        if self.disabled {
460            state.disabled = true;
461        }
462        let idle = self.deco.background;
463        if state.disabled {
464            self.disabled_background.unwrap_or(theme.disabled)
465        } else if state.pressed {
466            self.pressed.unwrap_or(idle)
467        } else if state.hovered {
468            self.hover.unwrap_or(idle)
469        } else {
470            idle
471        }
472    }
473
474    fn fire(&self, env: &Environment) {
475        if let Some(f) = &self.on_click {
476            f();
477        }
478        if let Some(f) = &self.on_click_env {
479            f(env);
480        }
481    }
482}
483
484impl View for Container {
485    fn body(&self) -> impl View + 'static {
486        Unit
487    }
488    fn clip_self(&self) -> bool {
489        self.clip
490    }
491    fn clip_radius(&self) -> f32 {
492        if self.clip { self.deco.radius } else { 0.0 }
493    }
494    fn children_override(&self) -> Option<Vec<AnyView>> {
495        self.child.clone().map(|c| vec![c])
496    }
497    fn layout_style(&self) -> LayoutStyle {
498        let mut style = self.style;
499        let edges = self.deco.border_edges();
500        style.padding.left += edges.left;
501        style.padding.top += edges.top;
502        style.padding.right += edges.right;
503        style.padding.bottom += edges.bottom;
504        style
505    }
506    fn paint_self(&self, ctx: &mut PaintCtx) {
507        if let Some(sig) = &self.hovered {
508            let hovered = ctx.hovered_subtree;
509            if sig.get_untracked() != hovered {
510                sig.set(hovered);
511            }
512        }
513        if !self.is_interactive() {
514            ctx.paint_box(self.deco);
515            return;
516        }
517        let target = self.target_background(&ctx.theme(), ctx.state);
518        let bg = if self.blend_rate <= 0.0 {
519            target
520        } else {
521            ctx.live_color_exp(Prop::BACKGROUND, target, self.blend_rate)
522        };
523        let mut deco = self.deco;
524        deco.background = bg;
525        ctx.paint_box(deco);
526    }
527
528    fn has_visual_state(&self) -> bool {
529        self.is_interactive()
530    }
531
532    fn focusable(&self) -> bool {
533        self.is_interactive() && !self.disabled
534    }
535
536    fn cursor_icon(&self, _state: VisualState) -> CursorIcon {
537        if self.is_interactive() && !self.disabled {
538            CursorIcon::Pointer
539        } else {
540            CursorIcon::Default
541        }
542    }
543
544    fn on_event(&self, event: &Event, ctx: &mut EventCtx) {
545        if self.disabled || !self.is_interactive() {
546            return;
547        }
548        match event {
549            Event::Pointer(pe) => match pe {
550                PointerEvent::Down {
551                    button: PointerButton::Left,
552                    pos,
553                    ..
554                } => {
555                    let inside = ctx.rect.contains(*pos);
556                    self.pressed_flag.set(inside);
557                    if self.trigger == ClickTrigger::Press
558                        && ctx.phase != EventPhase::Capture
559                        && inside
560                    {
561                        self.pressed_flag.set(false);
562                        self.fire(ctx.environment);
563                        ctx.handled();
564                    }
565                }
566                PointerEvent::Up {
567                    button: PointerButton::Left,
568                    pos,
569                    ..
570                } if ctx.phase != EventPhase::Capture
571                    && self.pressed_flag.take()
572                    && ctx.rect.contains(*pos) =>
573                {
574                    self.fire(ctx.environment);
575                    ctx.handled();
576                }
577                _ => {}
578            },
579
580            Event::Key(key)
581                if ctx.phase == EventPhase::Target
582                    && ctx.state.focused
583                    && (key.key == Key::ENTER || key.key == Key::SPACE) =>
584            {
585                self.fire(ctx.environment);
586                ctx.handled();
587            }
588            _ => {}
589        }
590    }
591}