Skip to main content

iced_widget/
container.rs

1//! Containers let you align a widget inside their boundaries.
2//!
3//! # Example
4//! ```no_run
5//! # mod iced { pub mod widget { pub use iced_widget::*; } }
6//! # pub type State = ();
7//! # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
8//! use iced::widget::container;
9//!
10//! enum Message {
11//!     // ...
12//! }
13//!
14//! fn view(state: &State) -> Element<'_, Message> {
15//!     container("This text is centered inside a rounded box!")
16//!         .padding(10)
17//!         .center(800)
18//!         .style(container::rounded_box)
19//!         .into()
20//! }
21//! ```
22use 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
37/// A widget that aligns its contents inside of its boundaries.
38///
39/// # Example
40/// ```no_run
41/// # mod iced { pub mod widget { pub use iced_widget::*; } }
42/// # pub type State = ();
43/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
44/// use iced::widget::container;
45///
46/// enum Message {
47///     // ...
48/// }
49///
50/// fn view(state: &State) -> Element<'_, Message> {
51///     container("This text is centered inside a rounded box!")
52///         .padding(10)
53///         .center(800)
54///         .style(container::rounded_box)
55///         .into()
56/// }
57/// ```
58pub 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    /// Creates a [`Container`] with the given content.
80    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    /// Sets the [`widget::Id`] of the [`Container`].
97    pub fn id(mut self, id: impl Into<widget::Id>) -> Self {
98        self.id = Some(id.into());
99        self
100    }
101
102    /// Sets the [`Padding`] of the [`Container`].
103    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
104        self.padding = padding.into();
105        self
106    }
107
108    /// Sets the width of the [`Container`].
109    pub fn width(mut self, width: impl Into<Length>) -> Self {
110        self.width = width.into();
111        self
112    }
113
114    /// Sets the height of the [`Container`].
115    pub fn height(mut self, height: impl Into<Length>) -> Self {
116        self.height = height.into();
117        self
118    }
119
120    /// Sets the width of the [`Container`] and centers its contents horizontally.
121    pub fn center_x(self, width: impl Into<Length>) -> Self {
122        self.width(width).align_x(alignment::Horizontal::Center)
123    }
124
125    /// Sets the height of the [`Container`] and centers its contents vertically.
126    pub fn center_y(self, height: impl Into<Length>) -> Self {
127        self.height(height).align_y(alignment::Vertical::Center)
128    }
129
130    /// Sets the width and height of the [`Container`] and centers its contents in
131    /// both the horizontal and vertical axes.
132    ///
133    /// This is equivalent to chaining [`center_x`] and [`center_y`].
134    ///
135    /// [`center_x`]: Self::center_x
136    /// [`center_y`]: Self::center_y
137    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    /// Sets the width of the [`Container`] and aligns its contents to the left.
144    pub fn align_left(self, width: impl Into<Length>) -> Self {
145        self.width(width).align_x(alignment::Horizontal::Left)
146    }
147
148    /// Sets the width of the [`Container`] and aligns its contents to the right.
149    pub fn align_right(self, width: impl Into<Length>) -> Self {
150        self.width(width).align_x(alignment::Horizontal::Right)
151    }
152
153    /// Sets the height of the [`Container`] and aligns its contents to the top.
154    pub fn align_top(self, height: impl Into<Length>) -> Self {
155        self.height(height).align_y(alignment::Vertical::Top)
156    }
157
158    /// Sets the height of the [`Container`] and aligns its contents to the bottom.
159    pub fn align_bottom(self, height: impl Into<Length>) -> Self {
160        self.height(height).align_y(alignment::Vertical::Bottom)
161    }
162
163    /// Sets the content alignment for the horizontal axis of the [`Container`].
164    pub fn align_x(mut self, alignment: impl Into<alignment::Horizontal>) -> Self {
165        self.horizontal_alignment = alignment.into();
166        self
167    }
168
169    /// Sets the content alignment for the vertical axis of the [`Container`].
170    pub fn align_y(mut self, alignment: impl Into<alignment::Vertical>) -> Self {
171        self.vertical_alignment = alignment.into();
172        self
173    }
174
175    /// Sets whether the contents of the [`Container`] should be clipped on
176    /// overflow.
177    pub fn clip(mut self, clip: bool) -> Self {
178        self.clip = clip;
179        self
180    }
181
182    /// Sets the style of the [`Container`].
183    #[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    /// Sets the style class of the [`Container`].
193    #[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
368/// Computes the layout of a [`Container`].
369pub 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
394/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
395pub 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/// The appearance of a container.
415#[derive(Debug, Clone, Copy, PartialEq)]
416pub struct Style {
417    /// The text [`Color`] of the container.
418    pub text_color: Option<Color>,
419    /// The [`Background`] of the container.
420    pub background: Option<Background>,
421    /// The [`Border`] of the container.
422    pub border: Border,
423    /// The [`Shadow`] of the container.
424    pub shadow: Shadow,
425    /// Whether the container should be snapped to the pixel grid.
426    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    /// Updates the text color of the [`Style`].
443    pub fn color(self, color: impl Into<Color>) -> Self {
444        Self {
445            text_color: Some(color.into()),
446            ..self
447        }
448    }
449
450    /// Updates the border of the [`Style`].
451    pub fn border(self, border: impl Into<Border>) -> Self {
452        Self {
453            border: border.into(),
454            ..self
455        }
456    }
457
458    /// Updates the background of the [`Style`].
459    pub fn background(self, background: impl Into<Background>) -> Self {
460        Self {
461            background: Some(background.into()),
462            ..self
463        }
464    }
465
466    /// Updates the shadow of the [`Style`].
467    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
493/// The theme catalog of a [`Container`].
494pub trait Catalog {
495    /// The item class of the [`Catalog`].
496    type Class<'a>;
497
498    /// The default class produced by the [`Catalog`].
499    fn default<'a>() -> Self::Class<'a>;
500
501    /// The [`Style`] of a class with the given status.
502    fn style(&self, class: &Self::Class<'_>) -> Style;
503}
504
505/// A styling function for a [`Container`].
506pub 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
526/// A transparent [`Container`].
527pub fn transparent<Theme>(_theme: &Theme) -> Style {
528    Style::default()
529}
530
531/// A [`Container`] with the given [`Background`].
532pub fn background(background: impl Into<Background>) -> Style {
533    Style::default().background(background)
534}
535
536/// A rounded [`Container`] with a background.
537pub 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
548/// A bordered [`Container`] with a background.
549pub 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
564/// A [`Container`] with a dark background and white text.
565pub fn dark(_theme: &Theme) -> Style {
566    style(theme::palette::Pair {
567        color: color!(0x111111),
568        text: Color::WHITE,
569    })
570}
571
572/// A [`Container`] with a primary background color.
573pub fn primary(theme: &Theme) -> Style {
574    let palette = theme.palette();
575
576    style(palette.primary.base)
577}
578
579/// A [`Container`] with a secondary background color.
580pub fn secondary(theme: &Theme) -> Style {
581    let palette = theme.palette();
582
583    style(palette.secondary.base)
584}
585
586/// A [`Container`] with a success background color.
587pub fn success(theme: &Theme) -> Style {
588    let palette = theme.palette();
589
590    style(palette.success.base)
591}
592
593/// A [`Container`] with a warning background color.
594pub fn warning(theme: &Theme) -> Style {
595    let palette = theme.palette();
596
597    style(palette.warning.base)
598}
599
600/// A [`Container`] with a danger background color.
601pub 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}