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}