Add mouse support to pancurses

This commit is contained in:
Alexandre Bury 2017-11-19 00:40:56 -08:00
parent ee7170c66b
commit 0b5b6ceace
5 changed files with 268 additions and 40 deletions

View File

@ -1,10 +1,21 @@
[package]
authors = ["Alexandre Bury <alexandre.bury@gmail.com>"]
categories = ["command-line-interface", "gui"]
categories = [
"command-line-interface",
"gui",
]
description = "A TUI (Text User Interface) library focused on ease-of-use."
documentation = "https://gyscos.github.io/Cursive/cursive/index.html"
exclude = ["doc/**", "assets/**", "examples/**"]
keywords = ["ncurses", "TUI", "UI"]
exclude = [
"doc/**",
"assets/**",
"examples/**",
]
keywords = [
"ncurses",
"TUI",
"UI",
]
license = "MIT"
name = "cursive"
readme = "Readme.md"
@ -14,7 +25,6 @@ version = "0.7.5-alpha.0"
[badges.travis-ci]
repository = "gyscos/Cursive"
[dependencies]
log = "0.3"
num = "0.1"
@ -24,6 +34,10 @@ toml = "0.4"
unicode-segmentation = "1.0"
unicode-width = "0.1"
[dependencies.bear-lib-terminal]
optional = true
version = "1.3.1"
[dependencies.chan]
optional = true
version = "0.1.18"
@ -32,10 +46,6 @@ version = "0.1.18"
optional = true
version = "0.3"
[dependencies.bear-lib-terminal]
optional = true
version = "1.3.1"
[dependencies.ncurses]
features = ["wide"]
optional = true
@ -54,12 +64,15 @@ version = "1.5.0"
rand = "0.3"
[features]
default = ["ncurses-backend"]
ncurses-backend = ["ncurses"]
termion-backend = ["termion", "chan", "chan-signal"]
pancurses-backend = ["pancurses"]
blt-backend = ["bear-lib-terminal"]
default = ["ncurses-backend"]
ncurses-backend = ["ncurses"]
pancurses-backend = ["pancurses"]
termion-backend = [
"termion",
"chan",
"chan-signal",
]
[lib]
name = "cursive"

View File

