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