1use crate::core::alignment::{self, Alignment};
23use crate::core::border::{self, Border};
24use crate::core::event::{self, Event};
25use crate::core::gradient::{self, Gradient};
26use crate::core::layout;
27use crate::core::mouse;
28use crate::core::overlay;
29use crate::core::renderer;
30use crate::core::widget::tree::{self, Tree};
31use crate::core::widget::{self, Operation};
32use crate::core::{
33 self, color, Background, Clipboard, Color, Element, Layout, Length,
34 Padding, Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector,
35 Widget,
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: Id) -> Self {
112 self.id = Some(id);
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<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
233 for Container<'a, 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 on_event(
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 ) -> event::Status {
312 self.content.as_widget_mut().on_event(
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
484pub fn visible_bounds(id: Id) -> Task<Option<Rectangle>> {
487 struct VisibleBounds {
488 target: widget::Id,
489 depth: usize,
490 scrollables: Vec<(Vector, Rectangle, usize)>,
491 bounds: Option<Rectangle>,
492 }
493
494 impl Operation<Option<Rectangle>> for VisibleBounds {
495 fn scrollable(
496 &mut self,
497 _state: &mut dyn widget::operation::Scrollable,
498 _id: Option<&widget::Id>,
499 bounds: Rectangle,
500 _content_bounds: Rectangle,
501 translation: Vector,
502 ) {
503 match self.scrollables.last() {
504 Some((last_translation, last_viewport, _depth)) => {
505 let viewport = last_viewport
506 .intersection(&(bounds - *last_translation))
507 .unwrap_or(Rectangle::new(Point::ORIGIN, Size::ZERO));
508
509 self.scrollables.push((
510 translation + *last_translation,
511 viewport,
512 self.depth,
513 ));
514 }
515 None => {
516 self.scrollables.push((translation, bounds, self.depth));
517 }
518 }
519 }
520
521 fn container(
522 &mut self,
523 id: Option<&widget::Id>,
524 bounds: Rectangle,
525 operate_on_children: &mut dyn FnMut(
526 &mut dyn Operation<Option<Rectangle>>,
527 ),
528 ) {
529 if self.bounds.is_some() {
530 return;
531 }
532
533 if id == Some(&self.target) {
534 match self.scrollables.last() {
535 Some((translation, viewport, _)) => {
536 self.bounds =
537 viewport.intersection(&(bounds - *translation));
538 }
539 None => {
540 self.bounds = Some(bounds);
541 }
542 }
543
544 return;
545 }
546
547 self.depth += 1;
548
549 operate_on_children(self);
550
551 self.depth -= 1;
552
553 match self.scrollables.last() {
554 Some((_, _, depth)) if self.depth == *depth => {
555 let _ = self.scrollables.pop();
556 }
557 _ => {}
558 }
559 }
560
561 fn finish(&self) -> widget::operation::Outcome<Option<Rectangle>> {
562 widget::operation::Outcome::Some(self.bounds)
563 }
564 }
565
566 task::widget(VisibleBounds {
567 target: id.into(),
568 depth: 0,
569 scrollables: Vec::new(),
570 bounds: None,
571 })
572}
573
574#[derive(Debug, Clone, Copy, Default)]
576pub struct Style {
577 pub text_color: Option<Color>,
579 pub background: Option<Background>,
581 pub border: Border,
583 pub shadow: Shadow,
585}
586
587impl Style {
588 pub fn color(self, color: impl Into<Color>) -> Self {
590 Self {
591 text_color: Some(color.into()),
592 ..self
593 }
594 }
595
596 pub fn border(self, border: impl Into<Border>) -> Self {
598 Self {
599 border: border.into(),
600 ..self
601 }
602 }
603
604 pub fn background(self, background: impl Into<Background>) -> Self {
606 Self {
607 background: Some(background.into()),
608 ..self
609 }
610 }
611
612 pub fn shadow(self, shadow: impl Into<Shadow>) -> Self {
614 Self {
615 shadow: shadow.into(),
616 ..self
617 }
618 }
619}
620
621impl From<Color> for Style {
622 fn from(color: Color) -> Self {
623 Self::default().background(color)
624 }
625}
626
627impl From<Gradient> for Style {
628 fn from(gradient: Gradient) -> Self {
629 Self::default().background(gradient)
630 }
631}
632
633impl From<gradient::Linear> for Style {
634 fn from(gradient: gradient::Linear) -> Self {
635 Self::default().background(gradient)
636 }
637}
638
639pub trait Catalog {
641 type Class<'a>;
643
644 fn default<'a>() -> Self::Class<'a>;
646
647 fn style(&self, class: &Self::Class<'_>) -> Style;
649}
650
651pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
653
654impl<'a, Theme> From<Style> for StyleFn<'a, Theme> {
655 fn from(style: Style) -> Self {
656 Box::new(move |_theme| style)
657 }
658}
659
660impl Catalog for Theme {
661 type Class<'a> = StyleFn<'a, Self>;
662
663 fn default<'a>() -> Self::Class<'a> {
664 Box::new(transparent)
665 }
666
667 fn style(&self, class: &Self::Class<'_>) -> Style {
668 class(self)
669 }
670}
671
672pub fn transparent<Theme>(_theme: &Theme) -> Style {
674 Style::default()
675}
676
677pub fn background(background: impl Into<Background>) -> Style {
679 Style::default().background(background)
680}
681
682pub fn rounded_box(theme: &Theme) -> Style {
684 let palette = theme.extended_palette();
685
686 Style {
687 background: Some(palette.background.weak.color.into()),
688 border: border::rounded(2),
689 ..Style::default()
690 }
691}
692
693pub fn bordered_box(theme: &Theme) -> Style {
695 let palette = theme.extended_palette();
696
697 Style {
698 background: Some(palette.background.weak.color.into()),
699 border: Border {
700 width: 1.0,
701 radius: 0.0.into(),
702 color: palette.background.strong.color,
703 },
704 ..Style::default()
705 }
706}
707
708pub fn dark(_theme: &Theme) -> Style {
710 Style {
711 background: Some(color!(0x111111).into()),
712 text_color: Some(Color::WHITE),
713 border: border::rounded(2),
714 ..Style::default()
715 }
716}