mirror of
https://github.com/ratatui/ratatui.git
synced 2025-10-02 15:25:54 +00:00
feat: add initial support for crossterm
This commit is contained in:
parent
3d63f9607f
commit
40bad7a718
@ -27,6 +27,7 @@ unicode-segmentation = "1.2.0"
|
|||||||
unicode-width = "0.1.4"
|
unicode-width = "0.1.4"
|
||||||
termion = { version = "1.5.1", optional = true }
|
termion = { version = "1.5.1", optional = true }
|
||||||
rustbox = { version = "0.11.0", optional = true }
|
rustbox = { version = "0.11.0", optional = true }
|
||||||
|
crossterm = { version = "0.4", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
stderrlog = "0.3.0"
|
stderrlog = "0.3.0"
|
||||||
@ -36,3 +37,8 @@ rand = "0.4.2"
|
|||||||
name = "rustbox"
|
name = "rustbox"
|
||||||
path = "examples/rustbox.rs"
|
path = "examples/rustbox.rs"
|
||||||
required-features = ["rustbox"]
|
required-features = ["rustbox"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "crossterm"
|
||||||
|
path = "examples/crossterm.rs"
|
||||||
|
required-features = ["crossterm"]
|
||||||
|
50
examples/crossterm.rs
Normal file
50
examples/crossterm.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
extern crate crossterm;
|
||||||
|
extern crate tui;
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use tui::backend::CrosstermBackend;
|
||||||
|
use tui::layout::{Constraint, Direction, Layout};
|
||||||
|
use tui::style::{Color, Modifier, Style};
|
||||||
|
use tui::widgets::{Block, Borders, Paragraph, Text, Widget};
|
||||||
|
use tui::Terminal;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut terminal = Terminal::new(CrosstermBackend::new()).unwrap();
|
||||||
|
terminal.clear().unwrap();
|
||||||
|
terminal.hide_cursor().unwrap();
|
||||||
|
draw(&mut terminal).unwrap();
|
||||||
|
loop {
|
||||||
|
{
|
||||||
|
let input = crossterm::input(terminal.backend().screen());
|
||||||
|
match input.read_char() {
|
||||||
|
Ok(c) => if c == 'q' {
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
Err(e) => panic!("{}", e.description()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
draw(&mut terminal).unwrap();
|
||||||
|
}
|
||||||
|
terminal.show_cursor().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(t: &mut Terminal<CrosstermBackend>) -> io::Result<()> {
|
||||||
|
let size = t.size()?;
|
||||||
|
t.draw(|mut f| {
|
||||||
|
let text = [
|
||||||
|
Text::Data("It "),
|
||||||
|
Text::StyledData("works", Style::default().fg(Color::Yellow)),
|
||||||
|
];
|
||||||
|
Paragraph::new(text.iter())
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.title("Crossterm Backend")
|
||||||
|
.title_style(Style::default().fg(Color::Yellow).modifier(Modifier::Bold))
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_style(Style::default().fg(Color::Magenta)),
|
||||||
|
)
|
||||||
|
.render(&mut f, size);
|
||||||
|
})
|
||||||
|
}
|
128
src/backend/crossterm.rs
Normal file
128
src/backend/crossterm.rs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
extern crate crossterm;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use backend::Backend;
|
||||||
|
use buffer::Cell;
|
||||||
|
use layout::Rect;
|
||||||
|
use style::{Color, Modifier};
|
||||||
|
|
||||||
|
pub struct CrosstermBackend {
|
||||||
|
screen: crossterm::Screen,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CrosstermBackend {
|
||||||
|
pub fn new() -> CrosstermBackend {
|
||||||
|
CrosstermBackend {
|
||||||
|
screen: crossterm::Screen::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn screen(&self) -> &crossterm::Screen {
|
||||||
|
&self.screen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Backend for CrosstermBackend {
|
||||||
|
fn clear(&mut self) -> io::Result<()> {
|
||||||
|
let terminal = crossterm::terminal::terminal(&self.screen);
|
||||||
|
terminal.clear(crossterm::terminal::ClearType::All);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hide_cursor(&mut self) -> io::Result<()> {
|
||||||
|
let cursor = crossterm::cursor(&self.screen);
|
||||||
|
cursor.hide();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_cursor(&mut self) -> io::Result<()> {
|
||||||
|
let cursor = crossterm::cursor(&self.screen);
|
||||||
|
cursor.show();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> io::Result<Rect> {
|
||||||
|
let terminal = crossterm::terminal::terminal(&self.screen);
|
||||||
|
let (width, height) = terminal.terminal_size();
|
||||||
|
Ok(Rect {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
||||||
|
{
|
||||||
|
let cursor = crossterm::cursor(&self.screen);
|
||||||
|
let mut last_y = 0;
|
||||||
|
let mut last_x = 0;
|
||||||
|
for (x, y, cell) in content {
|
||||||
|
if y != last_y || x != last_x + 1 {
|
||||||
|
cursor.goto(x + 1, y + 1);
|
||||||
|
}
|
||||||
|
last_x = x;
|
||||||
|
last_y = y;
|
||||||
|
|
||||||
|
let mut s = crossterm::style(&cell.symbol);
|
||||||
|
if let Some(color) = cell.style.fg.into() {
|
||||||
|
s = s.with(color)
|
||||||
|
}
|
||||||
|
if let Some(color) = cell.style.bg.into() {
|
||||||
|
s = s.on(color)
|
||||||
|
}
|
||||||
|
if let Some(attr) = cell.style.modifier.into() {
|
||||||
|
s = s.attr(attr)
|
||||||
|
}
|
||||||
|
s.paint(&self.screen);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Color> for Option<crossterm::Color> {
|
||||||
|
fn from(color: Color) -> Option<crossterm::Color> {
|
||||||
|
match color {
|
||||||
|
Color::Reset => None,
|
||||||
|
Color::Black => Some(crossterm::Color::Black),
|
||||||
|
Color::Red => Some(crossterm::Color::DarkRed),
|
||||||
|
Color::Green => Some(crossterm::Color::DarkGreen),
|
||||||
|
Color::Yellow => Some(crossterm::Color::DarkYellow),
|
||||||
|
Color::Blue => Some(crossterm::Color::DarkBlue),
|
||||||
|
Color::Magenta => Some(crossterm::Color::DarkMagenta),
|
||||||
|
Color::Cyan => Some(crossterm::Color::DarkCyan),
|
||||||
|
Color::Gray => Some(crossterm::Color::Grey),
|
||||||
|
Color::DarkGray => Some(crossterm::Color::Grey),
|
||||||
|
Color::LightRed => Some(crossterm::Color::Red),
|
||||||
|
Color::LightGreen => Some(crossterm::Color::Green),
|
||||||
|
Color::LightBlue => Some(crossterm::Color::Blue),
|
||||||
|
Color::LightYellow => Some(crossterm::Color::Yellow),
|
||||||
|
Color::LightMagenta => Some(crossterm::Color::Magenta),
|
||||||
|
Color::LightCyan => Some(crossterm::Color::Cyan),
|
||||||
|
Color::White => Some(crossterm::Color::White),
|
||||||
|
Color::Rgb(r, g, b) => Some(crossterm::Color::Rgb { r, g, b }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Modifier> for Option<crossterm::Attribute> {
|
||||||
|
fn from(modifier: Modifier) -> Option<crossterm::Attribute> {
|
||||||
|
match modifier {
|
||||||
|
Modifier::Blink => Some(crossterm::Attribute::SlowBlink),
|
||||||
|
Modifier::Bold => Some(crossterm::Attribute::Bold),
|
||||||
|
Modifier::CrossedOut => Some(crossterm::Attribute::CrossedOut),
|
||||||
|
Modifier::Faint => Some(crossterm::Attribute::Dim),
|
||||||
|
Modifier::Invert => Some(crossterm::Attribute::Reverse),
|
||||||
|
Modifier::Italic => Some(crossterm::Attribute::Italic),
|
||||||
|
Modifier::Underline => Some(crossterm::Attribute::Underlined),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,11 @@ mod termion;
|
|||||||
#[cfg(feature = "termion")]
|
#[cfg(feature = "termion")]
|
||||||
pub use self::termion::{AlternateScreenBackend, MouseBackend, RawBackend, TermionBackend};
|
pub use self::termion::{AlternateScreenBackend, MouseBackend, RawBackend, TermionBackend};
|
||||||
|
|
||||||
|
#[cfg(feature = "crossterm")]
|
||||||
|
mod crossterm;
|
||||||
|
#[cfg(feature = "crossterm")]
|
||||||
|
pub use self::crossterm::{CrosstermBackend};
|
||||||
|
|
||||||
pub trait Backend {
|
pub trait Backend {
|
||||||
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
|
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
|
||||||
where
|
where
|
||||||
|
@ -141,7 +141,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the size of the terminal
|
/// Return the size of the terminal
|
||||||
fn size(&self) -> Result<Rect, io::Error> {
|
fn size(&self) -> io::Result<Rect> {
|
||||||
let terminal = try!(termion::terminal_size());
|
let terminal = try!(termion::terminal_size());
|
||||||
Ok(Rect {
|
Ok(Rect {
|
||||||
x: 0,
|
x: 0,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user