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::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
6//! # use iced::widget::Widget;
7//! # pub type State = ();
8//! use iced::widget::container;
9//!
10//! enum Message {
11//!     // ...
12//! }
13//!
14//! fn view(state: &State) -> impl Widget<Message> {
15//!     container("This text is centered inside a rounded box!")
16//!         .padding(10)
17//!         .center(800)
18//!         .style(container::rounded_box)
19//! }
20//! ```
21use crate::core::alignment::{self, Alignment};
22use crate::core::border::{self, Border};
23use crate::core::gradient::{self, Gradient};
24use crate::core::layout;
25use crate::core::mouse;
26use crate::core::overlay;
27use crate::core::renderer;
28use crate::core::theme;
29use crate::core::widget::tree::{self, Tree};
30use crate::core::widget::{self, Operation};
31use crate::core::{
32    self, Background, Color, Event, Layout, Length, Padding, Rectangle, Shadow, Shell, Size, Theme,
33    Vector, Widget, color,
34};
35
36/// A widget that aligns its contents inside of its boundaries.
37///
38/// # Example
39/// ```no_run
40/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
41/// # use iced::widget::Widget;
42/// # pub type State = ();
43/// use iced::widget::container;
44///
45/// enum Message {
46///     // ...
47/// }
48///
49/// fn view(state: &State) -> impl Widget<Message> {
50///     container("This text is centered inside a rounded box!")
51///         .padding(10)
52///         .center(800)
53///         .style(container::rounded_box)
54/// }
55/// ```
56pub struct Container<'a, W, Theme = crate::Theme>
57where
58    Theme: Catalog,
59{
60    id: Option<widget::Id>,
61    padding: Padding,
62    width: Length,
63    height: Length,
64    horizontal_alignment: alignment::Horizontal,
65    vertical_alignment: alignment::Vertical,
66    clip: bool,
67    content: W,
68    class: Theme::Class<'a>,
69}
70
71impl<'a, W, Theme> Container<'a, W, Theme>
72where
73    Theme: Catalog,
74{
75    /// Creates a [`Container`] with the given content.
76    pub fn new(content: W) -> Self {
77        Container {
78            id: None,
79            padding: Padding::ZERO,
80            width: Length::Fit,
81            height: Length::Fit,
82            horizontal_alignment: alignment::Horizontal::Left,
83            vertical_alignment: alignment::Vertical::Top,
84            clip: false,
85            class: Theme::default(),
86            content,
87        }
88    }
89
90    /// Sets the [`widget::Id`] of the [`Container`].
91    pub fn id(mut self, id: impl Into<widget::Id>) -> Self {
92        self.id = Some(id.into());
93        self
94    }
95
96    /// Sets the [`Padding`] of the [`Container`].
97    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
98        self.padding = padding.into();
99        self
100    }
101
102    /// Sets the width of the [`Container`].
103    pub fn width(mut self, width: impl Into<Length>) -> Self {
104        self.width = width.into();
105        self
106    }
107
108    /// Sets the height of the [`Container`].
109    pub fn height(mut self, height: impl Into<Length>) -> Self {
110        self.height = height.into();
111        self
112    }
113
114    /// Sets the width of the [`Container`] and centers its contents horizontally.
115    pub fn center_x(self, width: impl Into<Length>) -> Self {
116        self.width(width).align_x(alignment::Horizontal::Center)
117    }
118
119    /// Sets the height of the [`Container`] and centers its contents vertically.
120    pub fn center_y(self, height: impl Into<Length>) -> Self {
121        self.height(height).align_y(alignment::Vertical::Center)
122    }
123
124    /// Sets the width and height of the [`Container`] and centers its contents in
125    /// both the horizontal and vertical axes.
126    ///
127    /// This is equivalent to chaining [`center_x`] and [`center_y`].
128    ///
129    /// [`center_x`]: Self::center_x
130    /// [`center_y`]: Self::center_y
131    pub fn center(self, length: impl Into<Length>) -> Self {
132        let length = length.into();
133
134        self.center_x(length).center_y(length)
135    }
136
137    /// Sets the width of the [`Container`] and aligns its contents to the left.
138    pub fn align_left(self, width: impl Into<Length>) -> Self {
139        self.width(width).align_x(alignment::Horizontal::Left)
140    }
141
142    /// Sets the width of the [`Container`] and aligns its contents to the right.
143    pub fn align_right(self, width: impl Into<Length>) -> Self {
144        self.width(width).align_x(alignment::Horizontal::Right)
145    }
146
147    /// Sets the height of the [`Container`] and aligns its contents to the top.
148    pub fn align_top(self, height: impl Into<Length>) -> Self {
149        self.height(height).align_y(alignment::Vertical::Top)
150    }
151
152    /// Sets the height of the [`Container`] and aligns its contents to the bottom.
153    pub fn align_bottom(self, height: impl Into<Length>) -> Self {
154        self.height(height).align_y(alignment::Vertical::Bottom)
155    }
156
157    /// Sets the content alignment for the horizontal axis of the [`Container`].
158    pub fn align_x(mut self, alignment: impl Into<alignment::Horizontal>) -> Self {
159        self.horizontal_alignment = alignment.into();
160        self
161    }
162
163    /// Sets the content alignment for the vertical axis of the [`Container`].
164    pub fn align_y(mut self, alignment: impl Into<alignment::Vertical>) -> Self {
165        self.vertical_alignment = alignment.into();
166        self
167    }
168
169    /// Sets whether the contents of the [`Container`] should be clipped on
170    /// overflow.
171    pub fn clip(mut self, clip: bool) -> Self {
172        self.clip = clip;
173        self
174    }
175
176    /// Sets the style of the [`Container`].
177    #[must_use]
178    pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
179    where
180        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
181    {
182        self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
183        self
184    }
185
186    /// Sets the style class of the [`Container`].
187    #[must_use]
188    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
189        self.class = class.into();
190        self
191    }
192}
193
194impl<W, Theme> widget::Meta for Container<'_, W, Theme> where Theme: Catalog {}
195
196impl<W, Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Container<'_, W, Theme>
197where
198    Theme: Catalog,
199    Renderer: core::Renderer,
200    W: Widget<Message, Theme, Renderer>,
201{
202    fn tag(&self) -> tree::Tag {
203        self.content.tag()
204    }
205
206    fn state(&self) -> tree::State {
207        self.content.state()
208    }
209
210    fn diff(&mut self, tree: &mut Tree) {
211        tree.diff_children(std::slice::from_mut(&mut self.content));
212
213        let size = self.content.size();
214        self.width = self.width.stack(size.width);
215        self.height = self.height.stack(size.height);
216    }
217
218    fn size(&self) -> Size<Length> {
219        Size {
220            width: self.width,
221            height: self.height,
222        }
223    }
224
225    fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits) {
226        layout(
227            tree,
228            limits,
229            self.width,
230            self.height,
231            self.padding,
232            self.horizontal_alignment,
233            self.vertical_alignment,
234            |tree, limits| {
235                self.content.layout(tree, renderer, limits);
236                tree.size
237            },
238        );
239    }
240
241    fn operate(
242        &mut self,
243        tree: &mut Tree,
244        layout: Layout,
245        viewport: &Rectangle,
246        renderer: &Renderer,
247        operation: &mut dyn Operation,
248    ) {
249        let viewport = if self.clip {
250            layout.bounds().intersection(viewport).unwrap_or_default()
251        } else {
252            *viewport
253        };
254
255        operation.container(self.id.as_ref(), layout.bounds(), &viewport);
256        operation.traverse(&mut |operation| {
257            let (layout, tree) = layout.iter_mut(&mut tree.children).next().unwrap();
258
259            self.content
260                .operate(tree, layout, &viewport, renderer, operation);
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        let (layout, tree) = layout.iter_mut(&mut tree.children).next().unwrap();
275
276        self.content
277            .update(tree, event, layout, cursor, renderer, shell, viewport);
278    }
279
280    fn mouse_interaction(
281        &self,
282        tree: &Tree,
283        layout: Layout,
284        cursor: mouse::Cursor,
285        viewport: &Rectangle,
286        renderer: &Renderer,
287    ) -> mouse::Interaction {
288        let (layout, tree) = layout.iter(&tree.children).next().unwrap();
289
290        self.content
291            .mouse_interaction(tree, layout, cursor, viewport, renderer)
292    }
293
294    fn draw(
295        &self,
296        tree: &Tree,
297        renderer: &mut Renderer,
298        theme: &Theme,
299        renderer_style: &renderer::Style,
300        layout: Layout,
301        cursor: mouse::Cursor,
302        viewport: &Rectangle,
303    ) {
304        let bounds = layout.bounds();
305        let style = theme.style(&self.class);
306
307        if let Some(clipped_viewport) = bounds.intersection(viewport) {
308            draw_background(renderer, &style, bounds);
309
310            let (layout, tree) = layout.iter(&tree.children).next().unwrap();
311
312            self.content.draw(
313                tree,
314                renderer,
315                theme,
316                &renderer::Style {
317                    text_color: style.text_color.unwrap_or(renderer_style.text_color),
318                },
319                layout,
320                cursor,
321                if self.clip {
322                    &clipped_viewport
323                } else {
324                    viewport
325                },
326            );
327        }
328    }
329
330    fn overlay<'b>(
331        &'b mut self,
332        tree: &'b mut Tree,
333        layout: Layout,
334        renderer: &Renderer,
335        viewport: &Rectangle,
336        translation: Vector,
337        window: Size,
338    ) -> Vec<overlay::Element<'b, Message, Theme, Renderer>> {
339        let (layout, tree) = layout.iter_mut(&mut tree.children).next().unwrap();
340
341        self.content
342            .overlay(tree, layout, renderer, viewport, translation, window)
343    }
344}
345
346/// Computes the layout of a [`Container`].
347pub fn layout(
348    tree: &mut Tree,
349    limits: &layout::Limits,
350    width: Length,
351    height: Length,
352    padding: Padding,
353    horizontal_alignment: alignment::Horizontal,
354    vertical_alignment: alignment::Vertical,
355    layout_content: impl FnOnce(&mut Tree, &layout::Limits) -> Size,
356) {
357    let limits = limits.width(width).height(height);
358
359    layout::positioned(
360        tree,
361        &limits,
362        width,
363        height,
364        padding,
365        |tree, limits| layout_content(tree, &limits.loose()),
366        |content, container| {
367            content.align(
368                container,
369                Alignment::from(horizontal_alignment),
370                Alignment::from(vertical_alignment),
371            )
372        },
373    );
374}
375
376/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
377pub fn draw_background<Renderer>(renderer: &mut Renderer, style: &Style, bounds: Rectangle)
378where
379    Renderer: core::Renderer,
380{
381    if style.background.is_some() || style.border.width > 0.0 || style.shadow.color.a > 0.0 {
382        renderer.fill_quad(
383            renderer::Quad {
384                bounds,
385                border: style.border,
386                shadow: style.shadow,
387                snap: style.snap,
388            },
389            style
390                .background
391                .unwrap_or(Background::Color(Color::TRANSPARENT)),
392        );
393    }
394}
395
396/// The appearance of a container.
397#[derive(Debug, Clone, Copy, PartialEq)]
398pub struct Style {
399    /// The text [`Color`] of the container.
400    pub text_color: Option<Color>,
401    /// The [`Background`] of the container.
402    pub background: Option<Background>,
403    /// The [`Border`] of the container.
404    pub border: Border,
405    /// The [`Shadow`] of the container.
406    pub shadow: Shadow,
407    /// Whether the container should be snapped to the pixel grid.
408    pub snap: bool,
409}
410
411impl Default for Style {
412    fn default() -> Self {
413        Self {
414            text_color: None,
415            background: None,
416            border: Border::default(),
417            shadow: Shadow::default(),
418            snap: renderer::CRISP,
419        }
420    }
421}
422
423impl Style {
424    /// Updates the text color of the [`Style`].
425    pub fn color(self, color: impl Into<Color>) -> Self {
426        Self {
427            text_color: Some(color.into()),
428            ..self
429        }
430    }
431
432    /// Updates the border of the [`Style`].
433    pub fn border(self, border: impl Into<Border>) -> Self {
434        Self {
435            border: border.into(),
436            ..self
437        }
438    }
439
440    /// Updates the background of the [`Style`].
441    pub fn background(self, background: impl Into<Background>) -> Self {
442        Self {
443            background: Some(background.into()),
444            ..self
445        }
446    }
447
448    /// Updates the shadow of the [`Style`].
449    pub fn shadow(self, shadow: impl Into<Shadow>) -> Self {
450        Self {
451            shadow: shadow.into(),
452            ..self
453        }
454    }
455}
456
457impl From<Color> for Style {
458    fn from(color: Color) -> Self {
459        Self::default().background(color)
460    }
461}
462
463impl From<Gradient> for Style {
464    fn from(gradient: Gradient) -> Self {
465        Self::default().background(gradient)
466    }
467}
468
469impl From<gradient::Linear> for Style {
470    fn from(gradient: gradient::Linear) -> Self {
471        Self::default().background(gradient)
472    }
473}
474
475/// The theme catalog of a [`Container`].
476pub trait Catalog {
477    /// The item class of the [`Catalog`].
478    type Class<'a>;
479
480    /// The default class produced by the [`Catalog`].
481    fn default<'a>() -> Self::Class<'a>;
482
483    /// The [`Style`] of a class with the given status.
484    fn style(&self, class: &Self::Class<'_>) -> Style;
485}
486
487/// A styling function for a [`Container`].
488pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
489
490impl<Theme> From<Style> for StyleFn<'_, Theme> {
491    fn from(style: Style) -> Self {
492        Box::new(move |_theme| style)
493    }
494}
495
496impl Catalog for Theme {
497    type Class<'a> = StyleFn<'a, Self>;
498
499    fn default<'a>() -> Self::Class<'a> {
500        Box::new(transparent)
501    }
502
503    fn style(&self, class: &Self::Class<'_>) -> Style {
504        class(self)
505    }
506}
507
508/// A transparent [`Container`].
509pub fn transparent<Theme>(_theme: &Theme) -> Style {
510    Style::default()
511}
512
513/// A [`Container`] with the given [`Background`].
514pub fn background(background: impl Into<Background>) -> Style {
515    Style::default().background(background)
516}
517
518/// A rounded [`Container`] with a background.
519pub fn rounded_box(theme: &Theme) -> Style {
520    let palette = theme.palette();
521
522    Style {
523        background: Some(palette.background.weak.color.into()),
524        text_color: Some(palette.background.weak.text),
525        border: border::rounded(2),
526        ..Style::default()
527    }
528}
529
530/// A bordered [`Container`] with a background.
531pub fn bordered_box(theme: &Theme) -> Style {
532    let palette = theme.palette();
533
534    Style {
535        background: Some(palette.background.weakest.color.into()),
536        text_color: Some(palette.background.weakest.text),
537        border: Border {
538            width: 1.0,
539            radius: 5.0.into(),
540            color: palette.background.weak.color,
541        },
542        ..Style::default()
543    }
544}
545
546/// A [`Container`] with a dark background and white text.
547pub fn dark(_theme: &Theme) -> Style {
548    style(theme::palette::Pair {
549        color: color!(0x111111),
550        text: Color::WHITE,
551    })
552}
553
554/// A [`Container`] with a primary background color.
555pub fn primary(theme: &Theme) -> Style {
556    let palette = theme.palette();
557
558    style(palette.primary.base)
559}
560
561/// A [`Container`] with a secondary background color.
562pub fn secondary(theme: &Theme) -> Style {
563    let palette = theme.palette();
564
565    style(palette.secondary.base)
566}
567
568/// A [`Container`] with a success background color.
569pub fn success(theme: &Theme) -> Style {
570    let palette = theme.palette();
571
572    style(palette.success.base)
573}
574
575/// A [`Container`] with a warning background color.
576pub fn warning(theme: &Theme) -> Style {
577    let palette = theme.palette();
578
579    style(palette.warning.base)
580}
581
582/// A [`Container`] with a danger background color.
583pub fn danger(theme: &Theme) -> Style {
584    let palette = theme.palette();
585
586    style(palette.danger.base)
587}
588
589fn style(pair: theme::palette::Pair) -> Style {
590    Style {
591        background: Some(pair.color.into()),
592        text_color: Some(pair.text),
593        border: border::rounded(2),
594        ..Style::default()
595    }
596}