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