rat_widget/
list.rs

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