use std::{
cmp::Ordering,
fmt::Display,
sync::{
atomic::AtomicUsize,
Arc, RwLock,
atomic::Ordering as AtomicOrdering
}
};
use cursive_core::{
Cursive, impl_enabled, Printer,
Rect, Vec2, View, With,
direction::{Direction, Absolute},
view::{CannotFocus, Position},
event::{
Event, EventResult, Key,
MouseButton, MouseEvent
},
utils::markup::StyledString,
views::{
Dialog,
LayerPosition,
OnEventView,
}
};
use rust_utils::{chainable, encapsulated};
use crate::{
c_focus, vlayout,
AdvancedButton
};
use super::ButtonContent;
type SelectCallback<T> = Arc<dyn Fn(&mut Cursive, &T) + Send + Sync>;
#[derive(Clone)]
#[encapsulated]
pub struct AdvancedSelectView<T = String>
where T: 'static
{
items: Vec<StyledItem<T>>,
enabled: bool,
selected: Arc<AtomicUsize>,
#[setter(doc = "Allow jumping to items starting with the the pressed letter key")]
#[chainable]
autojump: bool,
popup: bool,
popup_ofs: Arc<RwLock<Vec2>>,
selected_cb: SelectCallback<T>,
submit_cb: SelectCallback<T>,
size_cache: Option<Vec2>
}
impl AdvancedSelectView {
#[chainable]
pub fn add_item_str<S: Display>(&mut self, label: S) {
self.add_item(label.to_string(), label.to_string());
}
pub fn insert_item_str<S: Display>(&mut self, index: usize, label: S) {
self.insert_item(index, label.to_string(), label.to_string());
}
pub fn add_all_str<S, I>(&mut self, iter: I)
where
S: Display,
I: IntoIterator<Item = S>
{
for label in iter {
self.add_item_str(label);
}
}
#[must_use]
pub fn with_all_str<S, I>(mut self, iter: I) -> Self
where
S: Display,
I: IntoIterator<Item = S>
{
self.add_all_str(iter);
self
}
}
impl<T: Ord + 'static> AdvancedSelectView<T> {
pub fn sort(&mut self) { self.items.sort_by(|a, b| a.item.cmp(&b.item)); }
}
impl<T: Send + Sync + 'static> AdvancedSelectView<T> {
impl_enabled!(self.enabled);
#[must_use]
pub fn new() -> AdvancedSelectView<T> {
AdvancedSelectView {
items: vec![],
enabled: true,
selected: Arc::new(AtomicUsize::new(0)),
autojump: false,
popup: false,
popup_ofs: Arc::new(RwLock::new((0, 0).into())),
selected_cb: Arc::new(|_, _| { }),
submit_cb: Arc::new(|_, _| { }),
size_cache: None
}
}
#[chainable]
pub fn set_popup(&mut self, popup: bool) {
self.popup = popup;
self.size_cache = None;
}
#[chainable]
pub fn set_on_select<F: Fn(&mut Cursive, &T) + Send + Sync + 'static>(&mut self, cb: F) { self.selected_cb = Arc::new(cb); }
#[chainable]
pub fn set_on_submit<F: Fn(&mut Cursive, &T) + Send + Sync + 'static>(&mut self, cb: F) { self.submit_cb = Arc::new(cb); }
pub fn selection(&self) -> Option<&T> {
Some(&self.items.get(self.selected_index())?.item)
}
pub fn selection_mut(&mut self) -> Option<&mut T> {
let index = self.selected_index();
Arc::get_mut(&mut self.items.get_mut(index)?.item)
}
pub fn selected_index(&self) -> usize { self.selected.load(AtomicOrdering::Relaxed) }
pub fn selected_label(&self) -> Option<&StyledString> {
Some(self.items.get(self.selected_index())?.label.get_content())
}
pub fn selected_label_mut(&mut self) -> Option<&mut StyledString> {
let index = self.selected_index();
Some(self.items.get_mut(index)?.label.get_content_mut())
}
pub fn set_selection(&mut self, index: usize) {
if index < self.items.len() { self.selected.store(index, AtomicOrdering::Relaxed); }
}
#[must_use]
pub fn selected(mut self, index: usize) -> Self {
self.set_selection(index);
self
}
pub fn move_down(&mut self) -> bool {
let index = self.selected_index();
if index < self.items.len().saturating_sub(1) {
let new_selected = index + 1;
self.set_selection(new_selected);
return true;
}
false
}
pub fn move_up(&mut self) -> bool {
let index = self.selected_index();
if index != 0 {
let new_selected = index - 1;
self.set_selection(new_selected);
return true;
}
false
}
pub fn clear(&mut self) {
self.size_cache = None;
self.items.clear();
self.set_selection(0)
}
pub fn len(&self) -> usize { self.items.len() }
pub fn is_empty(&self) -> bool { self.items.is_empty() }
#[chainable]
pub fn add_item<L: Into<StyledString>>(&mut self, label: L, item: T) {
let mut new_item = StyledItem::new(label, item);
if let Some(size) = self.size_cache {
new_item.fit_to_width(size.x, false);
}
self.items.push(new_item)
}
pub fn add_all<L, I>(&mut self, iter: I)
where
L: Into<StyledString>,
I: IntoIterator<Item = (L, T)>
{
for (label, item) in iter {
self.add_item(label, item);
}
}
#[must_use]
pub fn with_all<L, I>(mut self, iter: I) -> Self
where
L: Into<StyledString>,
I: IntoIterator<Item = (L, T)>
{
self.add_all(iter);
self
}
pub fn get_item(&self, index: usize) -> Option<(&StyledString, &T)> {
let item = self.items.get(index)?;
Some((item.label.get_content(), &item.item))
}
pub fn get_item_mut(&mut self, index: usize) -> Option<(&mut StyledString, &mut T)> {
let item = self.items.get_mut(index)?;
if let Some(t) = Arc::get_mut(&mut item.item) {
let label = &mut item.label;
Some((label.get_content_mut(), t))
}
else { None }
}
pub fn iter(&self) -> impl Iterator<Item = (&StyledString, &T)> + DoubleEndedIterator + ExactSizeIterator {
self.items.iter().map(|item| (item.label.get_content(), &*item.item))
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&mut StyledString, &mut T)> + DoubleEndedIterator + ExactSizeIterator
where T: Clone
{
self.items.iter_mut().map(|item| (item.label.get_content_mut(), Arc::make_mut(&mut item.item)))
}
pub fn remove_item(&mut self, index: usize) {
if index < self.items.len() {
self.items.remove(index);
if index >= self.len() {
self.set_selection(self.len() - 1);
}
let new_size = self.calc_size();
self.size_cache = Some(new_size);
}
}
pub fn insert_item<L: Into<StyledString>>(&mut self, index: usize, label: L, item: T) {
if index < self.items.len() {
let mut new_item = StyledItem::new(label, item);
if let Some(size) = self.size_cache {
new_item.fit_to_width(size.x, false);
}
self.items.insert(index, new_item);
}
}
pub fn label_sort(&mut self) {
self.items
.sort_by(|a, b| a.label.get_content().source().cmp(b.label.get_content().source()));
self.calc_y_ofs();
}
pub fn sort_by<F>(&mut self, mut compare: F)
where
F: FnMut(&T, &T) -> Ordering,
{
self.items.sort_by(|a, b| compare(&a.item, &b.item));
self.calc_y_ofs();
}
pub fn sort_by_key<K, F>(&mut self, mut key_of: F)
where
F: FnMut(&T) -> K,
K: Ord
{
self.items.sort_by_key(|item| key_of(&item.item));
self.calc_y_ofs();
}
pub fn move_item_up(&mut self, index: usize) {
if index > 0 {
self.items.swap(index, index - 1);
self.calc_y_ofs();
}
}
pub fn move_item_down(&mut self, index: usize) {
if index < self.items.len() - 1 {
self.items.swap(index, index + 1);
self.calc_y_ofs();
}
}
fn open_popup(&mut self) -> EventResult {
let index = self.selected_index();
let mut sv_layout = vlayout!();
for (i, item) in self.items.iter().enumerate() {
let selected = self.selected.clone();
let submit_cb = self.submit_cb.clone();
let selected_cb = self.selected_cb.clone();
let data = item.item.clone();
sv_layout.add_child(
AdvancedButton::new_with_data(
item.label.get_content().clone(),
item.item.clone(),
move |root| {
selected.store(i, AtomicOrdering::Relaxed);
root.pop_layer();
submit_cb(root, &data);
selected_cb(root, &data);
}
)
);
if i == index { sv_layout.set_focus_index(i).unwrap(); }
}
let y_ofs = self.items[index].y_ofs;
let ofs = *(self.popup_ofs.read().unwrap());
let ofs = ofs.saturating_sub((0, y_ofs + 1)) + (1, 0);
EventResult::with_cb_once(move |root| {
let current_offset = root
.screen()
.layer_offset(LayerPosition::FromFront(0))
.unwrap_or_else(Vec2::zero);
let offset = ofs.signed() - current_offset;
root.screen_mut().add_layer_at(
Position::parent(offset),
c_focus!(
Dialog::around(sv_layout)
.wrap_with(OnEventView::new)
.on_event(Event::Key(Key::Esc), |r| { r.pop_layer(); })
)
);
})
}
fn view_height(&self) -> usize { self.items.iter().map(|item| item.size.y).sum() }
fn calc_y_ofs(&mut self) {
let mut y = 0;
for item in &mut self.items {
item.y_ofs = y;
y += item.size.y;
}
}
fn calc_size(&mut self) -> Vec2 {
let width = self
.items
.iter()
.map(|item| item.size.x)
.max()
.unwrap_or(1);
(width, self.view_height()).into()
}
}
impl<T: Send + Sync + 'static> View for AdvancedSelectView<T> {
fn draw(&self, printer: &Printer) {
let index = self.selected_index();
if printer.size.x == 0 || printer.size.y == 0 { return; }
if self.popup && !self.items.is_empty() {
*(self.popup_ofs.write().unwrap()) = printer.offset;
self.items[index].draw(printer, self.enabled, printer.focused, true);
}
else {
for (i, item) in self.items.iter().enumerate() {
item.draw(printer, self.enabled, i == index && printer.focused, false);
}
}
}
fn required_size(&mut self, bound: Vec2) -> Vec2 {
let index = self.selected_index();
if bound.x == 0 || bound.y == 0 { return (0, 0).into() }
let new_size = if self.popup && !self.items.is_empty() {
let item = &mut self.items[index];
item.fit_to_width(bound.x, true);
item.label.size(true)
}
else {
if let Some(size) = self.size_cache {
if let Some(item) = self.items.get(0) {
if item.size.x > 0 { return size; }
}
}
for item in &mut self.items {
item.fit_to_width(bound.x, false);
}
self.calc_size()
};
new_size
}
fn layout(&mut self, size: Vec2) {
let index = self.selected_index();
if size.x == 0 || size.y == 0 { return; }
self.calc_y_ofs();
for item in &mut self.items {
item.fit_to_width(size.x, false);
}
if self.popup {
let selected = &mut self.items[index];
selected.fit_to_width(size.x, true);
}
self.size_cache = Some(size);
}
fn take_focus(&mut self, dir: Direction) -> Result<EventResult, CannotFocus> {
if self.enabled && !self.items.is_empty() {
if !self.popup {
match dir {
Direction::Abs(Absolute::Up) => self.set_selection(0),
Direction::Abs(Absolute::Down) => self.set_selection(self.items.len().saturating_sub(1)),
_ => { }
}
}
Ok(EventResult::consumed())
}
else { Err(CannotFocus) }
}
fn on_event(&mut self, event: Event) -> EventResult {
let index = self.selected_index();
if self.enabled {
match event {
Event::Key(Key::Up) if !self.popup => if !self.move_up() { return EventResult::Ignored; }
Event::Key(Key::Down) if !self.popup => if !self.move_down() { return EventResult::Ignored; },
Event::Key(Key::End) if !self.popup => self.set_selection(self.items.len() - 1),
Event::Key(Key::Home) if !self.popup => self.set_selection(0),
Event::Key(Key::PageUp) if !self.popup => {
if index < 10 { self.selected.store(0, AtomicOrdering::Relaxed) }
else {
let new_selected = index - 10;
self.selected.store(new_selected, AtomicOrdering::Relaxed);
}
}
Event::Key(Key::PageDown) if !self.popup => {
if self.items.len() < 10 { self.selected.store(self.items.len() - 1, AtomicOrdering::Relaxed) }
else if index > self.items.len() - 10 { self.selected.store(self.items.len() - 1, AtomicOrdering::Relaxed); }
else {
let new_selected = index + 10;
self.selected.store(new_selected, AtomicOrdering::Relaxed);
}
}
Event::Key(Key::Enter) => {
if self.is_empty() { return EventResult::Ignored; }
if self.popup { return self.open_popup(); }
let callback = self.submit_cb.clone();
let item = self.items[index].item.clone();
return EventResult::with_cb_once(move |root| callback(root, &item))
}
Event::Mouse {
event: MouseEvent::Press(button),
position,
offset
}
if position.checked_sub(offset).is_some() && !self.popup => {
for (i, item) in self.items.iter().enumerate() {
if item.has_mouse_pos(position - offset) {
self.selected.store(i, AtomicOrdering::Relaxed);
}
}
if button == MouseButton::Left {
let callback = self.submit_cb.clone();
let item = self.items[index].item.clone();
return EventResult::with_cb_once(move |root| callback(root, &item))
}
}
Event::Mouse {
event: MouseEvent::Release(MouseButton::Left),
position,
offset
} if self.popup => {
let item = &self.items[index];
let b_rect = Rect::from_size((0, 0), item.size);
if let Some(new_pos) = position.checked_sub(offset) {
if b_rect.contains(new_pos) {
return self.open_popup();
}
}
}
Event::Char(c) if self.autojump && !self.popup => {
let lc = c.to_ascii_lowercase();
for (i, item) in self.items.iter().enumerate() {
let raw_label = item.label.get_content().source();
if raw_label.is_empty() || i <= index { continue; }
else {
let first_char = raw_label
.chars()
.next().unwrap()
.to_ascii_lowercase();
if lc == first_char {
self.set_selection(i);
break;
}
}
}
}
Event::WindowResize => {
self.size_cache = None;
for item in &mut self.items {
item.size = (0, 1).into();
}
return EventResult::Ignored;
}
_ => return EventResult::Ignored
}
if index >= self.items.len() {
self.set_selection(self.items.len() - 1);
}
if self.items.is_empty() { EventResult::Ignored }
else {
let callback = self.selected_cb.clone();
let item = self.items[index].item.clone();
EventResult::with_cb_once(move |root| callback(root, &item))
}
}
else { EventResult::Ignored }
}
fn important_area(&self, size: Vec2) -> Rect {
if self.popup { Rect::from_size((0, 0), size) }
else {
let loc = (0, self.items[self.selected_index()].y_ofs);
Rect::from_size(loc, self.items[self.selected_index()].size)
}
}
}
impl<T: Send + Sync + 'static> Default for AdvancedSelectView<T> {
fn default() -> Self { Self::new() }
}
#[derive(Clone)]
struct StyledItem<T: 'static> {
label: ButtonContent,
item: Arc<T>,
size: Vec2,
y_ofs: usize
}
impl<T: 'static> StyledItem<T> {
fn new<L: Into<StyledString>>(label: L, item: T) -> StyledItem<T> {
StyledItem {
label: ButtonContent::new(label),
item: Arc::new(item),
size: (0, 1).into(),
y_ofs: 0
}
}
fn has_mouse_pos(&self, pos: Vec2) -> bool {
Rect::from_size((0, self.y_ofs), self.size)
.contains(pos)
}
fn draw(&self, printer: &Printer, enabled: bool, focused: bool, popup: bool) {
self.label.draw(printer, if popup { (0, 0).into() } else { (0, self.y_ofs).into() }, enabled, focused, popup);
}
fn fit_to_width(&mut self, new_width: usize, popup: bool) {
self.label.fit_to_width(new_width);
self.size = self.label.size(popup);
}
}