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