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.enclose(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.enclose(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 = self.width.fluid();
234
235 let mut cells = Vec::with_capacity(self.cells.len());
236 cells.resize(self.cells.len(), layout::Node::default());
237
238 metrics.columns = vec![0.0; self.columns.len()];
239 metrics.rows = vec![0.0; rows];
240
241 let mut column_factors = vec![0; self.columns.len()];
242 let mut total_row_factors = 0;
243 let mut total_fluid_height = 0.0;
244 let mut row_factor = 0;
245
246 let spacing_x = self.padding_x * 2.0 + self.separator_x;
247 let spacing_y = self.padding_y * 2.0 + self.separator_y;
248
249 let mut x = self.padding_x;
252 let mut y = self.padding_y;
253
254 for (i, (cell, state)) in self.cells.iter_mut().zip(&mut tree.children).enumerate() {
255 let row = i / columns;
256 let column = i % columns;
257
258 let width = self.columns[column].width;
259 let size = cell.as_widget().size();
260
261 if column == 0 {
262 x = self.padding_x;
263
264 if row > 0 {
265 y += metrics.rows[row - 1] + spacing_y;
266
267 if row_factor != 0 {
268 total_fluid_height += metrics.rows[row - 1];
269 total_row_factors += row_factor;
270
271 row_factor = 0;
272 }
273 }
274 }
275
276 let width_factor = width.fill_factor();
277 let height_factor = size.height.fill_factor();
278
279 if width_factor != 0 || height_factor != 0 || size.width.is_fill() {
280 column_factors[column] = column_factors[column].max(width_factor);
281
282 row_factor = row_factor.max(height_factor);
283
284 continue;
285 }
286
287 let limits = layout::Limits::new(
288 Size::ZERO,
289 Size::new(available.width - x, available.height - y),
290 )
291 .width(width);
292
293 let layout = cell.as_widget_mut().layout(state, renderer, &limits);
294 let size = limits.resolve(width, Length::Shrink, layout.size());
295
296 metrics.columns[column] = metrics.columns[column].max(size.width);
297 metrics.rows[row] = metrics.rows[row].max(size.height);
298 cells[i] = layout;
299
300 x += size.width + spacing_x;
301 }
302
303 let left = Size::new(
306 available.width
307 - metrics
308 .columns
309 .iter()
310 .enumerate()
311 .filter(|(i, _)| column_factors[*i] == 0)
312 .map(|(_, width)| width)
313 .sum::<f32>(),
314 available.height - total_fluid_height,
315 );
316
317 let width_unit = (left.width
318 - spacing_x * self.columns.len().saturating_sub(1) as f32
319 - self.padding_x * 2.0)
320 / column_factors.iter().sum::<u16>() as f32;
321
322 let height_unit =
323 (left.height - spacing_y * rows.saturating_sub(1) as f32 - self.padding_y * 2.0)
324 / total_row_factors as f32;
325
326 let mut x = self.padding_x;
327 let mut y = self.padding_y;
328
329 for (i, (cell, state)) in self.cells.iter_mut().zip(&mut tree.children).enumerate() {
330 let row = i / columns;
331 let column = i % columns;
332
333 let size = cell.as_widget().size();
334
335 let width = self.columns[column].width;
336 let width_factor = width.fill_factor();
337 let height_factor = size.height.fill_factor();
338
339 if column == 0 {
340 x = self.padding_x;
341
342 if row > 0 {
343 y += metrics.rows[row - 1] + spacing_y;
344 }
345 }
346
347 if width_factor == 0 && size.width.fill_factor() == 0 && size.height.fill_factor() == 0
348 {
349 continue;
350 }
351
352 let max_width = if width_factor == 0 {
353 if size.width.is_fill() {
354 metrics.columns[column]
355 } else {
356 (available.width - x).max(0.0)
357 }
358 } else {
359 width_unit * width_factor as f32
360 };
361
362 let max_height = if height_factor == 0 {
363 if size.height.is_fill() {
364 metrics.rows[row]
365 } else {
366 (available.height - y).max(0.0)
367 }
368 } else {
369 height_unit * height_factor as f32
370 };
371
372 let limits =
373 layout::Limits::new(Size::ZERO, Size::new(max_width, max_height)).width(width);
374
375 let layout = cell.as_widget_mut().layout(state, renderer, &limits);
376 let size = limits.resolve(
377 if let Length::Fixed(_) = width {
378 width
379 } else {
380 table_fluid
381 },
382 Length::Shrink,
383 layout.size(),
384 );
385
386 metrics.columns[column] = metrics.columns[column].max(size.width);
387 metrics.rows[row] = metrics.rows[row].max(size.height);
388 cells[i] = layout;
389
390 x += size.width + spacing_x;
391 }
392
393 let mut x = self.padding_x;
396 let mut y = self.padding_y;
397
398 for (i, cell) in cells.iter_mut().enumerate() {
399 let row = i / columns;
400 let column = i % columns;
401
402 if column == 0 {
403 x = self.padding_x;
404
405 if row > 0 {
406 y += metrics.rows[row - 1] + spacing_y;
407 }
408 }
409
410 let Column_ {
411 align_x, align_y, ..
412 } = &self.columns[column];
413
414 cell.move_to_mut((x, y));
415 cell.align_mut(
416 Alignment::from(*align_x),
417 Alignment::from(*align_y),
418 Size::new(metrics.columns[column], metrics.rows[row]),
419 );
420
421 x += metrics.columns[column] + spacing_x;
422 }
423
424 let intrinsic = limits.resolve(
425 self.width,
426 self.height,
427 Size::new(
428 x - spacing_x + self.padding_x,
429 y + metrics
430 .rows
431 .last()
432 .copied()
433 .map(|height| height + self.padding_y)
434 .unwrap_or_default(),
435 ),
436 );
437
438 layout::Node::with_children(intrinsic, cells)
439 }
440
441 fn update(
442 &mut self,
443 tree: &mut widget::Tree,
444 event: &core::Event,
445 layout: Layout<'_>,
446 cursor: mouse::Cursor,
447 renderer: &Renderer,
448 shell: &mut core::Shell<'_, Message>,
449 viewport: &Rectangle,
450 ) {
451 for ((cell, tree), layout) in self
452 .cells
453 .iter_mut()
454 .zip(&mut tree.children)
455 .zip(layout.children())
456 {
457 cell.as_widget_mut()
458 .update(tree, event, layout, cursor, renderer, shell, viewport);
459 }
460 }
461
462 fn draw(
463 &self,
464 tree: &widget::Tree,
465 renderer: &mut Renderer,
466 theme: &Theme,
467 style: &renderer::Style,
468 layout: Layout<'_>,
469 cursor: mouse::Cursor,
470 viewport: &Rectangle,
471 ) {
472 for ((cell, state), layout) in self.cells.iter().zip(&tree.children).zip(layout.children())
473 {
474 cell.as_widget()
475 .draw(state, renderer, theme, style, layout, cursor, viewport);
476 }
477
478 let bounds = layout.bounds();
479 let metrics = tree.state.downcast_ref::<Metrics>();
480 let style = theme.style(&self.class);
481
482 if self.separator_x > 0.0 {
483 let mut x = self.padding_x;
484
485 for width in &metrics.columns[..metrics.columns.len().saturating_sub(1)] {
486 x += width + self.padding_x;
487
488 renderer.fill_quad(
489 renderer::Quad {
490 bounds: Rectangle {
491 x: bounds.x + x,
492 y: bounds.y,
493 width: self.separator_x,
494 height: bounds.height,
495 },
496 snap: true,
497 ..renderer::Quad::default()
498 },
499 style.separator_x,
500 );
501
502 x += self.separator_x + self.padding_x;
503 }
504 }
505
506 if self.separator_y > 0.0 {
507 let mut y = self.padding_y;
508
509 for height in &metrics.rows[..metrics.rows.len().saturating_sub(1)] {
510 y += height + self.padding_y;
511
512 renderer.fill_quad(
513 renderer::Quad {
514 bounds: Rectangle {
515 x: bounds.x,
516 y: bounds.y + y,
517 width: bounds.width,
518 height: self.separator_y,
519 },
520 snap: true,
521 ..renderer::Quad::default()
522 },
523 style.separator_y,
524 );
525
526 y += self.separator_y + self.padding_y;
527 }
528 }
529 }
530
531 fn mouse_interaction(
532 &self,
533 tree: &widget::Tree,
534 layout: Layout<'_>,
535 cursor: mouse::Cursor,
536 viewport: &Rectangle,
537 renderer: &Renderer,
538 ) -> mouse::Interaction {
539 self.cells
540 .iter()
541 .zip(&tree.children)
542 .zip(layout.children())
543 .map(|((cell, tree), layout)| {
544 cell.as_widget()
545 .mouse_interaction(tree, layout, cursor, viewport, renderer)
546 })
547 .max()
548 .unwrap_or_default()
549 }
550
551 fn operate(
552 &mut self,
553 tree: &mut widget::Tree,
554 layout: Layout<'_>,
555 renderer: &Renderer,
556 operation: &mut dyn widget::Operation,
557 ) {
558 for ((cell, state), layout) in self
559 .cells
560 .iter_mut()
561 .zip(&mut tree.children)
562 .zip(layout.children())
563 {
564 cell.as_widget_mut()
565 .operate(state, layout, renderer, operation);
566 }
567 }
568
569 fn overlay<'b>(
570 &'b mut self,
571 tree: &'b mut widget::Tree,
572 layout: Layout<'b>,
573 renderer: &Renderer,
574 viewport: &Rectangle,
575 translation: core::Vector,
576 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
577 overlay::from_children(
578 &mut self.cells,
579 tree,
580 layout,
581 renderer,
582 viewport,
583 translation,
584 )
585 }
586}
587
588impl<'a, Message, Theme, Renderer> From<Table<'a, Message, Theme, Renderer>>
589 for Element<'a, Message, Theme, Renderer>
590where
591 Message: 'a,
592 Theme: Catalog + 'a,
593 Renderer: core::Renderer + 'a,
594{
595 fn from(table: Table<'a, Message, Theme, Renderer>) -> Self {
596 Element::new(table)
597 }
598}
599
600pub struct Column<'a, 'b, T, Message, Theme = crate::Theme, Renderer = crate::Renderer> {
602 header: Element<'a, Message, Theme, Renderer>,
603 view: Box<dyn Fn(T) -> Element<'a, Message, Theme, Renderer> + 'b>,
604 width: Length,
605 align_x: alignment::Horizontal,
606 align_y: alignment::Vertical,
607}
608
609impl<'a, 'b, T, Message, Theme, Renderer> Column<'a, 'b, T, Message, Theme, Renderer> {
610 pub fn width(mut self, width: impl Into<Length>) -> Self {
612 self.width = width.into();
613 self
614 }
615
616 pub fn align_x(mut self, alignment: impl Into<alignment::Horizontal>) -> Self {
618 self.align_x = alignment.into();
619 self
620 }
621
622 pub fn align_y(mut self, alignment: impl Into<alignment::Vertical>) -> Self {
624 self.align_y = alignment.into();
625 self
626 }
627}
628
629#[derive(Debug, Clone, Copy)]
631pub struct Style {
632 pub separator_x: Background,
634 pub separator_y: Background,
636}
637
638pub trait Catalog {
640 type Class<'a>;
642
643 fn default<'a>() -> Self::Class<'a>;
645
646 fn style(&self, class: &Self::Class<'_>) -> Style;
648}
649
650pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
652
653impl<Theme> From<Style> for StyleFn<'_, Theme> {
654 fn from(style: Style) -> Self {
655 Box::new(move |_theme| style)
656 }
657}
658
659impl Catalog for crate::Theme {
660 type Class<'a> = StyleFn<'a, Self>;
661
662 fn default<'a>() -> Self::Class<'a> {
663 Box::new(default)
664 }
665
666 fn style(&self, class: &Self::Class<'_>) -> Style {
667 class(self)
668 }
669}
670
671pub fn default(theme: &crate::Theme) -> Style {
673 let palette = theme.palette();
674 let separator = palette.background.strong.color.into();
675
676 Style {
677 separator_x: separator,
678 separator_y: separator,
679 }
680}