1use crate::_private::NonExhaustive;
6use crate::event::util::MouseFlags;
7use crate::list::selection::{RowSelection, RowSetSelection};
8use crate::util::{fallback_select_style, revert_style};
9use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
10use rat_reloc::{RelocatableState, relocate_area, relocate_areas};
11use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
12use ratatui::buffer::Buffer;
13use ratatui::layout::Rect;
14use ratatui::style::Style;
15use ratatui::widgets::{Block, HighlightSpacing, ListDirection, ListItem, StatefulWidget};
16use std::cmp::min;
17use std::collections::HashSet;
18use std::marker::PhantomData;
19
20pub mod edit;
21
22pub trait ListSelection {
24 fn count(&self) -> usize;
26
27 fn is_selected(&self, n: usize) -> bool;
29
30 fn lead_selection(&self) -> Option<usize>;
32
33 fn scroll_selected(&self) -> bool {
35 false
36 }
37}
38
39#[derive(Debug, Default, Clone)]
44pub struct List<'a, Selection> {
45 block: Option<Block<'a>>,
46 scroll: Option<Scroll<'a>>,
47
48 items: Vec<ListItem<'a>>,
49
50 style: Style,
51 select_style: Option<Style>,
52 focus_style: Option<Style>,
53 direction: ListDirection,
54 highlight_spacing: HighlightSpacing,
55 highlight_symbol: Option<&'static str>,
56 repeat_highlight_symbol: bool,
57 scroll_padding: usize,
58
59 _phantom: PhantomData<Selection>,
60}
61
62#[derive(Debug, Clone)]
64pub struct ListStyle {
65 pub style: Style,
67 pub select: Option<Style>,
69 pub focus: Option<Style>,
71
72 pub block: Option<Block<'static>>,
73 pub scroll: Option<ScrollStyle>,
74
75 pub highlight_spacing: Option<HighlightSpacing>,
76 pub highlight_symbol: Option<&'static str>,
77 pub repeat_highlight_symbol: Option<bool>,
78 pub scroll_padding: Option<usize>,
79
80 pub non_exhaustive: NonExhaustive,
81}
82
83#[derive(Debug, PartialEq, Eq)]
85pub struct ListState<Selection> {
86 pub area: Rect,
89 pub inner: Rect,
92 pub row_areas: Vec<Rect>,
95
96 pub rows: usize,
99 pub scroll: ScrollState,
102
103 pub focus: FocusFlag,
106 pub selection: Selection,
109
110 pub mouse: MouseFlags,
113}
114
115impl Default for ListStyle {
116 fn default() -> Self {
117 Self {
118 style: Default::default(),
119 select: None,
120 focus: None,
121 block: None,
122 scroll: None,
123 highlight_spacing: None,
124 highlight_symbol: None,
125 repeat_highlight_symbol: None,
126 scroll_padding: None,
127 non_exhaustive: NonExhaustive,
128 }
129 }
130}
131
132impl<'a, Selection> List<'a, Selection> {
133 pub fn new<T>(items: T) -> Self
135 where
136 T: IntoIterator,
137 T::Item: Into<ListItem<'a>>,
138 {
139 let items = items.into_iter().map(|v| v.into()).collect();
140
141 Self {
142 block: None,
143 scroll: None,
144 items,
145 style: Default::default(),
146 select_style: Default::default(),
147 focus_style: Default::default(),
148 direction: Default::default(),
149 highlight_spacing: Default::default(),
150 highlight_symbol: Default::default(),
151 repeat_highlight_symbol: false,
152 scroll_padding: 0,
153 _phantom: Default::default(),
154 }
155 }
156
157 pub fn items<T>(mut self, items: T) -> Self
159 where
160 T: IntoIterator,
161 T::Item: Into<ListItem<'a>>,
162 {
163 let items = items.into_iter().map(|v| v.into()).collect();
164 self.items = items;
165 self
166 }
167
168 #[inline]
170 pub fn block(mut self, block: Block<'a>) -> Self {
171 self.block = Some(block);
172 self.block = self.block.map(|v| v.style(self.style));
173 self
174 }
175
176 #[inline]
178 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
179 self.scroll = Some(scroll);
180 self
181 }
182
183 #[inline]
185 pub fn styles_opt(self, styles: Option<ListStyle>) -> Self {
186 if let Some(styles) = styles {
187 self.styles(styles)
188 } else {
189 self
190 }
191 }
192
193 #[inline]
195 pub fn styles(mut self, styles: ListStyle) -> Self {
196 self.style = styles.style;
197 if styles.select.is_some() {
198 self.select_style = styles.select;
199 }
200 if styles.focus.is_some() {
201 self.focus_style = styles.focus;
202 }
203 if let Some(styles) = styles.scroll {
204 self.scroll = self.scroll.map(|v| v.styles(styles));
205 }
206 if let Some(block) = styles.block {
207 self.block = Some(block);
208 }
209 if let Some(highlight_spacing) = styles.highlight_spacing {
210 self.highlight_spacing = highlight_spacing;
211 }
212 if let Some(highlight_symbol) = styles.highlight_symbol {
213 self.highlight_symbol = Some(highlight_symbol);
214 }
215 if let Some(repeat_highlight_symbol) = styles.repeat_highlight_symbol {
216 self.repeat_highlight_symbol = repeat_highlight_symbol;
217 }
218 if let Some(scroll_padding) = styles.scroll_padding {
219 self.scroll_padding = scroll_padding;
220 }
221 self.block = self.block.map(|v| v.style(self.style));
222 self
223 }
224
225 #[inline]
227 pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
228 self.style = style.into();
229 self.block = self.block.map(|v| v.style(self.style));
230 self
231 }
232
233 #[inline]
235 pub fn select_style<S: Into<Style>>(mut self, select_style: S) -> Self {
236 self.select_style = Some(select_style.into());
237 self
238 }
239
240 #[inline]
242 pub fn focus_style<S: Into<Style>>(mut self, focus_style: S) -> Self {
243 self.focus_style = Some(focus_style.into());
244 self
245 }
246
247 #[inline]
249 pub fn direction(mut self, direction: ListDirection) -> Self {
250 self.direction = direction;
251 self
252 }
253
254 #[inline]
256 pub fn len(&self) -> usize {
257 self.items.len()
258 }
259
260 #[inline]
262 pub fn is_empty(&self) -> bool {
263 self.items.is_empty()
264 }
265}
266
267impl<'a, Item, Selection> FromIterator<Item> for List<'a, Selection>
268where
269 Item: Into<ListItem<'a>>,
270{
271 fn from_iter<Iter: IntoIterator<Item = Item>>(iter: Iter) -> Self {
272 Self::new(iter)
273 }
274}
275
276impl<Selection: ListSelection> StatefulWidget for List<'_, Selection> {
277 type State = ListState<Selection>;
278
279 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
280 render_list(self, area, buf, state)
281 }
282}
283
284fn render_list<Selection: ListSelection>(
285 widget: List<'_, Selection>,
286 area: Rect,
287 buf: &mut Buffer,
288 state: &mut ListState<Selection>,
289) {
290 state.area = area;
291 state.rows = widget.items.len();
292
293 let sa = ScrollArea::new()
294 .block(widget.block.as_ref())
295 .v_scroll(widget.scroll.as_ref());
296 state.inner = sa.inner(area, None, Some(&state.scroll));
297
298 state.row_areas.clear();
300 let mut item_area = Rect::new(state.inner.x, state.inner.y, state.inner.width, 1);
301 let mut total_height = 0;
302 for item in widget.items.iter().skip(state.offset()) {
303 item_area.height = item.height() as u16;
304
305 state.row_areas.push(item_area);
306
307 item_area.y += item_area.height;
308 total_height += item_area.height;
309 if total_height >= state.inner.height {
310 break;
311 }
312 }
313 if total_height < state.inner.height {
314 state.scroll.set_page_len(
315 state.row_areas.len() + state.inner.height as usize - total_height as usize,
316 );
317 } else {
318 state.scroll.set_page_len(state.row_areas.len());
319 }
320
321 let focus_style = widget.focus_style.unwrap_or(revert_style(widget.style));
322 let select_style = widget
323 .select_style
324 .unwrap_or(fallback_select_style(widget.style));
325
326 let mut n = 0;
328 let mut height = 0;
329 for item in widget.items.iter().rev() {
330 height += item.height();
331 if height > state.inner.height as usize {
332 break;
333 }
334 n += 1;
335 }
336 state.scroll.set_max_offset(state.rows.saturating_sub(n));
337
338 let (style, select_style) = if state.is_focused() {
339 (widget.style, focus_style)
340 } else {
341 (widget.style, select_style)
342 };
343
344 sa.render(
345 area,
346 buf,
347 &mut ScrollAreaState::new().v_scroll(&mut state.scroll),
348 );
349
350 let items = widget
352 .items
353 .into_iter()
354 .enumerate()
355 .map(|(i, v)| {
356 if state.selection.is_selected(i) {
357 v.style(style.patch(select_style))
358 } else {
359 v.style(style)
360 }
361 })
362 .collect::<Vec<_>>();
363
364 let mut list_state = ratatui::widgets::ListState::default().with_offset(state.scroll.offset());
365
366 let mut list = ratatui::widgets::List::default()
367 .items(items)
368 .style(widget.style)
369 .direction(widget.direction)
370 .highlight_spacing(widget.highlight_spacing)
371 .repeat_highlight_symbol(widget.repeat_highlight_symbol)
372 .scroll_padding(widget.scroll_padding);
373 if let Some(highlight_symbol) = widget.highlight_symbol {
374 list = list.highlight_symbol(highlight_symbol);
375 }
376 list.render(state.inner, buf, &mut list_state);
377}
378
379impl<Selection> HasFocus for ListState<Selection> {
380 fn build(&self, builder: &mut FocusBuilder) {
381 builder.leaf_widget(self);
382 }
383
384 #[inline]
385 fn focus(&self) -> FocusFlag {
386 self.focus.clone()
387 }
388
389 #[inline]
390 fn area(&self) -> Rect {
391 self.area
392 }
393}
394
395impl<Selection: Default> Default for ListState<Selection> {
396 fn default() -> Self {
397 Self {
398 area: Default::default(),
399 inner: Default::default(),
400 row_areas: Default::default(),
401 rows: Default::default(),
402 scroll: Default::default(),
403 focus: Default::default(),
404 selection: Default::default(),
405 mouse: Default::default(),
406 }
407 }
408}
409
410impl<Selection: Clone> Clone for ListState<Selection> {
411 fn clone(&self) -> Self {
412 Self {
413 area: self.area,
414 inner: self.inner,
415 row_areas: self.row_areas.clone(),
416 rows: self.rows,
417 scroll: self.scroll.clone(),
418 focus: FocusFlag::named(self.focus.name()),
419 selection: self.selection.clone(),
420 mouse: Default::default(),
421 }
422 }
423}
424
425impl<Selection> RelocatableState for ListState<Selection> {
426 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
427 self.area = relocate_area(self.area, shift, clip);
428 self.inner = relocate_area(self.inner, shift, clip);
429 relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
430 self.scroll.relocate(shift, clip);
431 }
432}
433
434impl<Selection: ListSelection> ListState<Selection> {
435 pub fn new() -> Self
437 where
438 Selection: Default,
439 {
440 Default::default()
441 }
442
443 pub fn named(name: &str) -> Self
445 where
446 Selection: Default,
447 {
448 Self {
449 focus: FocusFlag::named(name),
450 ..Default::default()
451 }
452 }
453
454 #[inline]
455 pub fn rows(&self) -> usize {
456 self.rows
457 }
458
459 #[inline]
460 pub fn clear_offset(&mut self) {
461 self.scroll.set_offset(0);
462 }
463
464 #[inline]
465 pub fn max_offset(&self) -> usize {
466 self.scroll.max_offset()
467 }
468
469 #[inline]
470 pub fn set_max_offset(&mut self, max: usize) {
471 self.scroll.set_max_offset(max);
472 }
473
474 #[inline]
475 pub fn offset(&self) -> usize {
476 self.scroll.offset()
477 }
478
479 #[inline]
480 pub fn set_offset(&mut self, offset: usize) -> bool {
481 self.scroll.set_offset(offset)
482 }
483
484 #[inline]
485 pub fn page_len(&self) -> usize {
486 self.scroll.page_len()
487 }
488
489 pub fn scroll_by(&self) -> usize {
490 self.scroll.scroll_by()
491 }
492
493 #[inline]
495 pub fn scroll_to_selected(&mut self) -> bool {
496 if let Some(selected) = self.selection.lead_selection() {
497 self.scroll_to(selected)
498 } else {
499 false
500 }
501 }
502
503 #[inline]
504 pub fn scroll_to(&mut self, pos: usize) -> bool {
505 if pos >= self.offset() + self.page_len() {
506 self.set_offset(pos - self.page_len() + 1)
507 } else if pos < self.offset() {
508 self.set_offset(pos)
509 } else {
510 false
511 }
512 }
513
514 #[inline]
515 pub fn scroll_up(&mut self, n: usize) -> bool {
516 self.scroll.scroll_up(n)
517 }
518
519 #[inline]
520 pub fn scroll_down(&mut self, n: usize) -> bool {
521 self.scroll.scroll_down(n)
522 }
523}
524
525impl<Selection: ListSelection> ListState<Selection> {
526 pub fn row_area(&self, row: usize) -> Option<Rect> {
528 if row < self.scroll.offset() || row >= self.scroll.offset() + self.scroll.page_len() {
529 return None;
530 }
531 let row = row - self.scroll.offset;
532 if row >= self.row_areas.len() {
533 return None;
534 }
535 Some(self.row_areas[row - self.scroll.offset])
536 }
537
538 #[inline]
539 pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
540 self.mouse
541 .row_at(&self.row_areas, pos.1)
542 .map(|v| self.scroll.offset() + v)
543 }
544
545 #[inline]
547 pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
548 match self.mouse.row_at_drag(self.inner, &self.row_areas, pos.1) {
549 Ok(v) => self.scroll.offset() + v,
550 Err(v) if v <= 0 => self.scroll.offset().saturating_sub((-v) as usize),
551 Err(v) => self.scroll.offset() + self.row_areas.len() + v as usize,
552 }
553 }
554}
555
556impl ListState<RowSelection> {
557 pub fn items_added(&mut self, pos: usize, n: usize) {
561 self.scroll.items_added(pos, n);
562 self.selection.items_added(pos, n);
563 self.rows += n;
564 }
565
566 pub fn items_removed(&mut self, pos: usize, n: usize) {
570 self.scroll.items_removed(pos, n);
571 self.selection
572 .items_removed(pos, n, self.rows.saturating_sub(1));
573 self.rows -= n;
574 }
575
576 #[inline]
578 pub fn set_scroll_selection(&mut self, scroll: bool) {
579 self.selection.set_scroll_selected(scroll);
580 }
581
582 pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
586 if self.scroll.max_offset() > 0 {
587 (self.rows * offset) / self.scroll.max_offset()
588 } else {
589 0 }
591 }
592
593 #[inline]
595 pub fn clear_selection(&mut self) {
596 self.selection.clear();
597 }
598
599 #[inline]
601 pub fn has_selection(&mut self) -> bool {
602 self.selection.has_selection()
603 }
604
605 #[inline]
607 pub fn selected(&self) -> Option<usize> {
608 self.selection.lead_selection()
609 }
610
611 #[inline]
614 pub fn selected_checked(&self) -> Option<usize> {
615 self.selection.lead_selection().filter(|v| *v < self.rows)
616 }
617
618 #[inline]
619 pub fn select(&mut self, row: Option<usize>) -> bool {
620 self.selection.select(row)
621 }
622
623 #[inline]
626 pub fn move_to(&mut self, row: usize) -> bool {
627 let r = self.selection.move_to(row, self.rows.saturating_sub(1));
628 let s = self.scroll_to(self.selection.selected().expect("row"));
629 r || s
630 }
631
632 #[inline]
635 pub fn move_up(&mut self, n: usize) -> bool {
636 let r = self.selection.move_up(n, self.rows.saturating_sub(1));
637 let s = self.scroll_to(self.selection.selected().expect("row"));
638 r || s
639 }
640
641 #[inline]
644 pub fn move_down(&mut self, n: usize) -> bool {
645 let r = self.selection.move_down(n, self.rows.saturating_sub(1));
646 let s = self.scroll_to(self.selection.selected().expect("row"));
647 r || s
648 }
649}
650
651impl ListState<RowSetSelection> {
652 #[inline]
654 pub fn clear_selection(&mut self) {
655 self.selection.clear();
656 }
657
658 #[inline]
660 pub fn has_selection(&mut self) -> bool {
661 self.selection.has_selection()
662 }
663
664 #[inline]
665 pub fn selected(&self) -> HashSet<usize> {
666 self.selection.selected()
667 }
668
669 #[inline]
676 pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
677 if let Some(row) = row {
678 self.selection
679 .set_lead(Some(min(row, self.rows.saturating_sub(1))), extend)
680 } else {
681 self.selection.set_lead(None, extend)
682 }
683 }
684
685 #[inline]
687 pub fn lead(&self) -> Option<usize> {
688 self.selection.lead()
689 }
690
691 #[inline]
693 pub fn anchor(&self) -> Option<usize> {
694 self.selection.anchor()
695 }
696
697 #[inline]
699 pub fn set_lead_clamped(&mut self, lead: usize, max: usize, extend: bool) {
700 self.selection.move_to(lead, max, extend);
701 }
702
703 #[inline]
706 pub fn retire_selection(&mut self) {
707 self.selection.retire_selection();
708 }
709
710 #[inline]
712 pub fn add_selected(&mut self, idx: usize) {
713 self.selection.add(idx);
714 }
715
716 #[inline]
719 pub fn remove_selected(&mut self, idx: usize) {
720 self.selection.remove(idx);
721 }
722
723 #[inline]
726 pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
727 let r = self
728 .selection
729 .move_to(row, self.rows.saturating_sub(1), extend);
730 let s = self.scroll_to(self.selection.lead().expect("row"));
731 r || s
732 }
733
734 #[inline]
737 pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
738 let r = self
739 .selection
740 .move_up(n, self.rows.saturating_sub(1), extend);
741 let s = self.scroll_to(self.selection.lead().expect("row"));
742 r || s
743 }
744
745 #[inline]
748 pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
749 let r = self
750 .selection
751 .move_down(n, self.rows.saturating_sub(1), extend);
752 let s = self.scroll_to(self.selection.lead().expect("row"));
753 r || s
754 }
755}
756
757pub mod selection {
758 use crate::event::{HandleEvent, MouseOnly, Outcome, Regular, ct_event, flow};
763 use crate::list::{ListSelection, ListState};
764 use crossterm::event::KeyModifiers;
765 use rat_focus::HasFocus;
766 use rat_ftable::TableSelection;
767 use rat_scrolled::ScrollAreaState;
768 use rat_scrolled::event::ScrollOutcome;
769 use std::mem;
770
771 pub type NoSelection = rat_ftable::selection::NoSelection;
773
774 impl ListSelection for NoSelection {
775 fn count(&self) -> usize {
776 0
777 }
778
779 #[inline]
780 fn is_selected(&self, _n: usize) -> bool {
781 false
782 }
783
784 #[inline]
785 fn lead_selection(&self) -> Option<usize> {
786 None
787 }
788 }
789
790 impl HandleEvent<crossterm::event::Event, Regular, Outcome> for ListState<NoSelection> {
791 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> Outcome {
792 let res = if self.is_focused() {
793 match event {
794 ct_event!(keycode press Down) => self.scroll_down(1).into(),
795 ct_event!(keycode press Up) => self.scroll_up(1).into(),
796 ct_event!(keycode press CONTROL-Down) | ct_event!(keycode press End) => {
797 self.scroll_to(self.max_offset()).into()
798 }
799 ct_event!(keycode press CONTROL-Up) | ct_event!(keycode press Home) => {
800 self.scroll_to(0).into()
801 }
802 ct_event!(keycode press PageUp) => {
803 self.scroll_up(self.page_len().saturating_sub(1)).into()
804 }
805 ct_event!(keycode press PageDown) => {
806 self.scroll_down(self.page_len().saturating_sub(1)).into()
807 }
808 _ => Outcome::Continue,
809 }
810 } else {
811 Outcome::Continue
812 };
813
814 if res == Outcome::Continue {
815 self.handle(event, MouseOnly)
816 } else {
817 res
818 }
819 }
820 }
821
822 impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ListState<NoSelection> {
823 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> Outcome {
824 let mut sas = ScrollAreaState::new()
825 .area(self.inner)
826 .v_scroll(&mut self.scroll);
827 let r = match sas.handle(event, MouseOnly) {
828 ScrollOutcome::Up(v) => self.scroll_up(v),
829 ScrollOutcome::Down(v) => self.scroll_down(v),
830 ScrollOutcome::VPos(v) => self.set_offset(v),
831 ScrollOutcome::Left(_) => false,
832 ScrollOutcome::Right(_) => false,
833 ScrollOutcome::HPos(_) => false,
834
835 ScrollOutcome::Continue => false,
836 ScrollOutcome::Unchanged => false,
837 ScrollOutcome::Changed => true,
838 };
839 if r {
840 return Outcome::Changed;
841 }
842
843 Outcome::Unchanged
844 }
845 }
846
847 pub type RowSelection = rat_ftable::selection::RowSelection;
849
850 impl ListSelection for RowSelection {
851 fn count(&self) -> usize {
852 if self.lead_row.is_some() { 1 } else { 0 }
853 }
854
855 #[inline]
856 fn is_selected(&self, n: usize) -> bool {
857 self.lead_row == Some(n)
858 }
859
860 #[inline]
861 fn lead_selection(&self) -> Option<usize> {
862 self.lead_row
863 }
864
865 fn scroll_selected(&self) -> bool {
866 self.scroll_selected
867 }
868 }
869
870 impl HandleEvent<crossterm::event::Event, Regular, Outcome> for ListState<RowSelection> {
871 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> Outcome {
872 let res = if self.is_focused() {
873 match event {
874 ct_event!(keycode press Down) => self.move_down(1).into(),
875 ct_event!(keycode press Up) => self.move_up(1).into(),
876 ct_event!(keycode press CONTROL-Down) | ct_event!(keycode press End) => {
877 self.move_to(self.rows.saturating_sub(1)).into()
878 }
879 ct_event!(keycode press CONTROL-Up) | ct_event!(keycode press Home) => {
880 self.move_to(0).into()
881 }
882 ct_event!(keycode press PageUp) => {
883 self.move_up(self.page_len().saturating_sub(1)).into()
884 }
885 ct_event!(keycode press PageDown) => {
886 self.move_down(self.page_len().saturating_sub(1)).into()
887 }
888 _ => Outcome::Continue,
889 }
890 } else {
891 Outcome::Continue
892 };
893
894 if res == Outcome::Continue {
895 self.handle(event, MouseOnly)
896 } else {
897 res
898 }
899 }
900 }
901
902 impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ListState<RowSelection> {
903 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> Outcome {
904 flow!(match event {
905 ct_event!(mouse any for m) if self.mouse.drag(self.inner, m) => {
906 self.move_to(self.row_at_drag((m.column, m.row))).into()
907 }
908 ct_event!(mouse down Left for column, row) => {
909 if self.inner.contains((*column, *row).into()) {
910 if let Some(new_row) = self.row_at_clicked((*column, *row)) {
911 self.move_to(new_row).into()
912 } else {
913 Outcome::Continue
914 }
915 } else {
916 Outcome::Continue
917 }
918 }
919
920 _ => Outcome::Continue,
921 });
922
923 let mut sas = ScrollAreaState::new()
924 .area(self.inner)
925 .v_scroll(&mut self.scroll);
926 let r = match sas.handle(event, MouseOnly) {
927 ScrollOutcome::Up(v) => {
928 if ListSelection::scroll_selected(&self.selection) {
929 self.move_up(1)
930 } else {
931 self.scroll_up(v)
932 }
933 }
934 ScrollOutcome::Down(v) => {
935 if ListSelection::scroll_selected(&self.selection) {
936 self.move_down(1)
937 } else {
938 self.scroll_down(v)
939 }
940 }
941 ScrollOutcome::VPos(v) => {
942 if ListSelection::scroll_selected(&self.selection) {
943 self.move_to(self.remap_offset_selection(v))
944 } else {
945 self.set_offset(v)
946 }
947 }
948 ScrollOutcome::Left(_) => false,
949 ScrollOutcome::Right(_) => false,
950 ScrollOutcome::HPos(_) => false,
951
952 ScrollOutcome::Continue => false,
953 ScrollOutcome::Unchanged => false,
954 ScrollOutcome::Changed => true,
955 };
956 if r {
957 return Outcome::Changed;
958 }
959
960 Outcome::Continue
961 }
962 }
963
964 pub type RowSetSelection = rat_ftable::selection::RowSetSelection;
965
966 impl ListSelection for RowSetSelection {
967 fn count(&self) -> usize {
968 let n = if let Some(anchor) = self.anchor_row {
969 if let Some(lead) = self.lead_row {
970 anchor.abs_diff(lead) + 1
971 } else {
972 0
973 }
974 } else {
975 0
976 };
977
978 n + self.selected.len()
979 }
980
981 fn is_selected(&self, n: usize) -> bool {
982 if let Some(mut anchor) = self.anchor_row {
983 if let Some(mut lead) = self.lead_row {
984 if lead < anchor {
985 mem::swap(&mut lead, &mut anchor);
986 }
987
988 if n >= anchor && n <= lead {
989 return true;
990 }
991 }
992 } else {
993 if let Some(lead) = self.lead_row {
994 if n == lead {
995 return true;
996 }
997 }
998 }
999
1000 self.selected.contains(&n)
1001 }
1002
1003 fn lead_selection(&self) -> Option<usize> {
1004 self.lead_row
1005 }
1006 }
1007
1008 impl HandleEvent<crossterm::event::Event, Regular, Outcome> for ListState<RowSetSelection> {
1009 fn handle(&mut self, event: &crossterm::event::Event, _: Regular) -> Outcome {
1010 let res = if self.is_focused() {
1011 match event {
1012 ct_event!(keycode press Down) => self.move_down(1, false).into(),
1013 ct_event!(keycode press SHIFT-Down) => self.move_down(1, true).into(),
1014 ct_event!(keycode press Up) => self.move_up(1, false).into(),
1015 ct_event!(keycode press SHIFT-Up) => self.move_up(1, true).into(),
1016 ct_event!(keycode press CONTROL-Down) | ct_event!(keycode press End) => {
1017 self.move_to(self.rows.saturating_sub(1), false).into()
1018 }
1019 ct_event!(keycode press SHIFT-End) => {
1020 self.move_to(self.rows.saturating_sub(1), true).into()
1021 }
1022 ct_event!(keycode press CONTROL-Up) | ct_event!(keycode press Home) => {
1023 self.move_to(0, false).into()
1024 }
1025 ct_event!(keycode press SHIFT-Home) => self.move_to(0, true).into(),
1026
1027 ct_event!(keycode press PageUp) => self
1028 .move_up(self.page_len().saturating_sub(1), false)
1029 .into(),
1030 ct_event!(keycode press SHIFT-PageUp) => {
1031 self.move_up(self.page_len().saturating_sub(1), true).into()
1032 }
1033 ct_event!(keycode press PageDown) => self
1034 .move_down(self.page_len().saturating_sub(1), false)
1035 .into(),
1036 ct_event!(keycode press SHIFT-PageDown) => self
1037 .move_down(self.page_len().saturating_sub(1), true)
1038 .into(),
1039 _ => Outcome::Continue,
1040 }
1041 } else {
1042 Outcome::Continue
1043 };
1044
1045 if res == Outcome::Continue {
1046 self.handle(event, MouseOnly)
1047 } else {
1048 res
1049 }
1050 }
1051 }
1052
1053 impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ListState<RowSetSelection> {
1054 fn handle(&mut self, event: &crossterm::event::Event, _: MouseOnly) -> Outcome {
1055 flow!(match event {
1056 ct_event!(mouse any for m) | ct_event!(mouse any CONTROL for m)
1057 if self.mouse.drag(self.inner, m)
1058 || self.mouse.drag2(self.inner, m, KeyModifiers::CONTROL) =>
1059 {
1060 self.move_to(self.row_at_drag((m.column, m.row)), true)
1061 .into()
1062 }
1063 ct_event!(mouse down Left for column, row) => {
1064 let pos = (*column, *row);
1065 if self.inner.contains(pos.into()) {
1066 if let Some(new_row) = self.row_at_clicked(pos) {
1067 self.move_to(new_row, false).into()
1068 } else {
1069 Outcome::Continue
1070 }
1071 } else {
1072 Outcome::Continue
1073 }
1074 }
1075 ct_event!(mouse down ALT-Left for column, row) => {
1076 let pos = (*column, *row);
1077 if self.area.contains(pos.into()) {
1078 if let Some(new_row) = self.row_at_clicked(pos) {
1079 self.move_to(new_row, true).into()
1080 } else {
1081 Outcome::Continue
1082 }
1083 } else {
1084 Outcome::Continue
1085 }
1086 }
1087 ct_event!(mouse down CONTROL-Left for column, row) => {
1088 let pos = (*column, *row);
1089 if self.area.contains(pos.into()) {
1090 if let Some(new_row) = self.row_at_clicked(pos) {
1091 self.retire_selection();
1092 if self.selection.is_selected_row(new_row) {
1093 self.selection.remove(new_row);
1094 } else {
1095 self.move_to(new_row, true);
1096 }
1097 Outcome::Changed
1098 } else {
1099 Outcome::Continue
1100 }
1101 } else {
1102 Outcome::Continue
1103 }
1104 }
1105 _ => Outcome::Continue,
1106 });
1107
1108 let mut sas = ScrollAreaState::new()
1109 .area(self.inner)
1110 .v_scroll(&mut self.scroll);
1111 let r = match sas.handle(event, MouseOnly) {
1112 ScrollOutcome::Up(v) => self.scroll_up(v),
1113 ScrollOutcome::Down(v) => self.scroll_down(v),
1114 ScrollOutcome::VPos(v) => self.set_offset(v),
1115 ScrollOutcome::Left(_) => false,
1116 ScrollOutcome::Right(_) => false,
1117 ScrollOutcome::HPos(_) => false,
1118
1119 ScrollOutcome::Continue => false,
1120 ScrollOutcome::Unchanged => false,
1121 ScrollOutcome::Changed => true,
1122 };
1123 if r {
1124 return Outcome::Changed;
1125 }
1126
1127 Outcome::Unchanged
1128 }
1129 }
1130}