From ed841825f283855e1ee8d6586abf45d53ff6f3c6 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Mon, 23 Jan 2017 15:42:36 -0800 Subject: [PATCH] Add methods to edit menubar and menutrees Fixes #101 --- examples/menubar.rs | 21 ++++++-- src/menu.rs | 107 ++++++++++++++++++++++++++++++++------- src/views/menu_popup.rs | 13 +++-- src/views/menubar.rs | 75 +++++++++++++++++++++++---- src/views/select_view.rs | 6 ++- 5 files changed, 186 insertions(+), 36 deletions(-) diff --git a/examples/menubar.rs b/examples/menubar.rs index dd9e3a7..c7f2361 100644 --- a/examples/menubar.rs +++ b/examples/menubar.rs @@ -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() diff --git a/src/menu.rs b/src/menu.rs index eadcd56..4f1be7f 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -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, } /// 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(&mut self, title: &str, - cb: F) { - self.children - .push(MenuItem::Leaf(title.to_string(), Callback::from_fn(cb))); + pub fn add_leaf(&mut self, title: S, cb: F) + where S: Into, + 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(&mut self, i: usize, title: S, cb: F) + where S: Into, + 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(self, title: &str, cb: F) -> Self - where F: 'static + Fn(&mut Cursive) + pub fn leaf(self, title: S, cb: F) -> Self + where S: Into, + F: 'static + Fn(&mut Cursive) { self.with(|menu| menu.add_leaf(title, cb)) } + /// Inserts a subtree at the given position. + pub fn insert_subtree(&mut self, i: usize, title: S, tree: MenuTree) + where S: Into + { + 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(&mut self, title: S, tree: MenuTree) + where S: Into + { + 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(self, title: S, tree: MenuTree) -> Self + where S: Into + { 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 { + 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() + } } diff --git a/src/views/menu_popup.rs b/src/views/menu_popup.rs index bb70393..2db71c3 100644 --- a/src/views/menu_popup.rs +++ b/src/views/menu_popup.rs @@ -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()); } } diff --git a/src/views/menubar.rs b/src/views/menubar.rs index 721dc7c..b9e7cfa 100644 --- a/src/views/menubar.rs +++ b/src/views/menubar.rs @@ -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)>, + menus: Vec<(String, Rc)>, /// 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 { + 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) { @@ -101,8 +154,9 @@ fn show_child(s: &mut Cursive, offset: (usize, usize), menu: Rc) { 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) { 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. diff --git a/src/views/select_view.rs b/src/views/select_view.rs index ef9dc6c..3dec7d5 100644 --- a/src/views/select_view.rs +++ b/src/views/select_view.rs @@ -403,6 +403,7 @@ impl View for SelectView { 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 View for SelectView { 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 View for SelectView { // A nice effect is that window resizes will keep both // layers together. let current_offset = s.screen().offset(); - let offset = XY::::from(offset) - current_offset; + let offset = XY::::from(offset) - + current_offset; // And finally, put the view in view! s.screen_mut() .add_layer_at(Position::parent(offset),