2016-07-10 02:05:51 +00:00
|
|
|
use Cursive;
|
2016-07-14 06:25:54 +00:00
|
|
|
use Printer;
|
2016-10-02 22:22:29 +00:00
|
|
|
use direction;
|
2016-06-28 04:59:42 +00:00
|
|
|
use event::*;
|
2016-10-02 22:22:29 +00:00
|
|
|
use menu::MenuTree;
|
2016-06-28 04:59:42 +00:00
|
|
|
use std::rc::Rc;
|
2017-01-23 23:42:36 +00:00
|
|
|
use theme::ColorStyle;
|
2016-07-04 23:04:32 +00:00
|
|
|
use unicode_width::UnicodeWidthStr;
|
2016-10-02 22:22:29 +00:00
|
|
|
use vec::Vec2;
|
|
|
|
use view::{Position, View};
|
2017-10-12 23:38:55 +00:00
|
|
|
use views::{MenuPopup, OnEventView};
|
2016-07-04 23:04:32 +00:00
|
|
|
|
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-07-20 03:28:34 +00:00
|
|
|
/// Shows a single-line list of items, with pop-up menus when one is selected.
|
|
|
|
///
|
2016-07-21 04:25:14 +00:00
|
|
|
/// The [`Cursive`] root already includes a menubar
|
|
|
|
/// that you just need to configure.
|
2016-07-20 03:28:34 +00:00
|
|
|
///
|
2016-07-20 03:50:21 +00:00
|
|
|
/// [`Cursive`]: ../struct.Cursive.html#method.menubar
|
2016-06-28 04:59:42 +00:00
|
|
|
pub struct Menubar {
|
2016-07-20 03:28:34 +00:00
|
|
|
/// Menu items in this menubar.
|
2017-01-23 23:42:36 +00:00
|
|
|
menus: Vec<(String, Rc<MenuTree>)>,
|
2017-10-12 23:42:41 +00:00
|
|
|
|
2016-07-20 03:28:34 +00:00
|
|
|
/// TODO: move this out of this view.
|
2016-06-28 04:59:42 +00:00
|
|
|
pub autohide: bool,
|
2016-07-20 03:28:34 +00:00
|
|
|
focus: usize,
|
|
|
|
|
|
|
|
// TODO: make Menubar impl View and take out the State management
|
2016-07-02 22:02:42 +00:00
|
|
|
state: State,
|
2016-06-28 04:59:42 +00:00
|
|
|
}
|
|
|
|
|
2016-07-17 00:28:42 +00:00
|
|
|
new_default!(Menubar);
|
2016-07-03 02:47:11 +00:00
|
|
|
|
2016-06-28 04:59:42 +00:00
|
|
|
impl Menubar {
|
2016-07-20 03:28:34 +00:00
|
|
|
/// Creates a new, empty menubar.
|
2016-06-28 04:59:42 +00:00
|
|
|
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-20 03:28:34 +00:00
|
|
|
/// Hides the menubar.
|
2016-07-16 08:07:37 +00:00
|
|
|
fn hide(&mut self) {
|
|
|
|
self.state = State::Inactive;
|
|
|
|
}
|
|
|
|
|
2016-07-20 03:28:34 +00:00
|
|
|
/// True if we should be receiving events.
|
2016-07-02 22:02:42 +00:00
|
|
|
pub fn receive_events(&self) -> bool {
|
|
|
|
self.state == State::Selected
|
|
|
|
}
|
|
|
|
|
2017-10-13 22:17:41 +00:00
|
|
|
/// True if some submenus are visible.
|
|
|
|
pub fn has_submenu(&self) -> bool {
|
|
|
|
self.state == State::Submenu
|
|
|
|
}
|
|
|
|
|
2016-07-20 03:28:34 +00:00
|
|
|
/// Returns `true` if we should be drawn.
|
2016-07-02 22:02:42 +00:00
|
|
|
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.
|
2017-01-24 00:51:48 +00:00
|
|
|
pub fn add_subtree(&mut self, title: &str, menu: MenuTree) -> &mut Self {
|
2017-01-23 23:42:36 +00:00
|
|
|
let i = self.menus.len();
|
2017-01-24 00:51:48 +00:00
|
|
|
self.insert_subtree(i, title, menu)
|
2017-01-23 23:42:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Insert a new item at the given position.
|
2017-10-12 23:38:55 +00:00
|
|
|
pub fn insert_subtree(
|
|
|
|
&mut self, i: usize, title: &str, menu: MenuTree
|
|
|
|
) -> &mut Self {
|
2017-01-23 23:42:36 +00:00
|
|
|
self.menus.insert(i, (title.to_string(), Rc::new(menu)));
|
2016-07-02 22:02:42 +00:00
|
|
|
self
|
|
|
|
}
|
2017-01-23 23:42:36 +00:00
|
|
|
|
|
|
|
/// Removes all menu items from this menubar.
|
|
|
|
pub fn clear(&mut self) {
|
|
|
|
self.menus.clear();
|
|
|
|
self.focus = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the number of items in this menubar.
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
self.menus.len()
|
|
|
|
}
|
|
|
|
|
2017-01-27 22:51:46 +00:00
|
|
|
/// Returns `true` if this menubar is empty.
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
self.menus.is_empty()
|
|
|
|
}
|
|
|
|
|
2017-01-23 23:42:36 +00:00
|
|
|
/// Returns the item at the given position.
|
|
|
|
///
|
|
|
|
/// Returns `None` if `i > self.len()`
|
2017-01-24 00:51:48 +00:00
|
|
|
pub fn get_subtree(&mut self, i: usize) -> Option<&mut MenuTree> {
|
2017-10-12 23:38:55 +00:00
|
|
|
self.menus
|
|
|
|
.get_mut(i)
|
|
|
|
.map(|&mut (_, ref mut tree)| Rc::make_mut(tree))
|
2017-01-23 23:42:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Looks for an item with the given label.
|
2017-01-24 00:51:48 +00:00
|
|
|
pub fn find_subtree(&mut self, label: &str) -> Option<&mut MenuTree> {
|
2017-01-23 23:42:36 +00:00
|
|
|
// Look for the menu with the correct label,
|
|
|
|
// then call Rc::make_mut on the tree.
|
|
|
|
// If another Rc on this tree existed, this will clone
|
|
|
|
// the tree and keep the forked version.
|
|
|
|
self.menus
|
|
|
|
.iter_mut()
|
|
|
|
.find(|&&mut (ref l, _)| l == label)
|
|
|
|
.map(|&mut (_, ref mut tree)| Rc::make_mut(tree))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the position of the item with the given label.
|
|
|
|
///
|
|
|
|
/// Returns `None` if no such label was found.
|
|
|
|
pub fn find_position(&mut self, label: &str) -> Option<usize> {
|
2017-06-12 23:39:12 +00:00
|
|
|
self.menus.iter().position(|&(ref l, _)| l == label)
|
2017-01-23 23:42:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Remove the item at the given position.
|
|
|
|
pub fn remove(&mut self, i: usize) {
|
|
|
|
self.menus.remove(i);
|
|
|
|
}
|
2017-10-12 23:42:41 +00:00
|
|
|
|
|
|
|
fn child_at(&self, x: usize) -> Option<usize> {
|
|
|
|
if x == 0 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
let mut offset = 1;
|
|
|
|
for (i, &(ref title, _)) in self.menus.iter().enumerate() {
|
|
|
|
offset += title.width() + 2;
|
|
|
|
|
|
|
|
if x < offset {
|
|
|
|
return Some(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
fn select_child(&mut self) -> EventResult {
|
|
|
|
// First, we need a new Rc to send the callback,
|
|
|
|
// since we don't know when it will be called.
|
2017-10-13 18:22:02 +00:00
|
|
|
let menu = Rc::clone(&self.menus[self.focus].1);
|
2017-10-12 23:42:41 +00:00
|
|
|
self.state = State::Submenu;
|
|
|
|
let offset = (
|
|
|
|
self.menus[..self.focus]
|
|
|
|
.iter()
|
|
|
|
.map(|&(ref title, _)| title.width() + 2)
|
|
|
|
.fold(0, |a, b| a + b),
|
|
|
|
if self.autohide { 1 } else { 0 },
|
|
|
|
);
|
|
|
|
// Since the closure will be called multiple times,
|
|
|
|
// we also need a new Rc on every call.
|
2017-10-13 18:22:02 +00:00
|
|
|
EventResult::with_cb(move |s| show_child(s, offset, Rc::clone(&menu)))
|
2017-10-12 23:42:41 +00:00
|
|
|
}
|
2016-07-20 03:44:20 +00:00
|
|
|
}
|
2016-07-02 22:02:42 +00:00
|
|
|
|
2016-07-20 03:44:20 +00:00
|
|
|
fn show_child(s: &mut Cursive, offset: (usize, usize), menu: Rc<MenuTree>) {
|
|
|
|
// 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
|
2017-06-12 23:39:12 +00:00
|
|
|
// consume it before our OnEventView. This means sub-menus can properly
|
2016-07-20 03:44:20 +00:00
|
|
|
// be entered.)
|
2017-10-12 23:38:55 +00:00
|
|
|
s.screen_mut().add_layer_at(
|
|
|
|
Position::absolute(offset),
|
|
|
|
OnEventView::new(
|
|
|
|
MenuPopup::new(menu)
|
|
|
|
.on_dismiss(|s| s.select_menubar())
|
|
|
|
.on_action(|s| s.menubar().state = State::Inactive),
|
|
|
|
).on_event(Key::Right, |s| {
|
2017-06-12 23:39:12 +00:00
|
|
|
s.pop_layer();
|
|
|
|
s.select_menubar();
|
|
|
|
// Act as if we sent "Right" then "Down"
|
|
|
|
s.menubar().on_event(Event::Key(Key::Right)).process(s);
|
|
|
|
if let EventResult::Consumed(Some(cb)) =
|
2017-10-12 23:38:55 +00:00
|
|
|
s.menubar().on_event(Event::Key(Key::Down))
|
|
|
|
{
|
2017-06-12 23:39:12 +00:00
|
|
|
cb(s);
|
|
|
|
}
|
|
|
|
})
|
2017-10-12 23:38:55 +00:00
|
|
|
.on_event(Key::Left, |s| {
|
|
|
|
s.pop_layer();
|
|
|
|
s.select_menubar();
|
|
|
|
// Act as if we sent "Left" then "Down"
|
|
|
|
s.menubar().on_event(Event::Key(Key::Left)).process(s);
|
|
|
|
if let EventResult::Consumed(Some(cb)) =
|
|
|
|
s.menubar().on_event(Event::Key(Key::Down))
|
|
|
|
{
|
|
|
|
cb(s);
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
);
|
2016-07-20 03:44:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl View for Menubar {
|
|
|
|
fn draw(&self, printer: &Printer) {
|
2016-06-28 04:59:42 +00:00
|
|
|
// 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.
|
2017-10-12 23:38:55 +00:00
|
|
|
let selected =
|
|
|
|
(self.state != State::Inactive) && (i == self.focus);
|
2016-07-02 22:02:42 +00:00
|
|
|
printer.with_selection(selected, |printer| {
|
|
|
|
printer.print((offset, 0), &format!(" {} ", title));
|
|
|
|
});
|
2017-10-12 23:42:41 +00:00
|
|
|
offset += title.width() + 2;
|
2016-07-02 22:02:42 +00:00
|
|
|
}
|
2016-06-28 04:59:42 +00:00
|
|
|
}
|
|
|
|
|
2016-07-20 03:44:20 +00:00
|
|
|
fn on_event(&mut self, event: Event) -> EventResult {
|
2016-07-02 22:02:42 +00:00
|
|
|
match event {
|
2017-10-13 22:17:41 +00:00
|
|
|
Event::Key(Key::Esc) => {
|
2016-10-09 22:47:06 +00:00
|
|
|
self.hide();
|
|
|
|
return EventResult::with_cb(|s| s.clear());
|
|
|
|
}
|
2017-10-12 23:38:55 +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) | Event::Key(Key::Enter) => {
|
2017-10-12 23:42:41 +00:00
|
|
|
return self.select_child();
|
|
|
|
}
|
|
|
|
Event::Mouse {
|
2017-10-13 22:22:59 +00:00
|
|
|
event: MouseEvent::Press(btn),
|
2017-10-12 23:42:41 +00:00
|
|
|
position,
|
|
|
|
offset,
|
|
|
|
} if position.fits(offset) && position.y == offset.y =>
|
|
|
|
{
|
2017-10-13 22:22:59 +00:00
|
|
|
if let Some(child) = position
|
2017-10-12 23:42:41 +00:00
|
|
|
.checked_sub(offset)
|
|
|
|
.and_then(|pos| self.child_at(pos.x))
|
2017-10-13 22:22:59 +00:00
|
|
|
{
|
|
|
|
self.focus = child;
|
|
|
|
if btn == MouseButton::Left {
|
|
|
|
return self.select_child();
|
|
|
|
}
|
|
|
|
}
|
2017-10-12 23:42:41 +00:00
|
|
|
}
|
|
|
|
Event::Mouse {
|
|
|
|
event: MouseEvent::Press(_),
|
2017-10-13 18:22:02 +00:00
|
|
|
..
|
2017-10-12 23:42:41 +00:00
|
|
|
} => {
|
|
|
|
self.hide();
|
|
|
|
return EventResult::with_cb(|s| s.clear());
|
|
|
|
}
|
2016-07-20 03:44:20 +00:00
|
|
|
_ => return EventResult::Ignored,
|
2016-07-02 22:02:42 +00:00
|
|
|
}
|
2016-07-20 03:44:20 +00:00
|
|
|
EventResult::Consumed(None)
|
2016-06-28 04:59:42 +00:00
|
|
|
}
|
2016-07-10 02:05:51 +00:00
|
|
|
|
2016-07-20 03:44:20 +00:00
|
|
|
fn take_focus(&mut self, _: direction::Direction) -> bool {
|
|
|
|
self.state = State::Selected;
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
2017-01-24 06:52:29 +00:00
|
|
|
fn required_size(&mut self, _: Vec2) -> Vec2 {
|
2016-07-20 03:44:20 +00:00
|
|
|
// TODO: scroll the options if the screen is too small?
|
2016-07-10 02:05:51 +00:00
|
|
|
|
2016-07-20 03:44:20 +00:00
|
|
|
// We add 2 to the length of every label for marin.
|
|
|
|
// Also, we add 1 at the beginning.
|
|
|
|
// (See the `draw()` method)
|
|
|
|
let width = self.menus
|
|
|
|
.iter()
|
|
|
|
.map(|&(ref title, _)| title.len() + 2)
|
|
|
|
.fold(1, |a, b| a + b);
|
|
|
|
|
|
|
|
Vec2::new(width, 1)
|
|
|
|
}
|
2016-07-10 02:05:51 +00:00
|
|
|
}
|