Use async input for termion backend

This enables async refresh, fixing the `logs` and `progress` examples.
This commit is contained in:
Alexandre Bury 2016-12-13 15:05:35 -08:00
parent 89ec140f41
commit ea3dde33ec

View File

@ -2,22 +2,28 @@ extern crate termion;
use ::backend; use ::backend;
use ::event::{Event, Key}; use ::event::{Event, Key};
use self::termion::color; use self::termion::color as tcolor;
use self::termion::event::Key as TKey; use self::termion::event::Key as TKey;
use self::termion::input::TermRead; use self::termion::input::TermRead;
use self::termion::style;
use self::termion::raw::IntoRawMode; use self::termion::raw::IntoRawMode;
use self::termion::style as tstyle;
use std::cell::Cell; use std::cell::Cell;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::io::Write;
use std::fmt; use std::fmt;
use std::io::Write;
use std::sync::mpsc;
use std::thread;
use std::time;
use ::theme; use ::theme;
pub struct Concrete { pub struct Concrete {
terminal: termion::raw::RawTerminal<::std::io::Stdout>, terminal: termion::raw::RawTerminal<::std::io::Stdout>,
current_style: Cell<theme::ColorStyle>, current_style: Cell<theme::ColorStyle>,
colors: BTreeMap<i16, (Box<color::Color>, Box<color::Color>)>, colors: BTreeMap<i16, (Box<tcolor::Color>, Box<tcolor::Color>)>,
input: mpsc::Receiver<Event>,
timeout: Option<time::Duration>,
} }
trait Effectable { trait Effectable {
@ -25,10 +31,9 @@ trait Effectable {
fn off(&self); fn off(&self);
} }
struct ColorRef<'a>(&'a color::Color); struct ColorRef<'a>(&'a tcolor::Color);
impl <'a> color::Color for ColorRef<'a> {
impl<'a> tcolor::Color for ColorRef<'a> {
#[inline] #[inline]
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result { fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.write_fg(f) self.0.write_fg(f)
@ -38,27 +43,26 @@ impl <'a> color::Color for ColorRef<'a> {
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result { fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.write_bg(f) self.0.write_bg(f)
} }
} }
impl Effectable for theme::Effect { impl Effectable for theme::Effect {
fn on(&self) { fn on(&self) {
match *self { match *self {
theme::Effect::Simple => (), theme::Effect::Simple => (),
theme::Effect::Reverse => print!("{}", style::Invert), theme::Effect::Reverse => print!("{}", tstyle::Invert),
} }
} }
fn off(&self) { fn off(&self) {
match *self { match *self {
theme::Effect::Simple => (), theme::Effect::Simple => (),
theme::Effect::Reverse => print!("{}", style::NoInvert), theme::Effect::Reverse => print!("{}", tstyle::NoInvert),
} }
} }
} }
fn apply_colors(fg: &color::Color, bg: &color::Color) { fn apply_colors(fg: &tcolor::Color, bg: &tcolor::Color) {
print!("{}{}", color::Fg(ColorRef(fg)), color::Bg(ColorRef(bg))); print!("{}{}", tcolor::Fg(ColorRef(fg)), tcolor::Bg(ColorRef(bg)));
} }
impl Concrete { impl Concrete {
@ -71,18 +75,33 @@ impl Concrete {
impl backend::Backend for Concrete { impl backend::Backend for Concrete {
fn init() -> Self { fn init() -> Self {
print!("{}", termion::cursor::Hide); print!("{}", termion::cursor::Hide);
let terminal = ::std::io::stdout().into_raw_mode().unwrap();
let (sender, receiver) = mpsc::channel();
// TODO: use signal_chan crate
thread::spawn(move || {
for key in ::std::io::stdin().keys() {
if let Ok(key) = key {
if sender.send(map_key(key)).is_err() {
break;
}
}
}
});
let backend = Concrete { let backend = Concrete {
terminal: ::std::io::stdout().into_raw_mode().unwrap(), terminal: terminal,
current_style: Cell::new(theme::ColorStyle::Background), current_style: Cell::new(theme::ColorStyle::Background),
colors: BTreeMap::new(), colors: BTreeMap::new(),
input: receiver,
timeout: None,
}; };
backend backend
} }
fn finish(&mut self) { fn finish(&mut self) {
// Maybe we should clear everything?
print!("{}{}", termion::cursor::Show, termion::cursor::Goto(1, 1)); print!("{}{}", termion::cursor::Show, termion::cursor::Goto(1, 1));
self.clear(); self.clear();
} }
@ -148,12 +167,22 @@ impl backend::Backend for Concrete {
// TODO: handle async refresh, when no input is entered. // TODO: handle async refresh, when no input is entered.
// Could be done with a timeout on the event polling, // Could be done with a timeout on the event polling,
// if it was supportedd. // if it was supportedd.
self.timeout = Some(time::Duration::from_millis(1000 / fps as u64));
} }
fn poll_event(&self) -> Event { fn poll_event(&self) -> Event {
// TODO: parse input // TODO: select! on the input and SIGWINCH signal channel.
if let Some(key) = ::std::io::stdin().keys().next() { // TODO: also handle timeout... recv_timeout?
match key.unwrap() { if let Some(timeout) = self.timeout {
self.input.recv_timeout(timeout).unwrap_or(Event::Refresh)
} else {
self.input.recv().unwrap()
}
}
}
fn map_key(key: TKey) -> Event {
match key {
TKey::Esc => Event::Key(Key::Esc), TKey::Esc => Event::Key(Key::Esc),
TKey::Backspace => Event::Key(Key::Backspace), TKey::Backspace => Event::Key(Key::Backspace),
TKey::Left => Event::Key(Key::Left), TKey::Left => Event::Key(Key::Left),
@ -171,60 +200,57 @@ impl backend::Backend for Concrete {
TKey::Char('\n') => Event::Key(Key::Enter), TKey::Char('\n') => Event::Key(Key::Enter),
TKey::Char('\t') => Event::Key(Key::Tab), TKey::Char('\t') => Event::Key(Key::Tab),
TKey::Char(c) => Event::Char(c), TKey::Char(c) => Event::Char(c),
TKey::Ctrl('c') => panic!("Ctrl-C pressed"), TKey::Ctrl('c') => Event::Exit,
TKey::Ctrl(c) => Event::CtrlChar(c), TKey::Ctrl(c) => Event::CtrlChar(c),
TKey::Alt(c) => Event::AltChar(c), TKey::Alt(c) => Event::AltChar(c),
_ => Event::Unknown(-1), _ => Event::Unknown(-1),
} }
} else {
Event::Refresh
}
}
} }
fn colour_to_termion_colour(clr: &theme::Color) -> Box<color::Color> { fn colour_to_termion_colour(clr: &theme::Color) -> Box<tcolor::Color> {
match *clr { match *clr {
theme::Color::Dark(theme::BaseColor::Black) => Box::new(color::Black), theme::Color::Dark(theme::BaseColor::Black) => Box::new(tcolor::Black),
theme::Color::Dark(theme::BaseColor::Red) => Box::new(color::Red), theme::Color::Dark(theme::BaseColor::Red) => Box::new(tcolor::Red),
theme::Color::Dark(theme::BaseColor::Green) => Box::new(color::Green), theme::Color::Dark(theme::BaseColor::Green) => Box::new(tcolor::Green),
theme::Color::Dark(theme::BaseColor::Yellow) => { theme::Color::Dark(theme::BaseColor::Yellow) => {
Box::new(color::Yellow) Box::new(tcolor::Yellow)
} }
theme::Color::Dark(theme::BaseColor::Blue) => Box::new(color::Blue), theme::Color::Dark(theme::BaseColor::Blue) => Box::new(tcolor::Blue),
theme::Color::Dark(theme::BaseColor::Magenta) => { theme::Color::Dark(theme::BaseColor::Magenta) => {
Box::new(color::Magenta) Box::new(tcolor::Magenta)
} }
theme::Color::Dark(theme::BaseColor::Cyan) => Box::new(color::Cyan), theme::Color::Dark(theme::BaseColor::Cyan) => Box::new(tcolor::Cyan),
theme::Color::Dark(theme::BaseColor::White) => Box::new(color::White), theme::Color::Dark(theme::BaseColor::White) => Box::new(tcolor::White),
theme::Color::Light(theme::BaseColor::Black) => { theme::Color::Light(theme::BaseColor::Black) => {
Box::new(color::LightBlack) Box::new(tcolor::LightBlack)
} }
theme::Color::Light(theme::BaseColor::Red) => { theme::Color::Light(theme::BaseColor::Red) => {
Box::new(color::LightRed) Box::new(tcolor::LightRed)
} }
theme::Color::Light(theme::BaseColor::Green) => { theme::Color::Light(theme::BaseColor::Green) => {
Box::new(color::LightGreen) Box::new(tcolor::LightGreen)
} }
theme::Color::Light(theme::BaseColor::Yellow) => { theme::Color::Light(theme::BaseColor::Yellow) => {
Box::new(color::LightYellow) Box::new(tcolor::LightYellow)
} }
theme::Color::Light(theme::BaseColor::Blue) => { theme::Color::Light(theme::BaseColor::Blue) => {
Box::new(color::LightBlue) Box::new(tcolor::LightBlue)
} }
theme::Color::Light(theme::BaseColor::Magenta) => { theme::Color::Light(theme::BaseColor::Magenta) => {
Box::new(color::LightMagenta) Box::new(tcolor::LightMagenta)
} }
theme::Color::Light(theme::BaseColor::Cyan) => { theme::Color::Light(theme::BaseColor::Cyan) => {
Box::new(color::LightCyan) Box::new(tcolor::LightCyan)
} }
theme::Color::Light(theme::BaseColor::White) => { theme::Color::Light(theme::BaseColor::White) => {
Box::new(color::LightWhite) Box::new(tcolor::LightWhite)
} }
theme::Color::Rgb(r, g, b) => Box::new(color::Rgb(r, g, b)), theme::Color::Rgb(r, g, b) => Box::new(tcolor::Rgb(r, g, b)),
theme::Color::RgbLowRes(r, g, b) => { theme::Color::RgbLowRes(r, g, b) => {
Box::new(color::AnsiValue::rgb(r, g, b)) Box::new(tcolor::AnsiValue::rgb(r, g, b))
} }
} }
} }