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
12use std::collections::HashMap;
13use std::iter::FromIterator;
14
15use 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)]
30pub 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)]
38struct TableBorders {
39 chars: Borders<char>,
40 colors: Borders<Color>,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
44struct 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)]
51struct 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)]
60enum HeadPosition {
61 Top,
62 Bottom,
63 Left,
64 Right,
65}
66
67impl 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
85impl 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
99impl Default for Theme {
100 fn default() -> Self {
101 Self::new()
102 }
103}
104
105macro_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
114macro_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
123macro_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
132macro_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
141macro_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
150macro_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]
160impl 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]
179impl 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]
198impl 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]
217impl 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]
236impl 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]
255impl 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
273impl 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
337impl 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
450impl 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
474impl 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
490impl 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
501impl<R, D> TableOption<R, ColoredConfig, D> for Theme
502where
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
526impl<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
532impl<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
538impl<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
546impl 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
562impl TableBorders {
563 const fn new(chars: Borders<char>, colors: Borders<Color>) -> Self {
564 Self { chars, colors }
565 }
566}
567
568impl 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
582impl 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
600fn 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
609fn 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
617fn 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
635const 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
654fn reverse_data<R>(records: &mut R, orientation: HeadPosition)
655where
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
672fn reverse_head<R>(data: &mut R, orientation: HeadPosition)
673where
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
682fn reverse_rows<R>(data: &mut R, from: usize, to: usize)
683where
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
694fn reverse_columns<R>(data: &mut R, from: usize, to: usize)
695where
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
706fn copy_head<R>(records: &mut R, orientation: HeadPosition)
707where
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
719fn collect_head_by<R>(records: &mut R, orientation: HeadPosition) -> Vec<String>
720where
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
731fn cp_row<R>(records: &mut R, row: Vec<String>, pos: usize)
732where
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
742fn cp_column<R>(records: &mut R, column: Vec<String>, pos: usize)
743where
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
753fn move_head_if<R>(records: &mut R, orientation: HeadPosition)
754where
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
774fn collect_head<R>(records: &mut R, row: usize) -> Vec<String>
775where
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
784fn collect_head_vertical<R>(records: &mut R, column: usize) -> Vec<String>
785where
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
794fn push_row<R>(records: &mut R, row: Vec<String>)
795where
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
807fn 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