Skip to main content

urushi/style/
grid.rs

1//! The [`GridStyle`] builder: shared column lengths and default cell padding.
2
3use crate::{Length, Sides};
4
5/// A grid's geometry: an optional [`Length`] per column and the padding its
6/// cells take.
7///
8/// A `GridStyle` is an immutable value, like [`BlockStyle`](crate::BlockStyle).
9/// It holds no box geometry or line presentation: a grid that needs a border,
10/// margin, or stated size is placed inside a
11/// [`View::Block`](crate::View::Block), while a presentation that needs an
12/// internal line network owns it in a [`Canvas`](crate::Canvas).
13///
14/// ```
15/// use urushi::{GridStyle, Length, TextStyle, View, measure};
16///
17/// let style = GridStyle::new()
18///     .cell_padding((0, 1))
19///     .columns([None, Some(Length::fill(1))]);
20/// let grid = View::grid(
21///     style,
22///     [
23///         [View::text("id", TextStyle::new()), View::text("name", TextStyle::new())],
24///         [View::text("1", TextStyle::new()), View::text("urushi", TextStyle::new())],
25///     ],
26/// );
27///
28/// assert_eq!(measure(&grid).height(), 2);
29/// ```
30#[derive(Debug, Clone, Default, PartialEq, Eq)]
31pub struct GridStyle {
32    columns: Vec<Option<Length>>,
33    cell_padding: Sides,
34}
35
36impl GridStyle {
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    /// States the [`Length`] each column claims, left to right.
42    ///
43    /// A column with no stated length — an absent entry, or one past the end
44    /// of this list — is auto: it claims the intrinsic width of the cells
45    /// beneath it. A stated length supplies only the *kind* of the claim; the
46    /// demand and the floor still come from those cells.
47    pub fn columns(mut self, columns: impl IntoIterator<Item = Option<Length>>) -> Self {
48        self.columns = columns.into_iter().collect();
49        self
50    }
51
52    /// Restores all column length claims to their default value.
53    pub fn reset_columns(mut self) -> Self {
54        self.columns.clear();
55        self
56    }
57
58    /// Sets the padding every cell that states none of its own takes.
59    ///
60    /// A cell that states its own padding replaces this value rather than
61    /// adding to it. Columns stay aligned either way: a column is as wide as
62    /// its widest cell, whatever padding that cell carries.
63    pub fn cell_padding(mut self, sides: impl Into<Sides>) -> Self {
64        self.cell_padding = sides.into();
65        self
66    }
67
68    /// Restores the default cell padding.
69    pub fn reset_cell_padding(mut self) -> Self {
70        self.cell_padding = Sides::default();
71        self
72    }
73
74    /// Returns the stated length of column `index`, if it states one.
75    pub fn get_column(&self, index: usize) -> Option<Length> {
76        self.columns.get(index).copied().flatten()
77    }
78
79    /// Returns the padding a cell that states none of its own takes.
80    pub const fn get_cell_padding(&self) -> Sides {
81        self.cell_padding
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn a_column_past_the_stated_list_is_auto() {
91        let style = GridStyle::new().columns([Some(Length::Cells(4)), None]);
92
93        assert_eq!(style.get_column(0), Some(Length::Cells(4)));
94        assert_eq!(style.get_column(1), None, "stated as absent");
95        assert_eq!(style.get_column(9), None, "past the end");
96    }
97
98    #[test]
99    fn builders_can_restore_grid_defaults_explicitly() {
100        let stated = GridStyle::new()
101            .columns([Some(Length::Cells(4))])
102            .cell_padding((0, 1));
103
104        assert_eq!(
105            stated.clone().reset_columns().reset_cell_padding(),
106            GridStyle::new()
107        );
108        assert_ne!(stated, GridStyle::new(), "the removals did the work");
109    }
110
111    #[test]
112    fn cell_padding_accepts_the_same_shorthands_a_block_takes() {
113        assert_eq!(
114            GridStyle::new().cell_padding((0, 1)).get_cell_padding(),
115            Sides {
116                top: 0,
117                right: 1,
118                bottom: 0,
119                left: 1,
120            }
121        );
122    }
123}