mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-27 11:16:03 +00:00
Add enabled state to menu items
This commit is contained in:
parent
06d64be0a0
commit
6543287704
@ -2,80 +2,159 @@
|
|||||||
//!
|
//!
|
||||||
//! Menus are a way to arrange many actions in groups of more manageable size.
|
//! Menus are a way to arrange many actions in groups of more manageable size.
|
||||||
//!
|
//!
|
||||||
//! A menu can be seen as a [`MenuTree`]. It has a list of children:
|
//! A menu can be seen as a [`Tree`]. It has a list of children:
|
||||||
//!
|
//!
|
||||||
//! * Leaf nodes are made of a label and a callback
|
//! * Leaf nodes are made of a label and a callback
|
||||||
//! * Sub-trees are made of a label, and another `MenuTree`.
|
//! * Sub-trees are made of a label, and another `Tree`.
|
||||||
//! * Delimiters are just there to separate groups of related children.
|
//! * Delimiters are just there to separate groups of related children.
|
||||||
//!
|
//!
|
||||||
//! The [menubar] is the main way to show menus.
|
//! The [menubar] is the main way to show menus.
|
||||||
//!
|
//!
|
||||||
//! [`MenuTree`]: struct.MenuTree.html
|
//! [`Tree`]: struct.Tree.html
|
||||||
//! [menubar]: ../struct.Cursive.html#method.menubar
|
//! [menubar]: ../struct.Cursive.html#method.menubar
|
||||||
|
|
||||||
use crate::event::Callback;
|
use crate::{event::Callback, Cursive, With};
|
||||||
use crate::Cursive;
|
|
||||||
use crate::With;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
/// Root of a menu tree.
|
/// Root of a menu tree.
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct MenuTree {
|
pub struct Tree {
|
||||||
/// Menu items
|
/// Menu items
|
||||||
pub children: Vec<MenuItem>,
|
pub children: Vec<Item>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Node in the menu tree.
|
/// Node in the menu tree.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum MenuItem {
|
pub enum Item {
|
||||||
/// Actionnable button with a label.
|
/// Actionnable button with a label.
|
||||||
Leaf(String, Callback),
|
Leaf {
|
||||||
|
/// Text displayed for this entry.
|
||||||
|
label: String,
|
||||||
|
/// Callback to run when the entry is selected.
|
||||||
|
cb: Callback,
|
||||||
|
/// Whether this item is enabled.
|
||||||
|
///
|
||||||
|
/// Disabled items cannot be selected and are displayed grayed out.
|
||||||
|
enabled: bool,
|
||||||
|
},
|
||||||
|
|
||||||
/// Sub-menu with a label.
|
/// Sub-menu with a label.
|
||||||
Subtree(String, Rc<MenuTree>),
|
Subtree {
|
||||||
|
/// Text displayed for this entry.
|
||||||
|
label: String,
|
||||||
|
/// Subtree under this item.
|
||||||
|
tree: Rc<Tree>,
|
||||||
|
/// Whether this item is enabled.
|
||||||
|
///
|
||||||
|
/// Disabled items cannot be selected and are displayed grayed out.
|
||||||
|
enabled: bool,
|
||||||
|
},
|
||||||
|
|
||||||
/// Delimiter without a label.
|
/// Delimiter without a label.
|
||||||
Delimiter,
|
Delimiter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuItem {
|
impl Item {
|
||||||
|
/// Create a new leaf menu item.
|
||||||
|
pub fn leaf<S, F>(label: S, cb: F) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
F: 'static + Fn(&mut Cursive),
|
||||||
|
{
|
||||||
|
let label = label.into();
|
||||||
|
let cb = Callback::from_fn(cb);
|
||||||
|
let enabled = true;
|
||||||
|
Item::Leaf { label, cb, enabled }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new subtree menu item.
|
||||||
|
pub fn subtree<S>(label: S, tree: Tree) -> Self
|
||||||
|
where
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
let label = label.into();
|
||||||
|
let tree = Rc::new(tree);
|
||||||
|
let enabled = true;
|
||||||
|
Item::Subtree {
|
||||||
|
label,
|
||||||
|
tree,
|
||||||
|
enabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the label for this item.
|
/// Returns the label for this item.
|
||||||
///
|
///
|
||||||
/// Returns an empty string if `self` is a delimiter.
|
/// Returns a vertical bar string if `self` is a delimiter.
|
||||||
pub fn label(&self) -> &str {
|
pub fn label(&self) -> &str {
|
||||||
match *self {
|
match *self {
|
||||||
MenuItem::Delimiter => "│",
|
Item::Delimiter => "│",
|
||||||
MenuItem::Leaf(ref label, _) | MenuItem::Subtree(ref label, _) => {
|
Item::Leaf { ref label, .. } | Item::Subtree { ref label, .. } => {
|
||||||
label
|
label
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if this item is enabled.
|
||||||
|
///
|
||||||
|
/// Only labels and subtrees can be enabled. Delimiters
|
||||||
|
pub fn is_enabled(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Item::Leaf { enabled, .. } | Item::Subtree { enabled, .. } => {
|
||||||
|
enabled
|
||||||
|
}
|
||||||
|
Item::Delimiter => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a disabled version of this item.
|
||||||
|
pub fn disabled(self) -> Self {
|
||||||
|
self.with(Self::disable)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable this item.
|
||||||
|
///
|
||||||
|
/// Disabled items cannot be selected and are shown grayed out.
|
||||||
|
///
|
||||||
|
/// Does not affect delimiters.
|
||||||
|
pub fn disable(&mut self) {
|
||||||
|
if let Item::Leaf {
|
||||||
|
ref mut enabled, ..
|
||||||
|
}
|
||||||
|
| Item::Subtree {
|
||||||
|
ref mut enabled, ..
|
||||||
|
} = self
|
||||||
|
{
|
||||||
|
*enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `true` if `self` is a delimiter.
|
/// Returns `true` if `self` is a delimiter.
|
||||||
pub fn is_delimiter(&self) -> bool {
|
pub fn is_delimiter(&self) -> bool {
|
||||||
matches!(*self, MenuItem::Delimiter)
|
matches!(*self, Item::Delimiter)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if `self` is a leaf node.
|
/// Returns `true` if `self` is a leaf node.
|
||||||
pub fn is_leaf(&self) -> bool {
|
pub fn is_leaf(&self) -> bool {
|
||||||
matches!(*self, MenuItem::Leaf(_, _))
|
matches!(*self, Item::Leaf { .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if `self` is a subtree.
|
/// Returns `true` if `self` is a subtree.
|
||||||
pub fn is_subtree(&self) -> bool {
|
pub fn is_subtree(&self) -> bool {
|
||||||
matches!(*self, MenuItem::Subtree(_, _))
|
matches!(*self, Item::Subtree { .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a mutable reference to the subtree, if applicable.
|
/// Return a mutable reference to the subtree, if applicable.
|
||||||
///
|
///
|
||||||
/// Returns `None` if `self` is not a `MenuItem::Subtree`.
|
/// Returns `None` if `self` is not a `Item::Subtree`.
|
||||||
pub fn as_subtree(&mut self) -> Option<&mut MenuTree> {
|
pub fn as_subtree(&mut self) -> Option<&mut Tree> {
|
||||||
match *self {
|
match *self {
|
||||||
MenuItem::Subtree(_, ref mut tree) => Some(Rc::make_mut(tree)),
|
Item::Subtree { ref mut tree, .. } => Some(Rc::make_mut(tree)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuTree {
|
impl Tree {
|
||||||
/// Creates a new, empty tree.
|
/// Creates a new, empty tree.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
@ -87,13 +166,13 @@ impl MenuTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts an item at the given position.
|
/// Inserts an item at the given position.
|
||||||
pub fn insert(&mut self, i: usize, item: MenuItem) {
|
pub fn insert(&mut self, i: usize, item: Item) {
|
||||||
self.children.insert(i, item);
|
self.children.insert(i, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a delimiter at the given position.
|
/// Inserts a delimiter at the given position.
|
||||||
pub fn insert_delimiter(&mut self, i: usize) {
|
pub fn insert_delimiter(&mut self, i: usize) {
|
||||||
self.insert(i, MenuItem::Delimiter);
|
self.insert(i, Item::Delimiter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a delimiter to the end of this tree.
|
/// Adds a delimiter to the end of this tree.
|
||||||
@ -108,100 +187,123 @@ 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<S, F>(&mut self, title: S, cb: F)
|
pub fn add_leaf<S, F>(&mut self, label: S, cb: F)
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
F: 'static + Fn(&mut Cursive),
|
F: 'static + Fn(&mut Cursive),
|
||||||
{
|
{
|
||||||
let i = self.children.len();
|
let i = self.children.len();
|
||||||
self.insert_leaf(i, title, cb);
|
self.insert_leaf(i, label, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a leaf at the given position.
|
/// Inserts a leaf at the given position.
|
||||||
pub fn insert_leaf<S, F>(&mut self, i: usize, title: S, cb: F)
|
pub fn insert_leaf<S, F>(&mut self, i: usize, label: S, cb: F)
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
F: 'static + Fn(&mut Cursive),
|
F: 'static + Fn(&mut Cursive),
|
||||||
{
|
{
|
||||||
let title = title.into();
|
let label = label.into();
|
||||||
self.insert(i, MenuItem::Leaf(title, Callback::from_fn(cb)));
|
self.insert(
|
||||||
|
i,
|
||||||
|
Item::Leaf {
|
||||||
|
label,
|
||||||
|
cb: Callback::from_fn(cb),
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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<S, F>(self, title: S, cb: F) -> Self
|
pub fn leaf<S, F>(self, label: S, cb: F) -> Self
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
F: 'static + Fn(&mut Cursive),
|
F: 'static + Fn(&mut Cursive),
|
||||||
{
|
{
|
||||||
self.with(|menu| menu.add_leaf(title, cb))
|
self.with(|menu| menu.add_leaf(label, cb))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a subtree at the given position.
|
/// Inserts a subtree at the given position.
|
||||||
pub fn insert_subtree<S>(&mut self, i: usize, title: S, tree: MenuTree)
|
pub fn insert_subtree<S>(&mut self, i: usize, label: S, tree: Tree)
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
let title = title.into();
|
let label = label.into();
|
||||||
let tree = MenuItem::Subtree(title, Rc::new(tree));
|
let tree = Item::Subtree {
|
||||||
|
label,
|
||||||
|
tree: Rc::new(tree),
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
self.insert(i, tree);
|
self.insert(i, tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds an item to the end of this tree.
|
||||||
|
///
|
||||||
|
/// Chainable variant.
|
||||||
|
pub fn item(self, item: Item) -> Self {
|
||||||
|
self.with(|s| s.add_item(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds an item to the end of this tree.
|
||||||
|
pub fn add_item(&mut self, item: Item) {
|
||||||
|
let i = self.children.len();
|
||||||
|
self.insert(i, item);
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds a submenu to the end of this tree.
|
/// Adds a submenu to the end of this tree.
|
||||||
pub fn add_subtree<S>(&mut self, title: S, tree: MenuTree)
|
pub fn add_subtree<S>(&mut self, label: S, tree: Tree)
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
let i = self.children.len();
|
let i = self.children.len();
|
||||||
self.insert_subtree(i, title, tree);
|
self.insert_subtree(i, label, 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<S>(self, title: S, tree: MenuTree) -> Self
|
pub fn subtree<S>(self, label: S, tree: Tree) -> Self
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
self.with(|menu| menu.add_subtree(title, tree))
|
self.with(|menu| menu.add_subtree(label, tree))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Looks for the child at the given position.
|
/// Looks for the child at the given position.
|
||||||
///
|
///
|
||||||
/// Returns `None` if `i >= self.len()`.
|
/// Returns `None` if `i >= self.len()`.
|
||||||
pub fn get_mut(&mut self, i: usize) -> Option<&mut MenuItem> {
|
pub fn get_mut(&mut self, i: usize) -> Option<&mut Item> {
|
||||||
self.children.get_mut(i)
|
self.children.get_mut(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the item at the given position.
|
/// Returns the item at the given position.
|
||||||
///
|
///
|
||||||
/// Returns `None` if `i > self.len()` or if the item is not a subtree.
|
/// Returns `None` if `i > self.len()` or if the item is not a subtree.
|
||||||
pub fn get_subtree(&mut self, i: usize) -> Option<&mut MenuTree> {
|
pub fn get_subtree(&mut self, i: usize) -> Option<&mut Tree> {
|
||||||
self.get_mut(i).and_then(MenuItem::as_subtree)
|
self.get_mut(i).and_then(Item::as_subtree)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Looks for a child with the given title.
|
/// Looks for a child with the given label.
|
||||||
///
|
///
|
||||||
/// Returns `None` if no such label was found.
|
/// Returns `None` if no such label was found.
|
||||||
pub fn find_item(&mut self, title: &str) -> Option<&mut MenuItem> {
|
pub fn find_item(&mut self, label: &str) -> Option<&mut Item> {
|
||||||
self.children
|
self.children
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.find(|child| child.label() == title)
|
.find(|child| child.label() == label)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Looks for a subtree with the given title.
|
/// Looks for a subtree with the given label.
|
||||||
pub fn find_subtree(&mut self, title: &str) -> Option<&mut MenuTree> {
|
pub fn find_subtree(&mut self, label: &str) -> Option<&mut Tree> {
|
||||||
self.children
|
self.children
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.filter(|child| child.label() == title)
|
.filter(|child| child.label() == label)
|
||||||
.filter_map(MenuItem::as_subtree)
|
.find_map(Item::as_subtree)
|
||||||
.next()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the position of a child with the given label.
|
/// Returns the position of a child with the given label.
|
||||||
///
|
///
|
||||||
/// Returns `None` if no such label was found.
|
/// Returns `None` if no such label was found.
|
||||||
pub fn find_position(&mut self, title: &str) -> Option<usize> {
|
pub fn find_position(&mut self, label: &str) -> Option<usize> {
|
||||||
self.children
|
self.children
|
||||||
.iter()
|
.iter()
|
||||||
.position(|child| child.label() == title)
|
.position(|child| child.label() == label)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the item at the given position.
|
/// Removes the item at the given position.
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
use crate::align::Align;
|
use crate::{
|
||||||
use crate::event::{
|
align::Align,
|
||||||
Callback, Event, EventResult, Key, MouseButton, MouseEvent,
|
event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent},
|
||||||
|
menu,
|
||||||
|
rect::Rect,
|
||||||
|
theme::ColorStyle,
|
||||||
|
view::scroll,
|
||||||
|
view::{Position, View},
|
||||||
|
views::OnEventView,
|
||||||
|
Cursive, Printer, Vec2, With,
|
||||||
};
|
};
|
||||||
use crate::menu::{MenuItem, MenuTree};
|
|
||||||
use crate::rect::Rect;
|
|
||||||
use crate::view::scroll;
|
|
||||||
use crate::view::{Position, View};
|
|
||||||
use crate::views::OnEventView;
|
|
||||||
use crate::Cursive;
|
|
||||||
use crate::Printer;
|
|
||||||
use crate::Vec2;
|
|
||||||
use crate::With;
|
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
@ -23,7 +21,7 @@ use unicode_width::UnicodeWidthStr;
|
|||||||
/// [1]: crate::views::SelectView::popup()
|
/// [1]: crate::views::SelectView::popup()
|
||||||
/// [2]: crate::Cursive::menubar()
|
/// [2]: crate::Cursive::menubar()
|
||||||
pub struct MenuPopup {
|
pub struct MenuPopup {
|
||||||
menu: Rc<MenuTree>,
|
menu: Rc<menu::Tree>,
|
||||||
focus: usize,
|
focus: usize,
|
||||||
scroll_core: scroll::Core,
|
scroll_core: scroll::Core,
|
||||||
align: Align,
|
align: Align,
|
||||||
@ -38,7 +36,7 @@ impl_scroller!(MenuPopup::scroll_core);
|
|||||||
|
|
||||||
impl MenuPopup {
|
impl MenuPopup {
|
||||||
/// Creates a new `MenuPopup` using the given menu tree.
|
/// Creates a new `MenuPopup` using the given menu tree.
|
||||||
pub fn new(menu: Rc<MenuTree>) -> Self {
|
pub fn new(menu: Rc<menu::Tree>) -> Self {
|
||||||
MenuPopup {
|
MenuPopup {
|
||||||
menu,
|
menu,
|
||||||
focus: 0,
|
focus: 0,
|
||||||
@ -66,11 +64,11 @@ impl MenuPopup {
|
|||||||
self.focus
|
self.focus
|
||||||
}
|
}
|
||||||
|
|
||||||
fn item_width(item: &MenuItem) -> usize {
|
fn item_width(item: &menu::Item) -> usize {
|
||||||
match *item {
|
match *item {
|
||||||
MenuItem::Delimiter => 1,
|
menu::Item::Delimiter => 1,
|
||||||
MenuItem::Leaf(ref title, _) => title.width(),
|
menu::Item::Leaf { ref label, .. } => label.width(),
|
||||||
MenuItem::Subtree(ref title, _) => title.width() + 3,
|
menu::Item::Subtree { ref label, .. } => label.width() + 3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +130,7 @@ impl MenuPopup {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.menu.children[self.focus].is_delimiter() {
|
if self.menu.children[self.focus].is_enabled() {
|
||||||
n -= 1;
|
n -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,7 +147,7 @@ impl MenuPopup {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.menu.children[self.focus].is_delimiter() {
|
if self.menu.children[self.focus].is_enabled() {
|
||||||
n -= 1;
|
n -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,7 +155,7 @@ impl MenuPopup {
|
|||||||
|
|
||||||
fn submit(&mut self) -> EventResult {
|
fn submit(&mut self) -> EventResult {
|
||||||
match self.menu.children[self.focus] {
|
match self.menu.children[self.focus] {
|
||||||
MenuItem::Leaf(_, ref cb) => {
|
menu::Item::Leaf { ref cb, .. } => {
|
||||||
let cb = cb.clone();
|
let cb = cb.clone();
|
||||||
let action_cb = self.on_action.clone();
|
let action_cb = self.on_action.clone();
|
||||||
EventResult::with_cb(move |s| {
|
EventResult::with_cb(move |s| {
|
||||||
@ -171,7 +169,7 @@ impl MenuPopup {
|
|||||||
cb.clone()(s);
|
cb.clone()(s);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
MenuItem::Subtree(_, ref tree) => self.make_subtree_cb(tree),
|
menu::Item::Subtree { ref tree, .. } => self.make_subtree_cb(tree),
|
||||||
_ => unreachable!("Delimiters cannot be submitted."),
|
_ => unreachable!("Delimiters cannot be submitted."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,7 +184,7 @@ impl MenuPopup {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_subtree_cb(&self, tree: &Rc<MenuTree>) -> EventResult {
|
fn make_subtree_cb(&self, tree: &Rc<menu::Tree>) -> EventResult {
|
||||||
let tree = Rc::clone(tree);
|
let tree = Rc::clone(tree);
|
||||||
let max_width = 4 + self
|
let max_width = 4 + self
|
||||||
.menu
|
.menu
|
||||||
@ -239,14 +237,14 @@ impl MenuPopup {
|
|||||||
if self.menu.children[self.focus].is_subtree() =>
|
if self.menu.children[self.focus].is_subtree() =>
|
||||||
{
|
{
|
||||||
return match self.menu.children[self.focus] {
|
return match self.menu.children[self.focus] {
|
||||||
MenuItem::Subtree(_, ref tree) => {
|
menu::Item::Subtree { ref tree, .. } => {
|
||||||
self.make_subtree_cb(tree)
|
self.make_subtree_cb(tree)
|
||||||
}
|
}
|
||||||
_ => unreachable!("Child is a subtree"),
|
_ => unreachable!("Child is a subtree"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Event::Key(Key::Enter)
|
Event::Key(Key::Enter)
|
||||||
if !self.menu.children[self.focus].is_delimiter() =>
|
if self.menu.children[self.focus].is_enabled() =>
|
||||||
{
|
{
|
||||||
return self.submit();
|
return self.submit();
|
||||||
}
|
}
|
||||||
@ -260,7 +258,7 @@ impl MenuPopup {
|
|||||||
// Now `position` is relative to the top-left of the content.
|
// Now `position` is relative to the top-left of the content.
|
||||||
let focus = position.y;
|
let focus = position.y;
|
||||||
if focus < self.menu.len()
|
if focus < self.menu.len()
|
||||||
&& !self.menu.children[focus].is_delimiter()
|
&& self.menu.children[focus].is_enabled()
|
||||||
{
|
{
|
||||||
self.focus = focus;
|
self.focus = focus;
|
||||||
}
|
}
|
||||||
@ -270,7 +268,7 @@ impl MenuPopup {
|
|||||||
event: MouseEvent::Release(MouseButton::Left),
|
event: MouseEvent::Release(MouseButton::Left),
|
||||||
position,
|
position,
|
||||||
offset,
|
offset,
|
||||||
} if !self.menu.children[self.focus].is_delimiter()
|
} if self.menu.children[self.focus].is_enabled()
|
||||||
&& position
|
&& position
|
||||||
.checked_sub(offset)
|
.checked_sub(offset)
|
||||||
.map(|position| position.y == self.focus)
|
.map(|position| position.y == self.focus)
|
||||||
@ -335,14 +333,23 @@ impl View for MenuPopup {
|
|||||||
let printer = printer.shrinked_centered((2, 2));
|
let printer = printer.shrinked_centered((2, 2));
|
||||||
|
|
||||||
scroll::draw_lines(self, &printer, |s, printer, i| {
|
scroll::draw_lines(self, &printer, |s, printer, i| {
|
||||||
printer.with_selection(i == s.focus, |printer| {
|
|
||||||
let item = &s.menu.children[i];
|
let item = &s.menu.children[i];
|
||||||
|
let enabled =
|
||||||
|
printer.enabled && (item.is_enabled() || item.is_delimiter());
|
||||||
|
let color = if !enabled {
|
||||||
|
ColorStyle::secondary()
|
||||||
|
} else if i == s.focus {
|
||||||
|
ColorStyle::highlight()
|
||||||
|
} else {
|
||||||
|
ColorStyle::primary()
|
||||||
|
};
|
||||||
|
printer.with_style(color, |printer| {
|
||||||
match *item {
|
match *item {
|
||||||
MenuItem::Delimiter => {
|
menu::Item::Delimiter => {
|
||||||
// printer.print_hdelim((0, 0), printer.size.x)
|
// printer.print_hdelim((0, 0), printer.size.x)
|
||||||
printer.print_hline((0, 0), printer.size.x, "─");
|
printer.print_hline((0, 0), printer.size.x, "─");
|
||||||
}
|
}
|
||||||
MenuItem::Subtree(ref label, _) => {
|
menu::Item::Subtree { ref label, .. } => {
|
||||||
if printer.size.x < 4 {
|
if printer.size.x < 4 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -351,7 +358,7 @@ impl View for MenuPopup {
|
|||||||
let x = printer.size.x.saturating_sub(3);
|
let x = printer.size.x.saturating_sub(3);
|
||||||
printer.print((x, 0), ">>");
|
printer.print((x, 0), ">>");
|
||||||
}
|
}
|
||||||
MenuItem::Leaf(ref label, _) => {
|
menu::Item::Leaf { ref label, .. } => {
|
||||||
if printer.size.x < 2 {
|
if printer.size.x < 2 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::direction;
|
use crate::direction;
|
||||||
use crate::event::*;
|
use crate::event::*;
|
||||||
use crate::menu::{MenuItem, MenuTree};
|
use crate::menu;
|
||||||
use crate::rect::Rect;
|
use crate::rect::Rect;
|
||||||
use crate::theme::ColorStyle;
|
use crate::theme::ColorStyle;
|
||||||
use crate::view::{Position, View};
|
use crate::view::{Position, View};
|
||||||
@ -34,7 +34,7 @@ enum State {
|
|||||||
/// [`Cursive`]: crate::Cursive::menubar
|
/// [`Cursive`]: crate::Cursive::menubar
|
||||||
pub struct Menubar {
|
pub struct Menubar {
|
||||||
/// Menu items in this menubar.
|
/// Menu items in this menubar.
|
||||||
root: MenuTree,
|
root: menu::Tree,
|
||||||
|
|
||||||
/// TODO: move this out of this view.
|
/// TODO: move this out of this view.
|
||||||
pub autohide: bool,
|
pub autohide: bool,
|
||||||
@ -50,7 +50,7 @@ impl Menubar {
|
|||||||
/// Creates a new, empty menubar.
|
/// Creates a new, empty menubar.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Menubar {
|
Menubar {
|
||||||
root: MenuTree::new(),
|
root: menu::Tree::new(),
|
||||||
autohide: true,
|
autohide: true,
|
||||||
state: State::Inactive,
|
state: State::Inactive,
|
||||||
focus: 0,
|
focus: 0,
|
||||||
@ -77,11 +77,23 @@ impl Menubar {
|
|||||||
!self.autohide || self.state != State::Inactive
|
!self.autohide || self.state != State::Inactive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a new item to the menubar.
|
||||||
|
pub fn insert(&mut self, i: usize, item: menu::Item) -> &mut Self {
|
||||||
|
self.root.insert(i, item);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new item to the menubar.
|
||||||
|
pub fn item(&mut self, item: menu::Item) -> &mut Self {
|
||||||
|
let i = self.root.len();
|
||||||
|
self.insert(i, item)
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds a new item to the menubar.
|
/// Adds a new item to the 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_subtree<S>(&mut self, title: S, menu: MenuTree) -> &mut Self
|
pub fn add_subtree<S>(&mut self, title: S, menu: menu::Tree) -> &mut Self
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
@ -110,7 +122,7 @@ impl Menubar {
|
|||||||
&mut self,
|
&mut self,
|
||||||
i: usize,
|
i: usize,
|
||||||
title: S,
|
title: S,
|
||||||
menu: MenuTree,
|
menu: menu::Tree,
|
||||||
) -> &mut Self
|
) -> &mut Self
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
@ -158,12 +170,12 @@ impl Menubar {
|
|||||||
/// Returns the item at the given position.
|
/// Returns the item at the given position.
|
||||||
///
|
///
|
||||||
/// Returns `None` if `i > self.len()`
|
/// Returns `None` if `i > self.len()`
|
||||||
pub fn get_subtree(&mut self, i: usize) -> Option<&mut MenuTree> {
|
pub fn get_subtree(&mut self, i: usize) -> Option<&mut menu::Tree> {
|
||||||
self.root.get_subtree(i)
|
self.root.get_subtree(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Looks for an item with the given label.
|
/// Looks for an item with the given label.
|
||||||
pub fn find_subtree(&mut self, label: &str) -> Option<&mut MenuTree> {
|
pub fn find_subtree(&mut self, label: &str) -> Option<&mut menu::Tree> {
|
||||||
self.root.find_subtree(label)
|
self.root.find_subtree(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,12 +209,12 @@ impl Menubar {
|
|||||||
|
|
||||||
fn select_child(&mut self, open_only: bool) -> EventResult {
|
fn select_child(&mut self, open_only: bool) -> EventResult {
|
||||||
match self.root.children[self.focus] {
|
match self.root.children[self.focus] {
|
||||||
MenuItem::Leaf(_, ref cb) if !open_only => {
|
menu::Item::Leaf { ref cb, .. } if !open_only => {
|
||||||
// Go inactive after an action.
|
// Go inactive after an action.
|
||||||
self.state = State::Inactive;
|
self.state = State::Inactive;
|
||||||
EventResult::Consumed(Some(cb.clone()))
|
EventResult::Consumed(Some(cb.clone()))
|
||||||
}
|
}
|
||||||
MenuItem::Subtree(_, ref tree) => {
|
menu::Item::Subtree { ref tree, .. } => {
|
||||||
// First, we need a new Rc to send the callback,
|
// First, we need a new Rc to send the callback,
|
||||||
// since we don't know when it will be called.
|
// since we don't know when it will be called.
|
||||||
let menu = Rc::clone(tree);
|
let menu = Rc::clone(tree);
|
||||||
@ -226,7 +238,7 @@ impl Menubar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_child(s: &mut Cursive, offset: Vec2, menu: Rc<MenuTree>) {
|
fn show_child(s: &mut Cursive, offset: Vec2, menu: Rc<menu::Tree>) {
|
||||||
// Adds a new layer located near the item title with the menu popup.
|
// Adds a new layer located near the item title with the menu popup.
|
||||||
// Also adds two key callbacks on this new view, to handle `left` and
|
// Also adds two key callbacks on this new view, to handle `left` and
|
||||||
// `right` key presses.
|
// `right` key presses.
|
||||||
@ -275,16 +287,30 @@ impl View for Menubar {
|
|||||||
// TODO: draw the rest
|
// TODO: draw the rest
|
||||||
let mut offset = 1;
|
let mut offset = 1;
|
||||||
for (i, item) in self.root.children.iter().enumerate() {
|
for (i, item) in self.root.children.iter().enumerate() {
|
||||||
let title = item.label();
|
let label = item.label();
|
||||||
|
|
||||||
|
// We print disabled items differently, except delimiters,
|
||||||
|
// which are still white.
|
||||||
|
let enabled =
|
||||||
|
printer.enabled && (item.is_enabled() || item.is_delimiter());
|
||||||
|
|
||||||
// We don't want to show HighlightInactive when we're not selected,
|
// We don't want to show HighlightInactive when we're not selected,
|
||||||
// because it's ugly on the menubar.
|
// because it's ugly on the menubar.
|
||||||
let selected =
|
let selected =
|
||||||
(self.state != State::Inactive) && (i == self.focus);
|
(self.state != State::Inactive) && (i == self.focus);
|
||||||
printer.with_selection(selected, |printer| {
|
|
||||||
printer.print((offset, 0), &format!(" {} ", title));
|
let color = if !enabled {
|
||||||
|
ColorStyle::secondary()
|
||||||
|
} else if selected {
|
||||||
|
ColorStyle::highlight()
|
||||||
|
} else {
|
||||||
|
ColorStyle::primary()
|
||||||
|
};
|
||||||
|
|
||||||
|
printer.with_style(color, |printer| {
|
||||||
|
printer.print((offset, 0), &format!(" {} ", label));
|
||||||
});
|
});
|
||||||
offset += title.width() + 2;
|
offset += label.width() + 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,12 +321,13 @@ impl View for Menubar {
|
|||||||
return EventResult::with_cb(Cursive::clear);
|
return EventResult::with_cb(Cursive::clear);
|
||||||
}
|
}
|
||||||
Event::Key(Key::Left) => loop {
|
Event::Key(Key::Left) => loop {
|
||||||
|
// TODO: fix endless loop if nothing is enabled?
|
||||||
if self.focus > 0 {
|
if self.focus > 0 {
|
||||||
self.focus -= 1;
|
self.focus -= 1;
|
||||||
} else {
|
} else {
|
||||||
self.focus = self.root.len() - 1;
|
self.focus = self.root.len() - 1;
|
||||||
}
|
}
|
||||||
if !self.root.children[self.focus].is_delimiter() {
|
if self.root.children[self.focus].is_enabled() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -310,7 +337,7 @@ impl View for Menubar {
|
|||||||
} else {
|
} else {
|
||||||
self.focus = 0;
|
self.focus = 0;
|
||||||
}
|
}
|
||||||
if !self.root.children[self.focus].is_delimiter() {
|
if self.root.children[self.focus].is_enabled() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -329,7 +356,7 @@ impl View for Menubar {
|
|||||||
.checked_sub(offset)
|
.checked_sub(offset)
|
||||||
.and_then(|pos| self.child_at(pos.x))
|
.and_then(|pos| self.child_at(pos.x))
|
||||||
{
|
{
|
||||||
if !self.root.children[child].is_delimiter() {
|
if self.root.children[child].is_enabled() {
|
||||||
self.focus = child;
|
self.focus = child;
|
||||||
if btn == MouseButton::Left {
|
if btn == MouseButton::Left {
|
||||||
return self.select_child(true);
|
return self.select_child(true);
|
||||||
|
@ -3,7 +3,7 @@ use crate::direction::Direction;
|
|||||||
use crate::event::{
|
use crate::event::{
|
||||||
Callback, Event, EventResult, Key, MouseButton, MouseEvent,
|
Callback, Event, EventResult, Key, MouseButton, MouseEvent,
|
||||||
};
|
};
|
||||||
use crate::menu::MenuTree;
|
use crate::menu;
|
||||||
use crate::rect::Rect;
|
use crate::rect::Rect;
|
||||||
use crate::theme::ColorStyle;
|
use crate::theme::ColorStyle;
|
||||||
use crate::utils::markup::StyledString;
|
use crate::utils::markup::StyledString;
|
||||||
@ -713,7 +713,7 @@ impl<T: 'static> SelectView<T> {
|
|||||||
fn open_popup(&mut self) -> EventResult {
|
fn open_popup(&mut self) -> EventResult {
|
||||||
// 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?
|
||||||
let mut tree = MenuTree::new();
|
let mut tree = menu::Tree::new();
|
||||||
for (i, item) in self.items.iter().enumerate() {
|
for (i, item) in self.items.iter().enumerate() {
|
||||||
let focus = Rc::clone(&self.focus);
|
let focus = Rc::clone(&self.focus);
|
||||||
let on_submit = self.on_submit.as_ref().cloned();
|
let on_submit = self.on_submit.as_ref().cloned();
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
use cursive::event::Key;
|
use cursive::{event::Key, menu, traits::*, views::Dialog};
|
||||||
use cursive::menu::MenuTree;
|
|
||||||
use cursive::traits::*;
|
|
||||||
use cursive::views::Dialog;
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
// This examples shows how to configure and use a menubar at the top of the
|
// This examples shows how to configure and use a menubar at the top of the
|
||||||
@ -18,7 +15,7 @@ fn main() {
|
|||||||
// We add a new "File" tree
|
// We add a new "File" tree
|
||||||
.add_subtree(
|
.add_subtree(
|
||||||
"File",
|
"File",
|
||||||
MenuTree::new()
|
menu::Tree::new()
|
||||||
// Trees are made of leaves, with are directly actionable...
|
// Trees are made of leaves, with are directly actionable...
|
||||||
.leaf("New", move |s| {
|
.leaf("New", move |s| {
|
||||||
// Here we use the counter to add an entry
|
// Here we use the counter to add an entry
|
||||||
@ -39,11 +36,13 @@ fn main() {
|
|||||||
"Recent",
|
"Recent",
|
||||||
// The `.with()` method can help when running loops
|
// The `.with()` method can help when running loops
|
||||||
// within builder patterns.
|
// within builder patterns.
|
||||||
MenuTree::new().with(|tree| {
|
menu::Tree::new().with(|tree| {
|
||||||
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_item(menu::Item::leaf(format!("Item {}", i), |_| ()).with(|s| {
|
||||||
|
if i % 5 == 0 { s.disable(); }
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -58,10 +57,10 @@ fn main() {
|
|||||||
)
|
)
|
||||||
.add_subtree(
|
.add_subtree(
|
||||||
"Help",
|
"Help",
|
||||||
MenuTree::new()
|
menu::Tree::new()
|
||||||
.subtree(
|
.subtree(
|
||||||
"Help",
|
"Help",
|
||||||
MenuTree::new()
|
menu::Tree::new()
|
||||||
.leaf("General", |s| {
|
.leaf("General", |s| {
|
||||||
s.add_layer(Dialog::info("Help message!"))
|
s.add_layer(Dialog::info("Help message!"))
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user