//! Containers let you align a widget inside their boundaries. //! //! # Example //! ```no_run //! # mod iced { pub mod widget { pub use iced_widget::*; } } //! # pub type State = (); //! # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; //! use iced::widget::container; //! //! enum Message { //! // ... //! } //! //! fn view(state: &State) -> Element<'_, Message> { //! container("This text is centered inside a rounded box!") //! .padding(10) //! .center(800) //! .style(container::rounded_box) //! .into() //! } //! ``` use crate::core::alignment::{self, Alignment}; use crate::core::border::{self, Border}; use crate::core::gradient::{self, Gradient}; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::theme; use crate::core::widget::tree::{self, Tree}; use crate::core::widget::{self, Operation}; use crate::core::{ self, Background, Clipboard, Color, Element, Event, Layout, Length, Padding, Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector, Widget, color, }; use crate::runtime::task::{self, Task}; /// A widget that aligns its contents inside of its boundaries. /// /// # Example /// ```no_run /// # mod iced { pub mod widget { pub use iced_widget::*; } } /// # pub type State = (); /// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; /// use iced::widget::container; /// /// enum Message { /// // ... /// } /// /// fn view(state: &State) -> Element<'_, Message> { /// container("This text is centered inside a rounded box!") /// .padding(10) /// .center(800) /// .style(container::rounded_box) /// .into() /// } /// ``` #[allow(missing_debug_implementations)] pub struct Container< 'a, Message, Theme = crate::Theme, Renderer = crate::Renderer, > where Theme: Catalog, Renderer: core::Renderer, { id: Option, padding: Padding, width: Length, height: Length, max_width: f32, max_height: f32, horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, clip: bool, content: Element<'a, Message, Theme, Renderer>, class: Theme::Class<'a>, } impl<'a, Message, Theme, Renderer> Container<'a, Message, Theme, Renderer> where Theme: Catalog, Renderer: core::Renderer, { /// Creates a [`Container`] with the given content. pub fn new( content: impl Into>, ) -> Self { let content = content.into(); let size = content.as_widget().size_hint(); Container { id: None, padding: Padding::ZERO, width: size.width.fluid(), height: size.height.fluid(), max_width: f32::INFINITY, max_height: f32::INFINITY, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, clip: false, class: Theme::default(), content, } } /// Sets the [`Id`] of the [`Container`]. pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); self } /// Sets the [`Padding`] of the [`Container`]. pub fn padding>(mut self, padding: P) -> Self { self.padding = padding.into(); self } /// Sets the width of the [`Container`]. pub fn width(mut self, width: impl Into) -> Self { self.width = width.into(); self } /// Sets the height of the [`Container`]. pub fn height(mut self, height: impl Into) -> Self { self.height = height.into(); self } /// Sets the maximum width of the [`Container`]. pub fn max_width(mut self, max_width: impl Into) -> Self { self.max_width = max_width.into().0; self } /// Sets the maximum height of the [`Container`]. pub fn max_height(mut self, max_height: impl Into) -> Self { self.max_height = max_height.into().0; self } /// Sets the width of the [`Container`] and centers its contents horizontally. pub fn center_x(self, width: impl Into) -> Self { self.width(width).align_x(alignment::Horizontal::Center) } /// Sets the height of the [`Container`] and centers its contents vertically. pub fn center_y(self, height: impl Into) -> Self { self.height(height).align_y(alignment::Vertical::Center) } /// Centers the contents in both the horizontal and vertical axes of the /// [`Container`]. /// /// This is equivalent to chaining [`center_x`] and [`center_y`]. /// /// [`center_x`]: Self::center_x /// [`center_y`]: Self::center_y pub fn center(self, length: impl Into) -> Self { let length = length.into(); self.center_x(length).center_y(length) } /// Aligns the contents of the [`Container`] to the left. pub fn align_left(self, width: impl Into) -> Self { self.width(width).align_x(alignment::Horizontal::Left) } /// Aligns the contents of the [`Container`] to the right. pub fn align_right(self, width: impl Into) -> Self { self.width(width).align_x(alignment::Horizontal::Right) } /// Aligns the contents of the [`Container`] to the top. pub fn align_top(self, height: impl Into) -> Self { self.height(height).align_y(alignment::Vertical::Top) } /// Aligns the contents of the [`Container`] to the bottom. pub fn align_bottom(self, height: impl Into) -> Self { self.height(height).align_y(alignment::Vertical::Bottom) } /// Sets the content alignment for the horizontal axis of the [`Container`]. pub fn align_x( mut self, alignment: impl Into, ) -> Self { self.horizontal_alignment = alignment.into(); self } /// Sets the content alignment for the vertical axis of the [`Container`]. pub fn align_y( mut self, alignment: impl Into, ) -> Self { self.vertical_alignment = alignment.into(); self } /// Sets whether the contents of the [`Container`] should be clipped on /// overflow. pub fn clip(mut self, clip: bool) -> Self { self.clip = clip; self } /// Sets the style of the [`Container`]. #[must_use] pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self where Theme::Class<'a>: From>, { self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); self } /// Sets the style class of the [`Container`]. #[must_use] pub fn class(mut self, class: impl Into>) -> Self { self.class = class.into(); self } } impl Widget for Container<'_, Message, Theme, Renderer> where Theme: Catalog, Renderer: core::Renderer, { fn tag(&self) -> tree::Tag { self.content.as_widget().tag() } fn state(&self) -> tree::State { self.content.as_widget().state() } fn children(&self) -> Vec { self.content.as_widget().children() } fn diff(&self, tree: &mut Tree) { self.content.as_widget().diff(tree); } fn size(&self) -> Size { Size { width: self.width, height: self.height, } } fn layout( &self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { layout( limits, self.width, self.height, self.max_width, self.max_height, self.padding, self.horizontal_alignment, self.vertical_alignment, |limits| self.content.as_widget().layout(tree, renderer, limits), ) } fn operate( &self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation, ) { operation.container( self.id.as_ref().map(|id| &id.0), layout.bounds(), &mut |operation| { self.content.as_widget().operate( tree, layout.children().next().unwrap(), renderer, operation, ); }, ); } fn update( &mut self, tree: &mut Tree, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { self.content.as_widget_mut().update( tree, event, layout.children().next().unwrap(), cursor, renderer, clipboard, shell, viewport, ); } fn mouse_interaction( &self, tree: &Tree, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { self.content.as_widget().mouse_interaction( tree, layout.children().next().unwrap(), cursor, viewport, renderer, ) } fn draw( &self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, renderer_style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ) { let bounds = layout.bounds(); let style = theme.style(&self.class); if let Some(clipped_viewport) = bounds.intersection(viewport) { draw_background(renderer, &style, bounds); self.content.as_widget().draw( tree, renderer, theme, &renderer::Style { text_color: style .text_color .unwrap_or(renderer_style.text_color), }, layout.children().next().unwrap(), cursor, if self.clip { &clipped_viewport } else { viewport }, ); } } fn overlay<'b>( &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, translation: Vector, ) -> Option> { self.content.as_widget_mut().overlay( tree, layout.children().next().unwrap(), renderer, translation, ) } } impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, Theme: Catalog + 'a, Renderer: core::Renderer + 'a, { fn from( column: Container<'a, Message, Theme, Renderer>, ) -> Element<'a, Message, Theme, Renderer> { Element::new(column) } } /// Computes the layout of a [`Container`]. pub fn layout( limits: &layout::Limits, width: Length, height: Length, max_width: f32, max_height: f32, padding: Padding, horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, layout_content: impl FnOnce(&layout::Limits) -> layout::Node, ) -> layout::Node { layout::positioned( &limits.max_width(max_width).max_height(max_height), width, height, padding, |limits| layout_content(&limits.loose()), |content, size| { content.align( Alignment::from(horizontal_alignment), Alignment::from(vertical_alignment), size, ) }, ) } /// Draws the background of a [`Container`] given its [`Style`] and its `bounds`. pub fn draw_background( renderer: &mut Renderer, style: &Style, bounds: Rectangle, ) where Renderer: core::Renderer, { if style.background.is_some() || style.border.width > 0.0 || style.shadow.color.a > 0.0 { renderer.fill_quad( renderer::Quad { bounds, border: style.border, shadow: style.shadow, }, style .background .unwrap_or(Background::Color(Color::TRANSPARENT)), ); } } /// The identifier of a [`Container`]. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Id(widget::Id); impl Id { /// Creates a custom [`Id`]. pub fn new(id: impl Into>) -> Self { Self(widget::Id::new(id)) } /// Creates a unique [`Id`]. /// /// This function produces a different [`Id`] every time it is called. pub fn unique() -> Self { Self(widget::Id::unique()) } } impl From for widget::Id { fn from(id: Id) -> Self { id.0 } } impl From<&'static str> for Id { fn from(value: &'static str) -> Self { Id::new(value) } } /// Produces a [`Task`] that queries the visible screen bounds of the /// [`Container`] with the given [`Id`]. pub fn visible_bounds(id: impl Into) -> Task> { let id = id.into(); struct VisibleBounds { target: widget::Id, depth: usize, scrollables: Vec<(Vector, Rectangle, usize)>, bounds: Option, } impl Operation> for VisibleBounds { fn scrollable( &mut self, _id: Option<&widget::Id>, bounds: Rectangle, _content_bounds: Rectangle, translation: Vector, _state: &mut dyn widget::operation::Scrollable, ) { match self.scrollables.last() { Some((last_translation, last_viewport, _depth)) => { let viewport = last_viewport .intersection(&(bounds - *last_translation)) .unwrap_or(Rectangle::new(Point::ORIGIN, Size::ZERO)); self.scrollables.push(( translation + *last_translation, viewport, self.depth, )); } None => { self.scrollables.push((translation, bounds, self.depth)); } } } fn container( &mut self, id: Option<&widget::Id>, bounds: Rectangle, operate_on_children: &mut dyn FnMut( &mut dyn Operation>, ), ) { if self.bounds.is_some() { return; } if id == Some(&self.target) { match self.scrollables.last() { Some((translation, viewport, _)) => { self.bounds = viewport.intersection(&(bounds - *translation)); } None => { self.bounds = Some(bounds); } } return; } self.depth += 1; operate_on_children(self); self.depth -= 1; match self.scrollables.last() { Some((_, _, depth)) if self.depth == *depth => { let _ = self.scrollables.pop(); } _ => {} } } fn finish(&self) -> widget::operation::Outcome> { widget::operation::Outcome::Some(self.bounds) } } task::widget(VisibleBounds { target: id.into(), depth: 0, scrollables: Vec::new(), bounds: None, }) } /// The appearance of a container. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Style { /// The text [`Color`] of the container. pub text_color: Option, /// The [`Background`] of the container. pub background: Option, /// The [`Border`] of the container. pub border: Border, /// The [`Shadow`] of the container. pub shadow: Shadow, } impl Style { /// Updates the text color of the [`Style`]. pub fn color(self, color: impl Into) -> Self { Self { text_color: Some(color.into()), ..self } } /// Updates the border of the [`Style`]. pub fn border(self, border: impl Into) -> Self { Self { border: border.into(), ..self } } /// Updates the background of the [`Style`]. pub fn background(self, background: impl Into) -> Self { Self { background: Some(background.into()), ..self } } /// Updates the shadow of the [`Style`]. pub fn shadow(self, shadow: impl Into) -> Self { Self { shadow: shadow.into(), ..self } } } impl From for Style { fn from(color: Color) -> Self { Self::default().background(color) } } impl From for Style { fn from(gradient: Gradient) -> Self { Self::default().background(gradient) } } impl From for Style { fn from(gradient: gradient::Linear) -> Self { Self::default().background(gradient) } } /// The theme catalog of a [`Container`]. pub trait Catalog { /// The item class of the [`Catalog`]. type Class<'a>; /// The default class produced by the [`Catalog`]. fn default<'a>() -> Self::Class<'a>; /// The [`Style`] of a class with the given status. fn style(&self, class: &Self::Class<'_>) -> Style; } /// A styling function for a [`Container`]. pub type StyleFn<'a, Theme> = Box Style + 'a>; impl From