rat_widget/
list.rs

1//!
2//! Extensions for ratatui List.
3//!
4
5use 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
24/// Trait for list-selection.
25pub trait ListSelection {
26    /// Number of selected rows.
27    fn count(&self) -> usize;
28
29    /// Is selected.
30    fn is_selected(&self, n: usize) -> bool;
31
32    /// Selection lead.
33    fn lead_selection(&self) -> Option<usize>;
34
35    /// Scroll the selection instead of the offset.
36    fn scroll_selected(&self) -> bool {
37        false
38    }
39}
40
41/// List widget.
42///
43/// Fully compatible with ratatui List.
44/// Adds Scroll, selection models, and event-handling.
45#[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/// Collected styles.
65#[derive(Debug, Clone)]
66pub struct ListStyle {
67    /// Style
68    pub style: Style,
69    /// Style for selection
70    pub select: Option<Style>,
71    /// Style for selection when focused.
72    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/// State & event handling.
86#[derive(Debug, PartialEq, Eq)]
87pub struct ListState<Selection = RowSelection> {
88    /// Total area
89    /// __readonly__. renewed for each render.
90    pub area: Rect,
91    /// Area inside the block.
92    /// __readonly__. renewed for each render.
93    pub inner: Rect,
94    /// Areas for the rendered items.
95    /// __readonly__. renewed for each render.
96    pub row_areas: Vec<Rect>,
97
98    /// Length in items.
99    /// __mostly readonly__. renewed for each render.
100    pub rows: usize,
101    /// Offset etc.
102    /// __read+write__
103    pub scroll: ScrollState,
104
105    /// Focus
106    /// __read+write__
107    pub focus: FocusFlag,
108    /// Selection model
109    /// __read+write__
110    pub selection: Selection,
111
112    /// Helper for mouse events.
113    /// __used for mouse interaction__
114    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    /// New list.
138    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    /// Set items.
162    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    /// Border support.
173    #[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    /// Scroll support.
181    #[inline]
182    pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
183        self.scroll = Some(scroll);
184        self
185    }
186
187    /// Set all styles.
188    #[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    /// Set all styles.
198    #[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    /// Base style
230    #[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    /// Select style.
238    #[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    /// Focused style.
245    #[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    /// List direction.
252    #[inline]
253    pub fn direction(mut self, direction: ListDirection) -> Self {
254        self.direction = direction;
255        self
256    }
257
258    /// Number of items.
259    #[inline]
260    pub fn len(&self) -> usize {
261        self.items.len()
262    }
263
264    /// Empty?
265    #[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    // area for each item
303    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    // max_v_offset
331    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    // rendering
355    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    /// New initial state.
448    pub fn new() -> Self
449    where
450        Selection: Default,
451    {
452        Default::default()
453    }
454
455    /// New state with a focus name
456    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    /// Scroll to selected.
505    #[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    /// Returns the row-area for the given row, if it is visible.
538    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    /// Row when dragging. Can go outside the area.
557    #[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    /// Update the state to match adding items.
569    ///
570    /// This corrects the number of rows, offset and selection.
571    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    /// Update the state to match removing items.
578    ///
579    /// This corrects the number of rows, offset and selection.
580    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    /// When scrolling the table, change the selection instead of the offset.
588    #[inline]
589    pub fn set_scroll_selection(&mut self, scroll: bool) {
590        self.selection.set_scroll_selected(scroll);
591    }
592
593    /// Scroll delivers a value between 0 and max_offset as offset.
594    /// This remaps the ratio to the selection with a range 0..row_len.
595    ///
596    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 // ???
601        }
602    }
603
604    /// Clear the selection.
605    #[inline]
606    pub fn clear_selection(&mut self) {
607        self.selection.clear();
608    }
609
610    /// Anything selected?
611    #[inline]
612    pub fn has_selection(&mut self) -> bool {
613        self.selection.has_selection()
614    }
615
616    /// Returns the lead selection.
617    #[inline]
618    pub fn selected(&self) -> Option<usize> {
619        self.selection.lead_selection()
620    }
621
622    /// Returns the lead selection. Ensures the index is
623    /// less than rows.
624    #[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    /// Move the selection to the given row. Limits the movement to the row-count.
635    /// Ensures the row is visible afterwards.
636    #[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    /// Move the selection up n rows.
644    /// Ensures the row is visible afterwards.
645    #[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    /// Move the selection down n rows.
653    /// Ensures the row is visible afterwards.
654    #[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    /// Clear the selection.
664    #[inline]
665    pub fn clear_selection(&mut self) {
666        self.selection.clear();
667    }
668
669    /// Anything selected?
670    #[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    /// Change the lead-selection. Limits the value to the number of rows.
681    /// If extend is false the current selection is cleared and both lead and
682    /// anchor are set to the given value.
683    /// If extend is true, the anchor is kept where it is and lead is changed.
684    /// Everything in the range `anchor..lead` is selected. It doesn't matter
685    /// if anchor < lead.
686    #[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    /// Current lead.
697    #[inline]
698    pub fn lead(&self) -> Option<usize> {
699        self.selection.lead()
700    }
701
702    /// Current anchor.
703    #[inline]
704    pub fn anchor(&self) -> Option<usize> {
705        self.selection.anchor()
706    }
707
708    /// Set a new lead, at the same time limit the lead to max.
709    #[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    /// Retire the current anchor/lead selection to the set of selected rows.
715    /// Resets lead and anchor and starts a new selection round.
716    #[inline]
717    pub fn retire_selection(&mut self) {
718        self.selection.retire_selection();
719    }
720
721    /// Add to selection.
722    #[inline]
723    pub fn add_selected(&mut self, idx: usize) {
724        self.selection.add(idx);
725    }
726
727    /// Remove from selection. Only works for retired selections, not for the
728    /// active anchor-lead range.
729    #[inline]
730    pub fn remove_selected(&mut self, idx: usize) {
731        self.selection.remove(idx);
732    }
733
734    /// Move the selection to the given row.
735    /// Ensures the row is visible afterwards.
736    #[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    /// Move the selection up n rows.
746    /// Ensures the row is visible afterwards.
747    #[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    /// Move the selection down n rows.
757    /// Ensures the row is visible afterwards.
758    #[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    //!
770    //! Different selection models.
771    //!
772
773    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    /// No selection
783    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    /// Single element selection.
859    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
1143/// Handle events for the popup.
1144/// Call before other handlers to deal with intersections
1145/// with other widgets.
1146pub 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
1155/// Handle only mouse-events.
1156pub fn handle_mouse_events(state: &mut ListState, event: &crossterm::event::Event) -> Outcome {
1157    HandleEvent::handle(state, event, MouseOnly)
1158}