tabled/settings/span/
column.rs1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
13pub struct ColumnSpan {
14 size: isize,
15}
16
17impl ColumnSpan {
18 pub fn new(size: isize) -> Self {
20 Self { size }
21 }
22
23 pub fn max() -> Self {
25 Self::new(isize::MAX)
26 }
27
28 pub fn min() -> Self {
30 Self::new(isize::MIN)
31 }
32
33 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 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}