1use std::collections::HashSet;
7
8use papergrid::{records::Records, Entity, GridConfig, Position};
9
10use crate::{object::Object, Border, Table, TableOption};
11
12#[cfg(feature = "color")]
13use crate::style::BorderColored;
14
15#[derive(Debug)]
87pub struct Highlight<O> {
88 target: O,
89 border: Border,
90}
91
92impl<O> Highlight<O>
93where
94 O: Object,
95{
96 pub fn new(target: O, border: Border) -> Self {
100 Self { target, border }
101 }
102}
103
104impl<O> Highlight<O> {
105 #[cfg(feature = "color")]
107 #[cfg_attr(docsrs, doc(cfg(feature = "color")))]
108 pub fn colored(target: O, border: BorderColored) -> HighlightColored<O> {
109 HighlightColored { target, border }
110 }
111}
112
113impl<O, R> TableOption<R> for Highlight<O>
114where
115 O: Object,
116 R: Records,
117{
118 fn change(&mut self, table: &mut Table<R>) {
119 let (count_rows, count_cols) = table.shape();
120 let cells = self.target.cells(table);
121 let segments = split_segments(cells, count_rows, count_cols);
122
123 for sector in segments {
124 set_border(table.get_config_mut(), §or, self.border.clone());
125 }
126
127 table.destroy_width_cache();
128 table.destroy_height_cache();
129 }
130}
131
132#[cfg(feature = "color")]
136#[cfg_attr(docsrs, doc(cfg(feature = "color")))]
137#[derive(Debug)]
138pub struct HighlightColored<O> {
139 target: O,
140 border: BorderColored,
141}
142
143#[cfg(feature = "color")]
144impl<O, R> TableOption<R> for HighlightColored<O>
145where
146 O: Object,
147 R: Records,
148{
149 fn change(&mut self, table: &mut Table<R>) {
150 let (count_rows, count_cols) = table.shape();
151 let cells = self.target.cells(table);
152 let segments = split_segments(cells, count_rows, count_cols);
153
154 for sector in segments {
155 set_border_colored(table.get_config_mut(), sector, &self.border);
156 }
157
158 table.destroy_width_cache();
159 table.destroy_height_cache();
160 }
161}
162
163#[cfg(feature = "color")]
164fn set_border_colored(
165 cfg: &mut GridConfig,
166 sector: HashSet<(usize, usize)>,
167 border: &BorderColored,
168) {
169 if sector.is_empty() {
170 return;
171 }
172
173 let color = border.clone().into();
174 for &(row, col) in §or {
175 let border = build_cell_border(§or, (row, col), &color);
176 cfg.set_border((row, col), border);
177 }
178
179 let color = border.clone().into();
180 for &(row, col) in §or {
181 let border = build_cell_border(§or, (row, col), &color);
182 cfg.set_border_color((row, col), border);
183 }
184}
185
186fn split_segments(
187 cells: impl Iterator<Item = Entity>,
188 count_rows: usize,
189 count_cols: usize,
190) -> Vec<HashSet<(usize, usize)>> {
191 let mut segments: Vec<HashSet<(usize, usize)>> = Vec::new();
192 for entity in cells {
193 for cell in entity.iter(count_rows, count_cols) {
194 let found_segment = segments
195 .iter_mut()
196 .find(|s| s.iter().any(|&c| is_cell_connected(cell, c)));
197
198 match found_segment {
199 Some(segment) => {
200 segment.insert(cell);
201 }
202 None => {
203 let mut segment = HashSet::new();
204 segment.insert(cell);
205 segments.push(segment);
206 }
207 }
208 }
209 }
210
211 let mut squashed_segments: Vec<HashSet<(usize, usize)>> = Vec::new();
212 while !segments.is_empty() {
213 let mut segment = segments.remove(0);
214
215 let mut i = 0;
216 while i < segments.len() {
217 if is_segment_connected(&segment, &segments[i]) {
218 segment.extend(&segments[i]);
219 segments.remove(i);
220 } else {
221 i += 1;
222 }
223 }
224
225 squashed_segments.push(segment);
226 }
227
228 squashed_segments
229}
230
231fn is_cell_connected((row1, col1): (usize, usize), (row2, col2): (usize, usize)) -> bool {
232 if col1 == col2 && row1 == row2 + 1 {
233 return true;
234 }
235
236 if col1 == col2 && (row2 > 0 && row1 == row2 - 1) {
237 return true;
238 }
239
240 if row1 == row2 && col1 == col2 + 1 {
241 return true;
242 }
243
244 if row1 == row2 && (col2 > 0 && col1 == col2 - 1) {
245 return true;
246 }
247
248 false
249}
250
251fn is_segment_connected(
252 segment1: &HashSet<(usize, usize)>,
253 segment2: &HashSet<(usize, usize)>,
254) -> bool {
255 for &cell1 in segment1.iter() {
256 for &cell2 in segment2.iter() {
257 if is_cell_connected(cell1, cell2) {
258 return true;
259 }
260 }
261 }
262
263 false
264}
265
266fn set_border(cfg: &mut GridConfig, sector: &HashSet<(usize, usize)>, border: Border) {
267 if sector.is_empty() {
268 return;
269 }
270
271 if let Some(border) = border.into() {
272 for &pos in sector {
273 let border = build_cell_border(sector, pos, &border);
274 cfg.set_border(pos, border);
275 }
276 }
277}
278
279fn build_cell_border<T>(
280 sector: &HashSet<(usize, usize)>,
281 (row, col): Position,
282 border: &papergrid::Border<T>,
283) -> papergrid::Border<T>
284where
285 T: Default + Clone,
286{
287 let cell_has_top_neighbor = cell_has_top_neighbor(sector, row, col);
288 let cell_has_bottom_neighbor = cell_has_bottom_neighbor(sector, row, col);
289 let cell_has_left_neighbor = cell_has_left_neighbor(sector, row, col);
290 let cell_has_right_neighbor = cell_has_right_neighbor(sector, row, col);
291
292 let this_has_left_top_neighbor = is_there_left_top_cell(sector, row, col);
293 let this_has_right_top_neighbor = is_there_right_top_cell(sector, row, col);
294 let this_has_left_bottom_neighbor = is_there_left_bottom_cell(sector, row, col);
295 let this_has_right_bottom_neighbor = is_there_right_bottom_cell(sector, row, col);
296
297 let mut cell_border = papergrid::Border::default();
298 if let Some(c) = border.top.clone() {
299 if !cell_has_top_neighbor {
300 cell_border.top = Some(c.clone());
301
302 if cell_has_right_neighbor && !this_has_right_top_neighbor {
303 cell_border.right_top_corner = Some(c);
304 }
305 }
306 }
307 if let Some(c) = border.bottom.clone() {
308 if !cell_has_bottom_neighbor {
309 cell_border.bottom = Some(c.clone());
310
311 if cell_has_right_neighbor && !this_has_right_bottom_neighbor {
312 cell_border.right_bottom_corner = Some(c);
313 }
314 }
315 }
316 if let Some(c) = border.left.clone() {
317 if !cell_has_left_neighbor {
318 cell_border.left = Some(c.clone());
319
320 if cell_has_bottom_neighbor && !this_has_left_bottom_neighbor {
321 cell_border.left_bottom_corner = Some(c);
322 }
323 }
324 }
325 if let Some(c) = border.right.clone() {
326 if !cell_has_right_neighbor {
327 cell_border.right = Some(c.clone());
328
329 if cell_has_bottom_neighbor && !this_has_right_bottom_neighbor {
330 cell_border.right_bottom_corner = Some(c);
331 }
332 }
333 }
334 if let Some(c) = border.left_top_corner.clone() {
335 if !cell_has_left_neighbor && !cell_has_top_neighbor {
336 cell_border.left_top_corner = Some(c);
337 }
338 }
339 if let Some(c) = border.left_bottom_corner.clone() {
340 if !cell_has_left_neighbor && !cell_has_bottom_neighbor {
341 cell_border.left_bottom_corner = Some(c);
342 }
343 }
344 if let Some(c) = border.right_top_corner.clone() {
345 if !cell_has_right_neighbor && !cell_has_top_neighbor {
346 cell_border.right_top_corner = Some(c);
347 }
348 }
349 if let Some(c) = border.right_bottom_corner.clone() {
350 if !cell_has_right_neighbor && !cell_has_bottom_neighbor {
351 cell_border.right_bottom_corner = Some(c);
352 }
353 }
354 {
355 if !cell_has_bottom_neighbor {
356 if !cell_has_left_neighbor && this_has_left_top_neighbor {
357 if let Some(c) = border.right_top_corner.clone() {
358 cell_border.left_top_corner = Some(c);
359 }
360 }
361
362 if cell_has_left_neighbor && this_has_left_bottom_neighbor {
363 if let Some(c) = border.left_top_corner.clone() {
364 cell_border.left_bottom_corner = Some(c);
365 }
366 }
367
368 if !cell_has_right_neighbor && this_has_right_top_neighbor {
369 if let Some(c) = border.left_top_corner.clone() {
370 cell_border.right_top_corner = Some(c);
371 }
372 }
373
374 if cell_has_right_neighbor && this_has_right_bottom_neighbor {
375 if let Some(c) = border.right_top_corner.clone() {
376 cell_border.right_bottom_corner = Some(c);
377 }
378 }
379 }
380
381 if !cell_has_top_neighbor {
382 if !cell_has_left_neighbor && this_has_left_bottom_neighbor {
383 if let Some(c) = border.right_bottom_corner.clone() {
384 cell_border.left_bottom_corner = Some(c);
385 }
386 }
387
388 if cell_has_left_neighbor && this_has_left_top_neighbor {
389 if let Some(c) = border.left_bottom_corner.clone() {
390 cell_border.left_top_corner = Some(c);
391 }
392 }
393
394 if !cell_has_right_neighbor && this_has_right_bottom_neighbor {
395 if let Some(c) = border.left_bottom_corner.clone() {
396 cell_border.right_bottom_corner = Some(c);
397 }
398 }
399
400 if cell_has_right_neighbor && this_has_right_top_neighbor {
401 if let Some(c) = border.right_bottom_corner.clone() {
402 cell_border.right_top_corner = Some(c);
403 }
404 }
405 }
406 }
407
408 cell_border
409}
410
411fn cell_has_top_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
412 row > 0 && sector.contains(&(row - 1, col))
413}
414
415fn cell_has_bottom_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
416 sector.contains(&(row + 1, col))
417}
418
419fn cell_has_left_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
420 col > 0 && sector.contains(&(row, col - 1))
421}
422
423fn cell_has_right_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
424 sector.contains(&(row, col + 1))
425}
426
427fn is_there_left_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
428 row > 0 && col > 0 && sector.contains(&(row - 1, col - 1))
429}
430
431fn is_there_right_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
432 row > 0 && sector.contains(&(row - 1, col + 1))
433}
434
435fn is_there_left_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
436 col > 0 && sector.contains(&(row + 1, col - 1))
437}
438
439fn is_there_right_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool {
440 sector.contains(&(row + 1, col + 1))
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446
447 #[test]
448 fn test_is_connected() {
449 assert!(is_cell_connected((0, 0), (0, 1)));
450 assert!(is_cell_connected((0, 0), (1, 0)));
451 assert!(!is_cell_connected((0, 0), (1, 1)));
452
453 assert!(is_cell_connected((0, 1), (0, 0)));
454 assert!(is_cell_connected((1, 0), (0, 0)));
455 assert!(!is_cell_connected((1, 1), (0, 0)));
456
457 assert!(is_cell_connected((1, 1), (0, 1)));
458 assert!(is_cell_connected((1, 1), (1, 0)));
459 assert!(is_cell_connected((1, 1), (2, 1)));
460 assert!(is_cell_connected((1, 1), (1, 2)));
461 assert!(!is_cell_connected((1, 1), (1, 1)));
462 }
463}