cosmic/widget/flex_row/
widget.rs

1// Copyright 2023 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use crate::{Element, Renderer};
5use derive_setters::Setters;
6use iced_core::event::{self, Event};
7use iced_core::widget::{Operation, Tree};
8use iced_core::{
9    Clipboard, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, layout, mouse, overlay,
10    renderer,
11};
12
13/// Responsively generates rows and columns of widgets based on its dimensions.
14#[derive(Setters)]
15#[must_use]
16pub struct FlexRow<'a, Message> {
17    #[setters(skip)]
18    children: Vec<Element<'a, Message>>,
19    /// Sets the padding around the widget.
20    #[setters(into)]
21    padding: Padding,
22    /// Sets the space between each column of items.
23    column_spacing: u16,
24    /// Sets the space between each item in a row.
25    row_spacing: u16,
26    /// Sets the width.
27    width: Length,
28    /// Sets minimum width of items that grow.
29    #[setters(into)]
30    min_item_width: Option<f32>,
31    /// Sets the max width
32    max_width: f32,
33    /// Defines how content will be aligned horizontally.
34    #[setters(skip)]
35    align_items: Option<taffy::AlignItems>,
36    /// Defines how content will be aligned vertically.
37    #[setters(skip)]
38    justify_items: Option<taffy::AlignItems>,
39    /// Defines how the content will be justified.
40    #[setters(into)]
41    justify_content: Option<crate::widget::JustifyContent>,
42}
43
44impl<'a, Message> FlexRow<'a, Message> {
45    pub(crate) const fn new(children: Vec<Element<'a, Message>>) -> Self {
46        Self {
47            children,
48            padding: Padding::ZERO,
49            column_spacing: 4,
50            row_spacing: 4,
51            width: Length::Shrink,
52            min_item_width: None,
53            max_width: f32::INFINITY,
54            align_items: None,
55            justify_items: None,
56            justify_content: None,
57        }
58    }
59
60    /// Defines how content will be aligned horizontally.
61    pub fn align_items(mut self, alignment: iced::Alignment) -> Self {
62        self.align_items = Some(match alignment {
63            iced::Alignment::Center => taffy::AlignItems::Center,
64            iced::Alignment::Start => taffy::AlignItems::Start,
65            iced::Alignment::End => taffy::AlignItems::End,
66        });
67        self
68    }
69
70    /// Defines how content will be aligned vertically.
71    pub fn justify_items(mut self, alignment: iced::Alignment) -> Self {
72        self.justify_items = Some(match alignment {
73            iced::Alignment::Center => taffy::AlignItems::Center,
74            iced::Alignment::Start => taffy::AlignItems::Start,
75            iced::Alignment::End => taffy::AlignItems::End,
76        });
77        self
78    }
79
80    /// Sets the space between each column and row.
81    #[inline]
82    pub const fn spacing(mut self, spacing: u16) -> Self {
83        self.column_spacing = spacing;
84        self.row_spacing = spacing;
85        self
86    }
87}
88
89impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for FlexRow<'_, Message> {
90    fn children(&self) -> Vec<Tree> {
91        self.children.iter().map(Tree::new).collect()
92    }
93
94    fn diff(&mut self, tree: &mut Tree) {
95        tree.diff_children(self.children.as_mut_slice());
96    }
97
98    fn size(&self) -> iced_core::Size<Length> {
99        iced_core::Size::new(self.width, Length::Shrink)
100    }
101
102    fn layout(
103        &self,
104        tree: &mut Tree,
105        renderer: &Renderer,
106        limits: &layout::Limits,
107    ) -> layout::Node {
108        let size = self.size();
109        let limits = limits
110            .max_width(self.max_width)
111            .width(size.width)
112            .height(size.height);
113
114        super::layout::resolve(
115            renderer,
116            &limits,
117            &self.children,
118            self.padding,
119            f32::from(self.column_spacing),
120            f32::from(self.row_spacing),
121            self.min_item_width,
122            self.align_items,
123            self.justify_items,
124            self.justify_content,
125            &mut tree.children,
126        )
127    }
128
129    fn operate(
130        &self,
131        tree: &mut Tree,
132        layout: Layout<'_>,
133        renderer: &Renderer,
134        operation: &mut dyn Operation<()>,
135    ) {
136        operation.container(None, layout.bounds(), &mut |operation| {
137            self.children
138                .iter()
139                .zip(&mut tree.children)
140                .zip(layout.children())
141                .for_each(|((child, state), c_layout)| {
142                    child.as_widget().operate(
143                        state,
144                        c_layout.with_virtual_offset(layout.virtual_offset()),
145                        renderer,
146                        operation,
147                    );
148                });
149        });
150    }
151
152    fn on_event(
153        &mut self,
154        tree: &mut Tree,
155        event: Event,
156        layout: Layout<'_>,
157        cursor: mouse::Cursor,
158        renderer: &Renderer,
159        clipboard: &mut dyn Clipboard,
160        shell: &mut Shell<'_, Message>,
161        viewport: &Rectangle,
162    ) -> event::Status {
163        self.children
164            .iter_mut()
165            .zip(&mut tree.children)
166            .zip(layout.children())
167            .map(|((child, state), c_layout)| {
168                child.as_widget_mut().on_event(
169                    state,
170                    event.clone(),
171                    c_layout.with_virtual_offset(layout.virtual_offset()),
172                    cursor,
173                    renderer,
174                    clipboard,
175                    shell,
176                    viewport,
177                )
178            })
179            .fold(event::Status::Ignored, event::Status::merge)
180    }
181
182    fn mouse_interaction(
183        &self,
184        tree: &Tree,
185        layout: Layout<'_>,
186        cursor: mouse::Cursor,
187        viewport: &Rectangle,
188        renderer: &Renderer,
189    ) -> mouse::Interaction {
190        self.children
191            .iter()
192            .zip(&tree.children)
193            .zip(layout.children())
194            .map(|((child, state), c_layout)| {
195                child.as_widget().mouse_interaction(
196                    state,
197                    c_layout.with_virtual_offset(layout.virtual_offset()),
198                    cursor,
199                    viewport,
200                    renderer,
201                )
202            })
203            .max()
204            .unwrap_or_default()
205    }
206
207    fn draw(
208        &self,
209        tree: &Tree,
210        renderer: &mut Renderer,
211        theme: &crate::Theme,
212        style: &renderer::Style,
213        layout: Layout<'_>,
214        cursor: mouse::Cursor,
215        viewport: &Rectangle,
216    ) {
217        for ((child, state), c_layout) in self
218            .children
219            .iter()
220            .zip(&tree.children)
221            .zip(layout.children())
222        {
223            child.as_widget().draw(
224                state,
225                renderer,
226                theme,
227                style,
228                c_layout.with_virtual_offset(layout.virtual_offset()),
229                cursor,
230                viewport,
231            );
232        }
233    }
234
235    fn overlay<'b>(
236        &'b mut self,
237        tree: &'b mut Tree,
238        layout: Layout<'_>,
239        renderer: &Renderer,
240        translation: Vector,
241    ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
242        overlay::from_children(&mut self.children, tree, layout, renderer, translation)
243    }
244
245    #[cfg(feature = "a11y")]
246    /// get the a11y nodes for the widget
247    fn a11y_nodes(
248        &self,
249        layout: Layout<'_>,
250        state: &Tree,
251        p: mouse::Cursor,
252    ) -> iced_accessibility::A11yTree {
253        use iced_accessibility::A11yTree;
254        A11yTree::join(
255            self.children
256                .iter()
257                .zip(layout.children())
258                .zip(state.children.iter())
259                .map(|((c, c_layout), state)| c.as_widget().a11y_nodes(c_layout, state, p)),
260        )
261    }
262
263    fn drag_destinations(
264        &self,
265        state: &Tree,
266        layout: Layout<'_>,
267        renderer: &Renderer,
268        dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
269    ) {
270        for ((e, layout), state) in self
271            .children
272            .iter()
273            .zip(layout.children())
274            .zip(state.children.iter())
275        {
276            e.as_widget()
277                .drag_destinations(state, layout, renderer, dnd_rectangles);
278        }
279    }
280}
281
282impl<'a, Message: 'static + Clone> From<FlexRow<'a, Message>> for Element<'a, Message> {
283    fn from(flex_row: FlexRow<'a, Message>) -> Self {
284        Self::new(flex_row)
285    }
286}