Skip to main content

qframe/widgets/table/
model.rs

1//! What a [`Table`](super::Table) shows: its columns, rows and cells.
2
3use crate::icons::Glyph;
4use crate::text;
5use crate::widget::Align;
6
7/// How wide a [`Column`] is.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ColumnWidth {
10    /// Exactly this many cells.
11    Fixed(u16),
12    /// As wide as its widest cell or its title.
13    Fit,
14    /// A share of the room left by the other columns, by weight.
15    Fill(u16),
16}
17
18/// Which way a sorted column is ordered.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum SortDirection {
21    /// Smallest first.
22    Ascending,
23    /// Largest first.
24    Descending,
25}
26
27impl SortDirection {
28    /// The other direction.
29    #[must_use]
30    pub fn reversed(self) -> Self {
31        match self {
32            Self::Ascending => Self::Descending,
33            Self::Descending => Self::Ascending,
34        }
35    }
36}
37
38/// A column of a [`Table`](super::Table): a title, a width rule, an alignment and whether it can be sorted.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct Column {
41    pub(super) title: String,
42    pub(super) width: ColumnWidth,
43    pub(super) min: Option<u16>,
44    pub(super) align: Align,
45    pub(super) sortable: bool,
46}
47
48impl Column {
49    /// A left-aligned column that shares the free room equally with other filling columns.
50    #[must_use]
51    pub fn new(title: impl Into<String>) -> Self {
52        Self { title: title.into(), width: ColumnWidth::Fill(1), min: None, align: Align::Start, sortable: false }
53    }
54
55    /// How wide the column is.
56    #[must_use]
57    pub fn width(mut self, width: ColumnWidth) -> Self {
58        self.width = width;
59        self
60    }
61
62    /// The fewest cells a fitting or filling column shrinks to. When the columns' minimums do
63    /// not fit, the table scrolls sideways instead of shrinking further. Filling columns keep
64    /// their title's width by default.
65    #[must_use]
66    pub fn min(mut self, cells: u16) -> Self {
67        self.min = Some(cells);
68        self
69    }
70
71    /// Where cell text sits; `Align::End` for numbers so their digits line up.
72    #[must_use]
73    pub fn align(mut self, align: Align) -> Self {
74        self.align = align;
75        self
76    }
77
78    /// Lets the user sort by this column (needs [`Table::on_sort`](super::Table::on_sort)).
79    #[must_use]
80    pub fn sortable(mut self, sortable: bool) -> Self {
81        self.sortable = sortable;
82        self
83    }
84
85    pub(super) fn title_width(&self) -> u16 {
86        text::width(&self.title).saturating_add(if self.sortable { 2 } else { 0 })
87    }
88}
89
90/// One cell: text, optionally with a glyph before it and a colour.
91#[derive(Debug, Clone, PartialEq, Eq, Default)]
92pub struct TableCell {
93    pub(super) text: String,
94    pub(super) icon: Option<Glyph>,
95    pub(super) icon_color: Option<String>,
96    pub(super) color: Option<String>,
97    pub(super) role: Option<String>,
98}
99
100impl TableCell {
101    /// A cell showing `text`.
102    #[must_use]
103    pub fn new(text: impl Into<String>) -> Self {
104        Self { text: text.into(), ..Self::default() }
105    }
106
107    /// A glyph drawn before the text with one space between them: an icon key such as `"dot"`
108    /// or [`Glyph::key`], or a [`Glyph::literal`] the application looked up itself.
109    ///
110    /// With `color` it is drawn in that theme colour, for a glyph that carries meaning such as a
111    /// status dot in `"success"`. Without, it is `muted`, quieter than the text, and takes the
112    /// row's text colour while the row is selected. A narrow column cuts the text with `…` and
113    /// always keeps the glyph and its space.
114    #[must_use]
115    pub fn icon(mut self, glyph: impl Into<Glyph>, color: Option<&str>) -> Self {
116        self.icon = Some(glyph.into());
117        self.icon_color = color.map(str::to_owned);
118        self
119    }
120
121    /// Draws the text in theme colour `token`, e.g. `"success"` next to a status icon.
122    #[must_use]
123    pub fn color(mut self, token: impl Into<String>) -> Self {
124        self.color = Some(token.into());
125        self
126    }
127
128    /// Draws the text in typography role `role`, as [`Text::role`](crate::widgets::Text::role) does, such
129    /// as `"faint"` for a value that matters less. Unlike a [`color`](Self::color), a role steps
130    /// aside on the selected row, whose text takes the selection's colour like the table's own
131    /// quiet marks, so the row reads as one.
132    #[must_use]
133    pub fn role(mut self, role: impl Into<String>) -> Self {
134        self.role = Some(role.into());
135        self
136    }
137
138    /// Cells the text and the glyph with its space take. An icon of the set is one cell, which the
139    /// icon set's rules keep; a literal glyph is measured.
140    pub(super) fn width(&self) -> u16 {
141        let glyph = match &self.icon {
142            None => 0,
143            Some(Glyph::Key(_)) => 2,
144            Some(Glyph::Literal(glyph)) => text::width(glyph).saturating_add(1),
145        };
146        text::width(&self.text).saturating_add(glyph)
147    }
148}
149
150impl From<&str> for TableCell {
151    fn from(text: &str) -> Self {
152        Self::new(text)
153    }
154}
155
156impl From<String> for TableCell {
157    fn from(text: String) -> Self {
158        Self::new(text)
159    }
160}
161
162/// One row of a [`Table`](super::Table).
163#[derive(Debug, Clone, PartialEq, Eq, Default)]
164pub struct TableRow {
165    pub(super) cells: Vec<TableCell>,
166    pub(super) faint: bool,
167}
168
169impl TableRow {
170    /// A row of `cells`, one per column.
171    #[must_use]
172    pub fn new(cells: impl IntoIterator<Item = impl Into<TableCell>>) -> Self {
173        Self { cells: cells.into_iter().map(Into::into).collect(), faint: false }
174    }
175
176    /// Draws the row faint while keeping it selectable.
177    #[must_use]
178    pub fn faint(mut self, faint: bool) -> Self {
179        self.faint = faint;
180        self
181    }
182}