1 | //! This module contains a [`Highlight`] primitive, which helps |
2 | //! changing a [`Border`] style of any segment on a [`Table`]. |
3 | //! |
4 | //! [`Table`]: crate::Table |
5 | |
6 | use std::collections::HashSet; |
7 | |
8 | use crate::{ |
9 | grid::{ |
10 | ansi::ANSIBuf, |
11 | config::{Border as GridBorder, ColoredConfig, Entity, Position, SpannedConfig}, |
12 | records::{ExactRecords, Records}, |
13 | }, |
14 | settings::{object::Object, style::BorderColor, Border, Color, TableOption}, |
15 | }; |
16 | |
17 | /// Highlight modifies a table style by changing a border of a target [`Table`] segment. |
18 | /// |
19 | /// # Example |
20 | /// |
21 | /// ``` |
22 | /// use tabled::{ |
23 | /// Table, |
24 | /// settings::{Highlight, Border, Style, object::Segment} |
25 | /// }; |
26 | /// |
27 | /// let data = [ |
28 | /// ("ELF" , "Extensible Linking Format" , true), |
29 | /// ("DWARF" , "" , true), |
30 | /// ("PE" , "Portable Executable" , false), |
31 | /// ]; |
32 | /// |
33 | /// let table = Table::new(data.iter().enumerate()) |
34 | /// .with(Style::markdown()) |
35 | /// .with(Highlight::border(Segment::all(), Border::new().set_top('^' ).set_bottom('v' ))) |
36 | /// .to_string(); |
37 | /// |
38 | /// assert_eq!( |
39 | /// table, |
40 | /// concat!( |
41 | /// " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \n" , |
42 | /// "| usize | &str | &str | bool | \n" , |
43 | /// "|-------|-------|---------------------------|-------| \n" , |
44 | /// "| 0 | ELF | Extensible Linking Format | true | \n" , |
45 | /// "| 1 | DWARF | | true | \n" , |
46 | /// "| 2 | PE | Portable Executable | false | \n" , |
47 | /// " vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv " , |
48 | /// ), |
49 | /// ); |
50 | /// ``` |
51 | /// |
52 | /// It's possible to use [`Highlight`] for many kinds of figures. |
53 | /// |
54 | /// ``` |
55 | /// use tabled::{ |
56 | /// Table, |
57 | /// settings::{ |
58 | /// Highlight, Border, Style, |
59 | /// object::{Segment, Object} |
60 | /// } |
61 | /// }; |
62 | /// |
63 | /// let data = [ |
64 | /// ("ELF" , "Extensible Linking Format" , true), |
65 | /// ("DWARF" , "" , true), |
66 | /// ("PE" , "Portable Executable" , false), |
67 | /// ]; |
68 | /// |
69 | /// let table = Table::new(data.iter().enumerate()) |
70 | /// .with(Style::markdown()) |
71 | /// .with(Highlight::border(Segment::all().not((0,0).and((1, 0)).and((0, 1)).and((0, 3))), Border::filled('*' ))) |
72 | /// .to_string(); |
73 | /// |
74 | /// println!("{}" , table); |
75 | /// |
76 | /// assert_eq!( |
77 | /// table, |
78 | /// concat!( |
79 | /// " ***************************** \n" , |
80 | /// "| usize | &str * &str * bool | \n" , |
81 | /// "|-------*********---------------------------********* \n" , |
82 | /// "| 0 * ELF | Extensible Linking Format | true * \n" , |
83 | /// "********* * \n" , |
84 | /// "* 1 | DWARF | | true * \n" , |
85 | /// "* * \n" , |
86 | /// "* 2 | PE | Portable Executable | false * \n" , |
87 | /// "*****************************************************" , |
88 | /// ), |
89 | /// ); |
90 | /// ``` |
91 | /// |
92 | /// [`Table`]: crate::Table |
93 | #[derive (Debug)] |
94 | pub struct Highlight<O> { |
95 | target: O, |
96 | border: HighlightInner, |
97 | } |
98 | |
99 | #[derive (Debug)] |
100 | enum HighlightInner { |
101 | Border(GridBorder<char>), |
102 | // #[cfg(feature = "ansi")] |
103 | Color(GridBorder<ANSIBuf>), |
104 | // #[cfg(feature = "ansi")] |
105 | ColoredBorder(GridBorder<char>, GridBorder<ANSIBuf>), |
106 | } |
107 | |
108 | // todo: Docs testss |
109 | |
110 | // todo: Move colors to Highlight itself |
111 | // todo: Maybe add a default Border and set ask for a char -> Highlight::new(target).border().outline().color().outline_color() |
112 | |
113 | // todo: Add BorderColor. |
114 | |
115 | impl<O> Highlight<O> { |
116 | /// Build a new instance of [`Highlight`] |
117 | /// |
118 | /// BE AWARE: if target exceeds boundaries it may panic. |
119 | pub const fn border<T, B, L, R>(target: O, border: Border<T, B, L, R>) -> Self { |
120 | Self { |
121 | target, |
122 | border: HighlightInner::Border(border.into_inner()), |
123 | } |
124 | } |
125 | |
126 | /// Build a new instance of [`Highlight`] |
127 | /// |
128 | /// BE AWARE: if target exceeds boundaries it may panic. |
129 | // #[cfg(feature = "ansi")] |
130 | pub fn color<T, B, L, R>(target: O, border: BorderColor<T, B, L, R>) -> Self { |
131 | let color = border.into_inner(); |
132 | let color = color.convert(); |
133 | |
134 | Self { |
135 | target, |
136 | border: HighlightInner::Color(color), |
137 | } |
138 | } |
139 | |
140 | /// Build a new instance of [`Highlight`] |
141 | /// |
142 | /// BE AWARE: if target exceeds boundaries it may panic. |
143 | pub const fn outline(target: O, c: char) -> Self { |
144 | Self::border(target, Border::filled(c)) |
145 | } |
146 | |
147 | /// Build a new instance of [`Highlight`] |
148 | /// |
149 | /// BE AWARE: if target exceeds boundaries it may panic. |
150 | pub fn colored_outline<T, B, L, R>(target: O, c: char, color: Color) -> Self { |
151 | Self::colored_border(target, Border::filled(c), BorderColor::filled(color)) |
152 | } |
153 | |
154 | /// Build a new instance of [`Highlight`] |
155 | /// |
156 | /// BE AWARE: if target exceeds boundaries it may panic. |
157 | // #[cfg(feature = "ansi")] |
158 | pub fn colored_border<T, B, L, R>( |
159 | target: O, |
160 | border: Border<T, B, L, R>, |
161 | color: BorderColor<T, B, L, R>, |
162 | ) -> Self { |
163 | let border = border.into_inner(); |
164 | let color = color.into_inner().convert(); |
165 | |
166 | Self { |
167 | target, |
168 | border: HighlightInner::ColoredBorder(border, color), |
169 | } |
170 | } |
171 | } |
172 | |
173 | impl<O, R, D> TableOption<R, ColoredConfig, D> for Highlight<O> |
174 | where |
175 | O: Object<R>, |
176 | R: Records + ExactRecords, |
177 | { |
178 | fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
179 | let count_rows = records.count_rows(); |
180 | let count_cols = records.count_columns(); |
181 | |
182 | let cells = self.target.cells(records); |
183 | let segments = split_segments(cells, count_rows, count_cols); |
184 | |
185 | match self.border { |
186 | HighlightInner::Border(border) => { |
187 | for sector in segments { |
188 | set_border(cfg, §or, border); |
189 | } |
190 | } |
191 | HighlightInner::Color(color) => { |
192 | for sector in segments { |
193 | set_border_color(cfg, §or, &color); |
194 | } |
195 | } |
196 | HighlightInner::ColoredBorder(border, color) => { |
197 | for sector in segments { |
198 | set_border(cfg, §or, border); |
199 | set_border_color(cfg, §or, &color); |
200 | } |
201 | } |
202 | } |
203 | } |
204 | |
205 | fn hint_change(&self) -> Option<Entity> { |
206 | None |
207 | } |
208 | } |
209 | |
210 | fn set_border_color( |
211 | cfg: &mut SpannedConfig, |
212 | sector: &HashSet<(usize, usize)>, |
213 | border: &GridBorder<ANSIBuf>, |
214 | ) { |
215 | if sector.is_empty() { |
216 | return; |
217 | } |
218 | let color: Border = border.clone(); |
219 | for &(row: usize, col: usize) in sector { |
220 | let border: Border = build_cell_border(sector, (row, col), &color); |
221 | cfg.set_border_color((row, col), border); |
222 | } |
223 | } |
224 | |
225 | fn split_segments( |
226 | cells: impl Iterator<Item = Entity>, |
227 | count_rows: usize, |
228 | count_cols: usize, |
229 | ) -> Vec<HashSet<(usize, usize)>> { |
230 | let mut segments: Vec<HashSet<(usize, usize)>> = Vec::new(); |
231 | for entity in cells { |
232 | for cell in entity.iter(count_rows, count_cols) { |
233 | let found_segment = segments |
234 | .iter_mut() |
235 | .find(|s| s.iter().any(|&c| is_cell_connected(cell, c))); |
236 | |
237 | match found_segment { |
238 | Some(segment) => { |
239 | let _ = segment.insert(cell); |
240 | } |
241 | None => { |
242 | let mut segment = HashSet::new(); |
243 | let _ = segment.insert(cell); |
244 | segments.push(segment); |
245 | } |
246 | } |
247 | } |
248 | } |
249 | |
250 | let mut squashed_segments: Vec<HashSet<(usize, usize)>> = Vec::new(); |
251 | while !segments.is_empty() { |
252 | let mut segment = segments.remove(0); |
253 | |
254 | let mut i = 0; |
255 | while i < segments.len() { |
256 | if is_segment_connected(&segment, &segments[i]) { |
257 | segment.extend(&segments[i]); |
258 | let _ = segments.remove(i); |
259 | } else { |
260 | i += 1; |
261 | } |
262 | } |
263 | |
264 | squashed_segments.push(segment); |
265 | } |
266 | |
267 | squashed_segments |
268 | } |
269 | |
270 | fn is_cell_connected((row1: usize, col1: usize): (usize, usize), (row2: usize, col2: usize): (usize, usize)) -> bool { |
271 | if col1 == col2 && row1 == row2 + 1 { |
272 | return true; |
273 | } |
274 | |
275 | if col1 == col2 && (row2 > 0 && row1 == row2 - 1) { |
276 | return true; |
277 | } |
278 | |
279 | if row1 == row2 && col1 == col2 + 1 { |
280 | return true; |
281 | } |
282 | |
283 | if row1 == row2 && (col2 > 0 && col1 == col2 - 1) { |
284 | return true; |
285 | } |
286 | |
287 | false |
288 | } |
289 | |
290 | fn is_segment_connected( |
291 | segment1: &HashSet<(usize, usize)>, |
292 | segment2: &HashSet<(usize, usize)>, |
293 | ) -> bool { |
294 | for &cell1: (usize, usize) in segment1.iter() { |
295 | for &cell2: (usize, usize) in segment2.iter() { |
296 | if is_cell_connected(cell1, cell2) { |
297 | return true; |
298 | } |
299 | } |
300 | } |
301 | |
302 | false |
303 | } |
304 | |
305 | fn set_border(cfg: &mut SpannedConfig, sector: &HashSet<(usize, usize)>, border: GridBorder<char>) { |
306 | if sector.is_empty() { |
307 | return; |
308 | } |
309 | |
310 | for &pos: (usize, usize) in sector { |
311 | let border: Border = build_cell_border(sector, pos, &border); |
312 | cfg.set_border(pos, border); |
313 | } |
314 | } |
315 | |
316 | fn build_cell_border<T>( |
317 | sector: &HashSet<(usize, usize)>, |
318 | (row: usize, col: usize): Position, |
319 | border: &GridBorder<T>, |
320 | ) -> GridBorder<T> |
321 | where |
322 | T: Default + Clone, |
323 | { |
324 | let cell_has_top_neighbor = cell_has_top_neighbor(sector, row, col); |
325 | let cell_has_bottom_neighbor = cell_has_bottom_neighbor(sector, row, col); |
326 | let cell_has_left_neighbor = cell_has_left_neighbor(sector, row, col); |
327 | let cell_has_right_neighbor = cell_has_right_neighbor(sector, row, col); |
328 | |
329 | let this_has_left_top_neighbor = is_there_left_top_cell(sector, row, col); |
330 | let this_has_right_top_neighbor = is_there_right_top_cell(sector, row, col); |
331 | let this_has_left_bottom_neighbor = is_there_left_bottom_cell(sector, row, col); |
332 | let this_has_right_bottom_neighbor = is_there_right_bottom_cell(sector, row, col); |
333 | |
334 | let mut cell_border = GridBorder::default(); |
335 | if let Some(c) = border.top.clone() { |
336 | if !cell_has_top_neighbor { |
337 | cell_border.top = Some(c.clone()); |
338 | |
339 | if cell_has_right_neighbor && !this_has_right_top_neighbor { |
340 | cell_border.right_top_corner = Some(c); |
341 | } |
342 | } |
343 | } |
344 | if let Some(c) = border.bottom.clone() { |
345 | if !cell_has_bottom_neighbor { |
346 | cell_border.bottom = Some(c.clone()); |
347 | |
348 | if cell_has_right_neighbor && !this_has_right_bottom_neighbor { |
349 | cell_border.right_bottom_corner = Some(c); |
350 | } |
351 | } |
352 | } |
353 | if let Some(c) = border.left.clone() { |
354 | if !cell_has_left_neighbor { |
355 | cell_border.left = Some(c.clone()); |
356 | |
357 | if cell_has_bottom_neighbor && !this_has_left_bottom_neighbor { |
358 | cell_border.left_bottom_corner = Some(c); |
359 | } |
360 | } |
361 | } |
362 | if let Some(c) = border.right.clone() { |
363 | if !cell_has_right_neighbor { |
364 | cell_border.right = Some(c.clone()); |
365 | |
366 | if cell_has_bottom_neighbor && !this_has_right_bottom_neighbor { |
367 | cell_border.right_bottom_corner = Some(c); |
368 | } |
369 | } |
370 | } |
371 | if let Some(c) = border.left_top_corner.clone() { |
372 | if !cell_has_left_neighbor && !cell_has_top_neighbor { |
373 | cell_border.left_top_corner = Some(c); |
374 | } |
375 | } |
376 | if let Some(c) = border.left_bottom_corner.clone() { |
377 | if !cell_has_left_neighbor && !cell_has_bottom_neighbor { |
378 | cell_border.left_bottom_corner = Some(c); |
379 | } |
380 | } |
381 | if let Some(c) = border.right_top_corner.clone() { |
382 | if !cell_has_right_neighbor && !cell_has_top_neighbor { |
383 | cell_border.right_top_corner = Some(c); |
384 | } |
385 | } |
386 | if let Some(c) = border.right_bottom_corner.clone() { |
387 | if !cell_has_right_neighbor && !cell_has_bottom_neighbor { |
388 | cell_border.right_bottom_corner = Some(c); |
389 | } |
390 | } |
391 | { |
392 | if !cell_has_bottom_neighbor { |
393 | if !cell_has_left_neighbor && this_has_left_top_neighbor { |
394 | if let Some(c) = border.right_top_corner.clone() { |
395 | cell_border.left_top_corner = Some(c); |
396 | } |
397 | } |
398 | |
399 | if cell_has_left_neighbor && this_has_left_bottom_neighbor { |
400 | if let Some(c) = border.left_top_corner.clone() { |
401 | cell_border.left_bottom_corner = Some(c); |
402 | } |
403 | } |
404 | |
405 | if !cell_has_right_neighbor && this_has_right_top_neighbor { |
406 | if let Some(c) = border.left_top_corner.clone() { |
407 | cell_border.right_top_corner = Some(c); |
408 | } |
409 | } |
410 | |
411 | if cell_has_right_neighbor && this_has_right_bottom_neighbor { |
412 | if let Some(c) = border.right_top_corner.clone() { |
413 | cell_border.right_bottom_corner = Some(c); |
414 | } |
415 | } |
416 | } |
417 | |
418 | if !cell_has_top_neighbor { |
419 | if !cell_has_left_neighbor && this_has_left_bottom_neighbor { |
420 | if let Some(c) = border.right_bottom_corner.clone() { |
421 | cell_border.left_bottom_corner = Some(c); |
422 | } |
423 | } |
424 | |
425 | if cell_has_left_neighbor && this_has_left_top_neighbor { |
426 | if let Some(c) = border.left_bottom_corner.clone() { |
427 | cell_border.left_top_corner = Some(c); |
428 | } |
429 | } |
430 | |
431 | if !cell_has_right_neighbor && this_has_right_bottom_neighbor { |
432 | if let Some(c) = border.left_bottom_corner.clone() { |
433 | cell_border.right_bottom_corner = Some(c); |
434 | } |
435 | } |
436 | |
437 | if cell_has_right_neighbor && this_has_right_top_neighbor { |
438 | if let Some(c) = border.right_bottom_corner.clone() { |
439 | cell_border.right_top_corner = Some(c); |
440 | } |
441 | } |
442 | } |
443 | } |
444 | |
445 | cell_border |
446 | } |
447 | |
448 | fn cell_has_top_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { |
449 | row > 0 && sector.contains(&(row - 1, col)) |
450 | } |
451 | |
452 | fn cell_has_bottom_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { |
453 | sector.contains(&(row + 1, col)) |
454 | } |
455 | |
456 | fn cell_has_left_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { |
457 | col > 0 && sector.contains(&(row, col - 1)) |
458 | } |
459 | |
460 | fn cell_has_right_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { |
461 | sector.contains(&(row, col + 1)) |
462 | } |
463 | |
464 | fn is_there_left_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { |
465 | row > 0 && col > 0 && sector.contains(&(row - 1, col - 1)) |
466 | } |
467 | |
468 | fn is_there_right_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { |
469 | row > 0 && sector.contains(&(row - 1, col + 1)) |
470 | } |
471 | |
472 | fn is_there_left_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { |
473 | col > 0 && sector.contains(&(row + 1, col - 1)) |
474 | } |
475 | |
476 | fn is_there_right_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { |
477 | sector.contains(&(row + 1, col + 1)) |
478 | } |
479 | |
480 | // fn split_tuple_border<A, B>(border: GridBorder<(A, B)>) -> (GridBorder<A>, GridBorder<B>) { |
481 | // macro_rules! set_border { |
482 | // ($border1:expr, $border2:expr => $symbl:expr) => { |
483 | // if let Some((x1, x2)) = $symbl { |
484 | // $border1 = Some(x1); |
485 | // $border2 = Some(x2); |
486 | // } |
487 | // }; |
488 | // } |
489 | |
490 | // let mut a = GridBorder::empty(); |
491 | // let mut b = GridBorder::empty(); |
492 | |
493 | // set_border!(a.bottom, b.bottom => border.bottom); |
494 | // set_border!(a.top, b.top => border.top); |
495 | // set_border!(a.left, b.left => border.left); |
496 | // set_border!(a.left_top_corner, b.left_top_corner => border.left_top_corner); |
497 | // set_border!(a.left_bottom_corner, b.left_bottom_corner => border.left_bottom_corner); |
498 | // set_border!(a.right, b.right => border.right); |
499 | // set_border!(a.right_top_corner, b.right_top_corner => border.right_top_corner); |
500 | // set_border!(a.right_bottom_corner, b.right_bottom_corner => border.right_bottom_corner); |
501 | |
502 | // (a, b) |
503 | // } |
504 | |
505 | #[cfg (test)] |
506 | mod tests { |
507 | use super::*; |
508 | |
509 | #[test ] |
510 | fn test_is_connected() { |
511 | assert!(is_cell_connected((0, 0), (0, 1))); |
512 | assert!(is_cell_connected((0, 0), (1, 0))); |
513 | assert!(!is_cell_connected((0, 0), (1, 1))); |
514 | |
515 | assert!(is_cell_connected((0, 1), (0, 0))); |
516 | assert!(is_cell_connected((1, 0), (0, 0))); |
517 | assert!(!is_cell_connected((1, 1), (0, 0))); |
518 | |
519 | assert!(is_cell_connected((1, 1), (0, 1))); |
520 | assert!(is_cell_connected((1, 1), (1, 0))); |
521 | assert!(is_cell_connected((1, 1), (2, 1))); |
522 | assert!(is_cell_connected((1, 1), (1, 2))); |
523 | assert!(!is_cell_connected((1, 1), (1, 1))); |
524 | } |
525 | } |
526 | |