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 &mut 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| {
277 self.content.as_widget_mut().layout(tree, renderer, limits)
278 },
279 )
280 }
281
282 fn operate(
283 &mut self,
284 tree: &mut Tree,
285 layout: Layout<'_>,
286 renderer: &Renderer,
287 operation: &mut dyn Operation,
288 ) {
289 operation.container(
290 self.id.as_ref().map(|id| &id.0),
291 layout.bounds(),
292 &mut |operation| {
293 self.content.as_widget_mut().operate(
294 tree,
295 layout.children().next().unwrap(),
296 renderer,
297 operation,
298 );
299 },
300 );
301 }
302
303 fn update(
304 &mut self,
305 tree: &mut Tree,
306 event: &Event,
307 layout: Layout<'_>,
308 cursor: mouse::Cursor,
309 renderer: &Renderer,
310 clipboard: &mut dyn Clipboard,
311 shell: &mut Shell<'_, Message>,
312 viewport: &Rectangle,
313 ) {
314 self.content.as_widget_mut().update(
315 tree,
316 event,
317 layout.children().next().unwrap(),
318 cursor,
319 renderer,
320 clipboard,
321 shell,
322 viewport,
323 );
324 }
325
326 fn mouse_interaction(
327 &self,
328 tree: &Tree,
329 layout: Layout<'_>,
330 cursor: mouse::Cursor,
331 viewport: &Rectangle,
332 renderer: &Renderer,
333 ) -> mouse::Interaction {
334 self.content.as_widget().mouse_interaction(
335 tree,
336 layout.children().next().unwrap(),
337 cursor,
338 viewport,
339 renderer,
340 )
341 }
342
343 fn draw(
344 &self,
345 tree: &Tree,
346 renderer: &mut Renderer,
347 theme: &Theme,
348 renderer_style: &renderer::Style,
349 layout: Layout<'_>,
350 cursor: mouse::Cursor,
351 viewport: &Rectangle,
352 ) {
353 let bounds = layout.bounds();
354 let style = theme.style(&self.class);
355
356 if let Some(clipped_viewport) = bounds.intersection(viewport) {
357 draw_background(renderer, &style, bounds);
358
359 self.content.as_widget().draw(
360 tree,
361 renderer,
362 theme,
363 &renderer::Style {
364 text_color: style
365 .text_color
366 .unwrap_or(renderer_style.text_color),
367 },
368 layout.children().next().unwrap(),
369 cursor,
370 if self.clip {
371 &clipped_viewport
372 } else {
373 viewport
374 },
375 );
376 }
377 }
378
379 fn overlay<'b>(
380 &'b mut self,
381 tree: &'b mut Tree,
382 layout: Layout<'b>,
383 renderer: &Renderer,
384 viewport: &Rectangle,
385 translation: Vector,
386 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
387 self.content.as_widget_mut().overlay(
388 tree,
389 layout.children().next().unwrap(),
390 renderer,
391 viewport,
392 translation,
393 )
394 }
395}
396
397impl<'a, Message, Theme, Renderer> From<Container<'a, Message, Theme, Renderer>>
398 for Element<'a, Message, Theme, Renderer>
399where
400 Message: 'a,
401 Theme: Catalog + 'a,
402 Renderer: core::Renderer + 'a,
403{
404 fn from(
405 container: Container<'a, Message, Theme, Renderer>,
406 ) -> Element<'a, Message, Theme, Renderer> {
407 Element::new(container)
408 }
409}
410
411pub fn layout(
413 limits: &layout::Limits,
414 width: Length,
415 height: Length,
416 max_width: f32,
417 max_height: f32,
418 padding: Padding,
419 horizontal_alignment: alignment::Horizontal,
420 vertical_alignment: alignment::Vertical,
421 layout_content: impl FnOnce(&layout::Limits) -> layout::Node,
422) -> layout::Node {
423 layout::positioned(
424 &limits.max_width(max_width).max_height(max_height),
425 width,
426 height,
427 padding,
428 |limits| layout_content(&limits.loose()),
429 |content, size| {
430 content.align(
431 Alignment::from(horizontal_alignment),
432 Alignment::from(vertical_alignment),
433 size,
434 )
435 },
436 )
437}
438
439pub fn draw_background<Renderer>(
441 renderer: &mut Renderer,
442 style: &Style,
443 bounds: Rectangle,
444) where
445 Renderer: core::Renderer,
446{
447 if style.background.is_some()
448 || style.border.width > 0.0
449 || style.shadow.color.a > 0.0
450 {
451 renderer.fill_quad(
452 renderer::Quad {
453 bounds,
454 border: style.border,
455 shadow: style.shadow,
456 snap: style.snap,
457 },
458 style
459 .background
460 .unwrap_or(Background::Color(Color::TRANSPARENT)),
461 );
462 }
463}
464
465#[derive(Debug, Clone, PartialEq, Eq, Hash)]
467pub struct Id(widget::Id);
468
469impl Id {
470 pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
472 Self(widget::Id::new(id))
473 }
474
475 pub fn unique() -> Self {
479 Self(widget::Id::unique())
480 }
481}
482
483impl From<Id> for widget::Id {
484 fn from(id: Id) -> Self {
485 id.0
486 }
487}
488
489impl From<&'static str> for Id {
490 fn from(value: &'static str) -> Self {
491 Id::new(value)
492 }
493}
494
495pub fn visible_bounds(id: impl Into<Id>) -> Task<Option<Rectangle>> {
498 let id = id.into();
499
500 struct VisibleBounds {
501 target: widget::Id,
502 depth: usize,
503 scrollables: Vec<(Vector, Rectangle, usize)>,
504 bounds: Option<Rectangle>,
505 }
506
507 impl Operation<Option<Rectangle>> for VisibleBounds {
508 fn scrollable(
509 &mut self,
510 _id: Option<&widget::Id>,
511 bounds: Rectangle,
512 _content_bounds: Rectangle,
513 translation: Vector,
514 _state: &mut dyn widget::operation::Scrollable,
515 ) {
516 match self.scrollables.last() {
517 Some((last_translation, last_viewport, _depth)) => {
518 let viewport = last_viewport
519 .intersection(&(bounds - *last_translation))
520 .unwrap_or(Rectangle::new(Point::ORIGIN, Size::ZERO));
521
522 self.scrollables.push((
523 translation + *last_translation,
524 viewport,
525 self.depth,
526 ));
527 }
528 None => {
529 self.scrollables.push((translation, bounds, self.depth));
530 }
531 }
532 }
533
534 fn container(
535 &mut self,
536 id: Option<&widget::Id>,
537 bounds: Rectangle,
538 operate_on_children: &mut dyn FnMut(
539 &mut dyn Operation<Option<Rectangle>>,
540 ),
541 ) {
542 if self.bounds.is_some() {
543 return;
544 }
545
546 if id == Some(&self.target) {
547 match self.scrollables.last() {
548 Some((translation, viewport, _)) => {
549 self.bounds =
550 viewport.intersection(&(bounds - *translation));
551 }
552 None => {
553 self.bounds = Some(bounds);
554 }
555 }
556
557 return;
558 }
559
560 self.depth += 1;
561
562 operate_on_children(self);
563
564 self.depth -= 1;
565
566 match self.scrollables.last() {
567 Some((_, _, depth)) if self.depth == *depth => {
568 let _ = self.scrollables.pop();
569 }
570 _ => {}
571 }
572 }
573
574 fn finish(&self) -> widget::operation::Outcome<Option<Rectangle>> {
575 widget::operation::Outcome::Some(self.bounds)
576 }
577 }
578
579 task::widget(VisibleBounds {
580 target: id.into(),
581 depth: 0,
582 scrollables: Vec::new(),
583 bounds: None,
584 })
585}
586
587#[derive(Debug, Clone, Copy, PartialEq, Default)]
589pub struct Style {
590 pub text_color: Option<Color>,
592 pub background: Option<Background>,
594 pub border: Border,
596 pub shadow: Shadow,
598 pub snap: bool,
600}
601
602impl Style {
603 pub fn color(self, color: impl Into<Color>) -> Self {
605 Self {
606 text_color: Some(color.into()),
607 ..self
608 }
609 }
610
611 pub fn border(self, border: impl Into<Border>) -> Self {
613 Self {
614 border: border.into(),
615 ..self
616 }
617 }
618
619 pub fn background(self, background: impl Into<Background>) -> Self {
621 Self {
622 background: Some(background.into()),
623 ..self
624 }
625 }
626
627 pub fn shadow(self, shadow: impl Into<Shadow>) -> Self {
629 Self {
630 shadow: shadow.into(),
631 ..self
632 }
633 }
634}
635
636impl From<Color> for Style {
637 fn from(color: Color) -> Self {
638 Self::default().background(color)
639 }
640}
641
642impl From<Gradient> for Style {
643 fn from(gradient: Gradient) -> Self {
644 Self::default().background(gradient)
645 }
646}
647
648impl From<gradient::Linear> for Style {
649 fn from(gradient: gradient::Linear) -> Self {
650 Self::default().background(gradient)
651 }
652}
653
654pub trait Catalog {
656 type Class<'a>;
658
659 fn default<'a>() -> Self::Class<'a>;
661
662 fn style(&self, class: &Self::Class<'_>) -> Style;
664}
665
666pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
668
669impl<Theme> From<Style> for StyleFn<'_, Theme> {
670 fn from(style: Style) -> Self {
671 Box::new(move |_theme| style)
672 }
673}
674
675impl Catalog for Theme {
676 type Class<'a> = StyleFn<'a, Self>;
677
678 fn default<'a>() -> Self::Class<'a> {
679 Box::new(transparent)
680 }
681
682 fn style(&self, class: &Self::Class<'_>) -> Style {
683 class(self)
684 }
685}
686
687pub fn transparent<Theme>(_theme: &Theme) -> Style {
689 Style::default()
690}
691
692pub fn background(background: impl Into<Background>) -> Style {
694 Style::default().background(background)
695}
696
697pub fn rounded_box(theme: &Theme) -> Style {
699 let palette = theme.extended_palette();
700
701 Style {
702 background: Some(palette.background.weak.color.into()),
703 border: border::rounded(2),
704 ..Style::default()
705 }
706}
707
708pub fn bordered_box(theme: &Theme) -> Style {
710 let palette = theme.extended_palette();
711
712 Style {
713 background: Some(palette.background.weakest.color.into()),
714 border: Border {
715 width: 1.0,
716 radius: 5.0.into(),
717 color: palette.background.weak.color,
718 },
719 ..Style::default()
720 }
721}
722
723pub fn dark(_theme: &Theme) -> Style {
725 style(theme::palette::Pair {
726 color: color!(0x111111),
727 text: Color::WHITE,
728 })
729}
730
731pub fn primary(theme: &Theme) -> Style {
733 let palette = theme.extended_palette();
734
735 style(palette.primary.base)
736}
737
738pub fn secondary(theme: &Theme) -> Style {
740 let palette = theme.extended_palette();
741
742 style(palette.secondary.base)
743}
744
745pub fn success(theme: &Theme) -> Style {
747 let palette = theme.extended_palette();
748
749 style(palette.success.base)
750}
751
752pub fn danger(theme: &Theme) -> Style {
754 let palette = theme.extended_palette();
755
756 style(palette.danger.base)
757}
758
759fn style(pair: theme::palette::Pair) -> Style {
760 Style {
761 background: Some(pair.color.into()),
762 text_color: Some(pair.text),
763 border: border::rounded(2),
764 ..Style::default()
765 }
766}