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