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<'b>,
381 renderer: &Renderer,
382 viewport: &Rectangle,
383 translation: Vector,
384 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
385 self.content.as_widget_mut().overlay(
386 tree,
387 layout.children().next().unwrap(),
388 renderer,
389 viewport,
390 translation,
391 )
392 }
393}
394
395impl<'a, Message, Theme, Renderer> From<Container<'a, Message, Theme, Renderer>>
396 for Element<'a, Message, Theme, Renderer>
397where
398 Message: 'a,
399 Theme: Catalog + 'a,
400 Renderer: core::Renderer + 'a,
401{
402 fn from(
403 column: Container<'a, Message, Theme, Renderer>,
404 ) -> Element<'a, Message, Theme, Renderer> {
405 Element::new(column)
406 }
407}
408
409pub fn layout(
411 limits: &layout::Limits,
412 width: Length,
413 height: Length,
414 max_width: f32,
415 max_height: f32,
416 padding: Padding,
417 horizontal_alignment: alignment::Horizontal,
418 vertical_alignment: alignment::Vertical,
419 layout_content: impl FnOnce(&layout::Limits) -> layout::Node,
420) -> layout::Node {
421 layout::positioned(
422 &limits.max_width(max_width).max_height(max_height),
423 width,
424 height,
425 padding,
426 |limits| layout_content(&limits.loose()),
427 |content, size| {
428 content.align(
429 Alignment::from(horizontal_alignment),
430 Alignment::from(vertical_alignment),
431 size,
432 )
433 },
434 )
435}
436
437pub fn draw_background<Renderer>(
439 renderer: &mut Renderer,
440 style: &Style,
441 bounds: Rectangle,
442) where
443 Renderer: core::Renderer,
444{
445 if style.background.is_some()
446 || style.border.width > 0.0
447 || style.shadow.color.a > 0.0
448 {
449 renderer.fill_quad(
450 renderer::Quad {
451 bounds,
452 border: style.border,
453 shadow: style.shadow,
454 snap: style.snap,
455 },
456 style
457 .background
458 .unwrap_or(Background::Color(Color::TRANSPARENT)),
459 );
460 }
461}
462
463#[derive(Debug, Clone, PartialEq, Eq, Hash)]
465pub struct Id(widget::Id);
466
467impl Id {
468 pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
470 Self(widget::Id::new(id))
471 }
472
473 pub fn unique() -> Self {
477 Self(widget::Id::unique())
478 }
479}
480
481impl From<Id> for widget::Id {
482 fn from(id: Id) -> Self {
483 id.0
484 }
485}
486
487impl From<&'static str> for Id {
488 fn from(value: &'static str) -> Self {
489 Id::new(value)
490 }
491}
492
493pub fn visible_bounds(id: impl Into<Id>) -> Task<Option<Rectangle>> {
496 let id = id.into();
497
498 struct VisibleBounds {
499 target: widget::Id,
500 depth: usize,
501 scrollables: Vec<(Vector, Rectangle, usize)>,
502 bounds: Option<Rectangle>,
503 }
504
505 impl Operation<Option<Rectangle>> for VisibleBounds {
506 fn scrollable(
507 &mut self,
508 _id: Option<&widget::Id>,
509 bounds: Rectangle,
510 _content_bounds: Rectangle,
511 translation: Vector,
512 _state: &mut dyn widget::operation::Scrollable,
513 ) {
514 match self.scrollables.last() {
515 Some((last_translation, last_viewport, _depth)) => {
516 let viewport = last_viewport
517 .intersection(&(bounds - *last_translation))
518 .unwrap_or(Rectangle::new(Point::ORIGIN, Size::ZERO));
519
520 self.scrollables.push((
521 translation + *last_translation,
522 viewport,
523 self.depth,
524 ));
525 }
526 None => {
527 self.scrollables.push((translation, bounds, self.depth));
528 }
529 }
530 }
531
532 fn container(
533 &mut self,
534 id: Option<&widget::Id>,
535 bounds: Rectangle,
536 operate_on_children: &mut dyn FnMut(
537 &mut dyn Operation<Option<Rectangle>>,
538 ),
539 ) {
540 if self.bounds.is_some() {
541 return;
542 }
543
544 if id == Some(&self.target) {
545 match self.scrollables.last() {
546 Some((translation, viewport, _)) => {
547 self.bounds =
548 viewport.intersection(&(bounds - *translation));
549 }
550 None => {
551 self.bounds = Some(bounds);
552 }
553 }
554
555 return;
556 }
557
558 self.depth += 1;
559
560 operate_on_children(self);
561
562 self.depth -= 1;
563
564 match self.scrollables.last() {
565 Some((_, _, depth)) if self.depth == *depth => {
566 let _ = self.scrollables.pop();
567 }
568 _ => {}
569 }
570 }
571
572 fn finish(&self) -> widget::operation::Outcome<Option<Rectangle>> {
573 widget::operation::Outcome::Some(self.bounds)
574 }
575 }
576
577 task::widget(VisibleBounds {
578 target: id.into(),
579 depth: 0,
580 scrollables: Vec::new(),
581 bounds: None,
582 })
583}
584
585#[derive(Debug, Clone, Copy, PartialEq, Default)]
587pub struct Style {
588 pub text_color: Option<Color>,
590 pub background: Option<Background>,
592 pub border: Border,
594 pub shadow: Shadow,
596 pub snap: bool,
598}
599
600impl Style {
601 pub fn color(self, color: impl Into<Color>) -> Self {
603 Self {
604 text_color: Some(color.into()),
605 ..self
606 }
607 }
608
609 pub fn border(self, border: impl Into<Border>) -> Self {
611 Self {
612 border: border.into(),
613 ..self
614 }
615 }
616
617 pub fn background(self, background: impl Into<Background>) -> Self {
619 Self {
620 background: Some(background.into()),
621 ..self
622 }
623 }
624
625 pub fn shadow(self, shadow: impl Into<Shadow>) -> Self {
627 Self {
628 shadow: shadow.into(),
629 ..self
630 }
631 }
632}
633
634impl From<Color> for Style {
635 fn from(color: Color) -> Self {
636 Self::default().background(color)
637 }
638}
639
640impl From<Gradient> for Style {
641 fn from(gradient: Gradient) -> Self {
642 Self::default().background(gradient)
643 }
644}
645
646impl From<gradient::Linear> for Style {
647 fn from(gradient: gradient::Linear) -> Self {
648 Self::default().background(gradient)
649 }
650}
651
652pub trait Catalog {
654 type Class<'a>;
656
657 fn default<'a>() -> Self::Class<'a>;
659
660 fn style(&self, class: &Self::Class<'_>) -> Style;
662}
663
664pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
666
667impl<Theme> From<Style> for StyleFn<'_, Theme> {
668 fn from(style: Style) -> Self {
669 Box::new(move |_theme| style)
670 }
671}
672
673impl Catalog for Theme {
674 type Class<'a> = StyleFn<'a, Self>;
675
676 fn default<'a>() -> Self::Class<'a> {
677 Box::new(transparent)
678 }
679
680 fn style(&self, class: &Self::Class<'_>) -> Style {
681 class(self)
682 }
683}
684
685pub fn transparent<Theme>(_theme: &Theme) -> Style {
687 Style::default()
688}
689
690pub fn background(background: impl Into<Background>) -> Style {
692 Style::default().background(background)
693}
694
695pub fn rounded_box(theme: &Theme) -> Style {
697 let palette = theme.extended_palette();
698
699 Style {
700 background: Some(palette.background.weak.color.into()),
701 border: border::rounded(2),
702 ..Style::default()
703 }
704}
705
706pub fn bordered_box(theme: &Theme) -> Style {
708 let palette = theme.extended_palette();
709
710 Style {
711 background: Some(palette.background.weakest.color.into()),
712 border: Border {
713 width: 1.0,
714 radius: 5.0.into(),
715 color: palette.background.strong.color,
716 },
717 ..Style::default()
718 }
719}
720
721pub fn dark(_theme: &Theme) -> Style {
723 style(theme::palette::Pair {
724 color: color!(0x111111),
725 text: Color::WHITE,
726 })
727}
728
729pub fn primary(theme: &Theme) -> Style {
731 let palette = theme.extended_palette();
732
733 style(palette.primary.base)
734}
735
736pub fn secondary(theme: &Theme) -> Style {
738 let palette = theme.extended_palette();
739
740 style(palette.secondary.base)
741}
742
743pub fn success(theme: &Theme) -> Style {
745 let palette = theme.extended_palette();
746
747 style(palette.success.base)
748}
749
750pub fn danger(theme: &Theme) -> Style {
752 let palette = theme.extended_palette();
753
754 style(palette.danger.base)
755}
756
757fn style(pair: theme::palette::Pair) -> Style {
758 Style {
759 background: Some(pair.color.into()),
760 text_color: Some(pair.text),
761 border: border::rounded(2),
762 ..Style::default()
763 }
764}