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