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, Pixels, Rectangle, Shadow,
34    Shell, 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    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    /// Creates a [`Container`] with the given content.
82    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    /// Sets the [`widget::Id`] of the [`Container`].
101    pub fn id(mut self, id: impl Into<widget::Id>) -> Self {
102        self.id = Some(id.into());
103        self
104    }
105
106    /// Sets the [`Padding`] of the [`Container`].
107    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
108        self.padding = padding.into();
109        self
110    }
111
112    /// Sets the width of the [`Container`].
113    pub fn width(mut self, width: impl Into<Length>) -> Self {
114        self.width = width.into();
115        self
116    }
117
118    /// Sets the height of the [`Container`].
119    pub fn height(mut self, height: impl Into<Length>) -> Self {
120        self.height = height.into();
121        self
122    }
123
124    /// Sets the maximum width of the [`Container`].
125    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    /// Sets the maximum height of the [`Container`].
131    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    /// Sets the width of the [`Container`] and centers its contents horizontally.
137    pub fn center_x(self, width: impl Into<Length>) -> Self {
138        self.width(width).align_x(alignment::Horizontal::Center)
139    }
140
141    /// Sets the height of the [`Container`] and centers its contents vertically.
142    pub fn center_y(self, height: impl Into<Length>) -> Self {
143        self.height(height).align_y(alignment::Vertical::Center)
144    }
145
146    /// Sets the width and height of the [`Container`] and centers its contents in
147    /// both the horizontal and vertical axes.
148    ///
149    /// This is equivalent to chaining [`center_x`] and [`center_y`].
150    ///
151    /// [`center_x`]: Self::center_x
152    /// [`center_y`]: Self::center_y
153    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    /// Sets the width of the [`Container`] and aligns its contents to the left.
160    pub fn align_left(self, width: impl Into<Length>) -> Self {
161        self.width(width).align_x(alignment::Horizontal::Left)
162    }
163
164    /// Sets the width of the [`Container`] and aligns its contents to the right.
165    pub fn align_right(self, width: impl Into<Length>) -> Self {
166        self.width(width).align_x(alignment::Horizontal::Right)
167    }
168
169    /// Sets the height of the [`Container`] and aligns its contents to the top.
170    pub fn align_top(self, height: impl Into<Length>) -> Self {
171        self.height(height).align_y(alignment::Vertical::Top)
172    }
173
174    /// Sets the height of the [`Container`] and aligns its contents to the bottom.
175    pub fn align_bottom(self, height: impl Into<Length>) -> Self {
176        self.height(height).align_y(alignment::Vertical::Bottom)
177    }
178
179    /// Sets the content alignment for the horizontal axis of the [`Container`].
180    pub fn align_x(mut self, alignment: impl Into<alignment::Horizontal>) -> Self {
181        self.horizontal_alignment = alignment.into();
182        self
183    }
184
185    /// Sets the content alignment for the vertical axis of the [`Container`].
186    pub fn align_y(mut self, alignment: impl Into<alignment::Vertical>) -> Self {
187        self.vertical_alignment = alignment.into();
188        self
189    }
190
191    /// Sets whether the contents of the [`Container`] should be clipped on
192    /// overflow.
193    pub fn clip(mut self, clip: bool) -> Self {
194        self.clip = clip;
195        self
196    }
197
198    /// Sets the style of the [`Container`].
199    #[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    /// Sets the style class of the [`Container`].
209    #[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
386/// Computes the layout of a [`Container`].
387pub 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
414/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
415pub 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/// The appearance of a container.
435#[derive(Debug, Clone, Copy, PartialEq)]
436pub struct Style {
437    /// The text [`Color`] of the container.
438    pub text_color: Option<Color>,
439    /// The [`Background`] of the container.
440    pub background: Option<Background>,
441    /// The [`Border`] of the container.
442    pub border: Border,
443    /// The [`Shadow`] of the container.
444    pub shadow: Shadow,
445    /// Whether the container should be snapped to the pixel grid.
446    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    /// Updates the text color of the [`Style`].
463    pub fn color(self, color: impl Into<Color>) -> Self {
464        Self {
465            text_color: Some(color.into()),
466            ..self
467        }
468    }
469
470    /// Updates the border of the [`Style`].
471    pub fn border(self, border: impl Into<Border>) -> Self {
472        Self {
473            border: border.into(),
474            ..self
475        }
476    }
477
478    /// Updates the background of the [`Style`].
479    pub fn background(self, background: impl Into<Background>) -> Self {
480        Self {
481            background: Some(background.into()),
482            ..self
483        }
484    }
485
486    /// Updates the shadow of the [`Style`].
487    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
513/// The theme catalog of a [`Container`].
514pub trait Catalog {
515    /// The item class of the [`Catalog`].
516    type Class<'a>;
517
518    /// The default class produced by the [`Catalog`].
519    fn default<'a>() -> Self::Class<'a>;
520
521    /// The [`Style`] of a class with the given status.
522    fn style(&self, class: &Self::Class<'_>) -> Style;
523}
524
525/// A styling function for a [`Container`].
526pub 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
546/// A transparent [`Container`].
547pub fn transparent<Theme>(_theme: &Theme) -> Style {
548    Style::default()
549}
550
551/// A [`Container`] with the given [`Background`].
552pub fn background(background: impl Into<Background>) -> Style {
553    Style::default().background(background)
554}
555
556/// A rounded [`Container`] with a background.
557pub 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
568/// A bordered [`Container`] with a background.
569pub 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
584/// A [`Container`] with a dark background and white text.
585pub fn dark(_theme: &Theme) -> Style {
586    style(theme::palette::Pair {
587        color: color!(0x111111),
588        text: Color::WHITE,
589    })
590}
591
592/// A [`Container`] with a primary background color.
593pub fn primary(theme: &Theme) -> Style {
594    let palette = theme.palette();
595
596    style(palette.primary.base)
597}
598
599/// A [`Container`] with a secondary background color.
600pub fn secondary(theme: &Theme) -> Style {
601    let palette = theme.palette();
602
603    style(palette.secondary.base)
604}
605
606/// A [`Container`] with a success background color.
607pub fn success(theme: &Theme) -> Style {
608    let palette = theme.palette();
609
610    style(palette.success.base)
611}
612
613/// A [`Container`] with a warning background color.
614pub fn warning(theme: &Theme) -> Style {
615    let palette = theme.palette();
616
617    style(palette.warning.base)
618}
619
620/// A [`Container`] with a danger background color.
621pub 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}