use std::{
rc::Rc,
fmt::Display,
cmp::Ordering,
cell::Cell
};
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 crate::{
c_focus, vlayout,
AdvancedButton
};
use super::ButtonContent;
type SelectCallback<T> = Rc<dyn Fn(&mut Cursive, &T)>;
#[derive(Clone)]
pub struct AdvancedSelectView<T = String>
where T: 'static
{
items: Vec<StyledItem<T>>,
enabled: bool,
selected: Rc<Cell<usize>>,
autojump: bool,
popup: bool,
popup_ofs: Cell<Vec2>,
selected_cb: SelectCallback<T>,
submit_cb: SelectCallback<T>,
size_cache: Option<Vec2>
}
impl AdvancedSelectView {
pub fn add_item_str<S: Display>(&mut self, label: S) {
self.add_item(label.to_string(), label.to_string());
}
#[must_use]
pub fn item_str<S: Display>(mut self, label: S) -> Self {
self.add_item_str(label);
self
}
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: 'static> AdvancedSelectView<T> {
impl_enabled!(self.enabled);
#[must_use]
pub fn new() -> AdvancedSelectView<T> {
AdvancedSelectView {
items: vec![],
enabled: true,
selected: Rc::new(Cell::new(0)),
autojump: false,
popup: false,
popup_ofs: Cell::new((0, 0).into()),
selected_cb: Rc::new(|_, _| { }),
submit_cb: Rc::new(|_, _| { }),
size_cache: None
}
}
pub fn set_autojump(&mut self, autojump: bool) { self.autojump = autojump; }
#[must_use]
pub fn autojump(mut self) -> Self {
self.set_autojump(true);
self
}
pub fn set_popup(&mut self, popup: bool) {
self.popup = popup;
self.size_cache = None;
}
#[must_use]
pub fn popup(mut self) -> Self {
self.set_popup(true);
self
}
pub fn set_on_select<F: Fn(&mut Cursive, &T) + 'static>(&mut self, cb: F) { self.selected_cb = Rc::new(cb); }
#[must_use]
pub fn on_select<F: Fn(&mut Cursive, &T) + 'static>(mut self, cb: F) -> Self {
self.set_on_select(cb);
self
}
pub fn set_on_submit<F: Fn(&mut Cursive, &T) + 'static>(&mut self, cb: F) { self.submit_cb = Rc::new(cb); }
#[must_use]
pub fn on_submit<F: Fn(&mut Cursive, &T) + 'static>(mut self, cb: F) -> Self {
self.set_on_submit(cb);
self
}
pub fn selection(&self) -> Option<&T> {
Some(&self.items.get(self.selected.get())?.item)
}
pub fn selection_mut(&mut self) -> Option<&mut T> {
Rc::get_mut(&mut self.items.get_mut(self.selected.get())?.item)
}
pub fn selected_index(&self) -> usize { self.selected.get() }
pub fn selected_label(&self) -> Option<&StyledString> {
Some(self.items.get(self.selected.get())?.label.get_content())
}
pub fn selected_label_mut(&mut self) -> Option<&mut StyledString> {
Some(self.items.get_mut(self.selected.get())?.label.get_content_mut())
}
pub fn set_selection(&mut self, index: usize) {
if index < self.items.len() { self.selected.set(index); }
}
#[must_use]
pub fn selected(mut self, index: usize) -> Self {
self.set_selection(index);
self
}
pub fn move_down(&mut self) -> bool {
if self.selected.get() < self.items.len().saturating_sub(1) {
let new_selected = self.selected.get() + 1;
self.selected.set(new_selected);
return true;
}
false
}
pub fn move_up(&mut self) -> bool {
if self.selected.get() != 0 {
let new_selected = self.selected.get() - 1;
self.selected.set(new_selected);
return true;
}
false
}
pub fn clear(&mut self) {
self.size_cache = None;
self.items.clear();
self.selected.set(0)
}
pub fn len(&self) -> usize { self.items.len() }
pub fn is_empty(&self) -> bool { self.items.is_empty() }
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
}
#[must_use]
pub fn item<L: Into<StyledString>>(mut self, label: L, item: T) -> Self {
self.add_item(label, item);
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) = Rc::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(), Rc::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.selected.set(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 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.set(i);
root.pop_layer();
submit_cb(root, &data);
selected_cb(root, &data);
}
)
);
if i == self.selected.get() { sv_layout.set_focus_index(i).unwrap(); }
}
let y_ofs = self.items[self.selected.get()].y_ofs;
let ofs = self.popup_ofs.get();
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: 'static> View for AdvancedSelectView<T> {
fn draw(&self, printer: &Printer) {
if printer.size.x == 0 || printer.size.y == 0 { return; }
if self.popup && !self.items.is_empty() {
self.popup_ofs.set(printer.offset);
self.items[self.selected.get()].draw(printer, self.enabled, printer.focused, true);
}
else {
for (i, item) in self.items.iter().enumerate() {
item.draw(printer, self.enabled, i == self.selected.get() && printer.focused, false);
}
}
}
fn required_size(&mut self, bound: Vec2) -> Vec2 {
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[self.selected.get()];
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) {
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[self.selected.get()];
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.selected.set(0),
Direction::Abs(Absolute::Down) => self.selected.set(self.items.len().saturating_sub(1)),
_ => { }
}
}
Ok(EventResult::consumed())
}
else { Err(CannotFocus) }
}
fn on_event(&mut self, event: Event) -> EventResult {
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.selected.set(self.items.len() - 1),
Event::Key(Key::Home) if !self.popup => self.selected.set(0),
Event::Key(Key::PageUp) if !self.popup => {
if self.selected.get() < 10 { self.selected.set(0) }
else {
let new_selected = self.selected.get() - 10;
self.selected.set(new_selected);
}
}
Event::Key(Key::PageDown) if !self.popup => {
if self.items.len() < 10 { self.selected.set(self.items.len() - 1) }
else if self.selected.get() > self.items.len() - 10 { self.selected.set(self.items.len() - 1); }
else {
let new_selected = self.selected.get() + 10;
self.selected.set(new_selected);
}
}
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[self.selected.get()].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.set(i);
}
}
if button == MouseButton::Left {
let callback = self.submit_cb.clone();
let item = self.items[self.selected.get()].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[self.selected.get()];
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 <= self.selected.get() { continue; }
else {
let first_char = raw_label
.chars()
.next().unwrap()
.to_ascii_lowercase();
if lc == first_char {
self.selected.set(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 self.items.is_empty() { EventResult::Ignored }
else {
let callback = self.selected_cb.clone();
let item = self.items[self.selected.get()].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.get()].y_ofs);
Rect::from_size(loc, self.items[self.selected.get()].size)
}
}
}
impl<T: 'static> Default for AdvancedSelectView<T> {
fn default() -> Self { Self::new() }
}
#[derive(Clone)]
struct StyledItem<T: 'static> {
label: ButtonContent,
item: Rc<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: Rc::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);
}
}