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}