extern crate cursive_core as cursive;
use crossbeam::channel::{Receiver, Sender};
use cursive::direction::Direction;
use cursive::event::{AnyCb, Event, EventResult};
use cursive::view::{CannotFocus, Selector, View, ViewNotFound};
use cursive::views::NamedView;
use cursive::{Printer, Rect, Vec2};
use log::debug;
use std::collections::HashMap;
mod bar;
mod error;
mod panel;
use bar::{Bar, TabBar};
pub use panel::{Align, Placement, TabPanel};
pub struct TabView {
current_id: Option<String>,
map: HashMap<String, Box<dyn View>>,
key_order: Vec<String>,
bar_rx: Option<Receiver<String>>,
active_key_tx: Option<Sender<String>>,
invalidated: bool,
}
impl Default for TabView {
fn default() -> Self {
Self::new()
}
}
impl TabView {
pub fn new() -> Self {
Self {
current_id: None,
map: HashMap::new(),
key_order: Vec::new(),
bar_rx: None,
active_key_tx: None,
invalidated: true,
}
}
pub fn active_tab(&self) -> Option<&str> {
self.current_id.as_deref()
}
pub fn active_view(&self) -> Option<&dyn View> {
self.active_tab()
.and_then(|k| self.map.get(k).map(|v| &**v))
}
pub fn active_view_mut(&mut self) -> Option<&mut dyn View> {
if let Some(k) = self.current_id.as_ref() {
self.map.get_mut(k).map(|v| &mut **v)
} else {
None
}
}
pub fn views(&self) -> Vec<&dyn View> {
self.map.values().map(|v| &**v).collect()
}
pub fn views_mut(&mut self) -> Vec<&mut dyn View> {
self.map.values_mut().map(|v| &mut **v).collect()
}
pub fn set_active_tab(&mut self, id: &str) -> Result<(), error::IdNotFound> {
if self.map.contains_key(id) {
if let Some(sender) = &self.active_key_tx {
match sender.send(id.to_owned()) {
Ok(_) => {}
Err(e) => debug!(
"error occured while trying to send new active key to sender: {}",
e
),
}
}
self.current_id = Some(id.to_owned());
self.invalidated = true;
Ok(())
} else {
Err(error::IdNotFound { id: id.to_owned() })
}
}
pub fn with_active_tab(mut self, id: &str) -> Result<Self, Self> {
match self.set_active_tab(id) {
Ok(_) => Ok(self),
Err(_) => Err(self),
}
}
pub fn add_tab<T: View>(&mut self, view: NamedView<T>) {
let id = view.name().to_owned();
self.map.insert(id.clone(), Box::new(view));
self.key_order.push(id.clone());
self.current_id = Some(id);
}
pub fn with_tab<T: View>(mut self, view: NamedView<T>) -> Self {
self.add_tab(view);
self
}
pub fn add_tab_at<T: View>(&mut self, view: NamedView<T>, pos: usize) {
let id = view.name().to_owned();
self.map.insert(id.clone(), Box::new(view));
if let Some(sender) = &self.active_key_tx {
match sender.send(id.clone()) {
Ok(_) => {}
Err(send_err) => debug!(
"Could not send new key to receiver in TabBar, has it been dropped? {}",
send_err
),
}
}
self.current_id = Some(id.clone());
if self.key_order.len() > pos {
self.key_order.insert(pos, id)
} else {
self.key_order.push(id);
}
}
pub fn with_tab_at<T: View>(mut self, view: NamedView<T>, pos: usize) -> Self {
self.add_tab_at(view, pos);
self
}
pub fn swap_tabs(&mut self, fst: &str, snd: &str) {
let mut fst_pos: Option<usize> = None;
let mut snd_pos: Option<usize> = None;
for (pos, key) in self.tab_order().into_iter().enumerate() {
match key {
val if val == *fst => fst_pos = Some(pos),
val if val == *snd => snd_pos = Some(pos),
_ => {}
}
}
if let (Some(fst_pos), Some(snd_pos)) = (fst_pos, snd_pos) {
if let Some(cur) = self.current_id.as_ref() {
if self.active_key_tx.is_some() && (fst == cur || snd == cur) {
self.active_key_tx
.as_mut()
.unwrap()
.send(cur.to_owned())
.expect("Sending failed.");
}
}
self.key_order.swap(fst_pos, snd_pos);
}
}
pub fn remove_tab(&mut self, id: &str) -> Result<(), error::IdNotFound> {
if self.map.remove(id).is_some() {
if let Some(key) = &self.current_id {
if key == id {
self.current_id = None;
}
}
self.key_order.retain(|k| k != id);
self.invalidated = true;
Ok(())
} else {
Err(error::IdNotFound { id: id.to_owned() })
}
}
pub fn tab_order(&self) -> Vec<String> {
self.key_order.clone()
}
fn index_key(cur_key: &str, key_order: &[String]) -> usize {
for (idx, key) in key_order.iter().enumerate() {
if *key == *cur_key {
return idx;
}
}
key_order.len()
}
pub fn next(&mut self) {
if let Some(cur_key) = &self.current_id {
let idx = (Self::index_key(&cur_key, &self.key_order) + 1) % self.key_order.len();
let key = &self.key_order[idx].clone();
self.set_active_tab(key)
.expect("Key content changed during operation, this should not happen");
}
}
pub fn prev(&mut self) {
if let Some(cur_key) = self.current_id.as_ref().cloned() {
let idx_key = Self::index_key(&cur_key, &self.key_order);
let idx = (self.key_order.len() + idx_key - 1) % self.key_order.len();
let key = &self.key_order[idx].clone();
self.set_active_tab(key)
.expect("Key content changed during operation, this should not happen");
}
}
pub fn set_bar_rx(&mut self, rx: Receiver<String>) {
self.bar_rx = Some(rx);
}
pub fn set_active_key_tx(&mut self, tx: Sender<String>) {
self.active_key_tx = Some(tx);
}
}
impl View for TabView {
fn draw(&self, printer: &Printer) {
if let Some(key) = &self.current_id {
if let Some(view) = self.map.get(key) {
view.draw(printer);
}
}
}
fn layout(&mut self, size: Vec2) {
self.invalidated = false;
if let Some(key) = &self.current_id {
if let Some(view) = self.map.get_mut(key) {
view.layout(size);
}
}
}
fn required_size(&mut self, req: Vec2) -> Vec2 {
if let Some(rx) = &self.bar_rx {
if let Ok(evt) = rx.try_recv() {
match self.set_active_tab(&evt) {
Ok(_) => {}
Err(err) => debug!("could not accept tab bar event: {:?}", err),
}
}
}
if let Some(key) = &self.current_id {
if let Some(view) = self.map.get_mut(key) {
view.required_size(req)
} else {
(1, 1).into()
}
} else {
(1, 1).into()
}
}
fn on_event(&mut self, evt: Event) -> EventResult {
if let Some(key) = &self.current_id {
if let Some(view) = self.map.get_mut(key) {
view.on_event(evt)
} else {
EventResult::Ignored
}
} else {
EventResult::Ignored
}
}
fn take_focus(&mut self, src: Direction) -> Result<EventResult, CannotFocus> {
if let Some(key) = &self.current_id {
if let Some(view) = self.map.get_mut(key) {
view.take_focus(src)
} else {
Err(CannotFocus)
}
} else {
Err(CannotFocus)
}
}
fn call_on_any<'a>(&mut self, slt: &Selector, cb: AnyCb<'a>) {
for (_, view) in self.map.iter_mut() {
view.call_on_any(slt, cb);
}
}
fn focus_view(&mut self, slt: &Selector) -> Result<EventResult, ViewNotFound> {
if let Some(key) = &self.current_id {
if let Some(view) = self.map.get_mut(key) {
view.focus_view(slt)
} else {
Err(ViewNotFound)
}
} else {
Err(ViewNotFound)
}
}
fn needs_relayout(&self) -> bool {
self.invalidated || {
if let Some(key) = &self.current_id {
if let Some(view) = self.map.get(key) {
view.needs_relayout()
} else {
false
}
} else {
false
}
}
}
fn important_area(&self, size: Vec2) -> Rect {
if let Some(key) = &self.current_id {
if let Some(view) = self.map.get(key) {
view.important_area(size)
} else {
Rect::from_point((1, 1))
}
} else {
Rect::from_point((1, 1))
}
}
}
#[cfg(test)]
mod test {
use super::TabView;
use cursive::{traits::Nameable, views::DummyView};
#[test]
fn smoke() {
let _ = TabView::new();
}
#[test]
fn insert() {
let mut tabs = TabView::new().with_tab(DummyView {}.with_name("0"));
tabs.add_tab(DummyView {}.with_name("1"));
}
#[test]
fn switch() {
let mut tabs = TabView::new();
tabs.add_tab(DummyView {}.with_name("0"));
tabs.add_tab(DummyView {}.with_name("1"));
assert_eq!(tabs.active_tab().expect("Id not correct"), "1");
tabs.set_active_tab("0").expect("Id not taken");
assert_eq!(tabs.active_tab().expect("Id not correct"), "0");
}
#[test]
fn remove() {
let mut tabs = TabView::new();
tabs.add_tab(DummyView {}.with_name("0"));
tabs.add_tab(DummyView {}.with_name("1"));
assert_eq!(tabs.remove_tab("1"), Ok(()));
assert!(tabs.active_tab().is_none());
}
}