1use std::{
2 cmp::Ordering,
3 fmt::Display,
4 sync::{
5 atomic::AtomicUsize,
6 Arc, RwLock,
7 atomic::Ordering as AtomicOrdering
8 }
9};
10use cursive_core::{
11 Cursive, impl_enabled, Printer,
12 Rect, Vec2, View, With,
13 direction::{Direction, Absolute},
14 view::{CannotFocus, Position},
15 event::{
16 Event, EventResult, Key,
17 MouseButton, MouseEvent
18 },
19 utils::markup::StyledString,
20 views::{
21 Dialog,
22 LayerPosition,
23 OnEventView,
24 }
25};
26use rust_utils::{chainable, encapsulated};
27use crate::{
28 c_focus, vlayout,
29 AdvancedButton
30};
31use super::ButtonContent;
32
33type SelectCallback<T> = Arc<dyn Fn(&mut Cursive, &T) + Send + Sync>;
34
35#[derive(Clone)]
39#[encapsulated]
40pub struct AdvancedSelectView<T = String>
41 where T: 'static
42{
43 items: Vec<StyledItem<T>>,
45
46 enabled: bool,
48
49 selected: Arc<AtomicUsize>,
51
52 #[setter(doc = "Allow jumping to items starting with the the pressed letter key")]
54 #[chainable]
55 autojump: bool,
56
57 popup: bool,
59
60 popup_ofs: Arc<RwLock<Vec2>>,
62
63 selected_cb: SelectCallback<T>,
65
66 submit_cb: SelectCallback<T>,
68
69 size_cache: Option<Vec2>
71}
72
73impl AdvancedSelectView {
74 #[chainable]
76 pub fn add_item_str<S: Display>(&mut self, label: S) {
77 self.add_item(label.to_string(), label.to_string());
78 }
79
80 pub fn insert_item_str<S: Display>(&mut self, index: usize, label: S) {
82 self.insert_item(index, label.to_string(), label.to_string());
83 }
84
85 pub fn add_all_str<S, I>(&mut self, iter: I)
87 where
88 S: Display,
89 I: IntoIterator<Item = S>
90 {
91 for label in iter {
92 self.add_item_str(label);
93 }
94 }
95
96 #[must_use]
100 pub fn with_all_str<S, I>(mut self, iter: I) -> Self
101 where
102 S: Display,
103 I: IntoIterator<Item = S>
104 {
105 self.add_all_str(iter);
106 self
107 }
108}
109
110impl<T: Ord + 'static> AdvancedSelectView<T> {
111 pub fn sort(&mut self) { self.items.sort_by(|a, b| a.item.cmp(&b.item)); }
113}
114
115impl<T: Send + Sync + 'static> AdvancedSelectView<T> {
116 impl_enabled!(self.enabled);
117
118 #[must_use]
120 pub fn new() -> AdvancedSelectView<T> {
121 AdvancedSelectView {
122 items: vec![],
123 enabled: true,
124 selected: Arc::new(AtomicUsize::new(0)),
125 autojump: false,
126 popup: false,
127 popup_ofs: Arc::new(RwLock::new((0, 0).into())),
128 selected_cb: Arc::new(|_, _| { }),
129 submit_cb: Arc::new(|_, _| { }),
130 size_cache: None
131 }
132 }
133
134 #[chainable]
136 pub fn set_popup(&mut self, popup: bool) {
137 self.popup = popup;
138 self.size_cache = None;
139 }
140
141 #[chainable]
143 pub fn set_on_select<F: Fn(&mut Cursive, &T) + Send + Sync + 'static>(&mut self, cb: F) { self.selected_cb = Arc::new(cb); }
144
145 #[chainable]
147 pub fn set_on_submit<F: Fn(&mut Cursive, &T) + Send + Sync + 'static>(&mut self, cb: F) { self.submit_cb = Arc::new(cb); }
148
149 pub fn selection(&self) -> Option<&T> {
153 Some(&self.items.get(self.selected_index())?.item)
154 }
155
156 pub fn selection_mut(&mut self) -> Option<&mut T> {
160 let index = self.selected_index();
161 Arc::get_mut(&mut self.items.get_mut(index)?.item)
162 }
163
164 pub fn selected_index(&self) -> usize { self.selected.load(AtomicOrdering::Relaxed) }
166
167 pub fn selected_label(&self) -> Option<&StyledString> {
169 Some(self.items.get(self.selected_index())?.label.get_content())
170 }
171
172 pub fn selected_label_mut(&mut self) -> Option<&mut StyledString> {
174 let index = self.selected_index();
175 Some(self.items.get_mut(index)?.label.get_content_mut())
176 }
177
178 pub fn set_selection(&mut self, index: usize) {
180 if index < self.items.len() { self.selected.store(index, AtomicOrdering::Relaxed); }
181 }
182
183 #[must_use]
187 pub fn selected(mut self, index: usize) -> Self {
188 self.set_selection(index);
189 self
190 }
191
192 pub fn move_down(&mut self) -> bool {
196 let index = self.selected_index();
197 if index < self.items.len().saturating_sub(1) {
198 let new_selected = index + 1;
199 self.set_selection(new_selected);
200 return true;
201 }
202 false
203 }
204
205 pub fn move_up(&mut self) -> bool {
209 let index = self.selected_index();
210 if index != 0 {
211 let new_selected = index - 1;
212 self.set_selection(new_selected);
213 return true;
214 }
215 false
216 }
217
218 pub fn clear(&mut self) {
220 self.size_cache = None;
221 self.items.clear();
222 self.set_selection(0)
223 }
224
225 pub fn len(&self) -> usize { self.items.len() }
227
228 pub fn is_empty(&self) -> bool { self.items.is_empty() }
230
231 #[chainable]
233 pub fn add_item<L: Into<StyledString>>(&mut self, label: L, item: T) {
234 let mut new_item = StyledItem::new(label, item);
235 if let Some(size) = self.size_cache {
236 new_item.fit_to_width(size.x, false);
237 }
238 self.items.push(new_item)
239 }
240
241 pub fn add_all<L, I>(&mut self, iter: I)
243 where
244 L: Into<StyledString>,
245 I: IntoIterator<Item = (L, T)>
246 {
247 for (label, item) in iter {
248 self.add_item(label, item);
249 }
250 }
251
252 #[must_use]
256 pub fn with_all<L, I>(mut self, iter: I) -> Self
257 where
258 L: Into<StyledString>,
259 I: IntoIterator<Item = (L, T)>
260 {
261 self.add_all(iter);
262 self
263 }
264
265 pub fn get_item(&self, index: usize) -> Option<(&StyledString, &T)> {
267 let item = self.items.get(index)?;
268 Some((item.label.get_content(), &item.item))
269 }
270
271 pub fn get_item_mut(&mut self, index: usize) -> Option<(&mut StyledString, &mut T)> {
273 let item = self.items.get_mut(index)?;
274 if let Some(t) = Arc::get_mut(&mut item.item) {
275 let label = &mut item.label;
276 Some((label.get_content_mut(), t))
277 }
278 else { None }
279 }
280
281 pub fn iter(&self) -> impl Iterator<Item = (&StyledString, &T)> + DoubleEndedIterator + ExactSizeIterator {
283 self.items.iter().map(|item| (item.label.get_content(), &*item.item))
284 }
285
286 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&mut StyledString, &mut T)> + DoubleEndedIterator + ExactSizeIterator
288 where T: Clone
289 {
290 self.items.iter_mut().map(|item| (item.label.get_content_mut(), Arc::make_mut(&mut item.item)))
291 }
292
293 pub fn remove_item(&mut self, index: usize) {
295 if index < self.items.len() {
296 self.items.remove(index);
297 if index >= self.len() {
298 self.set_selection(self.len() - 1);
299 }
300 let new_size = self.calc_size();
301 self.size_cache = Some(new_size);
302 }
303 }
304
305 pub fn insert_item<L: Into<StyledString>>(&mut self, index: usize, label: L, item: T) {
307 if index < self.items.len() {
308 let mut new_item = StyledItem::new(label, item);
309 if let Some(size) = self.size_cache {
310 new_item.fit_to_width(size.x, false);
311 }
312 self.items.insert(index, new_item);
313 }
314 }
315
316 pub fn label_sort(&mut self) {
318 self.items
319 .sort_by(|a, b| a.label.get_content().source().cmp(b.label.get_content().source()));
320
321 self.calc_y_ofs();
322 }
323
324 pub fn sort_by<F>(&mut self, mut compare: F)
326 where
327 F: FnMut(&T, &T) -> Ordering,
328 {
329 self.items.sort_by(|a, b| compare(&a.item, &b.item));
330 self.calc_y_ofs();
331 }
332
333 pub fn sort_by_key<K, F>(&mut self, mut key_of: F)
335 where
336 F: FnMut(&T) -> K,
337 K: Ord
338 {
339 self.items.sort_by_key(|item| key_of(&item.item));
340 self.calc_y_ofs();
341 }
342
343 pub fn move_item_up(&mut self, index: usize) {
345 if index > 0 {
346 self.items.swap(index, index - 1);
347 self.calc_y_ofs();
348 }
349 }
350
351 pub fn move_item_down(&mut self, index: usize) {
353 if index < self.items.len() - 1 {
354 self.items.swap(index, index + 1);
355 self.calc_y_ofs();
356 }
357 }
358
359 fn open_popup(&mut self) -> EventResult {
360 let index = self.selected_index();
361 let mut sv_layout = vlayout!();
362
363 for (i, item) in self.items.iter().enumerate() {
364 let selected = self.selected.clone();
365 let submit_cb = self.submit_cb.clone();
366 let selected_cb = self.selected_cb.clone();
367 let data = item.item.clone();
368 sv_layout.add_child(
369 AdvancedButton::new_with_data(
370 item.label.get_content().clone(),
371 item.item.clone(),
372 move |root| {
373 selected.store(i, AtomicOrdering::Relaxed);
374 root.pop_layer();
375 submit_cb(root, &data);
376 selected_cb(root, &data);
377 }
378 )
379 );
380 if i == index { sv_layout.set_focus_index(i).unwrap(); }
381 }
382
383 let y_ofs = self.items[index].y_ofs;
384 let ofs = *(self.popup_ofs.read().unwrap());
385 let ofs = ofs.saturating_sub((0, y_ofs + 1)) + (1, 0);
386
387 EventResult::with_cb_once(move |root| {
388 let current_offset = root
389 .screen()
390 .layer_offset(LayerPosition::FromFront(0))
391 .unwrap_or_else(Vec2::zero);
392
393 let offset = ofs.signed() - current_offset;
394 root.screen_mut().add_layer_at(
395 Position::parent(offset),
396 c_focus!(
397 Dialog::around(sv_layout)
398 .wrap_with(OnEventView::new)
399 .on_event(Event::Key(Key::Esc), |r| { r.pop_layer(); })
400 )
401 );
402 })
403 }
404
405 fn view_height(&self) -> usize { self.items.iter().map(|item| item.size.y).sum() }
407
408 fn calc_y_ofs(&mut self) {
410 let mut y = 0;
411 for item in &mut self.items {
412 item.y_ofs = y;
413 y += item.size.y;
414 }
415 }
416
417 fn calc_size(&mut self) -> Vec2 {
418 let width = self
419 .items
420 .iter()
421 .map(|item| item.size.x)
422 .max()
423 .unwrap_or(1);
424
425 (width, self.view_height()).into()
426 }
427}
428
429impl<T: Send + Sync + 'static> View for AdvancedSelectView<T> {
430 fn draw(&self, printer: &Printer) {
431 let index = self.selected_index();
432 if printer.size.x == 0 || printer.size.y == 0 { return; }
433 if self.popup && !self.items.is_empty() {
434 *(self.popup_ofs.write().unwrap()) = printer.offset;
435 self.items[index].draw(printer, self.enabled, printer.focused, true);
436 }
437 else {
438 for (i, item) in self.items.iter().enumerate() {
439 item.draw(printer, self.enabled, i == index && printer.focused, false);
440 }
441 }
442 }
443
444 fn required_size(&mut self, bound: Vec2) -> Vec2 {
445 let index = self.selected_index();
446 if bound.x == 0 || bound.y == 0 { return (0, 0).into() }
447 let new_size = if self.popup && !self.items.is_empty() {
448 let item = &mut self.items[index];
449 item.fit_to_width(bound.x, true);
450 item.label.size(true)
451 }
452 else {
453 if let Some(size) = self.size_cache {
454 if let Some(item) = self.items.get(0) {
455 if item.size.x > 0 { return size; }
456 }
457 }
458 for item in &mut self.items {
459 item.fit_to_width(bound.x, false);
460 }
461
462 self.calc_size()
463 };
464 new_size
465 }
466
467 fn layout(&mut self, size: Vec2) {
468 let index = self.selected_index();
469 if size.x == 0 || size.y == 0 { return; }
470 self.calc_y_ofs();
471 for item in &mut self.items {
472 item.fit_to_width(size.x, false);
473 }
474
475 if self.popup {
476 let selected = &mut self.items[index];
477 selected.fit_to_width(size.x, true);
478 }
479 self.size_cache = Some(size);
480 }
481
482 fn take_focus(&mut self, dir: Direction) -> Result<EventResult, CannotFocus> {
483 if self.enabled && !self.items.is_empty() {
484 if !self.popup {
485 match dir {
486 Direction::Abs(Absolute::Up) => self.set_selection(0),
487 Direction::Abs(Absolute::Down) => self.set_selection(self.items.len().saturating_sub(1)),
488 _ => { }
489 }
490 }
491 Ok(EventResult::consumed())
492 }
493 else { Err(CannotFocus) }
494 }
495
496 fn on_event(&mut self, event: Event) -> EventResult {
497 let index = self.selected_index();
498 if self.enabled {
499 match event {
500 Event::Key(Key::Up) if !self.popup => if !self.move_up() { return EventResult::Ignored; }
501 Event::Key(Key::Down) if !self.popup => if !self.move_down() { return EventResult::Ignored; },
502 Event::Key(Key::End) if !self.popup => self.set_selection(self.items.len() - 1),
503 Event::Key(Key::Home) if !self.popup => self.set_selection(0),
504
505 Event::Key(Key::PageUp) if !self.popup => {
506 if index < 10 { self.selected.store(0, AtomicOrdering::Relaxed) }
507 else {
508 let new_selected = index - 10;
509 self.selected.store(new_selected, AtomicOrdering::Relaxed);
510 }
511 }
512
513 Event::Key(Key::PageDown) if !self.popup => {
514 if self.items.len() < 10 { self.selected.store(self.items.len() - 1, AtomicOrdering::Relaxed) }
515 else if index > self.items.len() - 10 { self.selected.store(self.items.len() - 1, AtomicOrdering::Relaxed); }
516 else {
517 let new_selected = index + 10;
518 self.selected.store(new_selected, AtomicOrdering::Relaxed);
519 }
520 }
521
522 Event::Key(Key::Enter) => {
523 if self.is_empty() { return EventResult::Ignored; }
524 if self.popup { return self.open_popup(); }
525 let callback = self.submit_cb.clone();
526 let item = self.items[index].item.clone();
527 return EventResult::with_cb_once(move |root| callback(root, &item))
528 }
529
530 Event::Mouse {
531 event: MouseEvent::Press(button),
532 position,
533 offset
534 }
535 if position.checked_sub(offset).is_some() && !self.popup => {
536 for (i, item) in self.items.iter().enumerate() {
537 if item.has_mouse_pos(position - offset) {
538 self.selected.store(i, AtomicOrdering::Relaxed);
539 }
540 }
541
542 if button == MouseButton::Left {
543 let callback = self.submit_cb.clone();
544 let item = self.items[index].item.clone();
545 return EventResult::with_cb_once(move |root| callback(root, &item))
546 }
547 }
548
549 Event::Mouse {
550 event: MouseEvent::Release(MouseButton::Left),
551 position,
552 offset
553 } if self.popup => {
554 let item = &self.items[index];
555 let b_rect = Rect::from_size((0, 0), item.size);
556
557 if let Some(new_pos) = position.checked_sub(offset) {
558 if b_rect.contains(new_pos) {
559 return self.open_popup();
560 }
561 }
562 }
563
564 Event::Char(c) if self.autojump && !self.popup => {
565 let lc = c.to_ascii_lowercase();
566 for (i, item) in self.items.iter().enumerate() {
567 let raw_label = item.label.get_content().source();
568 if raw_label.is_empty() || i <= index { continue; }
569 else {
570 let first_char = raw_label
571 .chars()
572 .next().unwrap()
573 .to_ascii_lowercase();
574 if lc == first_char {
575 self.set_selection(i);
576 break;
577 }
578 }
579 }
580 }
581
582 Event::WindowResize => {
583 self.size_cache = None;
584 for item in &mut self.items {
585 item.size = (0, 1).into();
586 }
587 return EventResult::Ignored;
588 }
589
590 _ => return EventResult::Ignored
591 }
592
593 if index >= self.items.len() {
594 self.set_selection(self.items.len() - 1);
595 }
596
597 if self.items.is_empty() { EventResult::Ignored }
598 else {
599 let callback = self.selected_cb.clone();
600 let item = self.items[index].item.clone();
601 EventResult::with_cb_once(move |root| callback(root, &item))
602 }
603 }
604 else { EventResult::Ignored }
605 }
606
607 fn important_area(&self, size: Vec2) -> Rect {
608 if self.popup { Rect::from_size((0, 0), size) }
609 else {
610 let loc = (0, self.items[self.selected_index()].y_ofs);
611 Rect::from_size(loc, self.items[self.selected_index()].size)
612 }
613 }
614}
615
616impl<T: Send + Sync + 'static> Default for AdvancedSelectView<T> {
617 fn default() -> Self { Self::new() }
618}
619
620#[derive(Clone)]
621struct StyledItem<T: 'static> {
622 label: ButtonContent,
624
625 item: Arc<T>,
627
628 size: Vec2,
630
631 y_ofs: usize
633}
634
635impl<T: 'static> StyledItem<T> {
636 fn new<L: Into<StyledString>>(label: L, item: T) -> StyledItem<T> {
637 StyledItem {
638 label: ButtonContent::new(label),
639 item: Arc::new(item),
640 size: (0, 1).into(),
641 y_ofs: 0
642 }
643 }
644
645 fn has_mouse_pos(&self, pos: Vec2) -> bool {
646 Rect::from_size((0, self.y_ofs), self.size)
647 .contains(pos)
648 }
649
650 fn draw(&self, printer: &Printer, enabled: bool, focused: bool, popup: bool) {
651 self.label.draw(printer, if popup { (0, 0).into() } else { (0, self.y_ofs).into() }, enabled, focused, popup);
652 }
653
654 fn fit_to_width(&mut self, new_width: usize, popup: bool) {
655 self.label.fit_to_width(new_width);
656 self.size = self.label.size(popup);
657 }
658}