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