lexa_logger/layout/
grid.rs1use std::collections::HashMap;
12
13use super::cell::Cell;
14use super::row::Row;
15use super::style::{Alignment, Position, Style, STYLE_BLANK};
16
17#[derive(Clone, Debug)]
22pub struct GridLayout<'d>
23{
24 rows: Vec<Row<'d>>,
25
26 style: Style,
27
28 widths: HashMap<usize, usize>,
29 max_width: usize,
30
31 separate_rows: bool,
32 boarder: GridBoarder,
33}
34
35#[derive(Clone, Debug)]
36pub struct GridBoarder
37{
38 top: bool,
39 bottom: bool,
40}
41
42impl<'d> GridLayout<'d>
47{
48 pub fn new() -> Self
49 {
50 Self {
51 rows: Vec::new(),
52 style: STYLE_BLANK,
53
54 widths: HashMap::new(),
55 max_width: std::usize::MAX,
56
57 separate_rows: true,
58
59 boarder: GridBoarder {
60 top: true,
61 bottom: true,
62 },
63 }
64 }
65
66 pub fn with_style(mut self, style: Style) -> Self
67 {
68 self.style = style;
69 self
70 }
71
72 pub fn without_boarder(mut self) -> Self
73 {
74 self.boarder.top = false;
75 self.boarder.bottom = false;
76 self
77 }
78
79 pub fn define_max_width(mut self, width: usize) -> Self
80 {
81 self.max_width = width;
82 self
83 }
84
85 pub fn add_line(&mut self, cells: impl IntoIterator<Item = impl Into<Cell<'d>>>)
86 {
87 let row = Row::new(cells);
88 self.rows.push(row);
89 }
90
91 pub fn render(&self) -> String
92 {
93 let mut print_buffer = String::new();
94
95 if self.rows.is_empty() {
96 return print_buffer;
97 }
98
99 let max_widths = self.calculate_max_widths();
100 let mut previous_separator = None;
101 for index in 0..self.rows.len() {
102 let row_pos = if index == 0 { Position::First } else { Position::Middle };
103
104 let separator =
105 self.rows[index].generate_separator(&max_widths, &self.style, row_pos, previous_separator.as_ref());
106
107 previous_separator.replace(separator.clone());
108
109 if self.rows[index].separator && ((index == 0 && self.boarder.top) || index != 0 && self.separate_rows) {
110 Self::add_newline_to_buffer(&mut print_buffer, separator);
111 }
112
113 Self::add_newline_to_buffer(&mut print_buffer, &self.rows[index].format(&max_widths, &self.style));
114 }
115
116 if self.boarder.bottom {
117 let separator =
118 self.rows
119 .last()
120 .unwrap()
121 .generate_separator(&max_widths, &self.style, Position::Last, None);
122
123 Self::add_newline_to_buffer(&mut print_buffer, separator);
124 }
125
126 print_buffer
127 }
128}
129
130impl<'d> GridLayout<'d>
131{
132 fn calculate_max_widths(&self) -> Vec<usize>
133 {
134 let total_columns = self
135 .rows
136 .iter()
137 .fold(0, |n, row| core::cmp::max(row.total_columns(), n));
138
139 let (_, mut max_widths) =
140 self.rows
141 .iter()
142 .fold((vec![0; total_columns], vec![0; total_columns]), |acc, row| {
143 let column_widths = row.split_column();
144
145 (0..column_widths.len()).fold((acc.0, acc.1), |(mut min, mut max), index| {
146 min[index] = core::cmp::max(min[index], column_widths[index].1);
147
148 let mut max_width = *self.widths.get(&index).unwrap_or(&self.max_width);
149
150 max_width = core::cmp::max(min[index], max_width);
151
152 max[index] =
153 core::cmp::min(max_width, core::cmp::max(max[index], column_widths[index].0 as usize));
154
155 (min, max)
156 })
157 });
158
159 self.rows.iter().for_each(|row| {
160 row.cells.iter().fold(0, |mut column_index, cell| {
161 let total_col_width =
162 (column_index..column_index + cell.colspan).fold(0, |total, index| total + max_widths[index]);
163
164 if cell.width() != total_col_width
165 && cell.alignment == Alignment::Center
166 && total_col_width as f32 % 2.0 <= 0.001
167 {
168 let mut max_col_width = self.max_width;
169 if let Some(specific_width) = self.widths.get(&column_index) {
170 max_col_width = *specific_width;
171 }
172
173 if max_widths[column_index] < max_col_width {
174 max_widths[column_index] += 1;
175 }
176 }
177
178 if cell.colspan > 1 {
179 column_index += cell.colspan - 1;
180 } else {
181 column_index += 1;
182 }
183
184 column_index
185 });
186 });
187
188 max_widths
189 }
190
191 fn add_newline_to_buffer(buffer: &mut String, line: impl AsRef<str>)
192 {
193 buffer.push_str(&format!("{}\n", line.as_ref()));
194 }
195}
196
197impl<'d> Default for GridLayout<'d>
202{
203 fn default() -> Self
204 {
205 GridLayout::new()
206 }
207}
208
209impl<'d> ToString for GridLayout<'d>
210{
211 fn to_string(&self) -> String
212 {
213 self.render()
214 }
215}