mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Better menubar example
Added scrolling support, and left/right navigation
This commit is contained in:
parent
1d54764cdb
commit
8220fe529e
@ -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();
|
||||||
}
|
}
|
||||||
|
10
src/lib.rs
10
src/lib.rs
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,13 @@ impl MenuItem {
|
|||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_subtree(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
MenuItem::Subtree(_,_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuTree {
|
impl MenuTree {
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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, " ");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
Loading…
Reference in New Issue
Block a user