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