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