tabled/settings/panel/
vertical_panel.rs

1use crate::{
2    grid::config::{ColoredConfig, SpannedConfig},
3    grid::records::{ExactRecords, Records, RecordsMut, Resizable},
4    settings::TableOption,
5};
6
7/// A vertical/row span from 0 to a count columns.
8#[derive(Debug)]
9pub struct VerticalPanel<S> {
10    text: S,
11    col: usize,
12}
13
14impl<S> VerticalPanel<S> {
15    /// Creates a new vertical panel.
16    pub fn new(col: usize, text: S) -> Self
17    where
18        S: AsRef<str>,
19    {
20        Self { text, col }
21    }
22
23    /// Split the set text to a certain width, so it fits within it.
24    pub fn width(self, width: usize) -> VerticalPanel<String>
25    where
26        S: AsRef<str>,
27    {
28        let mut text = String::new();
29
30        if width > 0 {
31            text = split_string_by_width(self.text.as_ref(), width);
32        }
33
34        VerticalPanel {
35            text,
36            col: self.col,
37        }
38    }
39}
40
41impl<S, R, D> TableOption<R, ColoredConfig, D> for VerticalPanel<S>
42where
43    S: AsRef<str>,
44    R: Records + ExactRecords + Resizable + RecordsMut<String>,
45{
46    fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) {
47        let count_rows = records.count_rows();
48        let count_cols = records.count_columns();
49
50        if self.col > count_cols {
51            return;
52        }
53
54        let is_intersect_horizontal_span = (0..=records.count_rows())
55            .any(|row| cfg.is_cell_covered_by_column_span((row, self.col).into()));
56
57        if is_intersect_horizontal_span {
58            return;
59        }
60
61        move_columns_aside(records, self.col);
62        move_column_spans(cfg, self.col);
63
64        let text = self.text.as_ref().to_owned();
65        records.set((0, self.col).into(), text);
66
67        cfg.set_row_span((0, self.col).into(), count_rows);
68    }
69}
70
71fn move_columns_aside<R: Records + Resizable>(records: &mut R, column: usize) {
72    records.push_column();
73
74    let count_columns = records.count_columns();
75    let shift_count = count_columns - column;
76    for i in 1..shift_count {
77        let col = count_columns - i;
78        records.swap_column(col, col - 1);
79    }
80}
81
82fn move_column_spans(cfg: &mut SpannedConfig, target_column: usize) {
83    for (p, span) in cfg.get_column_spans() {
84        if p.col < target_column {
85            continue;
86        }
87
88        cfg.set_column_span(p, 1);
89        cfg.set_column_span(p + (0, 1), span);
90    }
91
92    for (p, span) in cfg.get_row_spans() {
93        if p.col < target_column {
94            continue;
95        }
96
97        cfg.set_row_span(p, 1);
98        cfg.set_row_span(p + (0, 1), span);
99    }
100}
101
102fn split_string_by_width(str: &str, width: usize) -> String {
103    if width == 0 {
104        return String::new();
105    }
106
107    let (lhs, rhs) = crate::util::string::split_str(str, width);
108    if rhs.is_empty() {
109        return lhs.into_owned();
110    }
111
112    let mut buf = lhs.into_owned();
113    let mut next = rhs.into_owned();
114    while !next.is_empty() {
115        let (lhs, rhs) = crate::util::string::split_str(&next, width);
116        buf.push('\n');
117        buf.push_str(&lhs);
118        next = rhs.into_owned();
119    }
120
121    buf
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn test_split_string_by_width() {
130        assert_eq!(split_string_by_width("123456789", 3), "123\n456\n789");
131        assert_eq!(split_string_by_width("123456789", 2), "12\n34\n56\n78\n9");
132        assert_eq!(
133            split_string_by_width("123456789", 1),
134            "1\n2\n3\n4\n5\n6\n7\n8\n9"
135        );
136        assert_eq!(split_string_by_width("123456789", 0), "");
137
138        assert_eq!(
139            split_string_by_width("\u{1b}[31;100m😳😳🏳️\u{1b}[39m\u{1b}[49m😳🏳️", 3),
140            {
141                #[cfg(feature = "ansi")]
142                {
143                    "\u{1b}[31m\u{1b}[100m😳\u{1b}[39m\u{1b}[49m�\n\u{1b}[31m\u{1b}[100m🏳\u{fe0f}\u{1b}[39m\u{1b}[49m�\n🏳\u{fe0f}"
144                }
145                #[cfg(not(feature = "ansi"))]
146                {
147                    "\u{1b}[3\n1;1\n00m\n😳�\n🏳\u{fe0f}\u{1b}\n[39\nm\u{1b}[\n49m\n😳🏳\n\u{fe0f}"
148                }
149            }
150        );
151    }
152}