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, Color, Element, Event, Layout, Length, Padding, Pixels, Rectangle, Shadow,
34 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 shell: &mut Shell<'_, Message>,
291 viewport: &Rectangle,
292 ) {
293 self.content.as_widget_mut().update(
294 tree,
295 event,
296 layout.children().next().unwrap(),
297 cursor,
298 renderer,
299 shell,
300 viewport,
301 );
302 }
303
304 fn mouse_interaction(
305 &self,
306 tree: &Tree,
307 layout: Layout<'_>,
308 cursor: mouse::Cursor,
309 viewport: &Rectangle,
310 renderer: &Renderer,
311 ) -> mouse::Interaction {
312 self.content.as_widget().mouse_interaction(
313 tree,
314 layout.children().next().unwrap(),
315 cursor,
316 viewport,
317 renderer,
318 )
319 }
320
321 fn draw(
322 &self,
323 tree: &Tree,
324 renderer: &mut Renderer,
325 theme: &Theme,
326 renderer_style: &renderer::Style,
327 layout: Layout<'_>,
328 cursor: mouse::Cursor,
329 viewport: &Rectangle,
330 ) {
331 let bounds = layout.bounds();
332 let style = theme.style(&self.class);
333
334 if let Some(clipped_viewport) = bounds.intersection(viewport) {
335 draw_background(renderer, &style, bounds);
336
337 self.content.as_widget().draw(
338 tree,
339 renderer,
340 theme,
341 &renderer::Style {
342 text_color: style.text_color.unwrap_or(renderer_style.text_color),
343 },
344 layout.children().next().unwrap(),
345 cursor,
346 if self.clip {
347 &clipped_viewport
348 } else {
349 viewport
350 },
351 );
352 }
353 }
354
355 fn overlay<'b>(
356 &'b mut self,
357 tree: &'b mut Tree,
358 layout: Layout<'b>,
359 renderer: &Renderer,
360 viewport: &Rectangle,
361 translation: Vector,
362 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
363 self.content.as_widget_mut().overlay(
364 tree,
365 layout.children().next().unwrap(),
366 renderer,
367 viewport,
368 translation,
369 )
370 }
371}
372
373impl<'a, Message, Theme, Renderer> From<Container<'a, Message, Theme, Renderer>>
374 for Element<'a, Message, Theme, Renderer>
375where
376 Message: 'a,
377 Theme: Catalog + 'a,
378 Renderer: core::Renderer + 'a,
379{
380 fn from(
381 container: Container<'a, Message, Theme, Renderer>,
382 ) -> Element<'a, Message, Theme, Renderer> {
383 Element::new(container)
384 }
385}
386
387pub fn layout(
389 limits: &layout::Limits,
390 width: Length,
391 height: Length,
392 max_width: f32,
393 max_height: f32,
394 padding: Padding,
395 horizontal_alignment: alignment::Horizontal,
396 vertical_alignment: alignment::Vertical,
397 layout_content: impl FnOnce(&layout::Limits) -> layout::Node,
398) -> layout::Node {
399 layout::positioned(
400 &limits.max_width(max_width).max_height(max_height),
401 width,
402 height,
403 padding,
404 |limits| layout_content(&limits.loose()),
405 |content, size| {
406 content.align(
407 Alignment::from(horizontal_alignment),
408 Alignment::from(vertical_alignment),
409 size,
410 )
411 },
412 )
413}
414
415pub fn draw_background<Renderer>(renderer: &mut Renderer, style: &Style, bounds: Rectangle)
417where
418 Renderer: core::Renderer,
419{
420 if style.background.is_some() || style.border.width > 0.0 || style.shadow.color.a > 0.0 {
421 renderer.fill_quad(
422 renderer::Quad {
423 bounds,
424 border: style.border,
425 shadow: style.shadow,
426 snap: style.snap,
427 },
428 style
429 .background
430 .unwrap_or(Background::Color(Color::TRANSPARENT)),
431 );
432 }
433}
434
435#[derive(Debug, Clone, Copy, PartialEq)]
437pub struct Style {
438 pub text_color: Option<Color>,
440 pub background: Option<Background>,
442 pub border: Border,
444 pub shadow: Shadow,
446 pub snap: bool,
448}
449
450impl Default for Style {
451 fn default() -> Self {
452 Self {
453 text_color: None,
454 background: None,
455 border: Border::default(),
456 shadow: Shadow::default(),
457 snap: renderer::CRISP,
458 }
459 }
460}
461
462impl Style {
463 pub fn color(self, color: impl Into<Color>) -> Self {
465 Self {
466 text_color: Some(color.into()),
467 ..self
468 }
469 }
470
471 pub fn border(self, border: impl Into<Border>) -> Self {
473 Self {
474 border: border.into(),
475 ..self
476 }
477 }
478
479 pub fn background(self, background: impl Into<Background>) -> Self {
481 Self {
482 background: Some(background.into()),
483 ..self
484 }
485 }
486
487 pub fn shadow(self, shadow: impl Into<Shadow>) -> Self {
489 Self {
490 shadow: shadow.into(),
491 ..self
492 }
493 }
494}
495
496impl From<Color> for Style {
497 fn from(color: Color) -> Self {
498 Self::default().background(color)
499 }
500}
501
502impl From<Gradient> for Style {
503 fn from(gradient: Gradient) -> Self {
504 Self::default().background(gradient)
505 }
506}
507
508impl From<gradient::Linear> for Style {
509 fn from(gradient: gradient::Linear) -> Self {
510 Self::default().background(gradient)
511 }
512}
513
514pub trait Catalog {
516 type Class<'a>;
518
519 fn default<'a>() -> Self::Class<'a>;
521
522 fn style(&self, class: &Self::Class<'_>) -> Style;
524}
525
526pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
528
529impl<Theme> From<Style> for StyleFn<'_, Theme> {
530 fn from(style: Style) -> Self {
531 Box::new(move |_theme| style)
532 }
533}
534
535impl Catalog for Theme {
536 type Class<'a> = StyleFn<'a, Self>;
537
538 fn default<'a>() -> Self::Class<'a> {
539 Box::new(transparent)
540 }
541
542 fn style(&self, class: &Self::Class<'_>) -> Style {
543 class(self)
544 }
545}
546
547pub fn transparent<Theme>(_theme: &Theme) -> Style {
549 Style::default()
550}
551
552pub fn background(background: impl Into<Background>) -> Style {
554 Style::default().background(background)
555}
556
557pub fn rounded_box(theme: &Theme) -> Style {
559 let palette = theme.extended_palette();
560
561 Style {
562 background: Some(palette.background.weak.color.into()),
563 text_color: Some(palette.background.weak.text),
564 border: border::rounded(2),
565 ..Style::default()
566 }
567}
568
569pub fn bordered_box(theme: &Theme) -> Style {
571 let palette = theme.extended_palette();
572
573 Style {
574 background: Some(palette.background.weakest.color.into()),
575 text_color: Some(palette.background.weakest.text),
576 border: Border {
577 width: 1.0,
578 radius: 5.0.into(),
579 color: palette.background.weak.color,
580 },
581 ..Style::default()
582 }
583}
584
585pub fn dark(_theme: &Theme) -> Style {
587 style(theme::palette::Pair {
588 color: color!(0x111111),
589 text: Color::WHITE,
590 })
591}
592
593pub fn primary(theme: &Theme) -> Style {
595 let palette = theme.extended_palette();
596
597 style(palette.primary.base)
598}
599
600pub fn secondary(theme: &Theme) -> Style {
602 let palette = theme.extended_palette();
603
604 style(palette.secondary.base)
605}
606
607pub fn success(theme: &Theme) -> Style {
609 let palette = theme.extended_palette();
610
611 style(palette.success.base)
612}
613
614pub fn warning(theme: &Theme) -> Style {
616 let palette = theme.extended_palette();
617
618 style(palette.warning.base)
619}
620
621pub fn danger(theme: &Theme) -> Style {
623 let palette = theme.extended_palette();
624
625 style(palette.danger.base)
626}
627
628fn style(pair: theme::palette::Pair) -> Style {
629 Style {
630 background: Some(pair.color.into()),
631 text_color: Some(pair.text),
632 border: border::rounded(2),
633 ..Style::default()
634 }
635}