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