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}
98
99impl TableCell {
100    /// A cell showing `text`.
101    #[must_use]
102    pub fn new(text: impl Into<String>) -> Self {
103        Self { text: text.into(), ..Self::default() }
104    }
105
106    /// A glyph drawn before the text with one space between them: an icon key such as `"dot"`
107    /// or [`Glyph::key`], or a [`Glyph::literal`] the application looked up itself.
108    ///
109    /// With `color` it is drawn in that theme colour, for a glyph that carries meaning such as a
110    /// status dot in `"success"`. Without, it is `muted`, quieter than the text, and takes the
111    /// row's text colour while the row is selected. A narrow column cuts the text with `…` and
112    /// always keeps the glyph and its space.
113    #[must_use]
114    pub fn icon(mut self, glyph: impl Into<Glyph>, color: Option<&str>) -> Self {
115        self.icon = Some(glyph.into());
116        self.icon_color = color.map(str::to_owned);
117        self
118    }
119
120    /// Draws the text in theme colour `token`, e.g. `"success"` next to a status icon.
121    #[must_use]
122    pub fn color(mut self, token: impl Into<String>) -> Self {
123        self.color = Some(token.into());
124        self
125    }
126
127    /// Cells the text and the glyph with its space take. An icon of the set is one cell, which the
128    /// icon set's rules keep; a literal glyph is measured.
129    pub(super) fn width(&self) -> u16 {
130        let glyph = match &self.icon {
131            None => 0,
132            Some(Glyph::Key(_)) => 2,
133            Some(Glyph::Literal(glyph)) => text::width(glyph).saturating_add(1),
134        };
135        text::width(&self.text).saturating_add(glyph)
136    }
137}
138
139impl From<&str> for TableCell {
140    fn from(text: &str) -> Self {
141        Self::new(text)
142    }
143}
144
145impl From<String> for TableCell {
146    fn from(text: String) -> Self {
147        Self::new(text)
148    }
149}
150
151/// One row of a [`Table`](super::Table).
152#[derive(Debug, Clone, PartialEq, Eq, Default)]
153pub struct TableRow {
154    pub(super) cells: Vec<TableCell>,
155    pub(super) faint: bool,
156}
157
158impl TableRow {
159    /// A row of `cells`, one per column.
160    #[must_use]
161    pub fn new(cells: impl IntoIterator<Item = impl Into<TableCell>>) -> Self {
162        Self { cells: cells.into_iter().map(Into::into).collect(), faint: false }
163    }
164
165    /// Draws the row faint while keeping it selectable.
166    #[must_use]
167    pub fn faint(mut self, faint: bool) -> Self {
168        self.faint = faint;
169        self
170    }
171}