mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-12 20:23:35 +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,235 +9,44 @@ use std::collections::HashMap;
|
|||||||
use std::io::{stdout, Write};
|
use std::io::{stdout, Write};
|
||||||
use theme::{Color, ColorPair, Effect};
|
use theme::{Color, ColorPair, Effect};
|
||||||
use vec::Vec2;
|
use vec::Vec2;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use chan;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
pub struct Backend {
|
pub struct Backend {
|
||||||
// Used
|
// Used
|
||||||
current_style: Cell<ColorPair>,
|
current_style: Cell<ColorPair>,
|
||||||
pairs: RefCell<HashMap<(i16, i16), i32>>,
|
pairs: RefCell<HashMap<(i16, i16), i32>>,
|
||||||
|
|
||||||
key_codes: HashMap<i32, Event>,
|
|
||||||
|
|
||||||
last_mouse_button: Option<MouseButton>,
|
|
||||||
event_queue: Vec<Event>,
|
|
||||||
|
|
||||||
// pancurses needs a handle to the current window.
|
// pancurses needs a handle to the current window.
|
||||||
window: pancurses::Window,
|
window: Arc<pancurses::Window>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_closest_pair(pair: &ColorPair) -> (i16, i16) {
|
struct InputParser {
|
||||||
super::find_closest_pair(pair, pancurses::COLORS() as i16)
|
key_codes: HashMap<i32, Event>,
|
||||||
|
last_mouse_button: Option<MouseButton>,
|
||||||
|
event_sink: chan::Sender<Event>,
|
||||||
|
window: Arc<pancurses::Window>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Backend {
|
// Ncurses (and pancurses) are not thread-safe
|
||||||
pub fn init() -> Box<Self> {
|
// (writing from two threads might cause garbage).
|
||||||
::std::env::set_var("ESCDELAY", "25");
|
// BUT it's probably fine to read while we write.
|
||||||
|
// So `InputParser` will only read, and `Backend` will mostly write.
|
||||||
|
unsafe impl Send for InputParser {}
|
||||||
|
|
||||||
let window = pancurses::initscr();
|
impl InputParser {
|
||||||
window.keypad(true);
|
fn new(event_sink: chan::Sender<Event>, window: Arc<pancurses::Window>) -> Self {
|
||||||
pancurses::noecho();
|
InputParser {
|
||||||
pancurses::cbreak();
|
|
||||||
pancurses::start_color();
|
|
||||||
pancurses::use_default_colors();
|
|
||||||
pancurses::curs_set(0);
|
|
||||||
pancurses::mouseinterval(0);
|
|
||||||
pancurses::mousemask(
|
|
||||||
pancurses::ALL_MOUSE_EVENTS | pancurses::REPORT_MOUSE_POSITION,
|
|
||||||
::std::ptr::null_mut(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// This asks the terminal to provide us with mouse drag events
|
|
||||||
// (Mouse move when a button is pressed).
|
|
||||||
// Replacing 1002 with 1003 would give us ANY mouse move.
|
|
||||||
print!("\x1B[?1002h");
|
|
||||||
stdout().flush().expect("could not flush stdout");
|
|
||||||
|
|
||||||
let c = Backend {
|
|
||||||
current_style: Cell::new(ColorPair::from_256colors(0, 0)),
|
|
||||||
pairs: RefCell::new(HashMap::new()),
|
|
||||||
window: window,
|
|
||||||
last_mouse_button: None,
|
|
||||||
event_queue: Vec::new(),
|
|
||||||
key_codes: initialize_keymap(),
|
key_codes: initialize_keymap(),
|
||||||
};
|
last_mouse_button: None,
|
||||||
|
event_sink,
|
||||||
Box::new(c)
|
window,
|
||||||
}
|
|
||||||
|
|
||||||
/// Save a new color pair.
|
|
||||||
fn insert_color(
|
|
||||||
&self,
|
|
||||||
pairs: &mut HashMap<(i16,i16), i32>,
|
|
||||||
(front, back): (i16, i16),
|
|
||||||
) -> i32 {
|
|
||||||
let n = 1 + pairs.len() as i32;
|
|
||||||
|
|
||||||
// TODO: when COLORS_PAIRS is available...
|
|
||||||
let target = if 256 > n {
|
|
||||||
// We still have plenty of space for everyone.
|
|
||||||
n
|
|
||||||
} else {
|
|
||||||
// The world is too small for both of us.
|
|
||||||
let target = n - 1;
|
|
||||||
// Remove the mapping to n-1
|
|
||||||
pairs.retain(|_, &mut v| v != target);
|
|
||||||
target
|
|
||||||
};
|
|
||||||
pairs.insert((front, back), target);
|
|
||||||
pancurses::init_pair(target as i16, front, back);
|
|
||||||
target
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks the pair in the cache, or re-define a color if needed.
|
|
||||||
fn get_or_create(&self, pair: ColorPair) -> i32 {
|
|
||||||
let mut pairs = self.pairs.borrow_mut();
|
|
||||||
let pair = find_closest_pair(&pair);
|
|
||||||
|
|
||||||
// Find if we have this color in stock
|
|
||||||
if pairs.contains_key(&pair) {
|
|
||||||
// We got it!
|
|
||||||
pairs[&pair]
|
|
||||||
} else {
|
|
||||||
self.insert_color(&mut *pairs, pair)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_colors(&self, pair: ColorPair) {
|
fn parse_next(&mut self) {
|
||||||
let i = self.get_or_create(pair);
|
let event = if let Some(ev) = self.window.getch() {
|
||||||
|
|
||||||
self.current_style.set(pair);
|
|
||||||
let style = pancurses::COLOR_PAIR(i as pancurses::chtype);
|
|
||||||
self.window.attron(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_mouse_event(&mut self) -> Event {
|
|
||||||
let mut mevent = match pancurses::getmouse() {
|
|
||||||
Err(code) => return Event::Unknown(split_i32(code)),
|
|
||||||
Ok(event) => event,
|
|
||||||
};
|
|
||||||
|
|
||||||
let _shift = (mevent.bstate & pancurses::BUTTON_SHIFT as mmask_t) != 0;
|
|
||||||
let _alt = (mevent.bstate & pancurses::BUTTON_ALT as mmask_t) != 0;
|
|
||||||
let _ctrl = (mevent.bstate & pancurses::BUTTON_CTRL as mmask_t) != 0;
|
|
||||||
|
|
||||||
mevent.bstate &= !(pancurses::BUTTON_SHIFT | pancurses::BUTTON_ALT
|
|
||||||
| pancurses::BUTTON_CTRL) as mmask_t;
|
|
||||||
|
|
||||||
let make_event = |event| Event::Mouse {
|
|
||||||
offset: Vec2::zero(),
|
|
||||||
position: Vec2::new(mevent.x as usize, mevent.y as usize),
|
|
||||||
event: event,
|
|
||||||
};
|
|
||||||
|
|
||||||
if mevent.bstate == pancurses::REPORT_MOUSE_POSITION as mmask_t {
|
|
||||||
// The event is either a mouse drag event,
|
|
||||||
// or a weird double-release event. :S
|
|
||||||
self.last_mouse_button
|
|
||||||
.map(MouseEvent::Hold)
|
|
||||||
.map(&make_event)
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
debug!("We got a mouse drag, but no last mouse pressed?");
|
|
||||||
Event::Unknown(vec![])
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Identify the button
|
|
||||||
let mut bare_event = mevent.bstate & ((1 << 25) - 1);
|
|
||||||
|
|
||||||
let mut event = None;
|
|
||||||
while bare_event != 0 {
|
|
||||||
let single_event = 1 << bare_event.trailing_zeros();
|
|
||||||
bare_event ^= single_event;
|
|
||||||
|
|
||||||
// Process single_event
|
|
||||||
on_mouse_event(single_event, |e| {
|
|
||||||
if event.is_none() {
|
|
||||||
event = Some(e);
|
|
||||||
} else {
|
|
||||||
self.event_queue.push(make_event(e));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if let Some(event) = event {
|
|
||||||
if let Some(btn) = event.button() {
|
|
||||||
self.last_mouse_button = Some(btn);
|
|
||||||
}
|
|
||||||
make_event(event)
|
|
||||||
} else {
|
|
||||||
debug!("No event parsed?...");
|
|
||||||
Event::Unknown(vec![])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl backend::Backend for Backend {
|
|
||||||
fn screen_size(&self) -> Vec2 {
|
|
||||||
// Coordinates are reversed here
|
|
||||||
let (y, x) = self.window.get_max_yx();
|
|
||||||
(x, y).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_colors(&self) -> bool {
|
|
||||||
pancurses::has_colors()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish(&mut self) {
|
|
||||||
print!("\x1B[?1002l");
|
|
||||||
stdout().flush().expect("could not flush stdout");
|
|
||||||
pancurses::endwin();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_color(&self, colors: ColorPair) -> ColorPair {
|
|
||||||
let current = self.current_style.get();
|
|
||||||
|
|
||||||
if current != colors {
|
|
||||||
self.set_colors(colors);
|
|
||||||
}
|
|
||||||
|
|
||||||
current
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_effect(&self, effect: Effect) {
|
|
||||||
let style = match effect {
|
|
||||||
Effect::Simple => pancurses::Attribute::Normal,
|
|
||||||
Effect::Reverse => pancurses::Attribute::Reverse,
|
|
||||||
Effect::Bold => pancurses::Attribute::Bold,
|
|
||||||
Effect::Italic => pancurses::Attribute::Italic,
|
|
||||||
Effect::Underline => pancurses::Attribute::Underline,
|
|
||||||
};
|
|
||||||
self.window.attron(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unset_effect(&self, effect: Effect) {
|
|
||||||
let style = match effect {
|
|
||||||
Effect::Simple => pancurses::Attribute::Normal,
|
|
||||||
Effect::Reverse => pancurses::Attribute::Reverse,
|
|
||||||
Effect::Bold => pancurses::Attribute::Bold,
|
|
||||||
Effect::Italic => pancurses::Attribute::Italic,
|
|
||||||
Effect::Underline => pancurses::Attribute::Underline,
|
|
||||||
};
|
|
||||||
self.window.attroff(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear(&self, color: Color) {
|
|
||||||
let id = self.get_or_create(ColorPair {
|
|
||||||
front: color,
|
|
||||||
back: color,
|
|
||||||
});
|
|
||||||
self.window.bkgd(pancurses::ColorPair(id as u8));
|
|
||||||
self.window.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn refresh(&mut self) {
|
|
||||||
self.window.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_at(&self, pos: Vec2, text: &str) {
|
|
||||||
self.window.mvaddstr(pos.y as i32, pos.x as i32, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_event(&mut self) -> Event {
|
|
||||||
self.event_queue.pop().unwrap_or_else(|| {
|
|
||||||
if let Some(ev) = self.window.getch() {
|
|
||||||
match ev {
|
match ev {
|
||||||
pancurses::Input::Character('\n') => {
|
pancurses::Input::Character('\n') => {
|
||||||
Event::Key(Key::Enter)
|
Event::Key(Key::Enter)
|
||||||
@ -396,16 +205,229 @@ impl backend::Backend for Backend {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Event::Refresh
|
Event::Refresh
|
||||||
}
|
};
|
||||||
})
|
self.event_sink.send(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_refresh_rate(&mut self, fps: u32) {
|
fn parse_mouse_event(&mut self) -> Event {
|
||||||
if fps == 0 {
|
let mut mevent = match pancurses::getmouse() {
|
||||||
self.window.timeout(-1);
|
Err(code) => return Event::Unknown(split_i32(code)),
|
||||||
|
Ok(event) => event,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _shift = (mevent.bstate & pancurses::BUTTON_SHIFT as mmask_t) != 0;
|
||||||
|
let _alt = (mevent.bstate & pancurses::BUTTON_ALT as mmask_t) != 0;
|
||||||
|
let _ctrl = (mevent.bstate & pancurses::BUTTON_CTRL as mmask_t) != 0;
|
||||||
|
|
||||||
|
mevent.bstate &= !(pancurses::BUTTON_SHIFT | pancurses::BUTTON_ALT
|
||||||
|
| pancurses::BUTTON_CTRL) as mmask_t;
|
||||||
|
|
||||||
|
let make_event = |event| Event::Mouse {
|
||||||
|
offset: Vec2::zero(),
|
||||||
|
position: Vec2::new(mevent.x as usize, mevent.y as usize),
|
||||||
|
event: event,
|
||||||
|
};
|
||||||
|
|
||||||
|
if mevent.bstate == pancurses::REPORT_MOUSE_POSITION as mmask_t {
|
||||||
|
// The event is either a mouse drag event,
|
||||||
|
// or a weird double-release event. :S
|
||||||
|
self.last_mouse_button
|
||||||
|
.map(MouseEvent::Hold)
|
||||||
|
.map(&make_event)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
debug!("We got a mouse drag, but no last mouse pressed?");
|
||||||
|
Event::Unknown(vec![])
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
self.window.timeout(1000 / fps as i32);
|
// Identify the button
|
||||||
|
let mut bare_event = mevent.bstate & ((1 << 25) - 1);
|
||||||
|
|
||||||
|
let mut event = None;
|
||||||
|
while bare_event != 0 {
|
||||||
|
let single_event = 1 << bare_event.trailing_zeros();
|
||||||
|
bare_event ^= single_event;
|
||||||
|
|
||||||
|
// Process single_event
|
||||||
|
on_mouse_event(single_event, |e| {
|
||||||
|
if event.is_none() {
|
||||||
|
event = Some(e);
|
||||||
|
} else {
|
||||||
|
self.event_sink.send(make_event(e));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(event) = event {
|
||||||
|
if let Some(btn) = event.button() {
|
||||||
|
self.last_mouse_button = Some(btn);
|
||||||
|
}
|
||||||
|
make_event(event)
|
||||||
|
} else {
|
||||||
|
debug!("No event parsed?...");
|
||||||
|
Event::Unknown(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_closest_pair(pair: &ColorPair) -> (i16, i16) {
|
||||||
|
super::find_closest_pair(pair, pancurses::COLORS() as i16)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Backend {
|
||||||
|
pub fn init() -> Box<Self> {
|
||||||
|
::std::env::set_var("ESCDELAY", "25");
|
||||||
|
|
||||||
|
let window = pancurses::initscr();
|
||||||
|
window.keypad(true);
|
||||||
|
pancurses::noecho();
|
||||||
|
pancurses::cbreak();
|
||||||
|
pancurses::start_color();
|
||||||
|
pancurses::use_default_colors();
|
||||||
|
pancurses::curs_set(0);
|
||||||
|
pancurses::mouseinterval(0);
|
||||||
|
pancurses::mousemask(
|
||||||
|
pancurses::ALL_MOUSE_EVENTS | pancurses::REPORT_MOUSE_POSITION,
|
||||||
|
::std::ptr::null_mut(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// This asks the terminal to provide us with mouse drag events
|
||||||
|
// (Mouse move when a button is pressed).
|
||||||
|
// Replacing 1002 with 1003 would give us ANY mouse move.
|
||||||
|
print!("\x1B[?1002h");
|
||||||
|
stdout().flush().expect("could not flush stdout");
|
||||||
|
|
||||||
|
let c = Backend {
|
||||||
|
current_style: Cell::new(ColorPair::from_256colors(0, 0)),
|
||||||
|
pairs: RefCell::new(HashMap::new()),
|
||||||
|
window: Arc::new(window),
|
||||||
|
};
|
||||||
|
|
||||||
|
Box::new(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save a new color pair.
|
||||||
|
fn insert_color(
|
||||||
|
&self,
|
||||||
|
pairs: &mut HashMap<(i16,i16), i32>,
|
||||||
|
(front, back): (i16, i16),
|
||||||
|
) -> i32 {
|
||||||
|
let n = 1 + pairs.len() as i32;
|
||||||
|
|
||||||
|
// TODO: when COLORS_PAIRS is available...
|
||||||
|
let target = if 256 > n {
|
||||||
|
// We still have plenty of space for everyone.
|
||||||
|
n
|
||||||
|
} else {
|
||||||
|
// The world is too small for both of us.
|
||||||
|
let target = n - 1;
|
||||||
|
// Remove the mapping to n-1
|
||||||
|
pairs.retain(|_, &mut v| v != target);
|
||||||
|
target
|
||||||
|
};
|
||||||
|
pairs.insert((front, back), target);
|
||||||
|
pancurses::init_pair(target as i16, front, back);
|
||||||
|
target
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks the pair in the cache, or re-define a color if needed.
|
||||||
|
fn get_or_create(&self, pair: ColorPair) -> i32 {
|
||||||
|
let mut pairs = self.pairs.borrow_mut();
|
||||||
|
let pair = find_closest_pair(&pair);
|
||||||
|
|
||||||
|
// Find if we have this color in stock
|
||||||
|
if pairs.contains_key(&pair) {
|
||||||
|
// We got it!
|
||||||
|
pairs[&pair]
|
||||||
|
} else {
|
||||||
|
self.insert_color(&mut *pairs, pair)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_colors(&self, pair: ColorPair) {
|
||||||
|
let i = self.get_or_create(pair);
|
||||||
|
|
||||||
|
self.current_style.set(pair);
|
||||||
|
let style = pancurses::COLOR_PAIR(i as pancurses::chtype);
|
||||||
|
self.window.attron(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 {
|
||||||
|
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 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,8 +705,8 @@ impl Cursive {
|
|||||||
self.backend.refresh();
|
self.backend.refresh();
|
||||||
|
|
||||||
// Wait for next event.
|
// Wait for next event.
|
||||||
// (If set_fps was called, this returns -1 now and then)
|
match self.poll() {
|
||||||
let event = self.backend.poll_event();
|
Interruption::Event(event) => {
|
||||||
if event == Event::Exit {
|
if event == Event::Exit {
|
||||||
self.quit();
|
self.quit();
|
||||||
}
|
}
|
||||||
@ -678,7 +736,9 @@ impl Cursive {
|
|||||||
self.menubar.on_event(event).process(self);
|
self.menubar.on_event(event).process(self);
|
||||||
} else {
|
} else {
|
||||||
let offset = if self.menubar.autohide { 0 } else { 1 };
|
let offset = if self.menubar.autohide { 0 } else { 1 };
|
||||||
match self.screen_mut().on_event(event.relativized((0, offset))) {
|
match self.screen_mut()
|
||||||
|
.on_event(event.relativized((0, offset)))
|
||||||
|
{
|
||||||
// If the event was ignored,
|
// If the event was ignored,
|
||||||
// it is our turn to play with it.
|
// it is our turn to play with it.
|
||||||
EventResult::Ignored => self.on_event(event),
|
EventResult::Ignored => self.on_event(event),
|
||||||
@ -686,6 +746,13 @@ impl Cursive {
|
|||||||
EventResult::Consumed(Some(cb)) => cb(self),
|
EventResult::Consumed(Some(cb)) => cb(self),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Interruption::Callback(cb) => {
|
||||||
|
cb.call_box(self);
|
||||||
|
},
|
||||||
|
Interruption::Timeout => {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stops the event loop.
|
/// Stops the event loop.
|
||||||
|
@ -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