papergrid/config/
borders.rs

1use std::collections::{HashMap, HashSet};
2
3use super::{Border, Position};
4
5#[derive(Debug, Clone, Default)]
6pub(crate) struct BordersConfig<T> {
7    global: Option<T>,
8    borders: Borders<T>,
9    cells: BordersMap<T>,
10    horizontals: HashMap<usize, HorizontalLine<T>>,
11    verticals: HashMap<usize, VerticalLine<T>>,
12    layout: BordersLayout,
13}
14
15impl<T: std::fmt::Debug> BordersConfig<T> {
16    pub(crate) fn insert_border(&mut self, pos: Position, border: Border<T>) {
17        if let Some(c) = border.top {
18            self.cells.horizontal.insert(pos, c);
19            self.layout.horizontals.insert(pos.0);
20        }
21
22        if let Some(c) = border.bottom {
23            self.cells.horizontal.insert((pos.0 + 1, pos.1), c);
24            self.layout.horizontals.insert(pos.0 + 1);
25        }
26
27        if let Some(c) = border.left {
28            self.cells.vertical.insert(pos, c);
29            self.layout.verticals.insert(pos.1);
30        }
31
32        if let Some(c) = border.right {
33            self.cells.vertical.insert((pos.0, pos.1 + 1), c);
34            self.layout.verticals.insert(pos.1 + 1);
35        }
36
37        if let Some(c) = border.left_top_corner {
38            self.cells.intersection.insert((pos.0, pos.1), c);
39            self.layout.horizontals.insert(pos.0);
40            self.layout.verticals.insert(pos.1);
41        }
42
43        if let Some(c) = border.right_top_corner {
44            self.cells.intersection.insert((pos.0, pos.1 + 1), c);
45            self.layout.horizontals.insert(pos.0);
46            self.layout.verticals.insert(pos.1 + 1);
47        }
48
49        if let Some(c) = border.left_bottom_corner {
50            self.cells.intersection.insert((pos.0 + 1, pos.1), c);
51            self.layout.horizontals.insert(pos.0 + 1);
52            self.layout.verticals.insert(pos.1);
53        }
54
55        if let Some(c) = border.right_bottom_corner {
56            self.cells.intersection.insert((pos.0 + 1, pos.1 + 1), c);
57            self.layout.horizontals.insert(pos.0 + 1);
58            self.layout.verticals.insert(pos.1 + 1);
59        }
60    }
61
62    pub(crate) fn remove_border(&mut self, pos: Position, shape: (usize, usize)) {
63        let (count_rows, count_cols) = shape;
64
65        self.cells.horizontal.remove(&pos);
66        self.cells.horizontal.remove(&(pos.0 + 1, pos.1));
67        self.cells.vertical.remove(&pos);
68        self.cells.vertical.remove(&(pos.0, pos.1 + 1));
69        self.cells.intersection.remove(&pos);
70        self.cells.intersection.remove(&(pos.0 + 1, pos.1));
71        self.cells.intersection.remove(&(pos.0, pos.1 + 1));
72        self.cells.intersection.remove(&(pos.0 + 1, pos.1 + 1));
73
74        // clean up the layout.
75
76        if !self.check_is_horizontal_set(pos.0, count_rows) {
77            self.layout.horizontals.remove(&pos.0);
78        }
79
80        if !self.check_is_horizontal_set(pos.0 + 1, count_rows) {
81            self.layout.horizontals.remove(&(pos.0 + 1));
82        }
83
84        if !self.check_is_vertical_set(pos.1, count_cols) {
85            self.layout.verticals.remove(&pos.1);
86        }
87
88        if !self.check_is_vertical_set(pos.1 + 1, count_cols) {
89            self.layout.verticals.remove(&(pos.1 + 1));
90        }
91    }
92
93    pub(crate) fn get_border(
94        &self,
95        pos: Position,
96        count_rows: usize,
97        count_cols: usize,
98    ) -> Border<&T> {
99        Border {
100            top: self.get_horizontal(pos, count_rows),
101            bottom: self.get_horizontal((pos.0 + 1, pos.1), count_rows),
102            left: self.get_vertical(pos, count_cols),
103            left_top_corner: self.get_intersection(pos, count_rows, count_cols),
104            left_bottom_corner: self.get_intersection((pos.0 + 1, pos.1), count_rows, count_cols),
105            right: self.get_vertical((pos.0, pos.1 + 1), count_cols),
106            right_top_corner: self.get_intersection((pos.0, pos.1 + 1), count_rows, count_cols),
107            right_bottom_corner: self.get_intersection(
108                (pos.0 + 1, pos.1 + 1),
109                count_rows,
110                count_cols,
111            ),
112        }
113    }
114
115    pub(crate) fn insert_horizontal_line(&mut self, row: usize, line: HorizontalLine<T>) {
116        if line.left.is_some() {
117            self.layout.left = true;
118        }
119
120        if line.right.is_some() {
121            self.layout.right = true;
122        }
123
124        if line.intersection.is_some() {
125            self.layout.inner_verticals = true;
126        }
127
128        self.horizontals.insert(row, line);
129        self.layout.horizontals.insert(row);
130    }
131
132    pub(crate) fn get_horizontal_line(&self, row: usize) -> Option<&HorizontalLine<T>> {
133        self.horizontals.get(&row)
134    }
135
136    pub(crate) fn remove_horizontal_line(&mut self, row: usize) {
137        self.horizontals.remove(&row);
138    }
139
140    pub(crate) fn insert_vertical_line(&mut self, row: usize, line: VerticalLine<T>) {
141        if line.top.is_some() {
142            self.layout.top = true;
143        }
144
145        if line.bottom.is_some() {
146            self.layout.bottom = true;
147        }
148
149        self.verticals.insert(row, line);
150        self.layout.verticals.insert(row);
151    }
152
153    pub(crate) fn get_vertical_line(&self, row: usize) -> Option<&VerticalLine<T>> {
154        self.verticals.get(&row)
155    }
156
157    pub(crate) fn remove_vertical_line(&mut self, row: usize) {
158        self.verticals.remove(&row);
159    }
160
161    pub(crate) fn set_borders(&mut self, borders: Borders<T>) {
162        self.borders = borders;
163    }
164
165    pub(crate) fn get_borders(&self) -> &Borders<T> {
166        &self.borders
167    }
168
169    pub(crate) fn get_global(&self) -> Option<&T> {
170        self.global.as_ref()
171    }
172
173    pub(crate) fn set_global(&mut self, value: T) {
174        self.global = Some(value);
175    }
176
177    pub(crate) fn get_vertical(&self, pos: Position, count_cols: usize) -> Option<&T> {
178        self.cells
179            .vertical
180            .get(&pos)
181            .or_else(|| self.verticals.get(&pos.1).and_then(|l| l.main.as_ref()))
182            .or({
183                if pos.1 == count_cols {
184                    self.borders.vertical_right.as_ref()
185                } else if pos.1 == 0 {
186                    self.borders.vertical_left.as_ref()
187                } else {
188                    self.borders.vertical.as_ref()
189                }
190            })
191            .or(self.global.as_ref())
192    }
193
194    pub(crate) fn get_horizontal(&self, pos: Position, count_rows: usize) -> Option<&T> {
195        self.cells
196            .horizontal
197            .get(&pos)
198            .or_else(|| self.horizontals.get(&pos.0).and_then(|l| l.main.as_ref()))
199            .or({
200                if pos.0 == 0 {
201                    self.borders.top.as_ref()
202                } else if pos.0 == count_rows {
203                    self.borders.bottom.as_ref()
204                } else {
205                    self.borders.horizontal.as_ref()
206                }
207            })
208            .or(self.global.as_ref())
209    }
210
211    pub(crate) fn get_intersection(
212        &self,
213        pos: Position,
214        count_rows: usize,
215        count_cols: usize,
216    ) -> Option<&T> {
217        let use_top = pos.0 == 0;
218        let use_bottom = pos.0 == count_rows;
219        let use_left = pos.1 == 0;
220        let use_right = pos.1 == count_cols;
221
222        if let Some(c) = self.cells.intersection.get(&pos) {
223            return Some(c);
224        }
225
226        let hl_c = self.horizontals.get(&pos.0).and_then(|l| {
227            if use_left && l.left.is_some() {
228                l.left.as_ref()
229            } else if use_right && l.right.is_some() {
230                l.right.as_ref()
231            } else if !use_right && !use_left && l.intersection.is_some() {
232                l.intersection.as_ref()
233            } else {
234                None
235            }
236        });
237
238        if let Some(c) = hl_c {
239            return Some(c);
240        }
241
242        let vl_c = self.verticals.get(&pos.1).and_then(|l| {
243            if use_top && l.top.is_some() {
244                l.top.as_ref()
245            } else if use_bottom && l.bottom.is_some() {
246                l.bottom.as_ref()
247            } else if !use_top && !use_bottom && l.intersection.is_some() {
248                l.intersection.as_ref()
249            } else {
250                None
251            }
252        });
253
254        if let Some(c) = vl_c {
255            return Some(c);
256        }
257
258        let borders_c = {
259            if use_top && use_left {
260                self.borders.top_left.as_ref()
261            } else if use_top && use_right {
262                self.borders.top_right.as_ref()
263            } else if use_bottom && use_left {
264                self.borders.bottom_left.as_ref()
265            } else if use_bottom && use_right {
266                self.borders.bottom_right.as_ref()
267            } else if use_top {
268                self.borders.top_intersection.as_ref()
269            } else if use_bottom {
270                self.borders.bottom_intersection.as_ref()
271            } else if use_left {
272                self.borders.horizontal_left.as_ref()
273            } else if use_right {
274                self.borders.horizontal_right.as_ref()
275            } else {
276                self.borders.intersection.as_ref()
277            }
278        };
279
280        if let Some(c) = borders_c {
281            return Some(c);
282        }
283
284        self.global.as_ref()
285    }
286
287    pub(crate) fn has_horizontal(&self, row: usize, count_rows: usize) -> bool {
288        self.global.is_some()
289            || (row == 0 && self.borders.has_top())
290            || (row == count_rows && self.borders.has_bottom())
291            || (row > 0 && row < count_rows && self.borders.has_horizontal())
292            || self.is_horizontal_set(row, count_rows)
293    }
294
295    pub(crate) fn has_vertical(&self, col: usize, count_cols: usize) -> bool {
296        self.global.is_some()
297            || (col == 0 && self.borders.has_left())
298            || (col == count_cols && self.borders.has_right())
299            || (col > 0 && col < count_cols && self.borders.has_vertical())
300            || self.is_vertical_set(col, count_cols)
301    }
302
303    fn is_horizontal_set(&self, row: usize, count_rows: usize) -> bool {
304        (row == 0 && self.layout.top)
305            || (row == count_rows && self.layout.bottom)
306            || (row > 0 && row < count_rows && self.layout.inner_horizontals)
307            || self.layout.horizontals.contains(&row)
308    }
309
310    fn is_vertical_set(&self, col: usize, count_cols: usize) -> bool {
311        (col == 0 && self.layout.left)
312            || (col == count_cols && self.layout.right)
313            || (col > 0 && col < count_cols && self.layout.inner_verticals)
314            || self.layout.verticals.contains(&col)
315    }
316
317    fn check_is_horizontal_set(&self, row: usize, count_rows: usize) -> bool {
318        (row == 0 && self.layout.top)
319            || (row == count_rows && self.layout.bottom)
320            || (row > 0 && row < count_rows && self.layout.inner_horizontals)
321            || self.cells.horizontal.keys().any(|&p| p.0 == row)
322            || self.cells.intersection.keys().any(|&p| p.0 == row)
323    }
324
325    fn check_is_vertical_set(&self, col: usize, count_cols: usize) -> bool {
326        (col == 0 && self.layout.left)
327            || (col == count_cols && self.layout.right)
328            || (col > 0 && col < count_cols && self.layout.inner_verticals)
329            || self.cells.vertical.keys().any(|&p| p.1 == col)
330            || self.cells.intersection.keys().any(|&p| p.1 == col)
331    }
332}
333
334/// Borders represents a Table frame with horizontal and vertical split lines.
335#[derive(Debug, Clone, Default)]
336pub struct Borders<T = char> {
337    /// A top horizontal on the frame.
338    pub top: Option<T>,
339    /// A top left on the frame.
340    pub top_left: Option<T>,
341    /// A top right on the frame.
342    pub top_right: Option<T>,
343    /// A top horizontal intersection on the frame.
344    pub top_intersection: Option<T>,
345
346    /// A bottom horizontal on the frame.
347    pub bottom: Option<T>,
348    /// A bottom left on the frame.
349    pub bottom_left: Option<T>,
350    /// A bottom right on the frame.
351    pub bottom_right: Option<T>,
352    /// A bottom horizontal intersection on the frame.
353    pub bottom_intersection: Option<T>,
354
355    /// A horizontal split.
356    pub horizontal: Option<T>,
357    /// A horizontal split on the left frame line.
358    pub horizontal_left: Option<T>,
359    /// A horizontal split on the right frame line.
360    pub horizontal_right: Option<T>,
361
362    /// A vertical split.
363    pub vertical: Option<T>,
364    /// A vertical split on the left frame line.
365    pub vertical_left: Option<T>,
366    /// A vertical split on the right frame line.
367    pub vertical_right: Option<T>,
368
369    /// A top left charcter on the frame.
370    pub intersection: Option<T>,
371}
372
373impl<T> Borders<T> {
374    /// Verifies if borders has left line set on the frame.
375    pub const fn has_left(&self) -> bool {
376        self.vertical_left.is_some()
377            || self.horizontal_left.is_some()
378            || self.top_left.is_some()
379            || self.bottom_left.is_some()
380    }
381
382    /// Verifies if borders has right line set on the frame.
383    pub const fn has_right(&self) -> bool {
384        self.vertical_right.is_some()
385            || self.horizontal_right.is_some()
386            || self.top_right.is_some()
387            || self.bottom_right.is_some()
388    }
389
390    /// Verifies if borders has top line set on the frame.
391    pub const fn has_top(&self) -> bool {
392        self.top.is_some()
393            || self.top_intersection.is_some()
394            || self.top_left.is_some()
395            || self.top_right.is_some()
396    }
397
398    /// Verifies if borders has bottom line set on the frame.
399    pub const fn has_bottom(&self) -> bool {
400        self.bottom.is_some()
401            || self.bottom_intersection.is_some()
402            || self.bottom_left.is_some()
403            || self.bottom_right.is_some()
404    }
405
406    /// Verifies if borders has horizontal lines set.
407    pub const fn has_horizontal(&self) -> bool {
408        self.horizontal.is_some()
409            || self.horizontal_left.is_some()
410            || self.horizontal_right.is_some()
411            || self.intersection.is_some()
412    }
413
414    /// Verifies if borders has vertical lines set.
415    pub const fn has_vertical(&self) -> bool {
416        self.intersection.is_some()
417            || self.vertical.is_some()
418            || self.top_intersection.is_some()
419            || self.bottom_intersection.is_some()
420    }
421}
422
423#[derive(Debug, Clone, Default)]
424pub(crate) struct BordersMap<T> {
425    vertical: HashMap<Position, T>,
426    horizontal: HashMap<Position, T>,
427    intersection: HashMap<Position, T>,
428}
429
430#[derive(Debug, Clone, Default)]
431pub(crate) struct BordersLayout {
432    left: bool,
433    right: bool,
434    top: bool,
435    bottom: bool,
436    inner_verticals: bool,
437    inner_horizontals: bool,
438    horizontals: HashSet<usize>,
439    verticals: HashSet<usize>,
440}
441
442/// A structre for a custom horizontal line.
443#[derive(Debug, Clone, Copy, Default)]
444pub struct HorizontalLine<T> {
445    /// Line character.
446    pub main: Option<T>,
447    /// Line intersection character.
448    pub intersection: Option<T>,
449    /// Left intersection character.
450    pub left: Option<T>,
451    /// Right intersection character.
452    pub right: Option<T>,
453}
454
455impl<T> HorizontalLine<T> {
456    /// Verifies if the line has any setting set.
457    pub const fn is_empty(&self) -> bool {
458        self.main.is_none()
459            && self.intersection.is_none()
460            && self.left.is_none()
461            && self.right.is_none()
462    }
463}
464
465/// A structre for a vertical line.
466#[derive(Debug, Clone, Copy, Default)]
467pub struct VerticalLine<T> {
468    /// Line character.
469    pub main: Option<T>,
470    /// Line intersection character.
471    pub intersection: Option<T>,
472    /// Left intersection character.
473    pub top: Option<T>,
474    /// Right intersection character.
475    pub bottom: Option<T>,
476}
477
478impl<T> VerticalLine<T> {
479    /// Verifies if the line has any setting set.
480    pub const fn is_empty(&self) -> bool {
481        self.main.is_none()
482            && self.intersection.is_none()
483            && self.top.is_none()
484            && self.bottom.is_none()
485    }
486}
487
488#[cfg(test)]
489mod tests {
490    use super::*;
491
492    #[test]
493    fn test_insert_border() {
494        let mut borders = BordersConfig::<char>::default();
495        borders.insert_border((0, 0), Border::filled('x'));
496
497        assert_eq!(borders.get_border((0, 0), 10, 10), Border::filled(&'x'));
498        assert_eq!(borders.get_border((0, 0), 0, 0), Border::filled(&'x'));
499
500        assert!(borders.is_horizontal_set(0, 10));
501        assert!(borders.is_horizontal_set(1, 10));
502        assert!(!borders.is_horizontal_set(2, 10));
503        assert!(borders.is_vertical_set(0, 10));
504        assert!(borders.is_vertical_set(1, 10));
505        assert!(!borders.is_vertical_set(2, 10));
506
507        assert!(borders.is_horizontal_set(0, 0));
508        assert!(borders.is_horizontal_set(1, 0));
509        assert!(!borders.is_horizontal_set(2, 0));
510        assert!(borders.is_vertical_set(0, 0));
511        assert!(borders.is_vertical_set(1, 0));
512        assert!(!borders.is_vertical_set(2, 0));
513    }
514
515    #[test]
516    fn test_insert_border_override() {
517        let mut borders = BordersConfig::<char>::default();
518        borders.insert_border((0, 0), Border::filled('x'));
519        borders.insert_border((1, 0), Border::filled('y'));
520        borders.insert_border((0, 1), Border::filled('w'));
521        borders.insert_border((1, 1), Border::filled('q'));
522
523        assert_eq!(
524            borders.get_border((0, 0), 10, 10).copied(),
525            Border::full('x', 'y', 'x', 'w', 'x', 'w', 'y', 'q')
526        );
527        assert_eq!(
528            borders.get_border((0, 1), 10, 10).copied(),
529            Border::full('w', 'q', 'w', 'w', 'w', 'w', 'q', 'q')
530        );
531        assert_eq!(
532            borders.get_border((1, 0), 10, 10).copied(),
533            Border::full('y', 'y', 'y', 'q', 'y', 'q', 'y', 'q')
534        );
535        assert_eq!(
536            borders.get_border((1, 1), 10, 10).copied(),
537            Border::filled('q')
538        );
539
540        assert!(borders.is_horizontal_set(0, 10));
541        assert!(borders.is_horizontal_set(1, 10));
542        assert!(borders.is_horizontal_set(2, 10));
543        assert!(!borders.is_horizontal_set(3, 10));
544        assert!(borders.is_vertical_set(0, 10));
545        assert!(borders.is_vertical_set(1, 10));
546        assert!(borders.is_vertical_set(2, 10));
547        assert!(!borders.is_vertical_set(3, 10));
548    }
549
550    #[test]
551    fn test_set_global() {
552        let mut borders = BordersConfig::<char>::default();
553        borders.insert_border((0, 0), Border::filled('x'));
554        borders.set_global('l');
555
556        assert_eq!(borders.get_border((0, 0), 10, 10), Border::filled(&'x'));
557        assert_eq!(borders.get_border((2, 0), 10, 10), Border::filled(&'l'));
558
559        assert!(borders.is_horizontal_set(0, 10));
560        assert!(borders.is_horizontal_set(1, 10));
561        assert!(!borders.is_horizontal_set(2, 10));
562        assert!(borders.is_vertical_set(0, 10));
563        assert!(borders.is_vertical_set(1, 10));
564        assert!(!borders.is_vertical_set(2, 10));
565
566        assert!(borders.is_horizontal_set(0, 0));
567        assert!(borders.is_horizontal_set(1, 0));
568        assert!(!borders.is_horizontal_set(2, 0));
569        assert!(borders.is_vertical_set(0, 0));
570        assert!(borders.is_vertical_set(1, 0));
571        assert!(!borders.is_vertical_set(2, 0));
572    }
573}