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::traits::*;
use cursive::views::Dialog; use cursive::views::Dialog;
use std::sync::atomic::{Ordering, AtomicUsize};
fn main() { fn main() {
let mut siv = Cursive::new(); 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. // The menubar is a list of (label, menu tree) pairs.
siv.menubar() siv.menubar()
// We add a new "File" tree // We add a new "File" tree
.add("File", .add("File",
MenuTree::new() MenuTree::new()
// Trees are made of leaves, with are directly actionable... // 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. // ... and of sub-trees, which open up when selected.
.subtree("Recent", .subtree("Recent",
// The `.with()` method can help when running loops // The `.with()` method can help when running loops
@ -25,7 +40,7 @@ fn main() {
for i in 1..100 { for i in 1..100 {
// We don't actually do anything here, // We don't actually do anything here,
// but you could! // but you could!
tree.add_leaf(&format!("Item {}", i), |_| ()) tree.add_leaf(format!("Item {}", i), |_| ())
} }
})) }))
// Delimiter are simple lines between items, // Delimiter are simple lines between items,
@ -33,7 +48,7 @@ fn main() {
.delimiter() .delimiter()
.with(|tree| { .with(|tree| {
for i in 1..10 { for i in 1..10 {
tree.add_leaf(&format!("Option {}", i), |_| ()); tree.add_leaf(format!("Option {}", i), |_| ());
} }
}) })
.delimiter() .delimiter()

View File

@ -18,13 +18,14 @@ use event::Callback;
use std::rc::Rc; use std::rc::Rc;
/// Root of a menu tree. /// Root of a menu tree.
#[derive(Default)] #[derive(Default, Clone)]
pub struct MenuTree { pub struct MenuTree {
/// Menu items /// Menu items
pub children: Vec<MenuItem>, pub children: Vec<MenuItem>,
} }
/// Node in the menu tree. /// Node in the menu tree.
#[derive(Clone)]
pub enum MenuItem { pub enum MenuItem {
/// Actionnable button with a label. /// Actionnable button with a label.
Leaf(String, Callback), Leaf(String, Callback),
@ -69,11 +70,6 @@ impl MenuTree {
Self::default() Self::default()
} }
/// Returns the number of children, including delimiters.
pub fn len(&self) -> usize {
self.children.len()
}
/// Remove every children from this tree. /// Remove every children from this tree.
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.children.clear(); self.children.clear();
@ -84,9 +80,15 @@ impl MenuTree {
self.children.is_empty() 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. /// Adds a delimiter to the end of this tree.
pub fn add_delimiter(&mut self) { 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. /// 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. /// Adds a actionnable leaf to the end of this tree.
pub fn add_leaf<F: 'static + Fn(&mut Cursive)>(&mut self, title: &str, pub fn add_leaf<S, F>(&mut self, title: S, cb: F)
cb: F) { where S: Into<String>,
self.children F: 'static + Fn(&mut Cursive)
.push(MenuItem::Leaf(title.to_string(), Callback::from_fn(cb))); {
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. /// Adds a actionnable leaf to the end of this tree - chainable variant.
pub fn leaf<F>(self, title: &str, cb: F) -> Self pub fn leaf<S, F>(self, title: S, cb: F) -> Self
where F: 'static + Fn(&mut Cursive) where S: Into<String>,
F: 'static + Fn(&mut Cursive)
{ {
self.with(|menu| menu.add_leaf(title, cb)) 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. /// Adds a submenu to the end of this tree.
pub fn add_subtree(&mut self, title: &str, tree: MenuTree) { pub fn add_subtree<S>(&mut self, title: S, tree: MenuTree)
self.children where S: Into<String>
.push(MenuItem::Subtree(title.to_string(), Rc::new(tree))); {
let i = self.children.len();
self.insert_subtree(i, title, tree);
} }
/// Adds a submenu to the end of this tree - chainable variant. /// 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)) 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::PageDown) => self.scroll_down(5, false),
Event::Key(Key::Home) => self.focus = 0, 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() => { .is_subtree() => {
return match self.menu.children[self.focus] { return match self.menu.children[self.focus] {
MenuItem::Subtree(_, ref tree) => { 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() => { .is_delimiter() => {
return match self.menu.children[self.focus] { return match self.menu.children[self.focus] {
MenuItem::Leaf(_, ref cb) => { MenuItem::Leaf(_, ref cb) => {
@ -274,6 +278,7 @@ impl View for MenuPopup {
} }
fn layout(&mut self, size: Vec2) { 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 menu::MenuTree;
use std::rc::Rc; use std::rc::Rc;
use theme::ColorStyle;
use theme::ColorStyle;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use vec::Vec2; use vec::Vec2;
use view::{Position, View}; use view::{Position, View};
@ -35,7 +35,7 @@ enum State {
/// [`Cursive`]: ../struct.Cursive.html#method.menubar /// [`Cursive`]: ../struct.Cursive.html#method.menubar
pub struct Menubar { pub struct Menubar {
/// Menu items in this menubar. /// Menu items in this menubar.
pub menus: Vec<(String, Rc<MenuTree>)>, menus: Vec<(String, Rc<MenuTree>)>,
/// TODO: move this out of this view. /// TODO: move this out of this view.
pub autohide: bool, pub autohide: bool,
focus: usize, focus: usize,
@ -77,9 +77,62 @@ impl Menubar {
/// The item will use the given title, and on selection, will open a /// The item will use the given title, and on selection, will open a
/// popup-menu with the given menu tree. /// popup-menu with the given menu tree.
pub fn add(&mut self, title: &str, menu: MenuTree) -> &mut Self { 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 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>) { 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(); s.select_menubar();
// Act as if we sent "Right" then "Down" // Act as if we sent "Right" then "Down"
s.menubar().on_event(Event::Key(Key::Right)).process(s); s.menubar().on_event(Event::Key(Key::Right)).process(s);
if let EventResult::Consumed(Some(cb)) = s.menubar() if let EventResult::Consumed(Some(cb)) =
.on_event(Event::Key(Key::Down)) { s.menubar()
.on_event(Event::Key(Key::Down)) {
cb(s); cb(s);
} }
}) })
@ -111,8 +165,9 @@ fn show_child(s: &mut Cursive, offset: (usize, usize), menu: Rc<MenuTree>) {
s.select_menubar(); s.select_menubar();
// Act as if we sent "Left" then "Down" // Act as if we sent "Left" then "Down"
s.menubar().on_event(Event::Key(Key::Left)).process(s); s.menubar().on_event(Event::Key(Key::Left)).process(s);
if let EventResult::Consumed(Some(cb)) = s.menubar() if let EventResult::Consumed(Some(cb)) =
.on_event(Event::Key(Key::Down)) { s.menubar()
.on_event(Event::Key(Key::Down)) {
cb(s); cb(s);
} }
})); }));
@ -167,9 +222,9 @@ impl View for Menubar {
let menu = self.menus[self.focus].1.clone(); let menu = self.menus[self.focus].1.clone();
self.state = State::Submenu; self.state = State::Submenu;
let offset = (self.menus[..self.focus] let offset = (self.menus[..self.focus]
.iter() .iter()
.map(|&(ref title, _)| title.width() + 2) .map(|&(ref title, _)| title.width() + 2)
.fold(0, |a, b| a + b), .fold(0, |a, b| a + b),
if self.autohide { 1 } else { 0 }); if self.autohide { 1 } else { 0 });
// Since the closure will be called multiple times, // Since the closure will be called multiple times,
// we also need a new Rc on every call. // 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 { fn on_event(&mut self, event: Event) -> EventResult {
if self.popup { if self.popup {
match event { match event {
// TODO: add Left/Right support for quick-switch?
Event::Key(Key::Enter) => { Event::Key(Key::Enter) => {
// Build a shallow menu tree to mimick the items array. // Build a shallow menu tree to mimick the items array.
// TODO: cache it? // TODO: cache it?
@ -411,7 +412,7 @@ impl<T: 'static> View for SelectView<T> {
let focus = self.focus.clone(); let focus = self.focus.clone();
let on_submit = self.on_submit.as_ref().cloned(); let on_submit = self.on_submit.as_ref().cloned();
let value = item.value.clone(); let value = item.value.clone();
tree.add_leaf(&item.label, move |s| { tree.add_leaf(item.label.clone(), move |s| {
focus.set(i); focus.set(i);
if let Some(ref on_submit) = on_submit { if let Some(ref on_submit) = on_submit {
on_submit(s, &value); 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 // A nice effect is that window resizes will keep both
// layers together. // layers together.
let current_offset = s.screen().offset(); 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! // And finally, put the view in view!
s.screen_mut() s.screen_mut()
.add_layer_at(Position::parent(offset), .add_layer_at(Position::parent(offset),