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}