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