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