1use cursive_core::{
2 align::{Align, HAlign, VAlign},
3 direction,
4 event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent},
5 Rect,
6 style::{PaletteStyle, Style, StyleType},
7 utils::markup::StyledString,
8 view::{CannotFocus, View},
9 Cursive, Printer, Vec2, With,
10};
11use std::borrow::Borrow;
12use std::cmp::{min, Ordering};
13use std::sync::atomic::AtomicUsize;
14use std::sync::Arc;
15
16type SelectCallback<T> = dyn Fn(&mut Cursive, &T) + Send + Sync;
17
18pub struct MultipleChoiceView<T = String> {
23 items: Vec<Item<T>>,
26
27 enabled: bool,
29
30 focus: Arc<AtomicUsize>,
32
33 inactive_highlight: bool,
36
37 on_submit: Option<Arc<SelectCallback<T>>>,
40
41 on_select: Option<Arc<SelectCallback<T>>>,
44
45 autojump: bool,
48
49 align: Align,
50
51 choice_indicators: [&'static str; 2],
53
54 last_size: Vec2,
55
56 last_required_size: Option<Vec2>,
58}
59
60impl<T: 'static + Send + Sync> Default for MultipleChoiceView<T> {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl<T: 'static + Send + Sync> MultipleChoiceView<T> {
67 cursive_core::impl_enabled!(self.enabled);
68
69 pub fn new() -> Self {
71 MultipleChoiceView {
72 items: Vec::new(),
73 enabled: true,
74 focus: Arc::new(AtomicUsize::new(0)),
75 inactive_highlight: true,
76 on_select: None,
77 on_submit: None,
78 align: Align::top_left(),
79 choice_indicators: ["[ ]", "[X]"],
80 autojump: false,
81 last_required_size: None,
82 last_size: Vec2::zero(),
83 }
84 }
85
86 pub fn set_choice_indicators(&mut self, selection_indicators: [&'static str; 2]) {
101 self.choice_indicators = selection_indicators;
102 }
103
104 #[must_use]
120 pub fn with_choice_indicators(self, selection_indicators: [&'static str; 2]) -> Self {
121 self.with(|s| s.set_choice_indicators(selection_indicators))
122 }
123
124 pub fn set_autojump(&mut self, autojump: bool) {
129 self.autojump = autojump;
130 }
131
132 #[must_use]
139 pub fn autojump(self) -> Self {
140 self.with(|s| s.set_autojump(true))
141 }
142
143 pub fn set_inactive_highlight(&mut self, inactive_highlight: bool) {
149 self.inactive_highlight = inactive_highlight;
150 }
151
152 pub fn with_inactive_highlight(self, inactive_highlight: bool) -> Self {
160 self.with(|s| s.set_inactive_highlight(inactive_highlight))
161 }
162
163 pub fn get_inactive_highlight(&self) -> bool {
165 self.inactive_highlight
166 }
167
168 #[cursive_core::callback_helpers]
170 pub fn set_on_select<F>(&mut self, cb: F)
171 where
172 F: Fn(&mut Cursive, &T) + 'static + Send + Sync,
173 {
174 self.on_select = Some(Arc::new(cb));
175 }
176
177 #[must_use]
208 pub fn on_select<F>(self, cb: F) -> Self
209 where
210 F: Fn(&mut Cursive, &T) + 'static + Send + Sync,
211 {
212 self.with(|s| s.set_on_select(cb))
213 }
214
215 pub fn set_on_submit<F, V: ?Sized>(&mut self, cb: F)
223 where
224 F: 'static + Fn(&mut Cursive, &V) + Send + Sync,
225 T: Borrow<V>,
226 {
227 self.on_submit = Some(Arc::new(move |s, t| {
228 cb(s, t.borrow());
229 }));
230 }
231
232 #[must_use]
261 pub fn on_submit<F, V: ?Sized>(self, cb: F) -> Self
262 where
263 F: Fn(&mut Cursive, &V) + 'static + Send + Sync,
264 T: Borrow<V>,
265 {
266 self.with(|s| s.set_on_submit(cb))
267 }
268
269 #[must_use]
282 pub fn align(mut self, align: Align) -> Self {
283 self.align = align;
284
285 self
286 }
287
288 #[must_use]
291 pub fn v_align(mut self, v: VAlign) -> Self {
292 self.align.v = v;
293
294 self
295 }
296
297 #[must_use]
299 pub fn h_align(mut self, h: HAlign) -> Self {
300 self.align.h = h;
301
302 self
303 }
304
305 pub fn selection(&self) -> Option<Arc<T>> {
309 let focus = self.focus();
310 if self.len() <= focus {
311 None
312 } else {
313 Some(Arc::clone(&self.items[focus].value))
314 }
315 }
316
317 pub fn clear(&mut self) {
319 self.items.clear();
320 self.focus.store(0, std::sync::atomic::Ordering::Relaxed);
321 self.last_required_size = None;
322 }
323
324 pub fn add_item<S: Into<StyledString>>(&mut self, label: S, value: T) {
337 self.add_item_with_choice(label, value, false);
338 }
339
340 pub fn get_item(&self, i: usize) -> Option<(&str, &T)> {
350 self.iter().nth(i)
351 }
352
353 pub fn get_item_mut(&mut self, i: usize) -> Option<(&mut StyledString, &mut T)> {
355 if i >= self.items.len() {
356 None
357 } else {
358 self.last_required_size = None;
359 let item = &mut self.items[i];
360 if let Some(t) = Arc::get_mut(&mut item.value) {
361 let label = &mut item.label;
362 Some((label, t))
363 } else {
364 None
365 }
366 }
367 }
368
369 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&mut StyledString, &mut T)>
378 where
379 T: Clone,
380 {
381 self.last_required_size = None;
382 self.items
383 .iter_mut()
384 .map(|item| (&mut item.label, Arc::make_mut(&mut item.value)))
385 }
386
387 pub fn try_iter_mut(&mut self) -> impl Iterator<Item = (&mut StyledString, Option<&mut T>)> {
394 self.last_required_size = None;
395 self.items
396 .iter_mut()
397 .map(|item| (&mut item.label, Arc::get_mut(&mut item.value)))
398 }
399
400 pub fn iter(&self) -> impl Iterator<Item = (&str, &T)> {
404 self.items
405 .iter()
406 .map(|item| (item.label.source(), &*item.value))
407 }
408
409 pub fn add_item_with_choice<S: Into<StyledString>>(&mut self, label: S, value: T, chosen: bool) {
423 self.items.push(Item::new(label.into(), value, chosen));
424 self.last_required_size = None;
425 }
426
427 pub fn remove_item(&mut self, id: usize) -> Callback {
433 self.items.remove(id);
434 self.last_required_size = None;
435 let focus = self.focus();
436 (focus >= id && focus > 0)
437 .then(|| {
438 self.set_focus(focus - 1);
439 self.make_select_cb()
440 })
441 .flatten()
442 .unwrap_or_else(Callback::dummy)
443 }
444
445 pub fn insert_item<S>(&mut self, index: usize, label: S, value: T)
448 where
449 S: Into<StyledString>,
450 {
451 self.insert_item_with_choice(index, label, value, false);
452 }
453
454 pub fn insert_item_with_choice<S>(&mut self, index: usize, label: S, value: T, chosen: bool)
455 where
456 S: Into<StyledString>,
457 {
458 self.items.insert(index, Item::new(label.into(), value, chosen));
459 let focus = self.focus();
460 if focus >= index && !self.items.is_empty() {
462 self.set_focus(focus + 1);
463 }
464 self.last_required_size = None;
465 }
466
467 #[must_use]
480 pub fn item<S: Into<StyledString>>(self, label: S, value: T) -> Self {
481 self.with(|s| s.add_item(label, value))
482 }
483
484 #[must_use]
485 pub fn item_with_choice<S: Into<StyledString>>(self, label: S, value: T, chosen: bool) -> Self {
486 self.with(|s| s.add_item_with_choice(label, value, chosen))
487 }
488
489 pub fn add_all<S, I>(&mut self, iter: I)
491 where
492 S: Into<StyledString>,
493 I: IntoIterator<Item = (S, T)>,
494 {
495 for (s, t) in iter {
496 self.add_item(s, t);
497 }
498 }
499
500 #[must_use]
514 pub fn with_all<S, I>(self, iter: I) -> Self
515 where
516 S: Into<StyledString>,
517 I: IntoIterator<Item = (S, T)>,
518 {
519 self.with(|s| s.add_all(iter))
520 }
521
522 fn get_label_decorated(&self, i: usize) -> StyledString {
523 let s = self.items[i].label.source();
524 let prefix = if self.items[i].chosen {
525 self.choice_indicators[1]
526 } else {
527 self.choice_indicators[0]
528 };
529 StyledString::plain(format!("{} {}", prefix, s))
530 }
531
532 fn draw_item(&self, printer: &Printer, i: usize) {
533 let s = self.get_label_decorated(i);
534 let l = s.width();
535 let x = self.align.h.get_offset(l, printer.size.x);
536 printer.print_hline((0, 0), x, " ");
537 printer.print_styled((x, 0), &s);
538 if l < printer.size.x {
539 assert!((l + x) <= printer.size.x);
540 printer.print_hline((x + l, 0), printer.size.x - (l + x), " ");
541 }
542 }
543
544 pub fn selected_id(&self) -> Option<usize> {
548 if self.items.is_empty() {
549 None
550 } else {
551 Some(self.focus())
552 }
553 }
554
555 pub fn len(&self) -> usize {
570 self.items.len()
571 }
572
573 pub fn is_empty(&self) -> bool {
591 self.items.is_empty()
592 }
593
594 fn focus(&self) -> usize {
595 self.focus.load(std::sync::atomic::Ordering::Relaxed)
596 }
597
598 fn set_focus(&mut self, focus: usize) {
599 self.focus
600 .store(focus, std::sync::atomic::Ordering::Relaxed);
601 }
602
603 pub fn sort_by_label(&mut self) {
610 self.items
611 .sort_by(|a, b| a.label.source().cmp(b.label.source()));
612 }
613
614 pub fn sort_by<F>(&mut self, mut compare: F)
626 where
627 F: FnMut(&T, &T) -> Ordering,
628 {
629 self.items.sort_by(|a, b| compare(&a.value, &b.value));
630 }
631
632 pub fn sort_by_key<K, F>(&mut self, mut key_of: F)
639 where
640 F: FnMut(&T) -> K,
641 K: Ord,
642 {
643 self.items.sort_by_key(|item| key_of(&item.value));
644 }
645
646 pub fn set_selection(&mut self, i: usize) -> Callback {
652 let i = if self.is_empty() {
656 0
657 } else {
658 min(i, self.len() - 1)
659 };
660 self.set_focus(i);
661
662 self.make_select_cb().unwrap_or_else(Callback::dummy)
663 }
664
665 #[must_use]
671 pub fn selected(self, i: usize) -> Self {
672 self.with(|s| {
673 s.set_selection(i);
674 })
675 }
676
677 pub fn get_choice(&self) -> Vec<Arc<T>> {
679 let mut ret = Vec::new();
680 for i in &self.items {
681 if i.chosen {
682 ret.push(i.value.clone());
683 }
684 }
685 ret
686 }
687
688 pub fn select_up(&mut self, n: usize) -> Callback {
703 self.focus_up(n);
704 self.make_select_cb().unwrap_or_else(Callback::dummy)
705 }
706
707 pub fn select_down(&mut self, n: usize) -> Callback {
713 self.focus_down(n);
714 self.make_select_cb().unwrap_or_else(Callback::dummy)
715 }
716
717 fn focus_up(&mut self, n: usize) {
718 let focus: usize = self.focus().saturating_sub(n);
719 self.set_focus(focus);
720 }
721
722 fn focus_down(&mut self, n: usize) {
723 let focus = min(self.focus() + n, self.items.len().saturating_sub(1));
724 self.set_focus(focus);
725 }
726
727 fn submit(&mut self) -> EventResult {
728 let cb = self.on_submit.clone().unwrap();
729 EventResult::Consumed(
731 self.selection()
732 .map(|v: Arc<T>| Callback::from_fn(move |s| cb(s, &v))),
733 )
734 }
735
736 fn toggle_choice(&mut self) {
737 if let Some(item_id) = self.selected_id() {
738 self.items[item_id].toggle_choice();
739 }
740 }
741
742 fn on_char_event(&mut self, c: char) -> EventResult {
743 let i = {
744 let iter = self.iter().chain(self.iter());
749
750 let lower_c: Vec<char> = c.to_lowercase().collect();
752 let lower_c: &[char] = &lower_c;
753
754 if let Some((i, _)) = iter
755 .enumerate()
756 .skip(self.focus() + 1)
757 .find(|&(_, (label, _))| label.to_lowercase().starts_with(lower_c))
758 {
759 i % self.len()
760 } else {
761 return EventResult::Ignored;
762 }
763 };
764
765 self.set_focus(i);
766 let cb = self.set_selection(i);
768 EventResult::Consumed(Some(cb))
769 }
770
771 fn on_event_regular(&mut self, event: Event) -> EventResult {
772 match event {
773 Event::Key(Key::Up) if self.focus() > 0 => self.focus_up(1),
774 Event::Key(Key::Down) if self.focus() + 1 < self.items.len() => self.focus_down(1),
775 Event::Key(Key::PageUp) => self.focus_up(10),
776 Event::Key(Key::PageDown) => self.focus_down(10),
777 Event::Key(Key::Home) => self.set_focus(0),
778 Event::Key(Key::End) => self.set_focus(self.items.len().saturating_sub(1)),
779 Event::Mouse {
780 event: MouseEvent::Press(_),
781 position,
782 offset,
783 } if position
784 .checked_sub(offset)
785 .map(|position| position < self.last_size && position.y < self.len())
786 .unwrap_or(false) =>
787 {
788 self.set_focus(position.y - offset.y)
789 }
790 Event::Mouse {
791 event: MouseEvent::Release(MouseButton::Left),
792 position,
793 offset,
794 } =>
795 {
796 self.toggle_choice();
797 if self.on_submit.is_some()
798 && position
799 .checked_sub(offset)
800 .map(|position| position < self.last_size && position.y == self.focus())
801 .unwrap_or(false)
802 {
803 return self.submit();
804 }
805 }
806 Event::Key(Key::Enter) => {
807 self.toggle_choice();
808 if self.on_submit.is_some() {
809 return self.submit();
810 }
811 }
812 Event::Char(c) if self.autojump => return self.on_char_event(c),
813 _ => return EventResult::Ignored,
814 }
815
816 EventResult::Consumed(self.make_select_cb())
817 }
818
819 fn make_select_cb(&self) -> Option<Callback> {
821 self.on_select.clone().and_then(|cb| {
822 self.selection()
823 .map(|v| Callback::from_fn(move |s| cb(s, &v)))
824 })
825 }
826}
827
828impl MultipleChoiceView<String> {
829 pub fn add_item_str<S: Into<String>>(&mut self, label: S) {
831 self.add_item_str_with_choice(label, false);
832 }
833
834 pub fn add_item_str_with_choice<S: Into<String>>(&mut self, label: S, chosen: bool) {
836 let label = label.into();
837 self.add_item_with_choice(label.clone(), label, chosen);
838 }
839
840 pub fn add_item_styled<S: Into<StyledString>>(&mut self, label: S) {
842 let label = label.into();
843
844 let mut content = String::new();
846 for span in label.spans() {
847 content.push_str(span.content);
848 }
849
850 self.add_item(label, content);
851 }
852
853 #[must_use]
866 pub fn item_str<S: Into<String>>(self, label: S) -> Self {
867 self.with(|s| s.add_item_str(label))
868 }
869
870 #[must_use]
872 pub fn item_str_with_choice<S: Into<String>>(self, label: S, chosen: bool) -> Self {
873 self.with(|s| s.add_item_str_with_choice(label, chosen))
874 }
875
876 #[must_use]
878 pub fn item_styled<S: Into<StyledString>>(self, label: S) -> Self {
879 self.with(|s| s.add_item_styled(label))
880 }
881
882 pub fn insert_item_str<S>(&mut self, index: usize, label: S)
884 where
885 S: Into<String>,
886 {
887 let label = label.into();
888 self.insert_item(index, label.clone(), label);
889 }
890
891 pub fn add_all_str<S, I>(&mut self, iter: I)
901 where
902 S: Into<String>,
903 I: IntoIterator<Item = S>,
904 {
905 for s in iter {
906 self.add_item_str(s);
907 }
908 }
909
910 #[must_use]
924 pub fn with_all_str<S, I>(self, iter: I) -> Self
925 where
926 S: Into<String>,
927 I: IntoIterator<Item = S>,
928 {
929 self.with(|s| s.add_all_str(iter))
930 }
931}
932
933impl<T: 'static> MultipleChoiceView<T>
934where
935 T: Ord,
936{
937 pub fn sort(&mut self) {
944 self.items.sort_by(|a, b| a.value.cmp(&b.value));
945 }
946}
947
948impl<T: 'static + Send + Sync> View for MultipleChoiceView<T> {
949 fn draw(&self, printer: &Printer) {
950 let focus = self.focus();
951
952 let h = self.items.len();
953 let offset = self.align.v.get_offset(h, printer.size.y);
954 let printer = &printer.offset((0, offset));
955
956 let enabled = self.enabled && printer.enabled;
957 let active = printer.focused;
958
959 let regular_style: StyleType = if enabled {
960 Style::inherit_parent().into()
961 } else {
962 PaletteStyle::Secondary.into()
963 };
964
965 let highlight_style = if active {
966 PaletteStyle::Highlight.into()
967 } else if self.inactive_highlight {
968 PaletteStyle::HighlightInactive.into()
969 } else {
970 regular_style
971 };
972
973 for i in 0..self.len() {
974 let style = if i == focus {
975 highlight_style
976 } else {
977 regular_style
978 };
979
980 printer.offset((0, i)).with_style(style, |printer| {
981 self.draw_item(printer, i);
982 });
983 }
984 }
985
986 fn required_size(&mut self, _: Vec2) -> Vec2 {
987 if let Some(s) = self.last_required_size {
988 return s;
989 }
990 let w = self
994 .items
995 .iter()
996 .map(|item| item.label.width())
997 .max()
998 .unwrap_or(1);
999 let size = {
1000 let h = self.items.len();
1001
1002 Vec2::new(w, h)
1003 };
1004 self.last_required_size = Some(size);
1005 size
1006 }
1007
1008 fn on_event(&mut self, event: Event) -> EventResult {
1009 if !self.enabled {
1010 return EventResult::Ignored;
1011 }
1012
1013 self.on_event_regular(event)
1014 }
1015
1016 fn take_focus(&mut self, source: direction::Direction) -> Result<EventResult, CannotFocus> {
1017 (self.enabled && !self.items.is_empty())
1018 .then(|| {
1019 match source {
1020 direction::Direction::Abs(direction::Absolute::Up) => {
1021 self.set_focus(0);
1022 }
1023 direction::Direction::Abs(direction::Absolute::Down) => {
1024 self.set_focus(self.items.len().saturating_sub(1));
1025 }
1026 _ => (),
1027 }
1028 EventResult::Consumed(None)
1029 })
1030 .ok_or(CannotFocus)
1031 }
1032
1033 fn layout(&mut self, size: Vec2) {
1034 self.last_size = size;
1035 }
1036
1037 fn important_area(&self, size: Vec2) -> Rect {
1038 self.selected_id()
1039 .map(|i| Rect::from_size((0, i), (size.x, 1)))
1040 .unwrap_or_else(|| Rect::from_size(Vec2::zero(), size))
1041 }
1042}
1043
1044struct Item<T> {
1046 label: StyledString,
1047 value: Arc<T>,
1048 chosen: bool,
1049}
1050
1051impl<T> Item<T> {
1052 fn new(label: StyledString, value: T, chosen: bool) -> Self {
1053 let value = Arc::new(value);
1054 Item {
1055 label,
1056 value,
1057 chosen,
1058 }
1059 }
1060
1061 fn toggle_choice(&mut self) {
1062 if self.chosen {
1063 self.chosen = false;
1064 } else {
1065 self.chosen = true;
1066 }
1067 }
1068}
1069
1070#[cursive_core::blueprint(MultipleChoiceView::<String>::new())]
1071struct Blueprint {
1072 autojump: Option<bool>,
1073
1074 on_select: Option<_>,
1075
1076 #[blueprint(foreach = add_item_str)]
1077 items: Vec<String>,
1078}
1079
1080#[cfg(test)]
1081mod tests {
1082 use super::*;
1083
1084 #[test]
1085 fn select_view_sorting() {
1086 let mut view = MultipleChoiceView::new();
1088 view.add_item_str("Y");
1089 view.add_item_str("Z");
1090 view.add_item_str("X");
1091
1092 view.sort_by_label();
1094
1095 assert_eq!(view.selection(), Some(Arc::new(String::from("X"))));
1098 view.on_event(Event::Key(Key::Down));
1099 assert_eq!(view.selection(), Some(Arc::new(String::from("Y"))));
1100 view.on_event(Event::Key(Key::Down));
1101 assert_eq!(view.selection(), Some(Arc::new(String::from("Z"))));
1102 view.on_event(Event::Key(Key::Down));
1103 assert_eq!(view.selection(), Some(Arc::new(String::from("Z"))));
1104 }
1105
1106 #[test]
1107 fn select_view_sorting_with_comparator() {
1108 let mut view = MultipleChoiceView::new();
1110 view.add_item("Y", 2);
1111 view.add_item("Z", 1);
1112 view.add_item("X", 3);
1113
1114 view.sort_by(|a, b| a.cmp(b));
1116
1117 assert_eq!(view.selection(), Some(Arc::new(1)));
1120 view.on_event(Event::Key(Key::Down));
1121 assert_eq!(view.selection(), Some(Arc::new(2)));
1122 view.on_event(Event::Key(Key::Down));
1123 assert_eq!(view.selection(), Some(Arc::new(3)));
1124 view.on_event(Event::Key(Key::Down));
1125 assert_eq!(view.selection(), Some(Arc::new(3)));
1126 }
1127
1128 #[test]
1129 fn select_view_sorting_by_key() {
1130 #[derive(Eq, PartialEq, Debug)]
1132 struct MyStruct {
1133 key: i32,
1134 }
1135
1136 let mut view = MultipleChoiceView::new();
1137 view.add_item("Y", MyStruct { key: 2 });
1138 view.add_item("Z", MyStruct { key: 1 });
1139 view.add_item("X", MyStruct { key: 3 });
1140
1141 view.sort_by_key(|s| s.key);
1143
1144 assert_eq!(view.selection(), Some(Arc::new(MyStruct { key: 1 })));
1147 view.on_event(Event::Key(Key::Down));
1148 assert_eq!(view.selection(), Some(Arc::new(MyStruct { key: 2 })));
1149 view.on_event(Event::Key(Key::Down));
1150 assert_eq!(view.selection(), Some(Arc::new(MyStruct { key: 3 })));
1151 view.on_event(Event::Key(Key::Down));
1152 assert_eq!(view.selection(), Some(Arc::new(MyStruct { key: 3 })));
1153 }
1154
1155 #[test]
1156 fn select_view_sorting_orderable_items() {
1157 let mut view = MultipleChoiceView::new();
1159 view.add_item("Y", 2);
1160 view.add_item("Z", 1);
1161 view.add_item("X", 3);
1162
1163 view.sort();
1165
1166 assert_eq!(view.selection(), Some(Arc::new(1)));
1169 view.on_event(Event::Key(Key::Down));
1170 assert_eq!(view.selection(), Some(Arc::new(2)));
1171 view.on_event(Event::Key(Key::Down));
1172 assert_eq!(view.selection(), Some(Arc::new(3)));
1173 view.on_event(Event::Key(Key::Down));
1174 assert_eq!(view.selection(), Some(Arc::new(3)));
1175 }
1176}
1177