1use crate::_private::NonExhaustive;
10use crate::event::util::MouseFlags;
11use crate::list::selection::{RowSelection, RowSetSelection};
12use crate::text::HasScreenCursor;
13use crate::util::{fallback_select_style, revert_style};
14use rat_event::{HandleEvent, MouseOnly, Outcome, Regular};
15use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
16use rat_reloc::{RelocatableState, relocate_areas};
17use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
18use ratatui_core::buffer::Buffer;
19use ratatui_core::layout::Rect;
20use ratatui_core::style::Style;
21use ratatui_core::text::{Line, Text};
22use ratatui_core::widgets::{StatefulWidget, Widget};
23use ratatui_crossterm::crossterm::event::Event;
24use ratatui_widgets::block::Block;
25use ratatui_widgets::list::ListDirection;
26use ratatui_widgets::table::HighlightSpacing;
27use std::cmp::{max, min};
28use std::collections::HashSet;
29use std::marker::PhantomData;
30
31pub mod edit;
32
33pub trait ListSelection {
35 fn count(&self) -> usize;
37
38 fn is_selected(&self, n: usize) -> bool;
40
41 fn lead_selection(&self) -> Option<usize>;
43
44 fn scroll_selected(&self) -> bool {
46 false
47 }
48
49 #[allow(unused_variables)]
51 fn validate_rows(&mut self, rows: usize) {}
52
53 #[allow(unused_variables)]
55 fn items_added(&mut self, pos: usize, n: usize) {}
56
57 #[allow(unused_variables)]
59 fn items_removed(&mut self, pos: usize, n: usize, rows: usize) {}
60}
61
62#[derive(Debug, Default, Clone)]
67pub struct List<'a, Selection = RowSelection> {
68 items: Vec<ListItem<'a>>,
69
70 style: Style,
71 block: Option<Block<'a>>,
72 scroll: Option<Scroll<'a>>,
73 scroll_padding: usize,
74 varying_heights: bool,
75
76 select_style: Option<Style>,
77 focus_style: Option<Style>,
78 direction: ListDirection,
79 highlight_spacing: HighlightSpacing,
80 highlight_symbol: Option<Line<'a>>,
81 repeat_highlight_symbol: bool,
82 highlight_style: Option<Style>,
83
84 _phantom: PhantomData<Selection>,
85}
86
87#[derive(Debug, Clone, Eq, PartialEq, Hash)]
88pub struct ListItem<'a> {
89 pub(crate) content: Text<'a>,
90 pub(crate) style: Style,
91}
92
93#[derive(Debug, Clone)]
95pub struct ListStyle {
96 pub style: Style,
98 pub block: Option<Block<'static>>,
100 pub border_style: Option<Style>,
102 pub title_style: Option<Style>,
104 pub scroll: Option<ScrollStyle>,
106
107 pub scroll_padding: Option<usize>,
109
110 pub select: Option<Style>,
112 pub focus: Option<Style>,
114
115 pub highlight_spacing: Option<HighlightSpacing>,
117 pub highlight_symbol: Option<&'static str>,
119 pub repeat_highlight_symbol: Option<bool>,
121 pub highlight_style: Option<Style>,
123
124 pub non_exhaustive: NonExhaustive,
125}
126
127#[derive(Debug, Clone)]
129pub enum RowHeights {
130 Uniform(u8),
132 Varying(Vec<u8>),
134}
135
136impl Default for RowHeights {
137 fn default() -> Self {
138 RowHeights::Uniform(1)
139 }
140}
141
142impl RowHeights {
143 pub(crate) fn clear(&mut self) {
144 match self {
145 RowHeights::Uniform(_) => {}
146 RowHeights::Varying(h) => h.clear(),
147 }
148 }
149
150 pub(crate) fn push(&mut self, row_height: u8, varying: bool) {
151 if varying {
152 match self {
153 RowHeights::Uniform(_) => {
154 *self = RowHeights::Varying(vec![row_height]);
155 }
156 RowHeights::Varying(h) => h.push(row_height),
157 }
158 } else {
159 *self = RowHeights::Uniform(row_height);
160 }
161 }
162
163 pub(crate) fn items_added(&mut self, pos: usize, n: usize) {
164 match self {
165 RowHeights::Uniform(_) => {}
166 RowHeights::Varying(h) => {
167 if pos <= h.len() {
168 for _ in 0..n {
169 h.insert(pos, 1);
170 }
171 }
172 }
173 }
174 }
175
176 pub(crate) fn items_added_with_heights(&mut self, pos: usize, height: &[u8]) {
177 match self {
178 RowHeights::Uniform(_) => {}
179 RowHeights::Varying(h) => {
180 if pos <= h.len() {
181 h.splice(pos..pos, height.into_iter().copied());
182 }
183 }
184 }
185 }
186
187 pub(crate) fn items_removed(&mut self, pos: usize, n: usize) {
188 match self {
189 RowHeights::Uniform(_) => {}
190 RowHeights::Varying(h) => {
191 if pos < h.len() {
192 if pos + n < h.len() {
193 h.drain(pos..pos + n);
194 } else {
195 h.drain(pos..);
196 }
197 }
198 }
199 }
200 }
201}
202
203#[derive(Debug)]
205pub struct ListState<Selection = RowSelection> {
206 pub area: Rect,
209 pub inner: Rect,
212 pub row_areas: Vec<Rect>,
215 pub row_heights: RowHeights,
218 pub scroll_padding: usize,
221 pub direction: ListDirection,
224
225 pub rows: usize,
228 pub scroll: ScrollState,
231
232 pub focus: FocusFlag,
235 pub selection: Selection,
238
239 pub mouse: MouseFlags,
242
243 pub non_exhaustive: NonExhaustive,
244}
245
246impl Default for ListStyle {
247 fn default() -> Self {
248 Self {
249 style: Default::default(),
250 block: Default::default(),
251 border_style: Default::default(),
252 title_style: Default::default(),
253 scroll: Default::default(),
254 scroll_padding: Default::default(),
255 select: Default::default(),
256 focus: Default::default(),
257 highlight_spacing: Default::default(),
258 highlight_symbol: Default::default(),
259 repeat_highlight_symbol: Default::default(),
260 highlight_style: Default::default(),
261 non_exhaustive: NonExhaustive,
262 }
263 }
264}
265
266impl<'a> ListItem<'a> {
267 pub fn new<T>(content: T) -> Self
270 where
271 T: Into<Text<'a>>,
272 {
273 Self {
274 content: content.into(),
275 style: Style::default(),
276 }
277 }
278
279 pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
281 self.style = style.into();
282 self
283 }
284
285 pub fn height(&self) -> usize {
287 self.content.height()
288 }
289
290 pub fn width(&self) -> usize {
292 self.content.width()
293 }
294}
295
296impl<'a, T> From<T> for ListItem<'a>
297where
298 T: Into<Text<'a>>,
299{
300 fn from(value: T) -> Self {
301 Self::new(value)
302 }
303}
304
305impl<'a, Selection> List<'a, Selection> {
306 pub fn new<T>(items: T) -> Self
308 where
309 T: IntoIterator,
310 T::Item: Into<ListItem<'a>>,
311 {
312 let items = items.into_iter().map(|v| v.into()).collect();
313
314 Self {
315 block: None,
316 scroll: None,
317 items,
318 style: Default::default(),
319 select_style: Default::default(),
320 focus_style: Default::default(),
321 direction: Default::default(),
322 highlight_spacing: Default::default(),
323 highlight_symbol: Default::default(),
324 repeat_highlight_symbol: Default::default(),
325 highlight_style: Default::default(),
326 scroll_padding: 0,
327 varying_heights: Default::default(),
328 _phantom: Default::default(),
329 }
330 }
331
332 pub fn items<T>(mut self, items: T) -> Self
334 where
335 T: IntoIterator,
336 T::Item: Into<ListItem<'a>>,
337 {
338 let items = items.into_iter().map(|v| v.into()).collect();
339 self.items = items;
340 self
341 }
342
343 #[inline]
345 pub fn block(mut self, block: Block<'a>) -> Self {
346 self.block = Some(block);
347 self.block = self.block.map(|v| v.style(self.style));
348 self
349 }
350
351 #[inline]
353 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
354 self.scroll = Some(scroll);
355 self
356 }
357
358 #[inline]
360 pub fn styles_opt(self, styles: Option<ListStyle>) -> Self {
361 if let Some(styles) = styles {
362 self.styles(styles)
363 } else {
364 self
365 }
366 }
367
368 #[inline]
370 pub fn styles(mut self, styles: ListStyle) -> Self {
371 self.style = styles.style;
372 if styles.block.is_some() {
373 self.block = styles.block;
374 }
375 if let Some(border_style) = styles.border_style {
376 self.block = self.block.map(|v| v.border_style(border_style));
377 }
378 if let Some(title_style) = styles.title_style {
379 self.block = self.block.map(|v| v.title_style(title_style));
380 }
381 self.block = self.block.map(|v| v.style(self.style));
382
383 if let Some(styles) = styles.scroll {
384 self.scroll = self.scroll.map(|v| v.styles(styles));
385 }
386 if let Some(scroll_padding) = styles.scroll_padding {
387 self.scroll_padding = scroll_padding;
388 }
389
390 if styles.select.is_some() {
391 self.select_style = styles.select;
392 }
393 if styles.focus.is_some() {
394 self.focus_style = styles.focus;
395 }
396 if let Some(highlight_spacing) = styles.highlight_spacing {
397 self.highlight_spacing = highlight_spacing;
398 }
399 if let Some(highlight_symbol) = styles.highlight_symbol {
400 self.highlight_symbol = Some(highlight_symbol.into());
401 }
402 if let Some(repeat_highlight_symbol) = styles.repeat_highlight_symbol {
403 self.repeat_highlight_symbol = repeat_highlight_symbol;
404 }
405 if let Some(highlight_style) = styles.highlight_style {
406 self.highlight_style = Some(highlight_style);
407 }
408 self
409 }
410
411 #[inline]
413 pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
414 self.style = style.into();
415 self.block = self.block.map(|v| v.style(self.style));
416 self
417 }
418
419 #[inline]
421 pub fn select_style<S: Into<Style>>(mut self, select_style: S) -> Self {
422 self.select_style = Some(select_style.into());
423 self
424 }
425
426 #[inline]
428 pub fn focus_style<S: Into<Style>>(mut self, focus_style: S) -> Self {
429 self.focus_style = Some(focus_style.into());
430 self
431 }
432
433 pub fn varying_heights(mut self, varying: bool) -> Self {
440 self.varying_heights = varying;
441 self
442 }
443
444 #[inline]
451 pub fn direction(mut self, direction: ListDirection) -> Self {
452 self.direction = direction;
453 self
454 }
455
456 #[inline]
459 pub fn highlight_symbol<L: Into<Line<'a>>>(mut self, highlight_symbol: L) -> Self {
460 self.highlight_symbol = Some(highlight_symbol.into());
461 self
462 }
463
464 #[inline]
468 pub fn highlight_style<S: Into<Style>>(mut self, style: S) -> Self {
469 self.highlight_style = Some(style.into());
470 self
471 }
472
473 #[inline]
477 pub fn repeat_highlight_symbol(mut self, repeat: bool) -> Self {
478 self.repeat_highlight_symbol = repeat;
479 self
480 }
481
482 pub fn highlight_spacing(mut self, value: HighlightSpacing) -> Self {
502 self.highlight_spacing = value;
503 self
504 }
505
506 pub fn scroll_padding(mut self, padding: usize) -> Self {
509 self.scroll_padding = padding;
510 self
511 }
512
513 #[inline]
515 pub fn len(&self) -> usize {
516 self.items.len()
517 }
518
519 #[inline]
521 pub fn is_empty(&self) -> bool {
522 self.items.is_empty()
523 }
524
525 pub fn width(&self) -> u16 {
527 let mut width = 0;
528 for item in &self.items {
529 width = max(width, item.width() as u16);
530 }
531 width
532 }
533
534 pub fn height(&self) -> u16 {
536 let mut height = 0u16;
537 for item in &self.items {
538 height += height.saturating_add(item.height() as u16);
539 }
540 height
541 }
542}
543
544impl<'a, Item, Selection> FromIterator<Item> for List<'a, Selection>
545where
546 Item: Into<ListItem<'a>>,
547{
548 fn from_iter<Iter: IntoIterator<Item = Item>>(iter: Iter) -> Self {
549 Self::new(iter)
550 }
551}
552
553impl<Selection: ListSelection> StatefulWidget for List<'_, Selection> {
554 type State = ListState<Selection>;
555
556 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
557 render_list(self, area, buf, state)
558 }
559}
560
561fn render_list<Selection: ListSelection>(
562 widget: List<'_, Selection>,
563 area: Rect,
564 buf: &mut Buffer,
565 state: &mut ListState<Selection>,
566) {
567 state.rows = widget.items.len();
568 state.area = area;
569 state.selection.validate_rows(state.rows);
570 state.scroll_padding = widget.scroll_padding;
571 state.direction = widget.direction;
572
573 let sa = ScrollArea::new()
574 .style(widget.style)
575 .block(widget.block.as_ref())
576 .v_scroll(widget.scroll.as_ref());
577 state.inner = sa.inner(area, None, Some(&state.scroll));
578 sa.render(
579 area,
580 buf,
581 &mut ScrollAreaState::new().v_scroll(&mut state.scroll),
582 );
583
584 let mut n = 0;
586 let mut height = 0;
587 for item in widget.items.iter().rev() {
588 height += item.height();
589 if height > state.inner.height as usize {
590 break;
591 }
592 n += 1;
593 }
594 state.scroll.set_max_offset(state.rows.saturating_sub(n));
595
596 let (high_area, item_area) = layout_columns(&widget, state.inner, state);
597
598 let focus_style = widget.focus_style.unwrap_or(revert_style(widget.style));
599 let select_style = widget
600 .select_style
601 .unwrap_or(fallback_select_style(widget.style));
602 let (style, select_style) = if state.is_focused() {
603 (widget.style, focus_style)
604 } else {
605 (widget.style, select_style)
606 };
607 let high_style = if let Some(highlight_style) = widget.highlight_style {
608 style.patch(highlight_style)
609 } else {
610 style
611 };
612 let high_sym = if let Some(high_sym) = widget.highlight_symbol {
613 high_sym
614 } else {
615 Line::default()
616 };
617
618 state.row_areas.clear();
619 state.row_heights.clear();
620 state.scroll.page_len = 0;
621
622 if widget.direction == ListDirection::TopToBottom {
623 let mut row_y = state.inner.y;
624 for (row, item) in widget.items.into_iter().enumerate() {
625 let row_height = item.height() as u8;
626 state.row_heights.push(row_height, widget.varying_heights);
627
628 if row < state.offset() || row_y >= state.inner.bottom() {
629 continue;
630 }
631
632 let row_area = Rect::new(state.inner.x, row_y, state.inner.width, row_height as u16)
633 .intersection(state.inner);
634
635 state.row_areas.push(row_area);
636 state.scroll.page_len += 1;
637
638 let style = if state.selection.is_selected(row) {
639 style.patch(select_style).patch(item.style)
640 } else {
641 style.patch(item.style)
642 };
643 buf.set_style(row_area, style);
644 buf.set_style(row_area, item.content.style);
645
646 for mut line in item.content.lines.into_iter() {
647 if row_y >= state.inner.bottom() {
648 break;
649 }
650
651 line.alignment = line.alignment.or(item.content.alignment);
652 line.render(
653 Rect::new(row_area.x + item_area.x, row_y, item_area.width, 1),
654 buf,
655 );
656
657 row_y += 1;
658 }
659
660 if state.selection.is_selected(row) {
661 for yy in row_area.y..row_area.bottom() {
662 let high_area = Rect::new(
663 row_area.x + high_area.x, yy,
665 high_area.width,
666 1,
667 );
668 if widget.repeat_highlight_symbol || yy == row_area.y {
669 buf.set_style(high_area, high_style);
670 (&high_sym).render(high_area, buf);
671 }
672 }
673 }
674 }
675 } else if widget.direction == ListDirection::BottomToTop {
676 let mut row_y = state.inner.bottom();
677 for (row, item) in widget.items.into_iter().enumerate() {
678 let row_height = item.height() as u8;
679 state.row_heights.push(row_height, widget.varying_heights);
680
681 if row < state.offset() || row_y <= state.inner.y {
682 continue;
683 }
684
685 let row_area = Rect::new(
686 state.inner.x,
687 row_y.saturating_sub(row_height as u16),
688 state.inner.width,
689 row_height as u16,
690 )
691 .intersection(state.inner);
692
693 state.row_areas.push(row_area);
694 state.scroll.page_len += 1;
695
696 let style = if state.selection.is_selected(row) {
697 style.patch(select_style).patch(item.style)
698 } else {
699 style.patch(item.style)
700 };
701 buf.set_style(row_area, style);
702 buf.set_style(row_area, item.content.style);
703
704 for mut line in item.content.lines.into_iter().rev() {
706 if row_y <= state.inner.y {
707 break;
708 }
709 row_y -= 1;
710
711 line.alignment = line.alignment.or(item.content.alignment);
712 line.render(
713 Rect::new(row_area.x + item_area.x, row_y, item_area.width, 1),
714 buf,
715 );
716 }
717
718 if state.selection.is_selected(row) {
719 for yy in row_area.y..row_area.bottom() {
720 let high_area = Rect::new(
721 row_area.x + high_area.x, yy,
723 high_area.width,
724 1,
725 );
726 if widget.repeat_highlight_symbol || yy == row_area.y {
727 buf.set_style(high_area, high_style);
728 (&high_sym).render(high_area, buf);
729 }
730 }
731 }
732 }
733 }
734}
735
736fn layout_columns<Selection: ListSelection>(
737 widget: &List<'_, Selection>,
738 area: Rect,
739 state: &ListState<Selection>,
740) -> (Rect, Rect) {
741 let is_highlight = match widget.highlight_spacing {
742 HighlightSpacing::Always => true,
743 HighlightSpacing::WhenSelected => state.selection.lead_selection().is_some(),
744 HighlightSpacing::Never => false,
745 };
746
747 let highlight_area = if let Some(highlight_symbol) = &widget.highlight_symbol
748 && is_highlight
749 {
750 Rect::new(0, 0, highlight_symbol.width() as u16, 0)
751 } else {
752 Rect::new(0, 0, 0, 0)
753 };
754
755 let item_area = Rect::new(
756 highlight_area.right(),
757 0,
758 area.width.saturating_sub(highlight_area.right()),
759 0,
760 );
761
762 (highlight_area, item_area)
763}
764
765impl<Selection> HasFocus for ListState<Selection> {
766 fn build(&self, builder: &mut FocusBuilder) {
767 builder.leaf_widget(self);
768 }
769
770 #[inline]
771 fn focus(&self) -> FocusFlag {
772 self.focus.clone()
773 }
774
775 #[inline]
776 fn area(&self) -> Rect {
777 self.area
778 }
779}
780
781impl<Selection> HasScreenCursor for ListState<Selection> {
782 fn screen_cursor(&self) -> Option<(u16, u16)> {
783 None
784 }
785}
786
787impl<Selection: Default> Default for ListState<Selection> {
788 fn default() -> Self {
789 Self {
790 area: Default::default(),
791 inner: Default::default(),
792 row_areas: Default::default(),
793 row_heights: Default::default(),
794 scroll_padding: Default::default(),
795 direction: Default::default(),
796 rows: Default::default(),
797 scroll: Default::default(),
798 focus: Default::default(),
799 selection: Default::default(),
800 mouse: Default::default(),
801 non_exhaustive: NonExhaustive,
802 }
803 }
804}
805
806impl<Selection: Clone> Clone for ListState<Selection> {
807 fn clone(&self) -> Self {
808 Self {
809 area: self.area,
810 inner: self.inner,
811 row_areas: self.row_areas.clone(),
812 row_heights: self.row_heights.clone(),
813 scroll_padding: self.scroll_padding,
814 direction: self.direction,
815 rows: self.rows,
816 scroll: self.scroll.clone(),
817 focus: self.focus.new_instance(),
818 selection: self.selection.clone(),
819 mouse: Default::default(),
820 non_exhaustive: NonExhaustive,
821 }
822 }
823}
824
825impl<Selection> RelocatableState for ListState<Selection> {
826 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
827 self.area.relocate(shift, clip);
828 self.inner.relocate(shift, clip);
829 relocate_areas(self.row_areas.as_mut_slice(), shift, clip);
830 self.scroll.relocate(shift, clip);
831 }
832}
833
834impl<Selection: ListSelection> ListState<Selection> {
835 pub fn new() -> Self
837 where
838 Selection: Default,
839 {
840 Default::default()
841 }
842
843 pub fn named(name: &str) -> Self
845 where
846 Selection: Default,
847 {
848 let mut z = Self::default();
849 z.focus = z.focus.with_name(name);
850 z
851 }
852
853 #[inline]
854 pub fn rows(&self) -> usize {
855 self.rows
856 }
857
858 pub fn items_added(&mut self, pos: usize, n: usize) {
862 self.rows = self.rows.saturating_add(n);
863 self.scroll.items_added(pos, n);
864 self.row_heights.items_added(pos, n);
865 self.selection.items_added(pos, n);
866 }
867
868 pub fn items_added_with_heights(&mut self, pos: usize, height: &[u8]) {
875 self.rows = self.rows.saturating_add(height.len());
876 self.scroll.items_added(pos, height.len());
877 self.row_heights.items_added_with_heights(pos, height);
878 self.selection.items_added(pos, height.len());
879 }
880
881 pub fn items_removed(&mut self, pos: usize, n: usize) {
885 self.rows = self.rows.saturating_sub(n);
886 self.scroll.items_removed(pos, n);
887 self.row_heights.items_removed(pos, n);
888 self.selection.items_removed(pos, n, self.rows);
889 }
890
891 pub fn rows_changed(&mut self, rows: usize) {
901 self.selection.validate_rows(rows);
902 self.scroll.rows_changed(rows);
903 self.row_heights.clear();
904 self.rows = rows;
905 }
906
907 #[inline]
908 pub fn clear_offset(&mut self) {
909 self.scroll.set_offset(0);
910 }
911
912 #[inline]
913 pub fn max_offset(&self) -> usize {
914 self.scroll.max_offset()
915 }
916
917 #[inline]
918 pub fn set_max_offset(&mut self, max: usize) {
919 self.scroll.set_max_offset(max);
920 }
921
922 #[inline]
923 pub fn offset(&self) -> usize {
924 self.scroll.offset()
925 }
926
927 #[inline]
928 pub fn set_offset(&mut self, offset: usize) -> bool {
929 self.scroll.set_offset(offset)
930 }
931
932 #[inline]
933 pub fn page_len(&self) -> usize {
934 self.scroll.page_len()
935 }
936
937 pub fn scroll_by(&self) -> usize {
938 self.scroll.scroll_by()
939 }
940
941 #[inline]
943 pub fn scroll_to_selected(&mut self) -> bool {
944 if let Some(selected) = self.selection.lead_selection() {
945 self.scroll_to(selected)
946 } else {
947 false
948 }
949 }
950
951 #[inline]
952 pub fn scroll_to(&mut self, mut pos: usize) -> bool {
953 if pos >= self.rows {
954 false
955 } else if self.inner.is_empty() {
956 self.set_offset(pos)
958 } else if let RowHeights::Uniform(row_height) = &self.row_heights {
959 let page_len = self.inner.height.div_ceil(*row_height as u16) as usize;
960 let scroll_padding = min((page_len - 1) / 2, self.scroll_padding);
961 if pos
962 >= self
963 .offset()
964 .saturating_add(page_len - 1)
965 .saturating_sub(scroll_padding)
966 {
967 if page_len as u16 * (*row_height) as u16 > self.inner.height {
968 self.set_offset(pos.saturating_sub(page_len - 2))
969 } else {
970 self.set_offset(pos.saturating_sub(page_len - 1))
971 }
972 } else if pos < self.offset() {
973 self.set_offset(pos.saturating_sub(scroll_padding))
974 } else {
975 false
976 }
977 } else if let RowHeights::Varying(row_heights) = &self.row_heights {
978 let scroll_padding = min((self.page_len() - 1) / 2, self.scroll_padding);
979 if pos
980 >= self
981 .offset()
982 .saturating_add(self.page_len() - 1)
983 .saturating_sub(scroll_padding)
984 {
985 if pos >= row_heights.len() {
986 self.set_offset(pos)
987 } else {
988 pos = min(pos + scroll_padding, row_heights.len().saturating_sub(1));
989 let mut sum_height = row_heights[pos] as u16;
990 if sum_height < self.inner.height {
991 loop {
992 if pos < 1 {
993 break;
994 }
995 sum_height += row_heights[pos - 1] as u16;
996 if sum_height > self.inner.height {
997 break;
998 }
999 pos -= 1;
1000 }
1001 }
1002 self.set_offset(pos)
1003 }
1004 } else if pos < self.offset().saturating_add(scroll_padding) {
1005 self.set_offset(pos.saturating_sub(scroll_padding))
1006 } else {
1007 false
1008 }
1009 } else {
1010 false
1011 }
1012 }
1013
1014 #[inline]
1015 pub fn scroll_up(&mut self, n: usize) -> bool {
1016 if self.direction == ListDirection::TopToBottom {
1017 self.scroll.scroll_up(n)
1018 } else {
1019 self.scroll.scroll_down(n)
1020 }
1021 }
1022
1023 #[inline]
1024 pub fn scroll_down(&mut self, n: usize) -> bool {
1025 if self.direction == ListDirection::TopToBottom {
1026 self.scroll.scroll_down(n)
1027 } else {
1028 self.scroll.scroll_up(n)
1029 }
1030 }
1031}
1032
1033impl<Selection: ListSelection> ListState<Selection> {
1034 pub fn row_area(&self, row: usize) -> Option<Rect> {
1036 if row < self.scroll.offset() || row >= self.scroll.offset() + self.scroll.page_len() {
1037 return None;
1038 }
1039 let row = row - self.scroll.offset;
1040 if row >= self.row_areas.len() {
1041 None
1042 } else {
1043 Some(self.row_areas[row])
1044 }
1045 }
1046
1047 #[inline]
1048 pub fn row_at_clicked(&self, pos: (u16, u16)) -> Option<usize> {
1049 self.mouse
1050 .row_at(&self.row_areas, pos.1)
1051 .map(|v| self.scroll.offset() + v)
1052 }
1053
1054 #[inline]
1056 pub fn row_at_drag(&self, pos: (u16, u16)) -> usize {
1057 match self.mouse.row_at_drag(self.inner, &self.row_areas, pos.1) {
1058 Ok(v) => self.scroll.offset() + v,
1059 Err(v) if v <= 0 => self.scroll.offset().saturating_sub((-v) as usize),
1060 Err(v) => self.scroll.offset() + self.row_areas.len() + v as usize,
1061 }
1062 }
1063}
1064
1065impl ListState<RowSelection> {
1066 #[inline]
1068 pub fn set_scroll_selection(&mut self, scroll: bool) {
1069 self.selection.set_scroll_selected(scroll);
1070 }
1071
1072 pub(crate) fn remap_offset_selection(&self, offset: usize) -> usize {
1076 if self.scroll.max_offset() > 0 {
1077 (self.rows * offset) / self.scroll.max_offset()
1078 } else {
1079 0 }
1081 }
1082
1083 #[inline]
1085 pub fn clear_selection(&mut self) {
1086 self.selection.clear();
1087 }
1088
1089 #[inline]
1091 pub fn has_selection(&mut self) -> bool {
1092 self.selection.has_selection()
1093 }
1094
1095 #[inline]
1097 pub fn selected(&self) -> Option<usize> {
1098 self.selection.lead_selection()
1099 }
1100
1101 #[inline]
1104 pub fn selected_checked(&self) -> Option<usize> {
1105 self.selection.lead_selection().filter(|v| *v < self.rows)
1106 }
1107
1108 #[inline]
1109 pub fn select(&mut self, row: Option<usize>) -> bool {
1110 self.selection.select(row)
1111 }
1112
1113 #[inline]
1116 pub fn move_to(&mut self, row: usize) -> bool {
1117 let r = self.selection.move_to(row, self.rows.saturating_sub(1));
1118 let s = self.scroll_to(self.selection.selected().expect("row"));
1119 r || s
1120 }
1121
1122 #[inline]
1125 pub fn move_up(&mut self, n: usize) -> bool {
1126 let r = if self.direction == ListDirection::TopToBottom {
1127 self.selection.move_up(n, self.rows.saturating_sub(1))
1128 } else {
1129 self.selection.move_down(n, self.rows.saturating_sub(1))
1130 };
1131 let s = self.scroll_to(self.selection.selected().expect("row"));
1132 r || s
1133 }
1134
1135 #[inline]
1138 pub fn move_down(&mut self, n: usize) -> bool {
1139 let r = if self.direction == ListDirection::TopToBottom {
1140 self.selection.move_down(n, self.rows.saturating_sub(1))
1141 } else {
1142 self.selection.move_up(n, self.rows.saturating_sub(1))
1143 };
1144 let s = self.scroll_to(self.selection.selected().expect("row"));
1145 r || s
1146 }
1147}
1148
1149impl ListState<RowSetSelection> {
1150 #[inline]
1152 pub fn clear_selection(&mut self) {
1153 self.selection.clear();
1154 }
1155
1156 #[inline]
1158 pub fn has_selection(&mut self) -> bool {
1159 self.selection.has_selection()
1160 }
1161
1162 #[inline]
1163 pub fn selected(&self) -> HashSet<usize> {
1164 self.selection.selected()
1165 }
1166
1167 #[inline]
1174 pub fn set_lead(&mut self, row: Option<usize>, extend: bool) -> bool {
1175 if let Some(row) = row {
1176 self.selection
1177 .set_lead(Some(min(row, self.rows.saturating_sub(1))), extend)
1178 } else {
1179 self.selection.set_lead(None, extend)
1180 }
1181 }
1182
1183 #[inline]
1185 pub fn lead(&self) -> Option<usize> {
1186 self.selection.lead()
1187 }
1188
1189 #[inline]
1191 pub fn anchor(&self) -> Option<usize> {
1192 self.selection.anchor()
1193 }
1194
1195 #[inline]
1197 pub fn set_lead_clamped(&mut self, lead: usize, max: usize, extend: bool) {
1198 self.selection.move_to(lead, max, extend);
1199 }
1200
1201 #[inline]
1204 pub fn retire_selection(&mut self) {
1205 self.selection.retire_selection();
1206 }
1207
1208 #[inline]
1210 pub fn add_selected(&mut self, idx: usize) {
1211 self.selection.add(idx);
1212 }
1213
1214 #[inline]
1217 pub fn remove_selected(&mut self, idx: usize) {
1218 self.selection.remove(idx);
1219 }
1220
1221 #[inline]
1224 pub fn move_to(&mut self, row: usize, extend: bool) -> bool {
1225 let r = self
1226 .selection
1227 .move_to(row, self.rows.saturating_sub(1), extend);
1228 let s = self.scroll_to(self.selection.lead().expect("row"));
1229 r || s
1230 }
1231
1232 #[inline]
1235 pub fn move_up(&mut self, n: usize, extend: bool) -> bool {
1236 let r = if self.direction == ListDirection::TopToBottom {
1237 self.selection
1238 .move_up(n, self.rows.saturating_sub(1), extend)
1239 } else {
1240 self.selection
1241 .move_down(n, self.rows.saturating_sub(1), extend)
1242 };
1243 let s = self.scroll_to(self.selection.lead().expect("row"));
1244 r || s
1245 }
1246
1247 #[inline]
1250 pub fn move_down(&mut self, n: usize, extend: bool) -> bool {
1251 let r = if self.direction == ListDirection::TopToBottom {
1252 self.selection
1253 .move_down(n, self.rows.saturating_sub(1), extend)
1254 } else {
1255 self.selection
1256 .move_up(n, self.rows.saturating_sub(1), extend)
1257 };
1258 let s = self.scroll_to(self.selection.lead().expect("row"));
1259 r || s
1260 }
1261}
1262
1263pub mod selection {
1264 use crate::event::{HandleEvent, MouseOnly, Outcome, Regular, ct_event};
1269 use crate::list::{ListSelection, ListState};
1270 use rat_event::event_flow;
1271 use rat_focus::HasFocus;
1272 use rat_ftable::TableSelection;
1273 use rat_scrolled::ScrollAreaState;
1274 use rat_scrolled::event::ScrollOutcome;
1275 use ratatui_crossterm::crossterm::event::{Event, KeyModifiers};
1276 use std::cmp::max;
1277
1278 pub type NoSelection = rat_ftable::selection::NoSelection;
1280
1281 impl ListSelection for NoSelection {
1282 fn count(&self) -> usize {
1283 0
1284 }
1285
1286 #[inline]
1287 fn is_selected(&self, _n: usize) -> bool {
1288 false
1289 }
1290
1291 #[inline]
1292 fn lead_selection(&self) -> Option<usize> {
1293 None
1294 }
1295
1296 fn validate_rows(&mut self, _rows: usize) {}
1297
1298 fn items_added(&mut self, _pos: usize, _n: usize) {}
1299
1300 fn items_removed(&mut self, _pos: usize, _n: usize, _rows: usize) {}
1301 }
1302
1303 impl HandleEvent<Event, Regular, Outcome> for ListState<NoSelection> {
1304 fn handle(&mut self, event: &Event, _keymap: Regular) -> Outcome {
1305 let res = if self.is_focused() {
1306 match event {
1307 ct_event!(keycode press Down) => self.scroll_down(1).into(),
1308 ct_event!(keycode press Up) => self.scroll_up(1).into(),
1309 ct_event!(keycode press CONTROL-Down) | ct_event!(keycode press End) => {
1310 self.scroll_to(self.max_offset()).into()
1311 }
1312 ct_event!(keycode press CONTROL-Up) | ct_event!(keycode press Home) => {
1313 self.scroll_to(0).into()
1314 }
1315 ct_event!(keycode press PageUp) => {
1316 self.scroll_up(self.page_len().saturating_sub(1)).into()
1317 }
1318 ct_event!(keycode press PageDown) => {
1319 self.scroll_down(self.page_len().saturating_sub(1)).into()
1320 }
1321 _ => Outcome::Continue,
1322 }
1323 } else {
1324 Outcome::Continue
1325 };
1326
1327 if res == Outcome::Continue {
1328 self.handle(event, MouseOnly)
1329 } else {
1330 res
1331 }
1332 }
1333 }
1334
1335 impl HandleEvent<Event, MouseOnly, Outcome> for ListState<NoSelection> {
1336 fn handle(&mut self, event: &Event, _keymap: MouseOnly) -> Outcome {
1337 if self.is_obscured() {
1338 return Outcome::Continue;
1339 }
1340
1341 let mut sas = ScrollAreaState::new()
1342 .area(self.inner)
1343 .v_scroll(&mut self.scroll);
1344 let r = match sas.handle(event, MouseOnly) {
1345 ScrollOutcome::Up(v) => self.scroll_up(v),
1346 ScrollOutcome::Down(v) => self.scroll_down(v),
1347 ScrollOutcome::VPos(v) => self.set_offset(v),
1348 ScrollOutcome::Left(_) => false,
1349 ScrollOutcome::Right(_) => false,
1350 ScrollOutcome::HPos(_) => false,
1351
1352 ScrollOutcome::Continue => false,
1353 ScrollOutcome::Unchanged => false,
1354 ScrollOutcome::Changed => true,
1355 };
1356 if r {
1357 return Outcome::Changed;
1358 }
1359
1360 Outcome::Unchanged
1361 }
1362 }
1363
1364 pub type RowSelection = rat_ftable::selection::RowSelection;
1366
1367 impl ListSelection for RowSelection {
1368 fn count(&self) -> usize {
1369 <Self as TableSelection>::count(self)
1370 }
1371
1372 #[inline]
1373 fn is_selected(&self, n: usize) -> bool {
1374 <Self as TableSelection>::is_selected_row(self, n)
1375 }
1376
1377 #[inline]
1378 fn lead_selection(&self) -> Option<usize> {
1379 <Self as TableSelection>::lead_selection(self).map(|(_, y)| y)
1380 }
1381
1382 fn scroll_selected(&self) -> bool {
1383 RowSelection::scroll_selected(self)
1384 }
1385
1386 fn validate_rows(&mut self, rows: usize) {
1387 <Self as TableSelection>::validate_rows(self, rows);
1388 }
1389
1390 fn items_added(&mut self, pos: usize, n: usize) {
1391 <Self as TableSelection>::items_added(self, pos, n);
1392 }
1393
1394 fn items_removed(&mut self, pos: usize, n: usize, rows: usize) {
1395 <Self as TableSelection>::items_removed(self, pos, n, rows);
1396 }
1397 }
1398
1399 impl HandleEvent<Event, Regular, Outcome> for ListState<RowSelection> {
1400 fn handle(&mut self, event: &Event, _keymap: Regular) -> Outcome {
1401 let res = if self.is_focused() {
1402 match event {
1403 ct_event!(keycode press Down) => self.move_down(1).into(),
1404 ct_event!(keycode press Up) => self.move_up(1).into(),
1405 ct_event!(keycode press CONTROL-Down) | ct_event!(keycode press End) => {
1406 self.move_to(self.rows.saturating_sub(1)).into()
1407 }
1408 ct_event!(keycode press CONTROL-Up) | ct_event!(keycode press Home) => {
1409 self.move_to(0).into()
1410 }
1411 ct_event!(keycode press PageUp) => self
1412 .move_up(max(1, self.page_len().saturating_sub(1)))
1413 .into(),
1414 ct_event!(keycode press PageDown) => self
1415 .move_down(max(1, self.page_len().saturating_sub(1)))
1416 .into(),
1417 _ => Outcome::Continue,
1418 }
1419 } else {
1420 Outcome::Continue
1421 };
1422
1423 if res == Outcome::Continue {
1424 self.handle(event, MouseOnly)
1425 } else {
1426 res
1427 }
1428 }
1429 }
1430
1431 impl HandleEvent<Event, MouseOnly, Outcome> for ListState<RowSelection> {
1432 fn handle(&mut self, event: &Event, _keymap: MouseOnly) -> Outcome {
1433 if self.is_obscured() {
1434 return Outcome::Continue;
1435 }
1436
1437 event_flow!(
1438 return match event {
1439 ct_event!(mouse any for m) if self.mouse.drag(self.inner, m) => {
1440 self.move_to(self.row_at_drag((m.column, m.row))).into()
1441 }
1442 ct_event!(mouse down Left for column, row) => {
1443 if self.inner.contains((*column, *row).into()) {
1444 if let Some(new_row) = self.row_at_clicked((*column, *row)) {
1445 self.move_to(new_row).into()
1446 } else {
1447 Outcome::Continue
1448 }
1449 } else {
1450 Outcome::Continue
1451 }
1452 }
1453
1454 _ => Outcome::Continue,
1455 }
1456 );
1457
1458 let mut sas = ScrollAreaState::new()
1459 .area(self.inner)
1460 .v_scroll(&mut self.scroll);
1461 let r = match sas.handle(event, MouseOnly) {
1462 ScrollOutcome::Up(v) => {
1463 if ListSelection::scroll_selected(&self.selection) {
1464 self.move_up(1)
1465 } else {
1466 self.scroll_up(v)
1467 }
1468 }
1469 ScrollOutcome::Down(v) => {
1470 if ListSelection::scroll_selected(&self.selection) {
1471 self.move_down(1)
1472 } else {
1473 self.scroll_down(v)
1474 }
1475 }
1476 ScrollOutcome::VPos(v) => {
1477 if ListSelection::scroll_selected(&self.selection) {
1478 self.move_to(self.remap_offset_selection(v))
1479 } else {
1480 self.set_offset(v)
1481 }
1482 }
1483 ScrollOutcome::Left(_) => false,
1484 ScrollOutcome::Right(_) => false,
1485 ScrollOutcome::HPos(_) => false,
1486
1487 ScrollOutcome::Continue => false,
1488 ScrollOutcome::Unchanged => false,
1489 ScrollOutcome::Changed => true,
1490 };
1491 if r {
1492 return Outcome::Changed;
1493 }
1494
1495 Outcome::Continue
1496 }
1497 }
1498
1499 pub type RowSetSelection = rat_ftable::selection::RowSetSelection;
1500
1501 impl ListSelection for RowSetSelection {
1502 fn count(&self) -> usize {
1503 <Self as TableSelection>::count(self)
1504 }
1505
1506 fn is_selected(&self, n: usize) -> bool {
1507 <Self as TableSelection>::is_selected_row(self, n)
1508 }
1509
1510 fn lead_selection(&self) -> Option<usize> {
1511 <Self as TableSelection>::lead_selection(self).map(|(_, y)| y)
1512 }
1513
1514 fn validate_rows(&mut self, rows: usize) {
1515 <Self as TableSelection>::validate_rows(self, rows);
1516 }
1517
1518 fn items_added(&mut self, pos: usize, n: usize) {
1519 <Self as TableSelection>::items_added(self, pos, n);
1520 }
1521
1522 fn items_removed(&mut self, pos: usize, n: usize, rows: usize) {
1523 <Self as TableSelection>::items_removed(self, pos, n, rows);
1524 }
1525 }
1526
1527 impl HandleEvent<Event, Regular, Outcome> for ListState<RowSetSelection> {
1528 fn handle(&mut self, event: &Event, _: Regular) -> Outcome {
1529 let res = if self.is_focused() {
1530 match event {
1531 ct_event!(keycode press Down) => self.move_down(1, false).into(),
1532 ct_event!(keycode press SHIFT-Down) => self.move_down(1, true).into(),
1533 ct_event!(keycode press Up) => self.move_up(1, false).into(),
1534 ct_event!(keycode press SHIFT-Up) => self.move_up(1, true).into(),
1535 ct_event!(keycode press CONTROL-Down) | ct_event!(keycode press End) => {
1536 self.move_to(self.rows.saturating_sub(1), false).into()
1537 }
1538 ct_event!(keycode press SHIFT-End) => {
1539 self.move_to(self.rows.saturating_sub(1), true).into()
1540 }
1541 ct_event!(keycode press CONTROL-Up) | ct_event!(keycode press Home) => {
1542 self.move_to(0, false).into()
1543 }
1544 ct_event!(keycode press SHIFT-Home) => self.move_to(0, true).into(),
1545
1546 ct_event!(keycode press PageUp) => self
1547 .move_up(max(1, self.page_len().saturating_sub(1)), false)
1548 .into(),
1549 ct_event!(keycode press SHIFT-PageUp) => self
1550 .move_up(max(1, self.page_len().saturating_sub(1)), true)
1551 .into(),
1552 ct_event!(keycode press PageDown) => self
1553 .move_down(max(1, self.page_len().saturating_sub(1)), false)
1554 .into(),
1555 ct_event!(keycode press SHIFT-PageDown) => self
1556 .move_down(max(1, self.page_len().saturating_sub(1)), true)
1557 .into(),
1558 _ => Outcome::Continue,
1559 }
1560 } else {
1561 Outcome::Continue
1562 };
1563
1564 if res == Outcome::Continue {
1565 self.handle(event, MouseOnly)
1566 } else {
1567 res
1568 }
1569 }
1570 }
1571
1572 impl HandleEvent<Event, MouseOnly, Outcome> for ListState<RowSetSelection> {
1573 fn handle(&mut self, event: &Event, _: MouseOnly) -> Outcome {
1574 if self.is_obscured() {
1575 return Outcome::Continue;
1576 }
1577
1578 event_flow!(
1579 return match event {
1580 ct_event!(mouse any for m) | ct_event!(mouse any CONTROL for m)
1581 if self.mouse.drag(self.inner, m)
1582 || self.mouse.drag2(self.inner, m, KeyModifiers::CONTROL) =>
1583 {
1584 self.move_to(self.row_at_drag((m.column, m.row)), true)
1585 .into()
1586 }
1587 ct_event!(mouse down Left for column, row) => {
1588 let pos = (*column, *row);
1589 if self.inner.contains(pos.into()) {
1590 if let Some(new_row) = self.row_at_clicked(pos) {
1591 self.move_to(new_row, false).into()
1592 } else {
1593 Outcome::Continue
1594 }
1595 } else {
1596 Outcome::Continue
1597 }
1598 }
1599 ct_event!(mouse down ALT-Left for column, row) => {
1600 let pos = (*column, *row);
1601 if self.area.contains(pos.into()) {
1602 if let Some(new_row) = self.row_at_clicked(pos) {
1603 self.move_to(new_row, true).into()
1604 } else {
1605 Outcome::Continue
1606 }
1607 } else {
1608 Outcome::Continue
1609 }
1610 }
1611 ct_event!(mouse down CONTROL-Left for column, row) => {
1612 let pos = (*column, *row);
1613 if self.area.contains(pos.into()) {
1614 if let Some(new_row) = self.row_at_clicked(pos) {
1615 self.retire_selection();
1616 if self.selection.is_selected_row(new_row) {
1617 self.selection.remove(new_row);
1618 } else {
1619 self.move_to(new_row, true);
1620 }
1621 Outcome::Changed
1622 } else {
1623 Outcome::Continue
1624 }
1625 } else {
1626 Outcome::Continue
1627 }
1628 }
1629 _ => Outcome::Continue,
1630 }
1631 );
1632
1633 let mut sas = ScrollAreaState::new()
1634 .area(self.inner)
1635 .v_scroll(&mut self.scroll);
1636 let r = match sas.handle(event, MouseOnly) {
1637 ScrollOutcome::Up(v) => self.scroll_up(v),
1638 ScrollOutcome::Down(v) => self.scroll_down(v),
1639 ScrollOutcome::VPos(v) => self.set_offset(v),
1640 ScrollOutcome::Left(_) => false,
1641 ScrollOutcome::Right(_) => false,
1642 ScrollOutcome::HPos(_) => false,
1643
1644 ScrollOutcome::Continue => false,
1645 ScrollOutcome::Unchanged => false,
1646 ScrollOutcome::Changed => true,
1647 };
1648 if r {
1649 return Outcome::Changed;
1650 }
1651
1652 Outcome::Continue
1653 }
1654 }
1655}
1656
1657pub fn handle_events(state: &mut ListState, focus: bool, event: &Event) -> Outcome {
1661 state.focus.set(focus);
1662 HandleEvent::handle(state, event, Regular)
1663}
1664
1665pub fn handle_mouse_events(state: &mut ListState, event: &Event) -> Outcome {
1667 HandleEvent::handle(state, event, MouseOnly)
1668}