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, Rectangle, Shadow, Shell, Size, Theme, Vector, Widget,
35 color,
36};
37
38pub struct Container<
60 'a,
61 Message,
62 Theme = crate::Theme,
63 Renderer = crate::Renderer,
64> where
65 Theme: Catalog,
66 Renderer: core::Renderer,
67{
68 id: Option<widget::Id>,
69 padding: Padding,
70 width: Length,
71 height: Length,
72 max_width: f32,
73 max_height: f32,
74 horizontal_alignment: alignment::Horizontal,
75 vertical_alignment: alignment::Vertical,
76 clip: bool,
77 content: Element<'a, Message, Theme, Renderer>,
78 class: Theme::Class<'a>,
79}
80
81impl<'a, Message, Theme, Renderer> Container<'a, Message, Theme, Renderer>
82where
83 Theme: Catalog,
84 Renderer: core::Renderer,
85{
86 pub fn new(
88 content: impl Into<Element<'a, Message, Theme, Renderer>>,
89 ) -> Self {
90 let content = content.into();
91 let size = content.as_widget().size_hint();
92
93 Container {
94 id: None,
95 padding: Padding::ZERO,
96 width: size.width.fluid(),
97 height: size.height.fluid(),
98 max_width: f32::INFINITY,
99 max_height: f32::INFINITY,
100 horizontal_alignment: alignment::Horizontal::Left,
101 vertical_alignment: alignment::Vertical::Top,
102 clip: false,
103 class: Theme::default(),
104 content,
105 }
106 }
107
108 pub fn id(mut self, id: impl Into<widget::Id>) -> Self {
110 self.id = Some(id.into());
111 self
112 }
113
114 pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
116 self.padding = padding.into();
117 self
118 }
119
120 pub fn width(mut self, width: impl Into<Length>) -> Self {
122 self.width = width.into();
123 self
124 }
125
126 pub fn height(mut self, height: impl Into<Length>) -> Self {
128 self.height = height.into();
129 self
130 }
131
132 pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {
134 self.max_width = max_width.into().0;
135 self
136 }
137
138 pub fn max_height(mut self, max_height: impl Into<Pixels>) -> Self {
140 self.max_height = max_height.into().0;
141 self
142 }
143
144 pub fn center_x(self, width: impl Into<Length>) -> Self {
146 self.width(width).align_x(alignment::Horizontal::Center)
147 }
148
149 pub fn center_y(self, height: impl Into<Length>) -> Self {
151 self.height(height).align_y(alignment::Vertical::Center)
152 }
153
154 pub fn center(self, length: impl Into<Length>) -> Self {
162 let length = length.into();
163
164 self.center_x(length).center_y(length)
165 }
166
167 pub fn align_left(self, width: impl Into<Length>) -> Self {
169 self.width(width).align_x(alignment::Horizontal::Left)
170 }
171
172 pub fn align_right(self, width: impl Into<Length>) -> Self {
174 self.width(width).align_x(alignment::Horizontal::Right)
175 }
176
177 pub fn align_top(self, height: impl Into<Length>) -> Self {
179 self.height(height).align_y(alignment::Vertical::Top)
180 }
181
182 pub fn align_bottom(self, height: impl Into<Length>) -> Self {
184 self.height(height).align_y(alignment::Vertical::Bottom)
185 }
186
187 pub fn align_x(
189 mut self,
190 alignment: impl Into<alignment::Horizontal>,
191 ) -> Self {
192 self.horizontal_alignment = alignment.into();
193 self
194 }
195
196 pub fn align_y(
198 mut self,
199 alignment: impl Into<alignment::Vertical>,
200 ) -> Self {
201 self.vertical_alignment = alignment.into();
202 self
203 }
204
205 pub fn clip(mut self, clip: bool) -> Self {
208 self.clip = clip;
209 self
210 }
211
212 #[must_use]
214 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
215 where
216 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
217 {
218 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
219 self
220 }
221
222 #[must_use]
224 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
225 self.class = class.into();
226 self
227 }
228}
229
230impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
231 for Container<'_, Message, Theme, Renderer>
232where
233 Theme: Catalog,
234 Renderer: core::Renderer,
235{
236 fn tag(&self) -> tree::Tag {
237 self.content.as_widget().tag()
238 }
239
240 fn state(&self) -> tree::State {
241 self.content.as_widget().state()
242 }
243
244 fn children(&self) -> Vec<Tree> {
245 self.content.as_widget().children()
246 }
247
248 fn diff(&self, tree: &mut Tree) {
249 self.content.as_widget().diff(tree);
250 }
251
252 fn size(&self) -> Size<Length> {
253 Size {
254 width: self.width,
255 height: self.height,
256 }
257 }
258
259 fn layout(
260 &mut self,
261 tree: &mut Tree,
262 renderer: &Renderer,
263 limits: &layout::Limits,
264 ) -> layout::Node {
265 layout(
266 limits,
267 self.width,
268 self.height,
269 self.max_width,
270 self.max_height,
271 self.padding,
272 self.horizontal_alignment,
273 self.vertical_alignment,
274 |limits| {
275 self.content.as_widget_mut().layout(tree, renderer, limits)
276 },
277 )
278 }
279
280 fn operate(
281 &mut self,
282 tree: &mut Tree,
283 layout: Layout<'_>,
284 renderer: &Renderer,
285 operation: &mut dyn Operation,
286 ) {
287 operation.container(self.id.as_ref(), layout.bounds());
288 operation.traverse(&mut |operation| {
289 self.content.as_widget_mut().operate(
290 tree,
291 layout.children().next().unwrap(),
292 renderer,
293 operation,
294 );
295 });
296 }
297
298 fn update(
299 &mut self,
300 tree: &mut Tree,
301 event: &Event,
302 layout: Layout<'_>,
303 cursor: mouse::Cursor,
304 renderer: &Renderer,
305 clipboard: &mut dyn Clipboard,
306 shell: &mut Shell<'_, Message>,
307 viewport: &Rectangle,
308 ) {
309 self.content.as_widget_mut().update(
310 tree,
311 event,
312 layout.children().next().unwrap(),
313 cursor,
314 renderer,
315 clipboard,
316 shell,
317 viewport,
318 );
319 }
320
321 fn mouse_interaction(
322 &self,
323 tree: &Tree,
324 layout: Layout<'_>,
325 cursor: mouse::Cursor,
326 viewport: &Rectangle,
327 renderer: &Renderer,
328 ) -> mouse::Interaction {
329 self.content.as_widget().mouse_interaction(
330 tree,
331 layout.children().next().unwrap(),
332 cursor,
333 viewport,
334 renderer,
335 )
336 }
337
338 fn draw(
339 &self,
340 tree: &Tree,
341 renderer: &mut Renderer,
342 theme: &Theme,
343 renderer_style: &renderer::Style,
344 layout: Layout<'_>,
345 cursor: mouse::Cursor,
346 viewport: &Rectangle,
347 ) {
348 let bounds = layout.bounds();
349 let style = theme.style(&self.class);
350
351 if let Some(clipped_viewport) = bounds.intersection(viewport) {
352 draw_background(renderer, &style, bounds);
353
354 self.content.as_widget().draw(
355 tree,
356 renderer,
357 theme,
358 &renderer::Style {
359 text_color: style
360 .text_color
361 .unwrap_or(renderer_style.text_color),
362 },
363 layout.children().next().unwrap(),
364 cursor,
365 if self.clip {
366 &clipped_viewport
367 } else {
368 viewport
369 },
370 );
371 }
372 }
373
374 fn overlay<'b>(
375 &'b mut self,
376 tree: &'b mut Tree,
377 layout: Layout<'b>,
378 renderer: &Renderer,
379 viewport: &Rectangle,
380 translation: Vector,
381 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
382 self.content.as_widget_mut().overlay(
383 tree,
384 layout.children().next().unwrap(),
385 renderer,
386 viewport,
387 translation,
388 )
389 }
390}
391
392impl<'a, Message, Theme, Renderer> From<Container<'a, Message, Theme, Renderer>>
393 for Element<'a, Message, Theme, Renderer>
394where
395 Message: 'a,
396 Theme: Catalog + 'a,
397 Renderer: core::Renderer + 'a,
398{
399 fn from(
400 container: Container<'a, Message, Theme, Renderer>,
401 ) -> Element<'a, Message, Theme, Renderer> {
402 Element::new(container)
403 }
404}
405
406pub fn layout(
408 limits: &layout::Limits,
409 width: Length,
410 height: Length,
411 max_width: f32,
412 max_height: f32,
413 padding: Padding,
414 horizontal_alignment: alignment::Horizontal,
415 vertical_alignment: alignment::Vertical,
416 layout_content: impl FnOnce(&layout::Limits) -> layout::Node,
417) -> layout::Node {
418 layout::positioned(
419 &limits.max_width(max_width).max_height(max_height),
420 width,
421 height,
422 padding,
423 |limits| layout_content(&limits.loose()),
424 |content, size| {
425 content.align(
426 Alignment::from(horizontal_alignment),
427 Alignment::from(vertical_alignment),
428 size,
429 )
430 },
431 )
432}
433
434pub fn draw_background<Renderer>(
436 renderer: &mut Renderer,
437 style: &Style,
438 bounds: Rectangle,
439) where
440 Renderer: core::Renderer,
441{
442 if style.background.is_some()
443 || style.border.width > 0.0
444 || style.shadow.color.a > 0.0
445 {
446 renderer.fill_quad(
447 renderer::Quad {
448 bounds,
449 border: style.border,
450 shadow: style.shadow,
451 snap: style.snap,
452 },
453 style
454 .background
455 .unwrap_or(Background::Color(Color::TRANSPARENT)),
456 );
457 }
458}
459
460#[derive(Debug, Clone, Copy, PartialEq, Default)]
462pub struct Style {
463 pub text_color: Option<Color>,
465 pub background: Option<Background>,
467 pub border: Border,
469 pub shadow: Shadow,
471 pub snap: bool,
473}
474
475impl Style {
476 pub fn color(self, color: impl Into<Color>) -> Self {
478 Self {
479 text_color: Some(color.into()),
480 ..self
481 }
482 }
483
484 pub fn border(self, border: impl Into<Border>) -> Self {
486 Self {
487 border: border.into(),
488 ..self
489 }
490 }
491
492 pub fn background(self, background: impl Into<Background>) -> Self {
494 Self {
495 background: Some(background.into()),
496 ..self
497 }
498 }
499
500 pub fn shadow(self, shadow: impl Into<Shadow>) -> Self {
502 Self {
503 shadow: shadow.into(),
504 ..self
505 }
506 }
507}
508
509impl From<Color> for Style {
510 fn from(color: Color) -> Self {
511 Self::default().background(color)
512 }
513}
514
515impl From<Gradient> for Style {
516 fn from(gradient: Gradient) -> Self {
517 Self::default().background(gradient)
518 }
519}
520
521impl From<gradient::Linear> for Style {
522 fn from(gradient: gradient::Linear) -> Self {
523 Self::default().background(gradient)
524 }
525}
526
527pub trait Catalog {
529 type Class<'a>;
531
532 fn default<'a>() -> Self::Class<'a>;
534
535 fn style(&self, class: &Self::Class<'_>) -> Style;
537}
538
539pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
541
542impl<Theme> From<Style> for StyleFn<'_, Theme> {
543 fn from(style: Style) -> Self {
544 Box::new(move |_theme| style)
545 }
546}
547
548impl Catalog for Theme {
549 type Class<'a> = StyleFn<'a, Self>;
550
551 fn default<'a>() -> Self::Class<'a> {
552 Box::new(transparent)
553 }
554
555 fn style(&self, class: &Self::Class<'_>) -> Style {
556 class(self)
557 }
558}
559
560pub fn transparent<Theme>(_theme: &Theme) -> Style {
562 Style::default()
563}
564
565pub fn background(background: impl Into<Background>) -> Style {
567 Style::default().background(background)
568}
569
570pub fn rounded_box(theme: &Theme) -> Style {
572 let palette = theme.extended_palette();
573
574 Style {
575 background: Some(palette.background.weak.color.into()),
576 text_color: Some(palette.background.weak.text),
577 border: border::rounded(2),
578 ..Style::default()
579 }
580}
581
582pub fn bordered_box(theme: &Theme) -> Style {
584 let palette = theme.extended_palette();
585
586 Style {
587 background: Some(palette.background.weakest.color.into()),
588 text_color: Some(palette.background.weakest.text),
589 border: Border {
590 width: 1.0,
591 radius: 5.0.into(),
592 color: palette.background.weak.color,
593 },
594 ..Style::default()
595 }
596}
597
598pub fn dark(_theme: &Theme) -> Style {
600 style(theme::palette::Pair {
601 color: color!(0x111111),
602 text: Color::WHITE,
603 })
604}
605
606pub fn primary(theme: &Theme) -> Style {
608 let palette = theme.extended_palette();
609
610 style(palette.primary.base)
611}
612
613pub fn secondary(theme: &Theme) -> Style {
615 let palette = theme.extended_palette();
616
617 style(palette.secondary.base)
618}
619
620pub fn success(theme: &Theme) -> Style {
622 let palette = theme.extended_palette();
623
624 style(palette.success.base)
625}
626
627pub fn danger(theme: &Theme) -> Style {
629 let palette = theme.extended_palette();
630
631 style(palette.danger.base)
632}
633
634fn style(pair: theme::palette::Pair) -> Style {
635 Style {
636 background: Some(pair.color.into()),
637 text_color: Some(pair.text),
638 border: border::rounded(2),
639 ..Style::default()
640 }
641}