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(); let mut siv = Cursive::new();
// We can quit by pressing q // 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.")); siv.add_layer(TextView::new("Hello World!\nPress q to quit the application."));

View File

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

View File

@ -13,7 +13,7 @@ fn main() {
// We want to refresh the page even when no input is given. // We want to refresh the page even when no input is given.
siv.set_fps(10); 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. // A channel will communicate data from our running task to the UI.
let (tx,rx) = mpsc::channel(); let (tx,rx) = mpsc::channel();

View File

@ -15,7 +15,7 @@ fn main() {
let mut siv = Cursive::new(); let mut siv = Cursive::new();
// We can quit by pressing q // 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, // The text is too long to fit on a line, so the view will wrap lines,
// and will adapt to the terminal size. // 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."; 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. // 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), // 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. // so that we can't call it when the popup is already visible.
siv.add_layer(KeyEventView::new(IdView::new("text", TextView::new(content))) 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(); siv.run();

View File

@ -1,7 +1,10 @@
//! User-input events and their effects. //! User-input events and their effects.
use std::fmt;
use std::rc::Rc; use std::rc::Rc;
use ncurses;
use ::Cursive; use ::Cursive;
/// Callback is a function that can be triggered by an event. /// 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. /// The event was consumed. An optionnal callback to run is attached.
Consumed(Option<Rc<Callback>>), 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 printer::Printer;
use view::{StackView,Selector}; use view::{StackView,Selector};
use event::{EventResult,Callback}; use event::{Event,ToEvent,Key,EventResult,Callback};
/// Identifies a screen in the cursive ROOT. /// Identifies a screen in the cursive ROOT.
pub type ScreenId = usize; pub type ScreenId = usize;
@ -59,7 +59,7 @@ pub struct Cursive {
running: bool, running: bool,
global_callbacks: HashMap<i32, Rc<Callback>>, global_callbacks: HashMap<Event, Rc<Callback>>,
} }
impl Cursive { impl Cursive {
@ -145,10 +145,10 @@ impl Cursive {
} }
/// Adds a global callback, triggered on the given key press when no view catches it. /// 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 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. /// 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 // Handles a key event when it was ignored by the current view
fn on_key_event(&mut self, ch: i32) { fn on_event(&mut self, event: Event) {
let cb = match self.global_callbacks.get(&ch) { let cb = match self.global_callbacks.get(&event) {
None => return, None => return,
Some(cb) => cb.clone(), Some(cb) => cb.clone(),
}; };
@ -197,6 +197,17 @@ impl Cursive {
ncurses::refresh(); 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. /// Runs the event loop.
/// It will wait for user input (key presses) and trigger callbacks accordingly. /// It will wait for user input (key presses) and trigger callbacks accordingly.
/// Blocks until quit() is called. /// Blocks until quit() is called.
@ -216,11 +227,13 @@ impl Cursive {
// Blocks until the user press a key. // Blocks until the user press a key.
// TODO: Add a timeout? Animations? // 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. // If the event was ignored, it is our turn to play with it.
match self.screen_mut().on_key_event(ch) { match self.screen_mut().on_event(event) {
EventResult::Ignored => self.on_key_event(ch), EventResult::Ignored => self.on_event(event),
EventResult::Consumed(None) => (), EventResult::Consumed(None) => (),
EventResult::Consumed(Some(cb)) => cb(self), EventResult::Consumed(Some(cb)) => cb(self),
} }

View File

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

View File

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

View File

@ -3,7 +3,7 @@ use ncurses;
use color; use color;
use vec::Vec2; use vec::Vec2;
use view::{View,SizeRequest}; use view::{View,SizeRequest};
use event::EventResult; use event::*;
use printer::Printer; use printer::Printer;
/// Displays an editable text. /// 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 { impl View for EditView {
fn draw(&mut self, printer: &Printer, focused: bool) { fn draw(&mut self, printer: &Printer, focused: bool) {
// let style = if focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE }; // let style = if focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE };
@ -87,22 +78,24 @@ impl View for EditView {
true 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) { match event {
Event::CharEvent(ch) => {
self.content.insert(self.cursor, ch); self.content.insert(self.cursor, ch);
self.cursor += 1; self.cursor += 1;
return EventResult::Consumed(None); return EventResult::Consumed(None);
} },
Event::KeyEvent(key) => match key {
match ch { Key::Home => self.cursor = 0,
ncurses::KEY_HOME => self.cursor = 0, Key::End => self.cursor = self.content.len(),
ncurses::KEY_END => self.cursor = self.content.len(), Key::ArrowLeft if self.cursor > 0 => self.cursor -= 1,
ncurses::KEY_LEFT if self.cursor > 0 => self.cursor -= 1, Key::ArrowRight if self.cursor < self.content.len() => self.cursor += 1,
ncurses::KEY_RIGHT if self.cursor < self.content.len() => self.cursor += 1, Key::Backspace if self.cursor > 0 => { self.cursor -= 1; self.content.remove(self.cursor); },
127 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); },
330 if self.cursor < self.content.len() => { self.content.remove(self.cursor); },
_ => return EventResult::Ignored, _ => return EventResult::Ignored,
},
} }
EventResult::Consumed(None) EventResult::Consumed(None)

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use ::Cursive; use ::Cursive;
use event::{EventResult,Callback}; use event::{Event,EventResult,ToEvent,Callback};
use super::{View,ViewWrapper}; use super::{View,ViewWrapper};
/// A simple wrapper view that catches some ignored event from its child. /// 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. /// 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<i32, Rc<Callback>>, callbacks: HashMap<Event, Rc<Callback>>,
} }
impl KeyEventView { impl KeyEventView {
@ -23,10 +23,10 @@ impl KeyEventView {
} }
/// Registers a callback when the given key is ignored by the child. /// 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 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 self
} }
@ -36,9 +36,9 @@ impl ViewWrapper for KeyEventView {
wrap_impl!(self.content); wrap_impl!(self.content);
fn wrap_on_key_event(&mut self, ch: i32) -> EventResult { fn wrap_on_event(&mut self, event: Event) -> EventResult {
match self.content.on_key_event(ch) { match self.content.on_event(event) {
EventResult::Ignored => match self.callbacks.get(&ch) { EventResult::Ignored => match self.callbacks.get(&event) {
None => EventResult::Ignored, None => EventResult::Ignored,
Some(cb) => EventResult::Consumed(Some(cb.clone())), 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::shadow_view::ShadowView;
pub use self::edit_view::EditView; pub use self::edit_view::EditView;
use event::EventResult; use event::{Event,EventResult};
use vec::{Vec2,ToVec2}; use vec::{Vec2,ToVec2};
use printer::Printer; use printer::Printer;
/// Main trait defining a view behaviour. /// Main trait defining a view behaviour.
pub trait View { pub trait View {
/// Called when a key was pressed. Default implementation just ignores it. /// 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. /// Returns the minimum size the view requires under the given restrictions.
fn get_min_size(&self, SizeRequest) -> Vec2 { Vec2::new(1,1) } 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 vec::Vec2;
use view::{View,SizeRequest,DimensionRequest,Selector,ShadowView}; use view::{View,SizeRequest,DimensionRequest,Selector,ShadowView};
use event::EventResult; use event::{Event,EventResult};
use printer::Printer; use printer::Printer;
/// Simple stack of views. /// 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() { match self.layers.last_mut() {
None => EventResult::Ignored, 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 std::cmp::{max,min};
use ncurses;
use color; use color;
use vec::Vec2; use vec::Vec2;
use view::{View,DimensionRequest,SizeRequest}; use view::{View,DimensionRequest,SizeRequest};
use div::*; use div::*;
use printer::Printer; use printer::Printer;
use event::EventResult; use event::*;
/// A simple view showing a fixed text /// A simple view showing a fixed text
pub struct TextView { 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() { if self.view_height >= self.rows.len() {
return EventResult::Ignored; return EventResult::Ignored;
} }
match ch { match event {
ncurses::KEY_UP if self.start_line > 0 => self.start_line -= 1, Event::KeyEvent(Key::ArrowUp) 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, Event::KeyEvent(Key::ArrowDown) 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), Event::KeyEvent(Key::PageUp) => 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), Event::KeyEvent(Key::PageDown) => self.start_line -= min(self.start_line, 10),
_ => return EventResult::Ignored, _ => return EventResult::Ignored,
} }

View File

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