1use std::collections::{HashMap, HashSet};
2
3use crate::config::{Border, Borders, Position};
4
5#[derive(Debug, Default, Clone, PartialEq, Eq)]
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(&self, pos: Position, shape: (usize, usize)) -> Border<&T> {
94 Border {
95 top: self.get_horizontal(pos, shape.0),
96 bottom: self.get_horizontal((pos.0 + 1, pos.1), shape.0),
97 left: self.get_vertical(pos, shape.1),
98 left_top_corner: self.get_intersection(pos, shape),
99 left_bottom_corner: self.get_intersection((pos.0 + 1, pos.1), shape),
100 right: self.get_vertical((pos.0, pos.1 + 1), shape.1),
101 right_top_corner: self.get_intersection((pos.0, pos.1 + 1), shape),
102 right_bottom_corner: self.get_intersection((pos.0 + 1, pos.1 + 1), shape),
103 }
104 }
105
106 pub(crate) fn insert_horizontal_line(&mut self, row: usize, line: HorizontalLine<T>) {
107 if line.left.is_some() {
108 self.layout.left = true;
109 }
110
111 // todo: when we delete lines these are still left set; so has_horizontal/vertical return true in some cases;
112 // it shall be fixed, but maybe we can improve the logic as it got a bit complicated.
113 if line.right.is_some() {
114 self.layout.right = true;
115 }
116
117 if line.intersection.is_some() {
118 self.layout.inner_verticals = true;
119 }
120
121 self.horizontals.insert(row, line);
122 self.layout.horizontals.insert(row);
123 }
124
125 pub(crate) fn get_horizontal_line(&self, row: usize) -> Option<&HorizontalLine<T>> {
126 self.horizontals.get(&row)
127 }
128
129 pub(crate) fn remove_horizontal_line(&mut self, row: usize, count_rows: usize) {
130 self.horizontals.remove(&row);
131 self.layout.horizontals.remove(&row);
132
133 if self.has_horizontal(row, count_rows) {
134 self.layout.horizontals.insert(row);
135 }
136 }
137
138 pub(crate) fn insert_vertical_line(&mut self, row: usize, line: VerticalLine<T>) {
139 if line.top.is_some() {
140 self.layout.top = true;
141 }
142
143 if line.bottom.is_some() {
144 self.layout.bottom = true;
145 }
146
147 self.verticals.insert(row, line);
148 self.layout.verticals.insert(row);
149 }
150
151 pub(crate) fn get_vertical_line(&self, row: usize) -> Option<&VerticalLine<T>> {
152 self.verticals.get(&row)
153 }
154
155 pub(crate) fn remove_vertical_line(&mut self, col: usize, count_columns: usize) {
156 self.verticals.remove(&col);
157 self.layout.verticals.remove(&col);
158
159 if self.has_vertical(col, count_columns) {
160 self.layout.verticals.insert(col);
161 }
162 }
163
164 pub(crate) fn set_borders(&mut self, borders: Borders<T>) {
165 self.borders = borders;
166 }
167
168 pub(crate) fn get_borders(&self) -> &Borders<T> {
169 &self.borders
170 }
171
172 pub(crate) fn get_global(&self) -> Option<&T> {
173 self.global.as_ref()
174 }
175
176 pub(crate) fn set_global(&mut self, value: T) {
177 self.global = Some(value);
178 }
179
180 pub(crate) fn get_vertical(&self, pos: Position, count_cols: usize) -> Option<&T> {
181 self.cells
182 .vertical
183 .get(&pos)
184 .or_else(|| self.verticals.get(&pos.1).and_then(|l| l.main.as_ref()))
185 .or({
186 if pos.1 == count_cols {
187 self.borders.right.as_ref()
188 } else if pos.1 == 0 {
189 self.borders.left.as_ref()
190 } else {
191 self.borders.vertical.as_ref()
192 }
193 })
194 .or(self.global.as_ref())
195 }
196
197 pub(crate) fn get_horizontal(&self, pos: Position, count_rows: usize) -> Option<&T> {
198 self.cells
199 .horizontal
200 .get(&pos)
201 .or_else(|| self.horizontals.get(&pos.0).and_then(|l| l.main.as_ref()))
202 .or({
203 if pos.0 == 0 {
204 self.borders.top.as_ref()
205 } else if pos.0 == count_rows {
206 self.borders.bottom.as_ref()
207 } else {
208 self.borders.horizontal.as_ref()
209 }
210 })
211 .or(self.global.as_ref())
212 }
213
214 pub(crate) fn get_intersection(
215 &self,
216 pos: Position,
217 (count_rows, count_cols): (usize, usize),
218 ) -> Option<&T> {
219 let use_top = pos.0 == 0;
220 let use_bottom = pos.0 == count_rows;
221 let use_left = pos.1 == 0;
222 let use_right = pos.1 == count_cols;
223
224 if let Some(c) = self.cells.intersection.get(&pos) {
225 return Some(c);
226 }
227
228 let hl_c = self.horizontals.get(&pos.0).and_then(|l| {
229 if use_left && l.left.is_some() {
230 l.left.as_ref()
231 } else if use_right && l.right.is_some() {
232 l.right.as_ref()
233 } else if !use_right && !use_left && l.intersection.is_some() {
234 l.intersection.as_ref()
235 } else {
236 None
237 }
238 });
239
240 if let Some(c) = hl_c {
241 return Some(c);
242 }
243
244 let vl_c = self.verticals.get(&pos.1).and_then(|l| {
245 if use_top && l.top.is_some() {
246 l.top.as_ref()
247 } else if use_bottom && l.bottom.is_some() {
248 l.bottom.as_ref()
249 } else if !use_top && !use_bottom && l.intersection.is_some() {
250 l.intersection.as_ref()
251 } else {
252 None
253 }
254 });
255
256 if let Some(c) = vl_c {
257 return Some(c);
258 }
259
260 let borders_c = {
261 if use_top && use_left {
262 self.borders.top_left.as_ref()
263 } else if use_top && use_right {
264 self.borders.top_right.as_ref()
265 } else if use_bottom && use_left {
266 self.borders.bottom_left.as_ref()
267 } else if use_bottom && use_right {
268 self.borders.bottom_right.as_ref()
269 } else if use_top {
270 self.borders.top_intersection.as_ref()
271 } else if use_bottom {
272 self.borders.bottom_intersection.as_ref()
273 } else if use_left {
274 self.borders.left_intersection.as_ref()
275 } else if use_right {
276 self.borders.right_intersection.as_ref()
277 } else {
278 self.borders.intersection.as_ref()
279 }
280 };
281
282 if let Some(c) = borders_c {
283 return Some(c);
284 }
285
286 self.global.as_ref()
287 }
288
289 pub(crate) fn has_horizontal(&self, row: usize, count_rows: usize) -> bool {
290 self.global.is_some()
291 || (row == 0 && self.borders.has_top())
292 || (row == count_rows && self.borders.has_bottom())
293 || (row > 0 && row < count_rows && self.borders.has_horizontal())
294 || self.is_horizontal_set(row, count_rows)
295 }
296
297 pub(crate) fn has_vertical(&self, col: usize, count_cols: usize) -> bool {
298 self.global.is_some()
299 || (col == 0 && self.borders.has_left())
300 || (col == count_cols && self.borders.has_right())
301 || (col > 0 && col < count_cols && self.borders.has_vertical())
302 || self.is_vertical_set(col, count_cols)
303 }
304
305 fn is_horizontal_set(&self, row: usize, count_rows: usize) -> bool {
306 (row == 0 && self.layout.top)
307 || (row == count_rows && self.layout.bottom)
308 || (row > 0 && row < count_rows && self.layout.inner_horizontals)
309 || self.layout.horizontals.contains(&row)
310 }
311
312 fn is_vertical_set(&self, col: usize, count_cols: usize) -> bool {
313 (col == 0 && self.layout.left)
314 || (col == count_cols && self.layout.right)
315 || (col > 0 && col < count_cols && self.layout.inner_verticals)
316 || self.layout.verticals.contains(&col)
317 }
318
319 fn check_is_horizontal_set(&self, row: usize, count_rows: usize) -> bool {
320 (row == 0 && self.layout.top)
321 || (row == count_rows && self.layout.bottom)
322 || (row > 0 && row < count_rows && self.layout.inner_horizontals)
323 || self.cells.horizontal.keys().any(|&p| p.0 == row)
324 || self.cells.intersection.keys().any(|&p| p.0 == row)
325 }
326
327 fn check_is_vertical_set(&self, col: usize, count_cols: usize) -> bool {
328 (col == 0 && self.layout.left)
329 || (col == count_cols && self.layout.right)
330 || (col > 0 && col < count_cols && self.layout.inner_verticals)
331 || self.cells.vertical.keys().any(|&p| p.1 == col)
332 || self.cells.intersection.keys().any(|&p| p.1 == col)
333 }
334}
335
336#[derive(Debug, Clone, Default, PartialEq, Eq)]
337pub(crate) struct BordersMap<T> {
338 vertical: HashMap<Position, T>,
339 horizontal: HashMap<Position, T>,
340 intersection: HashMap<Position, T>,
341}
342
343#[derive(Debug, Clone, Default, PartialEq, Eq)]
344pub(crate) struct BordersLayout {
345 left: bool,
346 right: bool,
347 top: bool,
348 bottom: bool,
349 inner_verticals: bool,
350 inner_horizontals: bool,
351 horizontals: HashSet<usize>,
352 verticals: HashSet<usize>,
353}
354
355/// A structure for a custom horizontal line.
356#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
357pub struct HorizontalLine<T> {
358 /// Line character.
359 pub main: Option<T>,
360 /// Line intersection character.
361 pub intersection: Option<T>,
362 /// Left intersection character.
363 pub left: Option<T>,
364 /// Right intersection character.
365 pub right: Option<T>,
366}
367
368impl<T> HorizontalLine<T> {
369 /// Verifies if the line has any setting set.
370 pub const fn is_empty(&self) -> bool {
371 self.main.is_none()
372 && self.intersection.is_none()
373 && self.left.is_none()
374 && self.right.is_none()
375 }
376}
377
378/// A structure for a vertical line.
379#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
380pub struct VerticalLine<T> {
381 /// Line character.
382 pub main: Option<T>,
383 /// Line intersection character.
384 pub intersection: Option<T>,
385 /// Left intersection character.
386 pub top: Option<T>,
387 /// Right intersection character.
388 pub bottom: Option<T>,
389}
390
391impl<T> VerticalLine<T> {
392 /// Verifies if the line has any setting set.
393 pub const fn is_empty(&self) -> bool {
394 self.main.is_none()
395 && self.intersection.is_none()
396 && self.top.is_none()
397 && self.bottom.is_none()
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404
405 #[test]
406 fn test_insert_border() {
407 let mut borders = BordersConfig::<char>::default();
408 borders.insert_border((0, 0), Border::filled('x'));
409
410 assert_eq!(borders.get_border((0, 0), (10, 10)), Border::filled(&'x'));
411 assert_eq!(borders.get_border((0, 0), (0, 0)), Border::filled(&'x'));
412
413 assert!(borders.is_horizontal_set(0, 10));
414 assert!(borders.is_horizontal_set(1, 10));
415 assert!(!borders.is_horizontal_set(2, 10));
416 assert!(borders.is_vertical_set(0, 10));
417 assert!(borders.is_vertical_set(1, 10));
418 assert!(!borders.is_vertical_set(2, 10));
419
420 assert!(borders.is_horizontal_set(0, 0));
421 assert!(borders.is_horizontal_set(1, 0));
422 assert!(!borders.is_horizontal_set(2, 0));
423 assert!(borders.is_vertical_set(0, 0));
424 assert!(borders.is_vertical_set(1, 0));
425 assert!(!borders.is_vertical_set(2, 0));
426 }
427
428 #[test]
429 fn test_insert_border_override() {
430 let mut borders = BordersConfig::<char>::default();
431 borders.insert_border((0, 0), Border::filled('x'));
432 borders.insert_border((1, 0), Border::filled('y'));
433 borders.insert_border((0, 1), Border::filled('w'));
434 borders.insert_border((1, 1), Border::filled('q'));
435
436 assert_eq!(
437 borders.get_border((0, 0), (10, 10)).copied(),
438 Border::full('x', 'y', 'x', 'w', 'x', 'w', 'y', 'q')
439 );
440 assert_eq!(
441 borders.get_border((0, 1), (10, 10)).copied(),
442 Border::full('w', 'q', 'w', 'w', 'w', 'w', 'q', 'q')
443 );
444 assert_eq!(
445 borders.get_border((1, 0), (10, 10)).copied(),
446 Border::full('y', 'y', 'y', 'q', 'y', 'q', 'y', 'q')
447 );
448 assert_eq!(
449 borders.get_border((1, 1), (10, 10)).copied(),
450 Border::filled('q')
451 );
452
453 assert!(borders.is_horizontal_set(0, 10));
454 assert!(borders.is_horizontal_set(1, 10));
455 assert!(borders.is_horizontal_set(2, 10));
456 assert!(!borders.is_horizontal_set(3, 10));
457 assert!(borders.is_vertical_set(0, 10));
458 assert!(borders.is_vertical_set(1, 10));
459 assert!(borders.is_vertical_set(2, 10));
460 assert!(!borders.is_vertical_set(3, 10));
461 }
462
463 #[test]
464 fn test_set_global() {
465 let mut borders = BordersConfig::<char>::default();
466 borders.insert_border((0, 0), Border::filled('x'));
467 borders.set_global('l');
468
469 assert_eq!(borders.get_border((0, 0), (10, 10)), Border::filled(&'x'));
470 assert_eq!(borders.get_border((2, 0), (10, 10)), Border::filled(&'l'));
471
472 assert!(borders.is_horizontal_set(0, 10));
473 assert!(borders.is_horizontal_set(1, 10));
474 assert!(!borders.is_horizontal_set(2, 10));
475 assert!(borders.is_vertical_set(0, 10));
476 assert!(borders.is_vertical_set(1, 10));
477 assert!(!borders.is_vertical_set(2, 10));
478
479 assert!(borders.is_horizontal_set(0, 0));
480 assert!(borders.is_horizontal_set(1, 0));
481 assert!(!borders.is_horizontal_set(2, 0));
482 assert!(borders.is_vertical_set(0, 0));
483 assert!(borders.is_vertical_set(1, 0));
484 assert!(!borders.is_vertical_set(2, 0));
485 }
486}
487