2016-10-10 00:16:35 +00:00
|
|
|
extern crate termion;
|
|
|
|
|
2016-12-14 06:10:00 +00:00
|
|
|
extern crate chan_signal;
|
|
|
|
|
2016-12-13 23:05:35 +00:00
|
|
|
use self::termion::color as tcolor;
|
2017-03-25 18:01:25 +00:00
|
|
|
use self::termion::event::Event as TEvent;
|
2016-10-24 18:16:56 +00:00
|
|
|
use self::termion::event::Key as TKey;
|
2016-10-10 21:08:07 +00:00
|
|
|
use self::termion::input::TermRead;
|
|
|
|
use self::termion::raw::IntoRawMode;
|
2017-04-26 06:20:52 +00:00
|
|
|
use self::termion::screen::AlternateScreen;
|
2016-12-13 23:05:35 +00:00
|
|
|
use self::termion::style as tstyle;
|
2017-03-25 18:01:25 +00:00
|
|
|
use backend;
|
|
|
|
use chan;
|
|
|
|
use event::{Event, Key};
|
2016-10-22 00:18:26 +00:00
|
|
|
use std::cell::Cell;
|
2017-06-11 22:01:35 +00:00
|
|
|
use std::collections::HashMap;
|
2016-10-28 03:35:34 +00:00
|
|
|
use std::fmt;
|
2016-12-13 23:05:35 +00:00
|
|
|
use std::io::Write;
|
|
|
|
use std::thread;
|
2016-10-22 00:18:26 +00:00
|
|
|
|
2017-03-25 18:01:25 +00:00
|
|
|
use theme;
|
2016-10-08 23:47:15 +00:00
|
|
|
|
2016-10-10 00:16:35 +00:00
|
|
|
pub struct Concrete {
|
2017-04-26 06:20:52 +00:00
|
|
|
terminal: AlternateScreen<termion::raw::RawTerminal<::std::io::Stdout>>,
|
2017-06-13 01:31:08 +00:00
|
|
|
current_style: Cell<theme::ColorPair>,
|
2016-12-14 06:10:00 +00:00
|
|
|
input: chan::Receiver<Event>,
|
|
|
|
resize: chan::Receiver<chan_signal::Signal>,
|
|
|
|
timeout: Option<u32>,
|
2016-10-22 00:18:26 +00:00
|
|
|
}
|
|
|
|
|
2016-10-24 18:16:56 +00:00
|
|
|
trait Effectable {
|
|
|
|
fn on(&self);
|
|
|
|
fn off(&self);
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Effectable for theme::Effect {
|
|
|
|
fn on(&self) {
|
|
|
|
match *self {
|
|
|
|
theme::Effect::Simple => (),
|
2016-12-13 23:05:35 +00:00
|
|
|
theme::Effect::Reverse => print!("{}", tstyle::Invert),
|
2016-10-24 18:16:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn off(&self) {
|
|
|
|
match *self {
|
|
|
|
theme::Effect::Simple => (),
|
2016-12-13 23:05:35 +00:00
|
|
|
theme::Effect::Reverse => print!("{}", tstyle::NoInvert),
|
2016-10-24 18:16:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-13 23:05:35 +00:00
|
|
|
fn apply_colors(fg: &tcolor::Color, bg: &tcolor::Color) {
|
2017-06-11 22:01:35 +00:00
|
|
|
print!("{}{}", tcolor::Fg(fg), tcolor::Bg(bg));
|
2016-10-22 00:18:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Concrete {
|
2017-06-13 01:31:08 +00:00
|
|
|
fn apply_colorstyle(&self, colors: theme::ColorPair) {
|
|
|
|
with_color(&colors.front, |c| print!("{}", tcolor::Fg(c)));
|
|
|
|
with_color(&colors.back, |c| print!("{}", tcolor::Bg(c)));
|
2016-10-22 00:18:26 +00:00
|
|
|
}
|
2016-10-08 23:47:15 +00:00
|
|
|
}
|
|
|
|
|
2016-10-10 00:16:35 +00:00
|
|
|
impl backend::Backend for Concrete {
|
2016-10-08 23:47:15 +00:00
|
|
|
fn init() -> Self {
|
|
|
|
print!("{}", termion::cursor::Hide);
|
2016-12-14 06:10:00 +00:00
|
|
|
|
|
|
|
let resize = chan_signal::notify(&[chan_signal::Signal::WINCH]);
|
|
|
|
|
2017-06-11 22:01:35 +00:00
|
|
|
let terminal = AlternateScreen::from(::std::io::stdout()
|
|
|
|
.into_raw_mode()
|
|
|
|
.unwrap());
|
2016-12-14 06:10:00 +00:00
|
|
|
let (sender, receiver) = chan::async();
|
2016-12-13 23:05:35 +00:00
|
|
|
|
2017-03-25 18:01:25 +00:00
|
|
|
thread::spawn(move || for key in ::std::io::stdin().events() {
|
|
|
|
if let Ok(key) = key {
|
|
|
|
sender.send(map_key(key))
|
|
|
|
}
|
|
|
|
});
|
2016-10-08 23:47:15 +00:00
|
|
|
|
2016-10-10 00:16:35 +00:00
|
|
|
let backend = Concrete {
|
2016-12-13 23:05:35 +00:00
|
|
|
terminal: terminal,
|
2017-06-13 01:31:08 +00:00
|
|
|
current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)),
|
2016-12-13 23:05:35 +00:00
|
|
|
input: receiver,
|
2016-12-14 06:10:00 +00:00
|
|
|
resize: resize,
|
2016-12-13 23:05:35 +00:00
|
|
|
timeout: None,
|
2016-10-09 23:03:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
backend
|
2016-10-08 23:47:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn finish(&mut self) {
|
|
|
|
print!("{}{}", termion::cursor::Show, termion::cursor::Goto(1, 1));
|
2017-01-19 21:44:49 +00:00
|
|
|
print!("{}[49m{}[39m{}",
|
|
|
|
27 as char,
|
|
|
|
27 as char,
|
|
|
|
termion::clear::All);
|
2016-10-08 23:47:15 +00:00
|
|
|
}
|
|
|
|
|
2017-06-13 01:31:08 +00:00
|
|
|
fn with_color<F: FnOnce()>(&self, color: theme::ColorPair, f: F) {
|
2016-10-22 00:18:26 +00:00
|
|
|
let current_style = self.current_style.get();
|
|
|
|
|
|
|
|
self.apply_colorstyle(color);
|
|
|
|
|
|
|
|
self.current_style.set(color);
|
2016-10-08 23:47:15 +00:00
|
|
|
f();
|
2016-10-22 00:18:26 +00:00
|
|
|
self.current_style.set(current_style);
|
|
|
|
|
|
|
|
self.apply_colorstyle(current_style);
|
2016-10-08 23:47:15 +00:00
|
|
|
}
|
|
|
|
|
2016-10-22 00:18:26 +00:00
|
|
|
fn with_effect<F: FnOnce()>(&self, effect: theme::Effect, f: F) {
|
2016-10-24 18:16:56 +00:00
|
|
|
effect.on();
|
2016-10-08 23:47:15 +00:00
|
|
|
f();
|
2016-10-24 18:16:56 +00:00
|
|
|
effect.off();
|
2016-10-08 23:47:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn has_colors(&self) -> bool {
|
|
|
|
// TODO: color support detection?
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
|
|
|
fn screen_size(&self) -> (usize, usize) {
|
|
|
|
let (x, y) = termion::terminal_size().unwrap_or((1, 1));
|
|
|
|
(x as usize, y as usize)
|
|
|
|
}
|
|
|
|
|
2017-06-13 06:29:26 +00:00
|
|
|
fn clear(&self, color: theme::Color) {
|
|
|
|
self.apply_colorstyle(theme::ColorPair {
|
|
|
|
front: color,
|
|
|
|
back: color,
|
|
|
|
});
|
2016-10-08 23:47:15 +00:00
|
|
|
print!("{}", termion::clear::All);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn refresh(&mut self) {
|
|
|
|
self.terminal.flush().unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn print_at(&self, (x, y): (usize, usize), text: &str) {
|
2017-01-19 21:44:49 +00:00
|
|
|
print!("{}{}",
|
|
|
|
termion::cursor::Goto(1 + x as u16, 1 + y as u16),
|
|
|
|
text);
|
2016-10-08 23:47:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn set_refresh_rate(&mut self, fps: u32) {
|
2016-12-14 06:10:00 +00:00
|
|
|
self.timeout = Some(1000 / fps as u32);
|
2016-10-08 23:47:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn poll_event(&self) -> Event {
|
2016-12-14 06:10:00 +00:00
|
|
|
let input = &self.input;
|
|
|
|
let resize = &self.resize;
|
|
|
|
|
2016-12-13 23:05:35 +00:00
|
|
|
if let Some(timeout) = self.timeout {
|
2016-12-14 06:10:00 +00:00
|
|
|
let timeout = chan::after_ms(timeout);
|
|
|
|
chan_select!{
|
|
|
|
timeout.recv() => return Event::Refresh,
|
|
|
|
resize.recv() => return Event::WindowResize,
|
|
|
|
input.recv() -> input => return input.unwrap(),
|
|
|
|
}
|
2016-10-24 18:16:56 +00:00
|
|
|
} else {
|
2016-12-14 06:10:00 +00:00
|
|
|
chan_select!{
|
|
|
|
resize.recv() => return Event::WindowResize,
|
|
|
|
input.recv() -> input => return input.unwrap(),
|
|
|
|
}
|
2016-10-24 18:16:56 +00:00
|
|
|
}
|
2016-10-08 23:47:15 +00:00
|
|
|
}
|
|
|
|
}
|
2016-10-22 00:18:26 +00:00
|
|
|
|
2017-03-25 18:01:25 +00:00
|
|
|
fn map_key(event: TEvent) -> Event {
|
|
|
|
match event {
|
|
|
|
TEvent::Unsupported(bytes) => Event::Unknown(bytes),
|
|
|
|
TEvent::Key(TKey::Esc) => Event::Key(Key::Esc),
|
|
|
|
TEvent::Key(TKey::Backspace) => Event::Key(Key::Backspace),
|
|
|
|
TEvent::Key(TKey::Left) => Event::Key(Key::Left),
|
|
|
|
TEvent::Key(TKey::Right) => Event::Key(Key::Right),
|
|
|
|
TEvent::Key(TKey::Up) => Event::Key(Key::Up),
|
|
|
|
TEvent::Key(TKey::Down) => Event::Key(Key::Down),
|
|
|
|
TEvent::Key(TKey::Home) => Event::Key(Key::Home),
|
|
|
|
TEvent::Key(TKey::End) => Event::Key(Key::End),
|
|
|
|
TEvent::Key(TKey::PageUp) => Event::Key(Key::PageUp),
|
|
|
|
TEvent::Key(TKey::PageDown) => Event::Key(Key::PageDown),
|
|
|
|
TEvent::Key(TKey::Delete) => Event::Key(Key::Del),
|
|
|
|
TEvent::Key(TKey::Insert) => Event::Key(Key::Ins),
|
|
|
|
TEvent::Key(TKey::F(i)) if i < 12 => Event::Key(Key::from_f(i)),
|
|
|
|
TEvent::Key(TKey::F(j)) => Event::Unknown(vec![j]),
|
|
|
|
TEvent::Key(TKey::Char('\n')) => Event::Key(Key::Enter),
|
|
|
|
TEvent::Key(TKey::Char('\t')) => Event::Key(Key::Tab),
|
|
|
|
TEvent::Key(TKey::Char(c)) => Event::Char(c),
|
|
|
|
TEvent::Key(TKey::Ctrl('c')) => Event::Exit,
|
|
|
|
TEvent::Key(TKey::Ctrl(c)) => Event::CtrlChar(c),
|
|
|
|
TEvent::Key(TKey::Alt(c)) => Event::AltChar(c),
|
|
|
|
_ => Event::Unknown(vec![]),
|
2016-12-13 23:05:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-06-13 01:31:08 +00:00
|
|
|
fn with_color<F, R>(clr: &theme::Color, f: F) -> R
|
|
|
|
where F: FnOnce(&tcolor::Color) -> R
|
|
|
|
{
|
2016-10-22 00:18:26 +00:00
|
|
|
|
2017-06-13 01:31:08 +00:00
|
|
|
match *clr {
|
|
|
|
theme::Color::Dark(theme::BaseColor::Black) => f(&tcolor::Black),
|
|
|
|
theme::Color::Dark(theme::BaseColor::Red) => f(&tcolor::Red),
|
|
|
|
theme::Color::Dark(theme::BaseColor::Green) => f(&tcolor::Green),
|
|
|
|
theme::Color::Dark(theme::BaseColor::Yellow) => f(&tcolor::Yellow),
|
|
|
|
theme::Color::Dark(theme::BaseColor::Blue) => f(&tcolor::Blue),
|
|
|
|
theme::Color::Dark(theme::BaseColor::Magenta) => f(&tcolor::Magenta),
|
|
|
|
theme::Color::Dark(theme::BaseColor::Cyan) => f(&tcolor::Cyan),
|
|
|
|
theme::Color::Dark(theme::BaseColor::White) => f(&tcolor::White),
|
|
|
|
|
|
|
|
theme::Color::Light(theme::BaseColor::Black) => {
|
|
|
|
f(&tcolor::LightBlack)
|
|
|
|
}
|
|
|
|
theme::Color::Light(theme::BaseColor::Red) => f(&tcolor::LightRed),
|
|
|
|
theme::Color::Light(theme::BaseColor::Green) => {
|
|
|
|
f(&tcolor::LightGreen)
|
|
|
|
}
|
|
|
|
theme::Color::Light(theme::BaseColor::Yellow) => {
|
|
|
|
f(&tcolor::LightYellow)
|
|
|
|
}
|
|
|
|
theme::Color::Light(theme::BaseColor::Blue) => f(&tcolor::LightBlue),
|
|
|
|
theme::Color::Light(theme::BaseColor::Magenta) => {
|
|
|
|
f(&tcolor::LightMagenta)
|
|
|
|
}
|
|
|
|
theme::Color::Light(theme::BaseColor::Cyan) => f(&tcolor::LightCyan),
|
|
|
|
theme::Color::Light(theme::BaseColor::White) => {
|
|
|
|
f(&tcolor::LightWhite)
|
|
|
|
}
|
|
|
|
|
|
|
|
theme::Color::Rgb(r, g, b) => f(&tcolor::Rgb(r, g, b)),
|
|
|
|
theme::Color::RgbLowRes(r, g, b) => {
|
|
|
|
f(&tcolor::AnsiValue::rgb(r, g, b))
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2016-10-22 00:18:26 +00:00
|
|
|
}
|