#![deny(
missing_docs,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unused_import_braces,
unused_qualifications
)]
extern crate cursive_core as cursive;
#[macro_use]
extern crate debug_stub_derive;
use std::cmp;
use std::fmt::{Debug, Display};
use std::sync::{Arc, Mutex};
use cursive::direction::Direction;
use cursive::event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent};
use cursive::theme::ColorStyle;
use cursive::vec::Vec2;
use cursive::view::{CannotFocus, View};
use cursive::{Cursive, Printer};
use cursive::{Rect, With};
mod tree_list;
pub use tree_list::Placement;
use tree_list::TreeList;
type IndexCallback = Arc<dyn Fn(&mut Cursive, usize) + Send + Sync>;
type CollapseCallback = Arc<dyn Fn(&mut Cursive, usize, bool, usize) + Send + Sync>;
#[derive(DebugStub)]
pub struct TreeView<T: Display + Debug> {
enabled: bool,
#[debug_stub(some = "Arc<Fn(&mut Cursive, usize)")]
on_submit: Option<IndexCallback>,
#[debug_stub(some = "Arc<Fn(&mut Cursive, usize)")]
on_select: Option<IndexCallback>,
#[debug_stub(some = "Arc<Fn(&mut Cursive, usize, bool, usize)>")]
on_collapse: Option<CollapseCallback>,
last_size: Vec2,
focus: usize,
list: TreeList<T>,
}
const SYMBOL_WIDTH: usize = 2;
impl<T: Display + Debug + Send + Sync> Default for TreeView<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Display + Debug + Send + Sync> TreeView<T> {
pub fn new() -> Self {
Self {
enabled: true,
on_submit: None,
on_select: None,
on_collapse: None,
last_size: (0, 0).into(),
focus: 0,
list: TreeList::new(),
}
}
pub fn disable(&mut self) {
self.enabled = false;
}
pub fn enable(&mut self) {
self.enabled = true;
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn set_on_submit<F>(&mut self, cb: F)
where
F: Fn(&mut Cursive, usize) + Send + Sync + 'static,
{
self.on_submit = Some(Arc::new(move |s, row| cb(s, row)));
}
pub fn on_submit<F>(self, cb: F) -> Self
where
F: Fn(&mut Cursive, usize) + Send + Sync + 'static,
{
self.with(|t| t.set_on_submit(cb))
}
pub fn set_on_select<F>(&mut self, cb: F)
where
F: Fn(&mut Cursive, usize) + Send + Sync + 'static,
{
self.on_select = Some(Arc::new(move |s, row| cb(s, row)));
}
pub fn on_select<F>(self, cb: F) -> Self
where
F: Fn(&mut Cursive, usize) + Send + Sync + 'static,
{
self.with(|t| t.set_on_select(cb))
}
pub fn set_on_collapse<F>(&mut self, cb: F)
where
F: Fn(&mut Cursive, usize, bool, usize) + Send + Sync + 'static,
{
self.on_collapse = Some(Arc::new(move |s, row, collapsed, children| {
cb(s, row, collapsed, children)
}));
}
pub fn on_collapse<F>(self, cb: F) -> Self
where
F: Fn(&mut Cursive, usize, bool, usize) + Send + Sync + 'static,
{
self.with(|t| t.set_on_collapse(cb))
}
pub fn clear(&mut self) {
self.list.clear();
self.focus = 0;
}
pub fn take_items(&mut self) -> Vec<T> {
let items = self.list.take_items();
self.focus = 0;
items
}
pub fn len(&self) -> usize {
self.list.len()
}
pub fn is_empty(&self) -> bool {
self.list.is_empty()
}
pub fn row(&self) -> Option<usize> {
if self.is_empty() {
None
} else {
Some(self.focus)
}
}
pub fn first_col(&self, row: usize) -> Option<usize> {
let index = self.list.row_to_item_index(row);
self.list.first_col(index)
}
pub fn item_width(&self, row: usize) -> Option<usize> {
let index = self.list.row_to_item_index(row);
self.list.width(index).map(|width| width + SYMBOL_WIDTH)
}
pub fn set_selected_row(&mut self, row: usize) {
self.focus = row;
}
pub fn selected_row(self, row: usize) -> Self {
self.with(|t| t.set_selected_row(row))
}
pub fn borrow_item(&self, row: usize) -> Option<&T> {
let index = self.list.row_to_item_index(row);
self.list.get(index)
}
pub fn borrow_item_mut(&mut self, row: usize) -> Option<&mut T> {
let index = self.list.row_to_item_index(row);
self.list.get_mut(index)
}
pub fn insert_item(&mut self, item: T, placement: Placement, row: usize) -> Option<usize> {
let index = self.list.row_to_item_index(row);
self.list.insert_item(placement, index, item)
}
pub fn insert_container_item(
&mut self,
item: T,
placement: Placement,
row: usize,
) -> Option<usize> {
let index = self.list.row_to_item_index(row);
self.list.insert_container_item(placement, index, item)
}
pub fn remove_item(&mut self, row: usize) -> Option<Vec<T>> {
let index = self.list.row_to_item_index(row);
let removed = self.list.remove_with_children(index);
self.focus = cmp::min(self.focus, self.list.height() - 1);
removed
}
pub fn remove_children(&mut self, row: usize) -> Option<Vec<T>> {
let index = self.list.row_to_item_index(row);
let removed = self.list.remove_children(index);
self.focus = cmp::min(self.focus, self.list.height() - 1);
removed
}
pub fn extract_item(&mut self, row: usize) -> Option<T> {
let index = self.list.row_to_item_index(row);
let removed = self.list.remove(index);
self.focus = cmp::min(self.focus, self.list.height() - 1);
removed
}
pub fn collapse_item(&mut self, row: usize) {
let index = self.list.row_to_item_index(row);
self.list.set_collapsed(index, true);
}
pub fn expand_item(&mut self, row: usize) {
let index = self.list.row_to_item_index(row);
self.list.set_collapsed(index, false);
}
pub fn set_collapsed(&mut self, row: usize, collapsed: bool) {
let index = self.list.row_to_item_index(row);
self.list.set_collapsed(index, collapsed);
}
pub fn collapsed(self, row: usize, collapsed: bool) -> Self {
self.with(|t| t.set_collapsed(row, collapsed))
}
pub fn focus_up(&mut self, n: usize) {
self.focus -= cmp::min(self.focus, n);
}
pub fn focus_down(&mut self, n: usize) {
self.focus = cmp::min(self.focus + n, self.list.height() - 1);
}
pub fn item_parent(&self, row: usize) -> Option<usize> {
let item_index = self.list.row_to_item_index(row);
let parent_index = self.list.item_parent_index(item_index)?;
Some(self.list.item_index_to_row(parent_index))
}
fn submit(&mut self) -> EventResult {
let row = self.focus;
let index = self.list.row_to_item_index(row);
if self.list.is_container_item(index) {
let collapsed = self.list.get_collapsed(index);
let children = self.list.get_children(index);
self.list.set_collapsed(index, !collapsed);
if self.on_collapse.is_some() {
let cb = self.on_collapse.clone().unwrap();
return EventResult::Consumed(Some(Callback::from_fn(move |s| {
cb(s, row, !collapsed, children)
})));
}
} else if self.on_submit.is_some() {
let cb = self.on_submit.clone().unwrap();
return EventResult::Consumed(Some(Callback::from_fn(move |s| cb(s, row))));
}
EventResult::Ignored
}
}
impl<T: Display + Send + Sync + Debug + 'static> View for TreeView<T> {
fn draw(&self, printer: &Printer<'_, '_>) {
let index = self.list.row_to_item_index(0);
let items = self.list.items();
let list_index = Arc::new(Mutex::new(index));
for i in 0..self.list.height() {
let printer = printer.offset((0, i));
let mut index = list_index.lock().unwrap();
let item = &items[*index];
*index += item.len();
let color = if i == self.focus {
if self.enabled && printer.focused {
ColorStyle::highlight()
} else {
ColorStyle::highlight_inactive()
}
} else {
ColorStyle::primary()
};
printer.print((item.offset(), 0), item.symbol());
printer.with_color(color, |printer| {
printer.print(
(item.offset() + SYMBOL_WIDTH, 0),
format!("{}", item.value()).as_str(),
);
});
}
}
fn required_size(&mut self, _req: Vec2) -> Vec2 {
let w: usize = self
.list
.items()
.iter()
.map(|item| item.level() * 2 + format!("{}", item.value()).len() + 2)
.max()
.unwrap_or(0);
let h = self.list.height();
(w, h).into()
}
fn layout(&mut self, size: Vec2) {
self.last_size = size;
}
fn take_focus(&mut self, _: Direction) -> Result<EventResult, CannotFocus> {
(self.enabled && !self.is_empty())
.then(EventResult::consumed)
.ok_or(CannotFocus)
}
fn on_event(&mut self, event: Event) -> EventResult {
if !self.enabled {
return EventResult::Ignored;
}
let last_focus = self.focus;
match event {
Event::Key(Key::Up) if self.focus > 0 => {
self.focus_up(1);
}
Event::Key(Key::Down) if self.focus + 1 < self.list.height() => {
self.focus_down(1);
}
Event::Key(Key::PageUp) => {
self.focus_up(10);
}
Event::Key(Key::PageDown) => {
self.focus_down(10);
}
Event::Key(Key::Home) => {
self.focus = 0;
}
Event::Key(Key::End) => {
self.focus = self.list.height() - 1;
}
Event::Key(Key::Enter) => {
if !self.is_empty() {
return self.submit();
}
}
Event::Mouse {
position,
offset,
event: MouseEvent::Press(btn),
} => {
if let Some(position) = position.checked_sub(offset) {
match position.y {
y if y == self.focus && btn == MouseButton::Left => return self.submit(),
y if y < self.list.height() => self.focus = position.y,
_ => return EventResult::Ignored,
}
}
}
_ => return EventResult::Ignored,
}
let focus = self.focus;
if !self.is_empty() && last_focus != focus {
let row = self.focus;
EventResult::Consumed(
self.on_select
.clone()
.map(|cb| Callback::from_fn(move |s| cb(s, row))),
)
} else {
EventResult::Ignored
}
}
fn important_area(&self, size: Vec2) -> Rect {
Rect::from_size((0, self.focus), (size.x, 1))
}
}