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::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        shell: &mut core::Shell<'_, Message>,
456        viewport: &Rectangle,
457    ) {
458        for ((cell, tree), layout) in self
459            .cells
460            .iter_mut()
461            .zip(&mut tree.children)
462            .zip(layout.children())
463        {
464            cell.as_widget_mut()
465                .update(tree, event, layout, cursor, renderer, shell, viewport);
466        }
467    }
468
469    fn draw(
470        &self,
471        tree: &widget::Tree,
472        renderer: &mut Renderer,
473        theme: &Theme,
474        style: &renderer::Style,
475        layout: Layout<'_>,
476        cursor: mouse::Cursor,
477        viewport: &Rectangle,
478    ) {
479        for ((cell, state), layout) in self.cells.iter().zip(&tree.children).zip(layout.children())
480        {
481            cell.as_widget()
482                .draw(state, renderer, theme, style, layout, cursor, viewport);
483        }
484
485        let bounds = layout.bounds();
486        let metrics = tree.state.downcast_ref::<Metrics>();
487        let style = theme.style(&self.class);
488
489        if self.separator_x > 0.0 {
490            let mut x = self.padding_x;
491
492            for width in &metrics.columns[..metrics.columns.len().saturating_sub(1)] {
493                x += width + self.padding_x;
494
495                renderer.fill_quad(
496                    renderer::Quad {
497                        bounds: Rectangle {
498                            x: bounds.x + x,
499                            y: bounds.y,
500                            width: self.separator_x,
501                            height: bounds.height,
502                        },
503                        snap: true,
504                        ..renderer::Quad::default()
505                    },
506                    style.separator_x,
507                );
508
509                x += self.separator_x + self.padding_x;
510            }
511        }
512
513        if self.separator_y > 0.0 {
514            let mut y = self.padding_y;
515
516            for height in &metrics.rows[..metrics.rows.len().saturating_sub(1)] {
517                y += height + self.padding_y;
518
519                renderer.fill_quad(
520                    renderer::Quad {
521                        bounds: Rectangle {
522                            x: bounds.x,
523                            y: bounds.y + y,
524                            width: bounds.width,
525                            height: self.separator_y,
526                        },
527                        snap: true,
528                        ..renderer::Quad::default()
529                    },
530                    style.separator_y,
531                );
532
533                y += self.separator_y + self.padding_y;
534            }
535        }
536    }
537
538    fn mouse_interaction(
539        &self,
540        tree: &widget::Tree,
541        layout: Layout<'_>,
542        cursor: mouse::Cursor,
543        viewport: &Rectangle,
544        renderer: &Renderer,
545    ) -> mouse::Interaction {
546        self.cells
547            .iter()
548            .zip(&tree.children)
549            .zip(layout.children())
550            .map(|((cell, tree), layout)| {
551                cell.as_widget()
552                    .mouse_interaction(tree, layout, cursor, viewport, renderer)
553            })
554            .max()
555            .unwrap_or_default()
556    }
557
558    fn operate(
559        &mut self,
560        tree: &mut widget::Tree,
561        layout: Layout<'_>,
562        renderer: &Renderer,
563        operation: &mut dyn widget::Operation,
564    ) {
565        for ((cell, state), layout) in self
566            .cells
567            .iter_mut()
568            .zip(&mut tree.children)
569            .zip(layout.children())
570        {
571            cell.as_widget_mut()
572                .operate(state, layout, renderer, operation);
573        }
574    }
575
576    fn overlay<'b>(
577        &'b mut self,
578        tree: &'b mut widget::Tree,
579        layout: Layout<'b>,
580        renderer: &Renderer,
581        viewport: &Rectangle,
582        translation: core::Vector,
583    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
584        overlay::from_children(
585            &mut self.cells,
586            tree,
587            layout,
588            renderer,
589            viewport,
590            translation,
591        )
592    }
593}
594
595impl<'a, Message, Theme, Renderer> From<Table<'a, Message, Theme, Renderer>>
596    for Element<'a, Message, Theme, Renderer>
597where
598    Message: 'a,
599    Theme: Catalog + 'a,
600    Renderer: core::Renderer + 'a,
601{
602    fn from(table: Table<'a, Message, Theme, Renderer>) -> Self {
603        Element::new(table)
604    }
605}
606
607/// A vertical visualization of some data with a header.
608pub struct Column<'a, 'b, T, Message, Theme = crate::Theme, Renderer = crate::Renderer> {
609    header: Element<'a, Message, Theme, Renderer>,
610    view: Box<dyn Fn(T) -> Element<'a, Message, Theme, Renderer> + 'b>,
611    width: Length,
612    align_x: alignment::Horizontal,
613    align_y: alignment::Vertical,
614}
615
616impl<'a, 'b, T, Message, Theme, Renderer> Column<'a, 'b, T, Message, Theme, Renderer> {
617    /// Sets the width of the [`Column`].
618    pub fn width(mut self, width: impl Into<Length>) -> Self {
619        self.width = width.into();
620        self
621    }
622
623    /// Sets the alignment for the horizontal axis of the [`Column`].
624    pub fn align_x(mut self, alignment: impl Into<alignment::Horizontal>) -> Self {
625        self.align_x = alignment.into();
626        self
627    }
628
629    /// Sets the alignment for the vertical axis of the [`Column`].
630    pub fn align_y(mut self, alignment: impl Into<alignment::Vertical>) -> Self {
631        self.align_y = alignment.into();
632        self
633    }
634}
635
636/// The appearance of a [`Table`].
637#[derive(Debug, Clone, Copy)]
638pub struct Style {
639    /// The background color of the horizontal line separator between cells.
640    pub separator_x: Background,
641    /// The background color of the vertical line separator between cells.
642    pub separator_y: Background,
643}
644
645/// The theme catalog of a [`Table`].
646pub trait Catalog {
647    /// The item class of the [`Catalog`].
648    type Class<'a>;
649
650    /// The default class produced by the [`Catalog`].
651    fn default<'a>() -> Self::Class<'a>;
652
653    /// The [`Style`] of a class with the given status.
654    fn style(&self, class: &Self::Class<'_>) -> Style;
655}
656
657/// A styling function for a [`Table`].
658pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
659
660impl<Theme> From<Style> for StyleFn<'_, Theme> {
661    fn from(style: Style) -> Self {
662        Box::new(move |_theme| style)
663    }
664}
665
666impl Catalog for crate::Theme {
667    type Class<'a> = StyleFn<'a, Self>;
668
669    fn default<'a>() -> Self::Class<'a> {
670        Box::new(default)
671    }
672
673    fn style(&self, class: &Self::Class<'_>) -> Style {
674        class(self)
675    }
676}
677
678/// The default style of a [`Table`].
679pub fn default(theme: &crate::Theme) -> Style {
680    let palette = theme.extended_palette();
681    let separator = palette.background.strong.color.into();
682
683    Style {
684        separator_x: separator,
685        separator_y: separator,
686    }
687}