mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-10 03:10:41 +00:00
Allow menu leaves and delimiter in menubar
This commit is contained in:
parent
ed94078218
commit
d384cbdaf1
@ -48,9 +48,7 @@ fn main() {
|
||||
for i in 1..10 {
|
||||
tree.add_leaf(format!("Option {}", i), |_| ());
|
||||
}
|
||||
})
|
||||
.delimiter()
|
||||
.leaf("Quit", |s| s.quit()))
|
||||
}))
|
||||
.add_subtree("Help",
|
||||
MenuTree::new()
|
||||
.subtree("Help",
|
||||
@ -64,7 +62,9 @@ fn main() {
|
||||
s.add_layer(Dialog::info(text))
|
||||
}))
|
||||
.leaf("About",
|
||||
|s| s.add_layer(Dialog::info("Cursive v0.0.0"))));
|
||||
|s| s.add_layer(Dialog::info("Cursive v0.0.0"))))
|
||||
.add_delimiter()
|
||||
.add_leaf("Quit", |s| s.quit());
|
||||
|
||||
// When `autohide` is on (default), the menu only appears when active.
|
||||
// Turning it off will leave the menu always visible.
|
||||
|
61
src/menu.rs
61
src/menu.rs
@ -41,7 +41,7 @@ impl MenuItem {
|
||||
/// Returns an empty string if `self` is a delimiter.
|
||||
pub fn label(&self) -> &str {
|
||||
match *self {
|
||||
MenuItem::Delimiter => "",
|
||||
MenuItem::Delimiter => "│",
|
||||
MenuItem::Leaf(ref label, _) | MenuItem::Subtree(ref label, _) => {
|
||||
label
|
||||
}
|
||||
@ -63,6 +63,16 @@ impl MenuItem {
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a mutable reference to the subtree, if applicable.
|
||||
///
|
||||
/// Returns `None` if `self` is not a `MenuItem::Subtree`.
|
||||
pub fn as_subtree(&mut self) -> Option<&mut MenuTree> {
|
||||
match *self {
|
||||
MenuItem::Subtree(_, ref mut tree) => Some(Rc::make_mut(tree)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MenuTree {
|
||||
@ -76,9 +86,14 @@ impl MenuTree {
|
||||
self.children.clear();
|
||||
}
|
||||
|
||||
/// Inserts an item at the given position.
|
||||
pub fn insert(&mut self, i: usize, item: MenuItem) {
|
||||
self.children.insert(i, item);
|
||||
}
|
||||
|
||||
/// Inserts a delimiter at the given position.
|
||||
pub fn insert_delimiter(&mut self, i: usize) {
|
||||
self.children.insert(i, MenuItem::Delimiter);
|
||||
self.insert(i, MenuItem::Delimiter);
|
||||
}
|
||||
|
||||
/// Adds a delimiter to the end of this tree.
|
||||
@ -109,8 +124,7 @@ impl MenuTree {
|
||||
F: 'static + Fn(&mut Cursive),
|
||||
{
|
||||
let title = title.into();
|
||||
self.children
|
||||
.insert(i, MenuItem::Leaf(title, Callback::from_fn(cb)));
|
||||
self.insert(i, MenuItem::Leaf(title, Callback::from_fn(cb)));
|
||||
}
|
||||
|
||||
/// Adds a actionnable leaf to the end of this tree - chainable variant.
|
||||
@ -129,7 +143,7 @@ impl MenuTree {
|
||||
{
|
||||
let title = title.into();
|
||||
let tree = MenuItem::Subtree(title, Rc::new(tree));
|
||||
self.children.insert(i, tree);
|
||||
self.insert(i, tree);
|
||||
}
|
||||
|
||||
/// Adds a submenu to the end of this tree.
|
||||
@ -149,6 +163,20 @@ impl MenuTree {
|
||||
self.with(|menu| menu.add_subtree(title, tree))
|
||||
}
|
||||
|
||||
/// Looks for the child at the given position.
|
||||
///
|
||||
/// Returns `None` if `i >= self.len()`.
|
||||
pub fn get_mut(&mut self, i: usize) -> Option<&mut MenuItem> {
|
||||
self.children.get_mut(i)
|
||||
}
|
||||
|
||||
/// Returns the item at the given position.
|
||||
///
|
||||
/// Returns `None` if `i > self.len()` or if the item is not a subtree.
|
||||
pub fn get_subtree(&mut self, i: usize) -> Option<&mut MenuTree> {
|
||||
self.get_mut(i).and_then(MenuItem::as_subtree)
|
||||
}
|
||||
|
||||
/// Looks for a child with the given title.
|
||||
///
|
||||
/// Returns `None` if no such label was found.
|
||||
@ -158,6 +186,15 @@ impl MenuTree {
|
||||
.find(|child| child.label() == title)
|
||||
}
|
||||
|
||||
/// Looks for a subtree with the given title.
|
||||
pub fn find_subtree(&mut self, title: &str) -> Option<&mut MenuTree> {
|
||||
self.children
|
||||
.iter_mut()
|
||||
.filter(|child| child.label() == title)
|
||||
.filter_map(MenuItem::as_subtree)
|
||||
.next()
|
||||
}
|
||||
|
||||
/// Returns the position of a child with the given label.
|
||||
///
|
||||
/// Returns `None` if no such label was found.
|
||||
@ -167,20 +204,6 @@ impl MenuTree {
|
||||
.position(|child| child.label() == title)
|
||||
}
|
||||
|
||||
/// Looks for a subtree child with the given label.
|
||||
///
|
||||
/// Returns `None` if the given title was not found,
|
||||
/// or if it wasn't a subtree.
|
||||
pub fn find_subtree(&mut self, title: &str) -> Option<&mut MenuTree> {
|
||||
self.find_item(title).and_then(|item| {
|
||||
if let MenuItem::Subtree(_, ref mut tree) = *item {
|
||||
Some(Rc::make_mut(tree))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Removes the item at the given position.
|
||||
pub fn remove(&mut self, i: usize) {
|
||||
self.children.remove(i);
|
||||
|
@ -2,7 +2,7 @@ use Cursive;
|
||||
use Printer;
|
||||
use direction;
|
||||
use event::*;
|
||||
use menu::MenuTree;
|
||||
use menu::{MenuItem, MenuTree};
|
||||
use std::rc::Rc;
|
||||
use theme::ColorStyle;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
@ -33,7 +33,7 @@ enum State {
|
||||
/// [`Cursive`]: ../struct.Cursive.html#method.menubar
|
||||
pub struct Menubar {
|
||||
/// Menu items in this menubar.
|
||||
menus: Vec<(String, Rc<MenuTree>)>,
|
||||
root: MenuTree,
|
||||
|
||||
/// TODO: move this out of this view.
|
||||
pub autohide: bool,
|
||||
@ -49,7 +49,7 @@ impl Menubar {
|
||||
/// Creates a new, empty menubar.
|
||||
pub fn new() -> Self {
|
||||
Menubar {
|
||||
menus: Vec::new(),
|
||||
root: MenuTree::new(),
|
||||
autohide: true,
|
||||
state: State::Inactive,
|
||||
focus: 0,
|
||||
@ -80,66 +80,99 @@ impl Menubar {
|
||||
///
|
||||
/// The item will use the given title, and on selection, will open a
|
||||
/// popup-menu with the given menu tree.
|
||||
pub fn add_subtree(&mut self, title: &str, menu: MenuTree) -> &mut Self {
|
||||
let i = self.menus.len();
|
||||
pub fn add_subtree<S>(&mut self, title: S, menu: MenuTree) -> &mut Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
let i = self.root.len();
|
||||
self.insert_subtree(i, title, menu)
|
||||
}
|
||||
|
||||
/// Adds a delimiter to the menubar.
|
||||
pub fn add_delimiter(&mut self) -> &mut Self {
|
||||
let i = self.root.len();
|
||||
self.insert_delimiter(i)
|
||||
}
|
||||
|
||||
/// Adds a leaf node to the menubar.
|
||||
pub fn add_leaf<S, F>(&mut self, title: S, cb: F) -> &mut Self
|
||||
where
|
||||
S: Into<String>,
|
||||
F: 'static + Fn(&mut Cursive),
|
||||
{
|
||||
let i = self.root.len();
|
||||
self.insert_leaf(i, title, cb)
|
||||
}
|
||||
|
||||
/// Insert a new item at the given position.
|
||||
pub fn insert_subtree(
|
||||
&mut self, i: usize, title: &str, menu: MenuTree
|
||||
) -> &mut Self {
|
||||
self.menus.insert(i, (title.to_string(), Rc::new(menu)));
|
||||
pub fn insert_subtree<S>(
|
||||
&mut self, i: usize, title: S, menu: MenuTree
|
||||
) -> &mut Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.root.insert_subtree(i, title, menu);
|
||||
self
|
||||
}
|
||||
|
||||
/// Inserts a new delimiter at the given position.
|
||||
///
|
||||
/// It will show up as `|`.
|
||||
pub fn insert_delimiter(&mut self, i: usize) -> &mut Self {
|
||||
self.root.insert_delimiter(i);
|
||||
self
|
||||
}
|
||||
|
||||
/// Inserts a new leaf node at the given position.
|
||||
///
|
||||
/// It will be directly actionable.
|
||||
pub fn insert_leaf<S, F>(&mut self, i: usize, title: S, cb: F) -> &mut Self
|
||||
where
|
||||
S: Into<String>,
|
||||
F: 'static + Fn(&mut Cursive),
|
||||
{
|
||||
self.root.insert_leaf(i, title, cb);
|
||||
self
|
||||
}
|
||||
|
||||
/// Removes all menu items from this menubar.
|
||||
pub fn clear(&mut self) {
|
||||
self.menus.clear();
|
||||
self.root.clear();
|
||||
self.focus = 0;
|
||||
}
|
||||
|
||||
/// Returns the number of items in this menubar.
|
||||
pub fn len(&self) -> usize {
|
||||
self.menus.len()
|
||||
self.root.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if this menubar is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.menus.is_empty()
|
||||
self.root.is_empty()
|
||||
}
|
||||
|
||||
/// Returns the item at the given position.
|
||||
///
|
||||
/// Returns `None` if `i > self.len()`
|
||||
pub fn get_subtree(&mut self, i: usize) -> Option<&mut MenuTree> {
|
||||
self.menus
|
||||
.get_mut(i)
|
||||
.map(|&mut (_, ref mut tree)| Rc::make_mut(tree))
|
||||
self.root.get_subtree(i)
|
||||
}
|
||||
|
||||
/// Looks for an item with the given label.
|
||||
pub fn find_subtree(&mut self, label: &str) -> Option<&mut MenuTree> {
|
||||
// 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))
|
||||
self.root.find_subtree(label)
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
self.menus.iter().position(|&(ref l, _)| l == label)
|
||||
self.root.find_position(label)
|
||||
}
|
||||
|
||||
/// Remove the item at the given position.
|
||||
pub fn remove(&mut self, i: usize) {
|
||||
self.menus.remove(i);
|
||||
self.root.remove(i);
|
||||
}
|
||||
|
||||
fn child_at(&self, x: usize) -> Option<usize> {
|
||||
@ -147,9 +180,9 @@ impl Menubar {
|
||||
return None;
|
||||
}
|
||||
let mut offset = 1;
|
||||
for (i, &(ref title, _)) in self.menus.iter().enumerate() {
|
||||
offset += title.width() + 2;
|
||||
|
||||
for (i, child) in self.root.children.iter().enumerate() {
|
||||
offset += child.label().width() + 2;
|
||||
if x < offset {
|
||||
return Some(i);
|
||||
}
|
||||
@ -158,25 +191,36 @@ impl Menubar {
|
||||
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.
|
||||
let menu = Rc::clone(&self.menus[self.focus].1);
|
||||
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.
|
||||
EventResult::with_cb(move |s| show_child(s, offset, Rc::clone(&menu)))
|
||||
fn select_child(&mut self, open_only: bool) -> EventResult {
|
||||
match self.root.children[self.focus] {
|
||||
MenuItem::Leaf(_, ref cb) if !open_only => {
|
||||
EventResult::Consumed(Some(cb.clone()))
|
||||
}
|
||||
MenuItem::Subtree(_, ref tree) => {
|
||||
// First, we need a new Rc to send the callback,
|
||||
// since we don't know when it will be called.
|
||||
let menu = Rc::clone(tree);
|
||||
|
||||
self.state = State::Submenu;
|
||||
let offset = Vec2::new(
|
||||
self.root.children[..self.focus]
|
||||
.iter()
|
||||
.map(|child| child.label().width() + 2)
|
||||
.sum(),
|
||||
if self.autohide { 1 } else { 0 },
|
||||
);
|
||||
// Since the closure will be called multiple times,
|
||||
// we also need a new Rc on every call.
|
||||
EventResult::with_cb(move |s| {
|
||||
show_child(s, offset, Rc::clone(&menu))
|
||||
})
|
||||
}
|
||||
_ => EventResult::Ignored,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_child(s: &mut Cursive, offset: (usize, usize), menu: Rc<MenuTree>) {
|
||||
fn show_child(s: &mut Cursive, offset: Vec2, 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.
|
||||
@ -223,7 +267,9 @@ impl View for Menubar {
|
||||
|
||||
// TODO: draw the rest
|
||||
let mut offset = 1;
|
||||
for (i, &(ref title, _)) in self.menus.iter().enumerate() {
|
||||
for (i, item) in self.root.children.iter().enumerate() {
|
||||
let title = item.label();
|
||||
|
||||
// We don't want to show HighlightInactive when we're not selected,
|
||||
// because it's ugly on the menubar.
|
||||
let selected =
|
||||
@ -241,18 +287,31 @@ impl View for Menubar {
|
||||
self.hide();
|
||||
return EventResult::with_cb(|s| s.clear());
|
||||
}
|
||||
Event::Key(Key::Left) => if self.focus > 0 {
|
||||
self.focus -= 1
|
||||
} else {
|
||||
self.focus = self.menus.len() - 1
|
||||
Event::Key(Key::Left) => loop {
|
||||
if self.focus > 0 {
|
||||
self.focus -= 1;
|
||||
} else {
|
||||
self.focus = self.root.len() - 1;
|
||||
}
|
||||
if !self.root.children[self.focus].is_delimiter() {
|
||||
break;
|
||||
}
|
||||
},
|
||||
Event::Key(Key::Right) => if self.focus + 1 < self.menus.len() {
|
||||
self.focus += 1
|
||||
} else {
|
||||
self.focus = 0
|
||||
Event::Key(Key::Right) => loop {
|
||||
if self.focus + 1 < self.root.len() {
|
||||
self.focus += 1;
|
||||
} else {
|
||||
self.focus = 0;
|
||||
}
|
||||
if !self.root.children[self.focus].is_delimiter() {
|
||||
break;
|
||||
}
|
||||
},
|
||||
Event::Key(Key::Down) | Event::Key(Key::Enter) => {
|
||||
return self.select_child();
|
||||
Event::Key(Key::Down) => {
|
||||
return self.select_child(true);
|
||||
}
|
||||
Event::Key(Key::Enter) => {
|
||||
return self.select_child(false);
|
||||
}
|
||||
Event::Mouse {
|
||||
event: MouseEvent::Press(btn),
|
||||
@ -266,7 +325,7 @@ impl View for Menubar {
|
||||
{
|
||||
self.focus = child;
|
||||
if btn == MouseButton::Left {
|
||||
return self.select_child();
|
||||
return self.select_child(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -293,10 +352,11 @@ impl View for Menubar {
|
||||
// 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
|
||||
let width = self.root
|
||||
.children
|
||||
.iter()
|
||||
.map(|&(ref title, _)| title.len() + 2)
|
||||
.fold(1, |a, b| a + b);
|
||||
.map(|item| item.label().len() + 2)
|
||||
.sum();
|
||||
|
||||
Vec2::new(width, 1)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user