use std::cmp::max;
use super::{
TitleButtonType,
TabContainer,
TabFocus,
TabIter,
TabIterMut,
ViewButton,
ButtonAction,
TitleButton
};
use crate::c_focus;
use cursive_core::{
Printer, Vec2, View,
With, Cursive,
align::HAlign,
direction::{Absolute, Direction},
event::{
AnyCb,
Event,
EventResult,
Key,
MouseButton,
MouseEvent
},
view::{
CannotFocus,
Scrollable,
Selector,
ViewNotFound,
Resizable,
Position
},
views::{
BoxedView,
Dialog,
OnEventView,
SelectView,
LayerPosition
}
};
use crossbeam_channel::{bounded, Receiver, Sender};
use rust_utils::chainable;
pub struct TabDialog {
tabs: TabContainer,
shown_title_buttons: Vec<TitleButton>,
title_buttons: Vec<TitleButton>,
title_align: HAlign,
buttons: Vec<ViewButton>,
button_align: HAlign,
focus: TabFocus,
id_snd: Sender<usize>,
id_rcv: Receiver<usize>
}
impl TabDialog {
#[must_use]
pub fn new() -> TabDialog {
let (id_snd, id_rcv) = bounded(1);
TabDialog {
tabs: TabContainer::new(),
title_buttons: vec![],
shown_title_buttons: vec![],
title_align: HAlign::Center,
buttons: vec![],
button_align: HAlign::Left,
focus: TabFocus::Content,
id_snd,
id_rcv
}
}
#[chainable]
pub fn add_button<F: Fn(&mut Cursive) + Send + Sync + 'static>(&mut self, text: &str, func: F) {
let new_button = ViewButton::from_fn(text, func);
if self.has_next_prev_buttons() {
let last_idx = self.buttons.len() - 2;
self.buttons.insert(last_idx, new_button);
}
else { self.buttons.push(new_button) }
}
#[chainable]
pub fn add_dismiss_button(&mut self, text: &str) {
let new_button = ViewButton::from_fn(text, |r| {
if r.screen().len() <= 1 { r.quit(); }
else { r.pop_layer(); }
});
if self.has_next_prev_buttons() {
let last_idx = self.buttons.len() - 2;
self.buttons.insert(last_idx, new_button);
}
else { self.buttons.push(new_button) }
}
#[chainable]
pub fn add_next_prev_buttons(&mut self, show: bool) {
if show {
self.buttons.insert(0, ViewButton::new("Previous", ButtonAction::Prev));
self.buttons.push(ViewButton::new("Next", ButtonAction::Next));
}
else if self.has_next_prev_buttons() {
self.buttons.remove(0);
self.buttons.pop();
}
}
#[chainable]
pub fn add_close_button(&mut self, show: bool) {
if show {
let new_button = ViewButton::new("Close", ButtonAction::Close);
if self.has_next_prev_buttons() {
let last_idx = self.buttons.len() - 2;
self.buttons.insert(last_idx, new_button);
}
else { self.buttons.push(new_button) }
}
else {
self.buttons.retain(|b| matches!(b.action, ButtonAction::Close));
}
}
#[chainable]
pub fn set_button_align(&mut self, align: HAlign) { self.button_align = align; }
#[chainable]
pub fn set_title_align(&mut self, align: HAlign) { self.title_align = align; }
pub fn clear_buttons(&mut self) { self.buttons.clear(); }
pub fn cur_id(&self) -> Option<usize> { self.tabs.cur_id() }
pub fn cur_view(&self) -> Option<&BoxedView> { self.tabs.cur_view() }
pub fn cur_view_mut(&mut self) -> Option<&mut BoxedView> { self.tabs.cur_view_mut() }
pub fn cur_title(&self) -> Option<&str> {
if let Some(id) = self.cur_id() {
for button in &self.title_buttons {
if let TitleButtonType::Tab(tab_id) = button.b_type {
if tab_id == id {
return Some(&button.label);
}
}
}
None
}
else { None }
}
pub fn views(&self) -> TabIter { self.tabs.views() }
pub fn views_mut(&mut self) -> TabIterMut { self.tabs.views_mut() }
pub fn set_cur_tab(&mut self, id: usize) { self.tabs.set_cur_tab(id); }
pub fn set_title(&mut self, id: usize, new_title: &str) {
for button in &mut self.title_buttons {
let upd_title = if let TitleButtonType::Tab(tab_id) = button.b_type { tab_id == id }
else { false };
if upd_title { button.set_label(new_title) }
}
}
pub fn add_tab<V: View>(&mut self, title: &str, view: V) -> usize {
let new_id = self.tabs.add_tab(view);
self.set_cur_tab(new_id);
self.title_buttons.push(TitleButton::new(title, new_id));
new_id
}
#[must_use]
pub fn tab<V: View>(mut self, title: &str, view: V) -> TabDialog {
self.add_tab(title, view);
self.set_cur_tab(0);
self
}
pub fn remove_tab(&mut self, id: usize) {
self.tabs.remove_tab(id);
self.title_buttons.retain(|t_bt| {
if let TitleButtonType::Tab(old_id) = t_bt.b_type {
old_id != id
}
else { true }
});
}
pub fn ids(&self) -> Vec<usize> { self.tabs.ids() }
fn has_next_prev_buttons(&self) -> bool {
matches!(self.buttons.last(), Some(ViewButton { action: ButtonAction::Next, ..}))
}
fn open_overflow(&self) -> EventResult {
let id_snd = self.id_snd.clone();
let mut tabs_menu = SelectView::new()
.on_submit(move |root, id| {
id_snd.send(*id).unwrap();
root.pop_layer();
});
for button in &self.title_buttons {
if let TitleButtonType::Tab(tab_id) = button.b_type {
tabs_menu.add_item(&button.label, tab_id);
}
}
let ofs = self.shown_title_buttons[3].rect.top_left() + (0, 1);
EventResult::with_cb_once(move |root| {
if root.fps().is_none() { root.set_fps(30); }
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(tabs_menu.scrollable())
.wrap_with(OnEventView::new)
.on_event(Event::Key(Key::Esc), |r| { r.pop_layer(); })
.max_height(20)
)
)
})
}
fn exec_button_action(&mut self, selected: usize) -> EventResult {
match &self.buttons[selected].action {
ButtonAction::Close => {
let tab_num = self.cur_id().unwrap();
if self.tabs.views().count() > 1 {
self.remove_tab(tab_num);
}
return EventResult::consumed();
}
ButtonAction::Next => self.tabs.next(),
ButtonAction::Prev => self.tabs.prev(),
ButtonAction::CallBack(cb) => return EventResult::Consumed(Some(cb.clone()))
}
EventResult::consumed()
}
fn button_event(&mut self, event: &Event) -> EventResult {
let num_title_buttons = self.shown_title_buttons.len();
let num_buttons = self.buttons.len();
match event {
Event::Key(Key::Enter) => {
match self.focus {
TabFocus::TitleBar(selected) => {
let button = &self.shown_title_buttons[selected];
return match button.b_type {
TitleButtonType::Tab(id) => {
self.set_cur_tab(id);
EventResult::consumed()
}
TitleButtonType::Overflow => self.open_overflow()
}
}
TabFocus::Buttons(selected) => return self.exec_button_action(selected),
TabFocus::Content => { }
}
}
Event::Key(Key::Down) => {
if matches!(self.focus, TabFocus::TitleBar(_)) {
self.focus = TabFocus::Content;
if let Ok(result) = self.tabs.take_focus(Direction::none()) {
return result.and(EventResult::consumed());
}
}
}
Event::Key(Key::Left) => {
match self.focus {
TabFocus::TitleBar(ref mut selected) => {
if *selected == 0 { *selected = num_title_buttons; }
*selected -= 1;
return EventResult::consumed();
}
TabFocus::Buttons(ref mut selected) => {
if *selected == 0 { *selected = num_buttons; }
*selected -= 1;
return EventResult::consumed();
}
TabFocus::Content => { }
}
}
Event::Key(Key::Right) => {
match self.focus {
TabFocus::TitleBar(ref mut selected) => {
*selected += 1;
if *selected >= num_title_buttons { *selected = 0; }
return EventResult::consumed();
}
TabFocus::Buttons(ref mut selected) => {
*selected += 1;
if *selected >= num_buttons { *selected = 0; }
return EventResult::consumed();
}
TabFocus::Content => { }
}
}
Event::Key(Key::End) => {
match self.focus {
TabFocus::TitleBar(ref mut selected) => {
*selected = num_title_buttons - 1;
return EventResult::consumed();
}
TabFocus::Buttons(ref mut selected) => {
*selected = num_buttons - 1;
return EventResult::consumed();
}
TabFocus::Content => { }
}
}
Event::Key(Key::Home) => {
match self.focus {
TabFocus::TitleBar(ref mut selected) | TabFocus::Buttons(ref mut selected) => {
*selected = 0;
return EventResult::consumed();
}
TabFocus::Content => { }
}
}
Event::Key(Key::Tab) => {
match self.focus {
TabFocus::TitleBar(_) => self.focus = TabFocus::Content,
TabFocus::Content => self.focus = TabFocus::Buttons(0),
TabFocus::Buttons(ref mut selected) => {
if *selected >= self.buttons.len() - 1 { return EventResult::Ignored }
*selected += 1;
}
}
return EventResult::consumed();
}
Event::Refresh => {
if let Ok(id) = self.id_rcv.try_recv() {
self.set_cur_tab(id);
self.tabs.set_as_first(id);
return EventResult::consumed();
}
}
Event::Key(Key::Up) => {
if matches!(self.focus, TabFocus::Buttons(_)) {
self.focus = TabFocus::Content;
return EventResult::consumed();
}
}
Event::FocusLost => self.focus = TabFocus::Content,
_ => { }
}
EventResult::Ignored
}
fn view_event(&mut self, event: &Event) -> EventResult {
match self.tabs.on_event(event.relativized((1, 1))) {
EventResult::Ignored => {
match event {
Event::Key(Key::Up) => {
self.focus = TabFocus::TitleBar(0);
return EventResult::consumed().and(self.tabs.on_event(Event::FocusLost));
}
Event::Key(Key::Down) => {
if !self.buttons.is_empty() {
self.focus = TabFocus::Buttons(0);
return EventResult::consumed().and(self.tabs.on_event(Event::FocusLost));
}
}
Event::Key(Key::Tab) => {
if matches!(self.focus, TabFocus::Content) {
self.focus = TabFocus::Buttons(0);
return EventResult::consumed();
}
}
Event::FocusLost => {
self.focus = TabFocus::Content;
return EventResult::consumed().and(self.tabs.on_event(Event::FocusLost));
}
_ => { }
}
EventResult::Ignored
}
res => res
}
}
fn mouse_event(&mut self, event: &Event) -> Option<EventResult> {
if let Event::Mouse {
offset, position,
event: MouseEvent::Release(button),
} = event {
let pos = position.checked_sub(offset)?;
for (i, t_button) in self.shown_title_buttons.iter().enumerate() {
if t_button.has_mouse_pos(pos) {
return if *button == MouseButton::Left {
let res = match t_button.b_type {
TitleButtonType::Tab(id) => {
self.set_cur_tab(id);
EventResult::consumed()
}
TitleButtonType::Overflow => self.open_overflow()
};
self.focus = TabFocus::TitleBar(i);
Some(res)
}
else {
self.focus = TabFocus::TitleBar(i);
Some(EventResult::consumed())
}
}
}
for (i, b_button) in self.buttons.iter().enumerate() {
if b_button.has_mouse_pos(pos) {
return if *button == MouseButton::Left {
self.focus = TabFocus::Buttons(i);
Some(self.exec_button_action(i))
}
else {
self.focus = TabFocus::Buttons(i);
Some(EventResult::consumed())
}
}
}
self.focus = TabFocus::Content;
}
None
}
}
impl View for TabDialog {
fn draw(&self, printer: &Printer) {
printer.print_box((0, 0), printer.size, false);
if !self.title_buttons.is_empty() {
let mut f_index: Option<usize> = None;
for (i, button) in self.shown_title_buttons.iter().enumerate() {
let selected = if let TabFocus::TitleBar(sel) = self.focus { i == sel && printer.focused }
else { false };
let focused = if let TitleButtonType::Tab(id) = button.b_type {
if let Some(cur_id) = self.cur_id() {
cur_id == id
}
else { false }
}
else { false };
if focused { f_index = Some(i); }
else {
button.draw(printer, selected, false, i == 0, i == self.shown_title_buttons.len() - 1);
}
}
if let Some(index) = f_index {
let selected = if let TabFocus::TitleBar(sel) = self.focus { index == sel && printer.focused }
else { false };
self.shown_title_buttons[index]
.draw(printer, selected, true, index == 0, index == self.shown_title_buttons.len() - 1);
}
}
let y = if self.buttons.is_empty() { printer.size.y - 1 }
else { printer.size.y - 2 };
let tabs_printer = printer
.cropped((printer.size.x - 1, y))
.offset((1, 1))
.focused(self.focus == TabFocus::Content);
self.tabs.draw(&tabs_printer);
if !self.buttons.is_empty() {
for (i, button) in self.buttons.iter().enumerate() {
let selected = if let TabFocus::Buttons(sel) = self.focus { i == sel && printer.focused }
else { false };
button.draw(printer, selected);
}
}
}
fn layout(&mut self, size: Vec2) {
let max_width = size.x.saturating_sub(2);
super::align_title_buttons(&mut self.shown_title_buttons, &self.title_buttons, self.title_align, max_width, true);
super::align_buttons(&mut self.buttons, self.button_align, size, true);
let new_size = Vec2::new(size.x.saturating_sub(2), size.y.saturating_sub(3));
self.tabs.layout(new_size);
}
fn take_focus(&mut self, source: Direction) -> Result<EventResult, CannotFocus> {
let focus_tab = |dialog: &mut Self, d: Direction| {
let result = dialog.tabs.take_focus(d);
let focus = if result.is_err() { TabFocus::TitleBar(0) }
else { TabFocus::Content };
dialog.focus = focus;
result
};
let mut result: Result<EventResult, CannotFocus> = Ok(EventResult::consumed());
match source {
Direction::Abs(Absolute::Down) => result = focus_tab(self, source),
Direction::Abs(Absolute::Left) | Direction::Abs(Absolute::Right) => {
if !matches!(self.focus, TabFocus::TitleBar(_)) {
result = focus_tab(self, source);
}
}
Direction::Abs(Absolute::Up) => self.focus = TabFocus::TitleBar(0),
_ => { }
}
Ok(result.unwrap_or(EventResult::Ignored))
}
fn on_event(&mut self, event: Event) -> EventResult {
self.mouse_event(&event).unwrap_or(
if self.focus == TabFocus::Content { self.view_event(&event) }
else { self.button_event(&event) }
)
}
fn required_size(&mut self, constraint: Vec2) -> Vec2 {
let tab_size = self.tabs.required_size(constraint.saturating_sub((0, 3)));
let mut buttons_len = 0;
for button in &self.buttons {
buttons_len += button.width();
}
let mut tbuttons_len = 0;
for tbutton in &self.shown_title_buttons {
tbuttons_len += tbutton.width();
}
let mut new_size = tab_size
.stack_vertical(&Vec2::new(max(buttons_len, tbuttons_len), 1 - self.buttons.is_empty() as usize))
+ (0, 2);
if new_size.y > constraint.y || new_size.x > constraint.x { new_size = constraint; }
new_size
}
fn needs_relayout(&self) -> bool { self.tabs.needs_relayout() }
fn focus_view(&mut self, sel: &Selector) -> Result<EventResult, ViewNotFound> { self.tabs.focus_view(sel) }
fn call_on_any(&mut self, sel: &Selector, cb: AnyCb) { self.tabs.call_on_any(sel, cb); }
}
impl Default for TabDialog {
fn default() -> TabDialog { TabDialog::new() }
}