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 },
455 style
456 .background
457 .unwrap_or(Background::Color(Color::TRANSPARENT)),
458 );
459 }
460}
461
462#[derive(Debug, Clone, PartialEq, Eq, Hash)]
464pub struct Id(widget::Id);
465
466impl Id {
467 pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
469 Self(widget::Id::new(id))
470 }
471
472 pub fn unique() -> Self {
476 Self(widget::Id::unique())
477 }
478}
479
480impl From<Id> for widget::Id {
481 fn from(id: Id) -> Self {
482 id.0
483 }
484}
485
486impl From<&'static str> for Id {
487 fn from(value: &'static str) -> Self {
488 Id::new(value)
489 }
490}
491
492pub fn visible_bounds(id: impl Into<Id>) -> Task<Option<Rectangle>> {
495 let id = id.into();
496
497 struct VisibleBounds {
498 target: widget::Id,
499 depth: usize,
500 scrollables: Vec<(Vector, Rectangle, usize)>,
501 bounds: Option<Rectangle>,
502 }
503
504 impl Operation<Option<Rectangle>> for VisibleBounds {
505 fn scrollable(
506 &mut self,
507 _id: Option<&widget::Id>,
508 bounds: Rectangle,
509 _content_bounds: Rectangle,
510 translation: Vector,
511 _state: &mut dyn widget::operation::Scrollable,
512 ) {
513 match self.scrollables.last() {
514 Some((last_translation, last_viewport, _depth)) => {
515 let viewport = last_viewport
516 .intersection(&(bounds - *last_translation))
517 .unwrap_or(Rectangle::new(Point::ORIGIN, Size::ZERO));
518
519 self.scrollables.push((
520 translation + *last_translation,
521 viewport,
522 self.depth,
523 ));
524 }
525 None => {
526 self.scrollables.push((translation, bounds, self.depth));
527 }
528 }
529 }
530
531 fn container(
532 &mut self,
533 id: Option<&widget::Id>,
534 bounds: Rectangle,
535 operate_on_children: &mut dyn FnMut(
536 &mut dyn Operation<Option<Rectangle>>,
537 ),
538 ) {
539 if self.bounds.is_some() {
540 return;
541 }
542
543 if id == Some(&self.target) {
544 match self.scrollables.last() {
545 Some((translation, viewport, _)) => {
546 self.bounds =
547 viewport.intersection(&(bounds - *translation));
548 }
549 None => {
550 self.bounds = Some(bounds);
551 }
552 }
553
554 return;
555 }
556
557 self.depth += 1;
558
559 operate_on_children(self);
560
561 self.depth -= 1;
562
563 match self.scrollables.last() {
564 Some((_, _, depth)) if self.depth == *depth => {
565 let _ = self.scrollables.pop();
566 }
567 _ => {}
568 }
569 }
570
571 fn finish(&self) -> widget::operation::Outcome<Option<Rectangle>> {
572 widget::operation::Outcome::Some(self.bounds)
573 }
574 }
575
576 task::widget(VisibleBounds {
577 target: id.into(),
578 depth: 0,
579 scrollables: Vec::new(),
580 bounds: None,
581 })
582}
583
584#[derive(Debug, Clone, Copy, PartialEq, Default)]
586pub struct Style {
587 pub text_color: Option<Color>,
589 pub background: Option<Background>,
591 pub border: Border,
593 pub shadow: Shadow,
595}
596
597impl Style {
598 pub fn color(self, color: impl Into<Color>) -> Self {
600 Self {
601 text_color: Some(color.into()),
602 ..self
603 }
604 }
605
606 pub fn border(self, border: impl Into<Border>) -> Self {
608 Self {
609 border: border.into(),
610 ..self
611 }
612 }
613
614 pub fn background(self, background: impl Into<Background>) -> Self {
616 Self {
617 background: Some(background.into()),
618 ..self
619 }
620 }
621
622 pub fn shadow(self, shadow: impl Into<Shadow>) -> Self {
624 Self {
625 shadow: shadow.into(),
626 ..self
627 }
628 }
629}
630
631impl From<Color> for Style {
632 fn from(color: Color) -> Self {
633 Self::default().background(color)
634 }
635}
636
637impl From<Gradient> for Style {
638 fn from(gradient: Gradient) -> Self {
639 Self::default().background(gradient)
640 }
641}
642
643impl From<gradient::Linear> for Style {
644 fn from(gradient: gradient::Linear) -> Self {
645 Self::default().background(gradient)
646 }
647}
648
649pub trait Catalog {
651 type Class<'a>;
653
654 fn default<'a>() -> Self::Class<'a>;
656
657 fn style(&self, class: &Self::Class<'_>) -> Style;
659}
660
661pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
663
664impl<Theme> From<Style> for StyleFn<'_, Theme> {
665 fn from(style: Style) -> Self {
666 Box::new(move |_theme| style)
667 }
668}
669
670impl Catalog for Theme {
671 type Class<'a> = StyleFn<'a, Self>;
672
673 fn default<'a>() -> Self::Class<'a> {
674 Box::new(transparent)
675 }
676
677 fn style(&self, class: &Self::Class<'_>) -> Style {
678 class(self)
679 }
680}
681
682pub fn transparent<Theme>(_theme: &Theme) -> Style {
684 Style::default()
685}
686
687pub fn background(background: impl Into<Background>) -> Style {
689 Style::default().background(background)
690}
691
692pub fn rounded_box(theme: &Theme) -> Style {
694 let palette = theme.extended_palette();
695
696 Style {
697 background: Some(palette.background.weak.color.into()),
698 border: border::rounded(2),
699 ..Style::default()
700 }
701}
702
703pub fn bordered_box(theme: &Theme) -> Style {
705 let palette = theme.extended_palette();
706
707 Style {
708 background: Some(palette.background.weakest.color.into()),
709 border: Border {
710 width: 1.0,
711 radius: 5.0.into(),
712 color: palette.background.strong.color,
713 },
714 ..Style::default()
715 }
716}
717
718pub fn dark(_theme: &Theme) -> Style {
720 style(theme::palette::Pair {
721 color: color!(0x111111),
722 text: Color::WHITE,
723 })
724}
725
726pub fn primary(theme: &Theme) -> Style {
728 let palette = theme.extended_palette();
729
730 style(palette.primary.base)
731}
732
733pub fn secondary(theme: &Theme) -> Style {
735 let palette = theme.extended_palette();
736
737 style(palette.secondary.base)
738}
739
740pub fn success(theme: &Theme) -> Style {
742 let palette = theme.extended_palette();
743
744 style(palette.success.base)
745}
746
747pub fn danger(theme: &Theme) -> Style {
749 let palette = theme.extended_palette();
750
751 style(palette.danger.base)
752}
753
754fn style(pair: theme::palette::Pair) -> Style {
755 Style {
756 background: Some(pair.color.into()),
757 text_color: Some(pair.text),
758 border: border::rounded(2),
759 ..Style::default()
760 }
761}