Add methods to edit menubar and menutrees

Fixes #101
This commit is contained in:
Alexandre Bury 2017-01-23 15:42:36 -08:00
parent 0849ae6efa
commit ed841825f2
5 changed files with 186 additions and 36 deletions

View File

@ -6,17 +6,32 @@ use cursive::menu::MenuTree;
use cursive::traits::*;
use cursive::views::Dialog;
use std::sync::atomic::{Ordering, AtomicUsize};
fn main() {
let mut siv = Cursive::new();
// We'll use a counter to name new files.
let counter = AtomicUsize::new(1);
// The menubar is a list of (label, menu tree) pairs.
siv.menubar()
// We add a new "File" tree
.add("File",
MenuTree::new()
// Trees are made of leaves, with are directly actionable...
.leaf("New", |s| s.add_layer(Dialog::info("New file!")))
.leaf("New", move |s| {
// Here we use the counter to add an entry
// in the list of "Recent" items.
let i = counter.fetch_add(1, Ordering::Relaxed);
let filename = format!("New {}", i);
s.menubar().find("File").unwrap()
.find_subtree("Recent").unwrap()
.insert_leaf(0, filename, |_| ());
s.add_layer(Dialog::info("New file!"));
})
// ... and of sub-trees, which open up when selected.
.subtree("Recent",
// The `.with()` method can help when running loops
@ -25,7 +40,7 @@ fn main() {
for i in 1..100 {
// We don't actually do anything here,
// but you could!
tree.add_leaf(&format!("Item {}", i), |_| ())
tree.add_leaf(format!("Item {}", i), |_| ())
}
}))
// Delimiter are simple lines between items,
@ -33,7 +48,7 @@ fn main() {
.delimiter()
.with(|tree| {
for i in 1..10 {
tree.add_leaf(&format!("Option {}", i), |_| ());
tree.add_leaf(format!("Option {}", i), |_| ());
}
})
.delimiter()

View File

@ -18,13 +18,14 @@ use event::Callback;
use std::rc::Rc;
/// Root of a menu tree.
#[derive(Default)]
#[derive(Default, Clone)]
pub struct MenuTree {
/// Menu items
pub children: Vec<MenuItem>,
}
/// Node in the menu tree.
#[derive(Clone)]
pub enum MenuItem {
/// Actionnable button with a label.
Leaf(String, Callback),
@ -69,11 +70,6 @@ impl MenuTree {
Self::default()
}
/// Returns the number of children, including delimiters.
pub fn len(&self) -> usize {
self.children.len()
}
/// Remove every children from this tree.
pub fn clear(&mut self) {
self.children.clear();
@ -84,9 +80,15 @@ impl MenuTree {
self.children.is_empty()
}
/// Inserts a delimiter at the given position.
pub fn insert_delimiter(&mut self, i: usize) {
self.children.insert(i, MenuItem::Delimiter);
}
/// Adds a delimiter to the end of this tree.
pub fn add_delimiter(&mut self) {
self.children.push(MenuItem::Delimiter);
let i = self.children.len();
self.insert_delimiter(i);
}
/// Adds a delimiter to the end of this tree - chainable variant.
@ -95,27 +97,98 @@ impl MenuTree {
}
/// Adds a actionnable leaf to the end of this tree.
pub fn add_leaf<F: 'static + Fn(&mut Cursive)>(&mut self, title: &str,
cb: F) {
self.children
.push(MenuItem::Leaf(title.to_string(), Callback::from_fn(cb)));
pub fn add_leaf<S, F>(&mut self, title: S, cb: F)
where S: Into<String>,
F: 'static + Fn(&mut Cursive)
{
let i = self.children.len();
self.insert_leaf(i, title, cb);
}
/// Inserts a leaf at the given position.
pub fn insert_leaf<S, F>(&mut self, i: usize, title: S, cb: F)
where S: Into<String>,
F: 'static + Fn(&mut Cursive)
{
let title = title.into();
self.children.insert(i, MenuItem::Leaf(title, Callback::from_fn(cb)));
}
/// Adds a actionnable leaf to the end of this tree - chainable variant.
pub fn leaf<F>(self, title: &str, cb: F) -> Self
where F: 'static + Fn(&mut Cursive)
pub fn leaf<S, F>(self, title: S, cb: F) -> Self
where S: Into<String>,
F: 'static + Fn(&mut Cursive)
{
self.with(|menu| menu.add_leaf(title, cb))
}
/// Inserts a subtree at the given position.
pub fn insert_subtree<S>(&mut self, i: usize, title: S, tree: MenuTree)
where S: Into<String>
{
let title = title.into();
let tree = MenuItem::Subtree(title, Rc::new(tree));
self.children.insert(i, tree);
}
/// Adds a submenu to the end of this tree.
pub fn add_subtree(&mut self, title: &str, tree: MenuTree) {
self.children
.push(MenuItem::Subtree(title.to_string(), Rc::new(tree)));
pub fn add_subtree<S>(&mut self, title: S, tree: MenuTree)
where S: Into<String>
{
let i = self.children.len();
self.insert_subtree(i, title, tree);
}
/// Adds a submenu to the end of this tree - chainable variant.
pub fn subtree(self, title: &str, tree: MenuTree) -> Self {
pub fn subtree<S>(self, title: S, tree: MenuTree) -> Self
where S: Into<String>
{
self.with(|menu| menu.add_subtree(title, tree))
}
/// Looks for a child with the given title.
///
/// Returns `None` if no such label was found.
pub fn find_item(&mut self, title: &str) -> Option<&mut MenuItem> {
self.children
.iter_mut()
.find(|child| child.label() == title)
}
/// Returns the position of a child with the given label.
///
/// Returns `None` if no such label was found.
pub fn find_position(&mut self, title: &str) -> Option<usize> {
self.children
.iter()
.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 &mut 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);
}
/// Returns the number of direct children in this node.
///
/// * Includes delimiters.
/// * Does not count nested children.
pub fn len(&self) -> usize {
self.children.len()
}
}

View File

@ -228,9 +228,12 @@ impl View for MenuPopup {
Event::Key(Key::PageDown) => self.scroll_down(5, false),
Event::Key(Key::Home) => self.focus = 0,
Event::Key(Key::End) => self.focus = self.menu.children.len() - 1,
Event::Key(Key::End) => {
self.focus = self.menu.children.len() - 1
}
Event::Key(Key::Right) if self.menu.children[self.focus]
Event::Key(Key::Right) if self.menu.children
[self.focus]
.is_subtree() => {
return match self.menu.children[self.focus] {
MenuItem::Subtree(_, ref tree) => {
@ -240,7 +243,8 @@ impl View for MenuPopup {
};
}
Event::Key(Key::Enter) if !self.menu.children[self.focus]
Event::Key(Key::Enter) if !self.menu.children
[self.focus]
.is_delimiter() => {
return match self.menu.children[self.focus] {
MenuItem::Leaf(_, ref cb) => {
@ -274,6 +278,7 @@ impl View for MenuPopup {
}
fn layout(&mut self, size: Vec2) {
self.scrollbase.set_heights(size.y - 2, self.menu.children.len());
self.scrollbase
.set_heights(size.y - 2, self.menu.children.len());
}
}

View File

@ -5,8 +5,8 @@ use event::*;
use menu::MenuTree;
use std::rc::Rc;
use theme::ColorStyle;
use theme::ColorStyle;
use unicode_width::UnicodeWidthStr;
use vec::Vec2;
use view::{Position, View};
@ -35,7 +35,7 @@ enum State {
/// [`Cursive`]: ../struct.Cursive.html#method.menubar
pub struct Menubar {
/// Menu items in this menubar.
pub menus: Vec<(String, Rc<MenuTree>)>,
menus: Vec<(String, Rc<MenuTree>)>,
/// TODO: move this out of this view.
pub autohide: bool,
focus: usize,
@ -77,9 +77,62 @@ 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(&mut self, title: &str, menu: MenuTree) -> &mut Self {
self.menus.push((title.to_string(), Rc::new(menu)));
let i = self.menus.len();
self.insert(i, title, menu)
}
/// Insert a new item at the given position.
pub fn insert(&mut self, i: usize, title: &str, menu: MenuTree)
-> &mut Self {
self.menus.insert(i, (title.to_string(), Rc::new(menu)));
self
}
/// 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()
}
/// Returns the item at the given position.
///
/// Returns `None` if `i > self.len()`
pub fn get(&mut self, i: usize) -> Option<&mut MenuTree> {
self.menus
.get_mut(i)
.map(|&mut (_, ref mut tree)| Rc::make_mut(tree))
}
/// Looks for an item with the given label.
pub fn find(&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))
}
/// 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)
}
/// Remove the item at the given position.
pub fn remove(&mut self, i: usize) {
self.menus.remove(i);
}
}
fn show_child(s: &mut Cursive, offset: (usize, usize), menu: Rc<MenuTree>) {
@ -101,8 +154,9 @@ fn show_child(s: &mut Cursive, offset: (usize, usize), menu: Rc<MenuTree>) {
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)) = s.menubar()
.on_event(Event::Key(Key::Down)) {
if let EventResult::Consumed(Some(cb)) =
s.menubar()
.on_event(Event::Key(Key::Down)) {
cb(s);
}
})
@ -111,8 +165,9 @@ fn show_child(s: &mut Cursive, offset: (usize, usize), menu: Rc<MenuTree>) {
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)) {
if let EventResult::Consumed(Some(cb)) =
s.menubar()
.on_event(Event::Key(Key::Down)) {
cb(s);
}
}));
@ -167,9 +222,9 @@ impl View for Menubar {
let menu = self.menus[self.focus].1.clone();
self.state = State::Submenu;
let offset = (self.menus[..self.focus]
.iter()
.map(|&(ref title, _)| title.width() + 2)
.fold(0, |a, b| a + b),
.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.

View File

@ -403,6 +403,7 @@ impl<T: 'static> View for SelectView<T> {
fn on_event(&mut self, event: Event) -> EventResult {
if self.popup {
match event {
// TODO: add Left/Right support for quick-switch?
Event::Key(Key::Enter) => {
// Build a shallow menu tree to mimick the items array.
// TODO: cache it?
@ -411,7 +412,7 @@ impl<T: 'static> View for SelectView<T> {
let focus = self.focus.clone();
let on_submit = self.on_submit.as_ref().cloned();
let value = item.value.clone();
tree.add_leaf(&item.label, move |s| {
tree.add_leaf(item.label.clone(), move |s| {
focus.set(i);
if let Some(ref on_submit) = on_submit {
on_submit(s, &value);
@ -450,7 +451,8 @@ impl<T: 'static> View for SelectView<T> {
// A nice effect is that window resizes will keep both
// layers together.
let current_offset = s.screen().offset();
let offset = XY::<isize>::from(offset) - current_offset;
let offset = XY::<isize>::from(offset) -
current_offset;
// And finally, put the view in view!
s.screen_mut()
.add_layer_at(Position::parent(offset),