From 01951aa213725fc7f761a730cf276b8c66ac0380 Mon Sep 17 00:00:00 2001 From: nabijaczleweli Date: Mon, 10 Oct 2016 00:48:51 +0200 Subject: [PATCH] Mostly add pancurses support. Test pancurses support on Travis --- .travis.yml | 6 + Cargo.toml | 16 +- src/backend/curses/mod.rs | 40 +++++ src/backend/{curses.rs => curses/n.rs} | 1 - src/backend/curses/pan.rs | 221 +++++++++++++++++++++++++ src/backend/mod.rs | 12 +- src/lib.rs | 25 +-- 7 files changed, 299 insertions(+), 22 deletions(-) create mode 100644 src/backend/curses/mod.rs rename src/backend/{curses.rs => curses/n.rs} (99%) create mode 100644 src/backend/curses/pan.rs diff --git a/.travis.yml b/.travis.yml index 877676d..84b36cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,3 +3,9 @@ cache: cargo rust: - stable - nightly +script: + - cargo build --verbose + - cargo test --verbose + - + - cargo build --verbose --features=pancurses --no-default-features + - cargo test --verbose --features=pancurses --no-default-features diff --git a/Cargo.toml b/Cargo.toml index 6577396..1d7350c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,12 @@ version = "0.3.1" [build-dependencies] skeptic = "0.6" +[features] +cursive_ncurses = ["ncurses"] +cursive_pancurses = ["pancurses"] +cursive_termion = ["termion"] +default = ["cursive_ncurses"] + [dependencies] odds = "0.2" toml = "0.2" @@ -21,20 +27,22 @@ unicode-segmentation = "0.1" unicode-width = "0.1" [dependencies.ncurses] +version = "5.83.0" features = ["wide"] optional = true version = "5.83.0" -[dependencies.termion] +[dependencies.pancurses] +version = "0.3.0" optional = true + +[dependencies.termion] version = "1.1.1" +optional = true [dev-dependencies] rand = "0.3" skeptic = "0.6" -[features] -default = ["ncurses"] - [lib] name = "cursive" diff --git a/src/backend/curses/mod.rs b/src/backend/curses/mod.rs new file mode 100644 index 0000000..bb1f137 --- /dev/null +++ b/src/backend/curses/mod.rs @@ -0,0 +1,40 @@ +use theme::{BaseColor, Color}; + +#[cfg(feature = "cursive_ncurses")] +mod n; +#[cfg(feature = "cursive_ncurses")] +pub use self::n::*; + +#[cfg(feature = "cursive_pancurses")] +mod pan; +#[cfg(feature = "cursive_pancurses")] +pub use self::pan::*; + + +fn find_closest(color: &Color) -> u8 { + match *color { + Color::Dark(BaseColor::Black) => 0, + Color::Dark(BaseColor::Red) => 1, + Color::Dark(BaseColor::Green) => 2, + Color::Dark(BaseColor::Yellow) => 3, + Color::Dark(BaseColor::Blue) => 4, + Color::Dark(BaseColor::Magenta) => 5, + Color::Dark(BaseColor::Cyan) => 6, + Color::Dark(BaseColor::White) => 7, + Color::Light(BaseColor::Black) => 8, + Color::Light(BaseColor::Red) => 9, + Color::Light(BaseColor::Green) => 10, + Color::Light(BaseColor::Yellow) => 11, + Color::Light(BaseColor::Blue) => 12, + Color::Light(BaseColor::Magenta) => 13, + Color::Light(BaseColor::Cyan) => 14, + Color::Light(BaseColor::White) => 15, + Color::Rgb(r, g, b) => { + let r = 6 * r as u16 / 256; + let g = 6 * g as u16 / 256; + let b = 6 * b as u16 / 256; + (16 + 36 * r + 6 * g + b) as u8 + } + Color::RgbLowRes(r, g, b) => (16 + 36 * r + 6 * g + b) as u8, + } +} diff --git a/src/backend/curses.rs b/src/backend/curses/n.rs similarity index 99% rename from src/backend/curses.rs rename to src/backend/curses/n.rs index 43d39cb..e46dd3b 100644 --- a/src/backend/curses.rs +++ b/src/backend/curses/n.rs @@ -105,7 +105,6 @@ impl backend::Backend for NcursesBackend { /// Returns the Key enum corresponding to the given ncurses event. fn parse_ncurses_char(ch: i32) -> Event { - match ch { // Value sent by ncurses when nothing happens -1 => Event::Refresh, diff --git a/src/backend/curses/pan.rs b/src/backend/curses/pan.rs new file mode 100644 index 0000000..c555085 --- /dev/null +++ b/src/backend/curses/pan.rs @@ -0,0 +1,221 @@ +use backend; +use event::{Event, Key}; + +use pancurses; +use self::super::find_closest; +use theme::{BaseColor, Color, ColorStyle, Effect}; +use utf8; + +pub struct PancursesBackend { + window: pancurses::Window, +} + +impl backend::Backend for PancursesBackend { + fn init() -> Self { + ::std::env::set_var("ESCDELAY", "25"); + let window = pancurses::initscr(); + window.keypad(true); + pancurses::noecho(); + pancurses::cbreak(); + pancurses::start_color(); + pancurses::curs_set(0); + window.bkgd(pancurses::COLOR_PAIR(ColorStyle::Background.id() as u64)); + + PancursesBackend { window: window } + } + + fn screen_size(&self) -> (usize, usize) { + let (x, y) = self.window.get_max_yx(); + (x as usize, y as usize) + } + + fn has_colors(&self) -> bool { + pancurses::has_colors() + } + + fn finish(&mut self) { + pancurses::endwin(); + } + + fn init_color_style(&mut self, style: ColorStyle, foreground: &Color, + background: &Color) { + pancurses::init_pair(style.id(), + find_closest(foreground) as i16, + find_closest(background) as i16); + } + + fn with_color(&self, color: ColorStyle, f: F) { + // TODO: pancurses doesn't have an `attr_get` equivalent + // let mut current_style: pancurses::attr_t = 0; + // let mut current_color: i16 = 0; + // pancurses::attr_get(&mut current_style, &mut current_color); + + let style = pancurses::COLOR_PAIR(color.id() as u64); + self.window.attron(style); + f(); + self.window.attroff(style); + // pancurses::attron(current_style); + } + + fn with_effect(&self, effect: Effect, f: F) { + let style = match effect { + // A_REVERSE, taken from ncurses + Effect::Reverse => 1 << (10 + 8u32), + Effect::Simple => pancurses::A_NORMAL, + }; + self.window.attron(style); + f(); + self.window.attroff(style); + } + + fn clear(&self) { + self.window.clear(); + } + + fn refresh(&self) { + self.window.refresh(); + } + + fn print_at(&self, (x, y): (usize, usize), text: &str) { + self.window.mvaddstr(y as i32, x as i32, text); + } + + fn poll_event(&self) -> Event { + // TODO: there seems to not be any indication + // of Ctrl/Alt/Shift in these :v + if let Some(ev) = self.window.getch() { + match ev { + pancurses::Input::Character(c) => Event::Char(c), + pancurses::Input::Unknown(i) => Event::Unknown(i), + // TODO: I honestly have no fucking idea what KeyCodeYes is + pancurses::Input::KeyCodeYes => Event::Refresh, + // TODO: There seems to not be a Key::Break?? + pancurses::Input::KeyBreak => Event::Refresh, + 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), + // TODO: there seems to not be an F0 key for some reason? + pancurses::Input::KeyF0 => Event::Refresh, + 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), + // TODO: there seems to not be any F-keys above F15 + // for some reason? + pancurses::Input::KeyF13 => Event::Refresh, + pancurses::Input::KeyF14 => Event::Refresh, + pancurses::Input::KeyF15 => Event::Refresh, + // TODO: not sure what those are + pancurses::Input::KeyDL => Event::Refresh, + pancurses::Input::KeyIL => Event::Refresh, + pancurses::Input::KeyDC => Event::Refresh, + pancurses::Input::KeyIC => Event::Refresh, + pancurses::Input::KeyEIC => Event::Refresh, + pancurses::Input::KeyClear => Event::Refresh, + pancurses::Input::KeyEOS => Event::Refresh, + pancurses::Input::KeyEOL => Event::Refresh, + pancurses::Input::KeySF => Event::Refresh, + pancurses::Input::KeySR => Event::Refresh, + pancurses::Input::KeyNPage => Event::Refresh, + pancurses::Input::KeyPPage => Event::Refresh, + // TODO: no way to handle this + pancurses::Input::KeySTab => Event::Refresh, + pancurses::Input::KeyCTab => Event::CtrlChar('\t'), + pancurses::Input::KeyCATab => Event::Refresh, + pancurses::Input::KeyEnter => Event::Key(Key::Enter), + // TODO: not sure what those are + 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::Refresh, + 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::Refresh, + 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::Refresh, + pancurses::Input::KeySDL => Event::Refresh, + pancurses::Input::KeySelect => Event::Refresh, + pancurses::Input::KeySEnd => Event::Refresh, + pancurses::Input::KeySEOL => Event::Refresh, + pancurses::Input::KeySExit => Event::Refresh, + pancurses::Input::KeySFind => Event::Refresh, + pancurses::Input::KeySHome => Event::Refresh, + pancurses::Input::KeySIC => Event::Refresh, + pancurses::Input::KeySLeft => Event::Refresh, + pancurses::Input::KeySMessage => Event::Refresh, + pancurses::Input::KeySMove => Event::Refresh, + pancurses::Input::KeySNext => Event::Refresh, + pancurses::Input::KeySOptions => Event::Refresh, + pancurses::Input::KeySPrevious => Event::Refresh, + pancurses::Input::KeySPrint => Event::Refresh, + pancurses::Input::KeySRedo => Event::Refresh, + pancurses::Input::KeySReplace => Event::Refresh, + pancurses::Input::KeySRight => Event::Refresh, + 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 => Event::Refresh, + pancurses::Input::KeyEvent => Event::Refresh, + // TODO: not sure how to handle this + pancurses::Input::KeyMouse => Event::Refresh, + // TODO: not sure what those are + pancurses::Input::KeyA1 => Event::Refresh, + pancurses::Input::KeyA3 => Event::Refresh, + pancurses::Input::KeyB2 => Event::Refresh, + pancurses::Input::KeyC1 => Event::Refresh, + pancurses::Input::KeyC3 => 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); + } + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 6c53cb0..ea562cf 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -1,11 +1,13 @@ use event; use theme; -// Module is not named `ncurses` to avoir naming conflict -#[cfg(feature = "ncurses")] -pub mod curses; -#[cfg(feature = "termion")] -pub mod termion; +#[cfg(feature = "cursive_termion")] +mod termion; +mod curses; + +pub use self::curses::*; +#[cfg(feature = "cursive_termion")] +pub use self::termion::*; pub trait Backend { fn init() -> Self; diff --git a/src/lib.rs b/src/lib.rs index a2d5a32..e5bc4ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,11 +56,12 @@ //! and log to it instead of stdout. #![deny(missing_docs)] -#[cfg(feature = "ncurses")] +#[cfg(feature = "cursive_ncurses")] extern crate ncurses; -#[cfg(feature = "termion")] +#[cfg(feature = "cursive_pancurses")] +extern crate pancurses; +#[cfg(feature = "cursive_termion")] extern crate termion; - extern crate toml; extern crate unicode_segmentation; extern crate unicode_width; @@ -109,7 +110,7 @@ mod utf8; mod backend; -use backend::{Backend}; +use backend::Backend; use event::{Callback, Event, EventResult}; @@ -156,18 +157,18 @@ pub struct Cursive { new_default!(Cursive); -// Use the Ncurses backend. -// TODO: make this feature-driven -#[cfg(feature = "termion")] #[doc(hidden)] -pub type B = backend::termion::TermionBackend; - +#[cfg(feature = "cursive_ncurses")] +pub type B = backend::NcursesBackend; #[doc(hidden)] -#[cfg(feature = "ncurses")] -pub type B = backend::curses::NcursesBackend; +#[cfg(feature = "cursive_pancurses")] +pub type B = backend::PancursesBackend; +#[doc(hidden)] +#[cfg(feature = "cursive_termion")] +pub type B = backend::TermionBackend; impl Cursive { - /// Creates a new Cursive root, and initialize ncurses. + /// Creates a new Cursive root, and initialize the back-end. pub fn new() -> Self { // Default delay is way too long. 25 is imperceptible yet works fine. let mut backend = B::init();