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