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