iced_widget/
grid.rs

1//! Distribute content on a grid.
2use crate::core::layout::{self, Layout};
3use crate::core::mouse;
4use crate::core::overlay;
5use crate::core::renderer;
6use crate::core::widget::{Operation, Tree};
7use crate::core::{
8    Clipboard, Element, Event, Length, Pixels, Rectangle, Shell, Size, Vector,
9    Widget,
10};
11
12/// A container that distributes its contents on a responsive grid.
13pub struct Grid<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> {
14    spacing: f32,
15    columns: Constraint,
16    width: Option<Pixels>,
17    height: Sizing,
18    children: Vec<Element<'a, Message, Theme, Renderer>>,
19}
20
21enum Constraint {
22    MaxWidth(Pixels),
23    Amount(usize),
24}
25
26impl<'a, Message, Theme, Renderer> Grid<'a, Message, Theme, Renderer>
27where
28    Renderer: crate::core::Renderer,
29{
30    /// Creates an empty [`Grid`].
31    pub fn new() -> Self {
32        Self::from_vec(Vec::new())
33    }
34
35    /// Creates a [`Grid`] with the given capacity.
36    pub fn with_capacity(capacity: usize) -> Self {
37        Self::from_vec(Vec::with_capacity(capacity))
38    }
39
40    /// Creates a [`Grid`] with the given elements.
41    pub fn with_children(
42        children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
43    ) -> Self {
44        let iterator = children.into_iter();
45
46        Self::with_capacity(iterator.size_hint().0).extend(iterator)
47    }
48
49    /// Creates a [`Grid`] from an already allocated [`Vec`].
50    pub fn from_vec(
51        children: Vec<Element<'a, Message, Theme, Renderer>>,
52    ) -> Self {
53        Self {
54            spacing: 0.0,
55            columns: Constraint::Amount(3),
56            width: None,
57            height: Sizing::AspectRatio(1.0),
58            children,
59        }
60    }
61
62    /// Sets the spacing _between_ cells in the [`Grid`].
63    pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self {
64        self.spacing = amount.into().0;
65        self
66    }
67
68    /// Sets the width of the [`Grid`] in [`Pixels`].
69    ///
70    /// By default, a [`Grid`] will [`Fill`] its parent.
71    ///
72    /// [`Fill`]: Length::Fill
73    pub fn width(mut self, width: impl Into<Pixels>) -> Self {
74        self.width = Some(width.into());
75        self
76    }
77
78    /// Sets the height of the [`Grid`].
79    ///
80    /// By default, a [`Grid`] uses a cell aspect ratio of `1.0` (i.e. squares).
81    pub fn height(mut self, height: impl Into<Sizing>) -> Self {
82        self.height = height.into();
83        self
84    }
85
86    /// Sets the amount of columns in the [`Grid`].
87    pub fn columns(mut self, column: usize) -> Self {
88        self.columns = Constraint::Amount(column);
89        self
90    }
91
92    /// Makes the amount of columns dynamic in the [`Grid`], never
93    /// exceeding the provided `max_width`.
94    pub fn fluid(mut self, max_width: impl Into<Pixels>) -> Self {
95        self.columns = Constraint::MaxWidth(max_width.into());
96        self
97    }
98
99    /// Adds an [`Element`] to the [`Grid`].
100    pub fn push(
101        mut self,
102        child: impl Into<Element<'a, Message, Theme, Renderer>>,
103    ) -> Self {
104        self.children.push(child.into());
105        self
106    }
107
108    /// Adds an element to the [`Grid`], if `Some`.
109    pub fn push_maybe(
110        self,
111        child: Option<impl Into<Element<'a, Message, Theme, Renderer>>>,
112    ) -> Self {
113        if let Some(child) = child {
114            self.push(child)
115        } else {
116            self
117        }
118    }
119
120    /// Extends the [`Grid`] with the given children.
121    pub fn extend(
122        self,
123        children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
124    ) -> Self {
125        children.into_iter().fold(self, Self::push)
126    }
127}
128
129impl<Message, Renderer> Default for Grid<'_, Message, Renderer>
130where
131    Renderer: crate::core::Renderer,
132{
133    fn default() -> Self {
134        Self::new()
135    }
136}
137
138impl<'a, Message, Theme, Renderer: crate::core::Renderer>
139    FromIterator<Element<'a, Message, Theme, Renderer>>
140    for Grid<'a, Message, Theme, Renderer>
141{
142    fn from_iter<
143        T: IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
144    >(
145        iter: T,
146    ) -> Self {
147        Self::with_children(iter)
148    }
149}
150
151impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
152    for Grid<'_, Message, Theme, Renderer>
153where
154    Renderer: crate::core::Renderer,
155{
156    fn children(&self) -> Vec<Tree> {
157        self.children.iter().map(Tree::new).collect()
158    }
159
160    fn diff(&self, tree: &mut Tree) {
161        tree.diff_children(&self.children);
162    }
163
164    fn size(&self) -> Size<Length> {
165        Size {
166            width: self
167                .width
168                .map(|pixels| Length::Fixed(pixels.0))
169                .unwrap_or(Length::Fill),
170            height: match self.height {
171                Sizing::AspectRatio(_) => Length::Shrink,
172                Sizing::EvenlyDistribute(length) => length,
173            },
174        }
175    }
176
177    fn layout(
178        &mut self,
179        tree: &mut Tree,
180        renderer: &Renderer,
181        limits: &layout::Limits,
182    ) -> layout::Node {
183        let size = self.size();
184        let limits = limits.width(size.width).height(size.height);
185        let available = limits.max();
186
187        let cells_per_row = match self.columns {
188            // width = n * (cell + spacing) - spacing, given n > 0
189            Constraint::MaxWidth(pixels) => ((available.width + self.spacing)
190                / (pixels.0 + self.spacing))
191                .ceil() as usize,
192            Constraint::Amount(amount) => amount,
193        };
194
195        if self.children.is_empty() || cells_per_row == 0 {
196            return layout::Node::new(limits.resolve(
197                size.width,
198                size.height,
199                Size::ZERO,
200            ));
201        }
202
203        let cell_width = (available.width
204            - self.spacing * (cells_per_row - 1) as f32)
205            / cells_per_row as f32;
206
207        let cell_height = match self.height {
208            Sizing::AspectRatio(ratio) => Some(cell_width / ratio),
209            Sizing::EvenlyDistribute(Length::Shrink) => None,
210            Sizing::EvenlyDistribute(_) => {
211                let total_rows = self.children.len().div_ceil(cells_per_row);
212                Some(
213                    (available.height - self.spacing * (total_rows - 1) as f32)
214                        / total_rows as f32,
215                )
216            }
217        };
218
219        let cell_limits = layout::Limits::new(
220            Size::new(cell_width, cell_height.unwrap_or(0.0)),
221            Size::new(cell_width, cell_height.unwrap_or(available.height)),
222        );
223
224        let mut nodes = Vec::with_capacity(self.children.len());
225        let mut x = 0.0;
226        let mut y = 0.0;
227        let mut row_height = 0.0f32;
228
229        for (i, (child, tree)) in
230            self.children.iter_mut().zip(&mut tree.children).enumerate()
231        {
232            let node = child
233                .as_widget_mut()
234                .layout(tree, renderer, &cell_limits)
235                .move_to((x, y));
236
237            let size = node.size();
238
239            x += size.width + self.spacing;
240            row_height = row_height.max(size.height);
241
242            if (i + 1) % cells_per_row == 0 {
243                y += cell_height.unwrap_or(row_height) + self.spacing;
244                x = 0.0;
245                row_height = 0.0;
246            }
247
248            nodes.push(node);
249        }
250
251        if x == 0.0 {
252            y -= self.spacing;
253        } else {
254            y += cell_height.unwrap_or(row_height);
255        }
256
257        layout::Node::with_children(Size::new(available.width, y), nodes)
258    }
259
260    fn operate(
261        &mut self,
262        tree: &mut Tree,
263        layout: Layout<'_>,
264        renderer: &Renderer,
265        operation: &mut dyn Operation,
266    ) {
267        operation.container(None, layout.bounds());
268        operation.traverse(&mut |operation| {
269            self.children
270                .iter_mut()
271                .zip(&mut tree.children)
272                .zip(layout.children())
273                .for_each(|((child, state), layout)| {
274                    child
275                        .as_widget_mut()
276                        .operate(state, layout, renderer, operation);
277                });
278        });
279    }
280
281    fn update(
282        &mut self,
283        tree: &mut Tree,
284        event: &Event,
285        layout: Layout<'_>,
286        cursor: mouse::Cursor,
287        renderer: &Renderer,
288        clipboard: &mut dyn Clipboard,
289        shell: &mut Shell<'_, Message>,
290        viewport: &Rectangle,
291    ) {
292        for ((child, tree), layout) in self
293            .children
294            .iter_mut()
295            .zip(&mut tree.children)
296            .zip(layout.children())
297        {
298            child.as_widget_mut().update(
299                tree, event, layout, cursor, renderer, clipboard, shell,
300                viewport,
301            );
302        }
303    }
304
305    fn mouse_interaction(
306        &self,
307        tree: &Tree,
308        layout: Layout<'_>,
309        cursor: mouse::Cursor,
310        viewport: &Rectangle,
311        renderer: &Renderer,
312    ) -> mouse::Interaction {
313        self.children
314            .iter()
315            .zip(&tree.children)
316            .zip(layout.children())
317            .map(|((child, tree), layout)| {
318                child
319                    .as_widget()
320                    .mouse_interaction(tree, layout, cursor, viewport, renderer)
321            })
322            .max()
323            .unwrap_or_default()
324    }
325
326    fn draw(
327        &self,
328        tree: &Tree,
329        renderer: &mut Renderer,
330        theme: &Theme,
331        style: &renderer::Style,
332        layout: Layout<'_>,
333        cursor: mouse::Cursor,
334        viewport: &Rectangle,
335    ) {
336        if let Some(viewport) = layout.bounds().intersection(viewport) {
337            for ((child, tree), layout) in self
338                .children
339                .iter()
340                .zip(&tree.children)
341                .zip(layout.children())
342                .filter(|(_, layout)| layout.bounds().intersects(&viewport))
343            {
344                child.as_widget().draw(
345                    tree, renderer, theme, style, layout, cursor, &viewport,
346                );
347            }
348        }
349    }
350
351    fn overlay<'b>(
352        &'b mut self,
353        tree: &'b mut Tree,
354        layout: Layout<'b>,
355        renderer: &Renderer,
356        viewport: &Rectangle,
357        translation: Vector,
358    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
359        overlay::from_children(
360            &mut self.children,
361            tree,
362            layout,
363            renderer,
364            viewport,
365            translation,
366        )
367    }
368}
369
370impl<'a, Message, Theme, Renderer> From<Grid<'a, Message, Theme, Renderer>>
371    for Element<'a, Message, Theme, Renderer>
372where
373    Message: 'a,
374    Theme: 'a,
375    Renderer: crate::core::Renderer + 'a,
376{
377    fn from(row: Grid<'a, Message, Theme, Renderer>) -> Self {
378        Self::new(row)
379    }
380}
381
382/// The sizing strategy of a [`Grid`].
383#[derive(Debug, Clone, Copy, PartialEq)]
384pub enum Sizing {
385    /// The [`Grid`] will ensure each cell follows the given aspect ratio and the
386    /// total size will be the sum of the cells and the spacing between them.
387    ///
388    /// The ratio is the amount of horizontal pixels per each vertical pixel of a cell
389    /// in the [`Grid`].
390    AspectRatio(f32),
391
392    /// The [`Grid`] will evenly distribute the space available in the given [`Length`]
393    /// for each cell.
394    EvenlyDistribute(Length),
395}
396
397impl From<f32> for Sizing {
398    fn from(height: f32) -> Self {
399        Self::EvenlyDistribute(Length::from(height))
400    }
401}
402
403impl From<Length> for Sizing {
404    fn from(height: Length) -> Self {
405        Self::EvenlyDistribute(height)
406    }
407}
408
409/// Creates a new [`Sizing`] strategy that maintains the given aspect ratio.
410pub fn aspect_ratio(
411    width: impl Into<Pixels>,
412    height: impl Into<Pixels>,
413) -> Sizing {
414    Sizing::AspectRatio(width.into().0 / height.into().0)
415}