Skip to main content

lexa_logger/layout/
row.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 super::cell::{str_len, Cell};
12use super::style::{Alignment, Position, Style};
13
14// --------- //
15// Structure //
16// --------- //
17
18#[derive(Debug)]
19#[derive(Clone)]
20pub struct Row<'d>
21{
22	pub cells: Vec<Cell<'d>>,
23	pub separator: bool,
24}
25
26// -------------- //
27// Implémentation //
28// -------------- //
29
30impl<'d> Row<'d>
31{
32	pub fn new(cells: impl IntoIterator<Item = impl Into<Cell<'d>>>) -> Row<'d>
33	{
34		let mut row = Row {
35			cells: vec![],
36			separator: true,
37		};
38
39		cells.into_iter().for_each(|entry| row.cells.push(entry.into()));
40
41		row
42	}
43}
44
45impl<'d> Row<'d>
46{
47	pub(crate) fn format(&self, widths: &[usize], style: &Style) -> String
48	{
49		let mut temporary_buffer = String::new();
50		let mut wrapped_cells = Vec::default();
51
52		let mut spanned_columns = 0;
53		let mut row_height = 0;
54
55		for cell in &self.cells {
56			let width = (0..cell.colspan).fold(0, |width, j| width + widths[j + spanned_columns]);
57			let wrapped_cell = cell.wrapped_content(width + cell.colspan - 1);
58			row_height = core::cmp::max(row_height, wrapped_cell.len());
59			wrapped_cells.push(wrapped_cell);
60			spanned_columns += cell.colspan;
61		}
62
63		spanned_columns = 0;
64
65		let mut lines = vec![String::new(); row_height];
66
67		for (column_index, _) in wrapped_cells.iter().enumerate().take(widths.len()) {
68			if self.cells.len() > column_index {
69				let cell = &self.cells[column_index];
70
71				let cell_span = (0..cell.colspan).fold(0, |n, c| n + widths[spanned_columns + c]);
72
73				let callback_fn = |(row_index, row): (usize, &mut String)| {
74					let padding = if wrapped_cells[column_index].len() > row_index {
75						let str_width = str_len(&wrapped_cells[column_index][row_index]);
76
77						let mut padding = 0;
78						if cell_span >= str_width {
79							padding += cell_span - str_width;
80
81							if cell.colspan > 1 {
82								padding += style.vertical.len_utf8() * (cell.colspan - 1);
83							}
84						}
85
86						self.padding_string(padding, cell.alignment, &wrapped_cells[column_index][row_index])
87					} else {
88						str::repeat(" ", widths[spanned_columns] * cell.colspan + cell.colspan - 1)
89					};
90
91					row.push_str(&format!("{}{padding}", style.vertical));
92				};
93
94				lines.iter_mut().enumerate().take(row_height).for_each(callback_fn);
95
96				spanned_columns += cell.colspan;
97			} else {
98				lines.iter_mut().take(row_height).for_each(|line| {
99					line.push_str(&format!(
100						"{}{}",
101						style.vertical,
102						str::repeat(" ", widths[spanned_columns])
103					));
104				});
105
106				spanned_columns += 1;
107			}
108
109			if spanned_columns == widths.len() {
110				break;
111			}
112		}
113
114		lines.iter().for_each(|line| {
115			temporary_buffer.push_str(line);
116			temporary_buffer.push(style.vertical);
117			temporary_buffer.push('\n');
118		});
119
120		temporary_buffer.pop();
121
122		temporary_buffer
123	}
124
125	pub(crate) fn generate_separator(
126		&self,
127		widths: &[usize],
128		style: &Style,
129		row: Position,
130		previous_separator: Option<&String>,
131	) -> String
132	{
133		let mut temporary_buffer = String::new();
134
135		let mut next_intersection = match self.cells.first() {
136			| Some(cell) => cell.colspan,
137			| None => 1,
138		};
139
140		temporary_buffer.push(style.start_position(row));
141
142		let mut current_column = 0;
143
144		for (index, width) in widths.iter().enumerate() {
145			if index == next_intersection {
146				temporary_buffer.push(style.intersect_position(row));
147
148				current_column += 1;
149
150				if self.cells.len() > current_column {
151					next_intersection += self.cells[current_column].colspan;
152				} else {
153					next_intersection += 1;
154				}
155			} else if index > 0 {
156				temporary_buffer.push(style.horizontal);
157			}
158
159			temporary_buffer.push_str(&str::repeat(&style.horizontal.to_string(), *width));
160		}
161
162		temporary_buffer.push(style.end_position(row));
163
164		if let Some(prev) = previous_separator {
165			let mut output = String::new();
166
167			for pair in temporary_buffer.chars().zip(prev.chars()) {
168				if pair.0 == style.left || pair.0 == style.right {
169					output.push(pair.0);
170				} else if pair.0 != style.horizontal || pair.1 != style.horizontal {
171					output.push(style.merge_intersection_position(pair.1, pair.0, row));
172				} else {
173					output.push(style.horizontal);
174				}
175			}
176
177			return output;
178		}
179
180		temporary_buffer
181	}
182
183	pub(crate) fn split_column(&self) -> Vec<(f32, usize)>
184	{
185		let callback_fn = |mut output: Vec<(f32, usize)>, cell: &Cell| {
186			let value = cell.split_width();
187
188			let min_w = (cell.min_width() as f32 / cell.colspan as f32) as usize;
189			let add_1 = cell.min_width() as f32 % cell.colspan as f32 > 0.001;
190
191			for i in 0..cell.colspan {
192				if add_1 && i == cell.colspan - 1 {
193					output.push((value + 1.0, min_w + 1));
194				} else {
195					output.push((value, min_w));
196				}
197			}
198
199			output
200		};
201
202		let output = self.cells.iter().fold(Vec::default(), callback_fn);
203
204		output
205	}
206
207	pub(crate) fn total_columns(&self) -> usize
208	{
209		self.cells.iter().map(|cell| cell.colspan).sum()
210	}
211
212	fn padding_string(&self, padding: usize, alignment: Alignment, text: &str) -> String
213	{
214		match alignment {
215			| Alignment::Left => {
216				let r = str::repeat(" ", padding);
217				format!("{text}{r}")
218			}
219			| Alignment::Right => {
220				let l = str::repeat(" ", padding);
221				format!("{l}{text}")
222			}
223			| Alignment::Center => {
224				let half_padding = padding as f32 / 2.0;
225				let l = str::repeat(" ", half_padding.ceil() as usize);
226				let r = str::repeat(" ", half_padding.floor() as usize);
227				format!("{l}{text}{r}",)
228			}
229		}
230	}
231}