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<'_>,
381        renderer: &Renderer,
382        translation: Vector,
383    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
384        self.content.as_widget_mut().overlay(
385            tree,
386            layout.children().next().unwrap(),
387            renderer,
388            translation,
389        )
390    }
391}
392
393impl<'a, Message, Theme, Renderer> From<Container<'a, Message, Theme, Renderer>>
394    for Element<'a, Message, Theme, Renderer>
395where
396    Message: 'a,
397    Theme: Catalog + 'a,
398    Renderer: core::Renderer + 'a,
399{
400    fn from(
401        column: Container<'a, Message, Theme, Renderer>,
402    ) -> Element<'a, Message, Theme, Renderer> {
403        Element::new(column)
404    }
405}
406
407/// Computes the layout of a [`Container`].
408pub fn layout(
409    limits: &layout::Limits,
410    width: Length,
411    height: Length,
412    max_width: f32,
413    max_height: f32,
414    padding: Padding,
415    horizontal_alignment: alignment::Horizontal,
416    vertical_alignment: alignment::Vertical,
417    layout_content: impl FnOnce(&layout::Limits) -> layout::Node,
418) -> layout::Node {
419    layout::positioned(
420        &limits.max_width(max_width).max_height(max_height),
421        width,
422        height,
423        padding,
424        |limits| layout_content(&limits.loose()),
425        |content, size| {
426            content.align(
427                Alignment::from(horizontal_alignment),
428                Alignment::from(vertical_alignment),
429                size,
430            )
431        },
432    )
433}
434
435/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
436pub fn draw_background<Renderer>(
437    renderer: &mut Renderer,
438    style: &Style,
439    bounds: Rectangle,
440) where
441    Renderer: core::Renderer,
442{
443    if style.background.is_some()
444        || style.border.width > 0.0
445        || style.shadow.color.a > 0.0
446    {
447        renderer.fill_quad(
448            renderer::Quad {
449                bounds,
450                border: style.border,
451                shadow: style.shadow,
452            },
453            style
454                .background
455                .unwrap_or(Background::Color(Color::TRANSPARENT)),
456        );
457    }
458}
459
460/// The identifier of a [`Container`].
461#[derive(Debug, Clone, PartialEq, Eq, Hash)]
462pub struct Id(widget::Id);
463
464impl Id {
465    /// Creates a custom [`Id`].
466    pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
467        Self(widget::Id::new(id))
468    }
469
470    /// Creates a unique [`Id`].
471    ///
472    /// This function produces a different [`Id`] every time it is called.
473    pub fn unique() -> Self {
474        Self(widget::Id::unique())
475    }
476}
477
478impl From<Id> for widget::Id {
479    fn from(id: Id) -> Self {
480        id.0
481    }
482}
483
484impl From<&'static str> for Id {
485    fn from(value: &'static str) -> Self {
486        Id::new(value)
487    }
488}
489
490/// Produces a [`Task`] that queries the visible screen bounds of the
491/// [`Container`] with the given [`Id`].
492pub fn visible_bounds(id: impl Into<Id>) -> Task<Option<Rectangle>> {
493    let id = id.into();
494
495    struct VisibleBounds {
496        target: widget::Id,
497        depth: usize,
498        scrollables: Vec<(Vector, Rectangle, usize)>,
499        bounds: Option<Rectangle>,
500    }
501
502    impl Operation<Option<Rectangle>> for VisibleBounds {
503        fn scrollable(
504            &mut self,
505            _id: Option<&widget::Id>,
506            bounds: Rectangle,
507            _content_bounds: Rectangle,
508            translation: Vector,
509            _state: &mut dyn widget::operation::Scrollable,
510        ) {
511            match self.scrollables.last() {
512                Some((last_translation, last_viewport, _depth)) => {
513                    let viewport = last_viewport
514                        .intersection(&(bounds - *last_translation))
515                        .unwrap_or(Rectangle::new(Point::ORIGIN, Size::ZERO));
516
517                    self.scrollables.push((
518                        translation + *last_translation,
519                        viewport,
520                        self.depth,
521                    ));
522                }
523                None => {
524                    self.scrollables.push((translation, bounds, self.depth));
525                }
526            }
527        }
528
529        fn container(
530            &mut self,
531            id: Option<&widget::Id>,
532            bounds: Rectangle,
533            operate_on_children: &mut dyn FnMut(
534                &mut dyn Operation<Option<Rectangle>>,
535            ),
536        ) {
537            if self.bounds.is_some() {
538                return;
539            }
540
541            if id == Some(&self.target) {
542                match self.scrollables.last() {
543                    Some((translation, viewport, _)) => {
544                        self.bounds =
545                            viewport.intersection(&(bounds - *translation));
546                    }
547                    None => {
548                        self.bounds = Some(bounds);
549                    }
550                }
551
552                return;
553            }
554
555            self.depth += 1;
556
557            operate_on_children(self);
558
559            self.depth -= 1;
560
561            match self.scrollables.last() {
562                Some((_, _, depth)) if self.depth == *depth => {
563                    let _ = self.scrollables.pop();
564                }
565                _ => {}
566            }
567        }
568
569        fn finish(&self) -> widget::operation::Outcome<Option<Rectangle>> {
570            widget::operation::Outcome::Some(self.bounds)
571        }
572    }
573
574    task::widget(VisibleBounds {
575        target: id.into(),
576        depth: 0,
577        scrollables: Vec::new(),
578        bounds: None,
579    })
580}
581
582/// The appearance of a container.
583#[derive(Debug, Clone, Copy, PartialEq, Default)]
584pub struct Style {
585    /// The text [`Color`] of the container.
586    pub text_color: Option<Color>,
587    /// The [`Background`] of the container.
588    pub background: Option<Background>,
589    /// The [`Border`] of the container.
590    pub border: Border,
591    /// The [`Shadow`] of the container.
592    pub shadow: Shadow,
593}
594
595impl Style {
596    /// Updates the text color of the [`Style`].
597    pub fn color(self, color: impl Into<Color>) -> Self {
598        Self {
599            text_color: Some(color.into()),
600            ..self
601        }
602    }
603
604    /// Updates the border of the [`Style`].
605    pub fn border(self, border: impl Into<Border>) -> Self {
606        Self {
607            border: border.into(),
608            ..self
609        }
610    }
611
612    /// Updates the background of the [`Style`].
613    pub fn background(self, background: impl Into<Background>) -> Self {
614        Self {
615            background: Some(background.into()),
616            ..self
617        }
618    }
619
620    /// Updates the shadow of the [`Style`].
621    pub fn shadow(self, shadow: impl Into<Shadow>) -> Self {
622        Self {
623            shadow: shadow.into(),
624            ..self
625        }
626    }
627}
628
629impl From<Color> for Style {
630    fn from(color: Color) -> Self {
631        Self::default().background(color)
632    }
633}
634
635impl From<Gradient> for Style {
636    fn from(gradient: Gradient) -> Self {
637        Self::default().background(gradient)
638    }
639}
640
641impl From<gradient::Linear> for Style {
642    fn from(gradient: gradient::Linear) -> Self {
643        Self::default().background(gradient)
644    }
645}
646
647/// The theme catalog of a [`Container`].
648pub trait Catalog {
649    /// The item class of the [`Catalog`].
650    type Class<'a>;
651
652    /// The default class produced by the [`Catalog`].
653    fn default<'a>() -> Self::Class<'a>;
654
655    /// The [`Style`] of a class with the given status.
656    fn style(&self, class: &Self::Class<'_>) -> Style;
657}
658
659/// A styling function for a [`Container`].
660pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
661
662impl<Theme> From<Style> for StyleFn<'_, Theme> {
663    fn from(style: Style) -> Self {
664        Box::new(move |_theme| style)
665    }
666}
667
668impl Catalog for Theme {
669    type Class<'a> = StyleFn<'a, Self>;
670
671    fn default<'a>() -> Self::Class<'a> {
672        Box::new(transparent)
673    }
674
675    fn style(&self, class: &Self::Class<'_>) -> Style {
676        class(self)
677    }
678}
679
680/// A transparent [`Container`].
681pub fn transparent<Theme>(_theme: &Theme) -> Style {
682    Style::default()
683}
684
685/// A [`Container`] with the given [`Background`].
686pub fn background(background: impl Into<Background>) -> Style {
687    Style::default().background(background)
688}
689
690/// A rounded [`Container`] with a background.
691pub fn rounded_box(theme: &Theme) -> Style {
692    let palette = theme.extended_palette();
693
694    Style {
695        background: Some(palette.background.weak.color.into()),
696        border: border::rounded(2),
697        ..Style::default()
698    }
699}
700
701/// A bordered [`Container`] with a background.
702pub fn bordered_box(theme: &Theme) -> Style {
703    let palette = theme.extended_palette();
704
705    Style {
706        background: Some(palette.background.weakest.color.into()),
707        border: Border {
708            width: 1.0,
709            radius: 5.0.into(),
710            color: palette.background.strong.color,
711        },
712        ..Style::default()
713    }
714}
715
716/// A [`Container`] with a dark background and white text.
717pub fn dark(_theme: &Theme) -> Style {
718    style(theme::palette::Pair {
719        color: color!(0x111111),
720        text: Color::WHITE,
721    })
722}
723
724/// A [`Container`] with a primary background color.
725pub fn primary(theme: &Theme) -> Style {
726    let palette = theme.extended_palette();
727
728    style(palette.primary.base)
729}
730
731/// A [`Container`] with a secondary background color.
732pub fn secondary(theme: &Theme) -> Style {
733    let palette = theme.extended_palette();
734
735    style(palette.secondary.base)
736}
737
738/// A [`Container`] with a success background color.
739pub fn success(theme: &Theme) -> Style {
740    let palette = theme.extended_palette();
741
742    style(palette.success.base)
743}
744
745/// A [`Container`] with a danger background color.
746pub fn danger(theme: &Theme) -> Style {
747    let palette = theme.extended_palette();
748
749    style(palette.danger.base)
750}
751
752fn style(pair: theme::palette::Pair) -> Style {
753    Style {
754        background: Some(pair.color.into()),
755        text_color: Some(pair.text),
756        border: border::rounded(2),
757        ..Style::default()
758    }
759}