@ -4,6 +4,7 @@ use cursive::{Cursive, Printer};
use cursive::event::{Event, EventResult};
use cursive::traits::*;
fn main() {
let mut siv = Cursive::new();

View File

@ -10,6 +10,9 @@ mod pan;
#[cfg(feature = "pancurses")]
pub use self::pan::*;
fn split_u32(code: i32) -> Vec<u8> {
(0..4).map(|i| ((code >> (8 * i)) & 0xFF) as u8).collect()
}
fn find_closest(color: &Color) -> i16 {
match *color {

View File

@ -1,6 +1,7 @@
extern crate ncurses;
use self::super::find_closest;
use self::ncurses::mmask_t;
use self::super::{find_closest, split_u32};
use backend;
use event::{Event, Key, MouseButton, MouseEvent};
use std::cell::{Cell, RefCell};
@ -78,17 +79,17 @@ impl Concrete {
// eprintln!("{:032b}", mevent.bstate);
// Currently unused
let _shift = (mevent.bstate
& ncurses::BUTTON_SHIFT as ncurses::mmask_t)
& ncurses::BUTTON_SHIFT as mmask_t)
!= 0;
let _alt =
(mevent.bstate & ncurses::BUTTON_ALT as ncurses::mmask_t) != 0;
(mevent.bstate & ncurses::BUTTON_ALT as mmask_t) != 0;
let _ctrl = (mevent.bstate
& ncurses::BUTTON_CTRL as ncurses::mmask_t)
& ncurses::BUTTON_CTRL as mmask_t)
!= 0;
mevent.bstate &= !(ncurses::BUTTON_SHIFT | ncurses::BUTTON_ALT
| ncurses::BUTTON_CTRL)
as ncurses::mmask_t;
as mmask_t;
let make_event = |event| {
Event::Mouse {
@ -99,7 +100,7 @@ impl Concrete {
};
if mevent.bstate
== ncurses::REPORT_MOUSE_POSITION as ncurses::mmask_t
== ncurses::REPORT_MOUSE_POSITION as mmask_t
{
// The event is either a mouse drag event,
// or a weird double-release event. :S
@ -117,7 +118,7 @@ impl Concrete {
bare_event ^= single_event;
// Process single_event
get_event(single_event as i32, |e| if event.is_none() {
on_mouse_event(single_event as i32, |e| if event.is_none() {
event = Some(e);
} else {
self.event_queue.push(make_event(e));
@ -256,11 +257,7 @@ impl Concrete {
c @ 1...25 => Event::CtrlChar((b'a' + (c - 1) as u8) as char),
other => {
// Split the i32 into 4 bytes
Event::Unknown(
(0..4)
.map(|i| ((other >> (8 * i)) & 0xFF) as u8)
.collect(),
)
Event::Unknown(split_u32(other))
}
}
}
@ -286,7 +283,7 @@ impl backend::Backend for Concrete {
// Listen to all mouse events.
ncurses::mousemask(
(ncurses::ALL_MOUSE_EVENTS | ncurses::REPORT_MOUSE_POSITION)
as ncurses::mmask_t,
as mmask_t,
None,
);
ncurses::noecho();
@ -395,7 +392,7 @@ impl backend::Backend for Concrete {
}
/// Returns the Key enum corresponding to the given ncurses event.
fn get_button(bare_event: i32) -> MouseButton {
fn get_mouse_button(bare_event: i32) -> MouseButton {
match bare_event {
ncurses::BUTTON1_RELEASED |
ncurses::BUTTON1_PRESSED |
@ -433,11 +430,11 @@ fn get_button(bare_event: i32) -> MouseButton {
/// 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)
fn on_mouse_event<F>(bare_event: i32, mut f: F)
where
F: FnMut(MouseEvent),
{
let button = get_button(bare_event);
let button = get_mouse_button(bare_event);
match bare_event {
ncurses::BUTTON4_PRESSED => f(MouseEvent::WheelUp),
ncurses::BUTTON5_PRESSED => f(MouseEvent::WheelDown),

View File

@ -1,17 +1,22 @@
extern crate pancurses;
use self::super::find_closest;
use self::pancurses::mmask_t;
use self::super::{find_closest, split_u32};
use backend;
use event::{Event, Key};
use event::{Event, Key, MouseButton, MouseEvent};
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use theme::{Color, ColorPair, Effect};
use utf8;
use vec::Vec2;
pub struct Concrete {
current_style: Cell<ColorPair>,
pairs: RefCell<HashMap<ColorPair, i32>>,
window: pancurses::Window,
last_mouse_button: Option<MouseButton>,
event_queue: Vec<Event>,
}
impl Concrete {
@ -61,23 +66,102 @@ impl Concrete {
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_u32(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 Concrete {
fn init() -> Self {
let window = pancurses::initscr();
::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.
println!("\x1B[?1002h");
Concrete {
current_style: Cell::new(ColorPair::from_256colors(0, 0)),
pairs: RefCell::new(HashMap::new()),
window: window,
last_mouse_button: None,
event_queue: Vec::new(),
}
}
@ -136,8 +220,7 @@ impl backend::Backend for Concrete {
}
fn poll_event(&mut self) -> Event {
// TODO: there seems to not be any indication
// of Ctrl/Alt/Shift in these :v
self.event_queue.pop().unwrap_or_else(|| {
if let Some(ev) = self.window.getch() {
match ev {
pancurses::Input::Character('\n') => Event::Key(Key::Enter),
@ -163,6 +246,9 @@ impl backend::Backend for Concrete {
}).unwrap(),
)
}
pancurses::Input::Character(c) if (c as u32) <= 26 => {
Event::CtrlChar((b'a' - 1 + c as u8) as char)
}
pancurses::Input::Character(c) => {
let mut bytes = [0u8; 4];
Event::Unknown(
@ -171,11 +257,53 @@ impl backend::Backend for Concrete {
}
// TODO: Some key combos are not recognized by pancurses,
// but are sent as Unknown. We could still parse them here.
pancurses::Input::Unknown(other) => Event::Unknown(
(0..4)
.map(|i| ((other >> (8 * i)) & 0xFF) as u8)
.collect(),
),
pancurses::Input::Unknown(code) => match code {
220 => Event::Ctrl(Key::Del),
224 => Event::Alt(Key::Down),
225 => Event::AltShift(Key::Down),
226 => Event::Ctrl(Key::Down),
227 => Event::CtrlShift(Key::Down),
229 => Event::Alt(Key::End),
230 => Event::AltShift(Key::End),
231 => Event::Ctrl(Key::End),
232 => Event::CtrlShift(Key::End),
235 => Event::Alt(Key::Home),
236 => Event::AltShift(Key::Home),
237 => Event::Ctrl(Key::Home),
238 => Event::CtrlShift(Key::Home),
246 => Event::Alt(Key::Left),
247 => Event::AltShift(Key::Left),
248 => Event::Ctrl(Key::Left),
249 => Event::CtrlShift(Key::Left),
251 => Event::Alt(Key::PageDown),
252 => Event::AltShift(Key::PageDown),
253 => Event::Ctrl(Key::PageDown),
254 => Event::CtrlShift(Key::PageDown),
256 => Event::Alt(Key::PageUp),
257 => Event::AltShift(Key::PageUp),
258 => Event::Ctrl(Key::PageUp),
259 => Event::CtrlShift(Key::PageUp),
261 => Event::Alt(Key::Right),
262 => Event::AltShift(Key::Right),
263 => Event::Ctrl(Key::Right),
264 => Event::CtrlShift(Key::Right),
267 => Event::Alt(Key::Up),
268 => Event::AltShift(Key::Up),
269 => Event::Ctrl(Key::Up),
270 => Event::CtrlShift(Key::Up),
other => {
eprintln!("Unknown: {}", other);
Event::Unknown(split_u32(other))
}
},
// TODO: I honestly have no fucking idea what KeyCodeYes is
pancurses::Input::KeyCodeYes => Event::Refresh,
pancurses::Input::KeyBreak => Event::Key(Key::PauseBreak),
@ -282,7 +410,7 @@ impl backend::Backend for Concrete {
pancurses::Input::KeyResize => Event::WindowResize,
pancurses::Input::KeyEvent => Event::Refresh,
// TODO: mouse support
pancurses::Input::KeyMouse => Event::Refresh,
pancurses::Input::KeyMouse => self.parse_mouse_event(),
pancurses::Input::KeyA1 => Event::Refresh,
pancurses::Input::KeyA3 => Event::Refresh,
pancurses::Input::KeyB2 => Event::Key(Key::NumpadCenter),
@ -292,6 +420,7 @@ impl backend::Backend for Concrete {
} else {
Event::Refresh
}
})
}
fn set_refresh_rate(&mut self, fps: u32) {
@ -302,3 +431,88 @@ impl backend::Backend for Concrete {
}
}
}
/// 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 on_mouse_event<F>(bare_event: u32, mut f: F)
where
F: FnMut(MouseEvent),
{
let button = get_mouse_button(bare_event);
match bare_event {
pancurses::BUTTON4_PRESSED => f(MouseEvent::WheelUp),
pancurses::BUTTON5_PRESSED => f(MouseEvent::WheelDown),
pancurses::BUTTON1_RELEASED |
pancurses::BUTTON2_RELEASED |
pancurses::BUTTON3_RELEASED |
pancurses::BUTTON4_RELEASED |
pancurses::BUTTON5_RELEASED => f(MouseEvent::Release(button)),
pancurses::BUTTON1_PRESSED |
pancurses::BUTTON2_PRESSED |
pancurses::BUTTON3_PRESSED => f(MouseEvent::Press(button)),
pancurses::BUTTON1_CLICKED |
pancurses::BUTTON2_CLICKED |
pancurses::BUTTON3_CLICKED |
pancurses::BUTTON4_CLICKED |
pancurses::BUTTON5_CLICKED => {
f(MouseEvent::Press(button));
f(MouseEvent::Release(button));
}
// Well, we disabled click detection
pancurses::BUTTON1_DOUBLE_CLICKED |
pancurses::BUTTON2_DOUBLE_CLICKED |
pancurses::BUTTON3_DOUBLE_CLICKED |
pancurses::BUTTON4_DOUBLE_CLICKED |
pancurses::BUTTON5_DOUBLE_CLICKED => for _ in 0..2 {
f(MouseEvent::Press(button));
f(MouseEvent::Release(button));
},
pancurses::BUTTON1_TRIPLE_CLICKED |
pancurses::BUTTON2_TRIPLE_CLICKED |
pancurses::BUTTON3_TRIPLE_CLICKED |
pancurses::BUTTON4_TRIPLE_CLICKED |
pancurses::BUTTON5_TRIPLE_CLICKED => for _ in 0..3 {
f(MouseEvent::Press(button));
f(MouseEvent::Release(button));
},
_ => debug!("Unknown event: {:032b}", bare_event),
}
}
/// Returns the Key enum corresponding to the given pancurses event.
fn get_mouse_button(bare_event: u32) -> MouseButton {
match bare_event {
pancurses::BUTTON1_RELEASED |
pancurses::BUTTON1_PRESSED |
pancurses::BUTTON1_CLICKED |
pancurses::BUTTON1_DOUBLE_CLICKED |
pancurses::BUTTON1_TRIPLE_CLICKED => MouseButton::Left,
pancurses::BUTTON2_RELEASED |
pancurses::BUTTON2_PRESSED |
pancurses::BUTTON2_CLICKED |
pancurses::BUTTON2_DOUBLE_CLICKED |
pancurses::BUTTON2_TRIPLE_CLICKED => MouseButton::Middle,
pancurses::BUTTON3_RELEASED |
pancurses::BUTTON3_PRESSED |
pancurses::BUTTON3_CLICKED |
pancurses::BUTTON3_DOUBLE_CLICKED |
pancurses::BUTTON3_TRIPLE_CLICKED => MouseButton::Right,
pancurses::BUTTON4_RELEASED |
pancurses::BUTTON4_PRESSED |
pancurses::BUTTON4_CLICKED |
pancurses::BUTTON4_DOUBLE_CLICKED |
pancurses::BUTTON4_TRIPLE_CLICKED => MouseButton::Button4,
pancurses::BUTTON5_RELEASED |
pancurses::BUTTON5_PRESSED |
pancurses::BUTTON5_CLICKED |
pancurses::BUTTON5_DOUBLE_CLICKED |
pancurses::BUTTON5_TRIPLE_CLICKED => MouseButton::Button5,
_ => MouseButton::Other,
}
}