Better menubar example

Added scrolling support, and left/right navigation
This commit is contained in:
Alexandre Bury 2016-07-02 19:37:38 -07:00
parent 1d54764cdb
commit 8220fe529e
8 changed files with 183 additions and 37 deletions

View File

@ -14,18 +14,41 @@ fn main() {
.add("File", .add("File",
MenuTree::new() MenuTree::new()
.leaf("New", |s| s.add_layer(Dialog::info("New file!"))) .leaf("New", |s| s.add_layer(Dialog::info("New file!")))
.subtree("Recent",
MenuTree::new()
.leaf("Item 1", |s| ())
.leaf("Item 1", |s| ())
.leaf("Item 1", |s| ())
.leaf("Item 1", |s| ()))
.delimiter()
.leaf("Item 1", |s| ())
.leaf("Item 1", |s| ())
.leaf("Item 1", |s| ())
.leaf("Item 1", |s| ())
.leaf("Item 1", |s| ())
.leaf("Item 1", |s| ())
.delimiter()
.leaf("Quit", |s| s.quit())) .leaf("Quit", |s| s.quit()))
.add("Help", .add("Help",
MenuTree::new() MenuTree::new()
.leaf("Help", |s| s.add_layer(Dialog::info("Help message!"))) .subtree("Help",
MenuTree::new()
.leaf("General", |s| {
s.add_layer(Dialog::info("Help message!"))
})
.leaf("Online", |s| {
s.add_layer(Dialog::info("Google it \
yourself!\nKids, \
these days..."))
}))
.leaf("About", .leaf("About",
|s| s.add_layer(Dialog::info("Cursive v0.0.0")))); |s| s.add_layer(Dialog::info("Cursive v0.0.0"))));
// siv.set_autohide_menu(false); // siv.set_autohide_menu(false);
siv.add_global_callback(Key::F(10), |s| s.select_menubar()); siv.add_global_callback(Key::Esc, |s| s.select_menubar());
siv.add_layer(Dialog::new(TextView::new("Hit <F10> to show the menu!"))); siv.add_layer(Dialog::new(TextView::new("Hit <Esc> to show the menu!")));
siv.run(); siv.run();
} }

View File

@ -256,15 +256,17 @@ impl Cursive {
} else { } else {
1 1
}; };
let selected = self.menubar.receive_events();
self.screen_mut()
.draw(&printer.sub_printer(Vec2::new(0, offset), printer.size, !selected));
// Draw the menubar? // Draw the menubar?
if self.menubar.visible() { if self.menubar.visible() {
let printer = printer.sub_printer(Vec2::zero(), printer.size, self.menubar.receive_events());
self.menubar.draw(&printer); self.menubar.draw(&printer);
} }
let selected = self.menubar.receive_events();
self.screen_mut()
.draw(&printer.sub_printer(Vec2::new(0, offset), printer.size, !selected));
B::refresh(); B::refresh();
} }

View File

