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