Refactor Backend input model

Backends now have to send input to the given `chan::Sender<Event>`.
They send these events from a separate thread, allowing selection
between input and callbacks.

This currently breaks the BearLibTerminal backend, which requires all
calls to come from the UI thread.

This might not be super-safe for the ncurses backend also. We hope that
input and output are separate enough that they can run concurrently without
problem.
This commit is contained in:
Alexandre Bury 2018-05-20 09:59:35 -07:00
parent 4a3bbbb998
commit 05e1212a50
12 changed files with 746 additions and 614 deletions

View File

@ -24,6 +24,7 @@ unicode-segmentation = "1.0"
unicode-width = "0.1"
xi-unicode = "0.1.0"
libc = "0.2"
chan = "0.1"
[dependencies.maplit]
optional = true
@ -33,10 +34,6 @@ version = "1.0.0"
optional = true
version = "1.3.1"
[dependencies.chan]
optional = true
version = "0.1.18"
[dependencies.chan-signal]
optional = true
version = "0.3"
@ -70,7 +67,7 @@ default = ["ncurses-backend"]
markdown = ["pulldown-cmark"]
ncurses-backend = ["ncurses", "maplit"]
pancurses-backend = ["pancurses", "maplit"]
termion-backend = ["termion", "chan", "chan-signal"]
termion-backend = ["termion", "chan-signal"]
[lib]
name = "cursive"

View File

@ -23,7 +23,6 @@ fn move_top(c: &mut Cursive, x_in: isize, y_in: isize) {
fn main() {
let mut siv = Cursive::default();
siv.set_fps(60);
// We can quit by pressing `q`
siv.add_global_callback('q', Cursive::quit);

View File

@ -27,7 +27,7 @@ fn main() {
.content(Button::new("Start", phase_1)),
);
// Auto-refresh is currently required for animated views
// Auto-refresh is required for animated views
siv.set_fps(30);
siv.run();
@ -52,7 +52,7 @@ fn phase_1(s: &mut Cursive) {
fake_load(n_max, &counter);
// When we're done, send a callback through the channel
cb.send(Box::new(coffee_break)).unwrap();
cb.send(Box::new(coffee_break));
})
.full_width(),
));
@ -110,7 +110,7 @@ fn phase_2(s: &mut Cursive) {
}
}
cb.send(Box::new(final_step)).unwrap();
cb.send(Box::new(final_step));
});
}

View File

@ -68,9 +68,7 @@ fn main() {
}
// When we're done, shut down the application
cb_sink
.send(Box::new(|s: &mut Cursive| s.quit()))
.unwrap();
cb_sink.send(Box::new(|s: &mut Cursive| s.quit()));
});
// Add a single view: progress status

View File

