Merge branch 'callback_preemption'

This commit is contained in:
Alexandre Bury 2018-06-16 11:05:49 -07:00
commit b70bd95806
12 changed files with 750 additions and 535 deletions

View File

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

View File

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

View File

@ -27,7 +27,7 @@ fn main() {
.content(Button::new("Start", phase_1)), .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.set_fps(30);
siv.run(); siv.run();
@ -52,7 +52,7 @@ fn phase_1(s: &mut Cursive) {
fake_load(n_max, &counter); fake_load(n_max, &counter);
// When we're done, send a callback through the channel // 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(), .full_width(),
)); ));
@ -114,7 +114,7 @@ fn phase_2(s: &mut Cursive) {
} }
} }
cb.send(Box::new(final_step)).unwrap(); cb.send(Box::new(final_step));
}); });
} }

View File

@ -65,7 +65,7 @@ fn main() {
} }
// When we're done, shut down the application // 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 // Add a single view: progress status

View File

@ -15,6 +15,8 @@ use event::{Event, Key, MouseButton, MouseEvent};
use std::collections::HashSet; use std::collections::HashSet;
use theme::{BaseColor, Color, ColorPair, Effect}; use theme::{BaseColor, Color, ColorPair, Effect};
use vec::Vec2; use vec::Vec2;
use chan;
use std::time::{Duration, Instant};
enum ColorRole { enum ColorRole {
Foreground, Foreground,
@ -22,12 +24,12 @@ enum ColorRole {
} }
pub struct Backend { pub struct Backend {
mouse_position: Vec2,
buttons_pressed: HashSet<MouseButton>, buttons_pressed: HashSet<MouseButton>,
mouse_position: Vec2,
} }
impl Backend { impl Backend {
pub fn init() -> Box<Self> { pub fn init() -> Box<backend::Backend> {
terminal::open("Cursive", 80, 24); terminal::open("Cursive", 80, 24);
terminal::set(terminal::config::Window::empty().resizeable(true)); terminal::set(terminal::config::Window::empty().resizeable(true));
terminal::set(vec![ terminal::set(vec![
@ -42,13 +44,70 @@ impl Backend {
]); ]);
let c = Backend { let c = Backend {
mouse_position: Vec2::zero(),
buttons_pressed: HashSet::new(), buttons_pressed: HashSet::new(),
mouse_position: Vec2::zero(),
}; };
Box::new(c) Box::new(c)
} }
fn parse_next(&mut self) -> Option<Event> {
// TODO: we could add backend-specific controls here.
// Ex: ctrl+mouse wheel cause window cellsize to change
terminal::read_event().map(|ev| {
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
}
}
})
}
fn blt_keycode_to_ev( fn blt_keycode_to_ev(
&mut self, kc: KeyCode, shift: bool, ctrl: bool, &mut self, kc: KeyCode, shift: bool, ctrl: bool,
) -> Event { ) -> Event {
@ -247,66 +306,16 @@ impl backend::Backend for Backend {
terminal::print_xy(pos.x as i32, pos.y as i32, text); terminal::print_xy(pos.x as i32, pos.y as i32, text);
} }
fn set_refresh_rate(&mut self, _: u32) { fn prepare_input(&mut self, event_sink: &chan::Sender<Event>, timeout: Duration) {
// TODO: unsupported // Wait for up to `timeout_ms`.
} let start = Instant::now();
while start.elapsed() < timeout {
fn poll_event(&mut self) -> Event { if let Some(event) = self.parse_next() {
// TODO: we could add backend-specific controls here. event_sink.send(event);
// Ex: ctrl+mouse wheel cause window cellsize to change return;
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_sink.send(Event::Refresh);
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
}
} }
} }

View File

@ -4,27 +4,143 @@ use self::super::split_i32;
use self::ncurses::mmask_t; use self::ncurses::mmask_t;
use backend; use backend;
use event::{Event, Key, MouseButton, MouseEvent}; 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::cell::{Cell, RefCell};
use std::collections::HashMap; use std::collections::HashMap;
use std::ffi::CString; use std::ffi::CString;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::Write; use std::io::{Write};
use theme::{Color, ColorPair, Effect}; use std::thread;
use utf8;
use vec::Vec2; use libc;
use chan;
pub struct Backend { pub struct Backend {
current_style: Cell<ColorPair>, current_style: Cell<ColorPair>,
// Maps (front, back) ncurses colors to ncurses pairs // Maps (front, back) ncurses colors to ncurses pairs
pairs: RefCell<HashMap<(i16, i16), i16>>, pairs: RefCell<HashMap<(i16, i16), i16>>,
}
struct InputParser {
key_codes: HashMap<i32, Event>, key_codes: HashMap<i32, Event>,
last_mouse_button: Option<MouseButton>, 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;
// ncurses encodes multiple events in the same value.
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| {
// Keep one event for later,
// send the rest through the channel.
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) { fn find_closest_pair(pair: &ColorPair) -> (i16, i16) {
@ -85,11 +201,6 @@ impl Backend {
let c = Backend { let c = Backend {
current_style: Cell::new(ColorPair::from_256colors(0, 0)), current_style: Cell::new(ColorPair::from_256colors(0, 0)),
pairs: RefCell::new(HashMap::new()), pairs: RefCell::new(HashMap::new()),
last_mouse_button: None,
event_queue: Vec::new(),
key_codes: initialize_keymap(),
}; };
Box::new(c) Box::new(c)
@ -138,87 +249,6 @@ impl Backend {
ncurses::attron(style); 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 { impl backend::Backend for Backend {
@ -233,6 +263,23 @@ impl backend::Backend for Backend {
ncurses::has_colors() ncurses::has_colors()
} }
fn start_input_thread(&mut self, event_sink: chan::Sender<Event>, stops: chan::Receiver<bool>) {
let mut parser = InputParser::new(event_sink);
// Start an input thread
thread::spawn(move || {
loop {
// This sends events to the event sender.
parser.parse_next();
if stops.recv() != Some(false) {
// If the channel was closed or if `true` was sent, abort.
break;
}
}
});
}
fn finish(&mut self) { fn finish(&mut self) {
write_to_tty(b"\x1B[?1002l").unwrap(); write_to_tty(b"\x1B[?1002l").unwrap();
ncurses::endwin(); ncurses::endwin();
@ -287,32 +334,6 @@ impl backend::Backend for Backend {
fn print_at(&self, pos: Vec2, text: &str) { fn print_at(&self, pos: Vec2, text: &str) {
ncurses::mvaddstr(pos.y as i32, pos.x as i32, text); 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. /// Returns the Key enum corresponding to the given ncurses event.

View File

@ -9,234 +9,44 @@ use std::collections::HashMap;
use std::io::{stdout, Write}; use std::io::{stdout, Write};
use theme::{Color, ColorPair, Effect}; use theme::{Color, ColorPair, Effect};
use vec::Vec2; use vec::Vec2;
use std::sync::Arc;
use chan;
use std::thread;
pub struct Backend { pub struct Backend {
// Used // Used
current_style: Cell<ColorPair>, current_style: Cell<ColorPair>,
pairs: RefCell<HashMap<(i16, i16), i32>>, 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. // pancurses needs a handle to the current window.
window: pancurses::Window, window: Arc<pancurses::Window>,
} }
fn find_closest_pair(pair: &ColorPair) -> (i16, i16) { struct InputParser {
super::find_closest_pair(pair, pancurses::COLORS() as i16) key_codes: HashMap<i32, Event>,
last_mouse_button: Option<MouseButton>,
event_sink: chan::Sender<Event>,
window: Arc<pancurses::Window>,
} }
impl Backend { // Ncurses (and pancurses) are not thread-safe
pub fn init() -> Box<Self> { // (writing from two threads might cause garbage).
::std::env::set_var("ESCDELAY", "25"); // 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 {}
let window = pancurses::initscr(); impl InputParser {
window.keypad(true); fn new(event_sink: chan::Sender<Event>, window: Arc<pancurses::Window>) -> Self {
pancurses::noecho(); InputParser {
pancurses::cbreak();
pancurses::start_color();
pancurses::use_default_colors();
pancurses::curs_set(0);
pancurses::mouseinterval(0);
pancurses::mousemask(
pancurses::ALL_MOUSE_EVENTS | pancurses::REPORT_MOUSE_POSITION,
::std::ptr::null_mut(),
);
// This asks the terminal to provide us with mouse drag events
// (Mouse move when a button is pressed).
// Replacing 1002 with 1003 would give us ANY mouse move.
print!("\x1B[?1002h");
stdout().flush().expect("could not flush stdout");
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(), key_codes: initialize_keymap(),
}; last_mouse_button: None,
event_sink,
Box::new(c) window,
}
/// Save a new color pair.
fn insert_color(
&self, pairs: &mut HashMap<(i16, i16), i32>, (front, back): (i16, i16),
) -> i32 {
let n = 1 + pairs.len() as i32;
// TODO: when COLORS_PAIRS is available...
let target = if 256 > n {
// We still have plenty of space for everyone.
n
} else {
// The world is too small for both of us.
let target = n - 1;
// Remove the mapping to n-1
pairs.retain(|_, &mut v| v != target);
target
};
pairs.insert((front, back), target);
pancurses::init_pair(target as i16, front, back);
target
}
/// Checks the pair in the cache, or re-define a color if needed.
fn get_or_create(&self, pair: ColorPair) -> i32 {
let mut pairs = self.pairs.borrow_mut();
let pair = find_closest_pair(&pair);
// Find if we have this color in stock
if pairs.contains_key(&pair) {
// We got it!
pairs[&pair]
} else {
self.insert_color(&mut *pairs, pair)
} }
} }
fn set_colors(&self, pair: ColorPair) { fn parse_next(&mut self) {
let i = self.get_or_create(pair); let event = if let Some(ev) = self.window.getch() {
self.current_style.set(pair);
let style = pancurses::COLOR_PAIR(i as pancurses::chtype);
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 {
fn screen_size(&self) -> Vec2 {
// Coordinates are reversed here
let (y, x) = self.window.get_max_yx();
(x, y).into()
}
fn has_colors(&self) -> bool {
pancurses::has_colors()
}
fn finish(&mut self) {
print!("\x1B[?1002l");
stdout().flush().expect("could not flush stdout");
pancurses::endwin();
}
fn set_color(&self, colors: ColorPair) -> ColorPair {
let current = self.current_style.get();
if current != colors {
self.set_colors(colors);
}
current
}
fn set_effect(&self, effect: Effect) {
let style = match effect {
Effect::Simple => pancurses::Attribute::Normal,
Effect::Reverse => pancurses::Attribute::Reverse,
Effect::Bold => pancurses::Attribute::Bold,
Effect::Italic => pancurses::Attribute::Italic,
Effect::Underline => pancurses::Attribute::Underline,
};
self.window.attron(style);
}
fn unset_effect(&self, effect: Effect) {
let style = match effect {
Effect::Simple => pancurses::Attribute::Normal,
Effect::Reverse => pancurses::Attribute::Reverse,
Effect::Bold => pancurses::Attribute::Bold,
Effect::Italic => pancurses::Attribute::Italic,
Effect::Underline => pancurses::Attribute::Underline,
};
self.window.attroff(style);
}
fn clear(&self, color: Color) {
let id = self.get_or_create(ColorPair {
front: color,
back: color,
});
self.window.bkgd(pancurses::ColorPair(id as u8));
self.window.clear();
}
fn refresh(&mut self) {
self.window.refresh();
}
fn print_at(&self, pos: Vec2, text: &str) {
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 { match ev {
pancurses::Input::Character('\n') => { pancurses::Input::Character('\n') => {
Event::Key(Key::Enter) Event::Key(Key::Enter)
@ -395,16 +205,294 @@ impl backend::Backend for Backend {
} }
} else { } else {
Event::Refresh Event::Refresh
} };
}) self.event_sink.send(event);
} }
fn set_refresh_rate(&mut self, fps: u32) { fn parse_mouse_event(&mut self) -> Event {
if fps == 0 { let mut mevent = match pancurses::getmouse() {
self.window.timeout(-1); 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 { } else {
self.window.timeout(1000 / fps as i32); // 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) {
super::find_closest_pair(pair, pancurses::COLORS() as i16)
}
impl Backend {
pub fn init() -> Box<Self> {
::std::env::set_var("ESCDELAY", "25");
let window = pancurses::initscr();
window.keypad(true);
pancurses::noecho();
pancurses::cbreak();
pancurses::start_color();
pancurses::use_default_colors();
pancurses::curs_set(0);
pancurses::mouseinterval(0);
pancurses::mousemask(
pancurses::ALL_MOUSE_EVENTS | pancurses::REPORT_MOUSE_POSITION,
::std::ptr::null_mut(),
);
// This asks the terminal to provide us with mouse drag events
// (Mouse move when a button is pressed).
// Replacing 1002 with 1003 would give us ANY mouse move.
print!("\x1B[?1002h");
stdout().flush().expect("could not flush stdout");
let c = Backend {
current_style: Cell::new(ColorPair::from_256colors(0, 0)),
pairs: RefCell::new(HashMap::new()),
window: Arc::new(window),
};
Box::new(c)
}
/// Save a new color pair.
fn insert_color(
&self, pairs: &mut HashMap<(i16, i16), i32>, (front, back): (i16, i16),
) -> i32 {
let n = 1 + pairs.len() as i32;
// TODO: when COLORS_PAIRS is available...
let target = if 256 > n {
// We still have plenty of space for everyone.
n
} else {
// The world is too small for both of us.
let target = n - 1;
// Remove the mapping to n-1
pairs.retain(|_, &mut v| v != target);
target
};
pairs.insert((front, back), target);
pancurses::init_pair(target as i16, front, back);
target
}
/// Checks the pair in the cache, or re-define a color if needed.
fn get_or_create(&self, pair: ColorPair) -> i32 {
let mut pairs = self.pairs.borrow_mut();
let pair = find_closest_pair(&pair);
// Find if we have this color in stock
if pairs.contains_key(&pair) {
// We got it!
pairs[&pair]
} else {
self.insert_color(&mut *pairs, pair)
}
}
fn set_colors(&self, pair: ColorPair) {
let i = self.get_or_create(pair);
self.current_style.set(pair);
let style = pancurses::COLOR_PAIR(i as pancurses::chtype);
self.window.attron(style);
}
<<<<<<< HEAD
=======
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![])
}
}
}
>>>>>>> master
}
impl backend::Backend for Backend {
fn screen_size(&self) -> Vec2 {
// Coordinates are reversed here
let (y, x) = self.window.get_max_yx();
(x, y).into()
}
fn has_colors(&self) -> bool {
pancurses::has_colors()
}
fn finish(&mut self) {
print!("\x1B[?1002l");
stdout().flush().expect("could not flush stdout");
pancurses::endwin();
}
fn set_color(&self, colors: ColorPair) -> ColorPair {
let current = self.current_style.get();
if current != colors {
self.set_colors(colors);
}
current
}
fn set_effect(&self, effect: Effect) {
let style = match effect {
Effect::Simple => pancurses::Attribute::Normal,
Effect::Reverse => pancurses::Attribute::Reverse,
Effect::Bold => pancurses::Attribute::Bold,
Effect::Italic => pancurses::Attribute::Italic,
Effect::Underline => pancurses::Attribute::Underline,
};
self.window.attron(style);
}
fn unset_effect(&self, effect: Effect) {
let style = match effect {
Effect::Simple => pancurses::Attribute::Normal,
Effect::Reverse => pancurses::Attribute::Reverse,
Effect::Bold => pancurses::Attribute::Bold,
Effect::Italic => pancurses::Attribute::Italic,
Effect::Underline => pancurses::Attribute::Underline,
};
self.window.attroff(style);
}
fn clear(&self, color: Color) {
let id = self.get_or_create(ColorPair {
front: color,
back: color,
});
self.window.bkgd(pancurses::ColorPair(id as u8));
self.window.clear();
}
fn refresh(&mut self) {
self.window.refresh();
}
fn print_at(&self, pos: Vec2, text: &str) {
self.window.mvaddstr(pos.y as i32, pos.x as i32, text);
}
fn start_input_thread(&mut self, event_sink: chan::Sender<Event>, stops: chan::Receiver<bool>) {
let mut input_parser = InputParser::new(event_sink, Arc::clone(&self.window));
thread::spawn(move || {
loop {
input_parser.parse_next();
if stops.recv() != Some(false) {
// If the channel was closed or if `true` was sent, abort.
break;
}
}
});
} }
} }

View File

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

View File

@ -10,8 +10,12 @@
use event; use event;
use theme; use theme;
use chan::{Receiver, Sender};
use vec::Vec2; use vec::Vec2;
use std::time::Duration;
pub mod dummy; pub mod dummy;
pub mod blt; pub mod blt;
@ -27,6 +31,24 @@ pub trait Backend {
/// This should clear any state in the terminal. /// This should clear any state in the terminal.
fn finish(&mut self); fn finish(&mut self);
/// Starts a thread to collect input and send it to the given channel.
fn start_input_thread(&mut self, event_sink: Sender<event::Event>, running: Receiver<bool>) {
// Dummy implementation for some backends.
let _ = event_sink;
let _ = running;
}
/// Prepares the backend to collect input.
///
/// This is only required for non-thread-safe backends like BearLibTerminal
/// where we cannot collect input in a separate thread.
fn prepare_input(&mut self, event_sink: &Sender<event::Event>, timeout: Duration) {
// Dummy implementation for most backends.
// Little trick to avoid unused variables.
let _ = event_sink;
let _ = timeout;
}
/// Refresh the screen. /// Refresh the screen.
fn refresh(&mut self); fn refresh(&mut self);
@ -36,20 +58,12 @@ pub trait Backend {
/// Returns the screen size. /// Returns the screen size.
fn screen_size(&self) -> Vec2; fn screen_size(&self) -> Vec2;
/// Main input method
fn poll_event(&mut self) -> event::Event;
/// Main method used for printing /// Main method used for printing
fn print_at(&self, pos: Vec2, text: &str); fn print_at(&self, pos: Vec2, text: &str);
/// Clears the screen with the given color. /// Clears the screen with the given color.
fn clear(&self, color: theme::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. /// Starts using a new color.
/// ///
/// This should return the previously active color. /// This should return the previously active color.

View File

@ -28,52 +28,20 @@ use vec::Vec2;
pub struct Backend { pub struct Backend {
terminal: AlternateScreen<MouseTerminal<RawTerminal<Stdout>>>, terminal: AlternateScreen<MouseTerminal<RawTerminal<Stdout>>>,
current_style: Cell<theme::ColorPair>, current_style: Cell<theme::ColorPair>,
}
struct InputParser {
input: chan::Receiver<TEvent>, input: chan::Receiver<TEvent>,
resize: chan::Receiver<chan_signal::Signal>, resize: chan::Receiver<chan_signal::Signal>,
timeout: Option<u32>, event_sink: chan::Sender<Event>,
last_button: Option<MouseButton>, last_button: Option<MouseButton>,
} }
trait Effectable { impl InputParser {
fn on(&self); fn new(event_sink: chan::Sender<Event>) -> 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(),
));
let (sender, receiver) = chan::async(); let (sender, receiver) = chan::async();
// Fill the input channel
thread::spawn(move || { thread::spawn(move || {
for key in ::std::io::stdin().events() { for key in ::std::io::stdin().events() {
if let Ok(key) = key { if let Ok(key) = key {
@ -82,22 +50,34 @@ impl Backend {
} }
}); });
let c = Backend { InputParser {
terminal: terminal, resize: chan_signal::notify(&[chan_signal::Signal::WINCH]),
current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)), event_sink,
input: receiver,
resize: resize,
timeout: None,
last_button: None, last_button: None,
}; input: receiver,
}
Box::new(c)
} }
fn apply_colors(&self, colors: theme::ColorPair) { fn next_event(&mut self) -> Event {
with_color(&colors.front, |c| print!("{}", tcolor::Fg(c))); let result;
with_color(&colors.back, |c| print!("{}", tcolor::Bg(c))); {
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 { fn map_key(&mut self, event: TEvent) -> Event {
match event { match event {
TEvent::Unsupported(bytes) => Event::Unknown(bytes), 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 { impl backend::Backend for Backend {
fn finish(&mut self) { fn finish(&mut self) {
print!("{}{}", termion::cursor::Show, termion::cursor::Goto(1, 1)); print!("{}{}", termion::cursor::Show, termion::cursor::Goto(1, 1));
@ -233,32 +265,19 @@ impl backend::Backend for Backend {
); );
} }
fn set_refresh_rate(&mut self, fps: u32) { fn start_input_thread(&mut self, event_sink: chan::Sender<Event>, stops: chan::Receiver<bool>) {
self.timeout = Some(1000 / fps as u32); let mut parser = InputParser::new(event_sink);
} thread::spawn(move || {
loop {
// This sends events to the event sender.
parser.parse_next();
fn poll_event(&mut self) -> Event { if stops.recv() != Some(false) {
let result; // If the channel was closed or if `true` was sent, abort.
{ break;
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()),
} }
} }
} });
self.map_key(result.unwrap())
} }
} }

View File

@ -1,16 +1,62 @@
use backend; use backend;
use chan;
use direction; use direction;
use event::{Callback, Event, EventResult}; use event::{Callback, Event, EventResult};
use printer::Printer; use printer::Printer;
use std::any::Any; use std::any::Any;
use std::collections::HashMap; use std::collections::HashMap;
use std::time::Duration;
use std::path::Path; use std::path::Path;
use std::sync::mpsc;
use theme; use theme;
use vec::Vec2; use vec::Vec2;
use view::{self, Finder, IntoBoxedView, Position, View}; use view::{self, Finder, IntoBoxedView, Position, View};
use views::{self, LayerPosition}; 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>,
event_sink: chan::Sender<Event>,
// Sends true or false after each event.
stop_sink: chan::Sender<bool>,
}
/// 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. /// Identifies a screen in the cursive root.
pub type ScreenId = usize; pub type ScreenId = usize;
@ -73,43 +119,20 @@ impl Default for Cursive {
} }
} }
/// 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 { impl Cursive {
/// Creates a new Cursive root, and initialize the back-end. /// 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(); let theme = theme::load_default();
// theme.activate(&mut backend);
// let theme = theme::load_toml("assets/style.toml").unwrap();
let (tx, rx) = mpsc::channel(); let (cb_sink, cb_source) = chan::async();
let (event_sink, event_source) = chan::async();
let (stop_sink, stop_source) = chan::async();
backend.start_input_thread(event_sink.clone(), stop_source);
Cursive { Cursive {
fps: 0,
theme, theme,
screens: vec![views::StackView::new()], screens: vec![views::StackView::new()],
last_sizes: Vec::new(), last_sizes: Vec::new(),
@ -117,9 +140,12 @@ impl Cursive {
menubar: views::Menubar::new(), menubar: views::Menubar::new(),
active_screen: 0, active_screen: 0,
running: true, running: true,
cb_source: rx, cb_source,
cb_sink: tx, cb_sink,
event_source,
event_sink,
backend, backend,
stop_sink,
} }
} }
@ -180,7 +206,7 @@ impl Cursive {
/// ``` /// ```
/// ///
/// [`set_fps`]: #method.set_fps /// [`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 &self.cb_sink
} }
@ -299,7 +325,8 @@ impl Cursive {
/// ///
/// [`cb_sink`]: #method.cb_sink /// [`cb_sink`]: #method.cb_sink
pub fn set_fps(&mut self, fps: u32) { 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. /// Returns a reference to the currently active screen.
@ -550,6 +577,39 @@ impl Cursive {
self.screen_mut().reposition_layer(layer, 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;
self.backend.prepare_input(&self.event_sink, Duration::from_millis(30));
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 // Handles a key event when it was ignored by the current view
fn on_event(&mut self, event: Event) { fn on_event(&mut self, event: Event) {
let cb_list = match self.global_callbacks.get(&event) { let cb_list = match self.global_callbacks.get(&event) {
@ -641,10 +701,6 @@ impl Cursive {
/// ///
/// [`run(&mut self)`]: #method.run /// [`run(&mut self)`]: #method.run
pub fn step(&mut self) { pub fn step(&mut self) {
while let Ok(cb) = self.cb_source.try_recv() {
cb.call_box(self);
}
// Do we need to redraw everytime? // Do we need to redraw everytime?
// Probably, actually. // Probably, actually.
// TODO: Do we need to re-layout everytime? // TODO: Do we need to re-layout everytime?
@ -656,8 +712,8 @@ impl Cursive {
self.backend.refresh(); self.backend.refresh();
// Wait for next event. // Wait for next event.
// (If set_fps was called, this returns -1 now and then) match self.poll() {
let event = self.backend.poll_event(); Interruption::Event(event) => {
if event == Event::Exit { if event == Event::Exit {
self.quit(); self.quit();
} }
@ -670,8 +726,7 @@ impl Cursive {
event, position, .. event, position, ..
} = event } = event
{ {
if event.grabs_focus() if event.grabs_focus() && !self.menubar.autohide
&& !self.menubar.autohide
&& !self.menubar.has_submenu() && !self.menubar.has_submenu()
&& position.y == 0 && position.y == 0
{ {
@ -688,7 +743,9 @@ impl Cursive {
self.menubar.on_event(event).process(self); self.menubar.on_event(event).process(self);
} else { } else {
let offset = if self.menubar.autohide { 0 } else { 1 }; let offset = if self.menubar.autohide { 0 } else { 1 };
match self.screen_mut().on_event(event.relativized((0, offset))) { match self.screen_mut()
.on_event(event.relativized((0, offset)))
{
// If the event was ignored, // If the event was ignored,
// it is our turn to play with it. // it is our turn to play with it.
EventResult::Ignored => self.on_event(event), EventResult::Ignored => self.on_event(event),
@ -696,6 +753,17 @@ impl Cursive {
EventResult::Consumed(Some(cb)) => cb(self), EventResult::Consumed(Some(cb)) => cb(self),
} }
} }
// Ok, we processed the event.
// Now tell the backend whether he sould keep receiving.
self.stop_sink.send(!self.running);
},
Interruption::Callback(cb) => {
cb.call_box(self);
},
Interruption::Timeout => {
}
}
} }
/// Stops the event loop. /// Stops the event loop.

View File

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