mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Working menubar example
Also update callback: use Rc instead of Box
This commit is contained in:
parent
1863e48b07
commit
1d54764cdb
@ -1,24 +1,31 @@
|
|||||||
extern crate cursive;
|
extern crate cursive;
|
||||||
|
|
||||||
use cursive::Cursive;
|
use cursive::Cursive;
|
||||||
|
use cursive::menu::MenuTree;
|
||||||
use cursive::view::Dialog;
|
use cursive::view::Dialog;
|
||||||
|
use cursive::view::TextView;
|
||||||
use cursive::event::Key;
|
use cursive::event::Key;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
||||||
let mut siv = Cursive::new();
|
let mut siv = Cursive::new();
|
||||||
|
|
||||||
siv.menu()
|
siv.menubar()
|
||||||
.new_subtree("File")
|
.add("File",
|
||||||
|
MenuTree::new()
|
||||||
.leaf("New", |s| s.add_layer(Dialog::info("New file!")))
|
.leaf("New", |s| s.add_layer(Dialog::info("New file!")))
|
||||||
.leaf("Quit", |s| s.quit());
|
.leaf("Quit", |s| s.quit()))
|
||||||
|
.add("Help",
|
||||||
siv.menu()
|
MenuTree::new()
|
||||||
.new_subtree("Help")
|
|
||||||
.leaf("Help", |s| s.add_layer(Dialog::info("Help message!")))
|
.leaf("Help", |s| s.add_layer(Dialog::info("Help message!")))
|
||||||
.leaf("About", |s| s.add_layer(Dialog::info("Cursive v0.0.0")));
|
.leaf("About",
|
||||||
|
|s| s.add_layer(Dialog::info("Cursive v0.0.0"))));
|
||||||
|
|
||||||
siv.add_global_callback(Key::F(10), |s| s.select_menu());
|
// siv.set_autohide_menu(false);
|
||||||
|
|
||||||
|
siv.add_global_callback(Key::F(10), |s| s.select_menubar());
|
||||||
|
|
||||||
|
siv.add_layer(Dialog::new(TextView::new("Hit <F10> to show the menu!")));
|
||||||
|
|
||||||
siv.run();
|
siv.run();
|
||||||
}
|
}
|
||||||
|
10
src/event.rs
10
src/event.rs
@ -7,7 +7,7 @@ use Cursive;
|
|||||||
|
|
||||||
/// Callback is a function that can be triggered by an event.
|
/// Callback is a function that can be triggered by an event.
|
||||||
/// It has a mutable access to the cursive root.
|
/// It has a mutable access to the cursive root.
|
||||||
pub type Callback = Box<Fn(&mut Cursive)>;
|
pub type Callback = Rc<Fn(&mut Cursive)>;
|
||||||
|
|
||||||
/// Answer to an event notification.
|
/// Answer to an event notification.
|
||||||
/// The event can be consumed or ignored.
|
/// The event can be consumed or ignored.
|
||||||
@ -15,7 +15,13 @@ pub enum EventResult {
|
|||||||
/// The event was ignored. The parent can keep handling it.
|
/// The event was ignored. The parent can keep handling it.
|
||||||
Ignored,
|
Ignored,
|
||||||
/// The event was consumed. An optionnal callback to run is attached.
|
/// The event was consumed. An optionnal callback to run is attached.
|
||||||
Consumed(Option<Rc<Callback>>),
|
Consumed(Option<Callback>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventResult {
|
||||||
|
pub fn with_cb<F: 'static + Fn(&mut Cursive)>(f: F) -> Self {
|
||||||
|
EventResult::Consumed(Some(Rc::new(f)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a key, or a combination of keys.
|
/// Represents a key, or a combination of keys.
|
||||||
|
30
src/lib.rs
30
src/lib.rs
@ -69,8 +69,8 @@ pub type ScreenId = usize;
|
|||||||
pub struct Cursive {
|
pub struct Cursive {
|
||||||
theme: theme::Theme,
|
theme: theme::Theme,
|
||||||
screens: Vec<StackView>,
|
screens: Vec<StackView>,
|
||||||
global_callbacks: HashMap<Event, Rc<Callback>>,
|
global_callbacks: HashMap<Event, Callback>,
|
||||||
menu: menubar::Menubar,
|
menubar: menubar::Menubar,
|
||||||
|
|
||||||
active_screen: ScreenId,
|
active_screen: ScreenId,
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ impl Cursive {
|
|||||||
theme: theme,
|
theme: theme,
|
||||||
screens: Vec::new(),
|
screens: Vec::new(),
|
||||||
global_callbacks: HashMap::new(),
|
global_callbacks: HashMap::new(),
|
||||||
menu: menubar::Menubar::new(),
|
menubar: menubar::Menubar::new(),
|
||||||
active_screen: 0,
|
active_screen: 0,
|
||||||
running: true,
|
running: true,
|
||||||
};
|
};
|
||||||
@ -111,8 +111,8 @@ impl Cursive {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Selects the menubar
|
/// Selects the menubar
|
||||||
pub fn select_menu(&mut self) {
|
pub fn select_menubar(&mut self) {
|
||||||
self.menu.selected = true;
|
self.menubar.take_focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the menubar autohide_menubar feature.
|
/// Sets the menubar autohide_menubar feature.
|
||||||
@ -120,12 +120,12 @@ impl Cursive {
|
|||||||
/// * When enabled, the menu is only visible when selected.
|
/// * When enabled, the menu is only visible when selected.
|
||||||
/// * When disabled, the menu is always visible and reserves the top row.
|
/// * When disabled, the menu is always visible and reserves the top row.
|
||||||
pub fn set_autohide_menu(&mut self, autohide: bool) {
|
pub fn set_autohide_menu(&mut self, autohide: bool) {
|
||||||
self.menu.autohide = autohide;
|
self.menubar.autohide = autohide;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve the menu tree used by the menubar.
|
/// Retrieve the menu tree used by the menubar.
|
||||||
pub fn menu(&mut self) -> &mut menu::MenuTree {
|
pub fn menubar(&mut self) -> &mut menubar::Menubar {
|
||||||
&mut self.menu.menu
|
&mut self.menubar
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the currently used theme
|
/// Returns the currently used theme
|
||||||
@ -206,7 +206,7 @@ impl Cursive {
|
|||||||
pub fn add_global_callback<F, E: ToEvent>(&mut self, event: E, cb: F)
|
pub fn add_global_callback<F, E: ToEvent>(&mut self, event: E, cb: F)
|
||||||
where F: Fn(&mut Cursive) + 'static
|
where F: Fn(&mut Cursive) + 'static
|
||||||
{
|
{
|
||||||
self.global_callbacks.insert(event.to_event(), Rc::new(Box::new(cb)));
|
self.global_callbacks.insert(event.to_event(), Rc::new(cb));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenient method to add a layer to the current screen.
|
/// Convenient method to add a layer to the current screen.
|
||||||
@ -251,18 +251,18 @@ impl Cursive {
|
|||||||
|
|
||||||
// Draw the currently active screen
|
// Draw the currently active screen
|
||||||
// If the menubar is active, nothing else can be.
|
// If the menubar is active, nothing else can be.
|
||||||
let offset = if self.menu.autohide {
|
let offset = if self.menubar.autohide {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
1
|
1
|
||||||
};
|
};
|
||||||
let selected = self.menu.selected;
|
let selected = self.menubar.receive_events();
|
||||||
self.screen_mut()
|
self.screen_mut()
|
||||||
.draw(&printer.sub_printer(Vec2::new(0, offset), printer.size, !selected));
|
.draw(&printer.sub_printer(Vec2::new(0, offset), printer.size, !selected));
|
||||||
|
|
||||||
// Draw the menubar?
|
// Draw the menubar?
|
||||||
if self.menu.selected || !self.menu.autohide {
|
if self.menubar.visible() {
|
||||||
self.menu.draw(&printer);
|
self.menubar.draw(&printer);
|
||||||
}
|
}
|
||||||
|
|
||||||
B::refresh();
|
B::refresh();
|
||||||
@ -295,8 +295,8 @@ impl Cursive {
|
|||||||
// * Menubar (if active)
|
// * Menubar (if active)
|
||||||
// * Current screen (top layer)
|
// * Current screen (top layer)
|
||||||
// * Global callbacks
|
// * Global callbacks
|
||||||
if self.menu.selected {
|
if self.menubar.receive_events() {
|
||||||
if let Some(cb) = self.menu.on_event(event) {
|
if let Some(cb) = self.menubar.on_event(event) {
|
||||||
cb(self);
|
cb(self);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
38
src/menu.rs
38
src/menu.rs
@ -8,8 +8,8 @@ pub struct MenuTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum MenuItem {
|
pub enum MenuItem {
|
||||||
Leaf(String, Rc<Callback>),
|
Leaf(String, Callback),
|
||||||
Subtree(String, Box<MenuTree>),
|
Subtree(String, Rc<MenuTree>),
|
||||||
Delimiter,
|
Delimiter,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,6 +21,13 @@ impl MenuItem {
|
|||||||
MenuItem::Subtree(ref label, _) => label,
|
MenuItem::Subtree(ref label, _) => label,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_delimiter(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
MenuItem::Delimiter => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuTree {
|
impl MenuTree {
|
||||||
@ -40,37 +47,20 @@ impl MenuTree {
|
|||||||
self.children.is_empty()
|
self.children.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_delimiter(&mut self) {
|
|
||||||
self.children.push(MenuItem::Delimiter);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delimiter(mut self) -> Self {
|
pub fn delimiter(mut self) -> Self {
|
||||||
self.add_delimiter();
|
self.children.push(MenuItem::Delimiter);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn leaf<F: 'static + Fn(&mut Cursive)>(&mut self, title: &str, cb: F) -> &mut Self {
|
pub fn leaf<F: 'static + Fn(&mut Cursive)>(mut self, title: &str, cb: F) -> Self {
|
||||||
self.children
|
self.children
|
||||||
.push(MenuItem::Leaf(title.to_string(), Rc::new(Box::new(cb))));
|
.push(MenuItem::Leaf(title.to_string(), Rc::new(cb)));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_subtree(&mut self, title: &str, tree: MenuTree) -> &mut Self {
|
|
||||||
self.children
|
|
||||||
.push(MenuItem::Subtree(title.to_string(), Box::new(tree)));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_subtree(&mut self, title: &str) -> &mut MenuTree {
|
|
||||||
self.add_subtree(title, MenuTree::new());
|
|
||||||
match *self.children.last_mut().unwrap() {
|
|
||||||
MenuItem::Subtree(_, ref mut tree) => &mut *tree,
|
|
||||||
_ => panic!("??"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn subtree(mut self, title: &str, tree: MenuTree) -> Self {
|
pub fn subtree(mut self, title: &str, tree: MenuTree) -> Self {
|
||||||
self.add_subtree(title, tree);
|
self.children
|
||||||
|
.push(MenuItem::Subtree(title.to_string(), Rc::new(tree)));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,61 @@
|
|||||||
use menu::*;
|
use menu::MenuTree;
|
||||||
|
use view::MenuPopup;
|
||||||
use theme::ColorStyle;
|
use theme::ColorStyle;
|
||||||
use printer::Printer;
|
use printer::Printer;
|
||||||
|
use view::Position;
|
||||||
use event::*;
|
use event::*;
|
||||||
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// Current state of the menubar
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
enum State {
|
||||||
|
/// The menubar is inactive.
|
||||||
|
Inactive,
|
||||||
|
/// The menubar is actively selected.
|
||||||
|
///
|
||||||
|
/// It will receive input.
|
||||||
|
Selected,
|
||||||
|
/// The menubar is still visible, but a submenu is open.
|
||||||
|
///
|
||||||
|
/// It will not receive input.
|
||||||
|
Submenu,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Menubar {
|
pub struct Menubar {
|
||||||
pub menu: MenuTree,
|
pub menus: Vec<(String, Rc<MenuTree>)>,
|
||||||
pub autohide: bool,
|
pub autohide: bool,
|
||||||
pub selected: bool,
|
pub focus: usize,
|
||||||
|
state: State,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Menubar {
|
impl Menubar {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Menubar {
|
Menubar {
|
||||||
menu: MenuTree::new(),
|
menus: Vec::new(),
|
||||||
autohide: true,
|
autohide: true,
|
||||||
selected: false,
|
state: State::Inactive,
|
||||||
|
focus: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn take_focus(&mut self) {
|
||||||
|
self.state = State::Selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn receive_events(&self) -> bool {
|
||||||
|
self.state == State::Selected
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn visible(&self) -> bool {
|
||||||
|
!self.autohide || self.state != State::Inactive
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, title: &str, menu: MenuTree) -> &mut Self {
|
||||||
|
self.menus.push((title.to_string(), Rc::new(menu)));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, printer: &Printer) {
|
pub fn draw(&mut self, printer: &Printer) {
|
||||||
// Draw the bar at the top
|
// Draw the bar at the top
|
||||||
printer.with_color(ColorStyle::Primary, |printer| {
|
printer.with_color(ColorStyle::Primary, |printer| {
|
||||||
@ -27,10 +63,50 @@ impl Menubar {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// TODO: draw the rest
|
// TODO: draw the rest
|
||||||
|
let mut offset = 1;
|
||||||
|
for (i, &(ref title, _)) in self.menus.iter().enumerate() {
|
||||||
|
// We don't want to show HighlightInactive when we're not selected,
|
||||||
|
// because it's ugly on the menubar.
|
||||||
|
let selected = (self.state != State::Inactive) &&
|
||||||
|
(i == self.focus);
|
||||||
|
printer.with_selection(selected, |printer| {
|
||||||
|
printer.print((offset, 0), &format!(" {} ", title));
|
||||||
|
offset += title.len() + 2;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_event(&mut self, event: Event) -> Option<Rc<Callback>> {
|
pub fn on_event(&mut self, event: Event) -> Option<Callback> {
|
||||||
let _ = &event;
|
match event {
|
||||||
|
Event::Key(Key::Esc) => self.state = State::Inactive,
|
||||||
|
Event::Key(Key::Left) if self.focus > 0 => self.focus -= 1,
|
||||||
|
Event::Key(Key::Right) if self.focus + 1 < self.menus.len() => self.focus += 1,
|
||||||
|
Event::Key(Key::Enter) => {
|
||||||
|
// First, we need a new Rc to send the callback,
|
||||||
|
// since we don't know when it will be called.
|
||||||
|
let menu = self.menus[self.focus].1.clone();
|
||||||
|
self.state = State::Submenu;
|
||||||
|
let offset = (self.menus[..self.focus]
|
||||||
|
.iter()
|
||||||
|
.map(|&(ref title, _)| title.len() + 2)
|
||||||
|
.fold(1, |a, b| a + b),
|
||||||
|
if self.autohide {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
});
|
||||||
|
return Some(Rc::new(move |s| {
|
||||||
|
// Since the closure will be called multiple times,
|
||||||
|
// we also need a new Rc on every call.
|
||||||
|
s.screen_mut()
|
||||||
|
.add_layer_at(Position::absolute(offset),
|
||||||
|
MenuPopup::new(menu.clone())
|
||||||
|
.on_dismiss(|s| s.select_menubar())
|
||||||
|
.on_action(|s| s.menubar().state = State::Inactive));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ use theme::{ColorStyle, Theme, Effect};
|
|||||||
use vec::{ToVec2, Vec2};
|
use vec::{ToVec2, Vec2};
|
||||||
|
|
||||||
/// Convenient interface to draw on a subset of the screen.
|
/// Convenient interface to draw on a subset of the screen.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Printer {
|
pub struct Printer {
|
||||||
/// Offset into the window this printer should start drawing at.
|
/// Offset into the window this printer should start drawing at.
|
||||||
pub offset: Vec2,
|
pub offset: Vec2,
|
||||||
|
@ -11,7 +11,7 @@ use printer::Printer;
|
|||||||
/// A button shows its content in a single line and has a fixed size.
|
/// A button shows its content in a single line and has a fixed size.
|
||||||
pub struct Button {
|
pub struct Button {
|
||||||
label: String,
|
label: String,
|
||||||
callback: Rc<Callback>,
|
callback: Callback,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Button {
|
impl Button {
|
||||||
@ -21,7 +21,7 @@ impl Button {
|
|||||||
{
|
{
|
||||||
Button {
|
Button {
|
||||||
label: label.to_string(),
|
label: label.to_string(),
|
||||||
callback: Rc::new(Box::new(cb)),
|
callback: Rc::new(cb),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ use super::{View, ViewWrapper};
|
|||||||
/// Events ignored by its child without a callback will stay ignored.
|
/// Events ignored by its child without a callback will stay ignored.
|
||||||
pub struct KeyEventView {
|
pub struct KeyEventView {
|
||||||
content: Box<View>,
|
content: Box<View>,
|
||||||
callbacks: HashMap<Event, Rc<Callback>>,
|
callbacks: HashMap<Event, Callback>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyEventView {
|
impl KeyEventView {
|
||||||
@ -26,7 +26,7 @@ impl KeyEventView {
|
|||||||
pub fn register<F, E: ToEvent>(mut self, event: E, cb: F) -> Self
|
pub fn register<F, E: ToEvent>(mut self, event: E, cb: F) -> Self
|
||||||
where F: Fn(&mut Cursive) + 'static
|
where F: Fn(&mut Cursive) + 'static
|
||||||
{
|
{
|
||||||
self.callbacks.insert(event.to_event(), Rc::new(Box::new(cb)));
|
self.callbacks.insert(event.to_event(), Rc::new(cb));
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,15 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
|
use Cursive;
|
||||||
use menu::{MenuItem, MenuTree};
|
use menu::{MenuItem, MenuTree};
|
||||||
use printer::Printer;
|
use printer::Printer;
|
||||||
use view::View;
|
use view::View;
|
||||||
|
use view::Position;
|
||||||
use view::scroll::ScrollBase;
|
use view::scroll::ScrollBase;
|
||||||
use align::Align;
|
use align::Align;
|
||||||
use vec::Vec2;
|
use vec::Vec2;
|
||||||
|
use event::{Callback, Event, EventResult, Key};
|
||||||
|
|
||||||
/// fd
|
/// fd
|
||||||
pub struct MenuPopup {
|
pub struct MenuPopup {
|
||||||
@ -15,6 +18,8 @@ pub struct MenuPopup {
|
|||||||
focus: usize,
|
focus: usize,
|
||||||
scrollbase: ScrollBase,
|
scrollbase: ScrollBase,
|
||||||
align: Align,
|
align: Align,
|
||||||
|
on_dismiss: Option<Callback>,
|
||||||
|
on_action: Option<Callback>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuPopup {
|
impl MenuPopup {
|
||||||
@ -24,6 +29,8 @@ impl MenuPopup {
|
|||||||
focus: 0,
|
focus: 0,
|
||||||
scrollbase: ScrollBase::new(),
|
scrollbase: ScrollBase::new(),
|
||||||
align: Align::top_left(),
|
align: Align::top_left(),
|
||||||
|
on_dismiss: None,
|
||||||
|
on_action: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,6 +40,16 @@ impl MenuPopup {
|
|||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_dismiss<F: 'static + Fn(&mut Cursive)>(mut self, f: F) -> Self {
|
||||||
|
self.on_dismiss = Some(Rc::new(f));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_action<F: 'static + Fn(&mut Cursive)>(mut self, f: F) -> Self {
|
||||||
|
self.on_action = Some(Rc::new(f));
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for MenuPopup {
|
impl View for MenuPopup {
|
||||||
@ -60,7 +77,8 @@ impl View for MenuPopup {
|
|||||||
}
|
}
|
||||||
MenuItem::Subtree(ref label, _) |
|
MenuItem::Subtree(ref label, _) |
|
||||||
MenuItem::Leaf(ref label, _) => {
|
MenuItem::Leaf(ref label, _) => {
|
||||||
printer.print((2, 0), label)
|
printer.print_hline((1, 0), printer.size.x - 2, " ");
|
||||||
|
printer.print((2, 0), label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,13 +88,14 @@ impl View for MenuPopup {
|
|||||||
|
|
||||||
fn get_min_size(&self, req: Vec2) -> Vec2 {
|
fn get_min_size(&self, req: Vec2) -> Vec2 {
|
||||||
// We can't really shrink our items here, so it's not flexible.
|
// We can't really shrink our items here, so it's not flexible.
|
||||||
let w = self.menu
|
let w = 2 +
|
||||||
|
self.menu
|
||||||
.children
|
.children
|
||||||
.iter()
|
.iter()
|
||||||
.map(|item| item.label().width())
|
.map(|item| 2 + item.label().width())
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(1);
|
.unwrap_or(1);
|
||||||
let h = self.menu.children.len();
|
let h = 2 + self.menu.children.len();
|
||||||
|
|
||||||
|
|
||||||
let scrolling = req.y < h;
|
let scrolling = req.y < h;
|
||||||
@ -89,4 +108,60 @@ impl View for MenuPopup {
|
|||||||
|
|
||||||
Vec2::new(w, h)
|
Vec2::new(w, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_event(&mut self, event: Event) -> EventResult {
|
||||||
|
match event {
|
||||||
|
Event::Key(Key::Esc) => {
|
||||||
|
let dismiss_cb = self.on_dismiss.clone();
|
||||||
|
return EventResult::with_cb(move |s| {
|
||||||
|
if let Some(ref cb) = dismiss_cb {
|
||||||
|
cb.clone()(s);
|
||||||
|
}
|
||||||
|
s.pop_layer();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Event::Key(Key::Up) if self.focus > 0 => self.focus -= 1,
|
||||||
|
Event::Key(Key::Down) if self.focus + 1 <
|
||||||
|
self.menu.children.len() => {
|
||||||
|
self.focus += 1
|
||||||
|
}
|
||||||
|
Event::Key(Key::Enter) if !self.menu.children[self.focus]
|
||||||
|
.is_delimiter() => {
|
||||||
|
return match self.menu.children[self.focus] {
|
||||||
|
MenuItem::Leaf(_, ref cb) => {
|
||||||
|
|
||||||
|
let cb = cb.clone();
|
||||||
|
let action_cb = self.on_action.clone();
|
||||||
|
EventResult::with_cb(move |s| {
|
||||||
|
if let Some(ref action_cb) = action_cb {
|
||||||
|
action_cb.clone()(s);
|
||||||
|
}
|
||||||
|
s.pop_layer();
|
||||||
|
cb.clone()(s);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
MenuItem::Subtree(_, ref tree) => {
|
||||||
|
let tree = tree.clone();
|
||||||
|
let offset = Vec2::new(10, self.focus + 1);
|
||||||
|
EventResult::with_cb(move |s| {
|
||||||
|
s.screen_mut()
|
||||||
|
.add_layer_at(Position::parent(offset),
|
||||||
|
MenuPopup::new(tree.clone()));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => panic!("No delimiter here"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => return EventResult::Ignored,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scrollbase.scroll_to(self.focus);
|
||||||
|
|
||||||
|
EventResult::Consumed(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(&mut self, size: Vec2) {
|
||||||
|
self.scrollbase.set_heights(size.y, self.menu.children.len());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,8 @@ pub struct SelectView<T = String> {
|
|||||||
items: Vec<Item<T>>,
|
items: Vec<Item<T>>,
|
||||||
focus: usize,
|
focus: usize,
|
||||||
scrollbase: ScrollBase,
|
scrollbase: ScrollBase,
|
||||||
select_cb: Option<Rc<Box<Fn(&mut Cursive, &T)>>>,
|
// This is a custom callback to include a &T
|
||||||
|
select_cb: Option<Rc<Fn(&mut Cursive, &T)>>,
|
||||||
align: Align,
|
align: Align,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ impl<T: 'static> SelectView<T> {
|
|||||||
pub fn set_on_select<F>(&mut self, cb: F)
|
pub fn set_on_select<F>(&mut self, cb: F)
|
||||||
where F: Fn(&mut Cursive, &T) + 'static
|
where F: Fn(&mut Cursive, &T) + 'static
|
||||||
{
|
{
|
||||||
self.select_cb = Some(Rc::new(Box::new(cb)));
|
self.select_cb = Some(Rc::new(cb));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets a function to be called when an item is selected.
|
/// Sets a function to be called when an item is selected.
|
||||||
@ -180,9 +181,8 @@ impl<T: 'static> View for SelectView<T> {
|
|||||||
Event::Key(Key::Enter) if self.select_cb.is_some() => {
|
Event::Key(Key::Enter) if self.select_cb.is_some() => {
|
||||||
let cb = self.select_cb.as_ref().unwrap().clone();
|
let cb = self.select_cb.as_ref().unwrap().clone();
|
||||||
let v = self.selection();
|
let v = self.selection();
|
||||||
// We return a Rc<Box<Callback>>
|
// We return a Callback Rc<|s| cb(s, &*v)>
|
||||||
// With callback being |s| cb(s, &*v)
|
return EventResult::Consumed(Some(Rc::new(move |s| cb(s, &*v))));
|
||||||
return EventResult::Consumed(Some(Rc::new(Box::new(move |s| cb(s, &*v)))));
|
|
||||||
}
|
}
|
||||||
Event::Char(c) => {
|
Event::Char(c) => {
|
||||||
// Starting from the current focus,
|
// Starting from the current focus,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use view::{View, ViewWrapper};
|
use view::{View, ViewWrapper};
|
||||||
use printer::Printer;
|
use printer::Printer;
|
||||||
use vec::Vec2;
|
use vec::Vec2;
|
||||||
@ -8,41 +10,70 @@ use theme::ColorStyle;
|
|||||||
/// It reserves a 1 pixel border on each side.
|
/// It reserves a 1 pixel border on each side.
|
||||||
pub struct ShadowView<T: View> {
|
pub struct ShadowView<T: View> {
|
||||||
view: T,
|
view: T,
|
||||||
|
topleft_padding: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: View> ShadowView<T> {
|
impl<T: View> ShadowView<T> {
|
||||||
/// Wraps the given view.
|
/// Wraps the given view.
|
||||||
pub fn new(view: T) -> Self {
|
pub fn new(view: T) -> Self {
|
||||||
ShadowView { view: view }
|
ShadowView {
|
||||||
|
view: view,
|
||||||
|
topleft_padding: true,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn padding(&self) -> (usize, usize) {
|
||||||
|
if self.topleft_padding {
|
||||||
|
(2, 2)
|
||||||
|
} else {
|
||||||
|
(1, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn no_topleft_padding(mut self) -> Self {
|
||||||
|
self.topleft_padding = false;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: View> ViewWrapper for ShadowView<T> {
|
impl<T: View> ViewWrapper for ShadowView<T> {
|
||||||
wrap_impl!(&self.view);
|
wrap_impl!(&self.view);
|
||||||
|
|
||||||
fn wrap_get_min_size(&self, req: Vec2) -> Vec2 {
|
fn wrap_get_min_size(&self, req: Vec2) -> Vec2 {
|
||||||
self.view.get_min_size(req - (2, 2)) + (2, 2)
|
let offset = self.padding();
|
||||||
|
self.view.get_min_size(req - offset) + offset
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_layout(&mut self, size: Vec2) {
|
fn wrap_layout(&mut self, size: Vec2) {
|
||||||
self.view.layout(size - (2, 2));
|
let offset = self.padding();
|
||||||
|
self.view.layout(size - offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_draw(&mut self, printer: &Printer) {
|
fn wrap_draw(&mut self, printer: &Printer) {
|
||||||
|
|
||||||
|
// Skip the first row/column
|
||||||
|
let printer = if self.topleft_padding {
|
||||||
|
Cow::Owned(printer.sub_printer(Vec2::new(1, 1), printer.size, true))
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(printer)
|
||||||
|
};
|
||||||
|
|
||||||
// Draw the view background
|
// Draw the view background
|
||||||
for y in 1..printer.size.y - 1 {
|
for y in 0..printer.size.y - 1 {
|
||||||
printer.print_hline((1, y), printer.size.x - 2, " ");
|
printer.print_hline((0, y), printer.size.x - 1, " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.view.draw(&printer.sub_printer(Vec2::new(1, 1), printer.size - (2, 2), true));
|
self.view.draw(&printer.sub_printer(Vec2::zero(),
|
||||||
|
printer.size - (1, 1),
|
||||||
|
true));
|
||||||
|
|
||||||
let h = printer.size.y - 1;
|
let h = printer.size.y;
|
||||||
let w = printer.size.x - 1;
|
let w = printer.size.x;
|
||||||
|
|
||||||
printer.with_color(ColorStyle::Shadow, |printer| {
|
printer.with_color(ColorStyle::Shadow, |printer| {
|
||||||
printer.print_hline((2, h), w - 1, " ");
|
printer.print_hline((1, h-1), w - 1, " ");
|
||||||
printer.print_vline((w, 2), h - 1, " ");
|
printer.print_vline((w-1, 1), h - 1, " ");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
use vec::Vec2;
|
use vec::Vec2;
|
||||||
use view::{Selector, ShadowView, View, Position};
|
use view::{Position, Selector, ShadowView, View};
|
||||||
use event::{Event, EventResult};
|
use event::{Event, EventResult};
|
||||||
use printer::Printer;
|
use printer::Printer;
|
||||||
use theme::ColorStyle;
|
use theme::ColorStyle;
|
||||||
@ -38,9 +38,10 @@ impl StackView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a view on top of the stack.
|
/// Adds a view on top of the stack.
|
||||||
pub fn add_layer_at<T: 'static + View>(&mut self, position: Position, view: T) {
|
pub fn add_layer_at<T: 'static + View>(&mut self, position: Position,
|
||||||
|
view: T) {
|
||||||
self.layers.push(Layer {
|
self.layers.push(Layer {
|
||||||
view: Box::new(ShadowView::new(view)),
|
view: Box::new(ShadowView::new(view).no_topleft_padding()),
|
||||||
size: Vec2::new(0, 0),
|
size: Vec2::new(0, 0),
|
||||||
position: position,
|
position: position,
|
||||||
virgin: true,
|
virgin: true,
|
||||||
@ -61,9 +62,11 @@ impl View for StackView {
|
|||||||
for (i, v) in self.layers.iter_mut().enumerate() {
|
for (i, v) in self.layers.iter_mut().enumerate() {
|
||||||
// Place the view
|
// Place the view
|
||||||
// Center the view
|
// Center the view
|
||||||
let offset = v.position.compute_offset(v.size, printer.size, previous);
|
let offset = v.position
|
||||||
|
.compute_offset(v.size, printer.size, previous);
|
||||||
previous = offset;
|
previous = offset;
|
||||||
v.view.draw(&printer.sub_printer(offset, v.size, i + 1 == last));
|
v.view
|
||||||
|
.draw(&printer.sub_printer(offset, v.size, i + 1 == last));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user