1use crate::event::TableOutcome;
2use crate::{TableSelection, TableState};
3use crossterm::event::KeyModifiers;
4use rat_event::{HandleEvent, MouseOnly, Regular, ct_event, flow};
5use rat_focus::HasFocus;
6use rat_scrolled::ScrollAreaState;
7use rat_scrolled::event::ScrollOutcome;
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
85impl RowSetSelection {
86 pub fn new() -> RowSetSelection {
88 RowSetSelection {
89 anchor_row: None,
90 lead_row: None,
91 selected: HashSet::new(),
92 }
93 }
94
95 pub fn clear(&mut self) {
97 self.anchor_row = None;
98 self.lead_row = None;
99 self.selected.clear();
100 }
101
102 pub fn lead(&self) -> Option<usize> {
104 self.lead_row
105 }
106
107 pub fn anchor(&self) -> Option<usize> {
109 self.anchor_row
110 }
111
112 pub fn selected(&self) -> HashSet<usize> {
114 let mut selected = self.selected.clone();
115 Self::fill(self.anchor_row, self.lead_row, &mut selected);
116 selected
117 }
118
119 pub fn has_selection(&self) -> bool {
121 self.lead_row.is_some() || !self.selected.is_empty()
122 }
123
124 pub fn set_lead(&mut self, lead: Option<usize>, extend: bool) -> bool {
126 let old_selection = (self.anchor_row, self.lead_row);
127 self.extend(extend);
128 self.lead_row = lead;
129 old_selection != (self.anchor_row, self.lead_row)
130 }
131
132 pub fn retire_selection(&mut self) {
134 Self::fill(self.anchor_row, self.lead_row, &mut self.selected);
135 self.anchor_row = None;
136 self.lead_row = None;
137 }
138
139 pub fn add(&mut self, idx: usize) {
142 self.selected.insert(idx);
143 }
144
145 pub fn remove(&mut self, idx: usize) {
148 self.selected.remove(&idx);
149 }
150
151 pub fn move_to(&mut self, lead: usize, max: usize, extend: bool) -> bool {
153 let old_selection = (self.anchor_row, self.lead_row);
154 self.extend(extend);
155 if lead <= max {
156 self.lead_row = Some(lead);
157 } else {
158 self.lead_row = Some(max);
159 }
160 old_selection != (self.anchor_row, self.lead_row)
161 }
162
163 pub fn move_down(&mut self, n: usize, maximum: usize, extend: bool) -> bool {
165 let old_selection = (self.anchor_row, self.lead_row);
166 self.extend(extend);
167 self.lead_row = Some(self.lead_row.map_or(0, |v| min(v + n, maximum)));
168 old_selection != (self.anchor_row, self.lead_row)
169 }
170
171 pub fn move_up(&mut self, n: usize, maximum: usize, extend: bool) -> bool {
173 let old_selection = (self.anchor_row, self.lead_row);
174 self.extend(extend);
175 self.lead_row = Some(self.lead_row.map_or(maximum, |v| v.saturating_sub(n)));
176 old_selection != (self.anchor_row, self.lead_row)
177 }
178
179 fn extend(&mut self, extend: bool) {
180 if extend {
181 if self.anchor_row.is_none() {
182 self.anchor_row = self.lead_row;
183 }
184 } else {
185 self.anchor_row = None;
186 self.selected.clear();
187 }
188 }
189
190 #[allow(clippy::collapsible_else_if)]
191 fn fill(anchor: Option<usize>, lead: Option<usize>, selection: &mut HashSet<usize>) {
192 if let Some(mut anchor) = anchor {
193 if let Some(mut lead) = lead {
194 if lead < anchor {
195 mem::swap(&mut lead, &mut anchor);
196 }
197
198 for n in anchor..=lead {
199 selection.insert(n);
200 }
201 }
202 } else {
203 if let Some(lead) = lead {
204 selection.insert(lead);
205 }
206 }
207 }
208}
209
210impl HandleEvent<crossterm::event::Event, Regular, TableOutcome> for TableState<RowSetSelection> {
211 fn handle(&mut self, event: &crossterm::event::Event, _: Regular) -> TableOutcome {
212 let res = if self.is_focused() {
213 match event {
214 ct_event!(keycode press Up) => {
215 if self.move_up(1, false) {
216 TableOutcome::Selected
217 } else {
218 TableOutcome::Unchanged
219 }
220 }
221 ct_event!(keycode press Down) => {
222 if self.move_down(1, false) {
223 TableOutcome::Selected
224 } else {
225 TableOutcome::Unchanged
226 }
227 }
228 ct_event!(keycode press CONTROL-Up)
229 | ct_event!(keycode press CONTROL-Home)
230 | ct_event!(keycode press Home) => {
231 if self.move_to(0, false) {
232 TableOutcome::Selected
233 } else {
234 TableOutcome::Unchanged
235 }
236 }
237 ct_event!(keycode press CONTROL-Down)
238 | ct_event!(keycode press CONTROL-End)
239 | ct_event!(keycode press End) => {
240 if self.move_to(self.rows.saturating_sub(1), false) {
241 TableOutcome::Selected
242 } else {
243 TableOutcome::Unchanged
244 }
245 }
246 ct_event!(keycode press PageUp) => {
247 if self.move_up(max(1, self.page_len().saturating_sub(1)), false) {
248 TableOutcome::Selected
249 } else {
250 TableOutcome::Unchanged
251 }
252 }
253 ct_event!(keycode press PageDown) => {
254 if self.move_down(max(1, self.page_len().saturating_sub(1)), false) {
255 TableOutcome::Selected
256 } else {
257 TableOutcome::Unchanged
258 }
259 }
260 ct_event!(keycode press SHIFT-Up) => {
261 if self.move_up(1, true) {
262 TableOutcome::Selected
263 } else {
264 TableOutcome::Unchanged
265 }
266 }
267 ct_event!(keycode press SHIFT-Down) => {
268 if self.move_down(1, true) {
269 TableOutcome::Selected
270 } else {
271 TableOutcome::Unchanged
272 }
273 }
274 ct_event!(keycode press CONTROL_SHIFT-Up)
275 | ct_event!(keycode press CONTROL_SHIFT-Home)
276 | ct_event!(keycode press SHIFT-Home) => {
277 if self.move_to(0, true) {
278 TableOutcome::Selected
279 } else {
280 TableOutcome::Unchanged
281 }
282 }
283 ct_event!(keycode press CONTROL_SHIFT-Down)
284 | ct_event!(keycode press CONTROL_SHIFT-End)
285 | ct_event!(keycode press SHIFT-End) => {
286 if self.move_to(self.rows.saturating_sub(1), true) {
287 TableOutcome::Selected
288 } else {
289 TableOutcome::Unchanged
290 }
291 }
292 ct_event!(keycode press SHIFT-PageUp) => {
293 if self.move_up(max(1, self.page_len().saturating_sub(1)), true) {
294 TableOutcome::Selected
295 } else {
296 TableOutcome::Unchanged
297 }
298 }
299 ct_event!(keycode press SHIFT-PageDown) => {
300 if self.move_down(max(1, self.page_len().saturating_sub(1)), true) {
301 TableOutcome::Selected
302 } else {
303 TableOutcome::Unchanged
304 }
305 }
306 ct_event!(keycode press Left) => {
307 if self.scroll_left(1) {
308 TableOutcome::Changed
309 } else {
310 TableOutcome::Unchanged
311 }
312 }
313 ct_event!(keycode press Right) => {
314 if self.scroll_right(1) {
315 TableOutcome::Changed
316 } else {
317 TableOutcome::Unchanged
318 }
319 }
320 ct_event!(keycode press CONTROL-Left) => {
321 if self.scroll_to_x(0) {
322 TableOutcome::Changed
323 } else {
324 TableOutcome::Unchanged
325 }
326 }
327 ct_event!(keycode press CONTROL-Right) => {
328 if self.scroll_to_x(self.x_max_offset()) {
329 TableOutcome::Changed
330 } else {
331 TableOutcome::Unchanged
332 }
333 }
334 _ => TableOutcome::Continue,
335 }
336 } else {
337 TableOutcome::Continue
338 };
339
340 if res == TableOutcome::Continue {
341 self.handle(event, MouseOnly)
342 } else {
343 res
344 }
345 }
346}
347
348impl HandleEvent<crossterm::event::Event, MouseOnly, TableOutcome> for TableState<RowSetSelection> {
349 fn handle(&mut self, event: &crossterm::event::Event, _: MouseOnly) -> TableOutcome {
350 flow!(match event {
351 ct_event!(mouse any for m) | ct_event!(mouse any CONTROL for m)
352 if self.mouse.drag(self.table_area, m)
353 || self.mouse.drag2(self.table_area, m, KeyModifiers::CONTROL) =>
354 {
355 if self.move_to(self.row_at_drag((m.column, m.row)), true) {
356 TableOutcome::Selected
357 } else {
358 TableOutcome::Unchanged
359 }
360 }
361 ct_event!(mouse down Left for column, row) => {
362 let pos = (*column, *row);
363 if self.table_area.contains(pos.into()) {
364 if let Some(new_row) = self.row_at_clicked(pos) {
365 if self.move_to(new_row, false) {
366 TableOutcome::Selected
367 } else {
368 TableOutcome::Unchanged
369 }
370 } else {
371 TableOutcome::Continue
372 }
373 } else {
374 TableOutcome::Continue
375 }
376 }
377 ct_event!(mouse down ALT-Left for column, row) => {
378 let pos = (*column, *row);
379 if self.area.contains(pos.into()) {
380 if let Some(new_row) = self.row_at_clicked(pos) {
381 if self.move_to(new_row, true) {
382 TableOutcome::Selected
383 } else {
384 TableOutcome::Unchanged
385 }
386 } else {
387 TableOutcome::Continue
388 }
389 } else {
390 TableOutcome::Continue
391 }
392 }
393 ct_event!(mouse down CONTROL-Left for column, row) => {
394 let pos = (*column, *row);
395 if self.area.contains(pos.into()) {
396 if let Some(new_row) = self.row_at_clicked(pos) {
397 self.retire_selection();
398 if self.selection.is_selected_row(new_row) {
399 self.selection.remove(new_row);
400 } else {
401 self.move_to(new_row, true);
402 }
403 TableOutcome::Selected
404 } else {
405 TableOutcome::Continue
406 }
407 } else {
408 TableOutcome::Continue
409 }
410 }
411 _ => TableOutcome::Continue,
412 });
413
414 let mut sas = ScrollAreaState::new()
415 .area(self.inner)
416 .h_scroll(&mut self.hscroll)
417 .v_scroll(&mut self.vscroll);
418
419 match sas.handle(event, MouseOnly) {
420 ScrollOutcome::Up(v) => {
421 if self.scroll_up(v) {
422 TableOutcome::Changed
423 } else {
424 TableOutcome::Unchanged
425 }
426 }
427 ScrollOutcome::Down(v) => {
428 if self.scroll_down(v) {
429 TableOutcome::Changed
430 } else {
431 TableOutcome::Unchanged
432 }
433 }
434 ScrollOutcome::VPos(v) => {
435 if self.set_row_offset(self.vscroll.limited_offset(v)) {
436 TableOutcome::Changed
437 } else {
438 TableOutcome::Unchanged
439 }
440 }
441 ScrollOutcome::Left(v) => {
442 if self.scroll_left(v) {
443 TableOutcome::Changed
444 } else {
445 TableOutcome::Unchanged
446 }
447 }
448 ScrollOutcome::Right(v) => {
449 if self.scroll_right(v) {
450 TableOutcome::Changed
451 } else {
452 TableOutcome::Unchanged
453 }
454 }
455 ScrollOutcome::HPos(v) => {
456 if self.set_x_offset(self.hscroll.limited_offset(v)) {
457 TableOutcome::Changed
458 } else {
459 TableOutcome::Unchanged
460 }
461 }
462
463 ScrollOutcome::Continue => TableOutcome::Continue,
464 ScrollOutcome::Unchanged => TableOutcome::Unchanged,
465 ScrollOutcome::Changed => TableOutcome::Changed,
466 }
467 }
468}
469
470pub fn handle_events(
474 state: &mut TableState<RowSetSelection>,
475 focus: bool,
476 event: &crossterm::event::Event,
477) -> TableOutcome {
478 state.focus.set(focus);
479 state.handle(event, Regular)
480}
481
482pub fn handle_mouse_events(
484 state: &mut TableState<RowSetSelection>,
485 event: &crossterm::event::Event,
486) -> TableOutcome {
487 state.handle(event, MouseOnly)
488}