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		self.last_required_size = None;
103	}
104
105	/// Sets the visual indicators for chosen and not chosen items.
106	/// 
107	/// The first indicator represents the not chosen variant.
108	/// 
109	/// Chainable variant.
110	/// 
111	/// # Examples
112	///
113	/// ```
114	/// use cursive_core::traits::Nameable;
115	/// use cursive_multiple_choice_view::MultipleChoiceView;
116	///
117	/// let multiple_choice_view: MultipleChoiceView<String> = MultipleChoiceView::new()
118	///     .with_choice_indicators(["☐", "☒"]);
119	/// ```
120	#[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	/// Sets the "auto-jump" property for this view.
126	///
127	/// If enabled, when a key is pressed, the selection will jump to the next
128	/// item beginning with the pressed letter.
129	pub fn set_autojump(&mut self, autojump: bool) {
130		self.autojump = autojump;
131	}
132
133	/// Sets the "auto-jump" property for this view.
134	///
135	/// If enabled, when a key is pressed, the selection will jump to the next
136	/// item beginning with the pressed letter.
137	///
138	/// Chainable variant.
139	#[must_use]
140	pub fn autojump(self) -> Self {
141		self.with(|s| s.set_autojump(true))
142	}
143
144	/// Sets the "inactive highlight" property for this view.
145	///
146	/// * If true (the default), the selected row will be highlighted when the
147	///   view is not focused.
148	/// * If false, the selected row will be printed like the others if inactive.
149	pub fn set_inactive_highlight(&mut self, inactive_highlight: bool) {
150		self.inactive_highlight = inactive_highlight;
151	}
152
153	/// Sets the "inactive highlight" property for this view.
154	///
155	/// * If true (the default), the selected row will be highlighted when the
156	///   view is not focused.
157	/// * If false, the selected row will be printed like the others if inactive.
158	///
159	/// Chainable variant.
160	pub fn with_inactive_highlight(self, inactive_highlight: bool) -> Self {
161		self.with(|s| s.set_inactive_highlight(inactive_highlight))
162	}
163
164	/// Returns the current status of the "inactive highlight" property.
165	pub fn get_inactive_highlight(&self) -> bool {
166		self.inactive_highlight
167	}
168
169	/// Sets a callback to be used when an item is selected.
170	#[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	/// Sets a callback to be used when an item is selected.
179	///
180	/// Chainable variant.
181	///
182	/// # Examples
183	///
184	/// ```
185	/// use cursive_core::traits::Nameable;
186	/// use cursive_core::views::TextView;
187	/// use cursive_multiple_choice_view::MultipleChoiceView;
188	///
189	/// let text_view = TextView::new("").with_name("text");
190	///
191	/// let multiple_choice_view = MultipleChoiceView::new()
192	///     .item("One", 1)
193	///     .item("Two", 2)
194	///     .on_select(|s, item| {
195	///         let content = match *item {
196	///             1 => "Content number one",
197	///             2 => "Content number two! Much better!",
198	///             _ => unreachable!("no such item"),
199	///         };
200	///
201	///         // Update the textview with the currently selected item.
202	///         s.call_on_name("text", |v: &mut TextView| {
203	///             v.set_content(content);
204	///         })
205	///         .unwrap();
206	///     });
207	/// ```
208	#[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	/// Sets a callback to be used when `<Enter>` is pressed.
217	///
218	/// Also happens if the user clicks an item.
219	///
220	/// The item currently selected will be given to the callback.
221	///
222	/// Here, `V` can be `T` itself, or a type that can be borrowed from `T`.
223	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	/// Sets a callback to be used when `<Enter>` is pressed.
234	///
235	/// Also happens if the user clicks an item.
236	///
237	/// The item currently selected will be given to the callback.
238	///
239	/// Chainable variant.
240	///
241	/// # Examples
242	///
243	/// ```
244	/// use cursive_core::views::Dialog;
245	/// use cursive_multiple_choice_view::MultipleChoiceView;
246	///
247	/// let multiple_choice_view = MultipleChoiceView::new()
248	///     .item("One", 1)
249	///     .item("Two", 2)
250	///     .on_submit(|s, item| {
251	///         let content = match *item {
252	///             1 => "Content number one",
253	///             2 => "Content number two! Much better!",
254	///             _ => unreachable!("no such item"),
255	///         };
256	///
257	///         // Show a popup whenever the user presses <Enter>.
258	///         s.add_layer(Dialog::info(content));
259	///     });
260	/// ```
261	#[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	/// Sets the alignment for this view.
271	///
272	/// # Examples
273	///
274	/// ```
275	/// use cursive_core::align;
276	/// use cursive_multiple_choice_view::MultipleChoiceView;
277	///
278	/// let multiple_choice_view = MultipleChoiceView::new()
279	///     .item("One", 1)
280	///     .align(align::Align::top_center());
281	/// ```
282	#[must_use]
283	pub fn align(mut self, align: Align) -> Self {
284		self.align = align;
285
286		self
287	}
288
289	/// Sets the vertical alignment for this view.
290	/// (If the view is given too much space vertically.)
291	#[must_use]
292	pub fn v_align(mut self, v: VAlign) -> Self {
293		self.align.v = v;
294
295		self
296	}
297
298	/// Sets the horizontal alignment for this view.
299	#[must_use]
300	pub fn h_align(mut self, h: HAlign) -> Self {
301		self.align.h = h;
302
303		self
304	}
305
306	/// Returns the value of the currently selected item.
307	///
308	/// Returns `None` if the list is empty.
309	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	/// Removes all items from this view.
319	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	/// Adds a item to the list, with given label and value.
326	///
327	/// # Examples
328	///
329	/// ```
330	/// use cursive_multiple_choice_view::MultipleChoiceView;
331	///
332	/// let mut multiple_choice_view = MultipleChoiceView::new();
333	///
334	/// multiple_choice_view.add_item("Item 1", 1);
335	/// multiple_choice_view.add_item("Item 2", 2);
336	/// ```
337	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	/// Gets an item at given idx or None.
342	///
343	/// ```
344	/// use cursive_core::views::TextView;
345	/// use cursive_multiple_choice_view::MultipleChoiceView;
346	/// use cursive_core::Cursive;
347	/// let select = MultipleChoiceView::new().item("Short", 1);
348	/// assert_eq!(select.get_item(0), Some(("Short", &1)));
349	/// ```
350	pub fn get_item(&self, i: usize) -> Option<(&str, &T)> {
351		self.iter().nth(i)
352	}
353
354	/// Gets a mut item at given idx or None.
355	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	/// Iterate mutably on the items in this view.
371	///
372	/// Returns an iterator with each item and their labels.
373	///
374	/// In some cases some items will need to be cloned (for example if a
375	/// `Arc<T>` is still alive after calling `MultipleChoiceView::selection()`).
376	///
377	/// If `T` does not implement `Clone`, check `MultipleChoiceView::try_iter_mut()`.
378	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	/// Try to iterate mutably on the items in this view.
389	///
390	/// Returns an iterator with each item and their labels.
391	///
392	/// Some items may not be returned mutably, for example if a `Arc<T>` is
393	/// still alive after calling `MultipleChoiceView::selection()`.
394	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	/// Iterate on the items in this view.
402	///
403	/// Returns an iterator with each item and their labels.
404	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	/// Adds a item to the list, with given label, value and whether it should
411	/// be marked as chosen.
412	///
413	/// # Examples
414	///
415	/// ```
416	/// use cursive_multiple_choice_view::MultipleChoiceView;
417	///
418	/// let mut multiple_choice_view = MultipleChoiceView::new();
419	///
420	/// multiple_choice_view.add_item_with_choice("Item 1", 1, true);
421	/// multiple_choice_view.add_item_with_choice("Item 2", 2, false);
422	/// ```
423	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	/// Removes an item from the list.
429	///
430	/// Returns a callback in response to the selection change.
431	///
432	/// You should run this callback with a `&mut Cursive`.
433	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	/// Inserts an item at position `index`, shifting all elements after it to
447	/// the right.
448	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		// Do not increase focus if we were empty with focus=0.
462		if focus >= index && !self.items.is_empty() {
463			self.set_focus(focus + 1);
464		}
465		self.last_required_size = None;
466	}
467
468	/// Chainable variant of add_item
469	///
470	/// # Examples
471	///
472	/// ```
473	/// use cursive_multiple_choice_view::MultipleChoiceView;
474	///
475	/// let multiple_choice_view = MultipleChoiceView::new()
476	///     .item("Item 1", 1)
477	///     .item("Item 2", 2)
478	///     .item("Surprise item", 42);
479	/// ```
480	#[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	/// Adds all items from from an iterator.
491	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	/// Adds all items from from an iterator.
502	///
503	/// Chainable variant.
504	///
505	/// # Examples
506	///
507	/// ```
508	/// use cursive_multiple_choice_view::MultipleChoiceView;
509	///
510	/// // Create a MultipleChoiceView with 100 items
511	/// let multiple_choice_view =
512	///     MultipleChoiceView::new().with_all((1u8..100).into_iter().map(|i| (format!("Item {}", i), i)));
513	/// ```
514	#[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	/// Returns the id of the item currently selected.
546	///
547	/// Returns `None` if the list is empty.
548	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	/// Returns the number of items in this list.
557	///
558	/// # Examples
559	///
560	/// ```
561	/// use cursive_multiple_choice_view::MultipleChoiceView;
562	///
563	/// let multiple_choice_view = MultipleChoiceView::new()
564	///     .item("Item 1", 1)
565	///     .item("Item 2", 2)
566	///     .item("Item 3", 3);
567	///
568	/// assert_eq!(multiple_choice_view.len(), 3);
569	/// ```
570	pub fn len(&self) -> usize {
571		self.items.len()
572	}
573
574	/// Returns `true` if this list has no item.
575	///
576	/// # Examples
577	///
578	/// ```
579	/// use cursive_multiple_choice_view::MultipleChoiceView;
580	///
581	/// let mut multiple_choice_view = MultipleChoiceView::new();
582	/// assert!(multiple_choice_view.is_empty());
583	///
584	/// multiple_choice_view.add_item("Item 1", 1);
585	/// multiple_choice_view.add_item("Item 2", 2);
586	/// assert!(!multiple_choice_view.is_empty());
587	///
588	/// multiple_choice_view.clear();
589	/// assert!(multiple_choice_view.is_empty());
590	/// ```
591	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	/// Sort the current items lexicographically by their label.
605	///
606	/// Note that this does not change the current focus index, which means that the current
607	/// selection will likely be changed by the sorting.
608	///
609	/// This sort is stable: items with identical label will not be reordered.
610	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	/// Sort the current items with the given comparator function.
616	///
617	/// Note that this does not change the current focus index, which means that the current
618	/// selection will likely be changed by the sorting.
619	///
620	/// The given comparator function must define a total order for the items.
621	///
622	/// If the comparator function does not define a total order, then the order after the sort is
623	/// unspecified.
624	///
625	/// This sort is stable: equal items will not be reordered.
626	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	/// Sort the current items with the given key extraction function.
634	///
635	/// Note that this does not change the current focus index, which means that the current
636	/// selection will likely be changed by the sorting.
637	///
638	/// This sort is stable: items with equal keys will not be reordered.
639	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	/// Moves the selection to the given position.
648	///
649	/// Returns a callback in response to the selection change.
650	///
651	/// You should run this callback with a `&mut Cursive`.
652	pub fn set_selection(&mut self, i: usize) -> Callback {
653		// TODO: Check if `i >= self.len()` ?
654		// assert!(i < self.len(), "MultipleChoiceView: trying to select out-of-bound");
655		// Or just cap the ID?
656		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	/// Sets the selection to the given position.
667	///
668	/// Chainable variant.
669	///
670	/// Does not apply `on_select` callbacks.
671	#[must_use]
672	pub fn selected(self, i: usize) -> Self {
673		self.with(|s| {
674			s.set_selection(i);
675		})
676	}
677
678	/// Get all chosen items
679	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	/// Moves the selection up by the given number of rows.
690	///
691	/// Returns a callback in response to the selection change.
692	///
693	/// You should run this callback with a `&mut Cursive`:
694	///
695	/// ```rust
696	/// # use cursive_core::Cursive;
697	/// # use cursive_multiple_choice_view::MultipleChoiceView;
698	/// fn select_up(siv: &mut Cursive, view: &mut MultipleChoiceView<()>) {
699	///     let cb = view.select_up(1);
700	///     cb(siv);
701	/// }
702	/// ```
703	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	/// Moves the selection down by the given number of rows.
709	///
710	/// Returns a callback in response to the selection change.
711	///
712	/// You should run this callback with a `&mut Cursive`.
713	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		// We return a Callback Arc<|s| cb(s, &*v)>
731		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			// * Starting from the current focus, find the first item that
746			//   match the char.
747			// * Cycle back to the beginning of the list when we reach the end.
748			// * This is achieved by chaining twice the iterator.
749			let iter = self.iter().chain(self.iter());
750
751			// We'll do a lowercase check.
752			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		// Apply modulo in case we have a hit from the chained iterator
768		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	/// Returns a callback from selection change.
821	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	/// Convenient method to use the label as value.
831	pub fn add_item_str<S: Into<String>>(&mut self, label: S) {
832		self.add_item_str_with_choice(label, false);
833	}
834
835	/// Convenient method to use the label as value.
836	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	/// Convenient method to use the label unstyled text as value.
842	pub fn add_item_styled<S: Into<StyledString>>(&mut self, label: S) {
843		let label = label.into();
844
845		// Accumulate the content of each span.
846		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	/// Chainable variant of `add_item_str`.
855	///
856	/// # Examples
857	///
858	/// ```
859	/// use cursive_multiple_choice_view::MultipleChoiceView;
860	///
861	/// let multiple_choice_view = MultipleChoiceView::new()
862	///     .item_str("Paris")
863	///     .item_str("New York")
864	///     .item_str("Tokyo");
865	/// ```
866	#[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	/// Chainable variant of `add_item_str_with_choice`.
872	#[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	/// Chainable variant of `add_item_styled`.
878	#[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	/// Convenient method to use the label as value.
884	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	/// Adds all strings from an iterator.
893	///
894	/// # Examples
895	///
896	/// ```
897	/// # use cursive_multiple_choice_view::MultipleChoiceView;
898	/// let mut multiple_choice_view = MultipleChoiceView::new();
899	/// multiple_choice_view.add_all_str(vec!["a", "b", "c"]);
900	/// ```
901	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	/// Adds all strings from an iterator.
912	///
913	/// Chainable variant.
914	///
915	/// # Examples
916	///
917	/// ```
918	/// use cursive_multiple_choice_view::MultipleChoiceView;
919	///
920	/// let text = "..."; // Maybe read some config file
921	///
922	/// let multiple_choice_view = MultipleChoiceView::new().with_all_str(text.lines());
923	/// ```
924	#[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	/// Sort the current items by their natural ordering.
939	///
940	/// Note that this does not change the current focus index, which means that the current
941	/// selection will likely be changed by the sorting.
942	///
943	/// This sort is stable: items that are equal will not be reordered.
944	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		// Items here are not compressible.
992		// So no matter what the horizontal requirements are,
993		// we'll still return our longest item.
994		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
1046// We wrap each value in a `Arc` and add a label
1047struct 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		// We add items in no particular order, from going by their label.
1089		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		// Then sorting the list...
1095		view.sort_by_label();
1096
1097		// ... should observe the items in sorted order.
1098		// And focus is NOT changed by the sorting, so the first item is "X".
1099		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		// We add items in no particular order, from going by their value.
1111		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		// Then sorting the list...
1117		view.sort_by(|a, b| a.cmp(b));
1118
1119		// ... should observe the items in sorted order.
1120		// And focus is NOT changed by the sorting, so the first item is "X".
1121		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		// We add items in no particular order, from going by their key value.
1133		#[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		// Then sorting the list...
1144		view.sort_by_key(|s| s.key);
1145
1146		// ... should observe the items in sorted order.
1147		// And focus is NOT changed by the sorting, so the first item is "X".
1148		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		// We add items in no particular order, from going by their value.
1160		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		// Then sorting the list...
1166		view.sort();
1167
1168		// ... should observe the items in sorted order.
1169		// And focus is NOT changed by the sorting, so the first item is "X".
1170		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