iced_widget/
table.rs

1//! Display tables.
2use 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
14/// Creates a new [`Table`] with the given columns and rows.
15///
16/// Columns can be created using the [`column()`] function, while rows can be any
17/// iterator over some data type `T`.
18pub 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
30/// Creates a new [`Column`] with the given header and view function.
31///
32/// The view function will be called for each row in a [`Table`] and it must
33/// produce the resulting contents of a cell.
34pub 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/// A grid-like visual representation of data distributed in columns and rows.
52pub 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    /// Creates a new [`Table`] with the given columns and rows.
79    ///
80    /// Columns can be created using the [`column()`] function, while rows can be any
81    /// iterator over some data type `T`.
82    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    /// Sets the width of the [`Table`].
149    pub fn width(mut self, width: impl Into<Length>) -> Self {
150        self.width = width.into();
151        self
152    }
153
154    /// Sets the padding of the cells of the [`Table`].
155    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    /// Sets the horizontal padding of the cells of the [`Table`].
162    pub fn padding_x(mut self, padding: impl Into<Pixels>) -> Self {
163        self.padding_x = padding.into().0;
164        self
165    }
166
167    /// Sets the vertical padding of the cells of the [`Table`].
168    pub fn padding_y(mut self, padding: impl Into<Pixels>) -> Self {
169        self.padding_y = padding.into().0;
170        self
171    }
172
173    /// Sets the thickness of the line separator between the cells of the [`Table`].
174    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    /// Sets the thickness of the horizontal line separator between the cells of the [`Table`].
181    pub fn separator_x(mut self, separator: impl Into<Pixels>) -> Self {
182        self.separator_x = separator.into().0;
183        self
184    }
185
186    /// Sets the thickness of the vertical line separator between the cells of the [`Table`].
187    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        // FIRST PASS
262        // Lay out non-fluid cells
263        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        // SECOND PASS
319        // Lay out fluid cells, using metrics from the first pass as limits
320        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        // THIRD PASS
417        // Position each cell
418        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
631/// A vertical visualization of some data with a header.
632pub 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    /// Sets the width of the [`Column`].
651    pub fn width(mut self, width: impl Into<Length>) -> Self {
652        self.width = width.into();
653        self
654    }
655
656    /// Sets the alignment for the horizontal axis of the [`Column`].
657    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    /// Sets the alignment for the vertical axis of the [`Column`].
666    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/// The appearance of a [`Table`].
676#[derive(Debug, Clone, Copy)]
677pub struct Style {
678    /// The background color of the horizontal line separator between cells.
679    pub separator_x: Background,
680    /// The background color of the vertical line separator between cells.
681    pub separator_y: Background,
682}
683
684/// The theme catalog of a [`Table`].
685pub trait Catalog {
686    /// The item class of the [`Catalog`].
687    type Class<'a>;
688
689    /// The default class produced by the [`Catalog`].
690    fn default<'a>() -> Self::Class<'a>;
691
692    /// The [`Style`] of a class with the given status.
693    fn style(&self, class: &Self::Class<'_>) -> Style;
694}
695
696/// A styling function for a [`Table`].
697pub 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
717/// The default style of a [`Table`].
718pub 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}