1use crate::event::TableOutcome;
2use crate::{TableSelection, TableState};
3use rat_event::{HandleEvent, MouseOnly, Regular, ct_event, flow};
4use rat_focus::HasFocus;
5use rat_scrolled::ScrollAreaState;
6use rat_scrolled::event::ScrollOutcome;
7use ratatui_crossterm::crossterm::event::{Event, KeyModifiers};
8use std::cmp::{max, min};
9use std::collections::HashSet;
10use std::mem;
11
12#[derive(Debug, Default, Clone)]
20pub struct RowSetSelection {
21 pub anchor_row: Option<usize>,
23 pub lead_row: Option<usize>,
25 pub selected: HashSet<usize>,
32}
33
34impl TableSelection for RowSetSelection {
35 fn count(&self) -> usize {
36 let n = if let Some(anchor) = self.anchor_row {
37 if let Some(lead) = self.lead_row {
38 lead.abs_diff(anchor) + 1
39 } else {
40 0
41 }
42 } else {
43 0
44 };
45
46 n + self.selected.len()
47 }
48
49 #[allow(clippy::collapsible_else_if)]
50 #[allow(clippy::collapsible_if)]
51 fn is_selected_row(&self, row: usize) -> bool {
52 if let Some(mut anchor) = self.anchor_row {
53 if let Some(mut lead) = self.lead_row {
54 if lead < anchor {
55 mem::swap(&mut lead, &mut anchor);
56 }
57 if row >= anchor && row <= lead {
58 return true;
59 }
60 }
61 } else {
62 if let Some(lead) = self.lead_row {
63 if row == lead {
64 return true;
65 }
66 }
67 }
68
69 self.selected.contains(&row)
70 }
71
72 fn is_selected_column(&self, _column: usize) -> bool {
73 false
74 }
75
76 fn is_selected_cell(&self, _column: usize, _row: usize) -> bool {
77 false
78 }
79
80 fn lead_selection(&self) -> Option<(usize, usize)> {
81 self.lead_row.map(|srow| (0, srow))
82 }
83
84 fn validate_rows(&mut self, rows: usize) {
85 if let Some(lead_row) = self.lead_row {
86 if rows == 0 {
87 self.lead_row = None;
88 } else if lead_row >= rows {
89 self.lead_row = Some(rows - 1);
90 }
91 }
92 if let Some(anchor_row) = self.anchor_row {
93 if rows == 0 {
94 self.anchor_row = None;
95 } else if anchor_row >= rows {
96 self.anchor_row = Some(rows - 1);
97 }
98 }
99 self.selected.retain(|v| *v < rows);
100 }
101
102 fn validate_cols(&mut self, _cols: usize) {}
103
104 fn items_added(&mut self, pos: usize, n: usize) {
105 if let Some(lead_row) = self.lead_row {
106 if lead_row > pos {
107 self.lead_row = Some(lead_row + n);
108 }
109 }
110 if let Some(anchor_row) = self.anchor_row {
111 if anchor_row > pos {
112 self.anchor_row = Some(anchor_row + n);
113 }
114 }
115 let corr = self.selected.extract_if(|v| *v > pos).collect::<Vec<_>>();
116 for v in corr {
117 self.selected.insert(v + n);
118 }
119 }
120
121 fn items_removed(&mut self, pos: usize, n: usize, rows: usize) {
122 if let Some(lead_row) = self.lead_row {
123 if rows == 0 {
124 self.lead_row = None;
125 } else if lead_row == pos && lead_row + n >= rows {
126 self.lead_row = Some(rows.saturating_sub(1))
127 } else if lead_row > pos {
128 self.lead_row = Some(lead_row.saturating_sub(n).min(pos));
129 }
130 }
131 if let Some(anchor_row) = self.anchor_row {
132 if rows == 0 {
133 self.anchor_row = None;
134 } else if anchor_row == pos && anchor_row + n >= rows {
135 self.anchor_row = Some(rows.saturating_sub(1))
136 } else if anchor_row > pos {
137 self.anchor_row = Some(anchor_row.saturating_sub(n).min(pos));
138 }
139 }
140 let corr = self.selected.extract_if(|v| *v > pos).collect::<Vec<_>>();
141 for v in corr {
142 if rows == 0 {
143 } else if v == pos && v + n >= rows {
145 self.selected.insert(rows.saturating_sub(1));
146 } else if v > pos {
147 self.selected.insert(v.saturating_sub(n).min(pos));
148 }
149 }
150 }
151}
152
153impl RowSetSelection {
154 pub fn new() -> RowSetSelection {
156 RowSetSelection {
157 anchor_row: None,
158 lead_row: None,
159 selected: HashSet::new(),
160 }
161 }
162
163 pub fn clear(&mut self) {
165 self.anchor_row = None;
166 self.lead_row = None;
167 self.selected.clear();
168 }
169
170 pub fn lead(&self) -> Option<usize> {
172 self.lead_row
173 }
174
175 pub fn anchor(&self) -> Option<usize> {
177 self.anchor_row
178 }
179
180 pub fn selected(&self) -> HashSet<usize> {
182 let mut selected = self.selected.clone();
183 Self::fill(self.anchor_row, self.lead_row, &mut selected);
184 selected
185 }
186
187 pub fn has_selection(&self) -> bool {
189 self.lead_row.is_some() || !self.selected.is_empty()
190 }
191
192 pub fn set_lead(&mut self, lead: Option<usize>, extend: bool) -> bool {
194 let old_selection = (self.anchor_row, self.lead_row);
195 self.extend(extend);
196 self.lead_row = lead;
197 old_selection != (self.anchor_row, self.lead_row)
198 }
199
200 pub fn retire_selection(&mut self) {
202 Self::fill(self.anchor_row, self.lead_row, &mut self.selected);
203 self.anchor_row = None;
204 self.lead_row = None;
205 }
206
207 pub fn add(&mut self, idx: usize) {
210 self.selected.insert(idx);
211 }
212
213 pub fn remove(&mut self, idx: usize) {
216 self.selected.remove(&idx);
217 }
218
219 pub fn move_to(&mut self, lead: usize, max: usize, extend: bool) -> bool {
221 let old_selection = (self.anchor_row, self.lead_row);
222 self.extend(extend);
223 if lead <= max {
224 self.lead_row = Some(lead);
225 } else {
226 self.lead_row = Some(max);
227 }
228 old_selection != (self.anchor_row, self.lead_row)
229 }
230
231 pub fn move_down(&mut self, n: usize, maximum: usize, extend: bool) -> bool {
233 let old_selection = (self.anchor_row, self.lead_row);
234 self.extend(extend);
235 self.lead_row = Some(self.lead_row.map_or(0, |v| min(v + n, maximum)));
236 old_selection != (self.anchor_row, self.lead_row)
237 }
238
239 pub fn move_up(&mut self, n: usize, maximum: usize, extend: bool) -> bool {
241 let old_selection = (self.anchor_row, self.lead_row);
242 self.extend(extend);
243 self.lead_row = Some(self.lead_row.map_or(maximum, |v| v.saturating_sub(n)));
244 old_selection != (self.anchor_row, self.lead_row)
245 }
246
247 fn extend(&mut self, extend: bool) {
248 if extend {
249 if self.anchor_row.is_none() {
250 self.anchor_row = self.lead_row;
251 }
252 } else {
253 self.anchor_row = None;
254 self.selected.clear();
255 }
256 }
257
258 #[allow(clippy::collapsible_else_if)]
259 fn fill(anchor: Option<usize>, lead: Option<usize>, selection: &mut HashSet<usize>) {
260 if let Some(mut anchor) = anchor {
261 if let Some(mut lead) = lead {
262 if lead < anchor {
263 mem::swap(&mut lead, &mut anchor);
264 }
265
266 for n in anchor..=lead {
267 selection.insert(n);
268 }
269 }
270 } else {
271 if let Some(lead) = lead {
272 selection.insert(lead);
273 }
274 }
275 }
276}
277
278impl HandleEvent<Event, Regular, TableOutcome> for TableState<RowSetSelection> {
279 fn handle(&mut self, event: &Event, _: Regular) -> TableOutcome {
280 let res = if self.is_focused() {
281 match event {
282 ct_event!(keycode press Up) => {
283 if self.move_up(1, false) {
284 TableOutcome::Selected
285 } else {
286 TableOutcome::Unchanged
287 }
288 }
289 ct_event!(keycode press Down) => {
290 if self.move_down(1, false) {
291 TableOutcome::Selected
292 } else {
293 TableOutcome::Unchanged
294 }
295 }
296 ct_event!(keycode press CONTROL-Up)
297 | ct_event!(keycode press CONTROL-Home)
298 | ct_event!(keycode press Home) => {
299 if self.move_to(0, false) {
300 TableOutcome::Selected
301 } else {
302 TableOutcome::Unchanged
303 }
304 }
305 ct_event!(keycode press CONTROL-Down)
306 | ct_event!(keycode press CONTROL-End)
307 | ct_event!(keycode press End) => {
308 if self.move_to(self.rows.saturating_sub(1), false) {
309 TableOutcome::Selected
310 } else {
311 TableOutcome::Unchanged
312 }
313 }
314 ct_event!(keycode press PageUp) => {
315 if self.move_up(max(1, self.page_len().saturating_sub(1)), false) {
316 TableOutcome::Selected
317 } else {
318 TableOutcome::Unchanged
319 }
320 }
321 ct_event!(keycode press PageDown) => {
322 if self.move_down(max(1, self.page_len().saturating_sub(1)), false) {
323 TableOutcome::Selected
324 } else {
325 TableOutcome::Unchanged
326 }
327 }
328 ct_event!(keycode press SHIFT-Up) => {
329 if self.move_up(1, true) {
330 TableOutcome::Selected
331 } else {
332 TableOutcome::Unchanged
333 }
334 }
335 ct_event!(keycode press SHIFT-Down) => {
336 if self.move_down(1, true) {
337 TableOutcome::Selected
338 } else {
339 TableOutcome::Unchanged
340 }
341 }
342 ct_event!(keycode press CONTROL_SHIFT-Up)
343 | ct_event!(keycode press CONTROL_SHIFT-Home)
344 | ct_event!(keycode press SHIFT-Home) => {
345 if self.move_to(0, true) {
346 TableOutcome::Selected
347 } else {
348 TableOutcome::Unchanged
349 }
350 }
351 ct_event!(keycode press CONTROL_SHIFT-Down)
352 | ct_event!(keycode press CONTROL_SHIFT-End)
353 | ct_event!(keycode press SHIFT-End) => {
354 if self.move_to(self.rows.saturating_sub(1), true) {
355 TableOutcome::Selected
356 } else {
357 TableOutcome::Unchanged
358 }
359 }
360 ct_event!(keycode press SHIFT-PageUp) => {
361 if self.move_up(max(1, self.page_len().saturating_sub(1)), true) {
362 TableOutcome::Selected
363 } else {
364 TableOutcome::Unchanged
365 }
366 }
367 ct_event!(keycode press SHIFT-PageDown) => {
368 if self.move_down(max(1, self.page_len().saturating_sub(1)), true) {
369 TableOutcome::Selected
370 } else {
371 TableOutcome::Unchanged
372 }
373 }
374 ct_event!(keycode press Left) => {
375 if self.scroll_left(1) {
376 TableOutcome::Changed
377 } else {
378 TableOutcome::Unchanged
379 }
380 }
381 ct_event!(keycode press Right) => {
382 if self.scroll_right(1) {
383 TableOutcome::Changed
384 } else {
385 TableOutcome::Unchanged
386 }
387 }
388 ct_event!(keycode press CONTROL-Left) => {
389 if self.scroll_to_x(0) {
390 TableOutcome::Changed
391 } else {
392 TableOutcome::Unchanged
393 }
394 }
395 ct_event!(keycode press CONTROL-Right) => {
396 if self.scroll_to_x(self.x_max_offset()) {
397 TableOutcome::Changed
398 } else {
399 TableOutcome::Unchanged
400 }
401 }
402 _ => TableOutcome::Continue,
403 }
404 } else {
405 TableOutcome::Continue
406 };
407
408 if res == TableOutcome::Continue {
409 self.handle(event, MouseOnly)
410 } else {
411 res
412 }
413 }
414}
415
416impl HandleEvent<Event, MouseOnly, TableOutcome> for TableState<RowSetSelection> {
417 fn handle(&mut self, event: &Event, _: MouseOnly) -> TableOutcome {
418 flow!(match event {
419 ct_event!(mouse any for m) | ct_event!(mouse any CONTROL for m)
420 if self.mouse.drag(self.table_area, m)
421 || self.mouse.drag2(self.table_area, m, KeyModifiers::CONTROL) =>
422 {
423 if self.move_to(self.row_at_drag((m.column, m.row)), true) {
424 TableOutcome::Selected
425 } else {
426 TableOutcome::Unchanged
427 }
428 }
429 ct_event!(mouse down Left for column, row) => {
430 let pos = (*column, *row);
431 if self.table_area.contains(pos.into()) {
432 if let Some(new_row) = self.row_at_clicked(pos) {
433 if self.move_to(new_row, false) {
434 TableOutcome::Selected
435 } else {
436 TableOutcome::Unchanged
437 }
438 } else {
439 TableOutcome::Continue
440 }
441 } else {
442 TableOutcome::Continue
443 }
444 }
445 ct_event!(mouse down ALT-Left for column, row) => {
446 let pos = (*column, *row);
447 if self.area.contains(pos.into()) {
448 if let Some(new_row) = self.row_at_clicked(pos) {
449 if self.move_to(new_row, true) {
450 TableOutcome::Selected
451 } else {
452 TableOutcome::Unchanged
453 }
454 } else {
455 TableOutcome::Continue
456 }
457 } else {
458 TableOutcome::Continue
459 }
460 }
461 ct_event!(mouse down CONTROL-Left for column, row) => {
462 let pos = (*column, *row);
463 if self.area.contains(pos.into()) {
464 if let Some(new_row) = self.row_at_clicked(pos) {
465 self.retire_selection();
466 if self.selection.is_selected_row(new_row) {
467 self.selection.remove(new_row);
468 } else {
469 self.move_to(new_row, true);
470 }
471 TableOutcome::Selected
472 } else {
473 TableOutcome::Continue
474 }
475 } else {
476 TableOutcome::Continue
477 }
478 }
479 _ => TableOutcome::Continue,
480 });
481
482 let mut sas = ScrollAreaState::new()
483 .area(self.inner)
484 .h_scroll(&mut self.hscroll)
485 .v_scroll(&mut self.vscroll);
486
487 match sas.handle(event, MouseOnly) {
488 ScrollOutcome::Up(v) => {
489 if self.scroll_up(v) {
490 TableOutcome::Changed
491 } else {
492 TableOutcome::Unchanged
493 }
494 }
495 ScrollOutcome::Down(v) => {
496 if self.scroll_down(v) {
497 TableOutcome::Changed
498 } else {
499 TableOutcome::Unchanged
500 }
501 }
502 ScrollOutcome::VPos(v) => {
503 if self.set_row_offset(self.vscroll.limited_offset(v)) {
504 TableOutcome::Changed
505 } else {
506 TableOutcome::Unchanged
507 }
508 }
509 ScrollOutcome::Left(v) => {
510 if self.scroll_left(v) {
511 TableOutcome::Changed
512 } else {
513 TableOutcome::Unchanged
514 }
515 }
516 ScrollOutcome::Right(v) => {
517 if self.scroll_right(v) {
518 TableOutcome::Changed
519 } else {
520 TableOutcome::Unchanged
521 }
522 }
523 ScrollOutcome::HPos(v) => {
524 if self.set_x_offset(self.hscroll.limited_offset(v)) {
525 TableOutcome::Changed
526 } else {
527 TableOutcome::Unchanged
528 }
529 }
530
531 ScrollOutcome::Continue => TableOutcome::Continue,
532 ScrollOutcome::Unchanged => TableOutcome::Unchanged,
533 ScrollOutcome::Changed => TableOutcome::Changed,
534 }
535 }
536}
537
538pub fn handle_events(
542 state: &mut TableState<RowSetSelection>,
543 focus: bool,
544 event: &Event,
545) -> TableOutcome {
546 state.focus.set(focus);
547 state.handle(event, Regular)
548}
549
550pub fn handle_mouse_events(state: &mut TableState<RowSetSelection>, event: &Event) -> TableOutcome {
552 state.handle(event, MouseOnly)
553}