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 {
507 self.set_selection(0);
508 }
509 else {
510 self.set_selection(index - 10);
511 }
512 }
513
514 Event::Key(Key::PageDown) if !self.popup => {
515 if self.items.len() < 10 {
516 self.set_selection(self.items.len() - 1);
517 }
518 else if index > self.items.len() - 10 {
519 self.set_selection(self.items.len() - 1);
520 }
521 else {
522 self.set_selection(index + 10);
523 }
524 }
525
526 Event::Key(Key::Enter) => {
527 if self.is_empty() { return EventResult::Ignored; }
528 if self.popup { return self.open_popup(); }
529 let callback = self.submit_cb.clone();
530 let item = self.items[index].item.clone();
531 return EventResult::with_cb_once(move |root| callback(root, &item))
532 }
533
534 Event::Mouse {
535 event: MouseEvent::Press(button),
536 position,
537 offset
538 }
539 if position.checked_sub(offset).is_some() && !self.popup => {
540 for (i, item) in self.items.iter().enumerate() {
541 if item.has_mouse_pos(position - offset) {
542 self.selected.store(i, AtomicOrdering::Relaxed);
543 }
544 }
545
546 if button == MouseButton::Left {
547 let callback = self.submit_cb.clone();
548 let item = self.items[self.selected_index()].item.clone();
549 return EventResult::with_cb_once(move |root| callback(root, &item))
550 }
551 }
552
553 Event::Mouse {
554 event: MouseEvent::Release(MouseButton::Left),
555 position,
556 offset
557 }
558 if self.popup => {
559 let item = &self.items[index];
560 let b_rect = Rect::from_size((0, 0), item.size);
561
562 if let Some(new_pos) = position.checked_sub(offset) {
563 if b_rect.contains(new_pos) {
564 return self.open_popup();
565 }
566 }
567 }
568
569 Event::Char(c) if self.autojump && !self.popup => {
570 let lc = c.to_ascii_lowercase();
571 for (i, item) in self.items.iter().enumerate() {
572 let raw_label = item.label.get_content().source();
573 if raw_label.is_empty() || i <= index { continue; }
574 else {
575 let first_char = raw_label
576 .chars()
577 .next().unwrap()
578 .to_ascii_lowercase();
579
580 if lc == first_char {
581 self.set_selection(i);
582 break;
583 }
584 }
585 }
586 }
587
588 Event::WindowResize => {
589 self.size_cache = None;
590
591 for item in &mut self.items {
592 item.size = (0, 1).into();
593 }
594
595 return EventResult::Ignored;
596 }
597
598 _ => return EventResult::Ignored
599 }
600
601 if index >= self.items.len() {
602 self.set_selection(self.items.len() - 1);
603 }
604
605 if self.items.is_empty() { EventResult::Ignored }
606 else {
607 let callback = self.selected_cb.clone();
608 let item = self.items[self.selected_index()].item.clone();
609 EventResult::with_cb_once(move |root| callback(root, &item))
610 }
611 }
612 else { EventResult::Ignored }
613 }
614
615 fn important_area(&self, size: Vec2) -> Rect {
616 if self.popup { Rect::from_size((0, 0), size) }
617 else {
618 let loc = (0, self.items[self.selected_index()].y_ofs);
619 Rect::from_size(loc, self.items[self.selected_index()].size)
620 }
621 }
622}
623
624impl<T: Send + Sync + 'static> Default for AdvancedSelectView<T> {
625 fn default() -> Self { Self::new() }
626}
627
628#[derive(Clone)]
629struct StyledItem<T: 'static> {
630 label: ButtonContent,
632
633 item: Arc<T>,
635
636 size: Vec2,
638
639 y_ofs: usize
641}
642
643impl<T: 'static> StyledItem<T> {
644 fn new<L: Into<StyledString>>(label: L, item: T) -> StyledItem<T> {
645 StyledItem {
646 label: ButtonContent::new(label),
647 item: Arc::new(item),
648 size: (0, 1).into(),
649 y_ofs: 0
650 }
651 }
652
653 fn has_mouse_pos(&self, pos: Vec2) -> bool {
654 Rect::from_size((0, self.y_ofs), self.size)
655 .contains(pos)
656 }
657
658 fn draw(&self, printer: &Printer, enabled: bool, focused: bool, popup: bool) {
659 self.label.draw(printer, if popup { (0, 0).into() } else { (0, self.y_ofs).into() }, enabled, focused, popup);
660 }
661
662 fn fit_to_width(&mut self, new_width: usize, popup: bool) {
663 self.label.fit_to_width(new_width);
664 self.size = self.label.size(popup);
665 }
666}