lexa_logger/layout/
grid.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ Copyright: (c) 2023, Mike 'PhiSyX' S. (https://github.com/PhiSyX)         ┃
3// ┃ SPDX-License-Identifier: MPL-2.0                                          ┃
4// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
5// ┃                                                                           ┃
6// ┃  This Source Code Form is subject to the terms of the Mozilla Public      ┃
7// ┃  License, v. 2.0. If a copy of the MPL was not distributed with this      ┃
8// ┃  file, You can obtain one at https://mozilla.org/MPL/2.0/.                ┃
9// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
10
11use std::collections::HashMap;
12
13use super::cell::Cell;
14use super::row::Row;
15use super::style::{Alignment, Position, Style, STYLE_BLANK};
16
17// --------- //
18// Structure //
19// --------- //
20
21#[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
42// -------------- //
43// Implémentation //
44// -------------- //
45
46impl<'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
197// -------------- //
198// Implémentation // -> Interface
199// -------------- //
200
201impl<'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}