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"
|
unicode-width = "0.1"
|
||||||
xi-unicode = "0.1.0"
|
xi-unicode = "0.1.0"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
chan = "0.1"
|
||||||
|
|
||||||
[dependencies.maplit]
|
[dependencies.maplit]
|
||||||
optional = true
|
optional = true
|
||||||
@ -33,10 +34,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"
|
||||||
@ -70,7 +67,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"
|
||||||
|
@ -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);
|
||||||
|
@ -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(),
|
||||||
));
|
));
|
||||||
@ -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
|
// When we're done, shut down the application
|
||||||
cb_sink
|
cb_sink.send(Box::new(|s: &mut Cursive| s.quit()));
|
||||||
.send(Box::new(|s: &mut Cursive| s.quit()))
|
|
||||||
.unwrap();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add a single view: progress status
|
// Add a single view: progress status
|
||||||
|
@ -14,6 +14,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::thread;
|
||||||
|
|
||||||
enum ColorRole {
|
enum ColorRole {
|
||||||
Foreground,
|
Foreground,
|
||||||
@ -21,31 +23,81 @@ enum ColorRole {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Backend {
|
pub struct Backend {
|
||||||
mouse_position: Vec2,
|
|
||||||
buttons_pressed: HashSet<MouseButton>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Backend {
|
struct InputParser {
|
||||||
pub fn init() -> Box<Self> {
|
event_sink: chan::Sender<Event>,
|
||||||
terminal::open("Cursive", 80, 24);
|
buttons_pressed: HashSet<MouseButton>,
|
||||||
terminal::set(terminal::config::Window::empty().resizeable(true));
|
mouse_position: Vec2,
|
||||||
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 {
|
impl InputParser {
|
||||||
mouse_position: Vec2::zero(),
|
fn new(event_sink: chan::Sender<Event>) -> Self {
|
||||||
|
InputParser {
|
||||||
|
event_sink,
|
||||||
buttons_pressed: HashSet::new(),
|
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(
|
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 {
|
impl backend::Backend for Backend {
|
||||||
fn finish(&mut self) {
|
fn finish(&mut self) {
|
||||||
terminal::close();
|
terminal::close();
|
||||||
@ -246,66 +320,14 @@ 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 start_input_thread(&mut self, event_sink: chan::Sender<Event>) {
|
||||||
// TODO: unsupported
|
let mut parser = InputParser::new(event_sink);
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_event(&mut self) -> Event {
|
thread::spawn(move || {
|
||||||
// TODO: we could add backend-specific controls here.
|
loop {
|
||||||
// Ex: ctrl+mouse wheel cause window cellsize to change
|
parser.parse_next();
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,27 +4,140 @@ use self::ncurses::mmask_t;
|
|||||||
use self::super::split_i32;
|
use self::super::split_i32;
|
||||||
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;
|
||||||
|
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) {
|
fn find_closest_pair(pair: &ColorPair) -> (i16, i16) {
|
||||||
@ -85,11 +198,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)
|
||||||
@ -139,86 +247,7 @@ 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 +262,18 @@ impl backend::Backend for Backend {
|
|||||||
ncurses::has_colors()
|
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) {
|
fn finish(&mut self) {
|
||||||
write_to_tty(b"\x1B[?1002l").unwrap();
|
write_to_tty(b"\x1B[?1002l").unwrap();
|
||||||
ncurses::endwin();
|
ncurses::endwin();
|
||||||
@ -287,32 +328,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.
|
||||||
|
@ -9,19 +9,264 @@ 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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InputParser {
|
||||||
|
key_codes: HashMap<i32, Event>,
|
||||||
|
last_mouse_button: Option<MouseButton>,
|
||||||
|
event_sink: chan::Sender<Event>,
|
||||||
|
window: Arc<pancurses::Window>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ncurses (and pancurses) are not thread-safe
|
||||||
|
// (writing from two threads might cause garbage).
|
||||||
|
// BUT it's probably fine to read while we write.
|
||||||
|
// So `InputParser` will only read, and `Backend` will mostly write.
|
||||||
|
unsafe impl Send for InputParser {}
|
||||||
|
|
||||||
|
impl InputParser {
|
||||||
|
fn new(event_sink: chan::Sender<Event>, window: Arc<pancurses::Window>) -> Self {
|
||||||
|
InputParser {
|
||||||
|
key_codes: initialize_keymap(),
|
||||||
|
last_mouse_button: None,
|
||||||
|
event_sink,
|
||||||
|
window,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_next(&mut self) {
|
||||||
|
let event = if let Some(ev) = self.window.getch() {
|
||||||
|
match ev {
|
||||||
|
pancurses::Input::Character('\n') => {
|
||||||
|
Event::Key(Key::Enter)
|
||||||
|
}
|
||||||
|
// TODO: wait for a very short delay. If more keys are
|
||||||
|
// pipelined, it may be an escape sequence.
|
||||||
|
pancurses::Input::Character('\u{7f}')
|
||||||
|
| pancurses::Input::Character('\u{8}') => {
|
||||||
|
Event::Key(Key::Backspace)
|
||||||
|
}
|
||||||
|
pancurses::Input::Character('\u{9}') => {
|
||||||
|
Event::Key(Key::Tab)
|
||||||
|
}
|
||||||
|
pancurses::Input::Character('\u{1b}') => {
|
||||||
|
Event::Key(Key::Esc)
|
||||||
|
}
|
||||||
|
pancurses::Input::Character(c) if (c as u32) <= 26 => {
|
||||||
|
Event::CtrlChar((b'a' - 1 + c as u8) as char)
|
||||||
|
}
|
||||||
|
pancurses::Input::Character(c) => Event::Char(c),
|
||||||
|
// TODO: Some key combos are not recognized by pancurses,
|
||||||
|
// but are sent as Unknown. We could still parse them here.
|
||||||
|
pancurses::Input::Unknown(code) => self.key_codes
|
||||||
|
// pancurses does some weird keycode mapping
|
||||||
|
.get(&(code + 256 + 48))
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
warn!("Unknown: {}", code);
|
||||||
|
Event::Unknown(split_i32(code))
|
||||||
|
}),
|
||||||
|
// TODO: I honestly have no fucking idea what KeyCodeYes is
|
||||||
|
pancurses::Input::KeyCodeYes => Event::Refresh,
|
||||||
|
pancurses::Input::KeyBreak => Event::Key(Key::PauseBreak),
|
||||||
|
pancurses::Input::KeyDown => Event::Key(Key::Down),
|
||||||
|
pancurses::Input::KeyUp => Event::Key(Key::Up),
|
||||||
|
pancurses::Input::KeyLeft => Event::Key(Key::Left),
|
||||||
|
pancurses::Input::KeyRight => Event::Key(Key::Right),
|
||||||
|
pancurses::Input::KeyHome => Event::Key(Key::Home),
|
||||||
|
pancurses::Input::KeyBackspace => {
|
||||||
|
Event::Key(Key::Backspace)
|
||||||
|
}
|
||||||
|
pancurses::Input::KeyF0 => Event::Key(Key::F0),
|
||||||
|
pancurses::Input::KeyF1 => Event::Key(Key::F1),
|
||||||
|
pancurses::Input::KeyF2 => Event::Key(Key::F2),
|
||||||
|
pancurses::Input::KeyF3 => Event::Key(Key::F3),
|
||||||
|
pancurses::Input::KeyF4 => Event::Key(Key::F4),
|
||||||
|
pancurses::Input::KeyF5 => Event::Key(Key::F5),
|
||||||
|
pancurses::Input::KeyF6 => Event::Key(Key::F6),
|
||||||
|
pancurses::Input::KeyF7 => Event::Key(Key::F7),
|
||||||
|
pancurses::Input::KeyF8 => Event::Key(Key::F8),
|
||||||
|
pancurses::Input::KeyF9 => Event::Key(Key::F9),
|
||||||
|
pancurses::Input::KeyF10 => Event::Key(Key::F10),
|
||||||
|
pancurses::Input::KeyF11 => Event::Key(Key::F11),
|
||||||
|
pancurses::Input::KeyF12 => Event::Key(Key::F12),
|
||||||
|
pancurses::Input::KeyF13 => Event::Shift(Key::F1),
|
||||||
|
pancurses::Input::KeyF14 => Event::Shift(Key::F2),
|
||||||
|
pancurses::Input::KeyF15 => Event::Shift(Key::F3),
|
||||||
|
pancurses::Input::KeyDL => Event::Refresh,
|
||||||
|
pancurses::Input::KeyIL => Event::Refresh,
|
||||||
|
pancurses::Input::KeyDC => Event::Key(Key::Del),
|
||||||
|
pancurses::Input::KeyIC => Event::Key(Key::Ins),
|
||||||
|
pancurses::Input::KeyEIC => Event::Refresh,
|
||||||
|
pancurses::Input::KeyClear => Event::Refresh,
|
||||||
|
pancurses::Input::KeyEOS => Event::Refresh,
|
||||||
|
pancurses::Input::KeyEOL => Event::Refresh,
|
||||||
|
pancurses::Input::KeySF => Event::Shift(Key::Down),
|
||||||
|
pancurses::Input::KeySR => Event::Shift(Key::Up),
|
||||||
|
pancurses::Input::KeyNPage => Event::Key(Key::PageDown),
|
||||||
|
pancurses::Input::KeyPPage => Event::Key(Key::PageUp),
|
||||||
|
pancurses::Input::KeySTab => Event::Shift(Key::Tab),
|
||||||
|
pancurses::Input::KeyCTab => Event::Ctrl(Key::Tab),
|
||||||
|
pancurses::Input::KeyCATab => Event::CtrlAlt(Key::Tab),
|
||||||
|
pancurses::Input::KeyEnter => Event::Key(Key::Enter),
|
||||||
|
pancurses::Input::KeySReset => Event::Refresh,
|
||||||
|
pancurses::Input::KeyReset => Event::Refresh,
|
||||||
|
pancurses::Input::KeyPrint => Event::Refresh,
|
||||||
|
pancurses::Input::KeyLL => Event::Refresh,
|
||||||
|
pancurses::Input::KeyAbort => Event::Refresh,
|
||||||
|
pancurses::Input::KeySHelp => Event::Refresh,
|
||||||
|
pancurses::Input::KeyLHelp => Event::Refresh,
|
||||||
|
pancurses::Input::KeyBTab => Event::Shift(Key::Tab),
|
||||||
|
pancurses::Input::KeyBeg => Event::Refresh,
|
||||||
|
pancurses::Input::KeyCancel => Event::Refresh,
|
||||||
|
pancurses::Input::KeyClose => Event::Refresh,
|
||||||
|
pancurses::Input::KeyCommand => Event::Refresh,
|
||||||
|
pancurses::Input::KeyCopy => Event::Refresh,
|
||||||
|
pancurses::Input::KeyCreate => Event::Refresh,
|
||||||
|
pancurses::Input::KeyEnd => Event::Key(Key::End),
|
||||||
|
pancurses::Input::KeyExit => Event::Refresh,
|
||||||
|
pancurses::Input::KeyFind => Event::Refresh,
|
||||||
|
pancurses::Input::KeyHelp => Event::Refresh,
|
||||||
|
pancurses::Input::KeyMark => Event::Refresh,
|
||||||
|
pancurses::Input::KeyMessage => Event::Refresh,
|
||||||
|
pancurses::Input::KeyMove => Event::Refresh,
|
||||||
|
pancurses::Input::KeyNext => Event::Refresh,
|
||||||
|
pancurses::Input::KeyOpen => Event::Refresh,
|
||||||
|
pancurses::Input::KeyOptions => Event::Refresh,
|
||||||
|
pancurses::Input::KeyPrevious => Event::Refresh,
|
||||||
|
pancurses::Input::KeyRedo => Event::Refresh,
|
||||||
|
pancurses::Input::KeyReference => Event::Refresh,
|
||||||
|
pancurses::Input::KeyRefresh => Event::Refresh,
|
||||||
|
pancurses::Input::KeyReplace => Event::Refresh,
|
||||||
|
pancurses::Input::KeyRestart => Event::Refresh,
|
||||||
|
pancurses::Input::KeyResume => Event::Refresh,
|
||||||
|
pancurses::Input::KeySave => Event::Refresh,
|
||||||
|
pancurses::Input::KeySBeg => Event::Refresh,
|
||||||
|
pancurses::Input::KeySCancel => Event::Refresh,
|
||||||
|
pancurses::Input::KeySCommand => Event::Refresh,
|
||||||
|
pancurses::Input::KeySCopy => Event::Refresh,
|
||||||
|
pancurses::Input::KeySCreate => Event::Refresh,
|
||||||
|
pancurses::Input::KeySDC => Event::Shift(Key::Del),
|
||||||
|
pancurses::Input::KeySDL => Event::Refresh,
|
||||||
|
pancurses::Input::KeySelect => Event::Refresh,
|
||||||
|
pancurses::Input::KeySEnd => Event::Shift(Key::End),
|
||||||
|
pancurses::Input::KeySEOL => Event::Refresh,
|
||||||
|
pancurses::Input::KeySExit => Event::Refresh,
|
||||||
|
pancurses::Input::KeySFind => Event::Refresh,
|
||||||
|
pancurses::Input::KeySHome => Event::Shift(Key::Home),
|
||||||
|
pancurses::Input::KeySIC => Event::Shift(Key::Ins),
|
||||||
|
pancurses::Input::KeySLeft => Event::Shift(Key::Left),
|
||||||
|
pancurses::Input::KeySMessage => Event::Refresh,
|
||||||
|
pancurses::Input::KeySMove => Event::Refresh,
|
||||||
|
pancurses::Input::KeySNext => Event::Shift(Key::PageDown),
|
||||||
|
pancurses::Input::KeySOptions => Event::Refresh,
|
||||||
|
pancurses::Input::KeySPrevious => {
|
||||||
|
Event::Shift(Key::PageUp)
|
||||||
|
}
|
||||||
|
pancurses::Input::KeySPrint => Event::Refresh,
|
||||||
|
pancurses::Input::KeySRedo => Event::Refresh,
|
||||||
|
pancurses::Input::KeySReplace => Event::Refresh,
|
||||||
|
pancurses::Input::KeySRight => Event::Shift(Key::Right),
|
||||||
|
pancurses::Input::KeySResume => Event::Refresh,
|
||||||
|
pancurses::Input::KeySSave => Event::Refresh,
|
||||||
|
pancurses::Input::KeySSuspend => Event::Refresh,
|
||||||
|
pancurses::Input::KeySUndo => Event::Refresh,
|
||||||
|
pancurses::Input::KeySuspend => Event::Refresh,
|
||||||
|
pancurses::Input::KeyUndo => Event::Refresh,
|
||||||
|
pancurses::Input::KeyResize => {
|
||||||
|
// Let pancurses adjust their structures when the
|
||||||
|
// window is resized.
|
||||||
|
// Do it for Windows only, as 'resize_term' is not
|
||||||
|
// implemented for Unix
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
pancurses::resize_term(0, 0);
|
||||||
|
}
|
||||||
|
Event::WindowResize
|
||||||
|
}
|
||||||
|
pancurses::Input::KeyEvent => Event::Refresh,
|
||||||
|
// TODO: mouse support
|
||||||
|
pancurses::Input::KeyMouse => self.parse_mouse_event(),
|
||||||
|
pancurses::Input::KeyA1 => Event::Refresh,
|
||||||
|
pancurses::Input::KeyA3 => Event::Refresh,
|
||||||
|
pancurses::Input::KeyB2 => Event::Key(Key::NumpadCenter),
|
||||||
|
pancurses::Input::KeyC1 => Event::Refresh,
|
||||||
|
pancurses::Input::KeyC3 => Event::Refresh,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Event::Refresh
|
||||||
|
};
|
||||||
|
self.event_sink.send(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_mouse_event(&mut self) -> Event {
|
||||||
|
let mut mevent = match pancurses::getmouse() {
|
||||||
|
Err(code) => return Event::Unknown(split_i32(code)),
|
||||||
|
Ok(event) => event,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _shift = (mevent.bstate & pancurses::BUTTON_SHIFT as mmask_t) != 0;
|
||||||
|
let _alt = (mevent.bstate & pancurses::BUTTON_ALT as mmask_t) != 0;
|
||||||
|
let _ctrl = (mevent.bstate & pancurses::BUTTON_CTRL as mmask_t) != 0;
|
||||||
|
|
||||||
|
mevent.bstate &= !(pancurses::BUTTON_SHIFT | pancurses::BUTTON_ALT
|
||||||
|
| pancurses::BUTTON_CTRL) as mmask_t;
|
||||||
|
|
||||||
|
let make_event = |event| Event::Mouse {
|
||||||
|
offset: Vec2::zero(),
|
||||||
|
position: Vec2::new(mevent.x as usize, mevent.y as usize),
|
||||||
|
event: event,
|
||||||
|
};
|
||||||
|
|
||||||
|
if mevent.bstate == pancurses::REPORT_MOUSE_POSITION as mmask_t {
|
||||||
|
// The event is either a mouse drag event,
|
||||||
|
// or a weird double-release event. :S
|
||||||
|
self.last_mouse_button
|
||||||
|
.map(MouseEvent::Hold)
|
||||||
|
.map(&make_event)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
debug!("We got a mouse drag, but no last mouse pressed?");
|
||||||
|
Event::Unknown(vec![])
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Identify the button
|
||||||
|
let mut bare_event = mevent.bstate & ((1 << 25) - 1);
|
||||||
|
|
||||||
|
let mut event = None;
|
||||||
|
while bare_event != 0 {
|
||||||
|
let single_event = 1 << bare_event.trailing_zeros();
|
||||||
|
bare_event ^= single_event;
|
||||||
|
|
||||||
|
// Process single_event
|
||||||
|
on_mouse_event(single_event, |e| {
|
||||||
|
if event.is_none() {
|
||||||
|
event = Some(e);
|
||||||
|
} else {
|
||||||
|
self.event_sink.send(make_event(e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(event) = event {
|
||||||
|
if let Some(btn) = event.button() {
|
||||||
|
self.last_mouse_button = Some(btn);
|
||||||
|
}
|
||||||
|
make_event(event)
|
||||||
|
} else {
|
||||||
|
debug!("No event parsed?...");
|
||||||
|
Event::Unknown(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_closest_pair(pair: &ColorPair) -> (i16, i16) {
|
fn find_closest_pair(pair: &ColorPair) -> (i16, i16) {
|
||||||
@ -54,10 +299,7 @@ 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()),
|
||||||
window: window,
|
window: Arc::new(window),
|
||||||
last_mouse_button: None,
|
|
||||||
event_queue: Vec::new(),
|
|
||||||
key_codes: initialize_keymap(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Box::new(c)
|
Box::new(c)
|
||||||
@ -109,64 +351,6 @@ impl Backend {
|
|||||||
self.window.attron(style);
|
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 {
|
impl backend::Backend for Backend {
|
||||||
@ -235,177 +419,15 @@ impl backend::Backend for Backend {
|
|||||||
self.window.mvaddstr(pos.y as i32, pos.x as i32, text);
|
self.window.mvaddstr(pos.y as i32, pos.x as i32, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_event(&mut self) -> Event {
|
fn start_input_thread(&mut self, event_sink: chan::Sender<Event>) {
|
||||||
self.event_queue.pop().unwrap_or_else(|| {
|
let mut input_parser = InputParser::new(event_sink, Arc::clone(&self.window));
|
||||||
if let Some(ev) = self.window.getch() {
|
|
||||||
match ev {
|
thread::spawn(move || {
|
||||||
pancurses::Input::Character('\n') => {
|
loop {
|
||||||
Event::Key(Key::Enter)
|
input_parser.parse_next();
|
||||||
}
|
}
|
||||||
// TODO: wait for a very short delay. If more keys are
|
});
|
||||||
// pipelined, it may be an escape sequence.
|
|
||||||
pancurses::Input::Character('\u{7f}')
|
|
||||||
| pancurses::Input::Character('\u{8}') => {
|
|
||||||
Event::Key(Key::Backspace)
|
|
||||||
}
|
|
||||||
pancurses::Input::Character('\u{9}') => {
|
|
||||||
Event::Key(Key::Tab)
|
|
||||||
}
|
|
||||||
pancurses::Input::Character('\u{1b}') => {
|
|
||||||
Event::Key(Key::Esc)
|
|
||||||
}
|
|
||||||
pancurses::Input::Character(c) if (c as u32) <= 26 => {
|
|
||||||
Event::CtrlChar((b'a' - 1 + c as u8) as char)
|
|
||||||
}
|
|
||||||
pancurses::Input::Character(c) => Event::Char(c),
|
|
||||||
// TODO: Some key combos are not recognized by pancurses,
|
|
||||||
// but are sent as Unknown. We could still parse them here.
|
|
||||||
pancurses::Input::Unknown(code) => self.key_codes
|
|
||||||
// pancurses does some weird keycode mapping
|
|
||||||
.get(&(code + 256 + 48))
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
warn!("Unknown: {}", code);
|
|
||||||
Event::Unknown(split_i32(code))
|
|
||||||
}),
|
|
||||||
// TODO: I honestly have no fucking idea what KeyCodeYes is
|
|
||||||
pancurses::Input::KeyCodeYes => Event::Refresh,
|
|
||||||
pancurses::Input::KeyBreak => Event::Key(Key::PauseBreak),
|
|
||||||
pancurses::Input::KeyDown => Event::Key(Key::Down),
|
|
||||||
pancurses::Input::KeyUp => Event::Key(Key::Up),
|
|
||||||
pancurses::Input::KeyLeft => Event::Key(Key::Left),
|
|
||||||
pancurses::Input::KeyRight => Event::Key(Key::Right),
|
|
||||||
pancurses::Input::KeyHome => Event::Key(Key::Home),
|
|
||||||
pancurses::Input::KeyBackspace => {
|
|
||||||
Event::Key(Key::Backspace)
|
|
||||||
}
|
|
||||||
pancurses::Input::KeyF0 => Event::Key(Key::F0),
|
|
||||||
pancurses::Input::KeyF1 => Event::Key(Key::F1),
|
|
||||||
pancurses::Input::KeyF2 => Event::Key(Key::F2),
|
|
||||||
pancurses::Input::KeyF3 => Event::Key(Key::F3),
|
|
||||||
pancurses::Input::KeyF4 => Event::Key(Key::F4),
|
|
||||||
pancurses::Input::KeyF5 => Event::Key(Key::F5),
|
|
||||||
pancurses::Input::KeyF6 => Event::Key(Key::F6),
|
|
||||||
pancurses::Input::KeyF7 => Event::Key(Key::F7),
|
|
||||||
pancurses::Input::KeyF8 => Event::Key(Key::F8),
|
|
||||||
pancurses::Input::KeyF9 => Event::Key(Key::F9),
|
|
||||||
pancurses::Input::KeyF10 => Event::Key(Key::F10),
|
|
||||||
pancurses::Input::KeyF11 => Event::Key(Key::F11),
|
|
||||||
pancurses::Input::KeyF12 => Event::Key(Key::F12),
|
|
||||||
pancurses::Input::KeyF13 => Event::Shift(Key::F1),
|
|
||||||
pancurses::Input::KeyF14 => Event::Shift(Key::F2),
|
|
||||||
pancurses::Input::KeyF15 => Event::Shift(Key::F3),
|
|
||||||
pancurses::Input::KeyDL => Event::Refresh,
|
|
||||||
pancurses::Input::KeyIL => Event::Refresh,
|
|
||||||
pancurses::Input::KeyDC => Event::Key(Key::Del),
|
|
||||||
pancurses::Input::KeyIC => Event::Key(Key::Ins),
|
|
||||||
pancurses::Input::KeyEIC => Event::Refresh,
|
|
||||||
pancurses::Input::KeyClear => Event::Refresh,
|
|
||||||
pancurses::Input::KeyEOS => Event::Refresh,
|
|
||||||
pancurses::Input::KeyEOL => Event::Refresh,
|
|
||||||
pancurses::Input::KeySF => Event::Shift(Key::Down),
|
|
||||||
pancurses::Input::KeySR => Event::Shift(Key::Up),
|
|
||||||
pancurses::Input::KeyNPage => Event::Key(Key::PageDown),
|
|
||||||
pancurses::Input::KeyPPage => Event::Key(Key::PageUp),
|
|
||||||
pancurses::Input::KeySTab => Event::Shift(Key::Tab),
|
|
||||||
pancurses::Input::KeyCTab => Event::Ctrl(Key::Tab),
|
|
||||||
pancurses::Input::KeyCATab => Event::CtrlAlt(Key::Tab),
|
|
||||||
pancurses::Input::KeyEnter => Event::Key(Key::Enter),
|
|
||||||
pancurses::Input::KeySReset => Event::Refresh,
|
|
||||||
pancurses::Input::KeyReset => Event::Refresh,
|
|
||||||
pancurses::Input::KeyPrint => Event::Refresh,
|
|
||||||
pancurses::Input::KeyLL => Event::Refresh,
|
|
||||||
pancurses::Input::KeyAbort => Event::Refresh,
|
|
||||||
pancurses::Input::KeySHelp => Event::Refresh,
|
|
||||||
pancurses::Input::KeyLHelp => Event::Refresh,
|
|
||||||
pancurses::Input::KeyBTab => Event::Shift(Key::Tab),
|
|
||||||
pancurses::Input::KeyBeg => Event::Refresh,
|
|
||||||
pancurses::Input::KeyCancel => Event::Refresh,
|
|
||||||
pancurses::Input::KeyClose => Event::Refresh,
|
|
||||||
pancurses::Input::KeyCommand => Event::Refresh,
|
|
||||||
pancurses::Input::KeyCopy => Event::Refresh,
|
|
||||||
pancurses::Input::KeyCreate => Event::Refresh,
|
|
||||||
pancurses::Input::KeyEnd => Event::Key(Key::End),
|
|
||||||
pancurses::Input::KeyExit => Event::Refresh,
|
|
||||||
pancurses::Input::KeyFind => Event::Refresh,
|
|
||||||
pancurses::Input::KeyHelp => Event::Refresh,
|
|
||||||
pancurses::Input::KeyMark => Event::Refresh,
|
|
||||||
pancurses::Input::KeyMessage => Event::Refresh,
|
|
||||||
pancurses::Input::KeyMove => Event::Refresh,
|
|
||||||
pancurses::Input::KeyNext => Event::Refresh,
|
|
||||||
pancurses::Input::KeyOpen => Event::Refresh,
|
|
||||||
pancurses::Input::KeyOptions => Event::Refresh,
|
|
||||||
pancurses::Input::KeyPrevious => Event::Refresh,
|
|
||||||
pancurses::Input::KeyRedo => Event::Refresh,
|
|
||||||
pancurses::Input::KeyReference => Event::Refresh,
|
|
||||||
pancurses::Input::KeyRefresh => Event::Refresh,
|
|
||||||
pancurses::Input::KeyReplace => Event::Refresh,
|
|
||||||
pancurses::Input::KeyRestart => Event::Refresh,
|
|
||||||
pancurses::Input::KeyResume => Event::Refresh,
|
|
||||||
pancurses::Input::KeySave => Event::Refresh,
|
|
||||||
pancurses::Input::KeySBeg => Event::Refresh,
|
|
||||||
pancurses::Input::KeySCancel => Event::Refresh,
|
|
||||||
pancurses::Input::KeySCommand => Event::Refresh,
|
|
||||||
pancurses::Input::KeySCopy => Event::Refresh,
|
|
||||||
pancurses::Input::KeySCreate => Event::Refresh,
|
|
||||||
pancurses::Input::KeySDC => Event::Shift(Key::Del),
|
|
||||||
pancurses::Input::KeySDL => Event::Refresh,
|
|
||||||
pancurses::Input::KeySelect => Event::Refresh,
|
|
||||||
pancurses::Input::KeySEnd => Event::Shift(Key::End),
|
|
||||||
pancurses::Input::KeySEOL => Event::Refresh,
|
|
||||||
pancurses::Input::KeySExit => Event::Refresh,
|
|
||||||
pancurses::Input::KeySFind => Event::Refresh,
|
|
||||||
pancurses::Input::KeySHome => Event::Shift(Key::Home),
|
|
||||||
pancurses::Input::KeySIC => Event::Shift(Key::Ins),
|
|
||||||
pancurses::Input::KeySLeft => Event::Shift(Key::Left),
|
|
||||||
pancurses::Input::KeySMessage => Event::Refresh,
|
|
||||||
pancurses::Input::KeySMove => Event::Refresh,
|
|
||||||
pancurses::Input::KeySNext => Event::Shift(Key::PageDown),
|
|
||||||
pancurses::Input::KeySOptions => Event::Refresh,
|
|
||||||
pancurses::Input::KeySPrevious => {
|
|
||||||
Event::Shift(Key::PageUp)
|
|
||||||
}
|
|
||||||
pancurses::Input::KeySPrint => Event::Refresh,
|
|
||||||
pancurses::Input::KeySRedo => Event::Refresh,
|
|
||||||
pancurses::Input::KeySReplace => Event::Refresh,
|
|
||||||
pancurses::Input::KeySRight => Event::Shift(Key::Right),
|
|
||||||
pancurses::Input::KeySResume => Event::Refresh,
|
|
||||||
pancurses::Input::KeySSave => Event::Refresh,
|
|
||||||
pancurses::Input::KeySSuspend => Event::Refresh,
|
|
||||||
pancurses::Input::KeySUndo => Event::Refresh,
|
|
||||||
pancurses::Input::KeySuspend => Event::Refresh,
|
|
||||||
pancurses::Input::KeyUndo => Event::Refresh,
|
|
||||||
pancurses::Input::KeyResize => {
|
|
||||||
// Let pancurses adjust their structures when the
|
|
||||||
// window is resized.
|
|
||||||
// Do it for Windows only, as 'resize_term' is not
|
|
||||||
// implemented for Unix
|
|
||||||
if cfg!(target_os = "windows") {
|
|
||||||
pancurses::resize_term(0, 0);
|
|
||||||
}
|
|
||||||
Event::WindowResize
|
|
||||||
}
|
|
||||||
pancurses::Input::KeyEvent => Event::Refresh,
|
|
||||||
// TODO: mouse support
|
|
||||||
pancurses::Input::KeyMouse => self.parse_mouse_event(),
|
|
||||||
pancurses::Input::KeyA1 => Event::Refresh,
|
|
||||||
pancurses::Input::KeyA3 => Event::Refresh,
|
|
||||||
pancurses::Input::KeyB2 => Event::Key(Key::NumpadCenter),
|
|
||||||
pancurses::Input::KeyC1 => Event::Refresh,
|
|
||||||
pancurses::Input::KeyC3 => Event::Refresh,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Event::Refresh
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_refresh_rate(&mut self, fps: u32) {
|
|
||||||
if fps == 0 {
|
|
||||||
self.window.timeout(-1);
|
|
||||||
} else {
|
|
||||||
self.window.timeout(1000 / fps as i32);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,10 @@ use theme;
|
|||||||
use event;
|
use event;
|
||||||
use vec::Vec2;
|
use vec::Vec2;
|
||||||
|
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
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 start_input_thread(&mut self, event_sink: chan::Sender<event::Event>) {
|
||||||
event::Event::Exit
|
thread::spawn(move || 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 {
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
use event;
|
use event;
|
||||||
use theme;
|
use theme;
|
||||||
|
|
||||||
|
use chan::Sender;
|
||||||
|
|
||||||
use vec::Vec2;
|
use vec::Vec2;
|
||||||
|
|
||||||
pub mod dummy;
|
pub mod dummy;
|
||||||
@ -27,6 +29,8 @@ 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);
|
||||||
|
|
||||||
|
fn start_input_thread(&mut self, event_sink: Sender<event::Event>);
|
||||||
|
|
||||||
/// Refresh the screen.
|
/// Refresh the screen.
|
||||||
fn refresh(&mut self);
|
fn refresh(&mut self);
|
||||||
|
|
||||||
@ -36,8 +40,8 @@ pub trait Backend {
|
|||||||
/// Returns the screen size.
|
/// Returns the screen size.
|
||||||
fn screen_size(&self) -> Vec2;
|
fn screen_size(&self) -> Vec2;
|
||||||
|
|
||||||
/// Main input method
|
// /// Gets a receiver for input events.
|
||||||
fn poll_event(&mut self) -> event::Event;
|
// fn event_receiver(&mut self) -> Receiver<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);
|
||||||
@ -45,11 +49,6 @@ pub trait Backend {
|
|||||||
/// 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.
|
||||||
|
@ -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,13 @@ impl backend::Backend for Backend {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_refresh_rate(&mut self, fps: u32) {
|
fn start_input_thread(&mut self, event_sink: chan::Sender<Event>) {
|
||||||
self.timeout = Some(1000 / fps as u32);
|
let mut parser = InputParser::new(event_sink);
|
||||||
}
|
thread::spawn(move || {
|
||||||
|
loop {
|
||||||
fn poll_event(&mut self) -> Event {
|
parser.parse_next();
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
225
src/cursive.rs
225
src/cursive.rs
@ -1,16 +1,57 @@
|
|||||||
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::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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
|
||||||
@ -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 {
|
impl Default for Cursive {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::blt()
|
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 {
|
impl Default for Cursive {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::ncurses()
|
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 {
|
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_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 {
|
Cursive {
|
||||||
|
fps: 0,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
screens: vec![views::StackView::new()],
|
screens: vec![views::StackView::new()],
|
||||||
last_sizes: Vec::new(),
|
last_sizes: Vec::new(),
|
||||||
@ -104,8 +133,9 @@ 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,
|
||||||
backend: backend,
|
backend: backend,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,7 +197,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,7 +291,7 @@ impl Cursive {
|
|||||||
///
|
///
|
||||||
/// `filename` must point to a valid toml file.
|
/// `filename` must point to a valid toml file.
|
||||||
pub fn load_theme_file<P: AsRef<Path>>(
|
pub fn load_theme_file<P: AsRef<Path>>(
|
||||||
&mut self, filename: P
|
&mut self, filename: P,
|
||||||
) -> Result<(), theme::Error> {
|
) -> Result<(), theme::Error> {
|
||||||
self.set_theme(try!(theme::load_theme_file(filename)));
|
self.set_theme(try!(theme::load_theme_file(filename)));
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -286,7 +316,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.
|
||||||
@ -366,7 +397,7 @@ impl Cursive {
|
|||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn call_on<V, F, R>(
|
pub fn call_on<V, F, R>(
|
||||||
&mut self, sel: &view::Selector, callback: F
|
&mut self, sel: &view::Selector, callback: F,
|
||||||
) -> Option<R>
|
) -> Option<R>
|
||||||
where
|
where
|
||||||
V: View + Any,
|
V: View + Any,
|
||||||
@ -532,11 +563,42 @@ impl Cursive {
|
|||||||
|
|
||||||
/// Convenient stub forwarding layer repositioning.
|
/// Convenient stub forwarding layer repositioning.
|
||||||
pub fn reposition_layer(
|
pub fn reposition_layer(
|
||||||
&mut self, layer: LayerPosition, position: Position
|
&mut self, layer: LayerPosition, position: Position,
|
||||||
) {
|
) {
|
||||||
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;
|
||||||
|
|
||||||
|
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) {
|
||||||
@ -632,10 +694,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?
|
||||||
@ -647,43 +705,52 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
if event == Event::WindowResize {
|
if event == Event::WindowResize {
|
||||||
self.clear();
|
self.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Event::Mouse {
|
if let Event::Mouse {
|
||||||
event, position, ..
|
event, position, ..
|
||||||
} = event
|
} = event
|
||||||
{
|
{
|
||||||
if event.grabs_focus() && !self.menubar.autohide
|
if event.grabs_focus() && !self.menubar.autohide
|
||||||
&& !self.menubar.has_submenu()
|
&& !self.menubar.has_submenu()
|
||||||
&& position.y == 0
|
&& position.y == 0
|
||||||
{
|
{
|
||||||
self.select_menubar();
|
self.select_menubar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event dispatch order:
|
// Event dispatch order:
|
||||||
// * Focused element:
|
// * Focused element:
|
||||||
// * Menubar (if active)
|
// * Menubar (if active)
|
||||||
// * Current screen (top layer)
|
// * Current screen (top layer)
|
||||||
// * Global callbacks
|
// * Global callbacks
|
||||||
if self.menubar.receive_events() {
|
if self.menubar.receive_events() {
|
||||||
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()
|
||||||
// If the event was ignored,
|
.on_event(event.relativized((0, offset)))
|
||||||
// it is our turn to play with it.
|
{
|
||||||
EventResult::Ignored => self.on_event(event),
|
// If the event was ignored,
|
||||||
EventResult::Consumed(None) => (),
|
// it is our turn to play with it.
|
||||||
EventResult::Consumed(Some(cb)) => cb(self),
|
EventResult::Ignored => self.on_event(event),
|
||||||
|
EventResult::Consumed(None) => (),
|
||||||
|
EventResult::Consumed(Some(cb)) => cb(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Interruption::Callback(cb) => {
|
||||||
|
cb.call_box(self);
|
||||||
|
},
|
||||||
|
Interruption::Timeout => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user