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