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.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    /// 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.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        // FIRST PASS
250        // Lay out non-fluid cells
251        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        // SECOND PASS
304        // Lay out fluid cells, using metrics from the first pass as limits
305        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        // THIRD PASS
394        // Position each cell
395        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
600/// A vertical visualization of some data with a header.
601pub 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    /// Sets the width of the [`Column`].
611    pub fn width(mut self, width: impl Into<Length>) -> Self {
612        self.width = width.into();
613        self
614    }
615
616    /// Sets the alignment for the horizontal axis of the [`Column`].
617    pub fn align_x(mut self, alignment: impl Into<alignment::Horizontal>) -> Self {
618        self.align_x = alignment.into();
619        self
620    }
621
622    /// Sets the alignment for the vertical axis of the [`Column`].
623    pub fn align_y(mut self, alignment: impl Into<alignment::Vertical>) -> Self {
624        self.align_y = alignment.into();
625        self
626    }
627}
628
629/// The appearance of a [`Table`].
630#[derive(Debug, Clone, Copy)]
631pub struct Style {
632    /// The background color of the horizontal line separator between cells.
633    pub separator_x: Background,
634    /// The background color of the vertical line separator between cells.
635    pub separator_y: Background,
636}
637
638/// The theme catalog of a [`Table`].
639pub trait Catalog {
640    /// The item class of the [`Catalog`].
641    type Class<'a>;
642
643    /// The default class produced by the [`Catalog`].
644    fn default<'a>() -> Self::Class<'a>;
645
646    /// The [`Style`] of a class with the given status.
647    fn style(&self, class: &Self::Class<'_>) -> Style;
648}
649
650/// A styling function for a [`Table`].
651pub 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
671/// The default style of a [`Table`].
672pub 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}