Add enabled state to menu items

This commit is contained in:
Alexandre Bury 2021-02-05 13:05:14 -08:00
parent 06d64be0a0
commit 6543287704
5 changed files with 246 additions and 111 deletions

View File

@ -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.

View File

@ -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;
} }

View File

@ -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);

View File

@ -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();

View File

@ -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!"))
}) })