Transform ncurses i32 key into Event enum

Prepares support for unicode char input spanning multiple ncurses
characters.
This commit is contained in:
Alexandre Bury 2015-05-27 18:04:33 -07:00
parent ca09885978
commit f9c9e56518
15 changed files with 179 additions and 86 deletions

View File

@ -7,7 +7,7 @@ fn main() {
let mut siv = Cursive::new();
// We can quit by pressing q
siv.add_global_callback('q' as i32, |s| s.quit());
siv.add_global_callback('q', |s| s.quit());
siv.add_layer(TextView::new("Hello World!\nPress q to quit the application."));

View File

@ -15,7 +15,7 @@ fn main() {
}
struct KeyCodeView {
history: Vec<i32>,
history: Vec<String>,
size: usize,
}
@ -30,13 +30,17 @@ impl KeyCodeView {
impl View for KeyCodeView {
fn draw(&mut self, printer: &Printer, _: bool) {
for (y,n) in self.history.iter().enumerate() {
printer.print((0,y), &format!("{}", n));
for (y,line) in self.history.iter().enumerate() {
printer.print((0,y), &line);
}
}
fn on_key_event(&mut self, ch: i32) -> EventResult {
self.history.push(ch);
fn on_event(&mut self, event: Event) -> EventResult {
let line = match event {
Event::CharEvent(c) => format!("Char: {}", c),
Event::KeyEvent(key) => format!("Key: {}", key),
}
self.history.push(line);
while self.history.len() > self.size {
self.history.remove(0);

View File

@ -13,7 +13,7 @@ fn main() {
// We want to refresh the page even when no input is given.
siv.set_fps(10);
siv.add_global_callback('q' as i32, |s| s.quit());
siv.add_global_callback('q', |s| s.quit());
// A channel will communicate data from our running task to the UI.
let (tx,rx) = mpsc::channel();

View File

@ -15,7 +15,7 @@ fn main() {
let mut siv = Cursive::new();
// We can quit by pressing q
siv.add_global_callback('q' as i32, |s| s.quit());
siv.add_global_callback('q', |s| s.quit());
// The text is too long to fit on a line, so the view will wrap lines,
// and will adapt to the terminal size.

View File

@ -21,13 +21,13 @@ fn main() {
let content = "Press Q to quit the application.\n\nPress P to open the popup.";
siv.add_global_callback('q' as i32, |s| s.quit());
siv.add_global_callback('q', |s| s.quit());
// Let's wrap the view to give it a recognizable ID, so we can look for it.
// We add the P callback on the textview only (and not globally),
// so that we can't call it when the popup is already visible.
siv.add_layer(KeyEventView::new(IdView::new("text", TextView::new(content)))
.register('p' as i32, |s| show_popup(s)));
.register('p', |s| show_popup(s)));
siv.run();

View File

@ -1,7 +1,10 @@
//! User-input events and their effects.
use std::fmt;
use std::rc::Rc;
use ncurses;
use ::Cursive;
/// Callback is a function that can be triggered by an event.
@ -16,3 +19,87 @@ pub enum EventResult {
/// The event was consumed. An optionnal callback to run is attached.
Consumed(Option<Rc<Callback>>),
}
#[derive(PartialEq,Eq,Clone,Copy,Hash)]
pub enum Key {
Enter,
ArrowLeft,
ArrowRight,
ArrowUp,
ArrowDown,
PageUp,
PageDown,
Backspace,
Home,
End,
Del,
Unknown(i32),
}
impl Key {
pub fn from_ncurses(ch: i32) -> Self {
match ch {
10 => Key::Enter,
127 => Key::Backspace,
330 => Key::Del,
ncurses::KEY_LEFT => Key::ArrowLeft,
ncurses::KEY_RIGHT => Key::ArrowRight,
ncurses::KEY_UP => Key::ArrowUp,
ncurses::KEY_DOWN => Key::ArrowDown,
ncurses::KEY_PPAGE => Key::PageUp,
ncurses::KEY_NPAGE => Key::PageDown,
ncurses::KEY_HOME => Key::Home,
ncurses::KEY_END => Key::End,
_ => Key::Unknown(ch),
}
}
}
impl fmt::Display for Key {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Key::Unknown(ch) => write!(f, "Unknown: {}", ch),
key => write!(f, "{}", match key {
Key::ArrowLeft => "Left",
Key::ArrowRight => "Right",
Key::ArrowDown => "Down",
Key::ArrowUp => "Up",
Key::PageUp => "PageUp",
Key::PageDown => "PageDown",
Key::Home => "Home",
Key::End => "End",
Key::Backspace => "Backspace",
Key::Del => "Del",
_ => "",
}),
}
}
}
#[derive(PartialEq,Eq,Clone,Copy,Hash)]
pub enum Event {
CharEvent(char),
KeyEvent(Key),
}
pub trait ToEvent {
fn to_event(self) -> Event;
}
impl ToEvent for char {
fn to_event(self) -> Event {
Event::CharEvent(self)
}
}
impl ToEvent for Key {
fn to_event(self) -> Event {
Event::KeyEvent(self)
}
}
impl ToEvent for Event {
fn to_event(self) -> Event {
self
}
}

View File

@ -40,7 +40,7 @@ use view::View;
use printer::Printer;
use view::{StackView,Selector};
use event::{EventResult,Callback};
use event::{Event,ToEvent,Key,EventResult,Callback};
/// Identifies a screen in the cursive ROOT.
pub type ScreenId = usize;
@ -59,7 +59,7 @@ pub struct Cursive {
running: bool,
global_callbacks: HashMap<i32, Rc<Callback>>,
global_callbacks: HashMap<Event, Rc<Callback>>,
}
impl Cursive {
@ -145,10 +145,10 @@ impl Cursive {
}
/// Adds a global callback, triggered on the given key press when no view catches it.
pub fn add_global_callback<F>(&mut self, key: i32, cb: F)
pub fn add_global_callback<F,E: ToEvent>(&mut self, event: E, cb: F)
where F: Fn(&mut Cursive) + 'static
{
self.global_callbacks.insert(key, Rc::new(Box::new(cb)));
self.global_callbacks.insert(event.to_event(), Rc::new(Box::new(cb)));
}
/// Convenient method to add a layer to the current screen.
@ -162,8 +162,8 @@ impl Cursive {
}
// Handles a key event when it was ignored by the current view
fn on_key_event(&mut self, ch: i32) {
let cb = match self.global_callbacks.get(&ch) {
fn on_event(&mut self, event: Event) {
let cb = match self.global_callbacks.get(&event) {
None => return,
Some(cb) => cb.clone(),
};
@ -197,6 +197,17 @@ impl Cursive {
ncurses::refresh();
}
fn poll_event() -> Event {
let ch = ncurses::getch();
// Is it a UTF-8 starting point?
if 32 <= ch && ch < 127 {
Event::CharEvent(ch as u8 as char)
} else {
Event::KeyEvent(Key::from_ncurses(ch))
}
}
/// Runs the event loop.
/// It will wait for user input (key presses) and trigger callbacks accordingly.
/// Blocks until quit() is called.
@ -216,11 +227,13 @@ impl Cursive {
// Blocks until the user press a key.
// TODO: Add a timeout? Animations?
let ch = ncurses::getch();
let event = Cursive::poll_event();
// Make an event out of it.
// If the event was ignored, it is our turn to play with it.
match self.screen_mut().on_key_event(ch) {
EventResult::Ignored => self.on_key_event(ch),
match self.screen_mut().on_event(event) {
EventResult::Ignored => self.on_event(event),
EventResult::Consumed(None) => (),
EventResult::Consumed(Some(cb)) => cb(self),
}

View File

@ -4,7 +4,7 @@ use color;
use ::Cursive;
use vec::Vec2;
use view::{View,SizeRequest};
use event::{Callback,EventResult};
use event::*;
use printer::Printer;
/// Simple text label with a callback when ENTER is pressed.
@ -44,10 +44,10 @@ impl View for Button {
Vec2::new(2 + self.label.len(), 1)
}
fn on_key_event(&mut self, ch: i32) -> EventResult {
match ch {
fn on_event(&mut self, event: Event) -> EventResult {
match event {
// 10 is the ascii code for '\n', that is the return key
10 => EventResult::Consumed(Some(self.callback.clone())),
Event::KeyEvent(Key::Enter) => EventResult::Consumed(Some(self.callback.clone())),
_ => EventResult::Ignored,
}
}

View File

@ -1,11 +1,9 @@
use std::cmp::max;
use std::any::Any;
use ncurses;
use color;
use ::{Cursive};
use event::EventResult;
use event::*;
use view::{View,SizeRequest,DimensionRequest,Selector};
use view::{Button,SizedView};
use vec::{Vec2,Vec4,ToVec4};
@ -165,12 +163,12 @@ impl View for Dialog {
self.content.layout(size - Vec2::new(0, buttons_height));
}
fn on_key_event(&mut self, ch: i32) -> EventResult {
fn on_event(&mut self, event: Event) -> EventResult {
match self.focus {
// If we are on the content, we can only go down.
Focus::Content => match self.content.on_key_event(ch) {
EventResult::Ignored if !self.buttons.is_empty() => match ch {
ncurses::KEY_DOWN => {
Focus::Content => match self.content.on_event(event) {
EventResult::Ignored if !self.buttons.is_empty() => match event {
Event::KeyEvent(Key::ArrowDown) => {
// Default to leftmost button when going down.
self.focus = Focus::Button(0);
EventResult::Consumed(None)
@ -180,10 +178,10 @@ impl View for Dialog {
res => res,
},
// If we are on a button, we have more choice
Focus::Button(i) => match self.buttons[i].on_key_event(ch) {
EventResult::Ignored => match ch {
Focus::Button(i) => match self.buttons[i].on_event(event) {
EventResult::Ignored => match event {
// Up goes back to the content
ncurses::KEY_UP => {
Event::KeyEvent(Key::ArrowUp) => {
if self.content.take_focus() {
self.focus = Focus::Content;
EventResult::Consumed(None)
@ -192,11 +190,11 @@ impl View for Dialog {
}
},
// Left and Right move to other buttons
ncurses::KEY_RIGHT if i+1 < self.buttons.len() => {
Event::KeyEvent(Key::ArrowRight) if i+1 < self.buttons.len() => {
self.focus = Focus::Button(i+1);
EventResult::Consumed(None)
},
ncurses::KEY_LEFT if i > 0 => {
Event::KeyEvent(Key::ArrowRight) if i > 0 => {
self.focus = Focus::Button(i-1);
EventResult::Consumed(None)
},

View File

@ -3,7 +3,7 @@ use ncurses;
use color;
use vec::Vec2;
use view::{View,SizeRequest};
use event::EventResult;
use event::*;
use printer::Printer;
/// Displays an editable text.
@ -48,15 +48,6 @@ impl EditView {
}
}
fn read_char(ch: i32) -> Option<char> {
// Printable ascii range: 32-126
if ch >= ' ' as i32 && ch <= '~' as i32 {
Some(ch as u8 as char)
} else {
None
}
}
impl View for EditView {
fn draw(&mut self, printer: &Printer, focused: bool) {
// let style = if focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE };
@ -87,22 +78,24 @@ impl View for EditView {
true
}
fn on_key_event(&mut self, ch: i32) -> EventResult {
fn on_event(&mut self, event: Event) -> EventResult {
if let Some(ch) = read_char(ch) {
self.content.insert(self.cursor, ch);
self.cursor += 1;
return EventResult::Consumed(None);
}
match event {
Event::CharEvent(ch) => {
self.content.insert(self.cursor, ch);
self.cursor += 1;
return EventResult::Consumed(None);
},
Event::KeyEvent(key) => match key {
Key::Home => self.cursor = 0,
Key::End => self.cursor = self.content.len(),
Key::ArrowLeft if self.cursor > 0 => self.cursor -= 1,
Key::ArrowRight if self.cursor < self.content.len() => self.cursor += 1,
Key::Backspace if self.cursor > 0 => { self.cursor -= 1; self.content.remove(self.cursor); },
Key::Del if self.cursor < self.content.len() => { self.content.remove(self.cursor); },
_ => return EventResult::Ignored,
match ch {
ncurses::KEY_HOME => self.cursor = 0,
ncurses::KEY_END => self.cursor = self.content.len(),
ncurses::KEY_LEFT if self.cursor > 0 => self.cursor -= 1,
ncurses::KEY_RIGHT if self.cursor < self.content.len() => self.cursor += 1,
127 if self.cursor > 0 => { self.cursor -= 1; self.content.remove(self.cursor); },
330 if self.cursor < self.content.len() => { self.content.remove(self.cursor); },
_ => return EventResult::Ignored,
},
}
EventResult::Consumed(None)

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::rc::Rc;
use ::Cursive;
use event::{EventResult,Callback};
use event::{Event,EventResult,ToEvent,Callback};
use super::{View,ViewWrapper};
/// A simple wrapper view that catches some ignored event from its child.
@ -10,7 +10,7 @@ use super::{View,ViewWrapper};
/// Events ignored by its child without a callback will stay ignored.
pub struct KeyEventView {
content: Box<View>,
callbacks: HashMap<i32, Rc<Callback>>,
callbacks: HashMap<Event, Rc<Callback>>,
}
impl KeyEventView {
@ -23,10 +23,10 @@ impl KeyEventView {
}
/// Registers a callback when the given key is ignored by the child.
pub fn register<F>(mut self, key: i32, cb: F) -> Self
pub fn register<F,E: ToEvent>(mut self, event: E, cb: F) -> Self
where F: Fn(&mut Cursive) + 'static
{
self.callbacks.insert(key, Rc::new(Box::new(cb)));
self.callbacks.insert(event.to_event(), Rc::new(Box::new(cb)));
self
}
@ -36,9 +36,9 @@ impl ViewWrapper for KeyEventView {
wrap_impl!(self.content);
fn wrap_on_key_event(&mut self, ch: i32) -> EventResult {
match self.content.on_key_event(ch) {
EventResult::Ignored => match self.callbacks.get(&ch) {
fn wrap_on_event(&mut self, event: Event) -> EventResult {
match self.content.on_event(event) {
EventResult::Ignored => match self.callbacks.get(&event) {
None => EventResult::Ignored,
Some(cb) => EventResult::Consumed(Some(cb.clone())),
},

View File

@ -30,14 +30,14 @@ pub use self::id_view::IdView;
pub use self::shadow_view::ShadowView;
pub use self::edit_view::EditView;
use event::EventResult;
use event::{Event,EventResult};
use vec::{Vec2,ToVec2};
use printer::Printer;
/// Main trait defining a view behaviour.
pub trait View {
/// Called when a key was pressed. Default implementation just ignores it.
fn on_key_event(&mut self, i32) -> EventResult { EventResult::Ignored }
fn on_event(&mut self, Event) -> EventResult { EventResult::Ignored }
/// Returns the minimum size the view requires under the given restrictions.
fn get_min_size(&self, SizeRequest) -> Vec2 { Vec2::new(1,1) }

View File

@ -3,7 +3,7 @@ use std::any::Any;
use vec::Vec2;
use view::{View,SizeRequest,DimensionRequest,Selector,ShadowView};
use event::EventResult;
use event::{Event,EventResult};
use printer::Printer;
/// Simple stack of views.
@ -50,10 +50,10 @@ impl View for StackView {
}
}
fn on_key_event(&mut self, ch: i32) -> EventResult {
fn on_event(&mut self, event: Event) -> EventResult {
match self.layers.last_mut() {
None => EventResult::Ignored,
Some(v) => v.view.on_key_event(ch),
Some(v) => v.view.on_event(event),
}
}

View File

@ -1,13 +1,11 @@
use std::cmp::{max,min};
use ncurses;
use color;
use vec::Vec2;
use view::{View,DimensionRequest,SizeRequest};
use div::*;
use printer::Printer;
use event::EventResult;
use event::*;
/// A simple view showing a fixed text
pub struct TextView {
@ -194,16 +192,16 @@ impl View for TextView {
}
}
fn on_key_event(&mut self, ch: i32) -> EventResult {
fn on_event(&mut self, event: Event) -> EventResult {
if self.view_height >= self.rows.len() {
return EventResult::Ignored;
}
match ch {
ncurses::KEY_UP if self.start_line > 0 => self.start_line -= 1,
ncurses::KEY_DOWN if self.start_line+self.view_height < self.rows.len() => self.start_line += 1,
ncurses::KEY_NPAGE => self.start_line = min(self.start_line+10, self.rows.len()-self.view_height),
ncurses::KEY_PPAGE => self.start_line -= min(self.start_line, 10),
match event {
Event::KeyEvent(Key::ArrowUp) if self.start_line > 0 => self.start_line -= 1,
Event::KeyEvent(Key::ArrowDown) if self.start_line+self.view_height < self.rows.len() => self.start_line += 1,
Event::KeyEvent(Key::PageUp) => self.start_line = min(self.start_line+10, self.rows.len()-self.view_height),
Event::KeyEvent(Key::PageDown) => self.start_line -= min(self.start_line, 10),
_ => return EventResult::Ignored,
}

View File

@ -3,7 +3,7 @@ use std::any::Any;
use vec::Vec2;
use view::{View,SizeRequest,Selector};
use printer::Printer;
use event::EventResult;
use event::{Event,EventResult};
/// Generic wrapper around a view.
///
@ -25,9 +25,9 @@ pub trait ViewWrapper {
self.get_view().get_min_size(req)
}
/// Wraps the on_key_event method.
fn wrap_on_key_event(&mut self, ch: i32) -> EventResult {
self.get_view_mut().on_key_event(ch)
/// Wraps the on_event method.
fn wrap_on_event(&mut self, ch: Event) -> EventResult {
self.get_view_mut().on_event(ch)
}
/// Wraps the layout method
@ -54,8 +54,8 @@ impl <T: ViewWrapper> View for T {
self.wrap_get_min_size(req)
}
fn on_key_event(&mut self, ch: i32) -> EventResult {
self.wrap_on_key_event(ch)
fn on_event(&mut self, ch: Event) -> EventResult {
self.wrap_on_event(ch)
}
fn layout(&mut self, size: Vec2) {