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