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