Output mouse events from ncurses backend

This commit is contained in:
Alexandre Bury 2017-10-10 17:50:44 -07:00
parent 976728fd25
commit 9497ded014

View File

@ -2,23 +2,27 @@ extern crate ncurses;
use self::super::find_closest; use self::super::find_closest;
use backend; use backend;
use event::{Event, Key}; use event::{Event, Key, MouseButton, MouseEvent};
use std::cell::{RefCell, Cell}; use std::cell::{Cell, RefCell};
use std::collections::HashMap; use std::collections::HashMap;
use theme::{Color, ColorPair, Effect}; use theme::{Color, ColorPair, Effect};
use utf8; use utf8;
use vec::Vec2;
pub struct Concrete { pub struct Concrete {
current_style: Cell<ColorPair>, current_style: Cell<ColorPair>,
pairs: RefCell<HashMap<ColorPair, i16>>, pairs: RefCell<HashMap<ColorPair, i16>>,
last_mouse_button: Option<MouseButton>,
event_queue: Vec<Event>,
} }
impl Concrete { impl Concrete {
/// Save a new color pair. /// Save a new color pair.
fn insert_color(&self, pairs: &mut HashMap<ColorPair, i16>, fn insert_color(
pair: ColorPair) &self, pairs: &mut HashMap<ColorPair, i16>, pair: ColorPair
-> i16 { ) -> i16 {
let n = 1 + pairs.len() as i16; let n = 1 + pairs.len() as i16;
let target = if ncurses::COLOR_PAIRS() > n as i32 { let target = if ncurses::COLOR_PAIRS() > n as i32 {
// We still have plenty of space for everyone. // We still have plenty of space for everyone.
@ -31,15 +35,16 @@ impl Concrete {
target target
}; };
pairs.insert(pair, target); pairs.insert(pair, target);
ncurses::init_pair(target, ncurses::init_pair(
target,
find_closest(&pair.front), find_closest(&pair.front),
find_closest(&pair.back)); find_closest(&pair.back),
);
target target
} }
/// Checks the pair in the cache, or re-define a color if needed. /// Checks the pair in the cache, or re-define a color if needed.
fn get_or_create(&self, pair: ColorPair) -> i16 { fn get_or_create(&self, pair: ColorPair) -> i16 {
let mut pairs = self.pairs.borrow_mut(); let mut pairs = self.pairs.borrow_mut();
// Find if we have this color in stock // Find if we have this color in stock
@ -52,117 +57,81 @@ impl Concrete {
} }
fn set_colors(&self, pair: ColorPair) { fn set_colors(&self, pair: ColorPair) {
let i = self.get_or_create(pair); let i = self.get_or_create(pair);
self.current_style.set(pair); self.current_style.set(pair);
let style = ncurses::COLOR_PAIR(i); let style = ncurses::COLOR_PAIR(i);
ncurses::attron(style); ncurses::attron(style);
} }
}
impl backend::Backend for Concrete { fn parse_mouse_event(&mut self) -> Event {
fn init() -> Self { let mut mevent = ncurses::MEVENT {
// The delay is the time ncurses wait after pressing ESC id: 0,
// to see if it's an escape sequence. x: 0,
// Default delay is way too long. 25 is imperceptible yet works fine. y: 0,
ncurses::setlocale(ncurses::LcCategory::all, ""); z: 0,
::std::env::set_var("ESCDELAY", "25"); bstate: 0,
ncurses::initscr();
ncurses::keypad(ncurses::stdscr(), true);
ncurses::noecho();
ncurses::cbreak();
ncurses::start_color();
ncurses::use_default_colors();
ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);
Concrete {
current_style: Cell::new(ColorPair::from_256colors(0, 0)),
pairs: RefCell::new(HashMap::new()),
}
}
fn screen_size(&self) -> (usize, usize) {
let mut x: i32 = 0;
let mut y: i32 = 0;
ncurses::getmaxyx(ncurses::stdscr(), &mut y, &mut x);
(x as usize, y as usize)
}
fn has_colors(&self) -> bool {
ncurses::has_colors()
}
fn finish(&mut self) {
ncurses::endwin();
}
fn with_color<F: FnOnce()>(&self, colors: ColorPair, f: F) {
let current = self.current_style.get();
if current != colors {
self.set_colors(colors);
}
f();
if current != colors {
self.set_colors(current);
}
}
fn with_effect<F: FnOnce()>(&self, effect: Effect, f: F) {
let style = match effect {
Effect::Reverse => ncurses::A_REVERSE(),
Effect::Simple => ncurses::A_NORMAL(),
}; };
ncurses::attron(style); if ncurses::getmouse(&mut mevent as *mut ncurses::MEVENT)
f(); == ncurses::OK
ncurses::attroff(style); {
} // eprintln!("{:032b}", mevent.bstate);
// Currently unused
let _shift = (mevent.bstate & ncurses::BUTTON_SHIFT as u32) != 0;
let _alt = (mevent.bstate & ncurses::BUTTON_ALT as u32) != 0;
let _ctrl = (mevent.bstate & ncurses::BUTTON_CTRL as u32) != 0;
fn clear(&self, color: Color) { mevent.bstate &= !(ncurses::BUTTON_SHIFT | ncurses::BUTTON_ALT
let id = self.get_or_create(ColorPair { | ncurses::BUTTON_CTRL) as u32;
front: color,
back: color, 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 == ncurses::REPORT_MOUSE_POSITION as u32 {
// The event is either a mouse drag event,
// or a weird double-release event. :S
self.last_mouse_button
.map(|b| MouseEvent::Hold(b))
.map(&make_event)
.unwrap_or(Event::Unknown(vec![]))
} else {
// Identify the button
let mut bare_event = (mevent.bstate & ((1 << 25) - 1)) as i32;
let mut event = None;
while bare_event != 0 {
let single_event = 1 << bare_event.trailing_zeros();
bare_event ^= single_event;
// Process single_event
get_event(single_event, |e| if event.is_none() {
event = Some(e);
} else {
self.event_queue.push(make_event(e));
}); });
ncurses::wbkgd(ncurses::stdscr(), ncurses::COLOR_PAIR(id));
ncurses::clear();
} }
if let Some(event) = event {
fn refresh(&mut self) { if let Some(btn) = event.button() {
ncurses::refresh(); self.last_mouse_button = Some(btn);
} }
make_event(event)
fn print_at(&self, (x, y): (usize, usize), text: &str) {
ncurses::mvaddstr(y as i32, x as i32, text);
}
fn poll_event(&mut self) -> Event {
let ch: i32 = ncurses::getch();
// Is it a UTF-8 starting point?
if 32 <= ch && ch <= 255 && ch != 127 {
Event::Char(utf8::read_char(ch as u8,
|| Some(ncurses::getch() as u8))
.unwrap())
} else { } else {
parse_ncurses_char(ch) debug!("No event parsed?...");
Event::Unknown(vec![])
} }
} }
fn set_refresh_rate(&mut self, fps: u32) {
if fps == 0 {
ncurses::timeout(-1);
} else { } else {
ncurses::timeout(1000 / fps as i32); debug!("Ncurses event not recognized.");
} Event::Unknown(vec![])
} }
} }
/// Returns the Key enum corresponding to the given ncurses event. fn parse_ncurses_char(&mut self, ch: i32) -> Event {
fn parse_ncurses_char(ch: i32) -> Event {
match ch { match ch {
// Value sent by ncurses when nothing happens // Value sent by ncurses when nothing happens
-1 => Event::Refresh, -1 => Event::Refresh,
@ -172,14 +141,12 @@ fn parse_ncurses_char(ch: i32) -> Event {
// Tab is '\t' // Tab is '\t'
9 => Event::Key(Key::Tab), 9 => Event::Key(Key::Tab),
// Treat '\n' and the numpad Enter the same // Treat '\n' and the numpad Enter the same
10 | 10 | ncurses::KEY_ENTER => Event::Key(Key::Enter),
ncurses::KEY_ENTER => Event::Key(Key::Enter),
// This is the escape key when pressed by itself. // This is the escape key when pressed by itself.
// When used for control sequences, it should have been caught earlier. // When used for control sequences, it should have been caught earlier.
27 => Event::Key(Key::Esc), 27 => Event::Key(Key::Esc),
// `Backspace` sends 127, but Ctrl-H sends `Backspace` // `Backspace` sends 127, but Ctrl-H sends `Backspace`
127 | 127 | ncurses::KEY_BACKSPACE => Event::Key(Key::Backspace),
ncurses::KEY_BACKSPACE => Event::Key(Key::Backspace),
410 => Event::WindowResize, 410 => Event::WindowResize,
@ -245,6 +212,7 @@ fn parse_ncurses_char(ch: i32) -> Event {
570 => Event::CtrlShift(Key::Up), 570 => Event::CtrlShift(Key::Up),
571 => Event::CtrlAlt(Key::Up), 571 => Event::CtrlAlt(Key::Up),
ncurses::KEY_MOUSE => self.parse_mouse_event(),
ncurses::KEY_B2 => Event::Key(Key::NumpadCenter), ncurses::KEY_B2 => Event::Key(Key::NumpadCenter),
ncurses::KEY_DC => Event::Key(Key::Del), ncurses::KEY_DC => Event::Key(Key::Del),
ncurses::KEY_IC => Event::Key(Key::Ins), ncurses::KEY_IC => Event::Key(Key::Ins),
@ -279,9 +247,223 @@ fn parse_ncurses_char(ch: i32) -> Event {
c @ 1...25 => Event::CtrlChar((b'a' + (c - 1) as u8) as char), c @ 1...25 => Event::CtrlChar((b'a' + (c - 1) as u8) as char),
other => { other => {
// Split the i32 into 4 bytes // Split the i32 into 4 bytes
Event::Unknown((0..4) Event::Unknown(
(0..4)
.map(|i| ((other >> (8 * i)) & 0xFF) as u8) .map(|i| ((other >> (8 * i)) & 0xFF) as u8)
.collect()) .collect(),
)
} }
} }
} }
}
impl backend::Backend for Concrete {
fn init() -> Self {
// Change the locale. For some reasons it's mandatory to get some UTF-8 support.
ncurses::setlocale(ncurses::LcCategory::all, "");
// The delay is the time ncurses wait after pressing ESC
// to see if it's an escape sequence.
// Default delay is way too long. 25 is imperceptible yet works fine.
::std::env::set_var("ESCDELAY", "25");
ncurses::initscr();
ncurses::keypad(ncurses::stdscr(), true);
// This disables mouse click detection,
// and provides 0-delay access to mouse presses.
ncurses::mouseinterval(0);
// Listen to all mouse events.
ncurses::mousemask(
(ncurses::ALL_MOUSE_EVENTS | ncurses::REPORT_MOUSE_POSITION)
as u32,
None,
);
ncurses::noecho();
ncurses::cbreak();
ncurses::start_color();
// Pick up background and text color from the terminal theme.
ncurses::use_default_colors();
// No cursor
ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);
// 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.
println!("\x1B[?1002h");
Concrete {
current_style: Cell::new(ColorPair::from_256colors(0, 0)),
pairs: RefCell::new(HashMap::new()),
last_mouse_button: None,
event_queue: Vec::new(),
}
}
fn screen_size(&self) -> (usize, usize) {
let mut x: i32 = 0;
let mut y: i32 = 0;
ncurses::getmaxyx(ncurses::stdscr(), &mut y, &mut x);
(x as usize, y as usize)
}
fn has_colors(&self) -> bool {
ncurses::has_colors()
}
fn finish(&mut self) {
println!("\x1B[?1002l");
ncurses::endwin();
}
fn with_color<F: FnOnce()>(&self, colors: ColorPair, f: F) {
let current = self.current_style.get();
if current != colors {
self.set_colors(colors);
}
f();
if current != colors {
self.set_colors(current);
}
}
fn with_effect<F: FnOnce()>(&self, effect: Effect, f: F) {
let style = match effect {
Effect::Reverse => ncurses::A_REVERSE(),
Effect::Simple => ncurses::A_NORMAL(),
};
ncurses::attron(style);
f();
ncurses::attroff(style);
}
fn clear(&self, color: Color) {
let id = self.get_or_create(ColorPair {
front: color,
back: color,
});
ncurses::wbkgd(ncurses::stdscr(), ncurses::COLOR_PAIR(id));
ncurses::clear();
}
fn refresh(&mut self) {
ncurses::refresh();
}
fn print_at(&self, (x, y): (usize, usize), text: &str) {
ncurses::mvaddstr(y as i32, 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 {
Event::Char(
utf8::read_char(ch as u8, || Some(ncurses::getch() as u8))
.unwrap(),
)
} 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.
fn get_button(bare_event: i32) -> MouseButton {
match bare_event {
ncurses::BUTTON1_RELEASED |
ncurses::BUTTON1_PRESSED |
ncurses::BUTTON1_CLICKED |
ncurses::BUTTON1_DOUBLE_CLICKED |
ncurses::BUTTON1_TRIPLE_CLICKED => MouseButton::Left,
ncurses::BUTTON2_RELEASED |
ncurses::BUTTON2_PRESSED |
ncurses::BUTTON2_CLICKED |
ncurses::BUTTON2_DOUBLE_CLICKED |
ncurses::BUTTON2_TRIPLE_CLICKED => MouseButton::Middle,
ncurses::BUTTON3_RELEASED |
ncurses::BUTTON3_PRESSED |
ncurses::BUTTON3_CLICKED |
ncurses::BUTTON3_DOUBLE_CLICKED |
ncurses::BUTTON3_TRIPLE_CLICKED => MouseButton::Right,
ncurses::BUTTON4_RELEASED |
ncurses::BUTTON4_PRESSED |
ncurses::BUTTON4_CLICKED |
ncurses::BUTTON4_DOUBLE_CLICKED |
ncurses::BUTTON4_TRIPLE_CLICKED => MouseButton::Button4,
ncurses::BUTTON5_RELEASED |
ncurses::BUTTON5_PRESSED |
ncurses::BUTTON5_CLICKED |
ncurses::BUTTON5_DOUBLE_CLICKED |
ncurses::BUTTON5_TRIPLE_CLICKED => MouseButton::Button5,
_ => MouseButton::Other,
}
}
/// Parse the given code into one or more event.
///
/// If the given event code should expend into multiple events
/// (for instance click expends into PRESS + RELEASE),
/// the returned Vec will include those queued events.
///
/// The main event is returned separately to avoid allocation in most cases.
fn get_event<F>(bare_event: i32, mut f: F)
where
F: FnMut(MouseEvent),
{
let button = get_button(bare_event);
match bare_event {
ncurses::BUTTON4_PRESSED => f(MouseEvent::WheelUp),
ncurses::BUTTON5_PRESSED => f(MouseEvent::WheelDown),
ncurses::BUTTON1_RELEASED |
ncurses::BUTTON2_RELEASED |
ncurses::BUTTON3_RELEASED |
ncurses::BUTTON4_RELEASED |
ncurses::BUTTON5_RELEASED => f(MouseEvent::Release(button)),
ncurses::BUTTON1_PRESSED |
ncurses::BUTTON2_PRESSED |
ncurses::BUTTON3_PRESSED => f(MouseEvent::Press(button)),
ncurses::BUTTON1_CLICKED |
ncurses::BUTTON2_CLICKED |
ncurses::BUTTON3_CLICKED |
ncurses::BUTTON4_CLICKED |
ncurses::BUTTON5_CLICKED => {
f(MouseEvent::Press(button));
f(MouseEvent::Release(button));
}
// Well, we disabled click detection
ncurses::BUTTON1_DOUBLE_CLICKED |
ncurses::BUTTON2_DOUBLE_CLICKED |
ncurses::BUTTON3_DOUBLE_CLICKED |
ncurses::BUTTON4_DOUBLE_CLICKED |
ncurses::BUTTON5_DOUBLE_CLICKED => for _ in 0..2 {
f(MouseEvent::Press(button));
f(MouseEvent::Release(button));
},
ncurses::BUTTON1_TRIPLE_CLICKED |
ncurses::BUTTON2_TRIPLE_CLICKED |
ncurses::BUTTON3_TRIPLE_CLICKED |
ncurses::BUTTON4_TRIPLE_CLICKED |
ncurses::BUTTON5_TRIPLE_CLICKED => for _ in 0..3 {
f(MouseEvent::Press(button));
f(MouseEvent::Release(button));
},
_ => debug!("Unknown event: {:032b}", bare_event),
}
}