1use std::collections::{HashMap, HashSet};
2
3use super::{Border, Position};
4
5#[derive(Debug, Clone, Default)]
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 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(
94 &self,
95 pos: Position,
96 count_rows: usize,
97 count_cols: usize,
98 ) -> Border<&T> {
99 Border {
100 top: self.get_horizontal(pos, count_rows),
101 bottom: self.get_horizontal((pos.0 + 1, pos.1), count_rows),
102 left: self.get_vertical(pos, count_cols),
103 left_top_corner: self.get_intersection(pos, count_rows, count_cols),
104 left_bottom_corner: self.get_intersection((pos.0 + 1, pos.1), count_rows, count_cols),
105 right: self.get_vertical((pos.0, pos.1 + 1), count_cols),
106 right_top_corner: self.get_intersection((pos.0, pos.1 + 1), count_rows, count_cols),
107 right_bottom_corner: self.get_intersection(
108 (pos.0 + 1, pos.1 + 1),
109 count_rows,
110 count_cols,
111 ),
112 }
113 }
114
115 pub(crate) fn insert_horizontal_line(&mut self, row: usize, line: HorizontalLine<T>) {
116 if line.left.is_some() {
117 self.layout.left = true;
118 }
119
120 if line.right.is_some() {
121 self.layout.right = true;
122 }
123
124 if line.intersection.is_some() {
125 self.layout.inner_verticals = true;
126 }
127
128 self.horizontals.insert(row, line);
129 self.layout.horizontals.insert(row);
130 }
131
132 pub(crate) fn get_horizontal_line(&self, row: usize) -> Option<&HorizontalLine<T>> {
133 self.horizontals.get(&row)
134 }
135
136 pub(crate) fn remove_horizontal_line(&mut self, row: usize) {
137 self.horizontals.remove(&row);
138 }
139
140 pub(crate) fn insert_vertical_line(&mut self, row: usize, line: VerticalLine<T>) {
141 if line.top.is_some() {
142 self.layout.top = true;
143 }
144
145 if line.bottom.is_some() {
146 self.layout.bottom = true;
147 }
148
149 self.verticals.insert(row, line);
150 self.layout.verticals.insert(row);
151 }
152
153 pub(crate) fn get_vertical_line(&self, row: usize) -> Option<&VerticalLine<T>> {
154 self.verticals.get(&row)
155 }
156
157 pub(crate) fn remove_vertical_line(&mut self, row: usize) {
158 self.verticals.remove(&row);
159 }
160
161 pub(crate) fn set_borders(&mut self, borders: Borders<T>) {
162 self.borders = borders;
163 }
164
165 pub(crate) fn get_borders(&self) -> &Borders<T> {
166 &self.borders
167 }
168
169 pub(crate) fn get_global(&self) -> Option<&T> {
170 self.global.as_ref()
171 }
172
173 pub(crate) fn set_global(&mut self, value: T) {
174 self.global = Some(value);
175 }
176
177 pub(crate) fn get_vertical(&self, pos: Position, count_cols: usize) -> Option<&T> {
178 self.cells
179 .vertical
180 .get(&pos)
181 .or_else(|| self.verticals.get(&pos.1).and_then(|l| l.main.as_ref()))
182 .or({
183 if pos.1 == count_cols {
184 self.borders.vertical_right.as_ref()
185 } else if pos.1 == 0 {
186 self.borders.vertical_left.as_ref()
187 } else {
188 self.borders.vertical.as_ref()
189 }
190 })
191 .or(self.global.as_ref())
192 }
193
194 pub(crate) fn get_horizontal(&self, pos: Position, count_rows: usize) -> Option<&T> {
195 self.cells
196 .horizontal
197 .get(&pos)
198 .or_else(|| self.horizontals.get(&pos.0).and_then(|l| l.main.as_ref()))
199 .or({
200 if pos.0 == 0 {
201 self.borders.top.as_ref()
202 } else if pos.0 == count_rows {
203 self.borders.bottom.as_ref()
204 } else {
205 self.borders.horizontal.as_ref()
206 }
207 })
208 .or(self.global.as_ref())
209 }
210
211 pub(crate) fn get_intersection(
212 &self,
213 pos: Position,
214 count_rows: usize,
215 count_cols: usize,
216 ) -> Option<&T> {
217 let use_top = pos.0 == 0;
218 let use_bottom = pos.0 == count_rows;
219 let use_left = pos.1 == 0;
220 let use_right = pos.1 == count_cols;
221
222 if let Some(c) = self.cells.intersection.get(&pos) {
223 return Some(c);
224 }
225
226 let hl_c = self.horizontals.get(&pos.0).and_then(|l| {
227 if use_left && l.left.is_some() {
228 l.left.as_ref()
229 } else if use_right && l.right.is_some() {
230 l.right.as_ref()
231 } else if !use_right && !use_left && l.intersection.is_some() {
232 l.intersection.as_ref()
233 } else {
234 None
235 }
236 });
237
238 if let Some(c) = hl_c {
239 return Some(c);
240 }
241
242 let vl_c = self.verticals.get(&pos.1).and_then(|l| {
243 if use_top && l.top.is_some() {
244 l.top.as_ref()
245 } else if use_bottom && l.bottom.is_some() {
246 l.bottom.as_ref()
247 } else if !use_top && !use_bottom && l.intersection.is_some() {
248 l.intersection.as_ref()
249 } else {
250 None
251 }
252 });
253
254 if let Some(c) = vl_c {
255 return Some(c);
256 }
257
258 let borders_c = {
259 if use_top && use_left {
260 self.borders.top_left.as_ref()
261 } else if use_top && use_right {
262 self.borders.top_right.as_ref()
263 } else if use_bottom && use_left {
264 self.borders.bottom_left.as_ref()
265 } else if use_bottom && use_right {
266 self.borders.bottom_right.as_ref()
267 } else if use_top {
268 self.borders.top_intersection.as_ref()
269 } else if use_bottom {
270 self.borders.bottom_intersection.as_ref()
271 } else if use_left {
272 self.borders.horizontal_left.as_ref()
273 } else if use_right {
274 self.borders.horizontal_right.as_ref()
275 } else {
276 self.borders.intersection.as_ref()
277 }
278 };
279
280 if let Some(c) = borders_c {
281 return Some(c);
282 }
283
284 self.global.as_ref()
285 }
286
287 pub(crate) fn has_horizontal(&self, row: usize, count_rows: usize) -> bool {
288 self.global.is_some()
289 || (row == 0 && self.borders.has_top())
290 || (row == count_rows && self.borders.has_bottom())
291 || (row > 0 && row < count_rows && self.borders.has_horizontal())
292 || self.is_horizontal_set(row, count_rows)
293 }
294
295 pub(crate) fn has_vertical(&self, col: usize, count_cols: usize) -> bool {
296 self.global.is_some()
297 || (col == 0 && self.borders.has_left())
298 || (col == count_cols && self.borders.has_right())
299 || (col > 0 && col < count_cols && self.borders.has_vertical())
300 || self.is_vertical_set(col, count_cols)
301 }
302
303 fn is_horizontal_set(&self, row: usize, count_rows: usize) -> bool {
304 (row == 0 && self.layout.top)
305 || (row == count_rows && self.layout.bottom)
306 || (row > 0 && row < count_rows && self.layout.inner_horizontals)
307 || self.layout.horizontals.contains(&row)
308 }
309
310 fn is_vertical_set(&self, col: usize, count_cols: usize) -> bool {
311 (col == 0 && self.layout.left)
312 || (col == count_cols && self.layout.right)
313 || (col > 0 && col < count_cols && self.layout.inner_verticals)
314 || self.layout.verticals.contains(&col)
315 }
316
317 fn check_is_horizontal_set(&self, row: usize, count_rows: usize) -> bool {
318 (row == 0 && self.layout.top)
319 || (row == count_rows && self.layout.bottom)
320 || (row > 0 && row < count_rows && self.layout.inner_horizontals)
321 || self.cells.horizontal.keys().any(|&p| p.0 == row)
322 || self.cells.intersection.keys().any(|&p| p.0 == row)
323 }
324
325 fn check_is_vertical_set(&self, col: usize, count_cols: usize) -> bool {
326 (col == 0 && self.layout.left)
327 || (col == count_cols && self.layout.right)
328 || (col > 0 && col < count_cols && self.layout.inner_verticals)
329 || self.cells.vertical.keys().any(|&p| p.1 == col)
330 || self.cells.intersection.keys().any(|&p| p.1 == col)
331 }
332}
333
334#[derive(Debug, Clone, Default)]
336pub struct Borders<T = char> {
337 pub top: Option<T>,
339 pub top_left: Option<T>,
341 pub top_right: Option<T>,
343 pub top_intersection: Option<T>,
345
346 pub bottom: Option<T>,
348 pub bottom_left: Option<T>,
350 pub bottom_right: Option<T>,
352 pub bottom_intersection: Option<T>,
354
355 pub horizontal: Option<T>,
357 pub horizontal_left: Option<T>,
359 pub horizontal_right: Option<T>,
361
362 pub vertical: Option<T>,
364 pub vertical_left: Option<T>,
366 pub vertical_right: Option<T>,
368
369 pub intersection: Option<T>,
371}
372
373impl<T> Borders<T> {
374 pub const fn has_left(&self) -> bool {
376 self.vertical_left.is_some()
377 || self.horizontal_left.is_some()
378 || self.top_left.is_some()
379 || self.bottom_left.is_some()
380 }
381
382 pub const fn has_right(&self) -> bool {
384 self.vertical_right.is_some()
385 || self.horizontal_right.is_some()
386 || self.top_right.is_some()
387 || self.bottom_right.is_some()
388 }
389
390 pub const fn has_top(&self) -> bool {
392 self.top.is_some()
393 || self.top_intersection.is_some()
394 || self.top_left.is_some()
395 || self.top_right.is_some()
396 }
397
398 pub const fn has_bottom(&self) -> bool {
400 self.bottom.is_some()
401 || self.bottom_intersection.is_some()
402 || self.bottom_left.is_some()
403 || self.bottom_right.is_some()
404 }
405
406 pub const fn has_horizontal(&self) -> bool {
408 self.horizontal.is_some()
409 || self.horizontal_left.is_some()
410 || self.horizontal_right.is_some()
411 || self.intersection.is_some()
412 }
413
414 pub const fn has_vertical(&self) -> bool {
416 self.intersection.is_some()
417 || self.vertical.is_some()
418 || self.top_intersection.is_some()
419 || self.bottom_intersection.is_some()
420 }
421}
422
423#[derive(Debug, Clone, Default)]
424pub(crate) struct BordersMap<T> {
425 vertical: HashMap<Position, T>,
426 horizontal: HashMap<Position, T>,
427 intersection: HashMap<Position, T>,
428}
429
430#[derive(Debug, Clone, Default)]
431pub(crate) struct BordersLayout {
432 left: bool,
433 right: bool,
434 top: bool,
435 bottom: bool,
436 inner_verticals: bool,
437 inner_horizontals: bool,
438 horizontals: HashSet<usize>,
439 verticals: HashSet<usize>,
440}
441
442#[derive(Debug, Clone, Copy, Default)]
444pub struct HorizontalLine<T> {
445 pub main: Option<T>,
447 pub intersection: Option<T>,
449 pub left: Option<T>,
451 pub right: Option<T>,
453}
454
455impl<T> HorizontalLine<T> {
456 pub const fn is_empty(&self) -> bool {
458 self.main.is_none()
459 && self.intersection.is_none()
460 && self.left.is_none()
461 && self.right.is_none()
462 }
463}
464
465#[derive(Debug, Clone, Copy, Default)]
467pub struct VerticalLine<T> {
468 pub main: Option<T>,
470 pub intersection: Option<T>,
472 pub top: Option<T>,
474 pub bottom: Option<T>,
476}
477
478impl<T> VerticalLine<T> {
479 pub const fn is_empty(&self) -> bool {
481 self.main.is_none()
482 && self.intersection.is_none()
483 && self.top.is_none()
484 && self.bottom.is_none()
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491
492 #[test]
493 fn test_insert_border() {
494 let mut borders = BordersConfig::<char>::default();
495 borders.insert_border((0, 0), Border::filled('x'));
496
497 assert_eq!(borders.get_border((0, 0), 10, 10), Border::filled(&'x'));
498 assert_eq!(borders.get_border((0, 0), 0, 0), Border::filled(&'x'));
499
500 assert!(borders.is_horizontal_set(0, 10));
501 assert!(borders.is_horizontal_set(1, 10));
502 assert!(!borders.is_horizontal_set(2, 10));
503 assert!(borders.is_vertical_set(0, 10));
504 assert!(borders.is_vertical_set(1, 10));
505 assert!(!borders.is_vertical_set(2, 10));
506
507 assert!(borders.is_horizontal_set(0, 0));
508 assert!(borders.is_horizontal_set(1, 0));
509 assert!(!borders.is_horizontal_set(2, 0));
510 assert!(borders.is_vertical_set(0, 0));
511 assert!(borders.is_vertical_set(1, 0));
512 assert!(!borders.is_vertical_set(2, 0));
513 }
514
515 #[test]
516 fn test_insert_border_override() {
517 let mut borders = BordersConfig::<char>::default();
518 borders.insert_border((0, 0), Border::filled('x'));
519 borders.insert_border((1, 0), Border::filled('y'));
520 borders.insert_border((0, 1), Border::filled('w'));
521 borders.insert_border((1, 1), Border::filled('q'));
522
523 assert_eq!(
524 borders.get_border((0, 0), 10, 10).copied(),
525 Border::full('x', 'y', 'x', 'w', 'x', 'w', 'y', 'q')
526 );
527 assert_eq!(
528 borders.get_border((0, 1), 10, 10).copied(),
529 Border::full('w', 'q', 'w', 'w', 'w', 'w', 'q', 'q')
530 );
531 assert_eq!(
532 borders.get_border((1, 0), 10, 10).copied(),
533 Border::full('y', 'y', 'y', 'q', 'y', 'q', 'y', 'q')
534 );
535 assert_eq!(
536 borders.get_border((1, 1), 10, 10).copied(),
537 Border::filled('q')
538 );
539
540 assert!(borders.is_horizontal_set(0, 10));
541 assert!(borders.is_horizontal_set(1, 10));
542 assert!(borders.is_horizontal_set(2, 10));
543 assert!(!borders.is_horizontal_set(3, 10));
544 assert!(borders.is_vertical_set(0, 10));
545 assert!(borders.is_vertical_set(1, 10));
546 assert!(borders.is_vertical_set(2, 10));
547 assert!(!borders.is_vertical_set(3, 10));
548 }
549
550 #[test]
551 fn test_set_global() {
552 let mut borders = BordersConfig::<char>::default();
553 borders.insert_border((0, 0), Border::filled('x'));
554 borders.set_global('l');
555
556 assert_eq!(borders.get_border((0, 0), 10, 10), Border::filled(&'x'));
557 assert_eq!(borders.get_border((2, 0), 10, 10), Border::filled(&'l'));
558
559 assert!(borders.is_horizontal_set(0, 10));
560 assert!(borders.is_horizontal_set(1, 10));
561 assert!(!borders.is_horizontal_set(2, 10));
562 assert!(borders.is_vertical_set(0, 10));
563 assert!(borders.is_vertical_set(1, 10));
564 assert!(!borders.is_vertical_set(2, 10));
565
566 assert!(borders.is_horizontal_set(0, 0));
567 assert!(borders.is_horizontal_set(1, 0));
568 assert!(!borders.is_horizontal_set(2, 0));
569 assert!(borders.is_vertical_set(0, 0));
570 assert!(borders.is_vertical_set(1, 0));
571 assert!(!borders.is_vertical_set(2, 0));
572 }
573}