1use crate::core;
3use crate::core::alignment;
4use crate::core::layout;
5use crate::core::mouse;
6use crate::core::overlay;
7use crate::core::renderer;
8use crate::core::widget;
9use crate::core::{
10 Alignment, Background, Element, Layout, Length, Pixels, Rectangle, Size, Widget,
11};
12
13pub fn table<'a, 'b, T, Message, Theme, Renderer>(
18 columns: impl IntoIterator<Item = Column<'a, 'b, T, Message, Theme, Renderer>>,
19 rows: impl IntoIterator<Item = T>,
20) -> Table<'a, Message, Theme, Renderer>
21where
22 T: Clone,
23 Theme: Catalog,
24 Renderer: core::Renderer,
25{
26 Table::new(columns, rows)
27}
28
29pub fn column<'a, 'b, T, E, Message, Theme, Renderer>(
34 header: impl Into<Element<'a, Message, Theme, Renderer>>,
35 view: impl Fn(T) -> E + 'b,
36) -> Column<'a, 'b, T, Message, Theme, Renderer>
37where
38 T: 'a,
39 E: Into<Element<'a, Message, Theme, Renderer>>,
40{
41 Column {
42 header: header.into(),
43 view: Box::new(move |data| view(data).into()),
44 width: Length::Shrink,
45 align_x: alignment::Horizontal::Left,
46 align_y: alignment::Vertical::Top,
47 }
48}
49
50pub struct Table<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
52where
53 Theme: Catalog,
54{
55 columns: Vec<Column_>,
56 cells: Vec<Element<'a, Message, Theme, Renderer>>,
57 width: Length,
58 height: Length,
59 padding_x: f32,
60 padding_y: f32,
61 separator_x: f32,
62 separator_y: f32,
63 class: Theme::Class<'a>,
64}
65
66struct Column_ {
67 width: Length,
68 align_x: alignment::Horizontal,
69 align_y: alignment::Vertical,
70}
71
72impl<'a, Message, Theme, Renderer> Table<'a, Message, Theme, Renderer>
73where
74 Theme: Catalog,
75 Renderer: core::Renderer,
76{
77 pub fn new<'b, T>(
82 columns: impl IntoIterator<Item = Column<'a, 'b, T, Message, Theme, Renderer>>,
83 rows: impl IntoIterator<Item = T>,
84 ) -> Self
85 where
86 T: Clone,
87 {
88 let columns = columns.into_iter();
89 let rows = rows.into_iter();
90
91 let mut width = Length::Shrink;
92 let mut height = Length::Shrink;
93
94 let mut cells = Vec::with_capacity(columns.size_hint().0 * (1 + rows.size_hint().0));
95
96 let (mut columns, views): (Vec<_>, Vec<_>) = columns
97 .map(|column| {
98 width = width.enclose(column.width);
99
100 cells.push(column.header);
101
102 (
103 Column_ {
104 width: column.width,
105 align_x: column.align_x,
106 align_y: column.align_y,
107 },
108 column.view,
109 )
110 })
111 .collect();
112
113 for row in rows {
114 for view in &views {
115 let cell = view(row.clone());
116 let size_hint = cell.as_widget().size_hint();
117
118 height = height.enclose(size_hint.height);
119
120 cells.push(cell);
121 }
122 }
123
124 if width == Length::Shrink
125 && let Some(first) = columns.first_mut()
126 {
127 first.width = Length::Fill;
128 }
129
130 Self {
131 columns,
132 cells,
133 width,
134 height,
135 padding_x: 10.0,
136 padding_y: 5.0,
137 separator_x: 1.0,
138 separator_y: 1.0,
139 class: Theme::default(),
140 }
141 }
142
143 pub fn width(mut self, width: impl Into<Length>) -> Self {
145 self.width = width.into();
146 self
147 }
148
149 pub fn padding(self, padding: impl Into<Pixels>) -> Self {
151 let padding = padding.into();
152
153 self.padding_x(padding).padding_y(padding)
154 }
155
156 pub fn padding_x(mut self, padding: impl Into<Pixels>) -> Self {
158 self.padding_x = padding.into().0;
159 self
160 }
161
162 pub fn padding_y(mut self, padding: impl Into<Pixels>) -> Self {
164 self.padding_y = padding.into().0;
165 self
166 }
167
168 pub fn separator(self, separator: impl Into<Pixels>) -> Self {
170 let separator = separator.into();
171
172 self.separator_x(separator).separator_y(separator)
173 }
174
175 pub fn separator_x(mut self, separator: impl Into<Pixels>) -> Self {
177 self.separator_x = separator.into().0;
178 self
179 }
180
181 pub fn separator_y(mut self, separator: impl Into<Pixels>) -> Self {
183 self.separator_y = separator.into().0;
184 self
185 }
186}
187
188struct Metrics {
189 columns: Vec<f32>,
190 rows: Vec<f32>,
191}
192
193impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
194 for Table<'a, Message, Theme, Renderer>
195where
196 Theme: Catalog,
197 Renderer: core::Renderer,
198{
199 fn size(&self) -> Size<Length> {
200 Size {
201 width: self.width,
202 height: self.height,
203 }
204 }
205
206 fn tag(&self) -> widget::tree::Tag {
207 widget::tree::Tag::of::<Metrics>()
208 }
209
210 fn state(&self) -> widget::tree::State {
211 widget::tree::State::new(Metrics {
212 columns: Vec::new(),
213 rows: Vec::new(),
214 })
215 }
216
217 fn children(&self) -> Vec<widget::Tree> {
218 self.cells
219 .iter()
220 .map(|cell| widget::Tree::new(cell.as_widget()))
221 .collect()
222 }
223
224 fn diff(&self, tree: &mut widget::Tree) {
225 tree.diff_children(&self.cells);
226 }
227
228 fn layout(
229 &mut self,
230 tree: &mut widget::Tree,
231 renderer: &Renderer,
232 limits: &layout::Limits,
233 ) -> layout::Node {
234 let metrics = tree.state.downcast_mut::<Metrics>();
235 let columns = self.columns.len();
236 let rows = self.cells.len() / columns;
237
238 let limits = limits.width(self.width).height(self.height);
239 let available = limits.max();
240 let table_fluid = self.width.fluid();
241
242 let mut cells = Vec::with_capacity(self.cells.len());
243 cells.resize(self.cells.len(), layout::Node::default());
244
245 metrics.columns = vec![0.0; self.columns.len()];
246 metrics.rows = vec![0.0; rows];
247
248 let mut column_factors = vec![0; self.columns.len()];
249 let mut total_row_factors = 0;
250 let mut total_fluid_height = 0.0;
251 let mut row_factor = 0;
252
253 let spacing_x = self.padding_x * 2.0 + self.separator_x;
254 let spacing_y = self.padding_y * 2.0 + self.separator_y;
255
256 let mut x = self.padding_x;
259 let mut y = self.padding_y;
260
261 for (i, (cell, state)) in self.cells.iter_mut().zip(&mut tree.children).enumerate() {
262 let row = i / columns;
263 let column = i % columns;
264
265 let width = self.columns[column].width;
266 let size = cell.as_widget().size();
267
268 if column == 0 {
269 x = self.padding_x;
270
271 if row > 0 {
272 y += metrics.rows[row - 1] + spacing_y;
273
274 if row_factor != 0 {
275 total_fluid_height += metrics.rows[row - 1];
276 total_row_factors += row_factor;
277
278 row_factor = 0;
279 }
280 }
281 }
282
283 let width_factor = width.fill_factor();
284 let height_factor = size.height.fill_factor();
285
286 if width_factor != 0 || height_factor != 0 || size.width.is_fill() {
287 column_factors[column] = column_factors[column].max(width_factor);
288
289 row_factor = row_factor.max(height_factor);
290
291 continue;
292 }
293
294 let limits = layout::Limits::new(
295 Size::ZERO,
296 Size::new(available.width - x, available.height - y),
297 )
298 .width(width);
299
300 let layout = cell.as_widget_mut().layout(state, renderer, &limits);
301 let size = limits.resolve(width, Length::Shrink, layout.size());
302
303 metrics.columns[column] = metrics.columns[column].max(size.width);
304 metrics.rows[row] = metrics.rows[row].max(size.height);
305 cells[i] = layout;
306
307 x += size.width + spacing_x;
308 }
309
310 let left = Size::new(
313 available.width
314 - metrics
315 .columns
316 .iter()
317 .enumerate()
318 .filter(|(i, _)| column_factors[*i] == 0)
319 .map(|(_, width)| width)
320 .sum::<f32>(),
321 available.height - total_fluid_height,
322 );
323
324 let width_unit = (left.width
325 - spacing_x * self.columns.len().saturating_sub(1) as f32
326 - self.padding_x * 2.0)
327 / column_factors.iter().sum::<u16>() as f32;
328
329 let height_unit =
330 (left.height - spacing_y * rows.saturating_sub(1) as f32 - self.padding_y * 2.0)
331 / total_row_factors as f32;
332
333 let mut x = self.padding_x;
334 let mut y = self.padding_y;
335
336 for (i, (cell, state)) in self.cells.iter_mut().zip(&mut tree.children).enumerate() {
337 let row = i / columns;
338 let column = i % columns;
339
340 let size = cell.as_widget().size();
341
342 let width = self.columns[column].width;
343 let width_factor = width.fill_factor();
344 let height_factor = size.height.fill_factor();
345
346 if column == 0 {
347 x = self.padding_x;
348
349 if row > 0 {
350 y += metrics.rows[row - 1] + spacing_y;
351 }
352 }
353
354 if width_factor == 0 && size.width.fill_factor() == 0 && size.height.fill_factor() == 0
355 {
356 continue;
357 }
358
359 let max_width = if width_factor == 0 {
360 if size.width.is_fill() {
361 metrics.columns[column]
362 } else {
363 (available.width - x).max(0.0)
364 }
365 } else {
366 width_unit * width_factor as f32
367 };
368
369 let max_height = if height_factor == 0 {
370 if size.height.is_fill() {
371 metrics.rows[row]
372 } else {
373 (available.height - y).max(0.0)
374 }
375 } else {
376 height_unit * height_factor as f32
377 };
378
379 let limits =
380 layout::Limits::new(Size::ZERO, Size::new(max_width, max_height)).width(width);
381
382 let layout = cell.as_widget_mut().layout(state, renderer, &limits);
383 let size = limits.resolve(
384 if let Length::Fixed(_) = width {
385 width
386 } else {
387 table_fluid
388 },
389 Length::Shrink,
390 layout.size(),
391 );
392
393 metrics.columns[column] = metrics.columns[column].max(size.width);
394 metrics.rows[row] = metrics.rows[row].max(size.height);
395 cells[i] = layout;
396
397 x += size.width + spacing_x;
398 }
399
400 let mut x = self.padding_x;
403 let mut y = self.padding_y;
404
405 for (i, cell) in cells.iter_mut().enumerate() {
406 let row = i / columns;
407 let column = i % columns;
408
409 if column == 0 {
410 x = self.padding_x;
411
412 if row > 0 {
413 y += metrics.rows[row - 1] + spacing_y;
414 }
415 }
416
417 let Column_ {
418 align_x, align_y, ..
419 } = &self.columns[column];
420
421 cell.move_to_mut((x, y));
422 cell.align_mut(
423 Alignment::from(*align_x),
424 Alignment::from(*align_y),
425 Size::new(metrics.columns[column], metrics.rows[row]),
426 );
427
428 x += metrics.columns[column] + spacing_x;
429 }
430
431 let intrinsic = limits.resolve(
432 self.width,
433 self.height,
434 Size::new(
435 x - spacing_x + self.padding_x,
436 y + metrics
437 .rows
438 .last()
439 .copied()
440 .map(|height| height + self.padding_y)
441 .unwrap_or_default(),
442 ),
443 );
444
445 layout::Node::with_children(intrinsic, cells)
446 }
447
448 fn update(
449 &mut self,
450 tree: &mut widget::Tree,
451 event: &core::Event,
452 layout: Layout<'_>,
453 cursor: mouse::Cursor,
454 renderer: &Renderer,
455 clipboard: &mut dyn core::Clipboard,
456 shell: &mut core::Shell<'_, Message>,
457 viewport: &Rectangle,
458 ) {
459 for ((cell, tree), layout) in self
460 .cells
461 .iter_mut()
462 .zip(&mut tree.children)
463 .zip(layout.children())
464 {
465 cell.as_widget_mut().update(
466 tree, event, layout, cursor, renderer, clipboard, shell, viewport,
467 );
468 }
469 }
470
471 fn draw(
472 &self,
473 tree: &widget::Tree,
474 renderer: &mut Renderer,
475 theme: &Theme,
476 style: &renderer::Style,
477 layout: Layout<'_>,
478 cursor: mouse::Cursor,
479 viewport: &Rectangle,
480 ) {
481 for ((cell, state), layout) in self.cells.iter().zip(&tree.children).zip(layout.children())
482 {
483 cell.as_widget()
484 .draw(state, renderer, theme, style, layout, cursor, viewport);
485 }
486
487 let bounds = layout.bounds();
488 let metrics = tree.state.downcast_ref::<Metrics>();
489 let style = theme.style(&self.class);
490
491 if self.separator_x > 0.0 {
492 let mut x = self.padding_x;
493
494 for width in &metrics.columns[..metrics.columns.len().saturating_sub(1)] {
495 x += width + self.padding_x;
496
497 renderer.fill_quad(
498 renderer::Quad {
499 bounds: Rectangle {
500 x: bounds.x + x,
501 y: bounds.y,
502 width: self.separator_x,
503 height: bounds.height,
504 },
505 snap: true,
506 ..renderer::Quad::default()
507 },
508 style.separator_x,
509 );
510
511 x += self.separator_x + self.padding_x;
512 }
513 }
514
515 if self.separator_y > 0.0 {
516 let mut y = self.padding_y;
517
518 for height in &metrics.rows[..metrics.rows.len().saturating_sub(1)] {
519 y += height + self.padding_y;
520
521 renderer.fill_quad(
522 renderer::Quad {
523 bounds: Rectangle {
524 x: bounds.x,
525 y: bounds.y + y,
526 width: bounds.width,
527 height: self.separator_y,
528 },
529 snap: true,
530 ..renderer::Quad::default()
531 },
532 style.separator_y,
533 );
534
535 y += self.separator_y + self.padding_y;
536 }
537 }
538 }
539
540 fn mouse_interaction(
541 &self,
542 tree: &widget::Tree,
543 layout: Layout<'_>,
544 cursor: mouse::Cursor,
545 viewport: &Rectangle,
546 renderer: &Renderer,
547 ) -> mouse::Interaction {
548 self.cells
549 .iter()
550 .zip(&tree.children)
551 .zip(layout.children())
552 .map(|((cell, tree), layout)| {
553 cell.as_widget()
554 .mouse_interaction(tree, layout, cursor, viewport, renderer)
555 })
556 .max()
557 .unwrap_or_default()
558 }
559
560 fn operate(
561 &mut self,
562 tree: &mut widget::Tree,
563 layout: Layout<'_>,
564 renderer: &Renderer,
565 operation: &mut dyn widget::Operation,
566 ) {
567 for ((cell, state), layout) in self
568 .cells
569 .iter_mut()
570 .zip(&mut tree.children)
571 .zip(layout.children())
572 {
573 cell.as_widget_mut()
574 .operate(state, layout, renderer, operation);
575 }
576 }
577
578 fn overlay<'b>(
579 &'b mut self,
580 tree: &'b mut widget::Tree,
581 layout: Layout<'b>,
582 renderer: &Renderer,
583 viewport: &Rectangle,
584 translation: core::Vector,
585 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
586 overlay::from_children(
587 &mut self.cells,
588 tree,
589 layout,
590 renderer,
591 viewport,
592 translation,
593 )
594 }
595}
596
597impl<'a, Message, Theme, Renderer> From<Table<'a, Message, Theme, Renderer>>
598 for Element<'a, Message, Theme, Renderer>
599where
600 Message: 'a,
601 Theme: Catalog + 'a,
602 Renderer: core::Renderer + 'a,
603{
604 fn from(table: Table<'a, Message, Theme, Renderer>) -> Self {
605 Element::new(table)
606 }
607}
608
609pub struct Column<'a, 'b, T, Message, Theme = crate::Theme, Renderer = crate::Renderer> {
611 header: Element<'a, Message, Theme, Renderer>,
612 view: Box<dyn Fn(T) -> Element<'a, Message, Theme, Renderer> + 'b>,
613 width: Length,
614 align_x: alignment::Horizontal,
615 align_y: alignment::Vertical,
616}
617
618impl<'a, 'b, T, Message, Theme, Renderer> Column<'a, 'b, T, Message, Theme, Renderer> {
619 pub fn width(mut self, width: impl Into<Length>) -> Self {
621 self.width = width.into();
622 self
623 }
624
625 pub fn align_x(mut self, alignment: impl Into<alignment::Horizontal>) -> Self {
627 self.align_x = alignment.into();
628 self
629 }
630
631 pub fn align_y(mut self, alignment: impl Into<alignment::Vertical>) -> Self {
633 self.align_y = alignment.into();
634 self
635 }
636}
637
638#[derive(Debug, Clone, Copy)]
640pub struct Style {
641 pub separator_x: Background,
643 pub separator_y: Background,
645}
646
647pub trait Catalog {
649 type Class<'a>;
651
652 fn default<'a>() -> Self::Class<'a>;
654
655 fn style(&self, class: &Self::Class<'_>) -> Style;
657}
658
659pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
661
662impl<Theme> From<Style> for StyleFn<'_, Theme> {
663 fn from(style: Style) -> Self {
664 Box::new(move |_theme| style)
665 }
666}
667
668impl Catalog for crate::Theme {
669 type Class<'a> = StyleFn<'a, Self>;
670
671 fn default<'a>() -> Self::Class<'a> {
672 Box::new(default)
673 }
674
675 fn style(&self, class: &Self::Class<'_>) -> Style {
676 class(self)
677 }
678}
679
680pub fn default(theme: &crate::Theme) -> Style {
682 let palette = theme.extended_palette();
683 let separator = palette.background.strong.color.into();
684
685 Style {
686 separator_x: separator,
687 separator_y: separator,
688 }
689}