1 | //! This module contains [`RawStyle`] structure, which is analogues to [`Style`] but not generic, |
2 | //! so sometimes it can be used more conviently. |
3 | |
4 | // todo: StyleFromTable() |
5 | // table.with(&mut StyleFromTable); |
6 | // vs |
7 | // Theme::from(table.get_config()); |
8 | // |
9 | // not sure what the best interface is |
10 | // IMHO 2 |
11 | |
12 | use std::collections::HashMap; |
13 | use std::iter::FromIterator; |
14 | |
15 | use crate::{ |
16 | grid::{ |
17 | config::{ |
18 | AlignmentHorizontal, AlignmentVertical, Border, Borders, ColoredConfig, CompactConfig, |
19 | CompactMultilineConfig, HorizontalLine, VerticalLine, |
20 | }, |
21 | records::{ExactRecords, PeekableRecords, Records, RecordsMut, Resizable}, |
22 | }, |
23 | settings::{style::Style, themes::Colorization, Alignment, Color, Rotate, TableOption}, |
24 | }; |
25 | |
26 | /// A raw style data, which can be produced safely from [`Style`]. |
27 | /// |
28 | /// It can be useful in order to not have a generics and be able to use it as a variable more conveniently. |
29 | #[derive (Debug, Clone, PartialEq, Eq)] |
30 | pub struct Theme { |
31 | border: TableBorders, |
32 | lines: BorderLines, |
33 | layout: Layout, |
34 | colorization: Option<Colorization>, |
35 | } |
36 | |
37 | #[derive (Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] |
38 | struct TableBorders { |
39 | chars: Borders<char>, |
40 | colors: Borders<Color>, |
41 | } |
42 | |
43 | #[derive (Debug, Clone, PartialEq, Eq)] |
44 | struct BorderLines { |
45 | horizontal1: Option<HorizontalLine<char>>, |
46 | horizontals: Option<HashMap<usize, HorizontalLine<char>>>, |
47 | verticals: Option<HashMap<usize, VerticalLine<char>>>, |
48 | } |
49 | |
50 | #[derive (Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] |
51 | struct Layout { |
52 | orientation: HeadPosition, |
53 | footer: bool, |
54 | reverse_rows: bool, |
55 | reverse_column: bool, |
56 | move_header_on_borders: bool, |
57 | } |
58 | |
59 | #[derive (Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
60 | enum HeadPosition { |
61 | Top, |
62 | Bottom, |
63 | Left, |
64 | Right, |
65 | } |
66 | |
67 | impl Theme { |
68 | /// Build a theme out of a style builder. |
69 | pub const fn from_style<T, B, L, R, H, V, const HS: usize, const VS: usize>( |
70 | style: Style<T, B, L, R, H, V, HS, VS>, |
71 | ) -> Self { |
72 | let chars: Borders = style.get_borders(); |
73 | let horizontals: [(usize, HorizontalLine); HS] = style.get_horizontals(); |
74 | let horizontal1: Option> = hlines_find(lines:horizontals, search:1); |
75 | |
76 | Self::_new( |
77 | border:TableBorders::new(chars, Borders::empty()), |
78 | lines:BorderLines::new(horizontal1, None, None), |
79 | Layout::new(HeadPosition::Top, false, false, false, false), |
80 | colorization:None, |
81 | ) |
82 | } |
83 | } |
84 | |
85 | impl Theme { |
86 | /// Creates a new empty style. |
87 | /// |
88 | /// It's quite an analog of [`Style::empty`] |
89 | pub const fn new() -> Self { |
90 | Self::_new( |
91 | border:TableBorders::new(Borders::empty(), Borders::empty()), |
92 | lines:BorderLines::new(None, None, None), |
93 | Layout::new(HeadPosition::Top, false, false, false, false), |
94 | colorization:None, |
95 | ) |
96 | } |
97 | } |
98 | |
99 | impl Default for Theme { |
100 | fn default() -> Self { |
101 | Self::new() |
102 | } |
103 | } |
104 | |
105 | macro_rules! func_set_chars { |
106 | ($name:ident, $arg:ident, $desc:expr) => { |
107 | #[doc = concat!("Set a border character" , " " , "" , $desc, "" , " " , "." )] |
108 | pub fn $name(&mut self, c: char) { |
109 | self.border.chars.$arg = Some(c); |
110 | } |
111 | }; |
112 | } |
113 | |
114 | macro_rules! func_remove_chars { |
115 | ($name:ident, $arg:ident, $desc:expr) => { |
116 | #[doc = concat!("Remove a border character" , " " , "" , $desc, "" , " " , "." )] |
117 | pub fn $name(&mut self) { |
118 | self.border.chars.$arg = None; |
119 | } |
120 | }; |
121 | } |
122 | |
123 | macro_rules! func_get_chars { |
124 | ($name:ident, $arg:ident, $desc:expr) => { |
125 | #[doc = concat!("Get a border character" , " " , "" , $desc, "" , " " , "." )] |
126 | pub const fn $name(&self) -> Option<char> { |
127 | self.border.chars.$arg |
128 | } |
129 | }; |
130 | } |
131 | |
132 | macro_rules! func_set_colors { |
133 | ($name:ident, $arg:ident, $desc:expr) => { |
134 | #[doc = concat!("Set a border color" , " " , "" , $desc, "" , " " , "." )] |
135 | pub fn $name(&mut self, color: Color) { |
136 | self.border.colors.$arg = Some(color); |
137 | } |
138 | }; |
139 | } |
140 | |
141 | macro_rules! func_remove_colors { |
142 | ($name:ident, $arg:ident, $desc:expr) => { |
143 | #[doc = concat!("Remove a border color" , " " , "" , $desc, "" , " " , "." )] |
144 | pub fn $name(&mut self) { |
145 | self.border.colors.$arg = None; |
146 | } |
147 | }; |
148 | } |
149 | |
150 | macro_rules! func_get_colors { |
151 | ($name:ident, $arg:ident, $desc:expr) => { |
152 | #[doc = concat!("Get a border color" , " " , "" , $desc, "" , " " , "." )] |
153 | pub fn $name(&self) -> Option<&Color> { |
154 | self.border.colors.$arg.as_ref() |
155 | } |
156 | }; |
157 | } |
158 | |
159 | #[rustfmt::skip] |
160 | impl Theme { |
161 | func_set_chars!(set_border_top, top, "top" ); |
162 | func_set_chars!(set_border_bottom, bottom, "bottom" ); |
163 | func_set_chars!(set_border_left, left, "left" ); |
164 | func_set_chars!(set_border_right, right, "right" ); |
165 | func_set_chars!(set_border_corner_top_left, top_left, "top left corner" ); |
166 | func_set_chars!(set_border_corner_top_right, top_right, "top right corner" ); |
167 | func_set_chars!(set_border_corner_bottom_left, bottom_left, "bottom left corner" ); |
168 | func_set_chars!(set_border_corner_bottom_right, bottom_right, "bottom right corner" ); |
169 | func_set_chars!(set_border_intersection_top, top_intersection, "top intersection with a vertical line" ); |
170 | func_set_chars!(set_border_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line" ); |
171 | func_set_chars!(set_border_intersection_left, left_intersection, "left intersection with a horizontal line" ); |
172 | func_set_chars!(set_border_intersection_right, right_intersection, "right intersection with a horizontal line" ); |
173 | func_set_chars!(set_border_intersection, intersection, "intersection of horizontal and vertical line" ); |
174 | func_set_chars!(set_border_horizontal, horizontal, "horizontal" ); |
175 | func_set_chars!(set_border_vertical, vertical, "vertical" ); |
176 | } |
177 | |
178 | #[rustfmt::skip] |
179 | impl Theme { |
180 | func_get_chars!(get_border_top, top, "top" ); |
181 | func_get_chars!(get_border_bottom, bottom, "bottom" ); |
182 | func_get_chars!(get_border_left, left, "left" ); |
183 | func_get_chars!(get_border_right, right, "right" ); |
184 | func_get_chars!(get_border_corner_top_left, top_left, "top left corner" ); |
185 | func_get_chars!(get_border_corner_top_right, top_right, "top right corner" ); |
186 | func_get_chars!(get_border_corner_bottom_left, bottom_left, "bottom left corner" ); |
187 | func_get_chars!(get_border_corner_bottom_right, bottom_right, "bottom right corner" ); |
188 | func_get_chars!(get_border_intersection_top, top_intersection, "top intersection with a vertical line" ); |
189 | func_get_chars!(get_border_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line" ); |
190 | func_get_chars!(get_border_intersection_left, left_intersection, "left intersection with a horizontal line" ); |
191 | func_get_chars!(get_border_intersection_right, right_intersection, "right intersection with a horizontal line" ); |
192 | func_get_chars!(get_border_intersection, intersection, "intersection of horizontal and vertical line" ); |
193 | func_get_chars!(get_border_horizontal, horizontal, "horizontal" ); |
194 | func_get_chars!(get_border_vertical, vertical, "vertical" ); |
195 | } |
196 | |
197 | #[rustfmt::skip] |
198 | impl Theme { |
199 | func_remove_chars!(remove_border_top, top, "top" ); |
200 | func_remove_chars!(remove_border_bottom, bottom, "bottom" ); |
201 | func_remove_chars!(remove_border_left, left, "left" ); |
202 | func_remove_chars!(remove_border_right, right, "right" ); |
203 | func_remove_chars!(remove_border_corner_top_left, top_left, "top left corner" ); |
204 | func_remove_chars!(remove_border_corner_top_right, top_right, "top right corner" ); |
205 | func_remove_chars!(remove_border_corner_bottom_left, bottom_left, "bottom left corner" ); |
206 | func_remove_chars!(remove_border_corner_bottom_right, bottom_right, "bottom right corner" ); |
207 | func_remove_chars!(remove_border_intersection_top, top_intersection, "top intersection with a vertical line" ); |
208 | func_remove_chars!(remove_border_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line" ); |
209 | func_remove_chars!(remove_border_intersection_left, left_intersection, "left intersection with a horizontal line" ); |
210 | func_remove_chars!(remove_border_intersection_right, right_intersection, "right intersection with a horizontal line" ); |
211 | func_remove_chars!(remove_border_intersection, intersection, "intersection of horizontal and vertical line" ); |
212 | func_remove_chars!(remove_border_horizontal, horizontal, "horizontal" ); |
213 | func_remove_chars!(remove_border_vertical, vertical, "vertical" ); |
214 | } |
215 | |
216 | #[rustfmt::skip] |
217 | impl Theme { |
218 | func_set_colors!(set_border_color_top, top, "top" ); |
219 | func_set_colors!(set_border_color_bottom, bottom, "bottom" ); |
220 | func_set_colors!(set_border_color_left, left, "left" ); |
221 | func_set_colors!(set_border_color_right, right, "right" ); |
222 | func_set_colors!(set_border_color_corner_top_left, top_left, "top left corner" ); |
223 | func_set_colors!(set_border_color_corner_top_right, top_right, "top right corner" ); |
224 | func_set_colors!(set_border_color_corner_bottom_left, bottom_left, "bottom left corner" ); |
225 | func_set_colors!(set_border_color_corner_bottom_right, bottom_right, "bottom right corner" ); |
226 | func_set_colors!(set_border_color_intersection_top, top_intersection, "top intersection with a vertical line" ); |
227 | func_set_colors!(set_border_color_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line" ); |
228 | func_set_colors!(set_border_color_intersection_left, left_intersection, "left intersection with a horizontal line" ); |
229 | func_set_colors!(set_border_color_intersection_right, right_intersection, "right intersection with a horizontal line" ); |
230 | func_set_colors!(set_border_color_intersection, intersection, "intersection of horizontal and vertical line" ); |
231 | func_set_colors!(set_border_color_horizontal, horizontal, "horizontal" ); |
232 | func_set_colors!(set_border_color_vertical, vertical, "vertical" ); |
233 | } |
234 | |
235 | #[rustfmt::skip] |
236 | impl Theme { |
237 | func_remove_colors!(remove_border_color_top, top, "top" ); |
238 | func_remove_colors!(remove_border_color_bottom, bottom, "bottom" ); |
239 | func_remove_colors!(remove_border_color_left, left, "left" ); |
240 | func_remove_colors!(remove_border_color_right, right, "right" ); |
241 | func_remove_colors!(remove_border_color_corner_top_left, top_left, "top left corner" ); |
242 | func_remove_colors!(remove_border_color_corner_top_right, top_right, "top right corner" ); |
243 | func_remove_colors!(remove_border_color_corner_bottom_left, bottom_left, "bottom left corner" ); |
244 | func_remove_colors!(remove_border_color_corner_bottom_right, bottom_right, "bottom right corner" ); |
245 | func_remove_colors!(remove_border_color_intersection_top, top_intersection, "top intersection with a vertical line" ); |
246 | func_remove_colors!(remove_border_color_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line" ); |
247 | func_remove_colors!(remove_border_color_intersection_left, left_intersection, "left intersection with a horizontal line" ); |
248 | func_remove_colors!(remove_border_color_intersection_right, right_intersection, "right intersection with a horizontal line" ); |
249 | func_remove_colors!(remove_border_color_intersection, intersection, "intersection of horizontal and vertical line" ); |
250 | func_remove_colors!(remove_border_color_horizontal, horizontal, "horizontal" ); |
251 | func_remove_colors!(remove_border_color_vertical, vertical, "vertical" ); |
252 | } |
253 | |
254 | #[rustfmt::skip] |
255 | impl Theme { |
256 | func_get_colors!(get_border_color_top, top, "top" ); |
257 | func_get_colors!(get_border_color_bottom, bottom, "bottom" ); |
258 | func_get_colors!(get_border_color_left, left, "left" ); |
259 | func_get_colors!(get_border_color_right, right, "right" ); |
260 | func_get_colors!(get_border_color_corner_top_left, top_left, "top left corner" ); |
261 | func_get_colors!(get_border_color_corner_top_right, top_right, "top right corner" ); |
262 | func_get_colors!(get_border_color_corner_bottom_left, bottom_left, "bottom left corner" ); |
263 | func_get_colors!(get_border_color_corner_bottom_right, bottom_right, "bottom right corner" ); |
264 | func_get_colors!(get_border_color_intersection_top, top_intersection, "top intersection with a vertical line" ); |
265 | func_get_colors!(get_border_color_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line" ); |
266 | func_get_colors!(get_border_color_intersection_left, left_intersection, "left intersection with a horizontal line" ); |
267 | func_get_colors!(get_border_color_intersection_right, right_intersection, "right intersection with a horizontal line" ); |
268 | func_get_colors!(get_border_color_intersection, intersection, "intersection of horizontal and vertical line" ); |
269 | func_get_colors!(get_border_color_horizontal, horizontal, "horizontal" ); |
270 | func_get_colors!(get_border_color_vertical, vertical, "vertical" ); |
271 | } |
272 | |
273 | impl Theme { |
274 | /// Returns an outer border of the style. |
275 | pub fn set_border_frame(&mut self, frame: Border<char>) { |
276 | self.border.chars.top = frame.top; |
277 | self.border.chars.bottom = frame.bottom; |
278 | self.border.chars.left = frame.left; |
279 | self.border.chars.right = frame.right; |
280 | self.border.chars.top_left = frame.left_top_corner; |
281 | self.border.chars.top_right = frame.right_top_corner; |
282 | self.border.chars.bottom_left = frame.left_bottom_corner; |
283 | self.border.chars.bottom_right = frame.right_bottom_corner; |
284 | } |
285 | |
286 | /// Returns an outer border of the style. |
287 | pub fn set_border_color_frame(&mut self, frame: Border<Color>) { |
288 | self.border.colors.top = frame.top; |
289 | self.border.colors.bottom = frame.bottom; |
290 | self.border.colors.left = frame.left; |
291 | self.border.colors.right = frame.right; |
292 | self.border.colors.top_left = frame.left_top_corner; |
293 | self.border.colors.top_right = frame.right_top_corner; |
294 | self.border.colors.bottom_left = frame.left_bottom_corner; |
295 | self.border.colors.bottom_right = frame.right_bottom_corner; |
296 | } |
297 | |
298 | /// Set borders structure. |
299 | pub fn set_border(&mut self, borders: Borders<char>) { |
300 | self.border.chars = borders; |
301 | } |
302 | |
303 | /// Set borders structure. |
304 | pub fn set_border_color(&mut self, borders: Borders<Color>) { |
305 | self.border.colors = borders; |
306 | } |
307 | |
308 | /// Set an outer border. |
309 | pub const fn get_border_frame(&self) -> Border<char> { |
310 | Border { |
311 | top: self.border.chars.top, |
312 | bottom: self.border.chars.bottom, |
313 | left: self.border.chars.left, |
314 | right: self.border.chars.right, |
315 | left_top_corner: self.border.chars.top_left, |
316 | right_top_corner: self.border.chars.top_right, |
317 | left_bottom_corner: self.border.chars.bottom_left, |
318 | right_bottom_corner: self.border.chars.bottom_right, |
319 | } |
320 | } |
321 | |
322 | /// Set an outer border. |
323 | pub const fn get_border_color_frame(&self) -> Border<&Color> { |
324 | Border { |
325 | top: self.border.colors.top.as_ref(), |
326 | bottom: self.border.colors.bottom.as_ref(), |
327 | left: self.border.colors.left.as_ref(), |
328 | right: self.border.colors.right.as_ref(), |
329 | left_top_corner: self.border.colors.top_left.as_ref(), |
330 | right_top_corner: self.border.colors.top_right.as_ref(), |
331 | left_bottom_corner: self.border.colors.bottom_left.as_ref(), |
332 | right_bottom_corner: self.border.colors.bottom_right.as_ref(), |
333 | } |
334 | } |
335 | } |
336 | |
337 | impl Theme { |
338 | /// Set horizontal border lines. |
339 | /// |
340 | /// # Example |
341 | /// |
342 | /// ``` |
343 | /// use std::collections::HashMap; |
344 | /// use tabled::{Table, settings::style::{Style, HorizontalLine}, settings::themes::Theme}; |
345 | /// |
346 | /// let mut style = Theme::from(Style::re_structured_text()); |
347 | /// |
348 | /// let mut lines = HashMap::new(); |
349 | /// lines.insert(1, HorizontalLine::inherit(Style::extended()).into()); |
350 | /// |
351 | /// style.set_lines_horizontal(lines); |
352 | /// |
353 | /// let data = (0..3).map(|i| ("Hello" , i)); |
354 | /// let table = Table::new(data).with(style).to_string(); |
355 | /// |
356 | /// assert_eq!( |
357 | /// table, |
358 | /// concat!( |
359 | /// " ======= ===== \n" , |
360 | /// " &str i32 \n" , |
361 | /// "╠═══════╬═════╣ \n" , |
362 | /// " Hello 0 \n" , |
363 | /// " Hello 1 \n" , |
364 | /// " Hello 2 \n" , |
365 | /// " ======= ===== " , |
366 | /// ), |
367 | /// ) |
368 | /// ``` |
369 | pub fn set_lines_horizontal(&mut self, lines: HashMap<usize, HorizontalLine<char>>) { |
370 | self.lines.horizontals = Some(lines); |
371 | } |
372 | |
373 | /// Set vertical border lines. |
374 | /// |
375 | /// # Example |
376 | /// |
377 | /// ``` |
378 | /// use std::collections::HashMap; |
379 | /// use tabled::{ |
380 | /// Table, |
381 | /// settings::style::{Style, HorizontalLine}, |
382 | /// settings::themes::Theme, |
383 | /// }; |
384 | /// |
385 | /// |
386 | /// let mut style = Theme::from_style(Style::re_structured_text()); |
387 | /// |
388 | /// let mut lines = HashMap::new(); |
389 | /// lines.insert(1, HorizontalLine::inherit(Style::extended()).into()); |
390 | /// |
391 | /// style.set_lines_vertical(lines); |
392 | /// |
393 | /// let data = (0..3).map(|i| ("Hello" , i)); |
394 | /// let table = Table::new(data).with(style).to_string(); |
395 | /// |
396 | /// assert_eq!( |
397 | /// table, |
398 | /// concat!( |
399 | /// "=======╠===== \n" , |
400 | /// " &str ═ i32 \n" , |
401 | /// "======= ===== \n" , |
402 | /// " Hello ═ 0 \n" , |
403 | /// " Hello ═ 1 \n" , |
404 | /// " Hello ═ 2 \n" , |
405 | /// "=======╣=====" , |
406 | /// ), |
407 | /// ) |
408 | /// ``` |
409 | pub fn set_lines_vertical(&mut self, lines: HashMap<usize, VerticalLine<char>>) { |
410 | self.lines.verticals = Some(lines); |
411 | } |
412 | |
413 | /// Insert a vertical line into specific column location. |
414 | pub fn insert_line_vertical(&mut self, line: usize, vertical: VerticalLine<char>) { |
415 | match &mut self.lines.verticals { |
416 | Some(verticals) => { |
417 | let _ = verticals.insert(line, vertical); |
418 | } |
419 | None => self.lines.verticals = Some(HashMap::from_iter([(line, vertical)])), |
420 | } |
421 | } |
422 | |
423 | /// Insert a horizontal line to a specific row location. |
424 | pub fn insert_line_horizontal(&mut self, line: usize, horizontal: HorizontalLine<char>) { |
425 | match &mut self.lines.horizontals { |
426 | Some(horizontals) => { |
427 | let _ = horizontals.insert(line, horizontal); |
428 | } |
429 | None => self.lines.horizontals = Some(HashMap::from_iter([(line, horizontal)])), |
430 | } |
431 | } |
432 | |
433 | /// Get a vertical line at the row if any set. |
434 | pub fn get_line_vertical(&self, column: usize) -> Option<VerticalLine<char>> { |
435 | self.lines |
436 | .verticals |
437 | .as_ref() |
438 | .and_then(|lines| lines.get(&column).cloned()) |
439 | } |
440 | |
441 | /// Get a horizontal line at the row if any set. |
442 | pub fn get_line_horizontal(&self, row: usize) -> Option<HorizontalLine<char>> { |
443 | self.lines |
444 | .horizontals |
445 | .as_ref() |
446 | .and_then(|list| list.get(&row).cloned()) |
447 | } |
448 | } |
449 | |
450 | impl Theme { |
451 | /// Reverse rows. |
452 | pub fn reverse_rows(&mut self, reverse: bool) { |
453 | self.layout.reverse_rows = reverse; |
454 | } |
455 | |
456 | /// Reverse columns. |
457 | pub fn reverse_columns(&mut self, reverse: bool) { |
458 | self.layout.reverse_column = reverse; |
459 | } |
460 | |
461 | /// Set a footer. |
462 | /// |
463 | /// Copy columns names to an apposite side of a table. |
464 | pub fn set_footer(&mut self, footer: bool) { |
465 | self.layout.footer = footer; |
466 | } |
467 | |
468 | /// Set column alignment |
469 | pub fn align_columns(&mut self, position: Alignment) { |
470 | self.layout.orientation = convert_orientation(position); |
471 | } |
472 | } |
473 | |
474 | impl Theme { |
475 | const fn _new( |
476 | border: TableBorders, |
477 | lines: BorderLines, |
478 | layout: Layout, |
479 | colorization: Option<Colorization>, |
480 | ) -> Self { |
481 | Self { |
482 | border, |
483 | lines, |
484 | layout, |
485 | colorization, |
486 | } |
487 | } |
488 | } |
489 | |
490 | impl From<Borders<char>> for Theme { |
491 | fn from(borders: Borders<char>) -> Self { |
492 | Self::_new( |
493 | border:TableBorders::new(borders, Borders::empty()), |
494 | lines:BorderLines::new(None, None, None), |
495 | Layout::new(HeadPosition::Top, false, false, false, false), |
496 | colorization:None, |
497 | ) |
498 | } |
499 | } |
500 | |
501 | impl<R, D> TableOption<R, ColoredConfig, D> for Theme |
502 | where |
503 | R: Records + Resizable + ExactRecords + PeekableRecords + RecordsMut<String>, |
504 | { |
505 | fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
506 | cfg_clear_borders(cfg); |
507 | cfg_set_custom_lines(cfg, self.lines); |
508 | cfg_set_borders(cfg, self.border); |
509 | |
510 | move_head_if(records, self.layout.orientation); |
511 | |
512 | if self.layout.reverse_column { |
513 | reverse_head(data:records, self.layout.orientation); |
514 | } |
515 | |
516 | if self.layout.reverse_rows { |
517 | reverse_data(records, self.layout.orientation); |
518 | } |
519 | |
520 | if self.layout.footer { |
521 | copy_head(records, self.layout.orientation); |
522 | } |
523 | } |
524 | } |
525 | |
526 | impl<R, D> TableOption<R, CompactConfig, D> for Theme { |
527 | fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { |
528 | *cfg = cfg.set_borders(self.border.chars); |
529 | } |
530 | } |
531 | |
532 | impl<R, D> TableOption<R, CompactMultilineConfig, D> for Theme { |
533 | fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) { |
534 | cfg.set_borders(self.border.chars); |
535 | } |
536 | } |
537 | |
538 | impl<T, B, L, R, H, V, const HSIZE: usize, const VSIZE: usize> |
539 | From<Style<T, B, L, R, H, V, HSIZE, VSIZE>> for Theme |
540 | { |
541 | fn from(style: Style<T, B, L, R, H, V, HSIZE, VSIZE>) -> Self { |
542 | Self::from_style(style) |
543 | } |
544 | } |
545 | |
546 | impl From<ColoredConfig> for Theme { |
547 | fn from(cfg: ColoredConfig) -> Self { |
548 | let borders: Borders = *cfg.get_borders(); |
549 | let colors: Borders = cfg.get_color_borders().clone().convert_into(); |
550 | let horizontals: HashMap> = cfg.get_horizontal_lines().into_iter().collect(); |
551 | let verticals: HashMap> = cfg.get_vertical_lines().into_iter().collect(); |
552 | |
553 | Self::_new( |
554 | border:TableBorders::new(borders, colors), |
555 | lines:BorderLines::new(None, Some(horizontals), Some(verticals)), |
556 | Layout::new(HeadPosition::Top, false, false, false, false), |
557 | colorization:None, |
558 | ) |
559 | } |
560 | } |
561 | |
562 | impl TableBorders { |
563 | const fn new(chars: Borders<char>, colors: Borders<Color>) -> Self { |
564 | Self { chars, colors } |
565 | } |
566 | } |
567 | |
568 | impl BorderLines { |
569 | const fn new( |
570 | horizontal1: Option<HorizontalLine<char>>, |
571 | horizontals: Option<HashMap<usize, HorizontalLine<char>>>, |
572 | verticals: Option<HashMap<usize, VerticalLine<char>>>, |
573 | ) -> Self { |
574 | Self { |
575 | horizontal1, |
576 | horizontals, |
577 | verticals, |
578 | } |
579 | } |
580 | } |
581 | |
582 | impl Layout { |
583 | const fn new( |
584 | orientation: HeadPosition, |
585 | footer: bool, |
586 | reverse_rows: bool, |
587 | reverse_column: bool, |
588 | move_header_on_borders: bool, |
589 | ) -> Self { |
590 | Self { |
591 | orientation, |
592 | footer, |
593 | reverse_rows, |
594 | reverse_column, |
595 | move_header_on_borders, |
596 | } |
597 | } |
598 | } |
599 | |
600 | fn cfg_clear_borders(cfg: &mut ColoredConfig) { |
601 | cfg.remove_borders(); |
602 | cfg.remove_borders_colors(); |
603 | cfg.remove_vertical_chars(); |
604 | cfg.remove_horizontal_chars(); |
605 | cfg.remove_color_line_horizontal(); |
606 | cfg.remove_color_line_vertical(); |
607 | } |
608 | |
609 | fn cfg_set_borders(cfg: &mut ColoredConfig, border: TableBorders) { |
610 | cfg.set_borders(border.chars); |
611 | |
612 | if !border.colors.is_empty() { |
613 | cfg.set_borders_color(clrs:border.colors.convert_into()); |
614 | } |
615 | } |
616 | |
617 | fn cfg_set_custom_lines(cfg: &mut ColoredConfig, lines: BorderLines) { |
618 | if let Some(line: HorizontalLine) = lines.horizontal1 { |
619 | cfg.insert_horizontal_line(line:1, val:line); |
620 | } |
621 | |
622 | if let Some(lines: HashMap>) = lines.horizontals { |
623 | for (row: usize, line: HorizontalLine) in lines { |
624 | cfg.insert_horizontal_line(line:row, val:line); |
625 | } |
626 | } |
627 | |
628 | if let Some(lines: HashMap>) = lines.verticals { |
629 | for (col: usize, line: VerticalLine) in lines { |
630 | cfg.insert_vertical_line(line:col, val:line); |
631 | } |
632 | } |
633 | } |
634 | |
635 | const fn hlines_find<const N: usize>( |
636 | lines: [(usize, HorizontalLine<char>); N], |
637 | search: usize, |
638 | ) -> Option<HorizontalLine<char>> { |
639 | let mut line: Option> = None; |
640 | |
641 | let mut i: usize = 0; |
642 | while i < lines.len() { |
643 | let (num: usize, hline: HorizontalLine) = lines[i]; |
644 | if num == search { |
645 | line = Some(hline); |
646 | } |
647 | |
648 | i += 1; |
649 | } |
650 | |
651 | line |
652 | } |
653 | |
654 | fn reverse_data<R>(records: &mut R, orientation: HeadPosition) |
655 | where |
656 | R: Records + Resizable + ExactRecords + PeekableRecords + RecordsMut<String>, |
657 | { |
658 | let count_rows: usize = records.count_rows(); |
659 | let count_columns: usize = records.count_columns(); |
660 | if count_columns < 2 || count_rows < 2 { |
661 | return; |
662 | } |
663 | |
664 | match orientation { |
665 | HeadPosition::Top => reverse_rows(data:records, from:1, to:count_rows), |
666 | HeadPosition::Bottom => reverse_rows(data:records, from:0, to:count_rows - 1), |
667 | HeadPosition::Left => reverse_columns(data:records, from:1, to:count_columns), |
668 | HeadPosition::Right => reverse_columns(data:records, from:0, to:count_columns - 1), |
669 | } |
670 | } |
671 | |
672 | fn reverse_head<R>(data: &mut R, orientation: HeadPosition) |
673 | where |
674 | R: Records + Resizable + ExactRecords + PeekableRecords + RecordsMut<String>, |
675 | { |
676 | match orientation { |
677 | HeadPosition::Top | HeadPosition::Bottom => reverse_columns(data, from:0, to:data.count_columns()), |
678 | HeadPosition::Left | HeadPosition::Right => reverse_rows(data, from:0, to:data.count_rows()), |
679 | } |
680 | } |
681 | |
682 | fn reverse_rows<R>(data: &mut R, from: usize, to: usize) |
683 | where |
684 | R: Resizable, |
685 | { |
686 | let end: usize = to / 2; |
687 | let mut to: usize = to - 1; |
688 | for row: usize in from..end { |
689 | data.swap_row(lhs:row, rhs:to); |
690 | to -= 1; |
691 | } |
692 | } |
693 | |
694 | fn reverse_columns<R>(data: &mut R, from: usize, to: usize) |
695 | where |
696 | R: Resizable, |
697 | { |
698 | let end: usize = to / 2; |
699 | let mut to: usize = to - 1; |
700 | for row: usize in from..end { |
701 | data.swap_column(lhs:row, rhs:to); |
702 | to -= 1; |
703 | } |
704 | } |
705 | |
706 | fn copy_head<R>(records: &mut R, orientation: HeadPosition) |
707 | where |
708 | R: Records + Resizable + ExactRecords + PeekableRecords + RecordsMut<String>, |
709 | { |
710 | let head: Vec = collect_head_by(records, orientation); |
711 | match orientation { |
712 | HeadPosition::Top => cp_row(records, row:head, pos:records.count_rows()), |
713 | HeadPosition::Bottom => cp_row(records, row:head, pos:0), |
714 | HeadPosition::Left => cp_column(records, column:head, pos:records.count_columns()), |
715 | HeadPosition::Right => cp_column(records, column:head, pos:0), |
716 | } |
717 | } |
718 | |
719 | fn collect_head_by<R>(records: &mut R, orientation: HeadPosition) -> Vec<String> |
720 | where |
721 | R: Records + PeekableRecords + ExactRecords, |
722 | { |
723 | match orientation { |
724 | HeadPosition::Top => collect_head(records, row:0), |
725 | HeadPosition::Bottom => collect_head(records, row:records.count_rows() - 1), |
726 | HeadPosition::Left => collect_head_vertical(records, column:0), |
727 | HeadPosition::Right => collect_head_vertical(records, column:records.count_columns() - 1), |
728 | } |
729 | } |
730 | |
731 | fn cp_row<R>(records: &mut R, row: Vec<String>, pos: usize) |
732 | where |
733 | R: Records + Resizable + ExactRecords + PeekableRecords + RecordsMut<String>, |
734 | { |
735 | records.insert_row(pos); |
736 | |
737 | for (col: usize, text: String) in row.into_iter().enumerate() { |
738 | records.set((pos, col), text); |
739 | } |
740 | } |
741 | |
742 | fn cp_column<R>(records: &mut R, column: Vec<String>, pos: usize) |
743 | where |
744 | R: Records + Resizable + ExactRecords + PeekableRecords + RecordsMut<String>, |
745 | { |
746 | records.insert_column(pos); |
747 | |
748 | for (row: usize, text: String) in column.into_iter().enumerate() { |
749 | records.set((row, pos), text); |
750 | } |
751 | } |
752 | |
753 | fn move_head_if<R>(records: &mut R, orientation: HeadPosition) |
754 | where |
755 | R: Records + Resizable + ExactRecords + PeekableRecords + RecordsMut<String>, |
756 | { |
757 | match orientation { |
758 | HeadPosition::Top => {} |
759 | HeadPosition::Bottom => { |
760 | let head: Vec = collect_head(records, row:0); |
761 | push_row(records, row:head); |
762 | records.remove_row(0); |
763 | } |
764 | HeadPosition::Left => { |
765 | Rotate::Left.change(records, &mut (), &mut ()); |
766 | Rotate::Bottom.change(records, &mut (), &mut ()); |
767 | } |
768 | HeadPosition::Right => { |
769 | Rotate::Right.change(records, &mut (), &mut ()); |
770 | } |
771 | } |
772 | } |
773 | |
774 | fn collect_head<R>(records: &mut R, row: usize) -> Vec<String> |
775 | where |
776 | R: Records + PeekableRecords, |
777 | { |
778 | (0..records.count_columns()) |
779 | .map(|column: usize| records.get_text((row, column))) |
780 | .map(ToString::to_string) |
781 | .collect() |
782 | } |
783 | |
784 | fn collect_head_vertical<R>(records: &mut R, column: usize) -> Vec<String> |
785 | where |
786 | R: Records + PeekableRecords + ExactRecords, |
787 | { |
788 | (0..records.count_rows()) |
789 | .map(|row: usize| records.get_text((row, column))) |
790 | .map(ToString::to_string) |
791 | .collect() |
792 | } |
793 | |
794 | fn push_row<R>(records: &mut R, row: Vec<String>) |
795 | where |
796 | R: Records + ExactRecords + Resizable + RecordsMut<String>, |
797 | { |
798 | records.push_row(); |
799 | |
800 | let last_row: usize = records.count_rows() - 1; |
801 | |
802 | for (col: usize, text: String) in row.into_iter().enumerate() { |
803 | records.set((last_row, col), text); |
804 | } |
805 | } |
806 | |
807 | fn convert_orientation(position: Alignment) -> HeadPosition { |
808 | let h: Option = Option::from(position); |
809 | let v: Option = Option::from(position); |
810 | |
811 | match (h, v) { |
812 | (None, Some(AlignmentVertical::Top)) => HeadPosition::Top, |
813 | (None, Some(AlignmentVertical::Bottom)) => HeadPosition::Bottom, |
814 | (Some(AlignmentHorizontal::Left), None) => HeadPosition::Left, |
815 | (Some(AlignmentHorizontal::Right), None) => HeadPosition::Right, |
816 | (None, Some(AlignmentVertical::Center)) => HeadPosition::Top, |
817 | (Some(AlignmentHorizontal::Center), None) => HeadPosition::Top, |
818 | (None, None) | (Some(_), Some(_)) => HeadPosition::Top, |
819 | } |
820 | } |
821 | |