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