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, Default)]
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 Style {
476    /// Updates the text color of the [`Style`].
477    pub fn color(self, color: impl Into<Color>) -> Self {
478        Self {
479            text_color: Some(color.into()),
480            ..self
481        }
482    }
483
484    /// Updates the border of the [`Style`].
485    pub fn border(self, border: impl Into<Border>) -> Self {
486        Self {
487            border: border.into(),
488            ..self
489        }
490    }
491
492    /// Updates the background of the [`Style`].
493    pub fn background(self, background: impl Into<Background>) -> Self {
494        Self {
495            background: Some(background.into()),
496            ..self
497        }
498    }
499
500    /// Updates the shadow of the [`Style`].
501    pub fn shadow(self, shadow: impl Into<Shadow>) -> Self {
502        Self {
503            shadow: shadow.into(),
504            ..self
505        }
506    }
507}
508
509impl From<Color> for Style {
510    fn from(color: Color) -> Self {
511        Self::default().background(color)
512    }
513}
514
515impl From<Gradient> for Style {
516    fn from(gradient: Gradient) -> Self {
517        Self::default().background(gradient)
518    }
519}
520
521impl From<gradient::Linear> for Style {
522    fn from(gradient: gradient::Linear) -> Self {
523        Self::default().background(gradient)
524    }
525}
526
527/// The theme catalog of a [`Container`].
528pub trait Catalog {
529    /// The item class of the [`Catalog`].
530    type Class<'a>;
531
532    /// The default class produced by the [`Catalog`].
533    fn default<'a>() -> Self::Class<'a>;
534
535    /// The [`Style`] of a class with the given status.
536    fn style(&self, class: &Self::Class<'_>) -> Style;
537}
538
539/// A styling function for a [`Container`].
540pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
541
542impl<Theme> From<Style> for StyleFn<'_, Theme> {
543    fn from(style: Style) -> Self {
544        Box::new(move |_theme| style)
545    }
546}
547
548impl Catalog for Theme {
549    type Class<'a> = StyleFn<'a, Self>;
550
551    fn default<'a>() -> Self::Class<'a> {
552        Box::new(transparent)
553    }
554
555    fn style(&self, class: &Self::Class<'_>) -> Style {
556        class(self)
557    }
558}
559
560/// A transparent [`Container`].
561pub fn transparent<Theme>(_theme: &Theme) -> Style {
562    Style::default()
563}
564
565/// A [`Container`] with the given [`Background`].
566pub fn background(background: impl Into<Background>) -> Style {
567    Style::default().background(background)
568}
569
570/// A rounded [`Container`] with a background.
571pub fn rounded_box(theme: &Theme) -> Style {
572    let palette = theme.extended_palette();
573
574    Style {
575        background: Some(palette.background.weak.color.into()),
576        text_color: Some(palette.background.weak.text),
577        border: border::rounded(2),
578        ..Style::default()
579    }
580}
581
582/// A bordered [`Container`] with a background.
583pub fn bordered_box(theme: &Theme) -> Style {
584    let palette = theme.extended_palette();
585
586    Style {
587        background: Some(palette.background.weakest.color.into()),
588        text_color: Some(palette.background.weakest.text),
589        border: Border {
590            width: 1.0,
591            radius: 5.0.into(),
592            color: palette.background.weak.color,
593        },
594        ..Style::default()
595    }
596}
597
598/// A [`Container`] with a dark background and white text.
599pub fn dark(_theme: &Theme) -> Style {
600    style(theme::palette::Pair {
601        color: color!(0x111111),
602        text: Color::WHITE,
603    })
604}
605
606/// A [`Container`] with a primary background color.
607pub fn primary(theme: &Theme) -> Style {
608    let palette = theme.extended_palette();
609
610    style(palette.primary.base)
611}
612
613/// A [`Container`] with a secondary background color.
614pub fn secondary(theme: &Theme) -> Style {
615    let palette = theme.extended_palette();
616
617    style(palette.secondary.base)
618}
619
620/// A [`Container`] with a success background color.
621pub fn success(theme: &Theme) -> Style {
622    let palette = theme.extended_palette();
623
624    style(palette.success.base)
625}
626
627/// A [`Container`] with a danger background color.
628pub fn danger(theme: &Theme) -> Style {
629    let palette = theme.extended_palette();
630
631    style(palette.danger.base)
632}
633
634fn style(pair: theme::palette::Pair) -> Style {
635    Style {
636        background: Some(pair.color.into()),
637        text_color: Some(pair.text),
638        border: border::rounded(2),
639        ..Style::default()
640    }
641}