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