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 children(&self) -> Vec<Tree> {
144 self.children.iter().map(Tree::new).collect()
145 }
146
147 fn diff(&self, tree: &mut Tree) {
148 tree.diff_children(&self.children);
149 }
150
151 fn size(&self) -> Size<Length> {
152 Size {
153 width: self
154 .width
155 .map(|pixels| Length::Fixed(pixels.0))
156 .unwrap_or(Length::Fill),
157 height: match self.height {
158 Sizing::AspectRatio(_) => Length::Shrink,
159 Sizing::EvenlyDistribute(length) => length,
160 },
161 }
162 }
163
164 fn layout(
165 &mut self,
166 tree: &mut Tree,
167 renderer: &Renderer,
168 limits: &layout::Limits,
169 ) -> layout::Node {
170 let size = self.size();
171 let limits = limits.width(size.width).height(size.height);
172 let available = limits.max();
173
174 if limits.compression().width && self.width.is_none() {
175 return layout::Node::new(Size::ZERO);
176 }
177
178 let cells_per_row = match self.columns {
179 Constraint::MaxWidth(pixels) => {
181 ((available.width + self.spacing) / (pixels.0 + self.spacing)).ceil() as usize
182 }
183 Constraint::Amount(amount) => amount,
184 };
185
186 if self.children.is_empty() || cells_per_row == 0 {
187 return layout::Node::new(limits.resolve(size.width, size.height, Size::ZERO));
188 }
189
190 let cell_width =
191 (available.width - self.spacing * (cells_per_row - 1) as f32) / cells_per_row as f32;
192
193 let cell_height = match self.height {
194 Sizing::AspectRatio(ratio) => Some(cell_width / ratio),
195 Sizing::EvenlyDistribute(Length::Shrink) => None,
196 Sizing::EvenlyDistribute(_) => {
197 let total_rows = self.children.len().div_ceil(cells_per_row);
198 Some(
199 (available.height - self.spacing * (total_rows - 1) as f32) / total_rows as f32,
200 )
201 }
202 };
203
204 let cell_limits = layout::Limits::new(
205 Size::new(cell_width, cell_height.unwrap_or(0.0)),
206 Size::new(cell_width, cell_height.unwrap_or(available.height)),
207 );
208
209 let mut nodes = Vec::with_capacity(self.children.len());
210 let mut x = 0.0;
211 let mut y = 0.0;
212 let mut row_height = 0.0f32;
213
214 for (i, (child, tree)) in self.children.iter_mut().zip(&mut tree.children).enumerate() {
215 let node = child
216 .as_widget_mut()
217 .layout(tree, renderer, &cell_limits)
218 .move_to((x, y));
219
220 let size = node.size();
221
222 x += size.width + self.spacing;
223 row_height = row_height.max(size.height);
224
225 if (i + 1) % cells_per_row == 0 {
226 y += cell_height.unwrap_or(row_height) + self.spacing;
227 x = 0.0;
228 row_height = 0.0;
229 }
230
231 nodes.push(node);
232 }
233
234 if x == 0.0 {
235 y -= self.spacing;
236 } else {
237 y += cell_height.unwrap_or(row_height);
238 }
239
240 layout::Node::with_children(Size::new(available.width, y), nodes)
241 }
242
243 fn operate(
244 &mut self,
245 tree: &mut Tree,
246 layout: Layout<'_>,
247 renderer: &Renderer,
248 operation: &mut dyn Operation,
249 ) {
250 operation.container(None, layout.bounds());
251 operation.traverse(&mut |operation| {
252 self.children
253 .iter_mut()
254 .zip(&mut tree.children)
255 .zip(layout.children())
256 .for_each(|((child, state), layout)| {
257 child
258 .as_widget_mut()
259 .operate(state, layout, renderer, operation);
260 });
261 });
262 }
263
264 fn update(
265 &mut self,
266 tree: &mut Tree,
267 event: &Event,
268 layout: Layout<'_>,
269 cursor: mouse::Cursor,
270 renderer: &Renderer,
271 shell: &mut Shell<'_, Message>,
272 viewport: &Rectangle,
273 ) {
274 for ((child, tree), layout) in self
275 .children
276 .iter_mut()
277 .zip(&mut tree.children)
278 .zip(layout.children())
279 {
280 child
281 .as_widget_mut()
282 .update(tree, event, layout, cursor, renderer, shell, viewport);
283 }
284 }
285
286 fn mouse_interaction(
287 &self,
288 tree: &Tree,
289 layout: Layout<'_>,
290 cursor: mouse::Cursor,
291 viewport: &Rectangle,
292 renderer: &Renderer,
293 ) -> mouse::Interaction {
294 self.children
295 .iter()
296 .zip(&tree.children)
297 .zip(layout.children())
298 .map(|((child, tree), layout)| {
299 child
300 .as_widget()
301 .mouse_interaction(tree, layout, cursor, viewport, renderer)
302 })
303 .max()
304 .unwrap_or_default()
305 }
306
307 fn draw(
308 &self,
309 tree: &Tree,
310 renderer: &mut Renderer,
311 theme: &Theme,
312 style: &renderer::Style,
313 layout: Layout<'_>,
314 cursor: mouse::Cursor,
315 viewport: &Rectangle,
316 ) {
317 if let Some(viewport) = layout.bounds().intersection(viewport) {
318 for ((child, tree), layout) in self
319 .children
320 .iter()
321 .zip(&tree.children)
322 .zip(layout.children())
323 .filter(|(_, layout)| layout.bounds().intersects(&viewport))
324 {
325 child
326 .as_widget()
327 .draw(tree, renderer, theme, style, layout, cursor, &viewport);
328 }
329 }
330 }
331
332 fn overlay<'b>(
333 &'b mut self,
334 tree: &'b mut Tree,
335 layout: Layout<'b>,
336 renderer: &Renderer,
337 viewport: &Rectangle,
338 translation: Vector,
339 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
340 overlay::from_children(
341 &mut self.children,
342 tree,
343 layout,
344 renderer,
345 viewport,
346 translation,
347 )
348 }
349}
350
351impl<'a, Message, Theme, Renderer> From<Grid<'a, Message, Theme, Renderer>>
352 for Element<'a, Message, Theme, Renderer>
353where
354 Message: 'a,
355 Theme: 'a,
356 Renderer: crate::core::Renderer + 'a,
357{
358 fn from(row: Grid<'a, Message, Theme, Renderer>) -> Self {
359 Self::new(row)
360 }
361}
362
363#[derive(Debug, Clone, Copy, PartialEq)]
365pub enum Sizing {
366 AspectRatio(f32),
372
373 EvenlyDistribute(Length),
376}
377
378impl From<f32> for Sizing {
379 fn from(height: f32) -> Self {
380 Self::EvenlyDistribute(Length::from(height))
381 }
382}
383
384impl From<Length> for Sizing {
385 fn from(height: Length) -> Self {
386 Self::EvenlyDistribute(height)
387 }
388}
389
390pub fn aspect_ratio(width: impl Into<Pixels>, height: impl Into<Pixels>) -> Sizing {
392 Sizing::AspectRatio(width.into().0 / height.into().0)
393}