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