cosmic/widget/grid/
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    Alignment, Clipboard, Layout, Length, Padding, Rectangle, Shell, Vector, Widget, layout, mouse,
10    overlay, renderer,
11};
12
13/// Responsively generates rows and columns of widgets based on its dimmensions.
14#[must_use]
15#[derive(Setters)]
16pub struct Grid<'a, Message> {
17    #[setters(skip)]
18    children: Vec<Element<'a, Message>>,
19    /// Where children shall be assigned in the grid.
20    #[setters(skip)]
21    assignments: Vec<Assignment>,
22    /// Sets the padding around the widget.
23    padding: Padding,
24    /// Alignment across columns
25    column_alignment: Alignment,
26    /// Alignment across rows
27    row_alignment: Alignment,
28    /// Defines how the content will be justified.
29    #[setters(into, strip_option)]
30    justify_content: Option<crate::widget::JustifyContent>,
31    /// Sets the space between each column of items.
32    column_spacing: u16,
33    /// Sets the space between each item in a row.
34    row_spacing: u16,
35    /// Sets the width of the grid.
36    width: Length,
37    /// Sets the height of the grid.
38    height: Length,
39    /// Sets the max width
40    max_width: f32,
41    #[setters(skip)]
42    column: u16,
43    #[setters(skip)]
44    row: u16,
45}
46
47impl<Message> Default for Grid<'_, Message> {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53impl<'a, Message> Grid<'a, Message> {
54    pub const fn new() -> Self {
55        Self {
56            children: Vec::new(),
57            assignments: Vec::new(),
58            padding: Padding::ZERO,
59            column_alignment: Alignment::Start,
60            row_alignment: Alignment::Start,
61            justify_content: None,
62            column_spacing: 4,
63            row_spacing: 4,
64            width: Length::Shrink,
65            height: Length::Shrink,
66            max_width: f32::INFINITY,
67            column: 1,
68            row: 1,
69        }
70    }
71
72    /// Attach a new element with a given grid assignment.
73    pub fn push(mut self, widget: impl Into<Element<'a, Message>>) -> Self {
74        self.children.push(widget.into());
75
76        self.assignments.push(Assignment {
77            column: self.column,
78            row: self.row,
79            width: 1,
80            height: 1,
81        });
82
83        self.column += 1;
84
85        self
86    }
87
88    /// Attach a new element with custom properties
89    pub fn push_with<W, S>(mut self, widget: W, setup: S) -> Self
90    where
91        W: Into<Element<'a, Message>>,
92        S: Fn(Assignment) -> Assignment,
93    {
94        self.children.push(widget.into());
95
96        self.assignments.push(setup(Assignment {
97            column: self.column,
98            row: self.row,
99            width: 1,
100            height: 1,
101        }));
102
103        self.column += 1;
104
105        self
106    }
107
108    #[inline]
109    pub fn insert_row(mut self) -> Self {
110        self.row += 1;
111        self.column = 1;
112        self
113    }
114}
115
116impl<Message: 'static + Clone> Widget<Message, crate::Theme, Renderer> for Grid<'_, Message> {
117    fn children(&self) -> Vec<Tree> {
118        self.children.iter().map(Tree::new).collect()
119    }
120
121    fn diff(&mut self, tree: &mut Tree) {
122        tree.diff_children(self.children.as_mut_slice());
123    }
124
125    fn size(&self) -> iced_core::Size<Length> {
126        iced_core::Size::new(self.width, self.height)
127    }
128
129    fn layout(
130        &mut self,
131        tree: &mut Tree,
132        renderer: &Renderer,
133        limits: &layout::Limits,
134    ) -> layout::Node {
135        let size = self.size();
136        let limits = limits
137            .max_width(self.max_width)
138            .width(size.width)
139            .height(size.height);
140
141        super::layout::resolve(
142            renderer,
143            &limits,
144            &mut self.children,
145            &self.assignments,
146            self.width,
147            self.height,
148            self.padding,
149            self.column_alignment,
150            self.row_alignment,
151            self.justify_content,
152            f32::from(self.column_spacing),
153            f32::from(self.row_spacing),
154            &mut tree.children,
155        )
156    }
157
158    fn operate(
159        &mut self,
160        tree: &mut Tree,
161        layout: Layout<'_>,
162        renderer: &Renderer,
163        operation: &mut dyn Operation<()>,
164    ) {
165        operation.traverse(&mut |operation| {
166            self.children
167                .iter_mut()
168                .zip(&mut tree.children)
169                .zip(layout.children())
170                .for_each(|((child, state), c_layout)| {
171                    child.as_widget_mut().operate(
172                        state,
173                        c_layout.with_virtual_offset(layout.virtual_offset()),
174                        renderer,
175                        operation,
176                    );
177                });
178        });
179    }
180
181    fn update(
182        &mut self,
183        tree: &mut Tree,
184        event: &Event,
185        layout: Layout<'_>,
186        cursor: mouse::Cursor,
187        renderer: &Renderer,
188        clipboard: &mut dyn Clipboard,
189        shell: &mut Shell<'_, Message>,
190        viewport: &Rectangle,
191    ) {
192        for ((child, state), c_layout) in self
193            .children
194            .iter_mut()
195            .zip(&mut tree.children)
196            .zip(layout.children())
197        {
198            child.as_widget_mut().update(
199                state,
200                event,
201                c_layout.with_virtual_offset(layout.virtual_offset()),
202                cursor,
203                renderer,
204                clipboard,
205                shell,
206                viewport,
207            );
208        }
209    }
210
211    fn mouse_interaction(
212        &self,
213        tree: &Tree,
214        layout: Layout<'_>,
215        cursor: mouse::Cursor,
216        viewport: &Rectangle,
217        renderer: &Renderer,
218    ) -> mouse::Interaction {
219        self.children
220            .iter()
221            .zip(&tree.children)
222            .zip(layout.children())
223            .map(|((child, state), c_layout)| {
224                child.as_widget().mouse_interaction(
225                    state,
226                    c_layout.with_virtual_offset(layout.virtual_offset()),
227                    cursor,
228                    viewport,
229                    renderer,
230                )
231            })
232            .max()
233            .unwrap_or_default()
234    }
235
236    fn draw(
237        &self,
238        tree: &Tree,
239        renderer: &mut Renderer,
240        theme: &crate::Theme,
241        style: &renderer::Style,
242        layout: Layout<'_>,
243        cursor: mouse::Cursor,
244        viewport: &Rectangle,
245    ) {
246        for ((child, state), c_layout) in self
247            .children
248            .iter()
249            .zip(&tree.children)
250            .zip(layout.children())
251        {
252            child.as_widget().draw(
253                state,
254                renderer,
255                theme,
256                style,
257                c_layout.with_virtual_offset(layout.virtual_offset()),
258                cursor,
259                viewport,
260            );
261        }
262    }
263
264    fn overlay<'b>(
265        &'b mut self,
266        tree: &'b mut Tree,
267        layout: Layout<'b>,
268        renderer: &Renderer,
269        viewport: &Rectangle,
270        translation: Vector,
271    ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
272        overlay::from_children(
273            &mut self.children,
274            tree,
275            layout,
276            renderer,
277            viewport,
278            translation,
279        )
280    }
281
282    #[cfg(feature = "a11y")]
283    /// get the a11y nodes for the widget
284    fn a11y_nodes(
285        &self,
286        layout: Layout<'_>,
287        state: &Tree,
288        p: mouse::Cursor,
289    ) -> iced_accessibility::A11yTree {
290        use iced_accessibility::A11yTree;
291        A11yTree::join(
292            self.children
293                .iter()
294                .zip(layout.children())
295                .zip(state.children.iter())
296                .map(|((c, c_layout), state)| {
297                    c.as_widget().a11y_nodes(
298                        c_layout.with_virtual_offset(layout.virtual_offset()),
299                        state,
300                        p,
301                    )
302                }),
303        )
304    }
305
306    fn drag_destinations(
307        &self,
308        state: &Tree,
309        layout: Layout<'_>,
310        renderer: &Renderer,
311        dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
312    ) {
313        for ((e, c_layout), state) in self
314            .children
315            .iter()
316            .zip(layout.children())
317            .zip(state.children.iter())
318        {
319            e.as_widget().drag_destinations(
320                state,
321                c_layout.with_virtual_offset(layout.virtual_offset()),
322                renderer,
323                dnd_rectangles,
324            );
325        }
326    }
327}
328
329impl<'a, Message: 'static + Clone> From<Grid<'a, Message>> for Element<'a, Message> {
330    fn from(flex_row: Grid<'a, Message>) -> Self {
331        Self::new(flex_row)
332    }
333}
334
335#[derive(Copy, Clone, Debug, Setters)]
336#[must_use]
337pub struct Assignment {
338    pub(super) column: u16,
339    pub(super) row: u16,
340    pub(super) width: u16,
341    pub(super) height: u16,
342}
343
344impl Default for Assignment {
345    fn default() -> Self {
346        Self::new()
347    }
348}
349
350impl Assignment {
351    pub const fn new() -> Self {
352        Self {
353            column: 0,
354            row: 0,
355            width: 1,
356            height: 1,
357        }
358    }
359}
360
361impl From<(u16, u16)> for Assignment {
362    fn from((column, row): (u16, u16)) -> Self {
363        Self {
364            column,
365            row,
366            width: 1,
367            height: 1,
368        }
369    }
370}
371
372impl From<(u16, u16, u16, u16)> for Assignment {
373    fn from((column, row, width, height): (u16, u16, u16, u16)) -> Self {
374        Self {
375            column,
376            row,
377            width,
378            height,
379        }
380    }
381}