Skip to main content

comfy_table/style/
table_style.rs

1/// A styling rule for a type of horizontal line of a table.
2///
3/// This could be, for example, the top border or the lines between rows.
4///
5/// A styling rule consists of four optional parts:
6///
7/// ```text
8/// ├╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
9/// ^     ^     ^           ^
10/// left  fill  junction    right
11/// ```
12///
13/// `left`/`right` are the border delimiters, `fill` is the horizontal delimiter between rows and
14/// `junction` is drawn where vertical and horizontal lines cross in the middle
15///
16/// Styles that're wholly `None` won't be drawn.
17#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
18pub struct LineStyle {
19    pub left: Option<char>,
20    pub fill: Option<char>,
21    pub junction: Option<char>,
22    pub right: Option<char>,
23}
24
25impl LineStyle {
26    /// Create a [LineStyle] with all four parts set.
27    pub const fn new(left: char, fill: char, junction: char, right: char) -> Self {
28        Self {
29            left: Some(left),
30            fill: Some(fill),
31            junction: Some(junction),
32            right: Some(right),
33        }
34    }
35
36    /// Create a line without any parts set.
37    pub const fn none() -> Self {
38        Self {
39            left: None,
40            fill: None,
41            junction: None,
42            right: None,
43        }
44    }
45
46    /// Set the left border character.
47    pub const fn left(mut self, character: char) -> Self {
48        self.left = Some(character);
49        self
50    }
51
52    /// Set the fill character.
53    pub const fn fill(mut self, character: char) -> Self {
54        self.fill = Some(character);
55        self
56    }
57
58    /// Set the junction character.
59    pub const fn junction(mut self, character: char) -> Self {
60        self.junction = Some(character);
61        self
62    }
63
64    /// Set the right border character.
65    pub const fn right(mut self, character: char) -> Self {
66        self.right = Some(character);
67        self
68    }
69
70    pub(crate) const fn is_visible(&self) -> bool {
71        self.left.is_some()
72            || self.fill.is_some()
73            || self.junction.is_some()
74            || self.right.is_some()
75    }
76}
77
78/// A styling rule for the lines of a table that contain content.
79///
80/// This could be the header lines or the content lines of normal rows.
81///
82/// A styling rule consists of three optional parts:
83///
84/// ```text
85/// │ a         ┆ b         │
86/// ^           ^           ^
87/// left        junction    right
88/// ```
89///
90/// `left`/`right` are the border delimiters and `junction` is the vertical delimiter that's
91/// drawn between columns.
92#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
93pub struct ContentLineStyle {
94    pub left: Option<char>,
95    pub junction: Option<char>,
96    pub right: Option<char>,
97}
98
99impl ContentLineStyle {
100    /// Create a [ContentLineStyle] with all three parts set.
101    pub const fn new(left: char, junction: char, right: char) -> Self {
102        Self {
103            left: Some(left),
104            junction: Some(junction),
105            right: Some(right),
106        }
107    }
108
109    /// Create a line without any parts set.
110    pub const fn none() -> Self {
111        Self {
112            left: None,
113            junction: None,
114            right: None,
115        }
116    }
117
118    /// Set the left border character.
119    pub const fn left(mut self, character: char) -> Self {
120        self.left = Some(character);
121        self
122    }
123
124    /// Set the junction character.
125    pub const fn junction(mut self, character: char) -> Self {
126        self.junction = Some(character);
127        self
128    }
129
130    /// Set the right border character.
131    pub const fn right(mut self, character: char) -> Self {
132        self.right = Some(character);
133        self
134    }
135}
136
137/// The full description of a table's look, built from four horizontal [LineStyle]s and two
138/// [ContentLineStyle]s:
139///
140/// ```text
141/// ┌─────────┬─────────┐   <- top_border
142/// │ Hello   ┆ there   │   <- header_lines
143/// ╞═════════╪═════════╡   <- header_separator
144/// │ a       ┆ b       │   <- content_lines
145/// ├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤   <- row_separator
146/// │ c       ┆ d       │
147/// └─────────┴─────────┘   <- bottom_border
148/// ```
149///
150/// All functions for building a style are `const`, so custom styles can be declared as
151/// constants, just like the ones in the [presets](crate::style::presets) module:
152///
153/// ```
154/// use comfy_table::{ContentLineStyle, LineStyle, Table, TableStyle};
155///
156/// const MY_STYLE: TableStyle = TableStyle::new()
157///     .top_border(LineStyle::new('┌', '─', '┬', '┐'))
158///     .header_lines(ContentLineStyle::new('│', '┆', '│'))
159///     .header_separator(LineStyle::new('╞', '═', '╪', '╡'))
160///     .content_lines(ContentLineStyle::new('│', '┆', '│'))
161///     .bottom_border(LineStyle::new('└', '─', '┴', '┘'));
162///
163/// let mut table = Table::new();
164/// table.load_style(MY_STYLE);
165/// ```
166#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
167pub struct TableStyle {
168    pub top_border: LineStyle,
169    pub header_lines: ContentLineStyle,
170    pub header_separator: LineStyle,
171    pub content_lines: ContentLineStyle,
172    pub row_separator: LineStyle,
173    pub bottom_border: LineStyle,
174}
175
176impl TableStyle {
177    /// Create a style that doesn't draw anything.
178    pub const fn new() -> Self {
179        Self {
180            top_border: LineStyle::none(),
181            header_lines: ContentLineStyle::none(),
182            header_separator: LineStyle::none(),
183            content_lines: ContentLineStyle::none(),
184            row_separator: LineStyle::none(),
185            bottom_border: LineStyle::none(),
186        }
187    }
188
189    /// Set the top border of the table.
190    pub const fn top_border(mut self, line: LineStyle) -> Self {
191        self.top_border = line;
192        self
193    }
194
195    /// Set the style of the lines that contain the header's content.
196    pub const fn header_lines(mut self, line: ContentLineStyle) -> Self {
197        self.header_lines = line;
198        self
199    }
200
201    /// Set the line that's drawn between the header and the first row.
202    pub const fn header_separator(mut self, line: LineStyle) -> Self {
203        self.header_separator = line;
204        self
205    }
206
207    /// Set the style of the lines that contain the rows' content.
208    pub const fn content_lines(mut self, line: ContentLineStyle) -> Self {
209        self.content_lines = line;
210        self
211    }
212
213    /// Set the line that's drawn between rows.
214    pub const fn row_separator(mut self, line: LineStyle) -> Self {
215        self.row_separator = line;
216        self
217    }
218
219    /// Set the bottom border of the table.
220    pub const fn bottom_border(mut self, line: LineStyle) -> Self {
221        self.bottom_border = line;
222        self
223    }
224
225    /// Convert the outer corners to round corners.
226    /// ```text
227    /// ╭───────┬───────╮
228    /// │ Hello │ there │
229    /// ╞═══════╪═══════╡
230    /// │ a     ┆ b     │
231    /// ├╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┤
232    /// │ c     ┆ d     │
233    /// ╰───────┴───────╯
234    /// ```
235    pub const fn with_rounded_corners(mut self) -> Self {
236        self.top_border.left = Some('╭');
237        self.top_border.right = Some('╮');
238        self.bottom_border.left = Some('╰');
239        self.bottom_border.right = Some('╯');
240        self
241    }
242
243    /// Convert the inner borders to solid lines.
244    /// ```text
245    /// ┌───────┬───────┐
246    /// │ Hello │ there │
247    /// ╞═══════╪═══════╡
248    /// │ a     │ b     │
249    /// ├───────┼───────┤
250    /// │ c     │ d     │
251    /// └───────┴───────┘
252    /// ```
253    pub const fn with_solid_inner_borders(mut self) -> Self {
254        self.header_lines.junction = Some('│');
255        self.content_lines.junction = Some('│');
256        self.row_separator.fill = Some('─');
257        self
258    }
259
260    pub(crate) const fn has_top_border(&self) -> bool {
261        self.top_border.is_visible()
262    }
263
264    pub(crate) const fn has_bottom_border(&self) -> bool {
265        self.bottom_border.is_visible()
266    }
267
268    pub(crate) const fn has_header_separator(&self) -> bool {
269        self.header_separator.is_visible()
270    }
271
272    pub(crate) const fn has_row_separator(&self) -> bool {
273        self.row_separator.is_visible()
274    }
275
276    /// The left border is drawn as soon as any component in the leftmost column exists.
277    pub(crate) const fn has_left_border(&self) -> bool {
278        self.header_lines.left.is_some()
279            || self.content_lines.left.is_some()
280            || self.top_border.left.is_some()
281            || self.header_separator.left.is_some()
282            || self.row_separator.left.is_some()
283            || self.bottom_border.left.is_some()
284    }
285
286    /// The right border is drawn as soon as any component in the rightmost column exists.
287    pub(crate) const fn has_right_border(&self) -> bool {
288        self.header_lines.right.is_some()
289            || self.content_lines.right.is_some()
290            || self.top_border.right.is_some()
291            || self.header_separator.right.is_some()
292            || self.row_separator.right.is_some()
293            || self.bottom_border.right.is_some()
294    }
295
296    /// Vertical lines are drawn as soon as any component between two columns exists.
297    pub(crate) const fn has_vertical_lines(&self) -> bool {
298        self.header_lines.junction.is_some()
299            || self.content_lines.junction.is_some()
300            || self.top_border.junction.is_some()
301            || self.header_separator.junction.is_some()
302            || self.row_separator.junction.is_some()
303            || self.bottom_border.junction.is_some()
304    }
305}