iced_aw/widget/sidebar/
column.rs

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