mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
parent
0849ae6efa
commit
ed841825f2
@ -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()
|
||||
|
107
src/menu.rs
107
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<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()
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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),
|
||||
|
Loading…
Reference in New Issue
Block a user