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