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