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