feat(backend): Refactor crossterm backend

* Remove compilation warnings
* Fix rendering artifacts in the crossterm demo. In particular, the bold modifier
was leaking on most of the terminal screen because the old logic was not
properly unsetting the bold modifier after use (took inspiration of the termion
backend implementation)
pull/197/head
Florian Dehau 5 years ago
parent 42731da546
commit 3747ddbefb

@ -7,7 +7,7 @@ use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use crossterm::{input, AlternateScreen, InputEvent, KeyEvent, RawScreen};
use crossterm::{input, AlternateScreen, InputEvent, KeyEvent};
use structopt::StructOpt;
use tui::backend::CrosstermBackend;
use tui::Terminal;

@ -1,13 +1,19 @@
use std::io;
use std::{
fmt,
io::{self, Write},
};
use crate::backend::Backend;
use crate::style::{Color, Modifier};
use crate::{buffer::Cell, layout::Rect, style};
use crossterm::{
execute, queue, terminal, Clear, ClearType, Command, Crossterm, ErrorKind, Goto, Hide, Output,
SetAttr, SetBg, SetFg, Show,
execute, queue, terminal, Clear, ClearType, Crossterm, ErrorKind, Goto, Hide, SetAttr, SetBg,
SetFg, Show,
};
use crate::{
backend::Backend,
buffer::Cell,
layout::Rect,
style::{Color, Modifier, Style},
};
use std::io::{stdout, Stdout, Write};
pub struct CrosstermBackend<W: Write> {
alternate_screen: Option<crossterm::AlternateScreen>,
@ -47,8 +53,6 @@ where
}
}
// TODO: consider associated Error type on Backend to allow custom error types
// per backend
fn convert_error(error: ErrorKind) -> io::Error {
match error {
ErrorKind::IoError(err) => err,
@ -81,21 +85,15 @@ where
W: Write,
{
fn clear(&mut self) -> io::Result<()> {
queue!(self.stdout, Clear(ClearType::All));
self.stdout.flush();
Ok(())
execute!(self.stdout, Clear(ClearType::All)).map_err(convert_error)
}
fn hide_cursor(&mut self) -> io::Result<()> {
execute!(self.stdout, Hide);
self.stdout.flush();
Ok(())
execute!(self.stdout, Hide).map_err(convert_error)
}
fn show_cursor(&mut self) -> io::Result<()> {
execute!(self.stdout, Show);
self.stdout.flush();
Ok(())
execute!(self.stdout, Show).map_err(convert_error)
}
fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
@ -104,9 +102,7 @@ where
}
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
queue!(self.stdout, Goto(x, y));
self.stdout.flush();
Ok(())
execute!(self.stdout, Goto(x, y)).map_err(convert_error)
}
fn size(&self) -> io::Result<Rect> {
@ -127,33 +123,35 @@ where
use std::fmt::Write;
let mut string = String::with_capacity(content.size_hint().0 * 3);
let mut style = style::Style::default();
let mut style = Style::default();
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 || inst == 0 {
queue!(string, Goto(x, y));
queue!(string, Goto(x, y))?;
}
last_x = x;
last_y = y;
if cell.style.modifier != style.modifier {
for attr in to_crossterm_attributes(cell.style.modifier) {
queue!(string, SetAttr(attr));
}
let diff = ModifierDiff {
from: style.modifier,
to: cell.style.modifier,
};
diff.queue(&mut string)?;
inst += 1;
style.modifier = cell.style.modifier;
}
if cell.style.fg != style.fg {
let color = to_crossterm_color(cell.style.fg);
queue!(string, SetFg(color));
queue!(string, SetFg(color))?;
style.fg = cell.style.fg;
inst += 1;
}
if cell.style.bg != style.bg {
let color = to_crossterm_color(cell.style.bg);
queue!(string, SetBg(color));
queue!(string, SetBg(color))?;
style.bg = cell.style.bg;
inst += 1;
}
@ -169,61 +167,108 @@ where
SetFg(crossterm::Color::Reset),
SetBg(crossterm::Color::Reset),
SetAttr(crossterm::Attribute::Reset)
);
)?;
Crossterm::new().color().reset();
Crossterm::new().color().reset()?;
Ok(())
}
}
#[derive(Debug)]
struct ModifierDiff {
pub from: Modifier,
pub to: Modifier,
}
#[cfg(unix)]
fn to_crossterm_attributes(modifier: Modifier) -> Vec<crossterm::Attribute> {
let mut result = Vec::new();
impl ModifierDiff {
fn queue<W>(&self, mut w: W) -> io::Result<()>
where
W: fmt::Write,
{
use crossterm::Attribute;
let removed = self.from - self.to;
if removed.contains(Modifier::REVERSED) {
queue!(w, SetAttr(Attribute::NoInverse))?;
}
if removed.contains(Modifier::BOLD) {
queue!(w, SetAttr(Attribute::NormalIntensity))?;
if self.to.contains(Modifier::DIM) {
queue!(w, SetAttr(Attribute::Dim))?;
}
}
if removed.contains(Modifier::ITALIC) {
queue!(w, SetAttr(Attribute::NoItalic))?;
}
if removed.contains(Modifier::UNDERLINED) {
queue!(w, SetAttr(Attribute::NoUnderline))?;
}
if removed.contains(Modifier::DIM) {
queue!(w, SetAttr(Attribute::NormalIntensity))?;
}
if removed.contains(Modifier::CROSSED_OUT) {
queue!(w, SetAttr(Attribute::NotCrossedOut))?;
}
if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
queue!(w, SetAttr(Attribute::NoBlink))?;
}
if modifier.contains(Modifier::BOLD) {
result.push(crossterm::Attribute::Bold)
}
if modifier.contains(Modifier::DIM) {
result.push(crossterm::Attribute::Dim)
}
if modifier.contains(Modifier::ITALIC) {
result.push(crossterm::Attribute::Italic)
}
if modifier.contains(Modifier::UNDERLINED) {
result.push(crossterm::Attribute::Underlined)
}
if modifier.contains(Modifier::SLOW_BLINK) {
result.push(crossterm::Attribute::SlowBlink)
}
if modifier.contains(Modifier::RAPID_BLINK) {
result.push(crossterm::Attribute::RapidBlink)
}
if modifier.contains(Modifier::REVERSED) {
result.push(crossterm::Attribute::Reverse)
}
if modifier.contains(Modifier::HIDDEN) {
result.push(crossterm::Attribute::Hidden)
}
if modifier.contains(Modifier::CROSSED_OUT) {
result.push(crossterm::Attribute::CrossedOut)
}
let added = self.to - self.from;
if added.contains(Modifier::REVERSED) {
queue!(w, SetAttr(Attribute::Reverse))?;
}
if added.contains(Modifier::BOLD) {
queue!(w, SetAttr(Attribute::Bold))?;
}
if added.contains(Modifier::ITALIC) {
queue!(w, SetAttr(Attribute::Italic))?;
}
if added.contains(Modifier::UNDERLINED) {
queue!(w, SetAttr(Attribute::Underlined))?;
}
if added.contains(Modifier::DIM) {
queue!(w, SetAttr(Attribute::Dim))?;
}
if added.contains(Modifier::CROSSED_OUT) {
queue!(w, SetAttr(Attribute::CrossedOut))?;
}
if added.contains(Modifier::SLOW_BLINK) {
queue!(w, SetAttr(Attribute::SlowBlink))?;
}
if added.contains(Modifier::RAPID_BLINK) {
queue!(w, SetAttr(Attribute::RapidBlink))?;
}
result
Ok(())
}
}
#[cfg(windows)]
fn to_crossterm_attributes(modifier: Modifier) -> Vec<crossterm::Attribute> {
let mut result = Vec::new();
impl ModifierDiff {
fn queue<W>(&self, mut w: W) -> io::Result<()>
where
W: fmt::Write,
{
use crossterm::Attribute;
if modifier.contains(Modifier::BOLD) {
result.push(crossterm::Attribute::Bold)
}
if modifier.contains(Modifier::UNDERLINED) {
result.push(crossterm::Attribute::Underlined)
}
let removed = self.from - self.to;
if removed.contains(Modifier::BOLD) {
queue!(w, SetAttr(Attribute::NormalIntensity))?;
}
if removed.contains(Modifier::UNDERLINED) {
queue!(w, SetAttr(Attribute::NoUnderline))?;
}
result
let added = self.to - self.from;
if added.contains(Modifier::BOLD) {
queue!(w, SetAttr(Attribute::Bold))?;
}
if added.contains(Modifier::UNDERLINED) {
queue!(w, SetAttr(Attribute::Underlined))?;
}
Ok(())
}
}
fn to_crossterm_color(color: Color) -> crossterm::Color {

Loading…
Cancel
Save