iced_aw/widget/sidebar/
row.rs

1//! Distribute content columns horizontally while setting the column height to highest column.
2//!
3//! For alignment [`Alignment::Start`] the last element of the column is flushed to the end.
4//! For alignment [`Alignment::End`] the first element of the column 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::Column;
18
19/// A container that distributes its contents horizontally.
20#[allow(missing_debug_implementations)]
21pub struct FlushRow<'a, Message, Theme = iced_core::Theme, Renderer = iced_widget::Renderer> {
22    spacing: Pixels,
23    padding: Padding,
24    width: Length,
25    height: Length,
26    max_height: 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> FlushRow<'a, Message, Theme, Renderer>
34where
35    Renderer: iced_core::Renderer + 'a,
36{
37    /// Creates an empty [`FlushRow`].
38    #[must_use]
39    pub fn new() -> Self {
40        Self::from_vec(Vec::new())
41    }
42
43    /// Creates a [`FlushRow`] 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 [`FlushRow`] with the given elements.
50    #[must_use]
51    pub fn with_children(
52        children: impl IntoIterator<Item = Column<'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 [`FlushRow`] from an already allocated [`Vec`].
59    ///
60    /// Keep in mind that the [`FlushRow`] 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 [`FlushRow::width`] or [`FlushRow::height`] accordingly.
65    #[must_use]
66    pub fn from_vec(children: Vec<Column<'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_height: 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 [`FlushRow`].
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 [`FlushRow`].
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 [`FlushRow`].
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 [`FlushRow`].
117    #[must_use]
118    pub fn max_height(mut self, max_height: impl Into<Pixels>) -> Self {
119        self.max_height = max_height.into().0;
120        self
121    }
122
123    /// Sets the horizontal alignment of the contents of the [`FlushRow`] .
124    #[must_use]
125    pub fn align_y(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 [`FlushRow`] 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 column element is flushed to the end when the alignment is set to
139    /// Start, or the start column element is flushed to the start when the alignment is set
140    /// to End. 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 [`FlushRow`].
148    #[must_use]
149    pub fn push(mut self, child: impl Into<Column<'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 [`FlushRow`], if `Some`.
159    #[must_use]
160    pub fn push_maybe(
161        self,
162        child: Option<impl Into<Column<'a, Message, Theme, Renderer>>>,
163    ) -> Self {
164        if let Some(child) = child {
165            self.push(child)
166        } else {
167            self
168        }
169    }
170
171    /// Extends the [`FlushRow`] with the given children.
172    #[must_use]
173    pub fn extend(
174        self,
175        children: impl IntoIterator<Item = Column<'a, Message, Theme, Renderer>>,
176    ) -> Self {
177        children.into_iter().fold(self, Self::push)
178    }
179}
180
181#[allow(clippy::mismatching_type_param_order)]
182impl<'a, Message: 'a, Renderer> Default for FlushRow<'a, Message, Renderer>
183where
184    Renderer: iced_core::Renderer + 'a,
185{
186    fn default() -> Self {
187        Self::new()
188    }
189}
190
191impl<'a, Message: 'a, Theme: 'a, Renderer: iced_core::Renderer + 'a>
192    FromIterator<Column<'a, Message, Theme, Renderer>> for FlushRow<'a, Message, Theme, Renderer>
193{
194    fn from_iter<T: IntoIterator<Item = Column<'a, Message, Theme, Renderer>>>(iter: T) -> Self {
195        Self::with_children(iter)
196    }
197}
198
199impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
200    for FlushRow<'_, Message, Theme, Renderer>
201where
202    Renderer: iced_core::Renderer,
203{
204    fn children(&self) -> Vec<Tree> {
205        self.children.iter().map(Tree::new).collect()
206    }
207
208    fn diff(&self, tree: &mut Tree) {
209        tree.diff_children(&self.children);
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        &mut self,
221        tree: &mut Tree,
222        renderer: &Renderer,
223        limits: &layout::Limits,
224    ) -> layout::Node {
225        let limits = limits.max_height(self.max_height);
226        let node = layout::flex::resolve(
227            layout::flex::Axis::Horizontal,
228            renderer,
229            &limits,
230            self.width,
231            self.height,
232            self.padding,
233            self.spacing.0,
234            self.align,
235            &mut self.children,
236            &mut tree.children,
237        );
238        let mut container_y = f32::MAX;
239        let mut container_height = 0.0f32;
240        for column in node.children() {
241            if column.size().height > container_height {
242                container_height = column.size().height;
243            }
244            if column.bounds().y < container_y {
245                container_y = column.bounds().y;
246            }
247        }
248        let mut children = Vec::<Node>::new();
249        for column in node.children() {
250            let mut column_children = Vec::<Node>::new();
251            let bounds = column.bounds();
252            let height_diff = container_height - bounds.height;
253            if !column.children().is_empty() {
254                for element in column.children() {
255                    let bounds = element.bounds();
256                    let y = bounds.y
257                        + match self.align {
258                            Alignment::Start => 0.0,
259                            Alignment::Center => height_diff / 2.0,
260                            Alignment::End => height_diff,
261                        };
262                    let mut element_node =
263                        Node::with_children(element.size(), element.children().to_owned());
264                    element_node.move_to_mut(Point::new(bounds.x, y));
265                    column_children.push(element_node);
266                }
267                if column_children.len() > 1 {
268                    match self.align {
269                        Alignment::Start => {
270                            let element = column_children.last().expect("Always exists.");
271                            let bounds = element.bounds();
272                            let mut position = bounds.position();
273                            let mut element_node =
274                                Node::with_children(bounds.size(), element.children().to_owned());
275                            position.y += height_diff;
276                            element_node.move_to_mut(position);
277                            let node = column_children.last_mut().expect("Always exists.");
278                            *node = element_node;
279                        }
280                        Alignment::Center => {}
281                        Alignment::End => {
282                            let element = column_children.first().expect("Always exists.");
283                            let bounds = element.bounds();
284                            let mut position = bounds.position();
285                            let mut element_node =
286                                Node::with_children(bounds.size(), element.children().to_owned());
287                            position.y -= height_diff;
288                            element_node.move_to_mut(position);
289                            let node = column_children.first_mut().expect("Always exists.");
290                            *node = element_node;
291                        }
292                    }
293                }
294            }
295            let mut column_node = Node::with_children(
296                Size::new(column.size().width, container_height),
297                column_children,
298            );
299            column_node.move_to_mut(Point::new(bounds.x, container_y));
300            children.push(column_node);
301        }
302        Node::with_children(node.size(), children)
303    }
304
305    fn operate(
306        &mut self,
307        tree: &mut Tree,
308        layout: Layout<'_>,
309        renderer: &Renderer,
310        operation: &mut dyn Operation<()>,
311    ) {
312        operation.container(None, layout.bounds());
313        operation.traverse(&mut |operation| {
314            self.children
315                .iter_mut()
316                .zip(&mut tree.children)
317                .zip(layout.children())
318                .for_each(|((child, state), layout)| {
319                    child
320                        .as_widget_mut()
321                        .operate(state, layout, renderer, operation);
322                });
323        });
324    }
325
326    fn update(
327        &mut self,
328        tree: &mut Tree,
329        event: &Event,
330        layout: Layout<'_>,
331        cursor: mouse::Cursor,
332        renderer: &Renderer,
333        clipboard: &mut dyn Clipboard,
334        shell: &mut Shell<'_, Message>,
335        viewport: &Rectangle,
336    ) {
337        for ((child, state), layout) in self
338            .children
339            .iter_mut()
340            .zip(&mut tree.children)
341            .zip(layout.children())
342        {
343            child.as_widget_mut().update(
344                state, event, layout, cursor, renderer, clipboard, shell, viewport,
345            );
346        }
347    }
348
349    fn mouse_interaction(
350        &self,
351        tree: &Tree,
352        layout: Layout<'_>,
353        cursor: mouse::Cursor,
354        viewport: &Rectangle,
355        renderer: &Renderer,
356    ) -> mouse::Interaction {
357        self.children
358            .iter()
359            .zip(&tree.children)
360            .zip(layout.children())
361            .map(|((child, state), layout)| {
362                child
363                    .as_widget()
364                    .mouse_interaction(state, layout, cursor, viewport, renderer)
365            })
366            .max()
367            .unwrap_or_default()
368    }
369
370    fn draw(
371        &self,
372        tree: &Tree,
373        renderer: &mut Renderer,
374        theme: &Theme,
375        style: &renderer::Style,
376        layout: Layout<'_>,
377        cursor: mouse::Cursor,
378        viewport: &Rectangle,
379    ) {
380        if let Some(clipped_viewport) = layout.bounds().intersection(viewport) {
381            for ((child, state), layout) in self
382                .children
383                .iter()
384                .zip(&tree.children)
385                .zip(layout.children())
386            {
387                child.as_widget().draw(
388                    state,
389                    renderer,
390                    theme,
391                    style,
392                    layout,
393                    cursor,
394                    if self.clip {
395                        &clipped_viewport
396                    } else {
397                        viewport
398                    },
399                );
400            }
401        }
402    }
403
404    fn overlay<'b>(
405        &'b mut self,
406        tree: &'b mut Tree,
407        layout: Layout<'b>,
408        renderer: &Renderer,
409        viewport: &Rectangle,
410        translation: Vector,
411    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
412        overlay::from_children(
413            &mut self.children,
414            tree,
415            layout,
416            renderer,
417            viewport,
418            translation,
419        )
420    }
421}
422
423impl<'a, Message, Theme, Renderer> From<FlushRow<'a, Message, Theme, Renderer>>
424    for Element<'a, Message, Theme, Renderer>
425where
426    Message: 'a,
427    Theme: 'a,
428    Renderer: iced_core::Renderer + 'a,
429{
430    fn from(row: FlushRow<'a, Message, Theme, Renderer>) -> Self {
431        Self::new(row)
432    }
433}