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