@ -28,6 +28,13 @@ impl MenuItem {
_ => false, _ => false,
} }
} }
pub fn is_subtree(&self) -> bool {
match *self {
MenuItem::Subtree(_,_) => true,
_ => false,
}
}
} }
impl MenuTree { impl MenuTree {

View File

@ -1,5 +1,6 @@
use menu::MenuTree; use menu::MenuTree;
use view::MenuPopup; use view::MenuPopup;
use view::KeyEventView;
use theme::ColorStyle; use theme::ColorStyle;
use printer::Printer; use printer::Printer;
use view::Position; use view::Position;
@ -79,8 +80,21 @@ impl Menubar {
pub fn on_event(&mut self, event: Event) -> Option<Callback> { pub fn on_event(&mut self, event: Event) -> Option<Callback> {
match event { match event {
Event::Key(Key::Esc) => self.state = State::Inactive, Event::Key(Key::Esc) => self.state = State::Inactive,
Event::Key(Key::Left) if self.focus > 0 => self.focus -= 1, Event::Key(Key::Left) => {
Event::Key(Key::Right) if self.focus + 1 < self.menus.len() => self.focus += 1, if self.focus > 0 {
self.focus -= 1
} else {
self.focus = self.menus.len() - 1
}
}
Event::Key(Key::Right) => {
if self.focus + 1 < self.menus.len() {
self.focus += 1
} else {
self.focus = 0
}
}
Event::Key(Key::Down) |
Event::Key(Key::Enter) => { Event::Key(Key::Enter) => {
// 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.
@ -89,7 +103,7 @@ impl Menubar {
let offset = (self.menus[..self.focus] let offset = (self.menus[..self.focus]
.iter() .iter()
.map(|&(ref title, _)| title.len() + 2) .map(|&(ref title, _)| title.len() + 2)
.fold(1, |a, b| a + b), .fold(0, |a, b| a + b),
if self.autohide { if self.autohide {
1 1
} else { } else {
@ -100,9 +114,29 @@ impl Menubar {
// we also need a new Rc on every call. // we also need a new Rc on every call.
s.screen_mut() s.screen_mut()
.add_layer_at(Position::absolute(offset), .add_layer_at(Position::absolute(offset),
MenuPopup::new(menu.clone()) KeyEventView::new(MenuPopup::new(menu.clone())
.on_dismiss(|s| s.select_menubar()) .on_dismiss(|s| s.select_menubar())
.on_action(|s| s.menubar().state = State::Inactive)); .on_action(|s| {
s.menubar().state = State::Inactive
}))
.register(Key::Right, |s| {
s.pop_layer();
// Act as if we sent "Left" then "Enter"
s.select_menubar();
s.menubar().on_event(Event::Key(Key::Right));
if let Some(cb) = s.menubar().on_event(Event::Key(Key::Down)) {
cb(s);
}
})
.register(Key::Left, |s| {
s.pop_layer();
// Act as if we sent "Left" then "Enter"
s.select_menubar();
s.menubar().on_event(Event::Key(Key::Left));
if let Some(cb) = s.menubar().on_event(Event::Key(Key::Down)) {
cb(s);
}
}));
})); }));
} }
_ => (), _ => (),

View File

@ -7,6 +7,7 @@ use menu::{MenuItem, MenuTree};
use printer::Printer; use printer::Printer;
use view::View; use view::View;
use view::Position; use view::Position;
use view::KeyEventView;
use view::scroll::ScrollBase; use view::scroll::ScrollBase;
use align::Align; use align::Align;
use vec::Vec2; use vec::Vec2;
@ -27,13 +28,22 @@ impl MenuPopup {
MenuPopup { MenuPopup {
menu: menu, menu: menu,
focus: 0, focus: 0,
scrollbase: ScrollBase::new(), scrollbase: ScrollBase::new().bar_padding(1),
align: Align::top_left(), align: Align::top_left(),
on_dismiss: None, on_dismiss: None,
on_action: None, on_action: None,
} }
} }
fn item_width(item: &MenuItem) -> usize {
match *item {
MenuItem::Delimiter => 1,
MenuItem::Leaf(ref title, _) => title.width(),
MenuItem::Subtree(ref title, _) => title.width() + 3,
}
}
/// Sets the alignment for this view. /// Sets the alignment for this view.
pub fn align(mut self, align: Align) -> Self { pub fn align(mut self, align: Align) -> Self {
self.align = align; self.align = align;
@ -50,6 +60,35 @@ impl MenuPopup {
self.on_action = Some(Rc::new(f)); self.on_action = Some(Rc::new(f));
self self
} }
fn make_subtree_cb(&self, tree: &Rc<MenuTree>) -> EventResult {
let tree = tree.clone();
let max_width = 4 +
self.menu
.children
.iter()
.map(Self::item_width)
.max()
.unwrap_or(1);
let offset = Vec2::new(max_width, self.focus);
let action_cb = self.on_action.clone();
EventResult::with_cb(move |s| {
let action_cb = action_cb.clone();
s.screen_mut()
.add_layer_at(Position::parent(offset),
KeyEventView::new(MenuPopup::new(tree.clone())
.on_action(move |s| {
// This will happen when the subtree popup
// activates something;
// First, remove ourselve.
s.pop_layer();
if let Some(ref action_cb) = action_cb {
action_cb.clone()(s);
}
}))
.register(Key::Left, |s| s.pop_layer()));
})
}
} }
impl View for MenuPopup { impl View for MenuPopup {
@ -75,7 +114,11 @@ impl View for MenuPopup {
MenuItem::Delimiter => { MenuItem::Delimiter => {
printer.print_hdelim((0, 0), printer.size.x) printer.print_hdelim((0, 0), printer.size.x)
} }
MenuItem::Subtree(ref label, _) | MenuItem::Subtree(ref label, _) => {
printer.print_hline((1, 0), printer.size.x - 2, " ");
printer.print((2, 0), label);
printer.print((printer.size.x - 4, 0), ">>");
}
MenuItem::Leaf(ref label, _) => { MenuItem::Leaf(ref label, _) => {
printer.print_hline((1, 0), printer.size.x - 2, " "); printer.print_hline((1, 0), printer.size.x - 2, " ");
printer.print((2, 0), label); printer.print((2, 0), label);
@ -88,11 +131,11 @@ 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 = 2 + let w = 4 +
self.menu self.menu
.children .children
.iter() .iter()
.map(|item| 2 + item.label().width()) .map(Self::item_width)
.max() .max()
.unwrap_or(1); .unwrap_or(1);
let h = 2 + self.menu.children.len(); let h = 2 + self.menu.children.len();
@ -101,7 +144,7 @@ impl View for MenuPopup {
let scrolling = req.y < h; let scrolling = req.y < h;
let w = if scrolling { let w = if scrolling {
w + 2 w + 1
} else { } else {
w w
}; };
@ -120,34 +163,60 @@ impl View for MenuPopup {
s.pop_layer(); s.pop_layer();
}); });
} }
Event::Key(Key::Up) if self.focus > 0 => self.focus -= 1, Event::Key(Key::Up) => {
Event::Key(Key::Down) if self.focus + 1 < loop {
self.menu.children.len() => { if self.focus > 0 {
self.focus += 1 self.focus -= 1;
} else {
self.focus = self.menu.children.len() - 1;
}
if !self.menu.children[self.focus].is_delimiter() {
break;
}
}
}
Event::Key(Key::Down) => {
loop {
if self.focus + 1 < self.menu.children.len() {
self.focus += 1;
} else {
self.focus = 0;
}
if !self.menu.children[self.focus].is_delimiter() {
break;
}
}
}
Event::Key(Key::Right) if self.menu.children[self.focus]
.is_subtree() => {
return match self.menu.children[self.focus] {
MenuItem::Subtree(_, ref tree) => {
self.make_subtree_cb(tree)
}
_ => panic!("Not a subtree???"),
};
} }
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) => {
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| {
// Remove ourselves from the face of the earth
s.pop_layer();
// If we had prior orders, do it now.
if let Some(ref action_cb) = action_cb { if let Some(ref action_cb) = action_cb {
action_cb.clone()(s); action_cb.clone()(s);
} }
s.pop_layer(); // And transmit his last words.
cb.clone()(s); cb.clone()(s);
}) })
} }
MenuItem::Subtree(_, ref tree) => { MenuItem::Subtree(_, ref tree) => {
let tree = tree.clone(); self.make_subtree_cb(tree)
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"), _ => panic!("No delimiter here"),
}; };
@ -162,6 +231,6 @@ impl View for MenuPopup {
} }
fn layout(&mut self, size: Vec2) { fn layout(&mut self, size: Vec2) {
self.scrollbase.set_heights(size.y, self.menu.children.len()); self.scrollbase.set_heights(size.y - 2, self.menu.children.len());
} }
} }

