Skip to main content

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::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    /// Sets the width of the [`Table`].
138    pub fn width(mut self, width: impl Into<Length>) -> Self {
139        self.width = width.into();
140        self
141    }
142
143    /// Sets the padding of the cells of the [`Table`].
144    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    /// Sets the horizontal padding of the cells of the [`Table`].
151    pub fn padding_x(mut self, padding: impl Into<Pixels>) -> Self {
152        self.padding_x = padding.into().0;
153        self
154    }
155
156    /// Sets the vertical padding of the cells of the [`Table`].
157    pub fn padding_y(mut self, padding: impl Into<Pixels>) -> Self {
158        self.padding_y = padding.into().0;
159        self
160    }
161
162    /// Sets the thickness of the line separator between the cells of the [`Table`].
163    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    /// Sets the thickness of the horizontal line separator between the cells of the [`Table`].
170    pub fn separator_x(mut self, separator: impl Into<Pixels>) -> Self {
171        self.separator_x = separator.into().0;
172        self
173    }
174
175    /// Sets the thickness of the vertical line separator between the cells of the [`Table`].
176    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        // FIRST PASS
254        // Lay out non-fluid cells
255        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        // SECOND PASS
308        // Lay out fluid cells, using metrics from the first pass as limits
309        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        // THIRD PASS
398        // Position each cell
399        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
604/// A vertical visualization of some data with a header.
605pub 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    /// Sets the width of the [`Column`].
615    pub fn width(mut self, width: impl Into<Length>) -> Self {
616        self.width = width.into();
617        self
618    }
619
620    /// Sets the alignment for the horizontal axis of the [`Column`].
621    pub fn align_x(mut self, alignment: impl Into<alignment::Horizontal>) -> Self {
622        self.align_x = alignment.into();
623        self
624    }
625
626    /// Sets the alignment for the vertical axis of the [`Column`].
627    pub fn align_y(mut self, alignment: impl Into<alignment::Vertical>) -> Self {
628        self.align_y = alignment.into();
629        self
630    }
631}
632
633/// The appearance of a [`Table`].
634#[derive(Debug, Clone, Copy)]
635pub struct Style {
636    /// The background color of the horizontal line separator between cells.
637    pub separator_x: Background,
638    /// The background color of the vertical line separator between cells.
639    pub separator_y: Background,
640}
641
642/// The theme catalog of a [`Table`].
643pub trait Catalog {
644    /// The item class of the [`Catalog`].
645    type Class<'a>;
646
647    /// The default class produced by the [`Catalog`].
648    fn default<'a>() -> Self::Class<'a>;
649
650    /// The [`Style`] of a class with the given status.
651    fn style(&self, class: &Self::Class<'_>) -> Style;
652}
653
654/// A styling function for a [`Table`].
655pub 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
675/// The default style of a [`Table`].
676pub 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}