tabled/settings/span/
column.rs

1use std::cmp::{self, Ordering};
2
3use crate::{
4    grid::{
5        config::{ColoredConfig, Entity, Position, SpannedConfig},
6        records::{ExactRecords, PeekableRecords, Records, RecordsMut},
7    },
8    settings::CellOption,
9};
10
11/// Columns (horizontal) span.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
13pub struct ColumnSpan {
14    size: isize,
15}
16
17impl ColumnSpan {
18    /// Creates a new column (horizontal) span.
19    pub fn new(size: isize) -> Self {
20        Self { size }
21    }
22
23    /// Creates a new column (horizontal) span with a maximux value possible.
24    pub fn max() -> Self {
25        Self::new(isize::MAX)
26    }
27
28    /// Creates a new column (horizontal) span with a min value possible.
29    pub fn min() -> Self {
30        Self::new(isize::MIN)
31    }
32
33    /// Creates a new column (horizontal) to spread all the columns.
34    pub fn spread() -> Self {
35        Self::new(0)
36    }
37}
38
39impl<R> CellOption<R, ColoredConfig> for ColumnSpan
40where
41    R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
42{
43    fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) {
44        let count_rows = records.count_rows();
45        let count_cols = records.count_columns();
46        let shape = (count_rows, count_cols).into();
47
48        for pos in entity.iter(count_rows, count_cols) {
49            set_col_span(records, cfg, self.size, pos, shape);
50        }
51
52        remove_false_spans(cfg);
53    }
54}
55
56fn set_col_span<R>(
57    recs: &mut R,
58    cfg: &mut SpannedConfig,
59    span: isize,
60    pos: Position,
61    shape: Position,
62) where
63    R: Records + ExactRecords + PeekableRecords + RecordsMut<String>,
64{
65    if !shape.has_coverage(pos) {
66        return;
67    }
68
69    match span.cmp(&0) {
70        Ordering::Less => {
71            // * got correct value [span, col]
72            // * clean the route from col to pos.col
73            // * set the (pos.row, col) to content from pos
74            // * set span
75
76            let span = span.unsigned_abs();
77            let (col, span) = if span > pos.col {
78                (0, pos.col)
79            } else {
80                (pos.col - span, span)
81            };
82
83            let content = recs.get_text(pos).to_string();
84
85            for i in col..span + 1 {
86                recs.set(Position::new(pos.row, i), String::new());
87            }
88
89            recs.set(Position::new(pos.row, col), content);
90            cfg.set_column_span(Position::new(pos.row, col), span + 1);
91        }
92        Ordering::Equal => {
93            let content = recs.get_text(pos).to_string();
94            let span = recs.count_columns();
95
96            for i in 0..recs.count_columns() {
97                recs.set(Position::new(pos.row, i), String::new());
98            }
99
100            recs.set(Position::new(pos.row, 0), content);
101            cfg.set_column_span(Position::new(pos.row, 0), span);
102        }
103        Ordering::Greater => {
104            let span = cmp::min(span as usize, shape.col - pos.col);
105            if span_has_intersections(cfg, pos, span) {
106                return;
107            }
108
109            set_span_column(cfg, pos, span);
110        }
111    }
112}
113
114fn set_span_column(cfg: &mut SpannedConfig, p: Position, span: usize) {
115    if span == 0 {
116        if p.col == 0 {
117            return;
118        }
119
120        if let Some(nearcol) = closest_visible(cfg, p - (0, 1)) {
121            let span = p.col + 1 - nearcol;
122            cfg.set_column_span((p.row, nearcol).into(), span);
123        }
124    }
125
126    cfg.set_column_span(p, span);
127}
128
129fn closest_visible(cfg: &SpannedConfig, mut pos: Position) -> Option<usize> {
130    loop {
131        if cfg.is_cell_visible(pos) {
132            return Some(pos.col);
133        }
134
135        if pos.col == 0 {
136            return None;
137        }
138
139        pos -= (0, 1);
140    }
141}
142
143fn span_has_intersections(cfg: &SpannedConfig, p: Position, span: usize) -> bool {
144    for col in p.col..p.col + span {
145        if !cfg.is_cell_visible((p.row, col).into()) {
146            return true;
147        }
148    }
149
150    false
151}
152
153fn remove_false_spans(cfg: &mut SpannedConfig) {
154    for (pos, _) in cfg.get_column_spans() {
155        if cfg.is_cell_visible(pos) {
156            continue;
157        }
158
159        cfg.set_row_span(pos, 1);
160        cfg.set_column_span(pos, 1);
161    }
162
163    for (pos, _) in cfg.get_row_spans() {
164        if cfg.is_cell_visible(pos) {
165            continue;
166        }
167
168        cfg.set_row_span(pos, 1);
169        cfg.set_column_span(pos, 1);
170    }
171}