mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
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:
parent
4a3bbbb998
commit
05e1212a50
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Event::Refresh
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
parser.parse_next();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -9,235 +9,44 @@ 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>,
|
||||
}
|
||||
|
||||
fn find_closest_pair(pair: &ColorPair) -> (i16, i16) {
|
||||
super::find_closest_pair(pair, pancurses::COLORS() as i16)
|
||||
struct InputParser {
|
||||
key_codes: HashMap<i32, Event>,
|
||||
last_mouse_button: Option<MouseButton>,
|
||||
event_sink: chan::Sender<Event>,
|
||||
window: Arc<pancurses::Window>,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
pub fn init() -> Box<Self> {
|
||||
::std::env::set_var("ESCDELAY", "25");
|
||||
// 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 {}
|
||||
|
||||
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: window,
|
||||
last_mouse_button: None,
|
||||
event_queue: Vec::new(),
|
||||
impl InputParser {
|
||||
fn new(event_sink: chan::Sender<Event>, window: Arc<pancurses::Window>) -> Self {
|
||||
InputParser {
|
||||
key_codes: initialize_keymap(),
|
||||
};
|
||||
|
||||
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)
|
||||
last_mouse_button: None,
|
||||
event_sink,
|
||||
window,
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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() {
|
||||
fn parse_next(&mut self) {
|
||||
let event = if let Some(ev) = self.window.getch() {
|
||||
match ev {
|
||||
pancurses::Input::Character('\n') => {
|
||||
Event::Key(Key::Enter)
|
||||
@ -396,16 +205,229 @@ impl backend::Backend for Backend {
|
||||
}
|
||||
} else {
|
||||
Event::Refresh
|
||||
}
|
||||
})
|
||||
};
|
||||
self.event_sink.send(event);
|
||||
}
|
||||
|
||||
fn set_refresh_rate(&mut self, fps: u32) {
|
||||
if fps == 0 {
|
||||
self.window.timeout(-1);
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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>) {
|
||||
let mut input_parser = InputParser::new(event_sink, Arc::clone(&self.window));
|
||||
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
input_parser.parse_next();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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 start_input_thread(&mut self, event_sink: chan::Sender<Event>) {
|
||||
let mut parser = InputParser::new(event_sink);
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
parser.parse_next();
|
||||
}
|
||||
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.map_key(result.unwrap())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
163
src/cursive.rs
163
src/cursive.rs
@ -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,8 +705,8 @@ 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();
|
||||
match self.poll() {
|
||||
Interruption::Event(event) => {
|
||||
if event == Event::Exit {
|
||||
self.quit();
|
||||
}
|
||||
@ -678,7 +736,9 @@ impl Cursive {
|
||||
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))) {
|
||||
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),
|
||||
@ -686,6 +746,13 @@ impl Cursive {
|
||||
EventResult::Consumed(Some(cb)) => cb(self),
|
||||
}
|
||||
}
|
||||
},
|
||||
Interruption::Callback(cb) => {
|
||||
cb.call_box(self);
|
||||
},
|
||||
Interruption::Timeout => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops the event loop.
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user