From e358c58826b1732763970848c0c6d77f12ef5e5a Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sat, 6 Mar 2021 16:47:16 -0500 Subject: basic sketch of an implementation --- .rustfmt.toml | 2 + Cargo.toml | 5 +- examples/basic.rs | 19 ++++++++ src/color.rs | 16 ++++++ src/lib.rs | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 178 insertions(+), 7 deletions(-) create mode 100644 .rustfmt.toml create mode 100644 examples/basic.rs create mode 100644 src/color.rs diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..55b0b14 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,2 @@ +edition = "2018" +max_width = 78 diff --git a/Cargo.toml b/Cargo.toml index a6962d2..1007f0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["Jesse Luehrs "] edition = "2018" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] +itoa = "0.4" +terminal_size = "0.1" +vt100 = "0.9" diff --git a/examples/basic.rs b/examples/basic.rs new file mode 100644 index 0000000..1026834 --- /dev/null +++ b/examples/basic.rs @@ -0,0 +1,19 @@ +fn main() { + let mut tm = textmode::Textmode::new().unwrap(); + + tm.move_to(5, 5); + tm.write_str("foo"); + std::thread::sleep(std::time::Duration::from_secs(2)); + tm.refresh().unwrap(); + std::thread::sleep(std::time::Duration::from_secs(2)); + + tm.move_to(8, 8); + tm.set_fgcolor(textmode::color::GREEN); + tm.write_str("bar"); + tm.move_to(11, 11); + tm.set_fgcolor(vt100::Color::Default); + tm.write_str("baz"); + std::thread::sleep(std::time::Duration::from_secs(2)); + tm.refresh().unwrap(); + std::thread::sleep(std::time::Duration::from_secs(2)); +} diff --git a/src/color.rs b/src/color.rs new file mode 100644 index 0000000..c2ba2b3 --- /dev/null +++ b/src/color.rs @@ -0,0 +1,16 @@ +pub const BLACK: vt100::Color = vt100::Color::Idx(0); +pub const RED: vt100::Color = vt100::Color::Idx(1); +pub const GREEN: vt100::Color = vt100::Color::Idx(2); +pub const YELLOW: vt100::Color = vt100::Color::Idx(3); +pub const BLUE: vt100::Color = vt100::Color::Idx(4); +pub const MAGENTA: vt100::Color = vt100::Color::Idx(5); +pub const CYAN: vt100::Color = vt100::Color::Idx(6); +pub const LIGHTGREY: vt100::Color = vt100::Color::Idx(7); +pub const DARKGREY: vt100::Color = vt100::Color::Idx(8); +pub const LIGHTRED: vt100::Color = vt100::Color::Idx(9); +pub const LIGHTGREEN: vt100::Color = vt100::Color::Idx(10); +pub const LIGHTYELLOW: vt100::Color = vt100::Color::Idx(11); +pub const LIGHTBLUE: vt100::Color = vt100::Color::Idx(12); +pub const LIGHTMAGENTA: vt100::Color = vt100::Color::Idx(13); +pub const LIGHTCYAN: vt100::Color = vt100::Color::Idx(14); +pub const WHITE: vt100::Color = vt100::Color::Idx(15); diff --git a/src/lib.rs b/src/lib.rs index 31e1bb2..39c201b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,140 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); +use std::io::Write as _; + +pub mod color; + +pub struct Textmode { + cur: vt100::Parser, + next: vt100::Parser, +} + +impl Textmode { + pub fn new() -> std::io::Result { + let (rows, cols) = match terminal_size::terminal_size() { + Some((terminal_size::Width(w), terminal_size::Height(h))) => { + (h, w) + } + _ => (24, 80), + }; + let cur = vt100::Parser::new(rows, cols, 0); + let next = vt100::Parser::new(rows, cols, 0); + + let self_ = Self { cur, next }; + self_.write_stdout(b"\x1b7\x1b[?47h\x1b[2J\x1b[H\x1b[?25h")?; + Ok(self_) + } + + pub fn cursor_position(&self) -> (u16, u16) { + self.next.screen().cursor_position() + } + + pub fn write(&mut self, buf: &[u8]) { + self.next.process(buf); + } + + pub fn refresh(&mut self) -> std::io::Result<()> { + let diff = self.next.screen().contents_diff(self.cur.screen()); + self.write_stdout(&diff)?; + self.cur.process(&diff); + Ok(()) + } + + pub fn set_size(&mut self, rows: u16, cols: u16) { + self.cur.set_size(rows, cols); + self.next.set_size(rows, cols); + } + + pub fn write_str(&mut self, text: &str) { + self.write(text.as_bytes()); + } + + pub fn move_to(&mut self, row: u16, col: u16) { + self.write(b"\x1b["); + self.write_u16(row); + self.write(b";"); + self.write_u16(col); + self.write(b"H"); + } + + pub fn clear(&mut self) { + self.write(b"\x1b[2J"); + } + + pub fn set_fgcolor(&mut self, color: vt100::Color) { + match color { + vt100::Color::Default => { + self.write(b"\x1b[39m"); + } + vt100::Color::Idx(i) => { + if i < 8 { + self.write(b"\x1b["); + self.write_u8(30 + i); + self.write(b"m"); + } else { + self.write(b"\x1b[38;5;"); + self.write_u8(i); + self.write(b"m"); + } + } + vt100::Color::Rgb(r, g, b) => { + self.write(b"\x1b[38;2;"); + self.write_u8(r); + self.write(b";"); + self.write_u8(g); + self.write(b";"); + self.write_u8(b); + self.write(b"m"); + } + } + } + + pub fn set_bgcolor(&mut self, color: vt100::Color) { + match color { + vt100::Color::Default => { + self.write(b"\x1b[49m"); + } + vt100::Color::Idx(i) => { + if i < 8 { + self.write(b"\x1b["); + self.write_u8(40 + i); + self.write(b"m"); + } else { + self.write(b"\x1b[48;5;"); + self.write_u8(i); + self.write(b"m"); + } + } + vt100::Color::Rgb(r, g, b) => { + self.write(b"\x1b[48;2;"); + self.write_u8(r); + self.write(b";"); + self.write_u8(g); + self.write(b";"); + self.write_u8(b); + self.write(b"m"); + } + } + } + + fn write_u16(&mut self, i: u16) { + // unwrap is fine because vt100::Parser::write can never fail + itoa::write(&mut self.next, i).unwrap(); + } + + fn write_u8(&mut self, i: u8) { + // unwrap is fine because vt100::Parser::write can never fail + itoa::write(&mut self.next, i).unwrap(); + } + + fn write_stdout(&self, buf: &[u8]) -> std::io::Result<()> { + let mut stdout = std::io::stdout(); + stdout.write_all(buf)?; + stdout.flush()?; + Ok(()) + } +} + +impl Drop for Textmode { + fn drop(&mut self) { + let _ = self.write_stdout(b"\x1b[?47l\x1b8\x1b[?25h"); } } -- cgit v1.2.3-54-g00ecf