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