iced_widget/
column.rs

1//! Distribute content vertically.
2use crate::core::alignment::{self, Alignment};
3use crate::core::event::{self, Event};
4use crate::core::layout;
5use crate::core::mouse;
6use crate::core::overlay;
7use crate::core::renderer;
8use crate::core::widget::{Operation, Tree};
9use crate::core::{
10    Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell,
11    Size, Vector, Widget,
12};
13
14/// A container that distributes its contents vertically.
15///
16/// # Example
17/// ```no_run
18/// # mod iced { pub mod widget { pub use iced_widget::*; } }
19/// # pub type State = ();
20/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
21/// use iced::widget::{button, column};
22///
23/// #[derive(Debug, Clone)]
24/// enum Message {
25///     // ...
26/// }
27///
28/// fn view(state: &State) -> Element<'_, Message> {
29///     column![
30///         "I am on top!",
31///         button("I am in the center!"),
32///         "I am below.",
33///     ].into()
34/// }
35/// ```
36#[allow(missing_debug_implementations)]
37pub struct Column<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
38{
39    spacing: f32,
40    padding: Padding,
41    width: Length,
42    height: Length,
43    max_width: f32,
44    align: Alignment,
45    clip: bool,
46    children: Vec<Element<'a, Message, Theme, Renderer>>,
47}
48
49impl<'a, Message, Theme, Renderer> Column<'a, Message, Theme, Renderer>
50where
51    Renderer: crate::core::Renderer,
52{
53    /// Creates an empty [`Column`].
54    pub fn new() -> Self {
55        Self::from_vec(Vec::new())
56    }
57
58    /// Creates a [`Column`] with the given capacity.
59    pub fn with_capacity(capacity: usize) -> Self {
60        Self::from_vec(Vec::with_capacity(capacity))
61    }
62
63    /// Creates a [`Column`] with the given elements.
64    pub fn with_children(
65        children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
66    ) -> Self {
67        let iterator = children.into_iter();
68
69        Self::with_capacity(iterator.size_hint().0).extend(iterator)
70    }
71
72    /// Creates a [`Column`] from an already allocated [`Vec`].
73    ///
74    /// Keep in mind that the [`Column`] will not inspect the [`Vec`], which means
75    /// it won't automatically adapt to the sizing strategy of its contents.
76    ///
77    /// If any of the children have a [`Length::Fill`] strategy, you will need to
78    /// call [`Column::width`] or [`Column::height`] accordingly.
79    pub fn from_vec(
80        children: Vec<Element<'a, Message, Theme, Renderer>>,
81    ) -> Self {
82        Self {
83            spacing: 0.0,
84            padding: Padding::ZERO,
85            width: Length::Shrink,
86            height: Length::Shrink,
87            max_width: f32::INFINITY,
88            align: Alignment::Start,
89            clip: false,
90            children,
91        }
92    }
93
94    /// Sets the vertical spacing _between_ elements.
95    ///
96    /// Custom margins per element do not exist in iced. You should use this
97    /// method instead! While less flexible, it helps you keep spacing between
98    /// elements consistent.
99    pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self {
100        self.spacing = amount.into().0;
101        self
102    }
103
104    /// Sets the [`Padding`] of the [`Column`].
105    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
106        self.padding = padding.into();
107        self
108    }
109
110    /// Sets the width of the [`Column`].
111    pub fn width(mut self, width: impl Into<Length>) -> Self {
112        self.width = width.into();
113        self
114    }
115
116    /// Sets the height of the [`Column`].
117    pub fn height(mut self, height: impl Into<Length>) -> Self {
118        self.height = height.into();
119        self
120    }
121
122    /// Sets the maximum width of the [`Column`].
123    pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self {
124        self.max_width = max_width.into().0;
125        self
126    }
127
128    /// Sets the horizontal alignment of the contents of the [`Column`] .
129    pub fn align_x(mut self, align: impl Into<alignment::Horizontal>) -> Self {
130        self.align = Alignment::from(align.into());
131        self
132    }
133
134    /// Sets whether the contents of the [`Column`] should be clipped on
135    /// overflow.
136    pub fn clip(mut self, clip: bool) -> Self {
137        self.clip = clip;
138        self
139    }
140
141    /// Adds an element to the [`Column`].
142    pub fn push(
143        mut self,
144        child: impl Into<Element<'a, Message, Theme, Renderer>>,
145    ) -> Self {
146        let child = child.into();
147        let child_size = child.as_widget().size_hint();
148
149        self.width = self.width.enclose(child_size.width);
150        self.height = self.height.enclose(child_size.height);
151
152        self.children.push(child);
153        self
154    }
155
156    /// Adds an element to the [`Column`], if `Some`.
157    pub fn push_maybe(
158        self,
159        child: Option<impl Into<Element<'a, Message, Theme, Renderer>>>,
160    ) -> Self {
161        if let Some(child) = child {
162            self.push(child)
163        } else {
164            self
165        }
166    }
167
168    /// Extends the [`Column`] with the given children.
169    pub fn extend(
170        self,
171        children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
172    ) -> Self {
173        children.into_iter().fold(self, Self::push)
174    }
175}
176
177impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer>
178where
179    Renderer: crate::core::Renderer,
180{
181    fn default() -> Self {
182        Self::new()
183    }
184}
185
186impl<'a, Message, Theme, Renderer: crate::core::Renderer>
187    FromIterator<Element<'a, Message, Theme, Renderer>>
188    for Column<'a, Message, Theme, Renderer>
189{
190    fn from_iter<
191        T: IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
192    >(
193        iter: T,
194    ) -> Self {
195        Self::with_children(iter)
196    }
197}
198
199impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
200    for Column<'a, Message, Theme, Renderer>
201where
202    Renderer: crate::core::Renderer,
203{
204    fn children(&self) -> Vec<Tree> {
205        self.children.iter().map(Tree::new).collect()
206    }
207
208    fn diff(&mut self, tree: &mut Tree) {
209        tree.diff_children(self.children.as_mut_slice());
210    }
211
212    fn size(&self) -> Size<Length> {
213        Size {
214            width: self.width,
215            height: self.height,
216        }
217    }
218
219    fn layout(
220        &self,
221        tree: &mut Tree,
222        renderer: &Renderer,
223        limits: &layout::Limits,
224    ) -> layout::Node {
225        let limits = limits.max_width(self.max_width);
226
227        layout::flex::resolve(
228            layout::flex::Axis::Vertical,
229            renderer,
230            &limits,
231            self.width,
232            self.height,
233            self.padding,
234            self.spacing,
235            self.align,
236            &self.children,
237            &mut tree.children,
238        )
239    }
240
241    fn operate(
242        &self,
243        tree: &mut Tree,
244        layout: Layout<'_>,
245        renderer: &Renderer,
246        operation: &mut dyn Operation,
247    ) {
248        operation.container(None, layout.bounds(), &mut |operation| {
249            self.children
250                .iter()
251                .zip(&mut tree.children)
252                .zip(layout.children())
253                .for_each(|((child, state), c_layout)| {
254                    child.as_widget().operate(
255                        state,
256                        c_layout.with_virtual_offset(layout.virtual_offset()),
257                        renderer,
258                        operation,
259                    );
260                });
261        });
262    }
263
264    fn on_event(
265        &mut self,
266        tree: &mut Tree,
267        event: Event,
268        layout: Layout<'_>,
269        cursor: mouse::Cursor,
270        renderer: &Renderer,
271        clipboard: &mut dyn Clipboard,
272        shell: &mut Shell<'_, Message>,
273        viewport: &Rectangle,
274    ) -> event::Status {
275        self.children
276            .iter_mut()
277            .zip(&mut tree.children)
278            .zip(layout.children())
279            .map(|((child, state), c_layout)| {
280                child.as_widget_mut().on_event(
281                    state,
282                    event.clone(),
283                    c_layout.with_virtual_offset(layout.virtual_offset()),
284                    cursor,
285                    renderer,
286                    clipboard,
287                    shell,
288                    viewport,
289                )
290            })
291            .fold(event::Status::Ignored, event::Status::merge)
292    }
293
294    fn mouse_interaction(
295        &self,
296        tree: &Tree,
297        layout: Layout<'_>,
298        cursor: mouse::Cursor,
299        viewport: &Rectangle,
300        renderer: &Renderer,
301    ) -> mouse::Interaction {
302        self.children
303            .iter()
304            .zip(&tree.children)
305            .zip(layout.children())
306            .map(|((child, state), c_layout)| {
307                child.as_widget().mouse_interaction(
308                    state,
309                    c_layout.with_virtual_offset(layout.virtual_offset()),
310                    cursor,
311                    viewport,
312                    renderer,
313                )
314            })
315            .max()
316            .unwrap_or_default()
317    }
318
319    fn draw(
320        &self,
321        tree: &Tree,
322        renderer: &mut Renderer,
323        theme: &Theme,
324        style: &renderer::Style,
325        layout: Layout<'_>,
326        cursor: mouse::Cursor,
327        viewport: &Rectangle,
328    ) {
329        if let Some(clipped_viewport) = layout.bounds().intersection(viewport) {
330            let viewport = if self.clip {
331                &clipped_viewport
332            } else {
333                viewport
334            };
335
336            for ((child, state), c_layout) in self
337                .children
338                .iter()
339                .zip(&tree.children)
340                .zip(layout.children())
341                .filter(|(_, layout)| layout.bounds().intersects(viewport))
342            {
343                child.as_widget().draw(
344                    state,
345                    renderer,
346                    theme,
347                    style,
348                    c_layout.with_virtual_offset(layout.virtual_offset()),
349                    cursor,
350                    viewport,
351                );
352            }
353        }
354    }
355
356    fn overlay<'b>(
357        &'b mut self,
358        tree: &'b mut Tree,
359        layout: Layout<'_>,
360        renderer: &Renderer,
361        translation: Vector,
362    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
363        overlay::from_children(
364            &mut self.children,
365            tree,
366            layout,
367            renderer,
368            translation,
369        )
370    }
371
372    #[cfg(feature = "a11y")]
373    /// get the a11y nodes for the widget
374    fn a11y_nodes(
375        &self,
376        layout: Layout<'_>,
377        state: &Tree,
378        cursor: mouse::Cursor,
379    ) -> iced_accessibility::A11yTree {
380        use iced_accessibility::A11yTree;
381        A11yTree::join(
382            self.children
383                .iter()
384                .zip(layout.children())
385                .zip(state.children.iter())
386                .map(|((c, c_layout), state)| {
387                    c.as_widget().a11y_nodes(
388                        c_layout.with_virtual_offset(layout.virtual_offset()),
389                        state,
390                        cursor,
391                    )
392                }),
393        )
394    }
395
396    fn drag_destinations(
397        &self,
398        state: &Tree,
399        layout: Layout<'_>,
400        renderer: &Renderer,
401        dnd_rectangles: &mut crate::core::clipboard::DndDestinationRectangles,
402    ) {
403        for ((e, c_layout), state) in self
404            .children
405            .iter()
406            .zip(layout.children())
407            .zip(state.children.iter())
408        {
409            e.as_widget().drag_destinations(
410                state,
411                c_layout.with_virtual_offset(layout.virtual_offset()),
412                renderer,
413                dnd_rectangles,
414            );
415        }
416    }
417}
418
419impl<'a, Message, Theme, Renderer> From<Column<'a, Message, Theme, Renderer>>
420    for Element<'a, Message, Theme, Renderer>
421where
422    Message: 'a,
423    Theme: 'a,
424    Renderer: crate::core::Renderer + 'a,
425{
426    fn from(column: Column<'a, Message, Theme, Renderer>) -> Self {
427        Self::new(column)
428    }
429}