1 | use std::collections::{HashMap, HashSet}; |
2 | |
3 | use crate::config::{Border, Borders, Position}; |
4 | |
5 | #[derive (Debug, Default, Clone, PartialEq, Eq)] |
6 | pub(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 | |
15 | impl<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)] |
337 | pub(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)] |
344 | pub(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)] |
357 | pub 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 | |
368 | impl<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)] |
380 | pub 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 | |
391 | impl<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)] |
402 | mod 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 | |