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