cursive/src/menubar.rs

197 lines
6.0 KiB
Rust
Raw Normal View History

2016-07-10 02:05:51 +00:00
use Cursive;
use menu::MenuTree;
use backend::Backend;
use view::MenuPopup;
use view::KeyEventView;
use theme::ColorStyle;
2016-07-14 06:25:54 +00:00
use Printer;
use view::Position;
use event::*;
use std::rc::Rc;
2016-07-04 23:04:32 +00:00
use unicode_width::UnicodeWidthStr;
/// 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-07-20 03:28:34 +00:00
/// Shows a single-line list of items, with pop-up menus when one is selected.
///
/// The [`Cursive`] root already includes a menubar that you just needs to configure.
///
/// [`Cursive`]: struct.Cursive.html#method.menubar
pub struct Menubar {
2016-07-20 03:28:34 +00:00
/// Menu items in this menubar.
pub menus: Vec<(String, Rc<MenuTree>)>,
2016-07-20 03:28:34 +00:00
/// TODO: move this out of this view.
pub autohide: bool,
2016-07-20 03:28:34 +00:00
focus: usize,
// TODO: make Menubar impl View and take out the State management
state: State,
}
2016-07-17 00:28:42 +00:00
new_default!(Menubar);
impl Menubar {
2016-07-20 03:28:34 +00:00
/// Creates a new, empty menubar.
pub fn new() -> Self {
Menubar {
menus: Vec::new(),
autohide: true,
state: State::Inactive,
focus: 0,
}
}
2016-07-20 03:28:34 +00:00
/// Hides the menubar.
fn hide(&mut self) {
self.state = State::Inactive;
::B::clear();
}
2016-07-20 03:28:34 +00:00
/// Takes the focus.
///
/// TODO: impl View
pub fn take_focus(&mut self) {
self.state = State::Selected;
}
2016-07-20 03:28:34 +00:00
/// True if we should be receiving events.
pub fn receive_events(&self) -> bool {
self.state == State::Selected
}
2016-07-20 03:28:34 +00:00
/// Returns `true` if we should be drawn.
pub fn visible(&self) -> bool {
!self.autohide || self.state != State::Inactive
}
2016-07-20 03:28:34 +00:00
/// Adds a new item to the menubar.
///
/// The item will use the given title, and on selection, will open a
/// popup-menu with the given menu tree.
pub fn add(&mut self, title: &str, menu: MenuTree) -> &mut Self {
self.menus.push((title.to_string(), Rc::new(menu)));
self
}
2016-07-20 03:28:34 +00:00
/// Draws the view.
///
/// TODO: impl View
pub fn draw(&mut self, printer: &Printer) {
// Draw the bar at the top
printer.with_color(ColorStyle::Primary, |printer| {
printer.print_hline((0, 0), printer.size.x, " ");
});
// TODO: draw the rest
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-20 03:28:34 +00:00
/// Reacts to event.
///
/// TODO: impl View
pub fn on_event(&mut self, event: Event) -> Option<Callback> {
match event {
Event::Key(Key::Esc) => self.hide(),
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) |
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),
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.
return Some(Rc::new(move |s| {
2016-07-10 02:05:51 +00:00
show_child(s, offset, menu.clone())
}));
}
_ => (),
}
None
}
}
2016-07-10 02:05:51 +00:00
fn show_child(s: &mut Cursive, offset: (usize, usize), menu: Rc<MenuTree>) {
2016-07-20 03:28:34 +00:00
// Adds a new layer located near the item title with the menu popup.
// Also adds two key callbacks on this new view, to handle `left` and
// `right` key presses.
// (If the view itself listens for a `left` or `right` press, it will
// consume it before our KeyEventView. This means sub-menus can properly
// be entered.)
2016-07-10 02:05:51 +00:00
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();
s.select_menubar();
2016-07-20 03:28:34 +00:00
// Act as if we sent "Right" then "Down"
2016-07-10 02:05:51 +00:00
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();
s.select_menubar();
2016-07-20 03:28:34 +00:00
// Act as if we sent "Left" then "Down"
2016-07-10 02:05:51 +00:00
s.menubar().on_event(Event::Key(Key::Left));
if let Some(cb) = s.menubar()
.on_event(Event::Key(Key::Down)) {
cb(s);
}
}));
}