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, 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
532        Some(self.row_areas[row - self.scroll.offset])
533    }
534
535    #[inline]
536    pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
537        self.mouse
538            .row_at(&self.row_areas, pos.1)
539            .map(|v| self.scroll.offset() + v)
540    }
541
542    /// Row when dragging. Can go outside the area.
543    #[inline]
544    pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
545        match self.mouse.row_at_drag(self.inner, &self.row_areas, pos.1) {
546            Ok(v) => self.scroll.offset() + v,
547            Err(v) if v <= 0 => self.scroll.offset().saturating_sub((-v) as usize),
548            Err(v) => self.scroll.offset() + self.row_areas.len() + v as usize,
549        }
550    }
551}
552
553impl ListState<RowSelection> {
554    /// Update the state to match adding items.
555    ///
556    /// This corrects the number of rows, offset and selection.
557    pub fn items_added(&mut self, pos: usize, n: usize) {
558        self.scroll.items_added(pos, n);
559        self.selection.items_added(pos, n);
560        self.rows += n;
561    }
562
563    /// Update the state to match removing items.
564    ///
565    /// This corrects the number of rows, offset and selection.
566    pub fn items_removed(&mut self, pos: usize, n: usize) {
567        self.scroll.items_removed(pos, n);
568        self.selection
569            .items_removed(pos, n, self.rows.saturating_sub(1));
570        self.rows -= n;
571    }
572
573    /// When scrolling the table, change the selection instead of the offset.
574    #[inline]
575    pub fn set_scroll_selection(&mut self, scroll: bool) {
576        self.selection.set_scroll_selected(scroll);
577    }
578
579    /// Scroll delivers a value between 0 and max_offset as offset.
580    /// This remaps the ratio to the selection with a range 0..row_len.
581    ///
582    pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
583        if self.scroll.max_offset() > 0 {
584            (self.rows * offset) / self.scroll.max_offset()
585        } else {
586            0 // ???
587        }
588    }
589
590    /// Clear the selection.
591    #[inline]
592    pub fn clear_selection(&mut self) {
593        self.selection.clear();
594    }
595
596    /// Anything selected?
597    #[inline]
598    pub fn has_selection(&mut self) -> bool {
599        self.selection.has_selection()
600    }
601
602    /// Returns the lead selection.
603    #[inline]
604    pub fn selected(&self) -> Option<usize> {
605        self.selection.lead_selection()
606    }
607
608    #[inline]
609    pub fn select(&mut self, row: Option<usize>) -> bool {
610        self.selection.select(row)
611    }
612
613    /// Move the selection to the given row. Limits the movement to the row-count.
614    /// Ensures the row is visible afterwards.
615    #[inline]
616    pub fn move_to(&mut self, row: usize) -> bool {
617        let r = self.selection.move_to(row, self.rows.saturating_sub(1));
618        let s = self.scroll_to(self.selection.selected().expect("row"));
619        r || s
620    }
621
622    /// Move the selection up n rows.
623    /// Ensures the row is visible afterwards.
624    #[inline]
625    pub fn move_up(&mut self, n: usize) -> bool {
626        let r = self.selection.move_up(n, self.rows.saturating_sub(1));
627        let s = self.scroll_to(self.selection.selected().expect("row"));
628        r || s
629    }
630
631    /// Move the selection down n rows.
632    /// Ensures the row is visible afterwards.
633    #[inline]
634    pub fn move_down(&mut self, n: usize) -> bool {
635        let r = self.selection.move_down(n, self.rows.saturating_sub(1));
636        let s = self.scroll_to(self.selection.selected().expect("row"));
637        r || s
638    }
639}
640
641impl ListState<RowSetSelection> {
642    /// Clear the selection.
643    #[inline]
644    pub fn clear_selection(&mut self) {
645        self.selection.clear();
646    }
647
648    /// Anything selected?
649    #[inline]
650    pub fn has_selection(&mut self) -> bool {
651        self.selection.has_selection()
652    }
653
654    #[inline]
655    pub fn selected(&self) -> HashSet<usize> {
656        self.selection.selected()
657    }
658
659    /// Change the lead-selection. Limits the value to the number of rows.
660    /// If extend is false the current selection is cleared and both lead and
661    /// anchor are set to the given value.
662    /// If extend is true, the anchor is kept where it is and lead is changed.
663    /// Everything in the range `anchor..lead` is selected. It doesn't matter
664    /// if anchor < lead.
665    #[inline]
666    pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
667        if let Some(row) = row {
668            self.selection
669                .set_lead(Some(min(row, self.rows.saturating_sub(1))), extend)
670        } else {
671            self.selection.set_lead(None, extend)
672        }
673    }
674
675    /// Current lead.
676    #[inline]
677    pub fn lead(&self) -> Option<usize> {
678        self.selection.lead()
679    }
680
681    /// Current anchor.
682    #[inline]
683    pub fn anchor(&self) -> Option<usize> {
684        self.selection.anchor()
685    }
686
687    /// Set a new lead, at the same time limit the lead to max.
688    #[inline]
689    pub fn set_lead_clamped(&mut self, lead: usize, max: usize, extend: bool) {
690        self.selection.move_to(lead, max, extend);
691    }
692
693    /// Retire the current anchor/lead selection to the set of selected rows.
694    /// Resets lead and anchor and starts a new selection round.
695    #[inline]
696    pub fn retire_selection(&mut self) {
697        self.selection.retire_selection();
698    }
699
700    /// Add to selection.
701    #[inline]
702    pub fn add_selected(&mut self, idx: usize) {
703        self.selection.add(idx);
704    }
705
706    /// Remove from selection. Only works for retired selections, not for the
707    /// active anchor-lead range.
708    #[inline]
709    pub fn remove_selected(&mut self, idx: usize) {
710        self.selection.remove(idx);
711    }
712
713    /// Move the selection to the given row.
714    /// Ensures the row is visible afterwards.
715    #[inline]
716    pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
717        let r = self
718            .selection
719            .move_to(row, self.rows.saturating_sub(1), extend);
720        let s = self.scroll_to(self.selection.lead().expect("row"));
721        r || s
722    }
723
724    /// Move the selection up n rows.
725    /// Ensures the row is visible afterwards.
726    #[inline]
727    pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
728        let r = self
729            .selection
730            .move_up(n, self.rows.saturating_sub(1), extend);
731        let s = self.scroll_to(self.selection.lead().expect("row"));
732        r || s
733    }
734
735    /// Move the selection down n rows.
736    /// Ensures the row is visible afterwards.
737    #[inline]
738    pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
739        let r = self
740            .selection
741            .move_down(n, self.rows.saturating_sub(1), extend);
742        let s = self.scroll_to(self.selection.lead().expect("row"));
743        r || s
744    }
745}
746
747pub mod selection {
748    use crate::event::{ct_event, flow, HandleEvent, MouseOnly, Outcome, Regular};
749    use crate::list::{ListSelection, ListState};
750    use crossterm::event::KeyModifiers;
751    use rat_focus::HasFocus;
752    use rat_ftable::TableSelection;
753    use rat_scrolled::event::ScrollOutcome;
754    use rat_scrolled::ScrollAreaState;
755    use std::mem;
756
757    /// No selection
758    pub type NoSelection = rat_ftable::selection::NoSelection;
759
760    impl ListSelection for NoSelection {
761        fn count(&self) -> usize {
762            0
763        }
764
765        #[inline]
766        fn is_selected(&self, _n: usize) -> bool {
767            false
768        }
769
770        #[inline]
771        fn lead_selection(&self) -> Option<usize> {
772            None
773        }
774    }
775
776    impl HandleEvent<crossterm::event::Event, Regular, Outcome> for ListState<NoSelection> {
777        fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> Outcome {
778            let res = if self.is_focused() {
779                match event {
780                    ct_event!(keycode press Down) => self.scroll_down(1).into(),
781                    ct_event!(keycode press Up) => self.scroll_up(1).into(),
782                    ct_event!(keycode press CONTROL-Down) | ct_event!(keycode press End) => {
783                        self.scroll_to(self.max_offset()).into()
784                    }
785                    ct_event!(keycode press CONTROL-Up) | ct_event!(keycode press Home) => {
786                        self.scroll_to(0).into()
787                    }
788                    ct_event!(keycode press PageUp) => {
789                        self.scroll_up(self.page_len().saturating_sub(1)).into()
790                    }
791                    ct_event!(keycode press PageDown) => {
792                        self.scroll_down(self.page_len().saturating_sub(1)).into()
793                    }
794                    _ => Outcome::Continue,
795                }
796            } else {
797                Outcome::Continue
798            };
799
800            if res == Outcome::Continue {
801                self.handle(event, MouseOnly)
802            } else {
803                res
804            }
805        }
806    }
807
808    impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ListState<NoSelection> {
809        fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> Outcome {
810            let mut sas = ScrollAreaState::new()
811                .area(self.inner)
812                .v_scroll(&mut self.scroll);
813            let r = match sas.handle(event, MouseOnly) {
814                ScrollOutcome::Up(v) => self.scroll_up(v),
815                ScrollOutcome::Down(v) => self.scroll_down(v),
816                ScrollOutcome::VPos(v) => self.set_offset(v),
817                ScrollOutcome::Left(_) => false,
818                ScrollOutcome::Right(_) => false,
819                ScrollOutcome::HPos(_) => false,
820
821                ScrollOutcome::Continue => false,
822                ScrollOutcome::Unchanged => false,
823                ScrollOutcome::Changed => true,
824            };
825            if r {
826                return Outcome::Changed;
827            }
828
829            Outcome::Unchanged
830        }
831    }
832
833    /// Single element selection.
834    pub type RowSelection = rat_ftable::selection::RowSelection;
835
836    impl ListSelection for RowSelection {
837        fn count(&self) -> usize {
838            if self.lead_row.is_some() {
839                1
840            } else {
841                0
842            }
843        }
844
845        #[inline]
846        fn is_selected(&self, n: usize) -> bool {
847            self.lead_row == Some(n)
848        }
849
850        #[inline]
851        fn lead_selection(&self) -> Option<usize> {
852            self.lead_row
853        }
854
855        fn scroll_selected(&self) -> bool {
856            self.scroll_selected
857        }
858    }
859
860    impl HandleEvent<crossterm::event::Event, Regular, Outcome> for ListState<RowSelection> {
861        fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> Outcome {
862            let res = if self.is_focused() {
863                match event {
864                    ct_event!(keycode press Down) => self.move_down(1).into(),
865                    ct_event!(keycode press Up) => self.move_up(1).into(),
866                    ct_event!(keycode press CONTROL-Down) | ct_event!(keycode press End) => {
867                        self.move_to(self.rows.saturating_sub(1)).into()
868                    }
869                    ct_event!(keycode press CONTROL-Up) | ct_event!(keycode press Home) => {
870                        self.move_to(0).into()
871                    }
872                    ct_event!(keycode press PageUp) => {
873                        self.move_up(self.page_len().saturating_sub(1)).into()
874                    }
875                    ct_event!(keycode press PageDown) => {
876                        self.move_down(self.page_len().saturating_sub(1)).into()
877                    }
878                    _ => Outcome::Continue,
879                }
880            } else {
881                Outcome::Continue
882            };
883
884            if res == Outcome::Continue {
885                self.handle(event, MouseOnly)
886            } else {
887                res
888            }
889        }
890    }
891
892    impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ListState<RowSelection> {
893        fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> Outcome {
894            flow!(match event {
895                ct_event!(mouse any for m) if self.mouse.drag(self.inner, m) => {
896                    self.move_to(self.row_at_drag((m.column, m.row))).into()
897                }
898                ct_event!(mouse down Left for column, row) => {
899                    if self.inner.contains((*column, *row).into()) {
900                        if let Some(new_row) = self.row_at_clicked((*column, *row)) {
901                            self.move_to(new_row).into()
902                        } else {
903                            Outcome::Continue
904                        }
905                    } else {
906                        Outcome::Continue
907                    }
908                }
909
910                _ => Outcome::Continue,
911            });
912
913            let mut sas = ScrollAreaState::new()
914                .area(self.inner)
915                .v_scroll(&mut self.scroll);
916            let r = match sas.handle(event, MouseOnly) {
917                ScrollOutcome::Up(v) => {
918                    if ListSelection::scroll_selected(&self.selection) {
919                        self.move_up(1)
920                    } else {
921                        self.scroll_up(v)
922                    }
923                }
924                ScrollOutcome::Down(v) => {
925                    if ListSelection::scroll_selected(&self.selection) {
926                        self.move_down(1)
927                    } else {
928                        self.scroll_down(v)
929                    }
930                }
931                ScrollOutcome::VPos(v) => {
932                    if ListSelection::scroll_selected(&self.selection) {
933                        self.move_to(self.remap_offset_selection(v))
934                    } else {
935                        self.set_offset(v)
936                    }
937                }
938                ScrollOutcome::Left(_) => false,
939                ScrollOutcome::Right(_) => false,
940                ScrollOutcome::HPos(_) => false,
941
942                ScrollOutcome::Continue => false,
943                ScrollOutcome::Unchanged => false,
944                ScrollOutcome::Changed => true,
945            };
946            if r {
947                return Outcome::Changed;
948            }
949
950            Outcome::Continue
951        }
952    }
953
954    pub type RowSetSelection = rat_ftable::selection::RowSetSelection;
955
956    impl ListSelection for RowSetSelection {
957        fn count(&self) -> usize {
958            let n = if let Some(anchor) = self.anchor_row {
959                if let Some(lead) = self.lead_row {
960                    anchor.abs_diff(lead) + 1
961                } else {
962                    0
963                }
964            } else {
965                0
966            };
967
968            n + self.selected.len()
969        }
970
971        fn is_selected(&self, n: usize) -> bool {
972            if let Some(mut anchor) = self.anchor_row {
973                if let Some(mut lead) = self.lead_row {
974                    if lead < anchor {
975                        mem::swap(&mut lead, &mut anchor);
976                    }
977
978                    if n >= anchor && n <= lead {
979                        return true;
980                    }
981                }
982            } else {
983                if let Some(lead) = self.lead_row {
984                    if n == lead {
985                        return true;
986                    }
987                }
988            }
989
990            self.selected.contains(&n)
991        }
992
993        fn lead_selection(&self) -> Option<usize> {
994            self.lead_row
995        }
996    }
997
998    impl HandleEvent<crossterm::event::Event, Regular, Outcome> for ListState<RowSetSelection> {
999        fn handle(&mut self, event: &crossterm::event::Event, _: Regular) -> Outcome {
1000            let res = if self.is_focused() {
1001                match event {
1002                    ct_event!(keycode press Down) => self.move_down(1, false).into(),
1003                    ct_event!(keycode press SHIFT-Down) => self.move_down(1, true).into(),
1004                    ct_event!(keycode press Up) => self.move_up(1, false).into(),
1005                    ct_event!(keycode press SHIFT-Up) => self.move_up(1, true).into(),
1006                    ct_event!(keycode press CONTROL-Down) | ct_event!(keycode press End) => {
1007                        self.move_to(self.rows.saturating_sub(1), false).into()
1008                    }
1009                    ct_event!(keycode press SHIFT-End) => {
1010                        self.move_to(self.rows.saturating_sub(1), true).into()
1011                    }
1012                    ct_event!(keycode press CONTROL-Up) | ct_event!(keycode press Home) => {
1013                        self.move_to(0, false).into()
1014                    }
1015                    ct_event!(keycode press SHIFT-Home) => self.move_to(0, true).into(),
1016
1017                    ct_event!(keycode press PageUp) => self
1018                        .move_up(self.page_len().saturating_sub(1), false)
1019                        .into(),
1020                    ct_event!(keycode press SHIFT-PageUp) => {
1021                        self.move_up(self.page_len().saturating_sub(1), true).into()
1022                    }
1023                    ct_event!(keycode press PageDown) => self
1024                        .move_down(self.page_len().saturating_sub(1), false)
1025                        .into(),
1026                    ct_event!(keycode press SHIFT-PageDown) => self
1027                        .move_down(self.page_len().saturating_sub(1), true)
1028                        .into(),
1029                    _ => Outcome::Continue,
1030                }
1031            } else {
1032                Outcome::Continue
1033            };
1034
1035            if res == Outcome::Continue {
1036                self.handle(event, MouseOnly)
1037            } else {
1038                res
1039            }
1040        }
1041    }
1042
1043    impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ListState<RowSetSelection> {
1044        fn handle(&mut self, event: &crossterm::event::Event, _: MouseOnly) -> Outcome {
1045            flow!(match event {
1046                ct_event!(mouse any for m) | ct_event!(mouse any CONTROL for m)
1047                    if self.mouse.drag(self.inner, m)
1048                        || self.mouse.drag2(self.inner, m, KeyModifiers::CONTROL) =>
1049                {
1050                    self.move_to(self.row_at_drag((m.column, m.row)), true)
1051                        .into()
1052                }
1053                ct_event!(mouse down Left for column, row) => {
1054                    let pos = (*column, *row);
1055                    if self.inner.contains(pos.into()) {
1056                        if let Some(new_row) = self.row_at_clicked(pos) {
1057                            self.move_to(new_row, false).into()
1058                        } else {
1059                            Outcome::Continue
1060                        }
1061                    } else {
1062                        Outcome::Continue
1063                    }
1064                }
1065                ct_event!(mouse down ALT-Left for column, row) => {
1066                    let pos = (*column, *row);
1067                    if self.area.contains(pos.into()) {
1068                        if let Some(new_row) = self.row_at_clicked(pos) {
1069                            self.move_to(new_row, true).into()
1070                        } else {
1071                            Outcome::Continue
1072                        }
1073                    } else {
1074                        Outcome::Continue
1075                    }
1076                }
1077                ct_event!(mouse down CONTROL-Left for column, row) => {
1078                    let pos = (*column, *row);
1079                    if self.area.contains(pos.into()) {
1080                        if let Some(new_row) = self.row_at_clicked(pos) {
1081                            self.retire_selection();
1082                            if self.selection.is_selected_row(new_row) {
1083                                self.selection.remove(new_row);
1084                            } else {
1085                                self.move_to(new_row, true);
1086                            }
1087                            Outcome::Changed
1088                        } else {
1089                            Outcome::Continue
1090                        }
1091                    } else {
1092                        Outcome::Continue
1093                    }
1094                }
1095                _ => Outcome::Continue,
1096            });
1097
1098            let mut sas = ScrollAreaState::new()
1099                .area(self.inner)
1100                .v_scroll(&mut self.scroll);
1101            let r = match sas.handle(event, MouseOnly) {
1102                ScrollOutcome::Up(v) => self.scroll_up(v),
1103                ScrollOutcome::Down(v) => self.scroll_down(v),
1104                ScrollOutcome::VPos(v) => self.set_offset(v),
1105                ScrollOutcome::Left(_) => false,
1106                ScrollOutcome::Right(_) => false,
1107                ScrollOutcome::HPos(_) => false,
1108
1109                ScrollOutcome::Continue => false,
1110                ScrollOutcome::Unchanged => false,
1111                ScrollOutcome::Changed => true,
1112            };
1113            if r {
1114                return Outcome::Changed;
1115            }
1116
1117            Outcome::Unchanged
1118        }
1119    }
1120}