1mod alignment;
2mod border;
3mod borders;
4mod entity;
5mod entity_map;
6mod formatting;
7mod offset;
8mod sides;
9
10use std::collections::HashMap;
11
12pub use self::{
13 alignment::{AlignmentHorizontal, AlignmentVertical},
14 border::Border,
15 borders::{Borders, HorizontalLine, VerticalLine},
16 entity::{Entity, EntityIterator, Position},
17 formatting::Formatting,
18 offset::Offset,
19 sides::{Indent, Sides},
20};
21
22#[cfg(feature = "color")]
23use crate::AnsiColor;
24
25use self::{borders::BordersConfig, entity_map::EntityMap};
26
27#[derive(Debug, Clone)]
31pub struct GridConfig {
32 tab_width: usize,
33 margin: Margin,
34 margin_offset: Sides<Offset>,
35 padding: EntityMap<Padding>,
36 alignment_h: EntityMap<AlignmentHorizontal>,
37 alignment_v: EntityMap<AlignmentVertical>,
38 formatting: EntityMap<Formatting>,
39 span_columns: HashMap<Position, usize>,
40 span_rows: HashMap<Position, usize>,
41 borders: BordersConfig<char>,
42 borders_missing_char: char,
43 override_horizontal_lines: HashMap<usize, (String, Offset)>,
44 override_horizontal_borders: HashMap<Position, HashMap<Offset, char>>,
45 override_vertical_borders: HashMap<Position, HashMap<Offset, char>>,
46 #[cfg(feature = "color")]
47 margin_color: MarginColor<'static>,
48 #[cfg(feature = "color")]
49 padding_color: EntityMap<PaddingColor<'static>>,
50 #[cfg(feature = "color")]
51 border_colors: BordersConfig<AnsiColor<'static>>,
52}
53
54impl Default for GridConfig {
55 fn default() -> Self {
56 let margin_offset = Sides::new(
57 Offset::Begin(0),
58 Offset::Begin(0),
59 Offset::Begin(0),
60 Offset::Begin(0),
61 );
62
63 Self {
64 tab_width: 4,
65 margin: Margin::default(),
66 margin_offset,
67 padding: EntityMap::default(),
68 formatting: EntityMap::default(),
69 alignment_h: EntityMap::new(AlignmentHorizontal::Left),
70 alignment_v: EntityMap::new(AlignmentVertical::Top),
71 borders: BordersConfig::default(),
72 borders_missing_char: ' ',
73 span_columns: HashMap::default(),
74 span_rows: HashMap::default(),
75 override_horizontal_lines: HashMap::default(),
76 override_horizontal_borders: HashMap::default(),
77 override_vertical_borders: HashMap::default(),
78 #[cfg(feature = "color")]
79 margin_color: MarginColor::default(),
80 #[cfg(feature = "color")]
81 padding_color: EntityMap::default(),
82 #[cfg(feature = "color")]
83 border_colors: BordersConfig::default(),
84 }
85 }
86}
87
88impl GridConfig {
89 pub fn set_column_span(&mut self, pos: Position, span: usize) {
91 set_cell_column_span(self, pos, span);
92 }
93
94 pub fn get_column_span(&self, pos: Position, shape: (usize, usize)) -> Option<usize> {
96 match self.span_columns.get(&pos) {
97 Some(&span) if is_column_span_valid(pos, span, shape) => Some(span),
98 _ => None,
99 }
100 }
101
102 pub fn has_column_spans(&self) -> bool {
104 !self.span_columns.is_empty()
105 }
106
107 pub fn iter_column_spans(
109 &self,
110 shape: (usize, usize),
111 ) -> impl Iterator<Item = (Position, usize)> + '_ {
112 self.span_columns
113 .iter()
114 .map(|(&pos, &span)| (pos, span))
115 .filter(move |&(pos, span)| is_column_span_valid(pos, span, shape))
116 }
117
118 pub fn set_row_span(&mut self, pos: Position, span: usize) {
120 set_cell_row_span(self, pos, span);
121 }
122
123 pub fn get_row_span(&self, pos: Position, shape: (usize, usize)) -> Option<usize> {
125 match self.span_rows.get(&pos) {
126 Some(&span) if is_row_span_valid(pos, span, shape) => Some(span),
127 _ => None,
128 }
129 }
130
131 pub fn has_row_spans(&self) -> bool {
133 !self.span_rows.is_empty()
134 }
135
136 pub fn iter_row_spans(
138 &self,
139 shape: (usize, usize),
140 ) -> impl Iterator<Item = (Position, usize)> + '_ {
141 self.span_rows
142 .iter()
143 .map(|(&pos, &span)| (pos, span))
144 .filter(move |&(pos, span)| is_row_span_valid(pos, span, shape))
145 }
146
147 pub fn set_margin(&mut self, margin: Margin) {
149 self.margin = margin;
150 }
151
152 pub fn get_margin(&self) -> &Margin {
154 &self.margin
155 }
156
157 pub fn set_margin_offset(&mut self, margin: Sides<Offset>) {
159 self.margin_offset = margin;
160 }
161
162 pub fn get_margin_offset(&self) -> &Sides<Offset> {
164 &self.margin_offset
165 }
166
167 pub fn clear_theme(&mut self) {
170 self.borders = BordersConfig::default();
171 self.override_horizontal_lines.clear();
172 self.override_horizontal_borders.clear();
173 self.override_vertical_borders.clear();
174 }
175
176 pub fn set_borders(&mut self, borders: Borders<char>) {
178 self.borders.set_borders(borders);
179 }
180
181 pub fn get_global_border(&self) -> Option<&char> {
183 self.borders.get_global()
184 }
185
186 pub fn set_global_border(&mut self, c: char) {
188 self.borders.set_global(c);
189 }
190
191 pub fn set_tab_width(&mut self, width: usize) {
193 self.tab_width = width;
194 }
195
196 pub fn get_tab_width(&self) -> usize {
198 self.tab_width
199 }
200
201 pub fn get_borders(&self) -> &Borders<char> {
203 self.borders.get_borders()
204 }
205
206 pub fn set_horizontal_line(&mut self, row: usize, line: HorizontalLine<char>) {
211 self.borders.insert_horizontal_line(row, line);
212 }
213
214 pub fn remove_horizontal_line(&mut self, row: usize) {
219 self.borders.remove_horizontal_line(row);
220 }
221
222 pub fn get_vertical_line(&self, row: usize) -> Option<&VerticalLine<char>> {
227 self.borders.get_vertical_line(row)
228 }
229
230 pub fn set_vertical_line(&mut self, row: usize, line: VerticalLine<char>) {
235 self.borders.insert_vertical_line(row, line);
236 }
237
238 pub fn remove_vertical_line(&mut self, row: usize) {
243 self.borders.remove_vertical_line(row);
244 }
245
246 pub fn get_horizontal_line(&self, row: usize) -> Option<&HorizontalLine<char>> {
251 self.borders.get_horizontal_line(row)
252 }
253
254 pub fn override_split_line(&mut self, row: usize, line: impl Into<String>, offset: Offset) {
258 self.override_horizontal_lines
259 .insert(row, (line.into(), offset));
260 }
261
262 pub fn get_split_line_text(&self, row: usize) -> Option<&str> {
264 self.override_horizontal_lines
265 .get(&row)
266 .map(|(s, _)| s.as_str())
267 }
268
269 pub fn get_split_line_offset(&self, row: usize) -> Option<Offset> {
271 self.override_horizontal_lines
272 .get(&row)
273 .map(|(_, offset)| offset)
274 .copied()
275 }
276
277 pub fn remove_split_line_text(&mut self, row: usize) -> Option<(String, Offset)> {
279 self.override_horizontal_lines.remove(&row)
280 }
281
282 pub fn override_horizontal_border(&mut self, pos: Position, c: char, offset: Offset) {
286 let chars = self
287 .override_horizontal_borders
288 .entry(pos)
289 .or_insert_with(|| HashMap::with_capacity(1));
290
291 chars.insert(offset, c);
292 }
293
294 pub fn lookup_overidden_horizontal(
296 &self,
297 pos: Position,
298 offset: usize,
299 end: usize,
300 ) -> Option<char> {
301 self.override_horizontal_borders
302 .get(&pos)
303 .and_then(|chars| {
304 chars.get(&Offset::Begin(offset)).or_else(|| {
305 if end > offset {
306 if end == 0 {
307 chars.get(&Offset::End(0))
308 } else {
309 chars.get(&Offset::End(end - offset - 1))
310 }
311 } else {
312 None
313 }
314 })
315 })
316 .copied()
317 }
318
319 pub fn is_overidden_horizontal(&self, pos: Position) -> bool {
321 self.override_horizontal_borders.get(&pos).is_some()
322 }
323
324 pub fn remove_overidden_horizontal(&mut self, pos: Position) {
326 self.override_horizontal_borders.remove(&pos);
327 }
328
329 pub fn override_vertical_border(&mut self, pos: Position, c: char, offset: Offset) {
333 let chars = self
334 .override_vertical_borders
335 .entry(pos)
336 .or_insert_with(|| HashMap::with_capacity(1));
337
338 chars.insert(offset, c);
339 }
340
341 pub fn lookup_overidden_vertical(
343 &self,
344 pos: Position,
345 offset: usize,
346 end: usize,
347 ) -> Option<char> {
348 self.override_vertical_borders
349 .get(&pos)
350 .and_then(|chars| {
351 chars.get(&Offset::Begin(offset)).or_else(|| {
352 if end > offset {
353 if end == 0 {
354 chars.get(&Offset::End(0))
355 } else {
356 chars.get(&Offset::End(end - offset - 1))
357 }
358 } else {
359 None
360 }
361 })
362 })
363 .copied()
364 }
365
366 pub fn is_overidden_vertical(&self, pos: Position) -> bool {
368 self.override_vertical_borders.get(&pos).is_some()
369 }
370
371 pub fn remove_overidden_vertical(&mut self, pos: Position) {
373 self.override_vertical_borders.remove(&pos);
374 }
375
376 pub fn set_padding(&mut self, entity: Entity, padding: Padding) {
378 self.padding.set(entity, padding);
379 }
380
381 pub fn get_padding(&self, entity: Entity) -> &Padding {
383 self.padding.lookup(entity)
384 }
385
386 pub fn set_formatting(&mut self, entity: Entity, formatting: Formatting) {
388 self.formatting.set(entity, formatting);
389 }
390
391 pub fn get_formatting(&self, entity: Entity) -> &Formatting {
393 self.formatting.lookup(entity)
394 }
395
396 pub fn set_alignment_vertical(&mut self, entity: Entity, alignment: AlignmentVertical) {
398 self.alignment_v.set(entity, alignment);
399 }
400
401 pub fn get_alignment_vertical(&self, entity: Entity) -> &AlignmentVertical {
403 self.alignment_v.lookup(entity)
404 }
405
406 pub fn set_alignment_horizontal(&mut self, entity: Entity, alignment: AlignmentHorizontal) {
408 self.alignment_h.set(entity, alignment);
409 }
410
411 pub fn get_alignment_horizontal(&self, entity: Entity) -> &AlignmentHorizontal {
413 self.alignment_h.lookup(entity)
414 }
415
416 pub fn is_cell_visible(&self, pos: Position, shape: (usize, usize)) -> bool {
418 !(self.is_cell_covered_by_column_span(pos, shape)
419 || self.is_cell_covered_by_row_span(pos, shape)
420 || self.is_cell_covered_by_both_spans(pos, shape))
421 }
422
423 pub fn is_cell_covered_by_row_span(&self, pos: Position, shape: (usize, usize)) -> bool {
425 is_cell_covered_by_row_span(self, pos, shape)
426 }
427
428 pub fn is_cell_covered_by_column_span(&self, pos: Position, shape: (usize, usize)) -> bool {
430 is_cell_covered_by_column_span(self, pos, shape)
431 }
432
433 pub fn is_cell_covered_by_both_spans(&self, pos: Position, shape: (usize, usize)) -> bool {
435 is_cell_covered_by_both_spans(self, pos, shape)
436 }
437
438 pub fn has_vertical(&self, col: usize, count_columns: usize) -> bool {
444 self.borders.has_vertical(col, count_columns)
445 }
446
447 pub fn has_horizontal(&self, row: usize, count_rows: usize) -> bool {
451 self.borders.has_horizontal(row, count_rows)
452 }
453
454 pub fn set_border(&mut self, pos: Position, border: Border) {
456 self.borders.insert_border(pos, border);
457 }
458
459 pub fn remove_border(&mut self, pos: Position, shape: (usize, usize)) {
463 self.borders.remove_border(pos, shape);
464 }
465
466 pub fn set_borders_missing(&mut self, c: char) {
470 self.borders_missing_char = c;
471 }
472
473 pub fn count_vertical(&self, count_columns: usize) -> usize {
477 (0..=count_columns)
478 .filter(|&col| self.has_vertical(col, count_columns))
479 .count()
480 }
481
482 pub fn count_horizontal(&self, count_rows: usize) -> usize {
486 (0..=count_rows)
487 .filter(|&row| self.has_horizontal(row, count_rows))
488 .count()
489 }
490
491 pub fn get_border(&self, pos: Position, shape: (usize, usize)) -> Border<char> {
493 self.borders.get_border(pos, shape.0, shape.1).copied()
494 }
495
496 pub fn get_vertical(&self, pos: Position, count_columns: usize) -> Option<&char> {
500 let c = self.borders.get_vertical(pos, count_columns);
501 if c.is_some() {
502 return c;
503 }
504
505 if self.has_vertical(pos.1, count_columns) {
506 return Some(&self.borders_missing_char);
507 }
508
509 None
510 }
511
512 pub fn get_horizontal(&self, pos: Position, count_rows: usize) -> Option<&char> {
516 let c = self.borders.get_horizontal(pos, count_rows);
517 if c.is_some() {
518 return c;
519 }
520
521 if self.has_horizontal(pos.0, count_rows) {
522 return Some(&self.borders_missing_char);
523 }
524
525 None
526 }
527
528 pub fn get_intersection(&self, pos: Position, shape: (usize, usize)) -> Option<&char> {
532 let c = self.borders.get_intersection(pos, shape.0, shape.1);
533 if c.is_some() {
534 return c;
535 }
536
537 if self.has_horizontal(pos.0, shape.0) && self.has_vertical(pos.1, shape.1) {
538 return Some(&self.borders_missing_char);
539 }
540
541 None
542 }
543}
544
545#[cfg(feature = "color")]
546impl GridConfig {
547 pub fn get_border_color_global(&self) -> Option<&AnsiColor<'_>> {
549 self.border_colors.get_global()
550 }
551
552 pub fn set_border_color_global(&mut self, clr: AnsiColor<'static>) {
554 self.border_colors = BordersConfig::default();
555 self.border_colors.set_global(clr);
556 }
557
558 pub fn get_color_borders(&self) -> &Borders<AnsiColor<'_>> {
560 self.border_colors.get_borders()
561 }
562
563 pub fn set_borders_color(&mut self, clrs: Borders<AnsiColor<'static>>) {
565 self.border_colors.set_borders(clrs);
566 }
567
568 pub fn set_border_color(&mut self, pos: Position, border: Border<AnsiColor<'static>>) {
570 self.border_colors.insert_border(pos, border)
571 }
572
573 pub fn get_border_color(&self, pos: Position, shape: (usize, usize)) -> Border<&AnsiColor<'_>> {
575 self.border_colors.get_border(pos, shape.0, shape.1)
576 }
577
578 pub fn remove_border_color(&mut self, pos: Position, shape: (usize, usize)) {
580 self.border_colors.remove_border(pos, shape);
581 }
582
583 pub fn get_margin_color(&self) -> &MarginColor<'_> {
585 &self.margin_color
586 }
587
588 pub fn set_margin_color(&mut self, color: MarginColor<'static>) {
590 self.margin_color = color;
591 }
592
593 pub fn get_padding_color(&self, entity: Entity) -> &PaddingColor<'_> {
595 self.padding_color.lookup(entity)
596 }
597
598 pub fn set_padding_color(&mut self, entity: Entity, color: PaddingColor<'static>) {
600 self.padding_color.set(entity, color);
601 }
602
603 pub fn get_horizontal_color(&self, pos: Position, count_rows: usize) -> Option<&AnsiColor<'_>> {
605 self.border_colors.get_horizontal(pos, count_rows)
606 }
607
608 pub fn get_vertical_color(
610 &self,
611 pos: Position,
612 count_columns: usize,
613 ) -> Option<&AnsiColor<'_>> {
614 self.border_colors.get_vertical(pos, count_columns)
615 }
616
617 pub fn get_intersection_color(
619 &self,
620 pos: Position,
621 shape: (usize, usize),
622 ) -> Option<&AnsiColor<'_>> {
623 self.border_colors.get_intersection(pos, shape.0, shape.1)
624 }
625}
626
627pub type Margin = Sides<Indent>;
629
630pub type Padding = Sides<Indent>;
632
633#[cfg(feature = "color")]
634pub type MarginColor<'a> = Sides<AnsiColor<'a>>;
636
637#[cfg(feature = "color")]
638pub type PaddingColor<'a> = Sides<AnsiColor<'a>>;
640
641fn set_cell_row_span(cfg: &mut GridConfig, (mut row, col): Position, mut span: usize) {
642 if row == 0 && span == 0 {
644 return;
645 }
646
647 if span == 1 {
650 cfg.span_rows.remove(&(row, col));
651 return;
652 }
653
654 if span == 0 && row > 0 {
655 match closest_visible_row(cfg, (row - 1, col)) {
656 Some(c) => {
657 span += 1 + row - c;
658 row = c;
659 }
660 None => return,
661 }
662 }
663
664 cfg.span_rows.insert((row, col), span);
665}
666
667fn closest_visible_row(cfg: &GridConfig, mut pos: Position) -> Option<usize> {
668 loop {
669 if cfg.is_cell_visible(pos, (std::usize::MAX, std::usize::MAX)) {
670 return Some(pos.0);
671 }
672
673 if pos.0 == 0 {
674 return None;
675 }
676
677 pos.0 -= 1;
678 }
679}
680
681fn set_cell_column_span(cfg: &mut GridConfig, (row, mut col): Position, mut span: usize) {
682 if col == 0 && span == 0 {
684 return;
685 }
686
687 if span == 1 {
690 cfg.span_columns.remove(&(row, col));
691 return;
692 }
693
694 if span == 0 && col > 0 {
695 match closest_visible_column(cfg, (row, col - 1)) {
696 Some(c) => {
697 span += 1 + col - c;
698 col = c;
699 }
700 None => return,
701 }
702 }
703
704 cfg.span_columns.insert((row, col), span);
705}
706
707fn closest_visible_column(cfg: &GridConfig, mut pos: Position) -> Option<usize> {
708 loop {
709 if cfg.is_cell_visible(pos, (std::usize::MAX, std::usize::MAX)) {
710 return Some(pos.1);
711 }
712
713 if pos.1 == 0 {
714 return None;
715 }
716
717 pos.1 -= 1;
718 }
719}
720
721fn is_cell_covered_by_column_span(cfg: &GridConfig, pos: Position, shape: (usize, usize)) -> bool {
722 if cfg.span_columns.is_empty() {
723 return false;
724 }
725
726 cfg.span_columns
727 .iter()
728 .filter(|(&pos, &span)| is_column_span_valid(pos, span, shape))
729 .any(|(&(row, col), span)| pos.1 > col && pos.1 < col + span && row == pos.0)
730}
731
732fn is_cell_covered_by_row_span(cfg: &GridConfig, pos: Position, shape: (usize, usize)) -> bool {
733 if cfg.span_rows.is_empty() {
734 return false;
735 }
736
737 cfg.span_rows
738 .iter()
739 .filter(|(&pos, &span)| is_row_span_valid(pos, span, shape))
740 .any(|(&(row, col), span)| pos.0 > row && pos.0 < row + span && col == pos.1)
741}
742
743fn is_cell_covered_by_both_spans(cfg: &GridConfig, pos: Position, shape: (usize, usize)) -> bool {
744 if cfg.span_rows.is_empty() || cfg.span_columns.is_empty() {
745 return false;
746 }
747
748 cfg.span_rows
749 .iter()
750 .filter(|(&pos, &span)| is_row_span_valid(pos, span, shape))
751 .any(|(p1, row_span)| {
752 cfg.span_columns
753 .iter()
754 .filter(|(&pos, &span)| is_column_span_valid(pos, span, shape))
755 .filter(|(p2, _)| &p1 == p2)
756 .any(|(_, col_span)| {
757 pos.0 > p1.0
758 && pos.0 < p1.0 + row_span
759 && pos.1 > p1.1
760 && pos.1 < p1.1 + col_span
761 })
762 })
763}
764
765fn is_column_span_valid(
766 pos: Position,
767 span: usize,
768 (count_rows, count_cols): (usize, usize),
769) -> bool {
770 let pos_correct = pos.1 < count_cols && pos.0 < count_rows;
772 let span_correct = span + pos.1 <= count_cols;
774
775 pos_correct && span_correct
776}
777
778fn is_row_span_valid(pos: Position, span: usize, (count_rows, count_cols): (usize, usize)) -> bool {
779 let pos_correct = pos.1 < count_cols && pos.0 < count_rows;
781 let span_correct = span + pos.0 <= count_rows;
783
784 pos_correct && span_correct
785}