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 if !self.has_mouse_focus() {
419 return TableOutcome::Continue;
420 }
421
422 flow!(match event {
423 ct_event!(mouse any for m) | ct_event!(mouse any CONTROL for m)
424 if self.mouse.drag(self.table_area, m)
425 || self.mouse.drag2(self.table_area, m, KeyModifiers::CONTROL) =>
426 {
427 if self.move_to(self.row_at_drag((m.column, m.row)), true) {
428 TableOutcome::Selected
429 } else {
430 TableOutcome::Unchanged
431 }
432 }
433 ct_event!(mouse down Left for column, row) => {
434 let pos = (*column, *row);
435 if self.table_area.contains(pos.into()) {
436 if let Some(new_row) = self.row_at_clicked(pos) {
437 if self.move_to(new_row, false) {
438 TableOutcome::Selected
439 } else {
440 TableOutcome::Unchanged
441 }
442 } else {
443 TableOutcome::Continue
444 }
445 } else {
446 TableOutcome::Continue
447 }
448 }
449 ct_event!(mouse down ALT-Left for column, row) => {
450 let pos = (*column, *row);
451 if self.area.contains(pos.into()) {
452 if let Some(new_row) = self.row_at_clicked(pos) {
453 if self.move_to(new_row, true) {
454 TableOutcome::Selected
455 } else {
456 TableOutcome::Unchanged
457 }
458 } else {
459 TableOutcome::Continue
460 }
461 } else {
462 TableOutcome::Continue
463 }
464 }
465 ct_event!(mouse down CONTROL-Left for column, row) => {
466 let pos = (*column, *row);
467 if self.area.contains(pos.into()) {
468 if let Some(new_row) = self.row_at_clicked(pos) {
469 self.retire_selection();
470 if self.selection.is_selected_row(new_row) {
471 self.selection.remove(new_row);
472 } else {
473 self.move_to(new_row, true);
474 }
475 TableOutcome::Selected
476 } else {
477 TableOutcome::Continue
478 }
479 } else {
480 TableOutcome::Continue
481 }
482 }
483 _ => TableOutcome::Continue,
484 });
485
486 let mut sas = ScrollAreaState::new()
487 .area(self.inner)
488 .h_scroll(&mut self.hscroll)
489 .v_scroll(&mut self.vscroll);
490
491 match sas.handle(event, MouseOnly) {
492 ScrollOutcome::Up(v) => {
493 if self.scroll_up(v) {
494 TableOutcome::Changed
495 } else {
496 TableOutcome::Unchanged
497 }
498 }
499 ScrollOutcome::Down(v) => {
500 if self.scroll_down(v) {
501 TableOutcome::Changed
502 } else {
503 TableOutcome::Unchanged
504 }
505 }
506 ScrollOutcome::VPos(v) => {
507 if self.set_row_offset(self.vscroll.limited_offset(v)) {
508 TableOutcome::Changed
509 } else {
510 TableOutcome::Unchanged
511 }
512 }
513 ScrollOutcome::Left(v) => {
514 if self.scroll_left(v) {
515 TableOutcome::Changed
516 } else {
517 TableOutcome::Unchanged
518 }
519 }
520 ScrollOutcome::Right(v) => {
521 if self.scroll_right(v) {
522 TableOutcome::Changed
523 } else {
524 TableOutcome::Unchanged
525 }
526 }
527 ScrollOutcome::HPos(v) => {
528 if self.set_x_offset(self.hscroll.limited_offset(v)) {
529 TableOutcome::Changed
530 } else {
531 TableOutcome::Unchanged
532 }
533 }
534
535 ScrollOutcome::Continue => TableOutcome::Continue,
536 ScrollOutcome::Unchanged => TableOutcome::Unchanged,
537 ScrollOutcome::Changed => TableOutcome::Changed,
538 }
539 }
540}
541
542pub fn handle_events(
546 state: &mut TableState<RowSetSelection>,
547 focus: bool,
548 event: &Event,
549) -> TableOutcome {
550 state.focus.set(focus);
551 state.handle(event, Regular)
552}
553
554pub fn handle_mouse_events(state: &mut TableState<RowSetSelection>, event: &Event) -> TableOutcome {
556 state.handle(event, MouseOnly)
557}