2016-07-10 02:05:51 +00:00
|
|
|
use Cursive;
|
2016-07-02 22:02:42 +00:00
|
|
|
use menu::MenuTree;
|
|
|
|
use view::MenuPopup;
|
2016-07-03 02:37:38 +00:00
|
|
|
use view::KeyEventView;
|
2016-07-01 06:38:01 +00:00
|
|
|
use theme::ColorStyle;
|
2016-06-28 04:59:42 +00:00
|
|
|
use printer::Printer;
|
2016-07-02 22:02:42 +00:00
|
|
|
use view::Position;
|
2016-06-28 04:59:42 +00:00
|
|
|
use event::*;
|
|
|
|
|
|
|
|
use std::rc::Rc;
|
|
|
|
|
2016-07-04 23:04:32 +00:00
|
|
|
use unicode_width::UnicodeWidthStr;
|
|
|
|
|
2016-07-02 22:02:42 +00:00
|
|
|
/// Current state of the menubar
|
|
|
|
#[derive(PartialEq, Debug)]
|
|
|
|
enum State {
|
|
|
|
/// The menubar is inactive.
|
|
|
|
Inactive,
|
|
|
|
/// The menubar is actively selected.
|
|
|
|
///
|
|
|
|
/// It will receive input.
|
|
|
|
Selected,
|
|
|
|
/// The menubar is still visible, but a submenu is open.
|
|
|
|
///
|
|
|
|
/// It will not receive input.
|
|
|
|
Submenu,
|
|
|
|
}
|
|
|
|
|
2016-06-28 04:59:42 +00:00
|
|
|
pub struct Menubar {
|
2016-07-02 22:02:42 +00:00
|
|
|
pub menus: Vec<(String, Rc<MenuTree>)>,
|
2016-06-28 04:59:42 +00:00
|
|
|
pub autohide: bool,
|
2016-07-02 22:02:42 +00:00
|
|
|
pub focus: usize,
|
|
|
|
state: State,
|
2016-06-28 04:59:42 +00:00
|
|
|
}
|
|
|
|
|
2016-07-03 02:47:11 +00:00
|
|
|
impl Default for Menubar {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-28 04:59:42 +00:00
|
|
|
impl Menubar {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Menubar {
|
2016-07-02 22:02:42 +00:00
|
|
|
menus: Vec::new(),
|
2016-06-28 04:59:42 +00:00
|
|
|
autohide: true,
|
2016-07-02 22:02:42 +00:00
|
|
|
state: State::Inactive,
|
|
|
|
focus: 0,
|
2016-06-28 04:59:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-02 22:02:42 +00:00
|
|
|
pub fn take_focus(&mut self) {
|
|
|
|
self.state = State::Selected;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn receive_events(&self) -> bool {
|
|
|
|
self.state == State::Selected
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn visible(&self) -> bool {
|
|
|
|
!self.autohide || self.state != State::Inactive
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add(&mut self, title: &str, menu: MenuTree) -> &mut Self {
|
|
|
|
self.menus.push((title.to_string(), Rc::new(menu)));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2016-06-28 04:59:42 +00:00
|
|
|
pub fn draw(&mut self, printer: &Printer) {
|
|
|
|
// Draw the bar at the top
|
2016-07-01 06:38:01 +00:00
|
|
|
printer.with_color(ColorStyle::Primary, |printer| {
|
2016-06-28 04:59:42 +00:00
|
|
|
printer.print_hline((0, 0), printer.size.x, " ");
|
|
|
|
});
|
|
|
|
|
|
|
|
// TODO: draw the rest
|
2016-07-02 22:02:42 +00:00
|
|
|
let mut offset = 1;
|
|
|
|
for (i, &(ref title, _)) in self.menus.iter().enumerate() {
|
|
|
|
// We don't want to show HighlightInactive when we're not selected,
|
|
|
|
// because it's ugly on the menubar.
|
|
|
|
let selected = (self.state != State::Inactive) &&
|
|
|
|
(i == self.focus);
|
|
|
|
printer.with_selection(selected, |printer| {
|
|
|
|
printer.print((offset, 0), &format!(" {} ", title));
|
2016-07-04 23:04:32 +00:00
|
|
|
offset += title.width() + 2;
|
2016-07-02 22:02:42 +00:00
|
|
|
});
|
|
|
|
}
|
2016-06-28 04:59:42 +00:00
|
|
|
}
|
|
|
|
|
2016-07-02 22:02:42 +00:00
|
|
|
pub fn on_event(&mut self, event: Event) -> Option<Callback> {
|
|
|
|
match event {
|
|
|
|
Event::Key(Key::Esc) => self.state = State::Inactive,
|
2016-07-03 02:37:38 +00:00
|
|
|
Event::Key(Key::Left) => {
|
|
|
|
if self.focus > 0 {
|
|
|
|
self.focus -= 1
|
|
|
|
} else {
|
|
|
|
self.focus = self.menus.len() - 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Event::Key(Key::Right) => {
|
|
|
|
if self.focus + 1 < self.menus.len() {
|
|
|
|
self.focus += 1
|
|
|
|
} else {
|
|
|
|
self.focus = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Event::Key(Key::Down) |
|
2016-07-02 22:02:42 +00:00
|
|
|
Event::Key(Key::Enter) => {
|
|
|
|
// First, we need a new Rc to send the callback,
|
|
|
|
// since we don't know when it will be called.
|
|
|
|
let menu = self.menus[self.focus].1.clone();
|
|
|
|
self.state = State::Submenu;
|
|
|
|
let offset = (self.menus[..self.focus]
|
2016-07-10 02:05:51 +00:00
|
|
|
.iter()
|
|
|
|
.map(|&(ref title, _)| title.width() + 2)
|
|
|
|
.fold(0, |a, b| a + b),
|
2016-07-02 22:02:42 +00:00
|
|
|
if self.autohide {
|
|
|
|
1
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
});
|
2016-07-10 02:05:51 +00:00
|
|
|
// Since the closure will be called multiple times,
|
|
|
|
// we also need a new Rc on every call.
|
2016-07-02 22:02:42 +00:00
|
|
|
return Some(Rc::new(move |s| {
|
2016-07-10 02:05:51 +00:00
|
|
|
show_child(s, offset, menu.clone())
|
2016-07-02 22:02:42 +00:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
_ => (),
|
|
|
|
}
|
2016-06-28 04:59:42 +00:00
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
2016-07-10 02:05:51 +00:00
|
|
|
|
|
|
|
fn show_child(s: &mut Cursive, offset: (usize, usize), menu: Rc<MenuTree>) {
|
|
|
|
s.screen_mut()
|
|
|
|
.add_layer_at(Position::absolute(offset),
|
|
|
|
KeyEventView::new(MenuPopup::new(menu)
|
|
|
|
.on_dismiss(|s| s.select_menubar())
|
|
|
|
.on_action(|s| {
|
|
|
|
s.menubar().state = State::Inactive
|
|
|
|
}))
|
|
|
|
.register(Key::Right, |s| {
|
|
|
|
s.pop_layer();
|
|
|
|
// Act as if we sent "Left" then "Enter"
|
|
|
|
s.select_menubar();
|
|
|
|
s.menubar().on_event(Event::Key(Key::Right));
|
|
|
|
if let Some(cb) = s.menubar()
|
|
|
|
.on_event(Event::Key(Key::Down)) {
|
|
|
|
cb(s);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.register(Key::Left, |s| {
|
|
|
|
s.pop_layer();
|
|
|
|
// Act as if we sent "Left" then "Enter"
|
|
|
|
s.select_menubar();
|
|
|
|
s.menubar().on_event(Event::Key(Key::Left));
|
|
|
|
if let Some(cb) = s.menubar()
|
|
|
|
.on_event(Event::Key(Key::Down)) {
|
|
|
|
cb(s);
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
|
|
|
}
|