1use crate::core::alignment::{self, Alignment};
23use crate::core::border::{self, Border};
24use crate::core::gradient::{self, Gradient};
25use crate::core::layout;
26use crate::core::mouse;
27use crate::core::overlay;
28use crate::core::renderer;
29use crate::core::theme;
30use crate::core::widget::tree::{self, Tree};
31use crate::core::widget::{self, Operation};
32use crate::core::{
33 self, Background, Clipboard, Color, Element, Event, Layout, Length,
34 Padding, Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector,
35 Widget, color,
36};
37use crate::runtime::task::{self, Task};
38
39#[allow(missing_debug_implementations)]
61pub struct Container<
62 'a,
63 Message,
64 Theme = crate::Theme,
65 Renderer = crate::Renderer,
66> where
67 Theme: Catalog,
68 Renderer: core::Renderer,
69{
70 id: Option<Id>,
71 padding: Padding,
72 width: Length,
73 height: Length,
74 max_width: f32,
75 max_height: f32,
76 horizontal_alignment: alignment::Horizontal,
77 vertical_alignment: alignment::Vertical,
78 clip: bool,
79 content: Element<'a, Message, Theme, Renderer>,
80 class: Theme::Class<'a>,
81}
82
83impl<'a, Message, Theme, Renderer> Container<'a, Message, Theme, Renderer>
84where
85 Theme: Catalog,
86 Renderer: core::Renderer,
87{
88 pub fn new(
90 content: impl Into<Element<'a, Message, Theme, Renderer>>,
91 ) -> Self {
92 let content = content.into();
93 let size = content.as_widget().size_hint();
94
95 Container {
96 id: None,
97 padding: Padding::ZERO,
98 width: size.width.fluid(),
99 height: size.height.fluid(),
100 max_width: f32::INFINITY,
101 max_height: f32::INFINITY,
102 horizontal_alignment: alignment::Horizontal::Left,
103 vertical_alignment: alignment::Vertical::Top,
104 clip: false,
105 class: Theme::default(),
106 content,
107 }
108 }
109
110 pub fn id(mut self, id: impl Into<Id>) -> Self {
112 self.id = Some(id.into());
113 self
114 }
115
116 pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
118 self.padding = padding.into();
119 self
120 }
121
122 pub fn width(mut self, width: impl Into<Length>) -> Self {
124 self.width = width.into();
125 self
126 }
127
128 pub fn height(mut self, height: impl Into<Length>) -> Self {
130 self.height = height.into();
131 self
132 }
133
134 pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {
136 self.max_width = max_width.into().0;
137 self
138 }
139
140 pub fn max_height(mut self, max_height: impl Into<Pixels>) -> Self {
142 self.max_height = max_height.into().0;
143 self
144 }
145
146 pub fn center_x(self, width: impl Into<Length>) -> Self {
148 self.width(width).align_x(alignment::Horizontal::Center)
149 }
150
151 pub fn center_y(self, height: impl Into<Length>) -> Self {
153 self.height(height).align_y(alignment::Vertical::Center)
154 }
155
156 pub fn center(self, length: impl Into<Length>) -> Self {
164 let length = length.into();
165
166 self.center_x(length).center_y(length)
167 }
168
169 pub fn align_left(self, width: impl Into<Length>) -> Self {
171 self.width(width).align_x(alignment::Horizontal::Left)
172 }
173
174 pub fn align_right(self, width: impl Into<Length>) -> Self {
176 self.width(width).align_x(alignment::Horizontal::Right)
177 }
178
179 pub fn align_top(self, height: impl Into<Length>) -> Self {
181 self.height(height).align_y(alignment::Vertical::Top)
182 }
183
184 pub fn align_bottom(self, height: impl Into<Length>) -> Self {
186 self.height(height).align_y(alignment::Vertical::Bottom)
187 }
188
189 pub fn align_x(
191 mut self,
192 alignment: impl Into<alignment::Horizontal>,
193 ) -> Self {
194 self.horizontal_alignment = alignment.into();
195 self
196 }
197
198 pub fn align_y(
200 mut self,
201 alignment: impl Into<alignment::Vertical>,
202 ) -> Self {
203 self.vertical_alignment = alignment.into();
204 self
205 }
206
207 pub fn clip(mut self, clip: bool) -> Self {
210 self.clip = clip;
211 self
212 }
213
214 #[must_use]
216 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
217 where
218 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
219 {
220 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
221 self
222 }
223
224 #[must_use]
226 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
227 self.class = class.into();
228 self
229 }
230}
231
232impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
233 for Container<'_, Message, Theme, Renderer>
234where
235 Theme: Catalog,
236 Renderer: core::Renderer,
237{
238 fn tag(&self) -> tree::Tag {
239 self.content.as_widget().tag()
240 }
241
242 fn state(&self) -> tree::State {
243 self.content.as_widget().state()
244 }
245
246 fn children(&self) -> Vec<Tree> {
247 self.content.as_widget().children()
248 }
249
250 fn diff(&self, tree: &mut Tree) {
251 self.content.as_widget().diff(tree);
252 }
253
254 fn size(&self) -> Size<Length> {
255 Size {
256 width: self.width,
257 height: self.height,
258 }
259 }
260
261 fn layout(
262 &self,
263 tree: &mut Tree,
264 renderer: &Renderer,
265 limits: &layout::Limits,
266 ) -> layout::Node {
267 layout(
268 limits,
269 self.width,
270 self.height,
271 self.max_width,
272 self.max_height,
273 self.padding,
274 self.horizontal_alignment,
275 self.vertical_alignment,
276 |limits| self.content.as_widget().layout(tree, renderer, limits),
277 )
278 }
279
280 fn operate(
281 &self,
282 tree: &mut Tree,
283 layout: Layout<'_>,
284 renderer: &Renderer,
285 operation: &mut dyn Operation,
286 ) {
287 operation.container(
288 self.id.as_ref().map(|id| &id.0),
289 layout.bounds(),
290 &mut |operation| {
291 self.content.as_widget().operate(
292 tree,
293 layout.children().next().unwrap(),
294 renderer,
295 operation,
296 );
297 },
298 );
299 }
300
301 fn update(
302 &mut self,
303 tree: &mut Tree,
304 event: &Event,
305 layout: Layout<'_>,
306 cursor: mouse::Cursor,
307 renderer: &Renderer,
308 clipboard: &mut dyn Clipboard,
309 shell: &mut Shell<'_, Message>,
310 viewport: &Rectangle,
311 ) {
312 self.content.as_widget_mut().update(
313 tree,
314 event,
315 layout.children().next().unwrap(),
316 cursor,
317 renderer,
318 clipboard,
319 shell,
320 viewport,
321 );
322 }
323
324 fn mouse_interaction(
325 &self,
326 tree: &Tree,
327 layout: Layout<'_>,
328 cursor: mouse::Cursor,
329 viewport: &Rectangle,
330 renderer: &Renderer,
331 ) -> mouse::Interaction {
332 self.content.as_widget().mouse_interaction(
333 tree,
334 layout.children().next().unwrap(),
335 cursor,
336 viewport,
337 renderer,
338 )
339 }
340
341 fn draw(
342 &self,
343 tree: &Tree,
344 renderer: &mut Renderer,
345 theme: &Theme,
346 renderer_style: &renderer::Style,
347 layout: Layout<'_>,
348 cursor: mouse::Cursor,
349 viewport: &Rectangle,
350 ) {
351 let bounds = layout.bounds();
352 let style = theme.style(&self.class);
353
354 if let Some(clipped_viewport) = bounds.intersection(viewport) {
355 draw_background(renderer, &style, bounds);
356
357 self.content.as_widget().draw(
358 tree,
359 renderer,
360 theme,
361 &renderer::Style {
362 text_color: style
363 .text_color
364 .unwrap_or(renderer_style.text_color),
365 },
366 layout.children().next().unwrap(),
367 cursor,
368 if self.clip {
369 &clipped_viewport
370 } else {
371 viewport
372 },
373 );
374 }
375 }
376
377 fn overlay<'b>(
378 &'b mut self,
379 tree: &'b mut Tree,
380 layout: Layout<'_>,
381 renderer: &Renderer,
382 translation: Vector,
383 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
384 self.content.as_widget_mut().overlay(
385 tree,
386 layout.children().next().unwrap(),
387 renderer,
388 translation,
389 )
390 }
391}
392
393impl<'a, Message, Theme, Renderer> From<Container<'a, Message, Theme, Renderer>>
394 for Element<'a, Message, Theme, Renderer>
395where
396 Message: 'a,
397 Theme: Catalog + 'a,
398 Renderer: core::Renderer + 'a,
399{
400 fn from(
401 column: Container<'a, Message, Theme, Renderer>,
402 ) -> Element<'a, Message, Theme, Renderer> {
403 Element::new(column)
404 }
405}
406
407pub fn layout(
409 limits: &layout::Limits,
410 width: Length,
411 height: Length,
412 max_width: f32,
413 max_height: f32,
414 padding: Padding,
415 horizontal_alignment: alignment::Horizontal,
416 vertical_alignment: alignment::Vertical,
417 layout_content: impl FnOnce(&layout::Limits) -> layout::Node,
418) -> layout::Node {
419 layout::positioned(
420 &limits.max_width(max_width).max_height(max_height),
421 width,
422 height,
423 padding,
424 |limits| layout_content(&limits.loose()),
425 |content, size| {
426 content.align(
427 Alignment::from(horizontal_alignment),
428 Alignment::from(vertical_alignment),
429 size,
430 )
431 },
432 )
433}
434
435pub fn draw_background<Renderer>(
437 renderer: &mut Renderer,
438 style: &Style,
439 bounds: Rectangle,
440) where
441 Renderer: core::Renderer,
442{
443 if style.background.is_some()
444 || style.border.width > 0.0
445 || style.shadow.color.a > 0.0
446 {
447 renderer.fill_quad(
448 renderer::Quad {
449 bounds,
450 border: style.border,
451 shadow: style.shadow,
452 },
453 style
454 .background
455 .unwrap_or(Background::Color(Color::TRANSPARENT)),
456 );
457 }
458}
459
460#[derive(Debug, Clone, PartialEq, Eq, Hash)]
462pub struct Id(widget::Id);
463
464impl Id {
465 pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
467 Self(widget::Id::new(id))
468 }
469
470 pub fn unique() -> Self {
474 Self(widget::Id::unique())
475 }
476}
477
478impl From<Id> for widget::Id {
479 fn from(id: Id) -> Self {
480 id.0
481 }
482}
483
484impl From<&'static str> for Id {
485 fn from(value: &'static str) -> Self {
486 Id::new(value)
487 }
488}
489
490pub fn visible_bounds(id: impl Into<Id>) -> Task<Option<Rectangle>> {
493 let id = id.into();
494
495 struct VisibleBounds {
496 target: widget::Id,
497 depth: usize,
498 scrollables: Vec<(Vector, Rectangle, usize)>,
499 bounds: Option<Rectangle>,
500 }
501
502 impl Operation<Option<Rectangle>> for VisibleBounds {
503 fn scrollable(
504 &mut self,
505 _id: Option<&widget::Id>,
506 bounds: Rectangle,
507 _content_bounds: Rectangle,
508 translation: Vector,
509 _state: &mut dyn widget::operation::Scrollable,
510 ) {
511 match self.scrollables.last() {
512 Some((last_translation, last_viewport, _depth)) => {
513 let viewport = last_viewport
514 .intersection(&(bounds - *last_translation))
515 .unwrap_or(Rectangle::new(Point::ORIGIN, Size::ZERO));
516
517 self.scrollables.push((
518 translation + *last_translation,
519 viewport,
520 self.depth,
521 ));
522 }
523 None => {
524 self.scrollables.push((translation, bounds, self.depth));
525 }
526 }
527 }
528
529 fn container(
530 &mut self,
531 id: Option<&widget::Id>,
532 bounds: Rectangle,
533 operate_on_children: &mut dyn FnMut(
534 &mut dyn Operation<Option<Rectangle>>,
535 ),
536 ) {
537 if self.bounds.is_some() {
538 return;
539 }
540
541 if id == Some(&self.target) {
542 match self.scrollables.last() {
543 Some((translation, viewport, _)) => {
544 self.bounds =
545 viewport.intersection(&(bounds - *translation));
546 }
547 None => {
548 self.bounds = Some(bounds);
549 }
550 }
551
552 return;
553 }
554
555 self.depth += 1;
556
557 operate_on_children(self);
558
559 self.depth -= 1;
560
561 match self.scrollables.last() {
562 Some((_, _, depth)) if self.depth == *depth => {
563 let _ = self.scrollables.pop();
564 }
565 _ => {}
566 }
567 }
568
569 fn finish(&self) -> widget::operation::Outcome<Option<Rectangle>> {
570 widget::operation::Outcome::Some(self.bounds)
571 }
572 }
573
574 task::widget(VisibleBounds {
575 target: id.into(),
576 depth: 0,
577 scrollables: Vec::new(),
578 bounds: None,
579 })
580}
581
582#[derive(Debug, Clone, Copy, PartialEq, Default)]
584pub struct Style {
585 pub text_color: Option<Color>,
587 pub background: Option<Background>,
589 pub border: Border,
591 pub shadow: Shadow,
593}
594
595impl Style {
596 pub fn color(self, color: impl Into<Color>) -> Self {
598 Self {
599 text_color: Some(color.into()),
600 ..self
601 }
602 }
603
604 pub fn border(self, border: impl Into<Border>) -> Self {
606 Self {
607 border: border.into(),
608 ..self
609 }
610 }
611
612 pub fn background(self, background: impl Into<Background>) -> Self {
614 Self {
615 background: Some(background.into()),
616 ..self
617 }
618 }
619
620 pub fn shadow(self, shadow: impl Into<Shadow>) -> Self {
622 Self {
623 shadow: shadow.into(),
624 ..self
625 }
626 }
627}
628
629impl From<Color> for Style {
630 fn from(color: Color) -> Self {
631 Self::default().background(color)
632 }
633}
634
635impl From<Gradient> for Style {
636 fn from(gradient: Gradient) -> Self {
637 Self::default().background(gradient)
638 }
639}
640
641impl From<gradient::Linear> for Style {
642 fn from(gradient: gradient::Linear) -> Self {
643 Self::default().background(gradient)
644 }
645}
646
647pub trait Catalog {
649 type Class<'a>;
651
652 fn default<'a>() -> Self::Class<'a>;
654
655 fn style(&self, class: &Self::Class<'_>) -> Style;
657}
658
659pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
661
662impl<Theme> From<Style> for StyleFn<'_, Theme> {
663 fn from(style: Style) -> Self {
664 Box::new(move |_theme| style)
665 }
666}
667
668impl Catalog for Theme {
669 type Class<'a> = StyleFn<'a, Self>;
670
671 fn default<'a>() -> Self::Class<'a> {
672 Box::new(transparent)
673 }
674
675 fn style(&self, class: &Self::Class<'_>) -> Style {
676 class(self)
677 }
678}
679
680pub fn transparent<Theme>(_theme: &Theme) -> Style {
682 Style::default()
683}
684
685pub fn background(background: impl Into<Background>) -> Style {
687 Style::default().background(background)
688}
689
690pub fn rounded_box(theme: &Theme) -> Style {
692 let palette = theme.extended_palette();
693
694 Style {
695 background: Some(palette.background.weak.color.into()),
696 border: border::rounded(2),
697 ..Style::default()
698 }
699}
700
701pub fn bordered_box(theme: &Theme) -> Style {
703 let palette = theme.extended_palette();
704
705 Style {
706 background: Some(palette.background.weakest.color.into()),
707 border: Border {
708 width: 1.0,
709 radius: 5.0.into(),
710 color: palette.background.strong.color,
711 },
712 ..Style::default()
713 }
714}
715
716pub fn dark(_theme: &Theme) -> Style {
718 style(theme::palette::Pair {
719 color: color!(0x111111),
720 text: Color::WHITE,
721 })
722}
723
724pub fn primary(theme: &Theme) -> Style {
726 let palette = theme.extended_palette();
727
728 style(palette.primary.base)
729}
730
731pub fn secondary(theme: &Theme) -> Style {
733 let palette = theme.extended_palette();
734
735 style(palette.secondary.base)
736}
737
738pub fn success(theme: &Theme) -> Style {
740 let palette = theme.extended_palette();
741
742 style(palette.success.base)
743}
744
745pub fn danger(theme: &Theme) -> Style {
747 let palette = theme.extended_palette();
748
749 style(palette.danger.base)
750}
751
752fn style(pair: theme::palette::Pair) -> Style {
753 Style {
754 background: Some(pair.color.into()),
755 text_color: Some(pair.text),
756 border: border::rounded(2),
757 ..Style::default()
758 }
759}