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