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        &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            &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        &self,
160        tree: &mut Tree,
161        layout: Layout<'_>,
162        renderer: &Renderer,
163        operation: &mut dyn Operation<()>,
164    ) {
165        operation.container(None, layout.bounds(), &mut |operation| {
166            self.children
167                .iter()
168                .zip(&mut tree.children)
169                .zip(layout.children())
170                .for_each(|((child, state), c_layout)| {
171                    child.as_widget().operate(
172                        state,
173                        c_layout.with_virtual_offset(layout.virtual_offset()),
174                        renderer,
175                        operation,
176                    );
177                });
178        });
179    }
180
181    fn on_event(
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    ) -> event::Status {
192        self.children
193            .iter_mut()
194            .zip(&mut tree.children)
195            .zip(layout.children())
196            .map(|((child, state), c_layout)| {
197                child.as_widget_mut().on_event(
198                    state,
199                    event.clone(),
200                    c_layout.with_virtual_offset(layout.virtual_offset()),
201                    cursor,
202                    renderer,
203                    clipboard,
204                    shell,
205                    viewport,
206                )
207            })
208            .fold(event::Status::Ignored, event::Status::merge)
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<'_>,
268        renderer: &Renderer,
269        translation: Vector,
270    ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
271        overlay::from_children(&mut self.children, tree, layout, renderer, translation)
272    }
273
274    #[cfg(feature = "a11y")]
275    /// get the a11y nodes for the widget
276    fn a11y_nodes(
277        &self,
278        layout: Layout<'_>,
279        state: &Tree,
280        p: mouse::Cursor,
281    ) -> iced_accessibility::A11yTree {
282        use iced_accessibility::A11yTree;
283        A11yTree::join(
284            self.children
285                .iter()
286                .zip(layout.children())
287                .zip(state.children.iter())
288                .map(|((c, c_layout), state)| {
289                    c.as_widget().a11y_nodes(
290                        c_layout.with_virtual_offset(layout.virtual_offset()),
291                        state,
292                        p,
293                    )
294                }),
295        )
296    }
297
298    fn drag_destinations(
299        &self,
300        state: &Tree,
301        layout: Layout<'_>,
302        renderer: &Renderer,
303        dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
304    ) {
305        for ((e, c_layout), state) in self
306            .children
307            .iter()
308            .zip(layout.children())
309            .zip(state.children.iter())
310        {
311            e.as_widget().drag_destinations(
312                state,
313                c_layout.with_virtual_offset(layout.virtual_offset()),
314                renderer,
315                dnd_rectangles,
316            );
317        }
318    }
319}
320
321impl<'a, Message: 'static + Clone> From<Grid<'a, Message>> for Element<'a, Message> {
322    fn from(flex_row: Grid<'a, Message>) -> Self {
323        Self::new(flex_row)
324    }
325}
326
327#[derive(Copy, Clone, Debug, Setters)]
328#[must_use]
329pub struct Assignment {
330    pub(super) column: u16,
331    pub(super) row: u16,
332    pub(super) width: u16,
333    pub(super) height: u16,
334}
335
336impl Default for Assignment {
337    fn default() -> Self {
338        Self::new()
339    }
340}
341
342impl Assignment {
343    pub const fn new() -> Self {
344        Self {
345            column: 0,
346            row: 0,
347            width: 1,
348            height: 1,
349        }
350    }
351}
352
353impl From<(u16, u16)> for Assignment {
354    fn from((column, row): (u16, u16)) -> Self {
355        Self {
356            column,
357            row,
358            width: 1,
359            height: 1,
360        }
361    }
362}
363
364impl From<(u16, u16, u16, u16)> for Assignment {
365    fn from((column, row, width, height): (u16, u16, u16, u16)) -> Self {
366        Self {
367            column,
368            row,
369            width,
370            height,
371        }
372    }
373}