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