View File

@ -1,3 +1,4 @@
use std::cmp::min;
use vec::{ToVec2, Vec2}; use vec::{ToVec2, Vec2};
/// Location of the view on screen /// Location of the view on screen
@ -47,8 +48,8 @@ impl Offset {
pub fn compute_offset(&self, size: usize, available: usize, parent: usize) -> usize { pub fn compute_offset(&self, size: usize, available: usize, parent: usize) -> usize {
match *self { match *self {
Offset::Center => (available - size) / 2, Offset::Center => (available - size) / 2,
Offset::Absolute(offset) => offset, Offset::Absolute(offset) => min(offset, available - size),
Offset::Parent(offset) => parent + offset, Offset::Parent(offset) => min(parent + offset, available - size),
} }
} }
} }

View File

@ -10,6 +10,7 @@ pub struct ScrollBase {
pub start_line: usize, pub start_line: usize,
pub content_height: usize, pub content_height: usize,
pub view_height: usize, pub view_height: usize,
pub scrollbar_padding: usize,
} }
impl ScrollBase { impl ScrollBase {
@ -18,9 +19,15 @@ impl ScrollBase {
start_line: 0, start_line: 0,
content_height: 0, content_height: 0,
view_height: 0, view_height: 0,
scrollbar_padding: 0,
} }
} }
pub fn bar_padding(mut self, padding: usize) -> Self {
self.scrollbar_padding = padding;
self
}
/// Call this method whem the content or the view changes. /// Call this method whem the content or the view changes.
pub fn set_heights(&mut self, view_height: usize, content_height: usize) { pub fn set_heights(&mut self, view_height: usize, content_height: usize) {
self.view_height = view_height; self.view_height = view_height;
@ -104,7 +111,7 @@ impl ScrollBase {
// Print the content in a sub_printer // Print the content in a sub_printer
let max_y = min(self.view_height, self.content_height - self.start_line); let max_y = min(self.view_height, self.content_height - self.start_line);
let w = if self.scrollable() { let w = if self.scrollable() {
printer.size.x - 2 printer.size.x - 1 // TODO: 2
} else { } else {
printer.size.x printer.size.x
}; };
@ -134,9 +141,11 @@ impl ScrollBase {
ColorStyle::HighlightInactive ColorStyle::HighlightInactive
}; };
printer.print_vline((printer.size.x - 1, 0), printer.size.y, "|"); // TODO: use 1 instead of 2
let scrollbar_x = printer.size.x - 1 - self.scrollbar_padding;
printer.print_vline((scrollbar_x, 0), printer.size.y, "|");
printer.with_color(color, |printer| { printer.with_color(color, |printer| {
printer.print_vline((printer.size.x - 1, start), height, " "); printer.print_vline((scrollbar_x, start), height, " ");
}); });
} }
} }

View File

@ -62,8 +62,9 @@ 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 let mut offset = v.position
.compute_offset(v.size, printer.size, previous); .compute_offset(v.size, printer.size, previous);
previous = offset; previous = offset;
v.view v.view
.draw(&printer.sub_printer(offset, v.size, i + 1 == last)); .draw(&printer.sub_printer(offset, v.size, i + 1 == last));