1use std::collections::HashMap;
13
14use crate::{
15 grid::config::{
16 Border, Borders, ColoredConfig, CompactConfig, CompactMultilineConfig, HorizontalLine,
17 VerticalLine,
18 },
19 settings::{style::Style, Color, TableOption},
20};
21
22#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct Theme {
27 chars: Borders<char>,
28 colors: Borders<Color>,
29 lines_horizontals: Option<HashMap<usize, HorizontalLine<char>>>,
30 lines_verticals: Option<HashMap<usize, VerticalLine<char>>>,
31 lines_horizontal1: Option<HorizontalLine<char>>,
32}
33
34impl Theme {
35 pub const fn new() -> Self {
39 Self::gen(Borders::empty(), Borders::empty(), None, None, None)
40 }
41
42 pub const fn from_style<T, B, L, R, H, V, const HS: usize, const VS: usize>(
44 style: Style<T, B, L, R, H, V, HS, VS>,
45 ) -> Self {
46 let chars = style.get_borders();
47 let hlines = style.get_horizontals();
48 let hlines1 = hlines_find(hlines, 1);
49
50 Self::gen(chars, Borders::empty(), None, None, hlines1)
51 }
52
53 pub fn set_frame(&mut self, frame: Border<char>) {
55 self.chars.top = frame.top;
56 self.chars.bottom = frame.bottom;
57 self.chars.left = frame.left;
58 self.chars.right = frame.right;
59 self.chars.top_left = frame.left_top_corner;
60 self.chars.top_right = frame.right_top_corner;
61 self.chars.bottom_left = frame.left_bottom_corner;
62 self.chars.bottom_right = frame.right_bottom_corner;
63 }
64
65 pub fn set_frame_colors(&mut self, frame: Border<Color>) {
67 self.colors.top = frame.top;
68 self.colors.bottom = frame.bottom;
69 self.colors.left = frame.left;
70 self.colors.right = frame.right;
71 self.colors.top_left = frame.left_top_corner;
72 self.colors.top_right = frame.right_top_corner;
73 self.colors.bottom_left = frame.left_bottom_corner;
74 self.colors.bottom_right = frame.right_bottom_corner;
75 }
76
77 pub fn set_borders(&mut self, borders: Borders<char>) {
79 self.chars = borders;
80 }
81
82 pub fn set_colors(&mut self, borders: Borders<Color>) {
84 self.colors = borders;
85 }
86
87 pub const fn get_borders(&self) -> &Borders<char> {
89 &self.chars
90 }
91
92 pub const fn get_borders_colors(&self) -> &Borders<Color> {
94 &self.colors
95 }
96
97 pub fn get_borders_mut(&mut self) -> &mut Borders<char> {
99 &mut self.chars
100 }
101
102 pub fn get_colors_mut(&mut self) -> &mut Borders<Color> {
104 &mut self.colors
105 }
106
107 pub fn remove_borders(&mut self) {
109 self.set_borders(Borders::empty());
110 }
111
112 pub fn remove_colors(&mut self) {
114 self.set_colors(Borders::empty());
115 }
116
117 pub fn remove_horizontal_lines(&mut self) {
119 self.set_horizontal_lines(HashMap::new());
120 self.lines_horizontal1 = None;
121 self.chars.horizontal = None;
122 self.chars.left_intersection = None;
123 self.chars.right_intersection = None;
124 self.chars.intersection = None;
125 }
126
127 pub fn remove_vertical_lines(&mut self) {
129 self.set_vertical_lines(HashMap::new());
130 self.chars.vertical = None;
131 self.chars.top_intersection = None;
132 self.chars.bottom_intersection = None;
133 self.chars.intersection = None;
134 }
135
136 pub const fn get_frame(&self) -> Border<char> {
138 Border {
139 top: self.chars.top,
140 bottom: self.chars.bottom,
141 left: self.chars.left,
142 right: self.chars.right,
143 left_top_corner: self.chars.top_left,
144 right_top_corner: self.chars.top_right,
145 left_bottom_corner: self.chars.bottom_left,
146 right_bottom_corner: self.chars.bottom_right,
147 }
148 }
149
150 pub const fn get_frame_colors(&self) -> Border<&Color> {
152 Border {
153 top: self.colors.top.as_ref(),
154 bottom: self.colors.bottom.as_ref(),
155 left: self.colors.left.as_ref(),
156 right: self.colors.right.as_ref(),
157 left_top_corner: self.colors.top_left.as_ref(),
158 right_top_corner: self.colors.top_right.as_ref(),
159 left_bottom_corner: self.colors.bottom_left.as_ref(),
160 right_bottom_corner: self.colors.bottom_right.as_ref(),
161 }
162 }
163
164 pub fn set_horizontal_lines(&mut self, lines: HashMap<usize, HorizontalLine<char>>) {
196 self.lines_horizontals = Some(lines);
197 }
198
199 pub fn set_vertical_lines(&mut self, lines: HashMap<usize, VerticalLine<char>>) {
236 self.lines_verticals = Some(lines);
237 }
238
239 pub fn insert_vertical_line<L>(&mut self, line: usize, vertical: L)
241 where
242 L: Into<VerticalLine<char>>,
243 {
244 let vertical = vertical.into();
245
246 let verticals = match &mut self.lines_verticals {
247 Some(verticals) => verticals,
248 None => {
249 self.lines_verticals = Some(HashMap::with_capacity(1));
250 self.lines_verticals.as_mut().expect("checked")
251 }
252 };
253
254 let _ = verticals.insert(line, vertical);
255 }
256
257 pub fn insert_horizontal_line<L>(&mut self, line: usize, horizontal: L)
259 where
260 L: Into<HorizontalLine<char>>,
261 {
262 let horizontal = horizontal.into();
263
264 let horizontals = match &mut self.lines_horizontals {
265 Some(horizontals) => horizontals,
266 None => {
267 self.lines_horizontals = Some(HashMap::with_capacity(1));
268 self.lines_horizontals.as_mut().expect("checked")
269 }
270 };
271
272 let _ = horizontals.insert(line, horizontal);
273 }
274
275 pub fn get_vertical_line(&self, column: usize) -> Option<&VerticalLine<char>> {
277 self.lines_verticals.as_ref().and_then(|m| m.get(&column))
278 }
279
280 pub fn get_horizontal_line(&self, row: usize) -> Option<&HorizontalLine<char>> {
282 let line = self.lines_horizontals.as_ref().and_then(|m| m.get(&row));
283 if line.is_some() {
284 return line;
285 }
286
287 if row == 1 && self.lines_horizontal1.is_some() {
288 return self.lines_horizontal1.as_ref();
289 }
290
291 None
292 }
293
294 pub const fn borders_has_left(&self) -> bool {
296 self.chars.has_left()
297 }
298
299 pub const fn borders_has_right(&self) -> bool {
301 self.chars.has_right()
302 }
303
304 pub const fn borders_has_top(&self) -> bool {
306 self.chars.has_top()
307 }
308
309 pub const fn borders_has_bottom(&self) -> bool {
311 self.chars.has_bottom()
312 }
313
314 pub const fn borders_has_horizontal(&self) -> bool {
316 self.chars.has_horizontal()
317 }
318
319 pub const fn borders_has_vertical(&self) -> bool {
321 self.chars.has_vertical()
322 }
323
324 const fn gen(
325 chars: Borders<char>,
326 colors: Borders<Color>,
327 horizontals: Option<HashMap<usize, HorizontalLine<char>>>,
328 verticals: Option<HashMap<usize, VerticalLine<char>>>,
329 horizontal1: Option<HorizontalLine<char>>,
330 ) -> Self {
331 Self {
332 chars,
333 colors,
334 lines_horizontals: horizontals,
335 lines_verticals: verticals,
336 lines_horizontal1: horizontal1,
337 }
338 }
339}
340
341impl From<Borders<char>> for Theme {
342 fn from(borders: Borders<char>) -> Self {
343 Self::gen(
344 borders,
345 Borders::empty(),
346 Default::default(),
347 Default::default(),
348 None,
349 )
350 }
351}
352
353impl Default for Theme {
354 fn default() -> Self {
355 Self::new()
356 }
357}
358
359impl<R, D> TableOption<R, ColoredConfig, D> for Theme {
360 fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
361 cfg_clear_borders(cfg);
362 cfg_set_custom_lines(
363 cfg,
364 self.lines_horizontals,
365 self.lines_verticals,
366 self.lines_horizontal1,
367 );
368 cfg_set_borders(cfg, self.chars, self.colors);
369 }
370}
371
372impl<R, D> TableOption<R, CompactConfig, D> for Theme {
373 fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) {
374 *cfg = cfg.set_borders(self.chars);
375 }
376}
377
378impl<R, D> TableOption<R, CompactMultilineConfig, D> for Theme {
379 fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) {
380 cfg.set_borders(self.chars);
381 }
382}
383
384impl<T, B, L, R, H, V, const HSIZE: usize, const VSIZE: usize>
385 From<Style<T, B, L, R, H, V, HSIZE, VSIZE>> for Theme
386{
387 fn from(style: Style<T, B, L, R, H, V, HSIZE, VSIZE>) -> Self {
388 Self::from_style(style)
389 }
390}
391
392impl From<ColoredConfig> for Theme {
393 fn from(cfg: ColoredConfig) -> Self {
394 let borders = *cfg.get_borders();
395 let colors = cfg.get_color_borders().clone().convert_into();
396 let horizontals = cfg.get_horizontal_lines().into_iter().collect();
397 let verticals = cfg.get_vertical_lines().into_iter().collect();
398
399 Self::gen(borders, colors, Some(horizontals), Some(verticals), None)
400 }
401}
402
403macro_rules! func_set_chars {
404 ($name:ident, $arg:ident, $desc:expr) => {
405 #[doc = concat!("Set a border character", " ", "", $desc, "", " ", ".")]
406 pub fn $name(&mut self, c: char) {
407 self.chars.$arg = Some(c);
408 }
409 };
410}
411
412macro_rules! func_remove_chars {
413 ($name:ident, $arg:ident, $desc:expr) => {
414 #[doc = concat!("Remove a border character", " ", "", $desc, "", " ", ".")]
415 pub fn $name(&mut self) {
416 self.chars.$arg = None;
417 }
418 };
419}
420
421macro_rules! func_get_chars {
422 ($name:ident, $arg:ident, $desc:expr) => {
423 #[doc = concat!("Get a border character", " ", "", $desc, "", " ", ".")]
424 pub const fn $name(&self) -> Option<char> {
425 self.chars.$arg
426 }
427 };
428}
429
430macro_rules! func_set_colors {
431 ($name:ident, $arg:ident, $desc:expr) => {
432 #[doc = concat!("Set a border color", " ", "", $desc, "", " ", ".")]
433 pub fn $name(&mut self, color: Color) {
434 self.colors.$arg = Some(color);
435 }
436 };
437}
438
439macro_rules! func_remove_colors {
440 ($name:ident, $arg:ident, $desc:expr) => {
441 #[doc = concat!("Remove a border color", " ", "", $desc, "", " ", ".")]
442 pub fn $name(&mut self) {
443 self.colors.$arg = None;
444 }
445 };
446}
447
448macro_rules! func_get_colors {
449 ($name:ident, $arg:ident, $desc:expr) => {
450 #[doc = concat!("Get a border color", " ", "", $desc, "", " ", ".")]
451 pub fn $name(&self) -> Option<&Color> {
452 self.colors.$arg.as_ref()
453 }
454 };
455}
456
457#[rustfmt::skip]
458impl Theme {
459 func_set_chars!(set_borders_top, top, "top");
460 func_set_chars!(set_borders_bottom, bottom, "bottom");
461 func_set_chars!(set_borders_left, left, "left");
462 func_set_chars!(set_borders_right, right, "right");
463 func_set_chars!(set_borders_corner_top_left, top_left, "top left corner");
464 func_set_chars!(set_borders_corner_top_right, top_right, "top right corner");
465 func_set_chars!(set_borders_corner_bottom_left, bottom_left, "bottom left corner");
466 func_set_chars!(set_borders_corner_bottom_right, bottom_right, "bottom right corner");
467 func_set_chars!(set_borders_intersection_top, top_intersection, "top intersection with a vertical line");
468 func_set_chars!(set_borders_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line");
469 func_set_chars!(set_borders_intersection_left, left_intersection, "left intersection with a horizontal line");
470 func_set_chars!(set_borders_intersection_right, right_intersection, "right intersection with a horizontal line");
471 func_set_chars!(set_borders_intersection, intersection, "intersection of horizontal and vertical line");
472 func_set_chars!(set_borders_horizontal, horizontal, "horizontal");
473 func_set_chars!(set_borders_vertical, vertical, "vertical");
474}
475
476#[rustfmt::skip]
477impl Theme {
478 func_get_chars!(get_borders_top, top, "top");
479 func_get_chars!(get_borders_bottom, bottom, "bottom");
480 func_get_chars!(get_borders_left, left, "left");
481 func_get_chars!(get_borders_right, right, "right");
482 func_get_chars!(get_borders_corner_top_left, top_left, "top left corner");
483 func_get_chars!(get_borders_corner_top_right, top_right, "top right corner");
484 func_get_chars!(get_borders_corner_bottom_left, bottom_left, "bottom left corner");
485 func_get_chars!(get_borders_corner_bottom_right, bottom_right, "bottom right corner");
486 func_get_chars!(get_borders_intersection_top, top_intersection, "top intersection with a vertical line");
487 func_get_chars!(get_borders_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line");
488 func_get_chars!(get_borders_intersection_left, left_intersection, "left intersection with a horizontal line");
489 func_get_chars!(get_borders_intersection_right, right_intersection, "right intersection with a horizontal line");
490 func_get_chars!(get_borders_intersection, intersection, "intersection of horizontal and vertical line");
491 func_get_chars!(get_borders_horizontal, horizontal, "horizontal");
492 func_get_chars!(get_borders_vertical, vertical, "vertical");
493}
494
495#[rustfmt::skip]
496impl Theme {
497 func_remove_chars!(remove_borders_top, top, "top");
498 func_remove_chars!(remove_borders_bottom, bottom, "bottom");
499 func_remove_chars!(remove_borders_left, left, "left");
500 func_remove_chars!(remove_borders_right, right, "right");
501 func_remove_chars!(remove_borders_corner_top_left, top_left, "top left corner");
502 func_remove_chars!(remove_borders_corner_top_right, top_right, "top right corner");
503 func_remove_chars!(remove_borders_corner_bottom_left, bottom_left, "bottom left corner");
504 func_remove_chars!(remove_borders_corner_bottom_right, bottom_right, "bottom right corner");
505 func_remove_chars!(remove_borders_intersection_top, top_intersection, "top intersection with a vertical line");
506 func_remove_chars!(remove_borders_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line");
507 func_remove_chars!(remove_borders_intersection_left, left_intersection, "left intersection with a horizontal line");
508 func_remove_chars!(remove_borders_intersection_right, right_intersection, "right intersection with a horizontal line");
509 func_remove_chars!(remove_borders_intersection, intersection, "intersection of horizontal and vertical line");
510 func_remove_chars!(remove_borders_horizontal, horizontal, "horizontal");
511 func_remove_chars!(remove_borders_vertical, vertical, "vertical");
512}
513
514#[rustfmt::skip]
515impl Theme {
516 func_set_colors!(set_colors_top, top, "top");
517 func_set_colors!(set_colors_bottom, bottom, "bottom");
518 func_set_colors!(set_colors_left, left, "left");
519 func_set_colors!(set_colors_right, right, "right");
520 func_set_colors!(set_colors_corner_top_left, top_left, "top left corner");
521 func_set_colors!(set_colors_corner_top_right, top_right, "top right corner");
522 func_set_colors!(set_colors_corner_bottom_left, bottom_left, "bottom left corner");
523 func_set_colors!(set_colors_corner_bottom_right, bottom_right, "bottom right corner");
524 func_set_colors!(set_colors_intersection_top, top_intersection, "top intersection with a vertical line");
525 func_set_colors!(set_colors_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line");
526 func_set_colors!(set_colors_intersection_left, left_intersection, "left intersection with a horizontal line");
527 func_set_colors!(set_colors_intersection_right, right_intersection, "right intersection with a horizontal line");
528 func_set_colors!(set_colors_intersection, intersection, "intersection of horizontal and vertical line");
529 func_set_colors!(set_colors_horizontal, horizontal, "horizontal");
530 func_set_colors!(set_colors_vertical, vertical, "vertical");
531}
532
533#[rustfmt::skip]
534impl Theme {
535 func_remove_colors!(remove_colors_top, top, "top");
536 func_remove_colors!(remove_colors_bottom, bottom, "bottom");
537 func_remove_colors!(remove_colors_left, left, "left");
538 func_remove_colors!(remove_colors_right, right, "right");
539 func_remove_colors!(remove_colors_corner_top_left, top_left, "top left corner");
540 func_remove_colors!(remove_colors_corner_top_right, top_right, "top right corner");
541 func_remove_colors!(remove_colors_corner_bottom_left, bottom_left, "bottom left corner");
542 func_remove_colors!(remove_colors_corner_bottom_right, bottom_right, "bottom right corner");
543 func_remove_colors!(remove_colors_intersection_top, top_intersection, "top intersection with a vertical line");
544 func_remove_colors!(remove_colors_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line");
545 func_remove_colors!(remove_colors_intersection_left, left_intersection, "left intersection with a horizontal line");
546 func_remove_colors!(remove_colors_intersection_right, right_intersection, "right intersection with a horizontal line");
547 func_remove_colors!(remove_colors_intersection, intersection, "intersection of horizontal and vertical line");
548 func_remove_colors!(remove_colors_horizontal, horizontal, "horizontal");
549 func_remove_colors!(remove_colors_vertical, vertical, "vertical");
550}
551
552#[rustfmt::skip]
553impl Theme {
554 func_get_colors!(get_colors_top, top, "top");
555 func_get_colors!(get_colors_bottom, bottom, "bottom");
556 func_get_colors!(get_colors_left, left, "left");
557 func_get_colors!(get_colors_right, right, "right");
558 func_get_colors!(get_colors_corner_top_left, top_left, "top left corner");
559 func_get_colors!(get_colors_corner_top_right, top_right, "top right corner");
560 func_get_colors!(get_colors_corner_bottom_left, bottom_left, "bottom left corner");
561 func_get_colors!(get_colors_corner_bottom_right, bottom_right, "bottom right corner");
562 func_get_colors!(get_colors_intersection_top, top_intersection, "top intersection with a vertical line");
563 func_get_colors!(get_colors_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line");
564 func_get_colors!(get_colors_intersection_left, left_intersection, "left intersection with a horizontal line");
565 func_get_colors!(get_colors_intersection_right, right_intersection, "right intersection with a horizontal line");
566 func_get_colors!(get_colors_intersection, intersection, "intersection of horizontal and vertical line");
567 func_get_colors!(get_colors_horizontal, horizontal, "horizontal");
568 func_get_colors!(get_colors_vertical, vertical, "vertical");
569}
570
571fn cfg_clear_borders(cfg: &mut ColoredConfig) {
572 cfg.remove_borders();
573 cfg.remove_borders_colors();
574 cfg.remove_vertical_chars();
575 cfg.remove_horizontal_chars();
576 cfg.remove_color_line_horizontal();
577 cfg.remove_color_line_vertical();
578}
579
580fn cfg_set_borders(cfg: &mut ColoredConfig, borders: Borders<char>, colors: Borders<Color>) {
581 cfg.set_borders(borders);
582
583 if !colors.is_empty() {
584 cfg.set_borders_color(colors.convert_into());
585 }
586}
587
588fn cfg_set_custom_lines(
589 cfg: &mut ColoredConfig,
590 horizontals: Option<HashMap<usize, HorizontalLine<char>>>,
591 verticals: Option<HashMap<usize, VerticalLine<char>>>,
592 horizontal1: Option<HorizontalLine<char>>,
593) {
594 if let Some(line) = horizontal1 {
595 cfg.insert_horizontal_line(1, line);
596 }
597
598 if let Some(horizontals) = horizontals {
599 for (row, line) in horizontals {
600 cfg.insert_horizontal_line(row, line);
601 }
602 }
603
604 if let Some(verticals) = verticals {
605 for (col, line) in verticals {
606 cfg.insert_vertical_line(col, line);
607 }
608 }
609}
610
611const fn hlines_find<const N: usize>(
612 lines: [(usize, HorizontalLine<char>); N],
613 search: usize,
614) -> Option<HorizontalLine<char>> {
615 let mut line = None;
616
617 let mut i = 0;
618 while i < lines.len() {
619 let (num, hline) = lines[i];
620 if num == search {
621 line = Some(hline);
622 }
623
624 i += 1;
625 }
626
627 line
628}