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