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::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()
|
||||||
|
107
src/menu.rs
107
src/menu.rs
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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),
|
||||||
|
Loading…
Reference in New Issue
Block a user