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