1use crate::core::layout::{self, Layout};
3use crate::core::mouse;
4use crate::core::overlay;
5use crate::core::renderer;
6use crate::core::widget::{Meta, Operation, Tree};
7use crate::core::{Event, Length, Pixels, Rectangle, Shell, Size, Vector, Widget};
8
9pub struct Grid<W> {
11 spacing: f32,
12 columns: Constraint,
13 width: Option<Pixels>,
14 height: Sizing,
15 children: Vec<W>,
16}
17
18enum Constraint {
19 MaxWidth(Pixels),
20 Amount(usize),
21}
22
23impl<W> Grid<W> {
24 pub fn new() -> Self {
26 Self::from_vec(Vec::new())
27 }
28
29 pub fn with_capacity(capacity: usize) -> Self {
31 Self::from_vec(Vec::with_capacity(capacity))
32 }
33
34 pub fn with_children(children: impl IntoIterator<Item = W>) -> Self
36 where
37 W: Meta,
38 {
39 let iterator = children.into_iter();
40
41 Self::with_capacity(iterator.size_hint().0).extend(iterator)
42 }
43
44 pub fn from_vec(children: Vec<W>) -> Self {
46 Self {
47 spacing: 0.0,
48 columns: Constraint::Amount(3),
49 width: None,
50 height: Sizing::AspectRatio(1.0),
51 children,
52 }
53 }
54
55 pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self {
57 self.spacing = amount.into().0;
58 self
59 }
60
61 pub fn width(mut self, width: impl Into<Pixels>) -> Self {
67 self.width = Some(width.into());
68 self
69 }
70
71 pub fn height(mut self, height: impl Into<Sizing>) -> Self {
75 self.height = height.into();
76 self
77 }
78
79 pub fn columns(mut self, column: usize) -> Self {
81 self.columns = Constraint::Amount(column);
82 self
83 }
84
85 pub fn fluid(mut self, max_width: impl Into<Pixels>) -> Self {
88 self.columns = Constraint::MaxWidth(max_width.into());
89 self
90 }
91
92 pub fn push(mut self, child: impl Into<W>) -> Self
94 where
95 W: Meta,
96 {
97 self.children.push(child.into());
98 self
99 }
100
101 pub fn push_maybe(self, child: Option<impl Into<W>>) -> Self
103 where
104 W: Meta,
105 {
106 if let Some(child) = child {
107 self.push(child)
108 } else {
109 self
110 }
111 }
112
113 pub fn extend(self, children: impl IntoIterator<Item = W>) -> Self
115 where
116 W: Meta,
117 {
118 children.into_iter().fold(self, Self::push)
119 }
120}
121
122impl<W> Default for Grid<W> {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128impl<W> FromIterator<W> for Grid<W>
129where
130 W: Meta,
131{
132 fn from_iter<T: IntoIterator<Item = W>>(iter: T) -> Self {
133 Self::with_children(iter)
134 }
135}
136
137impl<W> Meta for Grid<W> {}
138
139impl<W, Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Grid<W>
140where
141 Renderer: crate::core::Renderer,
142 W: Widget<Message, Theme, Renderer>,
143{
144 fn diff(&mut self, tree: &mut Tree) {
145 tree.diff_children(&mut self.children);
146 }
147
148 fn size(&self) -> Size<Length> {
149 Size {
150 width: self
151 .width
152 .map(|pixels| Length::Fixed(pixels.0))
153 .unwrap_or(Length::Fill),
154 height: match self.height {
155 Sizing::AspectRatio(_) => Length::Shrink,
156 Sizing::EvenlyDistribute(length) => length,
157 },
158 }
159 }
160
161 fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits) {
162 let size = self.size();
163 let limits = limits.width(size.width).height(size.height);
164 let available = limits.max;
165
166 if limits.compression.width && self.width.is_none() {
167 tree.size = Size::ZERO;
168 return;
169 }
170
171 let cells_per_row = match self.columns {
172 Constraint::MaxWidth(pixels) => {
174 ((available.width + self.spacing) / (pixels.0 + self.spacing)).ceil() as usize
175 }
176 Constraint::Amount(amount) => amount,
177 };
178
179 if self.children.is_empty() || cells_per_row == 0 {
180 tree.size = limits.resolve(size.width, size.height, Size::ZERO);
181 return;
182 }
183
184 let cell_width =
185 (available.width - self.spacing * (cells_per_row - 1) as f32) / cells_per_row as f32;
186
187 let cell_height = match self.height {
188 Sizing::AspectRatio(ratio) => Some(cell_width / ratio),
189 Sizing::EvenlyDistribute(Length::Shrink) => None,
190 Sizing::EvenlyDistribute(_) => {
191 let total_rows = self.children.len().div_ceil(cells_per_row);
192 Some(
193 (available.height - self.spacing * (total_rows - 1) as f32) / total_rows as f32,
194 )
195 }
196 };
197
198 let cell_limits = layout::Limits::new(
199 Size::new(cell_width, cell_height.unwrap_or(0.0)),
200 Size::new(cell_width, cell_height.unwrap_or(available.height)),
201 );
202
203 let mut x = 0.0;
204 let mut y = 0.0;
205 let mut row_height = 0.0f32;
206
207 for (i, (child, tree)) in self.children.iter_mut().zip(&mut tree.children).enumerate() {
208 child.layout(tree, renderer, &cell_limits);
209
210 let size = tree.size;
211
212 tree.translation = Vector::new(x, y);
213
214 x += size.width + self.spacing;
215 row_height = row_height.max(size.height);
216
217 if (i + 1) % cells_per_row == 0 {
218 y += cell_height.unwrap_or(row_height) + self.spacing;
219 x = 0.0;
220 row_height = 0.0;
221 }
222 }
223
224 if x == 0.0 {
225 y -= self.spacing;
226 } else {
227 y += cell_height.unwrap_or(row_height);
228 }
229
230 tree.size = Size::new(available.width, y);
231 }
232
233 fn operate(
234 &mut self,
235 tree: &mut Tree,
236 layout: Layout,
237 viewport: &Rectangle,
238 renderer: &Renderer,
239 operation: &mut dyn Operation,
240 ) {
241 operation.container(None, layout.bounds(), viewport);
242 operation.traverse(&mut |operation| {
243 self.children
244 .iter_mut()
245 .zip(layout.iter_mut(&mut tree.children))
246 .for_each(|(child, (layout, state))| {
247 child.operate(state, layout, viewport, renderer, operation);
248 });
249 });
250 }
251
252 fn update(
253 &mut self,
254 tree: &mut Tree,
255 event: &Event,
256 layout: Layout,
257 cursor: mouse::Cursor,
258 renderer: &Renderer,
259 shell: &mut Shell<'_, Message>,
260 viewport: &Rectangle,
261 ) {
262 for (child, (layout, tree)) in self
263 .children
264 .iter_mut()
265 .zip(layout.iter_mut(&mut tree.children))
266 {
267 child.update(tree, event, layout, cursor, renderer, shell, viewport);
268 }
269 }
270
271 fn mouse_interaction(
272 &self,
273 tree: &Tree,
274 layout: Layout,
275 cursor: mouse::Cursor,
276 viewport: &Rectangle,
277 renderer: &Renderer,
278 ) -> mouse::Interaction {
279 self.children
280 .iter()
281 .zip(layout.iter(&tree.children))
282 .map(|(child, (layout, tree))| {
283 child.mouse_interaction(tree, layout, cursor, viewport, renderer)
284 })
285 .max()
286 .unwrap_or_default()
287 }
288
289 fn draw(
290 &self,
291 tree: &Tree,
292 renderer: &mut Renderer,
293 theme: &Theme,
294 style: &renderer::Style,
295 layout: Layout,
296 cursor: mouse::Cursor,
297 viewport: &Rectangle,
298 ) {
299 if let Some(viewport) = layout.bounds().intersection(viewport) {
300 for (child, (layout, tree)) in self
301 .children
302 .iter()
303 .zip(layout.iter(&tree.children))
304 .filter(|(_, (layout, _))| layout.bounds().intersects(&viewport))
305 {
306 child.draw(tree, renderer, theme, style, layout, cursor, &viewport);
307 }
308 }
309 }
310
311 fn overlay<'b>(
312 &'b mut self,
313 tree: &'b mut Tree,
314 layout: Layout,
315 renderer: &Renderer,
316 viewport: &Rectangle,
317 translation: Vector,
318 window: Size,
319 ) -> Vec<overlay::Element<'b, Message, Theme, Renderer>> {
320 overlay::from_children(
321 &mut self.children,
322 tree,
323 layout,
324 renderer,
325 viewport,
326 translation,
327 window,
328 )
329 }
330}
331
332#[derive(Debug, Clone, Copy, PartialEq)]
334pub enum Sizing {
335 AspectRatio(f32),
341
342 EvenlyDistribute(Length),
345}
346
347impl From<f32> for Sizing {
348 fn from(height: f32) -> Self {
349 Self::EvenlyDistribute(Length::from(height))
350 }
351}
352
353impl From<Length> for Sizing {
354 fn from(height: Length) -> Self {
355 Self::EvenlyDistribute(height)
356 }
357}
358
359pub fn aspect_ratio(width: impl Into<Pixels>, height: impl Into<Pixels>) -> Sizing {
361 Sizing::AspectRatio(width.into().0 / height.into().0)
362}