mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Transform ncurses i32 key into Event enum
Prepares support for unicode char input spanning multiple ncurses characters.
This commit is contained in:
parent
ca09885978
commit
f9c9e56518
@ -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."));
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
87
src/event.rs
87
src/event.rs
@ -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
|
||||
}
|
||||
}
|
||||
|
31
src/lib.rs
31
src/lib.rs
@ -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),
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
},
|
||||
|
@ -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) {
|
||||
match event {
|
||||
Event::CharEvent(ch) => {
|
||||
self.content.insert(self.cursor, ch);
|
||||
self.cursor += 1;
|
||||
return EventResult::Consumed(None);
|
||||
}
|
||||
|
||||
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); },
|
||||
},
|
||||
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,
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
EventResult::Consumed(None)
|
||||
|
@ -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())),
|
||||
},
|
||||
|
@ -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) }
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user