//! Distribute content vertically. use crate::iced; use iced::core::alignment::{self, Alignment}; use iced::core::event::{self, Event}; use iced::core::layout; use iced::core::mouse; use iced::core::overlay; use iced::core::renderer; use iced::core::widget::{Operation, Tree}; use iced::core::{ Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, widget, }; /// A container that distributes its contents vertically. /// /// # 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::{button, column}; /// /// #[derive(Debug, Clone)] /// enum Message { /// // ... /// } /// /// fn view(state: &State) -> Element<'_, Message> { /// column![ /// "I am on top!", /// button("I am in the center!"), /// "I am below.", /// ].into() /// } /// ``` #[allow(missing_debug_implementations)] #[must_use] pub struct Column<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer> { spacing: f32, padding: Padding, width: Length, height: Length, max_width: f32, align: Alignment, clip: bool, children: Vec>, } impl<'a, Message, Theme, Renderer> Column<'a, Message, Theme, Renderer> where Renderer: iced::core::Renderer, { /// Creates an empty [`Column`]. pub fn new() -> Self { Self::from_vec(Vec::new()) } /// Creates a [`Column`] with the given capacity. pub fn with_capacity(capacity: usize) -> Self { Self::from_vec(Vec::with_capacity(capacity)) } /// Creates a [`Column`] with the given elements. pub fn with_children( children: impl IntoIterator>, ) -> Self { let iterator = children.into_iter(); Self::with_capacity(iterator.size_hint().0).extend(iterator) } /// Creates a [`Column`] from an already allocated [`Vec`]. /// /// Keep in mind that the [`Column`] will not inspect the [`Vec`], which means /// it won't automatically adapt to the sizing strategy of its contents. /// /// If any of the children have a [`Length::Fill`] strategy, you will need to /// call [`Column::width`] or [`Column::height`] accordingly. pub fn from_vec(children: Vec>) -> Self { Self { spacing: 0.0, padding: Padding::ZERO, width: Length::Shrink, height: Length::Shrink, max_width: f32::INFINITY, align: Alignment::Start, clip: false, children, } } /// Sets the vertical spacing _between_ elements. /// /// Custom margins per element do not exist in iced. You should use this /// method instead! While less flexible, it helps you keep spacing between /// elements consistent. pub fn spacing(mut self, amount: impl Into) -> Self { self.spacing = amount.into().0; self } /// Sets the [`Padding`] of the [`Column`]. pub fn padding>(mut self, padding: P) -> Self { self.padding = padding.into(); self } /// Sets the width of the [`Column`]. pub fn width(mut self, width: impl Into) -> Self { self.width = width.into(); self } /// Sets the height of the [`Column`]. pub fn height(mut self, height: impl Into) -> Self { self.height = height.into(); self } /// Sets the maximum width of the [`Column`]. pub fn max_width(mut self, max_width: impl Into) -> Self { self.max_width = max_width.into().0; self } /// Sets the horizontal alignment of the contents of the [`Column`] . pub fn align_x(mut self, align: impl Into) -> Self { self.align = Alignment::from(align.into()); self } /// Sets whether the contents of the [`Column`] should be clipped on /// overflow. pub fn clip(mut self, clip: bool) -> Self { self.clip = clip; self } /// Adds an element to the [`Column`]. pub fn push(mut self, child: impl Into>) -> Self { let child = child.into(); let child_size = child.as_widget().size_hint(); self.width = self.width.enclose(child_size.width); self.height = self.height.enclose(child_size.height); self.children.push(child); self } /// Adds an element to the [`Column`], if `Some`. #[must_use] pub fn push_maybe( self, child: Option>>, ) -> Self { if let Some(child) = child { self.push(child) } else { self } } /// Extends the [`Column`] with the given children. pub fn extend( self, children: impl IntoIterator>, ) -> Self { children.into_iter().fold(self, Self::push) } } impl Default for Column<'_, Message, Renderer> where Renderer: iced::core::Renderer, { fn default() -> Self { Self::new() } } impl<'a, Message, Theme, Renderer: iced::core::Renderer> FromIterator> for Column<'a, Message, Theme, Renderer> { fn from_iter>>(iter: T) -> Self { Self::with_children(iter) } } impl Widget for Column<'_, Message, Theme, Renderer> where Renderer: iced::core::Renderer, { fn children(&self) -> Vec { self.children.iter().map(Tree::new).collect() } fn state(&self) -> widget::tree::State { widget::tree::State::new(State::default()) } fn tag(&self) -> widget::tree::Tag { widget::tree::Tag::of::() } fn diff(&mut self, tree: &mut Tree) { tree.diff_children(self.children.as_mut_slice()); } fn size(&self) -> Size { Size { width: self.width, height: self.height, } } fn layout( &self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let limits = limits.max_width(self.max_width); layout::flex::resolve( layout::flex::Axis::Vertical, renderer, &limits, self.width, self.height, self.padding, self.spacing, self.align, &self.children, &mut tree.children, ) } fn operate( &self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation, ) { operation.container(None, layout.bounds(), &mut |operation| { self.children .iter() .zip(&mut tree.children) .zip(layout.children()) .for_each(|((child, state), c_layout)| { child.as_widget().operate( state, c_layout.with_virtual_offset(layout.virtual_offset()), renderer, operation, ); }); }); } fn on_event( &mut self, tree: &mut Tree, event: Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { let my_state = tree.state.downcast_mut::(); if let Some(hovered) = my_state.hovered { let child_layout = layout.children().nth(hovered); if let Some(child_layout) = child_layout && cursor.is_over(child_layout.bounds()) { // if mouse event, we can skip checking other children if let Event::Mouse(e) = &event { if !matches!( e, mouse::Event::CursorLeft | mouse::Event::ButtonReleased { .. } ) { return self.children[hovered].as_widget_mut().on_event( &mut tree.children[hovered], event, child_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, shell, viewport, ); } } else if let Event::Touch(t) = &event { if !matches!( t, iced::core::touch::Event::FingerLifted { .. } | iced::core::touch::Event::FingerLost { .. } ) { return self.children[hovered].as_widget_mut().on_event( &mut tree.children[hovered], event, child_layout.with_virtual_offset(layout.virtual_offset()), cursor, renderer, clipboard, shell, viewport, ); } } } else { my_state.hovered = None; } } self.children .iter_mut() .enumerate() .zip(&mut tree.children) .zip(layout.children()) .map(|(((i, child), state), c_layout)| { let mut cursor_virtual = cursor; if matches!( event, Event::Mouse(mouse::Event::CursorMoved { .. } | mouse::Event::CursorEntered) | Event::Touch( iced_core::touch::Event::FingerMoved { .. } | iced_core::touch::Event::FingerPressed { .. } ) ) && cursor.is_over(c_layout.bounds()) { my_state.hovered = Some(i); return child.as_widget_mut().on_event( state, event.clone(), c_layout.with_virtual_offset(layout.virtual_offset()), cursor_virtual, renderer, clipboard, shell, viewport, ); } else if my_state.hovered.is_some_and(|h| i != h) { cursor_virtual = mouse::Cursor::Unavailable; } child.as_widget_mut().on_event( state, event.clone(), c_layout.with_virtual_offset(layout.virtual_offset()), cursor_virtual, renderer, clipboard, shell, viewport, ) }) .fold(event::Status::Ignored, event::Status::merge) } fn mouse_interaction( &self, tree: &Tree, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { self.children .iter() .zip(&tree.children) .zip(layout.children()) .map(|((child, state), c_layout)| { child.as_widget().mouse_interaction( state, c_layout.with_virtual_offset(layout.virtual_offset()), cursor, viewport, renderer, ) }) .max() .unwrap_or_default() } fn draw( &self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ) { if let Some(clipped_viewport) = layout.bounds().intersection(viewport) { let my_state = tree.state.downcast_ref::(); let viewport = if self.clip { &clipped_viewport } else { viewport }; for (i, ((child, state), c_layout)) in self .children .iter() .zip(&tree.children) .zip(layout.children()) .filter(|(_, layout)| layout.bounds().intersects(viewport)) .enumerate() { child.as_widget().draw( state, renderer, theme, style, c_layout.with_virtual_offset(layout.virtual_offset()), if my_state.hovered.is_some_and(|h| i == h) { cursor } else { mouse::Cursor::Unavailable }, viewport, ); } } } fn overlay<'b>( &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, translation: Vector, ) -> Option> { overlay::from_children(&mut self.children, tree, layout, renderer, translation) } #[cfg(feature = "a11y")] /// get the a11y nodes for the widget fn a11y_nodes( &self, layout: Layout<'_>, state: &Tree, cursor: mouse::Cursor, ) -> iced_accessibility::A11yTree { use iced_accessibility::A11yTree; A11yTree::join( self.children .iter() .zip(layout.children()) .zip(state.children.iter()) .map(|((c, c_layout), state)| { c.as_widget().a11y_nodes( c_layout.with_virtual_offset(layout.virtual_offset()), state, cursor, ) }), ) } fn drag_destinations( &self, state: &Tree, layout: Layout<'_>, renderer: &Renderer, dnd_rectangles: &mut iced::core::clipboard::DndDestinationRectangles, ) { for ((e, c_layout), state) in self .children .iter() .zip(layout.children()) .zip(state.children.iter()) { e.as_widget().drag_destinations( state, c_layout.with_virtual_offset(layout.virtual_offset()), renderer, dnd_rectangles, ); } } } impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, Theme: 'a, Renderer: iced::core::Renderer + 'a, { fn from(column: Column<'a, Message, Theme, Renderer>) -> Self { Self::new(column) } } #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct State { hovered: Option, }