Skip to content
This repository has been archived by the owner on Aug 6, 2023. It is now read-only.

Commit

Permalink
add termwiz backend and termwiz_demo
Browse files Browse the repository at this point in the history
  • Loading branch information
prabirshrestha committed May 23, 2020
1 parent 4fe647d commit 13db5e5
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 2 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ rustbox = { version = "0.11", optional = true }
crossterm = { version = "0.17", optional = true }
easycurses = { version = "0.12.2", optional = true }
pancurses = { version = "0.16.1", optional = true, features = ["win32a"] }
termwiz = { version = "0.9.0", optional = true }

[dev-dependencies]
rand = "0.7"
Expand Down Expand Up @@ -116,6 +117,11 @@ name = "rustbox_demo"
path = "examples/rustbox_demo.rs"
required-features = ["rustbox"]

[[example]]
name = "termwiz_demo"
path = "examples/termwiz_demo.rs"
required-features = ["termwiz"]

[[example]]
name = "crossterm_demo"
path = "examples/crossterm_demo.rs"
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ user interfaces and dashboards. It is heavily inspired by the `Javascript`
library [blessed-contrib](https://github.com/yaronn/blessed-contrib) and the
`Go` library [termui](https://github.com/gizak/termui).

The library itself supports four different backends to draw to the terminal. You
The library itself supports five different backends to draw to the terminal. You
can either choose from:

- [termion](https://github.com/ticki/termion)
- [rustbox](https://github.com/gchp/rustbox)
- [crossterm](https://github.com/crossterm-rs/crossterm)
- [pancurses](https://github.com/ihalila/pancurses)
- [termwiz](https://github.com/wez/wezterm/tree/master/termwiz)

However, some features may only be available in one of the four.
However, some features may only be available in one of the five.

The library is based on the principle of immediate rendering with intermediate
buffers. This means that at each new frame you should build all widgets that are
Expand Down
86 changes: 86 additions & 0 deletions examples/termwiz_demo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
mod demo;
#[allow(dead_code)]
mod util;

use crate::demo::{ui, App};
use argh::FromArgs;
use std::{
error::Error,
time::{Duration, Instant},
};
use termwiz::{input::*, terminal::Terminal as TermwizTerminal};
use tui::{backend::TermwizBackend, Terminal};

/// Termwiz demo
#[derive(Debug, FromArgs)]
struct Cli {
/// time in ms between two ticks.
#[argh(option, default = "250")]
tick_rate: u64,
/// whether unicode symbols are used to improve the overall look of the app
#[argh(option, default = "true")]
enhanced_graphics: bool,
}

fn main() -> Result<(), Box<dyn Error>> {
let cli: Cli = argh::from_env();

let backend = TermwizBackend::new()?;
let mut terminal = Terminal::new(backend)?;
terminal.hide_cursor()?;

let mut app = App::new("Termwiz demo", cli.enhanced_graphics);

let mut last_tick = Instant::now();
let tick_rate = Duration::from_millis(cli.tick_rate);

loop {
terminal.draw(|mut f| ui::draw(&mut f, &mut app))?;
match terminal
.backend_mut()
.buffered_terminal_mut()
.terminal()
.poll_input(Some(tick_rate))
{
Ok(Some(input)) => match input {
InputEvent::Key(KeyEvent {
key: KeyCode::Char(c),
..
}) => app.on_key(c),
InputEvent::Key(KeyEvent {
key: KeyCode::UpArrow,
..
}) => app.on_up(),
InputEvent::Key(KeyEvent {
key: KeyCode::DownArrow,
..
}) => app.on_down(),
InputEvent::Key(KeyEvent {
key: KeyCode::LeftArrow,
..
}) => app.on_left(),
InputEvent::Key(KeyEvent {
key: KeyCode::RightArrow,
..
}) => app.on_right(),
InputEvent::Resized { cols, rows } => {
terminal
.backend_mut()
.buffered_terminal_mut()
.resize(cols, rows);
}
_ => {}
},
_ => {}
}

if last_tick.elapsed() > tick_rate {
app.on_tick();
last_tick = Instant::now();
}
if app.should_quit {
break;
}
}
Ok(())
}
5 changes: 5 additions & 0 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ mod rustbox;
#[cfg(feature = "rustbox")]
pub use self::rustbox::RustboxBackend;

#[cfg(feature = "termwiz")]
mod termwiz;
#[cfg(feature = "termwiz")]
pub use self::termwiz::TermwizBackend;

#[cfg(feature = "termion")]
mod termion;
#[cfg(feature = "termion")]
Expand Down
189 changes: 189 additions & 0 deletions src/backend/termwiz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
use crate::{
backend::Backend,
buffer::Cell,
layout::Rect,
style::{Color, Modifier},
};
use std::{error::Error, io};
use termwiz::{
caps::Capabilities,
cell::*,
color::*,
surface::*,
terminal::{buffered::BufferedTerminal, SystemTerminal, Terminal},
};

pub struct TermwizBackend {
buffered_terminal: BufferedTerminal<SystemTerminal>,
}

impl TermwizBackend {
pub fn new() -> Result<TermwizBackend, Box<dyn Error>> {
let mut buffered_terminal =
BufferedTerminal::new(SystemTerminal::new(Capabilities::new_from_env()?)?)?;
buffered_terminal.terminal().set_raw_mode()?;
buffered_terminal.terminal().enter_alternate_screen()?;
Ok(TermwizBackend { buffered_terminal })
}

pub fn with_buffered_terminal(instance: BufferedTerminal<SystemTerminal>) -> TermwizBackend {
TermwizBackend {
buffered_terminal: instance,
}
}

pub fn buffered_terminal(&self) -> &BufferedTerminal<SystemTerminal> {
&self.buffered_terminal
}

pub fn buffered_terminal_mut(&mut self) -> &mut BufferedTerminal<SystemTerminal> {
&mut self.buffered_terminal
}
}

impl Backend for TermwizBackend {
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
where
I: Iterator<Item = (u16, u16, &'a Cell)>,
{
for (x, y, cell) in content {
self.buffered_terminal.add_changes(vec![
Change::CursorPosition {
x: Position::Absolute(x as usize),
y: Position::Absolute(y as usize),
},
Change::Attribute(AttributeChange::Foreground(cell.style.fg.into())),
Change::Attribute(AttributeChange::Background(cell.style.bg.into())),
]);

self.buffered_terminal
.add_change(Change::Attribute(AttributeChange::Intensity(
if cell.style.modifier.contains(Modifier::BOLD) {
Intensity::Bold
} else if cell.style.modifier.contains(Modifier::DIM) {
Intensity::Half
} else {
Intensity::Normal
},
)));

self.buffered_terminal
.add_change(Change::Attribute(AttributeChange::Italic(
cell.style.modifier.contains(Modifier::ITALIC),
)));

self.buffered_terminal
.add_change(Change::Attribute(AttributeChange::Underline(
if cell.style.modifier.contains(Modifier::UNDERLINED) {
Underline::Single
} else {
Underline::None
},
)));

self.buffered_terminal
.add_change(Change::Attribute(AttributeChange::Reverse(
cell.style.modifier.contains(Modifier::REVERSED),
)));

self.buffered_terminal
.add_change(Change::Attribute(AttributeChange::Invisible(
cell.style.modifier.contains(Modifier::HIDDEN),
)));

self.buffered_terminal
.add_change(Change::Attribute(AttributeChange::StrikeThrough(
cell.style.modifier.contains(Modifier::CROSSED_OUT),
)));

self.buffered_terminal
.add_change(Change::Attribute(AttributeChange::Blink(
if cell.style.modifier.contains(Modifier::SLOW_BLINK) {
Blink::Slow
} else if cell.style.modifier.contains(Modifier::RAPID_BLINK) {
Blink::Rapid
} else {
Blink::None
},
)));

self.buffered_terminal.add_change(&cell.symbol);
}
Ok(())
}
fn hide_cursor(&mut self) -> Result<(), io::Error> {
self.buffered_terminal
.add_change(Change::CursorShape(CursorShape::Hidden));
Ok(())
}
fn show_cursor(&mut self) -> Result<(), io::Error> {
self.buffered_terminal
.add_change(Change::CursorShape(CursorShape::Default));
Ok(())
}
fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
let (x, y) = self.buffered_terminal.cursor_position();
Ok((x as u16, y as u16))
}
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
self.buffered_terminal.add_change(Change::CursorPosition {
x: Position::Absolute(x as usize),
y: Position::Absolute(y as usize),
});

Ok(())
}
fn clear(&mut self) -> Result<(), io::Error> {
self.buffered_terminal
.add_change(Change::ClearScreen(termwiz::color::ColorAttribute::Default));
Ok(())
}
fn size(&self) -> Result<Rect, io::Error> {
let (term_width, term_height) = self.buffered_terminal.dimensions();
let max = u16::max_value();
Ok(Rect::new(
0,
0,
if term_width > usize::from(max) {
max
} else {
term_width as u16
},
if term_height > usize::from(max) {
max
} else {
term_height as u16
},
))
}
fn flush(&mut self) -> Result<(), io::Error> {
self.buffered_terminal
.flush()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
Ok(())
}
}

impl Into<ColorAttribute> for Color {
fn into(self) -> ColorAttribute {
match self {
Color::Reset => ColorAttribute::Default,
Color::Black | Color::Gray | Color::DarkGray => AnsiColor::Black.into(),
Color::Red | Color::LightRed => AnsiColor::Red.into(),
Color::Green | Color::LightGreen => AnsiColor::Green.into(),
Color::Yellow | Color::LightYellow => AnsiColor::Yellow.into(),
Color::Magenta | Color::LightMagenta => AnsiColor::Purple.into(),
Color::Cyan | Color::LightCyan => ColorAttribute::TrueColorWithPaletteFallback(
RgbColor::new(0, 255, 255),
AnsiColor::Green.into(),
),
Color::White => AnsiColor::White.into(),
Color::Blue | Color::LightBlue => AnsiColor::Blue.into(),
Color::Indexed(i) => ColorAttribute::PaletteIndex(i),
Color::Rgb(r, g, b) => ColorAttribute::TrueColorWithPaletteFallback(
RgbColor::new(r, g, b),
AnsiColor::Black.into(),
),
}
}
}

0 comments on commit 13db5e5

Please sign in to comment.