@ -14,6 +14,8 @@ use event::{Event, Key, MouseButton, MouseEvent};
use std::collections::HashSet;
use theme::{BaseColor, Color, ColorPair, Effect};
use vec::Vec2;
use chan;
use std::thread;
enum ColorRole {
Foreground,
@ -21,31 +23,81 @@ enum ColorRole {
}
pub struct Backend {
mouse_position: Vec2,
buttons_pressed: HashSet<MouseButton>,
}
impl Backend {
pub fn init() -> Box<Self> {
terminal::open("Cursive", 80, 24);
terminal::set(terminal::config::Window::empty().resizeable(true));
terminal::set(vec![
terminal::config::InputFilter::Group {
group: terminal::config::InputFilterGroup::Keyboard,
both: false,
},
terminal::config::InputFilter::Group {
group: terminal::config::InputFilterGroup::Mouse,
both: true,
},
]);
struct InputParser {
event_sink: chan::Sender<Event>,
buttons_pressed: HashSet<MouseButton>,
mouse_position: Vec2,
}
let c = Backend {
mouse_position: Vec2::zero(),
impl InputParser {
fn new(event_sink: chan::Sender<Event>) -> Self {
InputParser {
event_sink,
buttons_pressed: HashSet::new(),
};
mouse_position: Vec2::zero(),
}
}
Box::new(c)
fn parse_next(&mut self) {
// TODO: we could add backend-specific controls here.
// Ex: ctrl+mouse wheel cause window cellsize to change
let event = if let Some(ev) = terminal::wait_event() {
match ev {
BltEvent::Close => Event::Exit,
BltEvent::Resize { .. } => Event::WindowResize,
// TODO: mouse support
BltEvent::MouseMove { x, y } => {
self.mouse_position = Vec2::new(x as usize, y as usize);
// TODO: find out if a button is pressed?
match self.buttons_pressed.iter().next() {
None => Event::Refresh,
Some(btn) => Event::Mouse {
event: MouseEvent::Hold(*btn),
position: self.mouse_position,
offset: Vec2::zero(),
},
}
}
BltEvent::MouseScroll { delta } => Event::Mouse {
event: if delta < 0 {
MouseEvent::WheelUp
} else {
MouseEvent::WheelDown
},
position: self.mouse_position,
offset: Vec2::zero(),
},
BltEvent::KeyPressed { key, ctrl, shift } => {
self.blt_keycode_to_ev(key, shift, ctrl)
}
// TODO: there's no Key::Shift/Ctrl for w/e reason
BltEvent::ShiftPressed => Event::Refresh,
BltEvent::ControlPressed => Event::Refresh,
// TODO: what should we do here?
BltEvent::KeyReleased { key, .. } => {
// It's probably a mouse key.
blt_keycode_to_mouse_button(key)
.map(|btn| {
self.buttons_pressed.remove(&btn);
Event::Mouse {
event: MouseEvent::Release(btn),
position: self.mouse_position,
offset: Vec2::zero(),
}
})
.unwrap_or(Event::Unknown(vec![]))
}
BltEvent::ShiftReleased | BltEvent::ControlReleased => {
Event::Refresh
}
}
} else {
Event::Refresh
};
self.event_sink.send(event);
}
fn blt_keycode_to_ev(
@ -171,6 +223,28 @@ impl Backend {
}
}
impl Backend {
pub fn init() -> Box<backend::Backend> {
terminal::open("Cursive", 80, 24);
terminal::set(terminal::config::Window::empty().resizeable(true));
terminal::set(vec![
terminal::config::InputFilter::Group {
group: terminal::config::InputFilterGroup::Keyboard,
both: false,
},
terminal::config::InputFilter::Group {
group: terminal::config::InputFilterGroup::Mouse,
both: true,
},
]);
let c = Backend {};
Box::new(c)
}
}
impl backend::Backend for Backend {
fn finish(&mut self) {
terminal::close();
@ -246,66 +320,14 @@ impl backend::Backend for Backend {
terminal::print_xy(pos.x as i32, pos.y as i32, text);
}
fn set_refresh_rate(&mut self, _: u32) {
// TODO: unsupported
}
fn start_input_thread(&mut self, event_sink: chan::Sender<Event>) {
let mut parser = InputParser::new(event_sink);
fn poll_event(&mut self) -> Event {
// TODO: we could add backend-specific controls here.
// Ex: ctrl+mouse wheel cause window cellsize to change
if let Some(ev) = terminal::wait_event() {
match ev {
BltEvent::Close => Event::Exit,
BltEvent::Resize { .. } => Event::WindowResize,
// TODO: mouse support
BltEvent::MouseMove { x, y } => {
self.mouse_position = Vec2::new(x as usize, y as usize);
// TODO: find out if a button is pressed?
match self.buttons_pressed.iter().next() {
None => Event::Refresh,
Some(btn) => Event::Mouse {
event: MouseEvent::Hold(*btn),
position: self.mouse_position,
offset: Vec2::zero(),
},
}
}
BltEvent::MouseScroll { delta } => Event::Mouse {
event: if delta < 0 {
MouseEvent::WheelUp
} else {
MouseEvent::WheelDown
},
position: self.mouse_position,
offset: Vec2::zero(),
},
BltEvent::KeyPressed { key, ctrl, shift } => {
self.blt_keycode_to_ev(key, shift, ctrl)
}
// TODO: there's no Key::Shift/Ctrl for w/e reason
BltEvent::ShiftPressed => Event::Refresh,
BltEvent::ControlPressed => Event::Refresh,
// TODO: what should we do here?
BltEvent::KeyReleased { key, .. } => {
// It's probably a mouse key.
blt_keycode_to_mouse_button(key)
.map(|btn| {
self.buttons_pressed.remove(&btn);
Event::Mouse {
event: MouseEvent::Release(btn),
position: self.mouse_position,
offset: Vec2::zero(),
}
})
.unwrap_or(Event::Unknown(vec![]))
}
BltEvent::ShiftReleased | BltEvent::ControlReleased => {
Event::Refresh
}
thread::spawn(move || {
loop {
parser.parse_next();
}
} else {
Event::Refresh
}
});
}
}

View File

@ -4,27 +4,140 @@ use self::ncurses::mmask_t;
use self::super::split_i32;
use backend;
use event::{Event, Key, MouseButton, MouseEvent};
use libc;
use theme::{Color, ColorPair, Effect};
use utf8;
use vec::Vec2;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::ffi::CString;
use std::fs::File;
use std::io;
use std::io::{Write};
use theme::{Color, ColorPair, Effect};
use utf8;
use vec::Vec2;
use std::thread;
use libc;
use chan;
pub struct Backend {
current_style: Cell<ColorPair>,
// Maps (front, back) ncurses colors to ncurses pairs
pairs: RefCell<HashMap<(i16, i16), i16>>,
}
struct InputParser {
key_codes: HashMap<i32, Event>,
last_mouse_button: Option<MouseButton>,
event_queue: Vec<Event>,
event_sink: chan::Sender<Event>,
}
impl InputParser {
fn new(event_sink: chan::Sender<Event>) -> Self {
InputParser {
key_codes: initialize_keymap(),
last_mouse_button: None,
event_sink,
}
}
fn parse_next(&mut self) {
let ch: i32 = ncurses::getch();
// Is it a UTF-8 starting point?
let event = if 32 <= ch && ch <= 255 && ch != 127 {
utf8::read_char(ch as u8, || Some(ncurses::getch() as u8))
.map(Event::Char)
.unwrap_or_else(|e| {
warn!("Error reading input: {}", e);
Event::Unknown(vec![ch as u8])
})
} else {
self.parse_ncurses_char(ch)
};
self.event_sink.send(event);
}
fn parse_ncurses_char(&mut self, ch: i32) -> Event {
// eprintln!("Found {:?}", ncurses::keyname(ch));
if ch == ncurses::KEY_MOUSE {
self.parse_mouse_event()
} else {
self.key_codes
.get(&ch)
.cloned()
.unwrap_or_else(|| Event::Unknown(split_i32(ch)))
}
}
fn parse_mouse_event(&mut self) -> Event {
let mut mevent = ncurses::MEVENT {
id: 0,
x: 0,
y: 0,
z: 0,
bstate: 0,
};
if ncurses::getmouse(&mut mevent as *mut ncurses::MEVENT)
== ncurses::OK
{
// eprintln!("{:032b}", mevent.bstate);
// Currently unused
let _shift =
(mevent.bstate & ncurses::BUTTON_SHIFT as mmask_t) != 0;
let _alt = (mevent.bstate & ncurses::BUTTON_ALT as mmask_t) != 0;
let _ctrl = (mevent.bstate & ncurses::BUTTON_CTRL as mmask_t) != 0;
mevent.bstate &= !(ncurses::BUTTON_SHIFT | ncurses::BUTTON_ALT
| ncurses::BUTTON_CTRL)
as mmask_t;
let make_event = |event| Event::Mouse {
offset: Vec2::zero(),
position: Vec2::new(mevent.x as usize, mevent.y as usize),
event,
};
if mevent.bstate == ncurses::REPORT_MOUSE_POSITION as mmask_t {
// The event is either a mouse drag event,
// or a weird double-release event. :S
self.last_mouse_button
.map(MouseEvent::Hold)
.map(&make_event)
.unwrap_or_else(|| Event::Unknown(vec![]))
} else {
// Identify the button
let mut bare_event = mevent.bstate & ((1 << 25) - 1);
let mut event = None;
while bare_event != 0 {
let single_event = 1 << bare_event.trailing_zeros();
bare_event ^= single_event;
// Process single_event
on_mouse_event(single_event as i32, |e| {
if event.is_none() {
event = Some(e);
} else {
self.event_sink.send(make_event(e));
}
});
}
if let Some(event) = event {
if let Some(btn) = event.button() {
self.last_mouse_button = Some(btn);
}
make_event(event)
} else {
debug!("No event parsed?...");
Event::Unknown(vec![])
}
}
} else {
debug!("Ncurses event not recognized.");
Event::Unknown(vec![])
}
}
}
fn find_closest_pair(pair: &ColorPair) -> (i16, i16) {
@ -85,11 +198,6 @@ impl Backend {
let c = Backend {
current_style: Cell::new(ColorPair::from_256colors(0, 0)),
pairs: RefCell::new(HashMap::new()),
last_mouse_button: None,
event_queue: Vec::new(),
key_codes: initialize_keymap(),
};
Box::new(c)
@ -139,86 +247,7 @@ impl Backend {
ncurses::attron(style);
}
fn parse_mouse_event(&mut self) -> Event {
let mut mevent = ncurses::MEVENT {
id: 0,
x: 0,
y: 0,
z: 0,
bstate: 0,
};
if ncurses::getmouse(&mut mevent as *mut ncurses::MEVENT)
== ncurses::OK
{
// eprintln!("{:032b}", mevent.bstate);
// Currently unused
let _shift =
(mevent.bstate & ncurses::BUTTON_SHIFT as mmask_t) != 0;
let _alt = (mevent.bstate & ncurses::BUTTON_ALT as mmask_t) != 0;
let _ctrl = (mevent.bstate & ncurses::BUTTON_CTRL as mmask_t) != 0;
mevent.bstate &= !(ncurses::BUTTON_SHIFT | ncurses::BUTTON_ALT
| ncurses::BUTTON_CTRL)
as mmask_t;
let make_event = |event| Event::Mouse {
offset: Vec2::zero(),
position: Vec2::new(mevent.x as usize, mevent.y as usize),
event,
};
if mevent.bstate == ncurses::REPORT_MOUSE_POSITION as mmask_t {
// The event is either a mouse drag event,
// or a weird double-release event. :S
self.last_mouse_button
.map(MouseEvent::Hold)
.map(&make_event)
.unwrap_or_else(|| Event::Unknown(vec![]))
} else {
// Identify the button
let mut bare_event = mevent.bstate & ((1 << 25) - 1);
let mut event = None;
while bare_event != 0 {
let single_event = 1 << bare_event.trailing_zeros();
bare_event ^= single_event;
// Process single_event
on_mouse_event(single_event as i32, |e| {
if event.is_none() {
event = Some(e);
} else {
self.event_queue.push(make_event(e));
}
});
}
if let Some(event) = event {
if let Some(btn) = event.button() {
self.last_mouse_button = Some(btn);
}
make_event(event)
} else {
debug!("No event parsed?...");
Event::Unknown(vec![])
}
}
} else {
debug!("Ncurses event not recognized.");
Event::Unknown(vec![])
}
}
fn parse_ncurses_char(&mut self, ch: i32) -> Event {
// eprintln!("Found {:?}", ncurses::keyname(ch));
if ch == ncurses::KEY_MOUSE {
self.parse_mouse_event()
} else {
self.key_codes
.get(&ch)
.cloned()
.unwrap_or_else(|| Event::Unknown(split_i32(ch)))
}
}
}
impl backend::Backend for Backend {
@ -233,6 +262,18 @@ impl backend::Backend for Backend {
ncurses::has_colors()
}
fn start_input_thread(&mut self, event_sink: chan::Sender<Event>) {
let mut parser = InputParser::new(event_sink);
// Start an input thread
thread::spawn(move || {
// TODO: use an atomic boolean to stop the thread on finish
loop {
parser.parse_next();
}
});
}
fn finish(&mut self) {
write_to_tty(b"\x1B[?1002l").unwrap();
ncurses::endwin();
@ -287,32 +328,6 @@ impl backend::Backend for Backend {
fn print_at(&self, pos: Vec2, text: &str) {
ncurses::mvaddstr(pos.y as i32, pos.x as i32, text);
}
fn poll_event(&mut self) -> Event {
self.event_queue.pop().unwrap_or_else(|| {
let ch: i32 = ncurses::getch();
// Is it a UTF-8 starting point?
if 32 <= ch && ch <= 255 && ch != 127 {
utf8::read_char(ch as u8, || Some(ncurses::getch() as u8))
.map(Event::Char)
.unwrap_or_else(|e| {
warn!("Error reading input: {}", e);
Event::Unknown(vec![ch as u8])
})
} else {
self.parse_ncurses_char(ch)
}
})
}
fn set_refresh_rate(&mut self, fps: u32) {
if fps == 0 {
ncurses::timeout(-1);
} else {
ncurses::timeout(1000 / fps as i32);
}
}
}
/// Returns the Key enum corresponding to the given ncurses event.

View File

@ -9,19 +9,264 @@ use std::collections::HashMap;
use std::io::{stdout, Write};
use theme::{Color, ColorPair, Effect};
use vec::Vec2;
use std::sync::Arc;
use chan;
use std::thread;
pub struct Backend {
// Used
current_style: Cell<ColorPair>,
pairs: RefCell<HashMap<(i16, i16), i32>>,
key_codes: HashMap<i32, Event>,
last_mouse_button: Option<MouseButton>,
event_queue: Vec<Event>,
// pancurses needs a handle to the current window.
window: pancurses::Window,
window: Arc<pancurses::Window>,
}
struct InputParser {
key_codes: HashMap<i32, Event>,
last_mouse_button: Option<MouseButton>,
event_sink: chan::Sender<Event>,
window: Arc<pancurses::Window>,
}
// Ncurses (and pancurses) are not thread-safe
// (writing from two threads might cause garbage).
// BUT it's probably fine to read while we write.
// So `InputParser` will only read, and `Backend` will mostly write.
unsafe impl Send for InputParser {}
impl InputParser {
fn new(event_sink: chan::Sender<Event>, window: Arc<pancurses::Window>) -> Self {
InputParser {
key_codes: initialize_keymap(),
last_mouse_button: None,
event_sink,
window,
}
}
fn parse_next(&mut self) {
let event = if let Some(ev) = self.window.getch() {
match ev {
pancurses::Input::Character('\n') => {
Event::Key(Key::Enter)
}
// TODO: wait for a very short delay. If more keys are
// pipelined, it may be an escape sequence.
pancurses::Input::Character('\u{7f}')
| pancurses::Input::Character('\u{8}') => {
Event::Key(Key::Backspace)
}
pancurses::Input::Character('\u{9}') => {
Event::Key(Key::Tab)
}
pancurses::Input::Character('\u{1b}') => {
Event::Key(Key::Esc)
}
pancurses::Input::Character(c) if (c as u32) <= 26 => {
Event::CtrlChar((b'a' - 1 + c as u8) as char)
}
pancurses::Input::Character(c) => Event::Char(c),
// TODO: Some key combos are not recognized by pancurses,
// but are sent as Unknown. We could still parse them here.
pancurses::Input::Unknown(code) => self.key_codes
// pancurses does some weird keycode mapping
.get(&(code + 256 + 48))
.cloned()
.unwrap_or_else(|| {
warn!("Unknown: {}", code);
Event::Unknown(split_i32(code))
}),
// TODO: I honestly have no fucking idea what KeyCodeYes is
pancurses::Input::KeyCodeYes => Event::Refresh,
pancurses::Input::KeyBreak => Event::Key(Key::PauseBreak),
pancurses::Input::KeyDown => Event::Key(Key::Down),
pancurses::Input::KeyUp => Event::Key(Key::Up),
pancurses::Input::KeyLeft => Event::Key(Key::Left),
pancurses::Input::KeyRight => Event::Key(Key::Right),
pancurses::Input::KeyHome => Event::Key(Key::Home),
pancurses::Input::KeyBackspace => {
Event::Key(Key::Backspace)
}
pancurses::Input::KeyF0 => Event::Key(Key::F0),
pancurses::Input::KeyF1 => Event::Key(Key::F1),
pancurses::Input::KeyF2 => Event::Key(Key::F2),
pancurses::Input::KeyF3 => Event::Key(Key::F3),
pancurses::Input::KeyF4 => Event::Key(Key::F4),
pancurses::Input::KeyF5 => Event::Key(Key::F5),
pancurses::Input::KeyF6 => Event::Key(Key::F6),
pancurses::Input::KeyF7 => Event::Key(Key::F7),
pancurses::Input::KeyF8 => Event::Key(Key::F8),
pancurses::Input::KeyF9 => Event::Key(Key::F9),
pancurses::Input::KeyF10 => Event::Key(Key::F10),
pancurses::Input::KeyF11 => Event::Key(Key::F11),
pancurses::Input::KeyF12 => Event::Key(Key::F12),
pancurses::Input::KeyF13 => Event::Shift(Key::F1),
pancurses::Input::KeyF14 => Event::Shift(Key::F2),
pancurses::Input::KeyF15 => Event::Shift(Key::F3),
pancurses::Input::KeyDL => Event::Refresh,
pancurses::Input::KeyIL => Event::Refresh,
pancurses::Input::KeyDC => Event::Key(Key::Del),
pancurses::Input::KeyIC => Event::Key(Key::Ins),
pancurses::Input::KeyEIC => Event::Refresh,
pancurses::Input::KeyClear => Event::Refresh,
pancurses::Input::KeyEOS => Event::Refresh,
pancurses::Input::KeyEOL => Event::Refresh,
pancurses::Input::KeySF => Event::Shift(Key::Down),
pancurses::Input::KeySR => Event::Shift(Key::Up),
pancurses::Input::KeyNPage => Event::Key(Key::PageDown),
pancurses::Input::KeyPPage => Event::Key(Key::PageUp),
pancurses::Input::KeySTab => Event::Shift(Key::Tab),
pancurses::Input::KeyCTab => Event::Ctrl(Key::Tab),
pancurses::Input::KeyCATab => Event::CtrlAlt(Key::Tab),
pancurses::Input::KeyEnter => Event::Key(Key::Enter),
pancurses::Input::KeySReset => Event::Refresh,
pancurses::Input::KeyReset => Event::Refresh,
pancurses::Input::KeyPrint => Event::Refresh,
pancurses::Input::KeyLL => Event::Refresh,
pancurses::Input::KeyAbort => Event::Refresh,
pancurses::Input::KeySHelp => Event::Refresh,
pancurses::Input::KeyLHelp => Event::Refresh,
pancurses::Input::KeyBTab => Event::Shift(Key::Tab),
pancurses::Input::KeyBeg => Event::Refresh,
pancurses::Input::KeyCancel => Event::Refresh,
pancurses::Input::KeyClose => Event::Refresh,
pancurses::Input::KeyCommand => Event::Refresh,
pancurses::Input::KeyCopy => Event::Refresh,
pancurses::Input::KeyCreate => Event::Refresh,
pancurses::Input::KeyEnd => Event::Key(Key::End),
pancurses::Input::KeyExit => Event::Refresh,
pancurses::Input::KeyFind => Event::Refresh,
pancurses::Input::KeyHelp => Event::Refresh,
pancurses::Input::KeyMark => Event::Refresh,
pancurses::Input::KeyMessage => Event::Refresh,
pancurses::Input::KeyMove => Event::Refresh,
pancurses::Input::KeyNext => Event::Refresh,
pancurses::Input::KeyOpen => Event::Refresh,
pancurses::Input::KeyOptions => Event::Refresh,
pancurses::Input::KeyPrevious => Event::Refresh,
pancurses::Input::KeyRedo => Event::Refresh,
pancurses::Input::KeyReference => Event::Refresh,
pancurses::Input::KeyRefresh => Event::Refresh,
pancurses::Input::KeyReplace => Event::Refresh,
pancurses::Input::KeyRestart => Event::Refresh,
pancurses::Input::KeyResume => Event::Refresh,
pancurses::Input::KeySave => Event::Refresh,
pancurses::Input::KeySBeg => Event::Refresh,
pancurses::Input::KeySCancel => Event::Refresh,
pancurses::Input::KeySCommand => Event::Refresh,
pancurses::Input::KeySCopy => Event::Refresh,
pancurses::Input::KeySCreate => Event::Refresh,
pancurses::Input::KeySDC => Event::Shift(Key::Del),
pancurses::Input::KeySDL => Event::Refresh,
pancurses::Input::KeySelect => Event::Refresh,
pancurses::Input::KeySEnd => Event::Shift(Key::End),
pancurses::Input::KeySEOL => Event::Refresh,
pancurses::Input::KeySExit => Event::Refresh,
pancurses::Input::KeySFind => Event::Refresh,
pancurses::Input::KeySHome => Event::Shift(Key::Home),
pancurses::Input::KeySIC => Event::Shift(Key::Ins),
pancurses::Input::KeySLeft => Event::Shift(Key::Left),
pancurses::Input::KeySMessage => Event::Refresh,
pancurses::Input::KeySMove => Event::Refresh,
pancurses::Input::KeySNext => Event::Shift(Key::PageDown),
pancurses::Input::KeySOptions => Event::Refresh,
pancurses::Input::KeySPrevious => {
Event::Shift(Key::PageUp)
}
pancurses::Input::KeySPrint => Event::Refresh,
pancurses::Input::KeySRedo => Event::Refresh,
pancurses::Input::KeySReplace => Event::Refresh,
pancurses::Input::KeySRight => Event::Shift(Key::Right),
pancurses::Input::KeySResume => Event::Refresh,
pancurses::Input::KeySSave => Event::Refresh,
pancurses::Input::KeySSuspend => Event::Refresh,
pancurses::Input::KeySUndo => Event::Refresh,
pancurses::Input::KeySuspend => Event::Refresh,
pancurses::Input::KeyUndo => Event::Refresh,
pancurses::Input::KeyResize => {
// Let pancurses adjust their structures when the
// window is resized.
// Do it for Windows only, as 'resize_term' is not
// implemented for Unix
if cfg!(target_os = "windows") {
pancurses::resize_term(0, 0);
}
Event::WindowResize
}
pancurses::Input::KeyEvent => Event::Refresh,
// TODO: mouse support
pancurses::Input::KeyMouse => self.parse_mouse_event(),
pancurses::Input::KeyA1 => Event::Refresh,
pancurses::Input::KeyA3 => Event::Refresh,
pancurses::Input::KeyB2 => Event::Key(Key::NumpadCenter),
pancurses::Input::KeyC1 => Event::Refresh,
pancurses::Input::KeyC3 => Event::Refresh,
}
} else {
Event::Refresh
};
self.event_sink.send(event);
}
fn parse_mouse_event(&mut self) -> Event {
let mut mevent = match pancurses::getmouse() {
Err(code) => return Event::Unknown(split_i32(code)),
Ok(event) => event,
};
let _shift = (mevent.bstate & pancurses::BUTTON_SHIFT as mmask_t) != 0;
let _alt = (mevent.bstate & pancurses::BUTTON_ALT as mmask_t) != 0;
let _ctrl = (mevent.bstate & pancurses::BUTTON_CTRL as mmask_t) != 0;
mevent.bstate &= !(pancurses::BUTTON_SHIFT | pancurses::BUTTON_ALT
| pancurses::BUTTON_CTRL) as mmask_t;
let make_event = |event| Event::Mouse {
offset: Vec2::zero(),
position: Vec2::new(mevent.x as usize, mevent.y as usize),
event: event,
};
if mevent.bstate == pancurses::REPORT_MOUSE_POSITION as mmask_t {
// The event is either a mouse drag event,
// or a weird double-release event. :S
self.last_mouse_button
.map(MouseEvent::Hold)
.map(&make_event)
.unwrap_or_else(|| {
debug!("We got a mouse drag, but no last mouse pressed?");
Event::Unknown(vec![])
})
} else {
// Identify the button
let mut bare_event = mevent.bstate & ((1 << 25) - 1);
let mut event = None;
while bare_event != 0 {
let single_event = 1 << bare_event.trailing_zeros();
bare_event ^= single_event;
// Process single_event
on_mouse_event(single_event, |e| {
if event.is_none() {
event = Some(e);
} else {
self.event_sink.send(make_event(e));
}
});
}
if let Some(event) = event {
if let Some(btn) = event.button() {
self.last_mouse_button = Some(btn);
}
make_event(event)
} else {
debug!("No event parsed?...");
Event::Unknown(vec![])
}
}
}
}
fn find_closest_pair(pair: &ColorPair) -> (i16, i16) {
@ -54,10 +299,7 @@ impl Backend {
let c = Backend {
current_style: Cell::new(ColorPair::from_256colors(0, 0)),
pairs: RefCell::new(HashMap::new()),
window: window,
last_mouse_button: None,
event_queue: Vec::new(),
key_codes: initialize_keymap(),
window: Arc::new(window),
};
Box::new(c)
@ -109,64 +351,6 @@ impl Backend {
self.window.attron(style);
}
fn parse_mouse_event(&mut self) -> Event {
let mut mevent = match pancurses::getmouse() {
Err(code) => return Event::Unknown(split_i32(code)),
Ok(event) => event,
};
let _shift = (mevent.bstate & pancurses::BUTTON_SHIFT as mmask_t) != 0;
let _alt = (mevent.bstate & pancurses::BUTTON_ALT as mmask_t) != 0;
let _ctrl = (mevent.bstate & pancurses::BUTTON_CTRL as mmask_t) != 0;
mevent.bstate &= !(pancurses::BUTTON_SHIFT | pancurses::BUTTON_ALT
| pancurses::BUTTON_CTRL) as mmask_t;
let make_event = |event| Event::Mouse {
offset: Vec2::zero(),
position: Vec2::new(mevent.x as usize, mevent.y as usize),
event: event,
};
if mevent.bstate == pancurses::REPORT_MOUSE_POSITION as mmask_t {
// The event is either a mouse drag event,
// or a weird double-release event. :S
self.last_mouse_button
.map(MouseEvent::Hold)
.map(&make_event)
.unwrap_or_else(|| {
debug!("We got a mouse drag, but no last mouse pressed?");
Event::Unknown(vec![])
})
} else {
// Identify the button
let mut bare_event = mevent.bstate & ((1 << 25) - 1);
let mut event = None;
while bare_event != 0 {
let single_event = 1 << bare_event.trailing_zeros();
bare_event ^= single_event;
// Process single_event
on_mouse_event(single_event, |e| {
if event.is_none() {
event = Some(e);
} else {
self.event_queue.push(make_event(e));
}
});
}
if let Some(event) = event {
if let Some(btn) = event.button() {
self.last_mouse_button = Some(btn);
}
make_event(event)
} else {
debug!("No event parsed?...");
Event::Unknown(vec![])
}
}
}
}
impl backend::Backend for Backend {
@ -235,177 +419,15 @@ impl backend::Backend for Backend {
self.window.mvaddstr(pos.y as i32, pos.x as i32, text);
}
fn poll_event(&mut self) -> Event {
self.event_queue.pop().unwrap_or_else(|| {
if let Some(ev) = self.window.getch() {
match ev {
pancurses::Input::Character('\n') => {
Event::Key(Key::Enter)
}
// TODO: wait for a very short delay. If more keys are
// pipelined, it may be an escape sequence.
pancurses::Input::Character('\u{7f}')
| pancurses::Input::Character('\u{8}') => {
Event::Key(Key::Backspace)
}
pancurses::Input::Character('\u{9}') => {
Event::Key(Key::Tab)
}
pancurses::Input::Character('\u{1b}') => {
Event::Key(Key::Esc)
}
pancurses::Input::Character(c) if (c as u32) <= 26 => {
Event::CtrlChar((b'a' - 1 + c as u8) as char)
}
pancurses::Input::Character(c) => Event::Char(c),
// TODO: Some key combos are not recognized by pancurses,
// but are sent as Unknown. We could still parse them here.
pancurses::Input::Unknown(code) => self.key_codes
// pancurses does some weird keycode mapping
.get(&(code + 256 + 48))
.cloned()
.unwrap_or_else(|| {
warn!("Unknown: {}", code);
Event::Unknown(split_i32(code))
}),
// TODO: I honestly have no fucking idea what KeyCodeYes is
pancurses::Input::KeyCodeYes => Event::Refresh,
pancurses::Input::KeyBreak => Event::Key(Key::PauseBreak),
pancurses::Input::KeyDown => Event::Key(Key::Down),
pancurses::Input::KeyUp => Event::Key(Key::Up),
pancurses::Input::KeyLeft => Event::Key(Key::Left),
pancurses::Input::KeyRight => Event::Key(Key::Right),
pancurses::Input::KeyHome => Event::Key(Key::Home),
pancurses::Input::KeyBackspace => {
Event::Key(Key::Backspace)
}
pancurses::Input::KeyF0 => Event::Key(Key::F0),
pancurses::Input::KeyF1 => Event::Key(Key::F1),
pancurses::Input::KeyF2 => Event::Key(Key::F2),
pancurses::Input::KeyF3 => Event::Key(Key::F3),
pancurses::Input::KeyF4 => Event::Key(Key::F4),
pancurses::Input::KeyF5 => Event::Key(Key::F5),
pancurses::Input::KeyF6 => Event::Key(Key::F6),
pancurses::Input::KeyF7 => Event::Key(Key::F7),
pancurses::Input::KeyF8 => Event::Key(Key::F8),
pancurses::Input::KeyF9 => Event::Key(Key::F9),
pancurses::Input::KeyF10 => Event::Key(Key::F10),
pancurses::Input::KeyF11 => Event::Key(Key::F11),
pancurses::Input::KeyF12 => Event::Key(Key::F12),
pancurses::Input::KeyF13 => Event::Shift(Key::F1),
pancurses::Input::KeyF14 => Event::Shift(Key::F2),
pancurses::Input::KeyF15 => Event::Shift(Key::F3),
pancurses::Input::KeyDL => Event::Refresh,
pancurses::Input::KeyIL => Event::Refresh,
pancurses::Input::KeyDC => Event::Key(Key::Del),
pancurses::Input::KeyIC => Event::Key(Key::Ins),
pancurses::Input::KeyEIC => Event::Refresh,
pancurses::Input::KeyClear => Event::Refresh,
pancurses::Input::KeyEOS => Event::Refresh,
pancurses::Input::KeyEOL => Event::Refresh,
pancurses::Input::KeySF => Event::Shift(Key::Down),
pancurses::Input::KeySR => Event::Shift(Key::Up),
pancurses::Input::KeyNPage => Event::Key(Key::PageDown),
pancurses::Input::KeyPPage => Event::Key(Key::PageUp),
pancurses::Input::KeySTab => Event::Shift(Key::Tab),
pancurses::Input::KeyCTab => Event::Ctrl(Key::Tab),
pancurses::Input::KeyCATab => Event::CtrlAlt(Key::Tab),
pancurses::Input::KeyEnter => Event::Key(Key::Enter),
pancurses::Input::KeySReset => Event::Refresh,
pancurses::Input::KeyReset => Event::Refresh,
pancurses::Input::KeyPrint => Event::Refresh,
pancurses::Input::KeyLL => Event::Refresh,
pancurses::Input::KeyAbort => Event::Refresh,
pancurses::Input::KeySHelp => Event::Refresh,
pancurses::Input::KeyLHelp => Event::Refresh,
pancurses::Input::KeyBTab => Event::Shift(Key::Tab),
pancurses::Input::KeyBeg => Event::Refresh,
pancurses::Input::KeyCancel => Event::Refresh,
pancurses::Input::KeyClose => Event::Refresh,
pancurses::Input::KeyCommand => Event::Refresh,
pancurses::Input::KeyCopy => Event::Refresh,
pancurses::Input::KeyCreate => Event::Refresh,
pancurses::Input::KeyEnd => Event::Key(Key::End),
pancurses::Input::KeyExit => Event::Refresh,
pancurses::Input::KeyFind => Event::Refresh,
pancurses::Input::KeyHelp => Event::Refresh,
pancurses::Input::KeyMark => Event::Refresh,
pancurses::Input::KeyMessage => Event::Refresh,
pancurses::Input::KeyMove => Event::Refresh,
pancurses::Input::KeyNext => Event::Refresh,
pancurses::Input::KeyOpen => Event::Refresh,
pancurses::Input::KeyOptions => Event::Refresh,
pancurses::Input::KeyPrevious => Event::Refresh,
pancurses::Input::KeyRedo => Event::Refresh,
pancurses::Input::KeyReference => Event::Refresh,
pancurses::Input::KeyRefresh => Event::Refresh,
pancurses::Input::KeyReplace => Event::Refresh,
pancurses::Input::KeyRestart => Event::Refresh,
pancurses::Input::KeyResume => Event::Refresh,
pancurses::Input::KeySave => Event::Refresh,
pancurses::Input::KeySBeg => Event::Refresh,
pancurses::Input::KeySCancel => Event::Refresh,
pancurses::Input::KeySCommand => Event::Refresh,
pancurses::Input::KeySCopy => Event::Refresh,
pancurses::Input::KeySCreate => Event::Refresh,
pancurses::Input::KeySDC => Event::Shift(Key::Del),
pancurses::Input::KeySDL => Event::Refresh,
pancurses::Input::KeySelect => Event::Refresh,
pancurses::Input::KeySEnd => Event::Shift(Key::End),
pancurses::Input::KeySEOL => Event::Refresh,
pancurses::Input::KeySExit => Event::Refresh,
pancurses::Input::KeySFind => Event::Refresh,
pancurses::Input::KeySHome => Event::Shift(Key::Home),
pancurses::Input::KeySIC => Event::Shift(Key::Ins),
pancurses::Input::KeySLeft => Event::Shift(Key::Left),
pancurses::Input::KeySMessage => Event::Refresh,
pancurses::Input::KeySMove => Event::Refresh,
pancurses::Input::KeySNext => Event::Shift(Key::PageDown),
pancurses::Input::KeySOptions => Event::Refresh,
pancurses::Input::KeySPrevious => {
Event::Shift(Key::PageUp)
}
pancurses::Input::KeySPrint => Event::Refresh,
pancurses::Input::KeySRedo => Event::Refresh,
pancurses::Input::KeySReplace => Event::Refresh,
pancurses::Input::KeySRight => Event::Shift(Key::Right),
pancurses::Input::KeySResume => Event::Refresh,
pancurses::Input::KeySSave => Event::Refresh,
pancurses::Input::KeySSuspend => Event::Refresh,
pancurses::Input::KeySUndo => Event::Refresh,
pancurses::Input::KeySuspend => Event::Refresh,
pancurses::Input::KeyUndo => Event::Refresh,
pancurses::Input::KeyResize => {
// Let pancurses adjust their structures when the
// window is resized.
// Do it for Windows only, as 'resize_term' is not
// implemented for Unix
if cfg!(target_os = "windows") {
pancurses::resize_term(0, 0);
}
Event::WindowResize
}
pancurses::Input::KeyEvent => Event::Refresh,
// TODO: mouse support
pancurses::Input::KeyMouse => self.parse_mouse_event(),
pancurses::Input::KeyA1 => Event::Refresh,
pancurses::Input::KeyA3 => Event::Refresh,
pancurses::Input::KeyB2 => Event::Key(Key::NumpadCenter),
pancurses::Input::KeyC1 => Event::Refresh,
pancurses::Input::KeyC3 => Event::Refresh,
}
} else {
Event::Refresh
}
})
}
fn start_input_thread(&mut self, event_sink: chan::Sender<Event>) {
let mut input_parser = InputParser::new(event_sink, Arc::clone(&self.window));
thread::spawn(move || {
loop {
input_parser.parse_next();
}
});
fn set_refresh_rate(&mut self, fps: u32) {
if fps == 0 {
self.window.timeout(-1);
} else {
self.window.timeout(1000 / fps as i32);
}
}
}

View File

@ -4,6 +4,10 @@ use theme;
use event;
use vec::Vec2;
use std::thread;
use chan;
pub struct Backend;
impl Backend {
@ -28,16 +32,14 @@ impl backend::Backend for Backend {
(1, 1).into()
}
fn poll_event(&mut self) -> event::Event {
event::Event::Exit
fn start_input_thread(&mut self, event_sink: chan::Sender<event::Event>) {
thread::spawn(move || event_sink.send(event::Event::Exit));
}
fn print_at(&self, _: Vec2, _: &str) {}
fn clear(&self, _: theme::Color) {}
fn set_refresh_rate(&mut self, _: u32) {}
// This sets the Colours and returns the previous colours
// to allow you to set them back when you're done.
fn set_color(&self, colors: theme::ColorPair) -> theme::ColorPair {

View File

@ -10,6 +10,8 @@
use event;
use theme;
use chan::Sender;
use vec::Vec2;
pub mod dummy;
@ -27,6 +29,8 @@ pub trait Backend {
/// This should clear any state in the terminal.
fn finish(&mut self);
fn start_input_thread(&mut self, event_sink: Sender<event::Event>);
/// Refresh the screen.
fn refresh(&mut self);
@ -36,8 +40,8 @@ pub trait Backend {
/// Returns the screen size.
fn screen_size(&self) -> Vec2;
/// Main input method
fn poll_event(&mut self) -> event::Event;
// /// Gets a receiver for input events.
// fn event_receiver(&mut self) -> Receiver<event::Event>;
/// Main method used for printing
fn print_at(&self, pos: Vec2, text: &str);
@ -45,11 +49,6 @@ pub trait Backend {
/// Clears the screen with the given color.
fn clear(&self, color: theme::Color);
/// Sets the refresh rate for the backend.
///
/// If no event is detected in the interval, send an `Event::Refresh`.
fn set_refresh_rate(&mut self, fps: u32);
/// Starts using a new color.
///
/// This should return the previously active color.

View File

@ -28,52 +28,20 @@ use vec::Vec2;
pub struct Backend {
terminal: AlternateScreen<MouseTerminal<RawTerminal<Stdout>>>,
current_style: Cell<theme::ColorPair>,
}
struct InputParser {
input: chan::Receiver<TEvent>,
resize: chan::Receiver<chan_signal::Signal>,
timeout: Option<u32>,
event_sink: chan::Sender<Event>,
last_button: Option<MouseButton>,
}
trait Effectable {
fn on(&self);
fn off(&self);
}
impl Effectable for theme::Effect {
fn on(&self) {
match *self {
theme::Effect::Simple => (),
theme::Effect::Reverse => print!("{}", tstyle::Invert),
theme::Effect::Bold => print!("{}", tstyle::Bold),
theme::Effect::Italic => print!("{}", tstyle::Italic),
theme::Effect::Underline => print!("{}", tstyle::Underline),
}
}
fn off(&self) {
match *self {
theme::Effect::Simple => (),
theme::Effect::Reverse => print!("{}", tstyle::NoInvert),
theme::Effect::Bold => print!("{}", tstyle::NoBold),
theme::Effect::Italic => print!("{}", tstyle::NoItalic),
theme::Effect::Underline => print!("{}", tstyle::NoUnderline),
}
}
}
impl Backend {
pub fn init() -> Box<Self> {
print!("{}", termion::cursor::Hide);
let resize = chan_signal::notify(&[chan_signal::Signal::WINCH]);
// TODO: lock stdout
let terminal = AlternateScreen::from(MouseTerminal::from(
::std::io::stdout().into_raw_mode().unwrap(),
));
impl InputParser {
fn new(event_sink: chan::Sender<Event>) -> Self {
let (sender, receiver) = chan::async();
// Fill the input channel
thread::spawn(move || {
for key in ::std::io::stdin().events() {
if let Ok(key) = key {
@ -82,22 +50,34 @@ impl Backend {
}
});
let c = Backend {
terminal: terminal,
current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)),
input: receiver,
resize: resize,
timeout: None,
InputParser {
resize: chan_signal::notify(&[chan_signal::Signal::WINCH]),
event_sink,
last_button: None,
};
Box::new(c)
input: receiver,
}
}
fn apply_colors(&self, colors: theme::ColorPair) {
with_color(&colors.front, |c| print!("{}", tcolor::Fg(c)));
with_color(&colors.back, |c| print!("{}", tcolor::Bg(c)));
fn next_event(&mut self) -> Event {
let result;
{
let input = &self.input;
let resize = &self.resize;
chan_select!{
resize.recv() => return Event::WindowResize,
input.recv() -> input => result = Some(input.unwrap()),
}
}
self.map_key(result.unwrap())
}
fn parse_next(&mut self) {
let event = self.next_event();
self.event_sink.send(event);
}
fn map_key(&mut self, event: TEvent) -> Event {
match event {
TEvent::Unsupported(bytes) => Event::Unknown(bytes),
@ -173,6 +153,58 @@ impl Backend {
}
}
trait Effectable {
fn on(&self);
fn off(&self);
}
impl Effectable for theme::Effect {
fn on(&self) {
match *self {
theme::Effect::Simple => (),
theme::Effect::Reverse => print!("{}", tstyle::Invert),
theme::Effect::Bold => print!("{}", tstyle::Bold),
theme::Effect::Italic => print!("{}", tstyle::Italic),
theme::Effect::Underline => print!("{}", tstyle::Underline),
}
}
fn off(&self) {
match *self {
theme::Effect::Simple => (),
theme::Effect::Reverse => print!("{}", tstyle::NoInvert),
theme::Effect::Bold => print!("{}", tstyle::NoBold),
theme::Effect::Italic => print!("{}", tstyle::NoItalic),
theme::Effect::Underline => print!("{}", tstyle::NoUnderline),
}
}
}
impl Backend {
pub fn init() -> Box<Self> {
print!("{}", termion::cursor::Hide);
// TODO: lock stdout
let terminal = AlternateScreen::from(MouseTerminal::from(
::std::io::stdout().into_raw_mode().unwrap(),
));
let c = Backend {
terminal: terminal,
current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)),
};
Box::new(c)
}
fn apply_colors(&self, colors: theme::ColorPair) {
with_color(&colors.front, |c| print!("{}", tcolor::Fg(c)));
with_color(&colors.back, |c| print!("{}", tcolor::Bg(c)));
}
}
impl backend::Backend for Backend {
fn finish(&mut self) {
print!("{}{}", termion::cursor::Show, termion::cursor::Goto(1, 1));
@ -233,32 +265,13 @@ impl backend::Backend for Backend {
);
}
fn set_refresh_rate(&mut self, fps: u32) {
self.timeout = Some(1000 / fps as u32);
}
fn poll_event(&mut self) -> Event {
let result;
{
let input = &self.input;
let resize = &self.resize;
if let Some(timeout) = self.timeout {
let timeout = chan::after_ms(timeout);
chan_select!{
timeout.recv() => return Event::Refresh,
resize.recv() => return Event::WindowResize,
input.recv() -> input => result = Some(input.unwrap()),
}
} else {
chan_select!{
resize.recv() => return Event::WindowResize,
input.recv() -> input => result = Some(input.unwrap()),
}
fn start_input_thread(&mut self, event_sink: chan::Sender<Event>) {
let mut parser = InputParser::new(event_sink);
thread::spawn(move || {
loop {
parser.parse_next();
}
}
self.map_key(result.unwrap())
});
}
}

View File

@ -1,16 +1,57 @@
use backend;
use chan;
use direction;
use event::{Callback, Event, EventResult};
use printer::Printer;
use std::any::Any;
use std::collections::HashMap;
use std::path::Path;
use std::sync::mpsc;
use theme;
use vec::Vec2;
use view::{self, Finder, IntoBoxedView, Position, View};
use views::{self, LayerPosition};
/// Central part of the cursive library.
///
/// It initializes ncurses on creation and cleans up on drop.
/// To use it, you should populate it with views, layouts and callbacks,
/// then start the event loop with run().
///
/// It uses a list of screen, with one screen active at a time.
pub struct Cursive {
theme: theme::Theme,
screens: Vec<views::StackView>,
global_callbacks: HashMap<Event, Vec<Callback>>,
menubar: views::Menubar,
// Last layer sizes of the stack view.
// If it changed, clear the screen.
last_sizes: Vec<Vec2>,
fps: u32,
active_screen: ScreenId,
running: bool,
backend: Box<backend::Backend>,
cb_source: chan::Receiver<Box<CbFunc>>,
cb_sink: chan::Sender<Box<CbFunc>>,
event_source: chan::Receiver<Event>,
}
/// Describes one of the possible interruptions we should handle.
enum Interruption {
/// An input event was received
Event(Event),
/// A callback was received
Callback(Box<CbFunc>),
/// A timeout ran out
Timeout,
}
/// Identifies a screen in the cursive root.
pub type ScreenId = usize;
@ -46,57 +87,45 @@ impl Default for Cursive {
}
}
#[cfg(all(not(feature = "termion"), not(feature = "pancurses"), feature = "bear-lib-terminal"))]
#[cfg(
all(
not(feature = "termion"),
not(feature = "pancurses"),
feature = "bear-lib-terminal"
)
)]
impl Default for Cursive {
fn default() -> Self {
Self::blt()
}
}
#[cfg(all(not(feature = "termion"), not(feature = "pancurses"), not(feature = "bear-lib-terminal"), feature = "ncurses"))]
#[cfg(
all(
not(feature = "termion"),
not(feature = "pancurses"),
not(feature = "bear-lib-terminal"),
feature = "ncurses"
)
)]
impl Default for Cursive {
fn default() -> Self {
Self::ncurses()
}
}
/// Central part of the cursive library.
///
/// It initializes ncurses on creation and cleans up on drop.
/// To use it, you should populate it with views, layouts and callbacks,
/// then start the event loop with run().
///
/// It uses a list of screen, with one screen active at a time.
pub struct Cursive {
theme: theme::Theme,
screens: Vec<views::StackView>,
global_callbacks: HashMap<Event, Vec<Callback>>,
menubar: views::Menubar,
// Last layer sizes of the stack view.
// If it changed, clear the screen.
last_sizes: Vec<Vec2>,
active_screen: ScreenId,
running: bool,
backend: Box<backend::Backend>,
cb_source: mpsc::Receiver<Box<CbFunc>>,
cb_sink: mpsc::Sender<Box<CbFunc>>,
}
impl Cursive {
/// Creates a new Cursive root, and initialize the back-end.
pub fn new(backend: Box<backend::Backend>) -> Self {
pub fn new(mut backend: Box<backend::Backend>) -> Self {
let theme = theme::load_default();
// theme.activate(&mut backend);
// let theme = theme::load_theme("assets/style.toml").unwrap();
let (tx, rx) = mpsc::channel();
let (cb_sink, cb_source) = chan::async();
let (event_sink, event_source) = chan::async();
backend.start_input_thread(event_sink);
Cursive {
fps: 0,
theme: theme,
screens: vec![views::StackView::new()],
last_sizes: Vec::new(),
@ -104,8 +133,9 @@ impl Cursive {
menubar: views::Menubar::new(),
active_screen: 0,
running: true,
cb_source: rx,
cb_sink: tx,
cb_source,
cb_sink,
event_source,
backend: backend,
}
}
@ -167,7 +197,7 @@ impl Cursive {
/// ```
///
/// [`set_fps`]: #method.set_fps
pub fn cb_sink(&self) -> &mpsc::Sender<Box<CbFunc>> {
pub fn cb_sink(&self) -> &chan::Sender<Box<CbFunc>> {
&self.cb_sink
}
@ -261,7 +291,7 @@ impl Cursive {
///
/// `filename` must point to a valid toml file.
pub fn load_theme_file<P: AsRef<Path>>(
&mut self, filename: P
&mut self, filename: P,
) -> Result<(), theme::Error> {
self.set_theme(try!(theme::load_theme_file(filename)));
Ok(())
@ -286,7 +316,8 @@ impl Cursive {
///
/// [`cb_sink`]: #method.cb_sink
pub fn set_fps(&mut self, fps: u32) {
self.backend.set_refresh_rate(fps)
// self.backend.set_refresh_rate(fps)
self.fps = fps;
}
/// Returns a reference to the currently active screen.
@ -366,7 +397,7 @@ impl Cursive {
/// # }
/// ```
pub fn call_on<V, F, R>(
&mut self, sel: &view::Selector, callback: F
&mut self, sel: &view::Selector, callback: F,
) -> Option<R>
where
V: View + Any,
@ -532,11 +563,42 @@ impl Cursive {
/// Convenient stub forwarding layer repositioning.
pub fn reposition_layer(
&mut self, layer: LayerPosition, position: Position
&mut self, layer: LayerPosition, position: Position,
) {
self.screen_mut().reposition_layer(layer, position);
}
// Wait until something happens.
fn poll(&mut self) -> Interruption {
let input_channel = &self.event_source;
let cb_channel = &self.cb_source;
if self.fps > 0 {
let timeout = 1000 / self.fps;
let timeout = chan::after_ms(timeout);
chan_select! {
input_channel.recv() -> input => {
return Interruption::Event(input.unwrap())
},
cb_channel.recv() -> cb => {
return Interruption::Callback(cb.unwrap())
},
timeout.recv() => {
return Interruption::Timeout;
},
}
} else {
chan_select! {
input_channel.recv() -> input => {
return Interruption::Event(input.unwrap())
},
cb_channel.recv() -> cb => {
return Interruption::Callback(cb.unwrap())
},
}
}
}
// Handles a key event when it was ignored by the current view
fn on_event(&mut self, event: Event) {
let cb_list = match self.global_callbacks.get(&event) {
@ -632,10 +694,6 @@ impl Cursive {
///
/// [`run(&mut self)`]: #method.run
pub fn step(&mut self) {
while let Ok(cb) = self.cb_source.try_recv() {
cb.call_box(self);
}
// Do we need to redraw everytime?
// Probably, actually.
// TODO: Do we need to re-layout everytime?
@ -647,43 +705,52 @@ impl Cursive {
self.backend.refresh();
// Wait for next event.
// (If set_fps was called, this returns -1 now and then)
let event = self.backend.poll_event();
if event == Event::Exit {
self.quit();
}
match self.poll() {
Interruption::Event(event) => {
if event == Event::Exit {
self.quit();
}
if event == Event::WindowResize {
self.clear();
}
if event == Event::WindowResize {
self.clear();
}
if let Event::Mouse {
event, position, ..
} = event
{
if event.grabs_focus() && !self.menubar.autohide
&& !self.menubar.has_submenu()
&& position.y == 0
{
self.select_menubar();
}
}
if let Event::Mouse {
event, position, ..
} = event
{
if event.grabs_focus() && !self.menubar.autohide
&& !self.menubar.has_submenu()
&& position.y == 0
{
self.select_menubar();
}
}
// Event dispatch order:
// * Focused element:
// * Menubar (if active)
// * Current screen (top layer)
// * Global callbacks
if self.menubar.receive_events() {
self.menubar.on_event(event).process(self);
} else {
let offset = if self.menubar.autohide { 0 } else { 1 };
match self.screen_mut().on_event(event.relativized((0, offset))) {
// If the event was ignored,
// it is our turn to play with it.
EventResult::Ignored => self.on_event(event),
EventResult::Consumed(None) => (),
EventResult::Consumed(Some(cb)) => cb(self),
// Event dispatch order:
// * Focused element:
// * Menubar (if active)
// * Current screen (top layer)
// * Global callbacks
if self.menubar.receive_events() {
self.menubar.on_event(event).process(self);
} else {
let offset = if self.menubar.autohide { 0 } else { 1 };
match self.screen_mut()
.on_event(event.relativized((0, offset)))
{
// If the event was ignored,
// it is our turn to play with it.
EventResult::Ignored => self.on_event(event),
EventResult::Consumed(None) => (),
EventResult::Consumed(Some(cb)) => cb(self),
}
}
},
Interruption::Callback(cb) => {
cb.call_box(self);
},
Interruption::Timeout => {
}
}
}

View File

@ -67,6 +67,8 @@ extern crate enum_map;
extern crate enumset;
#[macro_use]
extern crate log;
#[macro_use]
extern crate chan;
#[cfg(any(feature = "ncurses", feature = "pancurses"))]
#[macro_use]
@ -80,10 +82,6 @@ extern crate unicode_segmentation;
extern crate unicode_width;
extern crate xi_unicode;
#[cfg(feature = "termion")]
#[macro_use]
extern crate chan;
macro_rules! new_default(
($c:ty) => {
impl Default for $c {