Refactor Terminal to be able to support multiple backends

* Add Rustbox as an other possible backend
This commit is contained in:
Florian Dehau 2016-11-05 19:18:48 +01:00
parent 652ff12380
commit 9bc61551e2
6 changed files with 218 additions and 79 deletions

View File

@ -5,6 +5,7 @@ authors = ["Florian Dehau <florian.dehau@telecomnancy.net>"]
[dependencies] [dependencies]
termion = "1.1.1" termion = "1.1.1"
rustbox = "0.9.0"
bitflags = "0.7" bitflags = "0.7"
cassowary = "0.2.0" cassowary = "0.2.0"
log = "0.3" log = "0.3"

View File

@ -5,7 +5,7 @@ use cassowary::{Solver, Variable, Expression, Constraint};
use cassowary::WeightedRelation::*; use cassowary::WeightedRelation::*;
use cassowary::strength::{REQUIRED, WEAK}; use cassowary::strength::{REQUIRED, WEAK};
use terminal::Terminal; use terminal::{Terminal, Backend};
#[derive(Debug, Hash, PartialEq)] #[derive(Debug, Hash, PartialEq)]
pub enum Direction { pub enum Direction {
@ -306,8 +306,9 @@ impl Group {
self.sizes = Vec::from(sizes); self.sizes = Vec::from(sizes);
self self
} }
pub fn render<F>(&self, t: &mut Terminal, area: &Rect, mut f: F) pub fn render<F, B>(&self, t: &mut Terminal<B>, area: &Rect, mut f: F)
where F: FnMut(&mut Terminal, &[Rect]) where B: Backend,
F: FnMut(&mut Terminal<B>, &[Rect])
{ {
let chunks = t.compute_layout(self, area); let chunks = t.compute_layout(self, area);
f(t, &chunks); f(t, &chunks);

View File

@ -1,4 +1,5 @@
extern crate termion; extern crate termion;
extern crate rustbox;
#[macro_use] #[macro_use]
extern crate bitflags; extern crate bitflags;
#[macro_use] #[macro_use]
@ -15,4 +16,4 @@ pub mod widgets;
pub mod style; pub mod style;
pub mod layout; pub mod layout;
pub use self::terminal::Terminal; pub use self::terminal::{Terminal, Backend, TermionBackend, RustboxBackend};

View File

@ -1,4 +1,5 @@
use termion; use termion;
use rustbox;
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum Color { pub enum Color {
@ -37,7 +38,7 @@ macro_rules! termion_bg_rgb {
} }
impl Color { impl Color {
pub fn fg(&self) -> String { pub fn termion_fg(&self) -> String {
match *self { match *self {
Color::Reset => termion_fg!(Reset), Color::Reset => termion_fg!(Reset),
Color::Black => termion_fg!(Black), Color::Black => termion_fg!(Black),
@ -57,7 +58,7 @@ impl Color {
Color::Rgb(r, g, b) => termion_fg_rgb!(r, g, b), Color::Rgb(r, g, b) => termion_fg_rgb!(r, g, b),
} }
} }
pub fn bg(&self) -> String { pub fn termion_bg(&self) -> String {
match *self { match *self {
Color::Reset => termion_bg!(Reset), Color::Reset => termion_bg!(Reset),
Color::Black => termion_bg!(Black), Color::Black => termion_bg!(Black),
@ -78,3 +79,26 @@ impl Color {
} }
} }
} }
impl Into<rustbox::Color> for Color {
fn into(self) -> rustbox::Color {
match self {
Color::Reset => rustbox::Color::Default,
Color::Black => rustbox::Color::Black,
Color::Red => rustbox::Color::Red,
Color::Green => rustbox::Color::Green,
Color::Yellow => rustbox::Color::Yellow,
Color::Magenta => rustbox::Color::Magenta,
Color::Cyan => rustbox::Color::Cyan,
Color::Gray => rustbox::Color::Black,
Color::DarkGray => rustbox::Color::Black,
Color::LightRed => rustbox::Color::Red,
Color::LightGreen => rustbox::Color::Green,
Color::LightYellow => rustbox::Color::Yellow,
Color::LightMagenta => rustbox::Color::Magenta,
Color::LightCyan => rustbox::Color::Cyan,
Color::White => rustbox::Color::White,
Color::Rgb(r, g, b) => rustbox::Color::Default,
}
}
}

View File

@ -5,12 +5,165 @@ use std::collections::HashMap;
use termion; use termion;
use termion::raw::{IntoRawMode, RawTerminal}; use termion::raw::{IntoRawMode, RawTerminal};
use buffer::Buffer; use rustbox;
use buffer::{Buffer, Cell};
use layout::{Rect, Group, split}; use layout::{Rect, Group, split};
use widgets::Widget; use widgets::Widget;
use style::Color; use style::Color;
use util::hash; use util::hash;
pub trait Backend {
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
where I: Iterator<Item = (u16, u16, &'a Cell)>;
fn hide_cursor(&mut self) -> Result<(), io::Error>;
fn show_cursor(&mut self) -> Result<(), io::Error>;
fn clear(&mut self) -> Result<(), io::Error>;
fn size(&self) -> Result<Rect, io::Error>;
fn flush(&mut self) -> Result<(), io::Error>;
}
pub struct TermionBackend {
stdout: RawTerminal<io::Stdout>,
}
impl TermionBackend {
pub fn new() -> Result<TermionBackend, io::Error> {
let stdout = try!(io::stdout().into_raw_mode());
Ok(TermionBackend { stdout: stdout })
}
}
impl Backend for TermionBackend {
/// Clears the entire screen and move the cursor to the top left of the screen
fn clear(&mut self) -> Result<(), io::Error> {
try!(write!(self.stdout, "{}", termion::clear::All));
try!(write!(self.stdout, "{}", termion::cursor::Goto(1, 1)));
try!(self.stdout.flush());
Ok(())
}
/// Hides cursor
fn hide_cursor(&mut self) -> Result<(), io::Error> {
try!(write!(self.stdout, "{}", termion::cursor::Hide));
try!(self.stdout.flush());
Ok(())
}
/// Shows cursor
fn show_cursor(&mut self) -> Result<(), io::Error> {
try!(write!(self.stdout, "{}", termion::cursor::Show));
try!(self.stdout.flush());
Ok(())
}
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
where I: Iterator<Item = (u16, u16, &'a Cell)>
{
let mut string = String::with_capacity(content.size_hint().0 * 3);
let mut fg = Color::Reset;
let mut bg = Color::Reset;
let mut last_y = 0;
let mut last_x = 0;
let mut inst = 0;
for (x, y, cell) in content {
if y != last_y || x != last_x + 1 {
string.push_str(&format!("{}", termion::cursor::Goto(x + 1, y + 1)));
inst += 1;
}
last_x = x;
last_y = y;
if cell.fg != fg {
string.push_str(&cell.fg.termion_fg());
fg = cell.fg;
inst += 1;
}
if cell.bg != bg {
string.push_str(&cell.bg.termion_bg());
bg = cell.bg;
inst += 1;
}
string.push_str(&cell.symbol);
inst += 1;
}
debug!("{} instructions outputed.", inst);
try!(write!(self.stdout,
"{}{}{}",
string,
Color::Reset.termion_fg(),
Color::Reset.termion_bg()));
Ok(())
}
/// Return the size of the terminal
fn size(&self) -> Result<Rect, io::Error> {
let terminal = try!(termion::terminal_size());
Ok(Rect {
x: 0,
y: 0,
width: terminal.0,
height: terminal.1,
})
}
fn flush(&mut self) -> Result<(), io::Error> {
try!(self.stdout.flush());
Ok(())
}
}
pub struct RustboxBackend {
rustbox: rustbox::RustBox,
}
impl RustboxBackend {
pub fn new() -> Result<RustboxBackend, rustbox::InitError> {
let rustbox = try!(rustbox::RustBox::init(Default::default()));
Ok(RustboxBackend { rustbox: rustbox })
}
}
impl Backend for RustboxBackend {
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
where I: Iterator<Item = (u16, u16, &'a Cell)>
{
let mut inst = 0;
for (x, y, cell) in content {
inst += 1;
self.rustbox.print(x as usize,
y as usize,
rustbox::RB_NORMAL,
cell.fg.into(),
cell.bg.into(),
&cell.symbol);
}
debug!("{} instructions outputed", inst);
Ok(())
}
fn hide_cursor(&mut self) -> Result<(), io::Error> {
Ok(())
}
fn show_cursor(&mut self) -> Result<(), io::Error> {
Ok(())
}
fn clear(&mut self) -> Result<(), io::Error> {
self.rustbox.clear();
Ok(())
}
fn size(&self) -> Result<Rect, io::Error> {
Ok((Rect {
x: 0,
y: 0,
width: self.rustbox.width() as u16,
height: self.rustbox.height() as u16,
}))
}
fn flush(&mut self) -> Result<(), io::Error> {
self.rustbox.present();
Ok(())
}
}
/// Holds a computed layout and keeps track of its use between successive draw calls /// Holds a computed layout and keeps track of its use between successive draw calls
#[derive(Debug)] #[derive(Debug)]
pub struct LayoutEntry { pub struct LayoutEntry {
@ -19,9 +172,10 @@ pub struct LayoutEntry {
} }
/// Interface to the terminal backed by Termion /// Interface to the terminal backed by Termion
pub struct Terminal { pub struct Terminal<B>
/// Raw mode termion terminal where B: Backend
stdout: RawTerminal<io::Stdout>, {
backend: B,
/// Cache to prevent the layout to be computed at each draw call /// Cache to prevent the layout to be computed at each draw call
layout_cache: HashMap<u64, LayoutEntry>, layout_cache: HashMap<u64, LayoutEntry>,
/// Holds the results of the current and previous draw calls. The two are compared at the end /// Holds the results of the current and previous draw calls. The two are compared at the end
@ -31,29 +185,27 @@ pub struct Terminal {
current: usize, current: usize,
} }
impl Terminal { impl<B> Terminal<B>
where B: Backend
{
/// Wrapper around Termion initialization. Each buffer is initialized with a blank string and /// Wrapper around Termion initialization. Each buffer is initialized with a blank string and
/// default colors for the foreground and the background /// default colors for the foreground and the background
pub fn new() -> Result<Terminal, io::Error> { pub fn new(backend: B) -> Result<Terminal<B>, io::Error> {
let stdout = try!(io::stdout().into_raw_mode()); let size = try!(backend.size());
let size = try!(Terminal::size());
Ok(Terminal { Ok(Terminal {
stdout: stdout, backend: backend,
layout_cache: HashMap::new(), layout_cache: HashMap::new(),
buffers: [Buffer::empty(size), Buffer::empty(size)], buffers: [Buffer::empty(size), Buffer::empty(size)],
current: 0, current: 0,
}) })
} }
/// Return the size of the terminal pub fn backend(&self) -> &B {
pub fn size() -> Result<Rect, io::Error> { &self.backend
let terminal = try!(termion::terminal_size()); }
Ok(Rect {
x: 0, pub fn backend_mut(&mut self) -> &mut B {
y: 0, &mut self.backend
width: terminal.0,
height: terminal.1,
})
} }
/// Check if we have already computed a layout for a given group, otherwise it creates one and /// Check if we have already computed a layout for a given group, otherwise it creates one and
@ -82,11 +234,6 @@ impl Terminal {
/// update the UI and writes it to stdout. /// update the UI and writes it to stdout.
pub fn flush(&mut self) -> Result<(), io::Error> { pub fn flush(&mut self) -> Result<(), io::Error> {
let width = self.buffers[self.current].area.width; let width = self.buffers[self.current].area.width;
let mut string = String::with_capacity(self.buffers[self.current].content.len() * 3);
let mut fg = Color::Reset;
let mut bg = Color::Reset;
let mut last_y = 0;
let mut last_x = 0;
let content = self.buffers[self.current] let content = self.buffers[self.current]
.content .content
.iter() .iter()
@ -100,34 +247,7 @@ impl Terminal {
} else { } else {
None None
}); });
let mut inst = 0; self.backend.draw(content)
for (x, y, cell) in content {
if y != last_y || x != last_x + 1 {
string.push_str(&format!("{}", termion::cursor::Goto(x + 1, y + 1)));
inst += 1;
}
last_x = x;
last_y = y;
if cell.fg != fg {
string.push_str(&cell.fg.fg());
fg = cell.fg;
inst += 1;
}
if cell.bg != bg {
string.push_str(&cell.bg.bg());
bg = cell.bg;
inst += 1;
}
string.push_str(&cell.symbol);
inst += 1;
}
debug!("{} instructions outputed.", inst);
try!(write!(self.stdout,
"{}{}{}",
string,
Color::Reset.fg(),
Color::Reset.bg()));
Ok(())
} }
/// Calls the draw method of a given widget on the current buffer /// Calls the draw method of a given widget on the current buffer
@ -144,7 +264,7 @@ impl Terminal {
self.buffers[1 - self.current].resize(area); self.buffers[1 - self.current].resize(area);
self.buffers[1 - self.current].reset(); self.buffers[1 - self.current].reset();
self.layout_cache.clear(); self.layout_cache.clear();
try!(self.clear()); try!(self.backend.clear());
Ok(()) Ok(())
} }
@ -173,29 +293,20 @@ impl Terminal {
self.current = 1 - self.current; self.current = 1 - self.current;
// Flush // Flush
try!(self.stdout.flush()); try!(self.backend.flush());
Ok(()) Ok(())
} }
/// Clears the entire screen and move the cursor to the top left of the screen
pub fn clear(&mut self) -> Result<(), io::Error> {
try!(write!(self.stdout, "{}", termion::clear::All));
try!(write!(self.stdout, "{}", termion::cursor::Goto(1, 1)));
try!(self.stdout.flush());
Ok(())
}
/// Hides cursor
pub fn hide_cursor(&mut self) -> Result<(), io::Error> { pub fn hide_cursor(&mut self) -> Result<(), io::Error> {
try!(write!(self.stdout, "{}", termion::cursor::Hide)); self.backend.hide_cursor()
try!(self.stdout.flush());
Ok(())
} }
/// Shows cursor
pub fn show_cursor(&mut self) -> Result<(), io::Error> { pub fn show_cursor(&mut self) -> Result<(), io::Error> {
try!(write!(self.stdout, "{}", termion::cursor::Show)); self.backend.show_cursor()
try!(self.stdout.flush()); }
Ok(()) pub fn clear(&mut self) -> Result<(), io::Error> {
self.backend.clear()
}
pub fn size(&self) -> Result<Rect, io::Error> {
self.backend.size()
} }
} }

View File

@ -21,7 +21,7 @@ pub use self::table::Table;
use buffer::Buffer; use buffer::Buffer;
use layout::Rect; use layout::Rect;
use terminal::Terminal; use terminal::{Backend, Terminal};
use style::Color; use style::Color;
/// Bitflags that can be composed to set the visible borders essentially on the block widget. /// Bitflags that can be composed to set the visible borders essentially on the block widget.
@ -58,8 +58,9 @@ pub trait Widget {
} }
} }
/// Helper method that can be chained with a widget's builder methods to render it. /// Helper method that can be chained with a widget's builder methods to render it.
fn render(&self, area: &Rect, t: &mut Terminal) fn render<B>(&self, area: &Rect, t: &mut Terminal<B>)
where Self: Sized where Self: Sized,
B: Backend
{ {
t.render(self, area); t.render(self, area);
} }