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, Clipboard, Color, Element, Event, Layout, Length, Padding, Pixels, Rectangle,
34    Shadow, 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        clipboard: &mut dyn Clipboard,
291        shell: &mut Shell<'_, Message>,
292        viewport: &Rectangle,
293    ) {
294        self.content.as_widget_mut().update(
295            tree,
296            event,
297            layout.children().next().unwrap(),
298            cursor,
299            renderer,
300            clipboard,
301            shell,
302            viewport,
303        );
304    }
305
306    fn mouse_interaction(
307        &self,
308        tree: &Tree,
309        layout: Layout<'_>,
310        cursor: mouse::Cursor,
311        viewport: &Rectangle,
312        renderer: &Renderer,
313    ) -> mouse::Interaction {
314        self.content.as_widget().mouse_interaction(
315            tree,
316            layout.children().next().unwrap(),
317            cursor,
318            viewport,
319            renderer,
320        )
321    }
322
323    fn draw(
324        &self,
325        tree: &Tree,
326        renderer: &mut Renderer,
327        theme: &Theme,
328        renderer_style: &renderer::Style,
329        layout: Layout<'_>,
330        cursor: mouse::Cursor,
331        viewport: &Rectangle,
332    ) {
333        let bounds = layout.bounds();
334        let style = theme.style(&self.class);
335
336        if let Some(clipped_viewport) = bounds.intersection(viewport) {
337            draw_background(renderer, &style, bounds);
338
339            self.content.as_widget().draw(
340                tree,
341                renderer,
342                theme,
343                &renderer::Style {
344                    text_color: style.text_color.unwrap_or(renderer_style.text_color),
345                },
346                layout.children().next().unwrap(),
347                cursor,
348                if self.clip {
349                    &clipped_viewport
350                } else {
351                    viewport
352                },
353            );
354        }
355    }
356
357    fn overlay<'b>(
358        &'b mut self,
359        tree: &'b mut Tree,
360        layout: Layout<'b>,
361        renderer: &Renderer,
362        viewport: &Rectangle,
363        translation: Vector,
364    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
365        self.content.as_widget_mut().overlay(
366            tree,
367            layout.children().next().unwrap(),
368            renderer,
369            viewport,
370            translation,
371        )
372    }
373}
374
375impl<'a, Message, Theme, Renderer> From<Container<'a, Message, Theme, Renderer>>
376    for Element<'a, Message, Theme, Renderer>
377where
378    Message: 'a,
379    Theme: Catalog + 'a,
380    Renderer: core::Renderer + 'a,
381{
382    fn from(
383        container: Container<'a, Message, Theme, Renderer>,
384    ) -> Element<'a, Message, Theme, Renderer> {
385        Element::new(container)
386    }
387}
388
389/// Computes the layout of a [`Container`].
390pub fn layout(
391    limits: &layout::Limits,
392    width: Length,
393    height: Length,
394    max_width: f32,
395    max_height: f32,
396    padding: Padding,
397    horizontal_alignment: alignment::Horizontal,
398    vertical_alignment: alignment::Vertical,
399    layout_content: impl FnOnce(&layout::Limits) -> layout::Node,
400) -> layout::Node {
401    layout::positioned(
402        &limits.max_width(max_width).max_height(max_height),
403        width,
404        height,
405        padding,
406        |limits| layout_content(&limits.loose()),
407        |content, size| {
408            content.align(
409                Alignment::from(horizontal_alignment),
410                Alignment::from(vertical_alignment),
411                size,
412            )
413        },
414    )
415}
416
417/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
418pub fn draw_background<Renderer>(renderer: &mut Renderer, style: &Style, bounds: Rectangle)
419where
420    Renderer: core::Renderer,
421{
422    if style.background.is_some() || style.border.width > 0.0 || style.shadow.color.a > 0.0 {
423        renderer.fill_quad(
424            renderer::Quad {
425                bounds,
426                border: style.border,
427                shadow: style.shadow,
428                snap: style.snap,
429            },
430            style
431                .background
432                .unwrap_or(Background::Color(Color::TRANSPARENT)),
433        );
434    }
435}
436
437/// The appearance of a container.
438#[derive(Debug, Clone, Copy, PartialEq)]
439pub struct Style {
440    /// The text [`Color`] of the container.
441    pub text_color: Option<Color>,
442    /// The [`Background`] of the container.
443    pub background: Option<Background>,
444    /// The [`Border`] of the container.
445    pub border: Border,
446    /// The [`Shadow`] of the container.
447    pub shadow: Shadow,
448    /// Whether the container should be snapped to the pixel grid.
449    pub snap: bool,
450}
451
452impl Default for Style {
453    fn default() -> Self {
454        Self {
455            text_color: None,
456            background: None,
457            border: Border::default(),
458            shadow: Shadow::default(),
459            snap: cfg!(feature = "crisp"),
460        }
461    }
462}
463
464impl Style {
465    /// Updates the text color of the [`Style`].
466    pub fn color(self, color: impl Into<Color>) -> Self {
467        Self {
468            text_color: Some(color.into()),
469            ..self
470        }
471    }
472
473    /// Updates the border of the [`Style`].
474    pub fn border(self, border: impl Into<Border>) -> Self {
475        Self {
476            border: border.into(),
477            ..self
478        }
479    }
480
481    /// Updates the background of the [`Style`].
482    pub fn background(self, background: impl Into<Background>) -> Self {
483        Self {
484            background: Some(background.into()),
485            ..self
486        }
487    }
488
489    /// Updates the shadow of the [`Style`].
490    pub fn shadow(self, shadow: impl Into<Shadow>) -> Self {
491        Self {
492            shadow: shadow.into(),
493            ..self
494        }
495    }
496}
497
498impl From<Color> for Style {
499    fn from(color: Color) -> Self {
500        Self::default().background(color)
501    }
502}
503
504impl From<Gradient> for Style {
505    fn from(gradient: Gradient) -> Self {
506        Self::default().background(gradient)
507    }
508}
509
510impl From<gradient::Linear> for Style {
511    fn from(gradient: gradient::Linear) -> Self {
512        Self::default().background(gradient)
513    }
514}
515
516/// The theme catalog of a [`Container`].
517pub trait Catalog {
518    /// The item class of the [`Catalog`].
519    type Class<'a>;
520
521    /// The default class produced by the [`Catalog`].
522    fn default<'a>() -> Self::Class<'a>;
523
524    /// The [`Style`] of a class with the given status.
525    fn style(&self, class: &Self::Class<'_>) -> Style;
526}
527
528/// A styling function for a [`Container`].
529pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
530
531impl<Theme> From<Style> for StyleFn<'_, Theme> {
532    fn from(style: Style) -> Self {
533        Box::new(move |_theme| style)
534    }
535}
536
537impl Catalog for Theme {
538    type Class<'a> = StyleFn<'a, Self>;
539
540    fn default<'a>() -> Self::Class<'a> {
541        Box::new(transparent)
542    }
543
544    fn style(&self, class: &Self::Class<'_>) -> Style {
545        class(self)
546    }
547}
548
549/// A transparent [`Container`].
550pub fn transparent<Theme>(_theme: &Theme) -> Style {
551    Style::default()
552}
553
554/// A [`Container`] with the given [`Background`].
555pub fn background(background: impl Into<Background>) -> Style {
556    Style::default().background(background)
557}
558
559/// A rounded [`Container`] with a background.
560pub fn rounded_box(theme: &Theme) -> Style {
561    let palette = theme.extended_palette();
562
563    Style {
564        background: Some(palette.background.weak.color.into()),
565        text_color: Some(palette.background.weak.text),
566        border: border::rounded(2),
567        ..Style::default()
568    }
569}
570
571/// A bordered [`Container`] with a background.
572pub fn bordered_box(theme: &Theme) -> Style {
573    let palette = theme.extended_palette();
574
575    Style {
576        background: Some(palette.background.weakest.color.into()),
577        text_color: Some(palette.background.weakest.text),
578        border: Border {
579            width: 1.0,
580            radius: 5.0.into(),
581            color: palette.background.weak.color,
582        },
583        ..Style::default()
584    }
585}
586
587/// A [`Container`] with a dark background and white text.
588pub fn dark(_theme: &Theme) -> Style {
589    style(theme::palette::Pair {
590        color: color!(0x111111),
591        text: Color::WHITE,
592    })
593}
594
595/// A [`Container`] with a primary background color.
596pub fn primary(theme: &Theme) -> Style {
597    let palette = theme.extended_palette();
598
599    style(palette.primary.base)
600}
601
602/// A [`Container`] with a secondary background color.
603pub fn secondary(theme: &Theme) -> Style {
604    let palette = theme.extended_palette();
605
606    style(palette.secondary.base)
607}
608
609/// A [`Container`] with a success background color.
610pub fn success(theme: &Theme) -> Style {
611    let palette = theme.extended_palette();
612
613    style(palette.success.base)
614}
615
616/// A [`Container`] with a warning background color.
617pub fn warning(theme: &Theme) -> Style {
618    let palette = theme.extended_palette();
619
620    style(palette.warning.base)
621}
622
623/// A [`Container`] with a danger background color.
624pub fn danger(theme: &Theme) -> Style {
625    let palette = theme.extended_palette();
626
627    style(palette.danger.base)
628}
629
630fn style(pair: theme::palette::Pair) -> Style {
631    Style {
632        background: Some(pair.color.into()),
633        text_color: Some(pair.text),
634        border: border::rounded(2),
635        ..Style::default()
636    }
637}