cursive_multiple_choice_view/
lib.rs

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
18/// View to make a selection from multiple items.
19///
20/// It contains a list of values of type T, with associated labels.
21///
22pub struct MultipleChoiceView<T = String> {
23	// The core of the view: we store a list of items
24	// `Item` is more or less a `(String, Arc<T>)`.
25	items: Vec<Item<T>>,
26
27	// When disabled, we cannot change selection.
28	enabled: bool,
29
30	// Callbacks may need to manipulate focus, so give it some mutability.
31	focus: Arc<AtomicUsize>,
32
33	// If true, highlight the selection even when inactive (not focused).
34	// If false, selection will be drawn like regular text if inactive.
35	inactive_highlight: bool,
36
37	// This is a custom callback to include a &T.
38	// It will be called whenever "Enter" is pressed or when an item is clicked.
39	on_submit: Option<Arc<SelectCallback<T>>>,
40
41	// This callback is called when the selection is changed.
42	// TODO: add the previous selection? Indices?
43	on_select: Option<Arc<SelectCallback<T>>>,
44
45	// If `true`, when a character is pressed, jump to the next item starting
46	// with this character.
47	autojump: bool,
48
49	align: Align,
50
51	// Indicators showing whether the item is chosen or not.
52	choice_indicators: [&'static str; 2],
53
54	last_size: Vec2,
55
56	// Cache of required_size. Set to None when it needs to be recomputed.
57	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	/// Creates a new empty MultipleChoiceView.
70	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	/// Sets the visual indicators for chosen and not chosen items.
87	/// 
88	/// The first indicator represents the not chosen variant.
89    /// 
90    /// # Examples
91	///
92	/// ```
93	/// use cursive_core::traits::Nameable;
94	/// use cursive_multiple_choice_view::MultipleChoiceView;
95	///
96	/// let mut multiple_choice_view: MultipleChoiceView<String> = MultipleChoiceView::new();
97	/// 
98	/// multiple_choice_view.set_choice_indicators(["☐", "☒"]);
99	/// ```
100	pub fn set_choice_indicators(&mut self, selection_indicators: [&'static str; 2]) {
101		self.choice_indicators = selection_indicators;
102	}
103
104	/// Sets the visual indicators for chosen and not chosen items.
105	/// 
106	/// The first indicator represents the not chosen variant.
107	/// 
108	/// Chainable variant.
109	/// 
110	/// # Examples
111	///
112	/// ```
113	/// use cursive_core::traits::Nameable;
114	/// use cursive_multiple_choice_view::MultipleChoiceView;
115	///
116	/// let multiple_choice_view: MultipleChoiceView<String> = MultipleChoiceView::new()
117	///     .with_choice_indicators(["☐", "☒"]);
118	/// ```
119	#[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	/// Sets the "auto-jump" property for this view.
125	///
126	/// If enabled, when a key is pressed, the selection will jump to the next
127	/// item beginning with the pressed letter.
128	pub fn set_autojump(&mut self, autojump: bool) {
129		self.autojump = autojump;
130	}
131
132	/// Sets the "auto-jump" property for this view.
133	///
134	/// If enabled, when a key is pressed, the selection will jump to the next
135	/// item beginning with the pressed letter.
136	///
137	/// Chainable variant.
138	#[must_use]
139	pub fn autojump(self) -> Self {
140		self.with(|s| s.set_autojump(true))
141	}
142
143	/// Sets the "inactive highlight" property for this view.
144	///
145	/// * If true (the default), the selected row will be highlighted when the
146	///   view is not focused.
147	/// * If false, the selected row will be printed like the others if inactive.
148	pub fn set_inactive_highlight(&mut self, inactive_highlight: bool) {
149		self.inactive_highlight = inactive_highlight;
150	}
151
152	/// Sets the "inactive highlight" property for this view.
153	///
154	/// * If true (the default), the selected row will be highlighted when the
155	///   view is not focused.
156	/// * If false, the selected row will be printed like the others if inactive.
157	///
158	/// Chainable variant.
159	pub fn with_inactive_highlight(self, inactive_highlight: bool) -> Self {
160		self.with(|s| s.set_inactive_highlight(inactive_highlight))
161	}
162
163	/// Returns the current status of the "inactive highlight" property.
164	pub fn get_inactive_highlight(&self) -> bool {
165		self.inactive_highlight
166	}
167
168	/// Sets a callback to be used when an item is selected.
169	#[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	/// Sets a callback to be used when an item is selected.
178	///
179	/// Chainable variant.
180	///
181	/// # Examples
182	///
183	/// ```
184	/// use cursive_core::traits::Nameable;
185	/// use cursive_core::views::TextView;
186	/// use cursive_multiple_choice_view::MultipleChoiceView;
187	///
188	/// let text_view = TextView::new("").with_name("text");
189	///
190	/// let multiple_choice_view = MultipleChoiceView::new()
191	///     .item("One", 1)
192	///     .item("Two", 2)
193	///     .on_select(|s, item| {
194	///         let content = match *item {
195	///             1 => "Content number one",
196	///             2 => "Content number two! Much better!",
197	///             _ => unreachable!("no such item"),
198	///         };
199	///
200	///         // Update the textview with the currently selected item.
201	///         s.call_on_name("text", |v: &mut TextView| {
202	///             v.set_content(content);
203	///         })
204	///         .unwrap();
205	///     });
206	/// ```
207	#[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	/// Sets a callback to be used when `<Enter>` is pressed.
216	///
217	/// Also happens if the user clicks an item.
218	///
219	/// The item currently selected will be given to the callback.
220	///
221	/// Here, `V` can be `T` itself, or a type that can be borrowed from `T`.
222	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	/// Sets a callback to be used when `<Enter>` is pressed.
233	///
234	/// Also happens if the user clicks an item.
235	///
236	/// The item currently selected will be given to the callback.
237	///
238	/// Chainable variant.
239	///
240	/// # Examples
241	///
242	/// ```
243	/// use cursive_core::views::Dialog;
244	/// use cursive_multiple_choice_view::MultipleChoiceView;
245	///
246	/// let multiple_choice_view = MultipleChoiceView::new()
247	///     .item("One", 1)
248	///     .item("Two", 2)
249	///     .on_submit(|s, item| {
250	///         let content = match *item {
251	///             1 => "Content number one",
252	///             2 => "Content number two! Much better!",
253	///             _ => unreachable!("no such item"),
254	///         };
255	///
256	///         // Show a popup whenever the user presses <Enter>.
257	///         s.add_layer(Dialog::info(content));
258	///     });
259	/// ```
260	#[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	/// Sets the alignment for this view.
270	///
271	/// # Examples
272	///
273	/// ```
274	/// use cursive_core::align;
275	/// use cursive_multiple_choice_view::MultipleChoiceView;
276	///
277	/// let multiple_choice_view = MultipleChoiceView::new()
278	///     .item("One", 1)
279	///     .align(align::Align::top_center());
280	/// ```
281	#[must_use]
282	pub fn align(mut self, align: Align) -> Self {
283		self.align = align;
284
285		self
286	}
287
288	/// Sets the vertical alignment for this view.
289	/// (If the view is given too much space vertically.)
290	#[must_use]
291	pub fn v_align(mut self, v: VAlign) -> Self {
292		self.align.v = v;
293
294		self
295	}
296
297	/// Sets the horizontal alignment for this view.
298	#[must_use]
299	pub fn h_align(mut self, h: HAlign) -> Self {
300		self.align.h = h;
301
302		self
303	}
304
305	/// Returns the value of the currently selected item.
306	///
307	/// Returns `None` if the list is empty.
308	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	/// Removes all items from this view.
318	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	/// Adds a item to the list, with given label and value.
325	///
326	/// # Examples
327	///
328	/// ```
329	/// use cursive_multiple_choice_view::MultipleChoiceView;
330	///
331	/// let mut multiple_choice_view = MultipleChoiceView::new();
332	///
333	/// multiple_choice_view.add_item("Item 1", 1);
334	/// multiple_choice_view.add_item("Item 2", 2);
335	/// ```
336	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	/// Gets an item at given idx or None.
341	///
342	/// ```
343	/// use cursive_core::views::TextView;
344	/// use cursive_multiple_choice_view::MultipleChoiceView;
345	/// use cursive_core::Cursive;
346	/// let select = MultipleChoiceView::new().item("Short", 1);
347	/// assert_eq!(select.get_item(0), Some(("Short", &1)));
348	/// ```
349	pub fn get_item(&self, i: usize) -> Option<(&str, &T)> {
350		self.iter().nth(i)
351	}
352
353	/// Gets a mut item at given idx or None.
354	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	/// Iterate mutably on the items in this view.
370	///
371	/// Returns an iterator with each item and their labels.
372	///
373	/// In some cases some items will need to be cloned (for example if a
374	/// `Arc<T>` is still alive after calling `MultipleChoiceView::selection()`).
375	///
376	/// If `T` does not implement `Clone`, check `MultipleChoiceView::try_iter_mut()`.
377	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	/// Try to iterate mutably on the items in this view.
388	///
389	/// Returns an iterator with each item and their labels.
390	///
391	/// Some items may not be returned mutably, for example if a `Arc<T>` is
392	/// still alive after calling `MultipleChoiceView::selection()`.
393	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	/// Iterate on the items in this view.
401	///
402	/// Returns an iterator with each item and their labels.
403	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	/// Adds a item to the list, with given label, value and whether it should
410	/// be marked as chosen.
411	///
412	/// # Examples
413	///
414	/// ```
415	/// use cursive_multiple_choice_view::MultipleChoiceView;
416	///
417	/// let mut multiple_choice_view = MultipleChoiceView::new();
418	///
419	/// multiple_choice_view.add_item_with_choice("Item 1", 1, true);
420	/// multiple_choice_view.add_item_with_choice("Item 2", 2, false);
421	/// ```
422	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	/// Removes an item from the list.
428	///
429	/// Returns a callback in response to the selection change.
430	///
431	/// You should run this callback with a `&mut Cursive`.
432	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	/// Inserts an item at position `index`, shifting all elements after it to
446	/// the right.
447	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		// Do not increase focus if we were empty with focus=0.
461		if focus >= index && !self.items.is_empty() {
462			self.set_focus(focus + 1);
463		}
464		self.last_required_size = None;
465	}
466
467	/// Chainable variant of add_item
468	///
469	/// # Examples
470	///
471	/// ```
472	/// use cursive_multiple_choice_view::MultipleChoiceView;
473	///
474	/// let multiple_choice_view = MultipleChoiceView::new()
475	///     .item("Item 1", 1)
476	///     .item("Item 2", 2)
477	///     .item("Surprise item", 42);
478	/// ```
479	#[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	/// Adds all items from from an iterator.
490	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	/// Adds all items from from an iterator.
501	///
502	/// Chainable variant.
503	///
504	/// # Examples
505	///
506	/// ```
507	/// use cursive_multiple_choice_view::MultipleChoiceView;
508	///
509	/// // Create a MultipleChoiceView with 100 items
510	/// let multiple_choice_view =
511	///     MultipleChoiceView::new().with_all((1u8..100).into_iter().map(|i| (format!("Item {}", i), i)));
512	/// ```
513	#[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	/// Returns the id of the item currently selected.
545	///
546	/// Returns `None` if the list is empty.
547	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	/// Returns the number of items in this list.
556	///
557	/// # Examples
558	///
559	/// ```
560	/// use cursive_multiple_choice_view::MultipleChoiceView;
561	///
562	/// let multiple_choice_view = MultipleChoiceView::new()
563	///     .item("Item 1", 1)
564	///     .item("Item 2", 2)
565	///     .item("Item 3", 3);
566	///
567	/// assert_eq!(multiple_choice_view.len(), 3);
568	/// ```
569	pub fn len(&self) -> usize {
570		self.items.len()
571	}
572
573	/// Returns `true` if this list has no item.
574	///
575	/// # Examples
576	///
577	/// ```
578	/// use cursive_multiple_choice_view::MultipleChoiceView;
579	///
580	/// let mut multiple_choice_view = MultipleChoiceView::new();
581	/// assert!(multiple_choice_view.is_empty());
582	///
583	/// multiple_choice_view.add_item("Item 1", 1);
584	/// multiple_choice_view.add_item("Item 2", 2);
585	/// assert!(!multiple_choice_view.is_empty());
586	///
587	/// multiple_choice_view.clear();
588	/// assert!(multiple_choice_view.is_empty());
589	/// ```
590	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	/// Sort the current items lexicographically by their label.
604	///
605	/// Note that this does not change the current focus index, which means that the current
606	/// selection will likely be changed by the sorting.
607	///
608	/// This sort is stable: items with identical label will not be reordered.
609	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	/// Sort the current items with the given comparator function.
615	///
616	/// Note that this does not change the current focus index, which means that the current
617	/// selection will likely be changed by the sorting.
618	///
619	/// The given comparator function must define a total order for the items.
620	///
621	/// If the comparator function does not define a total order, then the order after the sort is
622	/// unspecified.
623	///
624	/// This sort is stable: equal items will not be reordered.
625	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	/// Sort the current items with the given key extraction function.
633	///
634	/// Note that this does not change the current focus index, which means that the current
635	/// selection will likely be changed by the sorting.
636	///
637	/// This sort is stable: items with equal keys will not be reordered.
638	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	/// Moves the selection to the given position.
647	///
648	/// Returns a callback in response to the selection change.
649	///
650	/// You should run this callback with a `&mut Cursive`.
651	pub fn set_selection(&mut self, i: usize) -> Callback {
652		// TODO: Check if `i >= self.len()` ?
653		// assert!(i < self.len(), "MultipleChoiceView: trying to select out-of-bound");
654		// Or just cap the ID?
655		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	/// Sets the selection to the given position.
666	///
667	/// Chainable variant.
668	///
669	/// Does not apply `on_select` callbacks.
670	#[must_use]
671	pub fn selected(self, i: usize) -> Self {
672		self.with(|s| {
673			s.set_selection(i);
674		})
675	}
676
677	/// Get all chosen items
678	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	/// Moves the selection up by the given number of rows.
689	///
690	/// Returns a callback in response to the selection change.
691	///
692	/// You should run this callback with a `&mut Cursive`:
693	///
694	/// ```rust
695	/// # use cursive_core::Cursive;
696	/// # use cursive_multiple_choice_view::MultipleChoiceView;
697	/// fn select_up(siv: &mut Cursive, view: &mut MultipleChoiceView<()>) {
698	///     let cb = view.select_up(1);
699	///     cb(siv);
700	/// }
701	/// ```
702	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	/// Moves the selection down by the given number of rows.
708	///
709	/// Returns a callback in response to the selection change.
710	///
711	/// You should run this callback with a `&mut Cursive`.
712	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		// We return a Callback Arc<|s| cb(s, &*v)>
730		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			// * Starting from the current focus, find the first item that
745			//   match the char.
746			// * Cycle back to the beginning of the list when we reach the end.
747			// * This is achieved by chaining twice the iterator.
748			let iter = self.iter().chain(self.iter());
749
750			// We'll do a lowercase check.
751			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		// Apply modulo in case we have a hit from the chained iterator
767		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	/// Returns a callback from selection change.
820	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	/// Convenient method to use the label as value.
830	pub fn add_item_str<S: Into<String>>(&mut self, label: S) {
831		self.add_item_str_with_choice(label, false);
832	}
833
834	/// Convenient method to use the label as value.
835	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	/// Convenient method to use the label unstyled text as value.
841	pub fn add_item_styled<S: Into<StyledString>>(&mut self, label: S) {
842		let label = label.into();
843
844		// Accumulate the content of each span.
845		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	/// Chainable variant of `add_item_str`.
854	///
855	/// # Examples
856	///
857	/// ```
858	/// use cursive_multiple_choice_view::MultipleChoiceView;
859	///
860	/// let multiple_choice_view = MultipleChoiceView::new()
861	///     .item_str("Paris")
862	///     .item_str("New York")
863	///     .item_str("Tokyo");
864	/// ```
865	#[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	/// Chainable variant of `add_item_str_with_choice`.
871	#[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	/// Chainable variant of `add_item_styled`.
877	#[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	/// Convenient method to use the label as value.
883	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	/// Adds all strings from an iterator.
892	///
893	/// # Examples
894	///
895	/// ```
896	/// # use cursive_multiple_choice_view::MultipleChoiceView;
897	/// let mut multiple_choice_view = MultipleChoiceView::new();
898	/// multiple_choice_view.add_all_str(vec!["a", "b", "c"]);
899	/// ```
900	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	/// Adds all strings from an iterator.
911	///
912	/// Chainable variant.
913	///
914	/// # Examples
915	///
916	/// ```
917	/// use cursive_multiple_choice_view::MultipleChoiceView;
918	///
919	/// let text = "..."; // Maybe read some config file
920	///
921	/// let multiple_choice_view = MultipleChoiceView::new().with_all_str(text.lines());
922	/// ```
923	#[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	/// Sort the current items by their natural ordering.
938	///
939	/// Note that this does not change the current focus index, which means that the current
940	/// selection will likely be changed by the sorting.
941	///
942	/// This sort is stable: items that are equal will not be reordered.
943	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		// Items here are not compressible.
991		// So no matter what the horizontal requirements are,
992		// we'll still return our longest item.
993		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
1044// We wrap each value in a `Arc` and add a label
1045struct 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		// We add items in no particular order, from going by their label.
1087		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		// Then sorting the list...
1093		view.sort_by_label();
1094
1095		// ... should observe the items in sorted order.
1096		// And focus is NOT changed by the sorting, so the first item is "X".
1097		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		// We add items in no particular order, from going by their value.
1109		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		// Then sorting the list...
1115		view.sort_by(|a, b| a.cmp(b));
1116
1117		// ... should observe the items in sorted order.
1118		// And focus is NOT changed by the sorting, so the first item is "X".
1119		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		// We add items in no particular order, from going by their key value.
1131		#[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		// Then sorting the list...
1142		view.sort_by_key(|s| s.key);
1143
1144		// ... should observe the items in sorted order.
1145		// And focus is NOT changed by the sorting, so the first item is "X".
1146		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		// We add items in no particular order, from going by their value.
1158		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		// Then sorting the list...
1164		view.sort();
1165
1166		// ... should observe the items in sorted order.
1167		// And focus is NOT changed by the sorting, so the first item is "X".
1168		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