From b499d4825f6d2b375ff14b5c03dee55b2294faf6 Mon Sep 17 00:00:00 2001 From: ticki Date: Sat, 2 Jul 2016 15:06:47 +0200 Subject: [PATCH] Refactor how colors are handled --- README.md | 55 ++++++++---- examples/colors.rs | 137 ------------------------------ src/color.rs | 202 +++++++++++++++++++++++++++++++-------------- src/control.rs | 8 +- src/lib.rs | 8 +- 5 files changed, 192 insertions(+), 218 deletions(-) delete mode 100644 examples/colors.rs diff --git a/README.md b/README.md index 8aff652..38b9937 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -Termion -======= +# Termion A pure Rust library for handling, manipulating and reading information about terminals. This provides a full-featured alternative to Termbox. @@ -7,18 +6,18 @@ Supports Redox and POSIX. Untested on Windows. [Documentation.](http://ticki.github.io/termion/termion/) | [Examples.](https://github.com/Ticki/termion/tree/master/examples) -A note on stability -------------------- +## A note on stability -This crate is not stable, yet. However, if you do want stability, you should specify the revision (commit hash) in your `Cargo.toml`, this way builds are complete reproducible, and won't break. +Although small breaking changes might happen, I will try my best to avoid them, +and this crate can generally be considered stable. -Features --------- +## Features - Raw mode. - 256-color mode. - Cursor movement. - Color output. +- Calculating ANSI escapes. - Text formatting. - Console size. - Control sequences. @@ -29,25 +28,51 @@ Features - Special keys events (modifiers, special keys, etc.). - Allocation-free. - Asynchronous key events. +- Carefully tested. and much more. -Usage ------ +## Example + +```rust +extern crate termion; + +use termion::{TermWrite, color, Style}; + +use std::io; + +fn main() { + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + + stdout.color(color::Red).unwrap(); + println!("Red"); + + stdout.color(color::Blue).unwrap(); + println!("Blue"); + + stdout.style(Style::Bold).unwrap(); + println!("Blue'n'Bold"); + + stdout.reset().unwrap(); + stdout.style(Style::Italic).unwrap(); + println!("Just plain italic") +} +``` + +## Usage See `examples/`, and the documentation, which can be rendered using `cargo doc`. For a more complete example, see [a minesweeper implementation](https://github.com/redox-os/games-for-redox/blob/master/src/minesweeper/main.rs), that I made for Redox using termion. - + -TODO ----- +## TODO - Mouse input -License -------- +## License -MIT. +MIT/X11. diff --git a/examples/colors.rs b/examples/colors.rs deleted file mode 100644 index 6d9280a..0000000 --- a/examples/colors.rs +++ /dev/null @@ -1,137 +0,0 @@ -extern crate termion; - -use termion::{TermWrite, Color, Style}; -use std::io::{self, Write}; - -const LINE_NUM_BG: Color = Color::Grayscale(3); -const LINE_NUM_FG: Color = Color::Grayscale(18); -const ERROR_FG: Color = Color::Grayscale(17); -const INFO_LINE: &'static str = "| "; - -fn main() { - let stdout = io::stdout(); - let mut stdout = stdout.lock(); - - stdout.color(Color::LightGreen).unwrap(); - stdout.write("-- src/test/ui/borrow-errors.rs at 82:18 --\n".as_bytes()).unwrap(); - stdout.reset().unwrap(); - - stdout.color(Color::Red).unwrap(); - stdout.style(Style::Bold).unwrap(); - stdout.write(b"error: ").unwrap(); - stdout.reset().unwrap(); - - stdout.style(Style::Bold).unwrap(); - stdout.write(b"two closures require unique access to `vec` at the same time").unwrap(); - stdout.reset().unwrap(); - - stdout.style(Style::Bold).unwrap(); - stdout.color(Color::Magenta).unwrap(); - stdout.write(b" [E0524]\n").unwrap(); - stdout.reset().unwrap(); - - stdout.color(LINE_NUM_FG).unwrap(); - stdout.bg_color(LINE_NUM_BG).unwrap(); - stdout.write(b"79 ").unwrap(); - stdout.reset().unwrap(); - - stdout.write(b" let append = |e| {\n").unwrap(); - - stdout.color(LINE_NUM_FG).unwrap(); - stdout.bg_color(LINE_NUM_BG).unwrap(); - stdout.write(INFO_LINE.as_bytes()).unwrap(); - stdout.reset().unwrap(); - stdout.color(Color::Red).unwrap(); - stdout.write(" ^^^ ".as_bytes()).unwrap(); - stdout.reset().unwrap(); - - stdout.color(ERROR_FG).unwrap(); - stdout.write(b"first closure is constructed here\n").unwrap(); - stdout.reset().unwrap(); - - stdout.color(LINE_NUM_FG).unwrap(); - stdout.bg_color(LINE_NUM_BG).unwrap(); - stdout.write(b"80 ").unwrap(); - stdout.reset().unwrap(); - - stdout.write(b" vec.push(e)\n").unwrap(); - - stdout.color(LINE_NUM_FG).unwrap(); - stdout.bg_color(LINE_NUM_BG).unwrap(); - stdout.write(INFO_LINE.as_bytes()).unwrap(); - stdout.reset().unwrap(); - stdout.color(Color::Red).unwrap(); - stdout.write(" ^^^ ".as_bytes()).unwrap(); - stdout.reset().unwrap(); - - stdout.color(ERROR_FG).unwrap(); - stdout.write(b"previous borrow occurs due to use of `vec` in closure\n").unwrap(); - stdout.reset().unwrap(); - - stdout.color(LINE_NUM_FG).unwrap(); - stdout.bg_color(LINE_NUM_BG).unwrap(); - stdout.write(b"81 ").unwrap(); - stdout.reset().unwrap(); - stdout.write(b" };\n").unwrap(); - - stdout.color(LINE_NUM_FG).unwrap(); - stdout.bg_color(LINE_NUM_BG).unwrap(); - stdout.write(b"82 ").unwrap(); - stdout.reset().unwrap(); - stdout.write(b" let append = |e| {\n").unwrap(); - - stdout.color(LINE_NUM_FG).unwrap(); - stdout.bg_color(LINE_NUM_BG).unwrap(); - stdout.write(INFO_LINE.as_bytes()).unwrap(); - stdout.reset().unwrap(); - stdout.color(Color::Red).unwrap(); - stdout.write(" ^^^ ".as_bytes()).unwrap(); - stdout.reset().unwrap(); - - stdout.color(ERROR_FG).unwrap(); - stdout.write(b"second closure is constructed here\n").unwrap(); - stdout.reset().unwrap(); - - stdout.color(LINE_NUM_FG).unwrap(); - stdout.bg_color(LINE_NUM_BG).unwrap(); - stdout.write(b"83 ").unwrap(); - stdout.reset().unwrap(); - - stdout.write(b" vec.push(e)\n").unwrap(); - - stdout.color(LINE_NUM_FG).unwrap(); - stdout.bg_color(LINE_NUM_BG).unwrap(); - stdout.write(INFO_LINE.as_bytes()).unwrap(); - stdout.reset().unwrap(); - stdout.color(Color::Red).unwrap(); - stdout.write(" ^^^ ".as_bytes()).unwrap(); - stdout.reset().unwrap(); - - stdout.color(ERROR_FG).unwrap(); - stdout.write(b"borrow occurs due to use of `vec` in closure\n").unwrap(); - stdout.reset().unwrap(); - - stdout.color(LINE_NUM_FG).unwrap(); - stdout.bg_color(LINE_NUM_BG).unwrap(); - stdout.write(b"84 ").unwrap(); - stdout.reset().unwrap(); - stdout.write(b" };\n").unwrap(); - - stdout.color(LINE_NUM_FG).unwrap(); - stdout.bg_color(LINE_NUM_BG).unwrap(); - stdout.write(b"85 ").unwrap(); - stdout.reset().unwrap(); - stdout.write(b" }\n").unwrap(); - - stdout.color(LINE_NUM_FG).unwrap(); - stdout.bg_color(LINE_NUM_BG).unwrap(); - stdout.write(INFO_LINE.as_bytes()).unwrap(); - stdout.reset().unwrap(); - stdout.color(Color::Red).unwrap(); - stdout.write(" ^ ".as_bytes()).unwrap(); - stdout.reset().unwrap(); - - stdout.color(ERROR_FG).unwrap(); - stdout.write(b"borrow from first closure ends here\n").unwrap(); - stdout.reset().unwrap(); -} diff --git a/src/color.rs b/src/color.rs index 256fe89..9d5daf5 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,6 +1,78 @@ /// A terminal color. +pub trait Color { + /// Convert this to its ANSI value. + fn to_ansi_val(self) -> u8; +} + +macro_rules! derive_color { + ($doc:expr, $name:ident, $value:expr) => { + #[doc = $doc] + pub struct $name; + + impl Color for $name { + #[inline] + fn to_ansi_val(self) -> u8 { + $value + } + } + }; +} + + +derive_color!("", Black, 0x0); +derive_color!("", Red, 0x1); +derive_color!("", Green, 0x2); +derive_color!("", Yellow, 0x3); +derive_color!("", Blue, 0x4); +derive_color!("", Magenta, 0x5); +derive_color!("", Cyan, 0x6); +derive_color!("", White, 0x7); +derive_color!("", LightBlack, 0x8); +derive_color!("", LightRed, 0x9); +derive_color!("", LightGreen, 0xA); +derive_color!("", LightYellow, 0xB); +derive_color!("", LightBlue, 0xC); +derive_color!("", LightMagenta, 0xD); +derive_color!("", LightCyan, 0xE); +derive_color!("", LightWhite, 0xF); + +/// 216-color (r, g, b ≤ 5) RGB. +pub fn rgb(r: u8, g: u8, b: u8) -> AnsiValue { + debug_assert!(r <= 5, "Red color fragment (r = {}) is out of bound. Make sure r ≤ 5.", r); + debug_assert!(g <= 5, "Green color fragment (g = {}) is out of bound. Make sure g ≤ 5.", g); + debug_assert!(b <= 5, "Blue color fragment (b = {}) is out of bound. Make sure b ≤ 5.", b); + + AnsiValue(16 + 36 * r + 6 * g + b) +} + +/// Grayscale color. +/// +/// There are 24 shades of gray. +pub fn grayscale(shade: u8) -> AnsiValue { + // Unfortunately, there are a little less than fifty shades. + debug_assert!(shade < 24, "Grayscale out of bound (shade = {}). There are only 24 shades of \ + gray.", shade); + + AnsiValue(0xE8 + shade) +} + +/// An arbitrary ANSI color value. +#[derive(Clone, Copy)] +pub struct AnsiValue(pub u8); + +impl Color for AnsiValue { + #[inline] + fn to_ansi_val(self) -> u8 { + self.0 + } +} + +/// A color palette. +/// +/// This should generally only be used when the color is runtime determined. Otherwise, use the +/// color types, which resolves the value at compile time. #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -pub enum Color { +pub enum Palette { /// Black. Black, /// Red. @@ -39,53 +111,27 @@ pub enum Color { Grayscale(u8), } -use Color::*; - -impl Color { - /// Get the corresponding ANSI value. - /// - /// Panics - /// ====== - /// - /// This method will panic in debug mode, if `self` is invalid (that is, the values are out of - /// bound). - pub fn to_ansi_val(self) -> u8 { - self.debug_check(); - +impl Color for Palette { + fn to_ansi_val(self) -> u8 { match self { - Black => 0x0, - Red => 0x1, - Green => 0x2, - Yellow => 0x3, - Blue => 0x4, - Magenta => 0x5, - Cyan => 0x6, - White => 0x7, - LightBlack => 0x8, - LightRed => 0x9, - LightGreen => 0xA, - LightYellow => 0xB, - LightBlue => 0xC, - LightMagenta => 0xD, - LightCyan => 0xE, - LightWhite => 0xF, - Rgb(r, g, b) => 16 + 36 * r + 6 * g + b, - Grayscale(shade) => 0xE8 + shade, - } - } - - fn debug_check(self) { - match self { - Rgb(r, g, b) => { - debug_assert!(r <= 5, "Red color fragment (r = {}) is out of bound. Make sure r ≤ 5.", r); - debug_assert!(g <= 5, "Green color fragment (g = {}) is out of bound. Make sure g ≤ 5.", g); - debug_assert!(b <= 5, "Blue color fragment (b = {}) is out of bound. Make sure b ≤ 5.", b); - }, - Grayscale(shade) => { - // Unfortunately, there are a little less than fifty shades. - debug_assert!(shade < 24, "Grayscale out of bound (shade = {}). There are only 24 shades of gray.", shade); - }, - _ => {}, + Palette::Black => Black.to_ansi_val(), + Palette::Red => Red.to_ansi_val(), + Palette::Green => Green.to_ansi_val(), + Palette::Yellow => Yellow.to_ansi_val(), + Palette::Blue => Blue.to_ansi_val(), + Palette::Magenta => Magenta.to_ansi_val(), + Palette::Cyan => Cyan.to_ansi_val(), + Palette::White => White.to_ansi_val(), + Palette::LightBlack => LightBlack.to_ansi_val(), + Palette::LightRed => LightRed.to_ansi_val(), + Palette::LightGreen => LightGreen.to_ansi_val(), + Palette::LightYellow => LightYellow.to_ansi_val(), + Palette::LightBlue => LightBlue.to_ansi_val(), + Palette::LightMagenta => LightMagenta.to_ansi_val(), + Palette::LightCyan => LightCyan.to_ansi_val(), + Palette::LightWhite => LightWhite.to_ansi_val(), + Palette::Rgb(r, g, b) => rgb(r, g, b).to_ansi_val(), + Palette::Grayscale(shade) => grayscale(shade).to_ansi_val(), } } } @@ -96,44 +142,78 @@ mod test { #[test] fn test_rgb() { - assert_eq!(Color::Rgb(2, 3, 4).to_ansi_val(), 110); - assert_eq!(Color::Rgb(2, 1, 4).to_ansi_val(), 98); - assert_eq!(Color::Rgb(5, 1, 4).to_ansi_val(), 206); + assert_eq!(rgb(2, 3, 4).to_ansi_val(), 110); + assert_eq!(rgb(2, 1, 4).to_ansi_val(), 98); + assert_eq!(rgb(5, 1, 4).to_ansi_val(), 206); } + #[test] fn test_grayscale() { - assert_eq!(Color::Grayscale(2).to_ansi_val(), 234); - assert_eq!(Color::Grayscale(5).to_ansi_val(), 237); + assert_eq!(grayscale(2).to_ansi_val(), 234); + assert_eq!(grayscale(5).to_ansi_val(), 237); } + #[test] fn test_normal() { - assert_eq!(Color::Black.to_ansi_val(), 0); - assert_eq!(Color::Green.to_ansi_val(), 2); - assert_eq!(Color::White.to_ansi_val(), 7); + assert_eq!(Black.to_ansi_val(), 0); + assert_eq!(Green.to_ansi_val(), 2); + assert_eq!(White.to_ansi_val(), 7); } + #[test] fn test_hi() { - assert_eq!(Color::LightRed.to_ansi_val(), 9); - assert_eq!(Color::LightCyan.to_ansi_val(), 0xE); - assert_eq!(Color::LightWhite.to_ansi_val(), 0xF); + assert_eq!(LightRed.to_ansi_val(), 9); + assert_eq!(LightCyan.to_ansi_val(), 0xE); + assert_eq!(LightWhite.to_ansi_val(), 0xF); + } + + #[test] + fn test_palette() { + assert_eq!(Palette::Black.to_ansi_val(), Black.to_ansi_val()); + assert_eq!(Palette::Red.to_ansi_val(), Red.to_ansi_val()); + assert_eq!(Palette::LightBlue.to_ansi_val(), LightBlue.to_ansi_val()); + assert_eq!(Palette::Rgb(2, 2, 2).to_ansi_val(), rgb(2, 2, 2).to_ansi_val()); } #[cfg(debug)] #[should_panic] #[test] fn test_bound_check_rgb() { - Color::Rgb(3, 9, 1).debug_check(); + rgb(3, 9, 1); } + #[cfg(debug)] #[should_panic] #[test] fn test_bound_check_rgb_2() { - Color::Rgb(3, 6, 1).debug_check(); + rgb(3, 6, 1); } + #[cfg(debug)] #[should_panic] #[test] fn test_bound_check_grayscale() { - Color::Grayscale(25).debug_check(); + grayscale(25); + } + + #[cfg(debug)] + #[should_panic] + #[test] + fn test_palette_rgb_bound_check_1() { + Palette::Rgb(3, 6, 1).to_ansi_val(); + } + + #[cfg(debug)] + #[should_panic] + #[test] + fn test_palette_rgb_bound_check_2() { + Palette::Rgb(3, 9, 1).to_ansi_val(); + } + + #[cfg(debug)] + #[should_panic] + #[test] + fn test_palette_grayscale_bound_check_2() { + Palette::Grayscale(25).to_ansi_val(); } } diff --git a/src/control.rs b/src/control.rs index 999d790..7a95c3a 100644 --- a/src/control.rs +++ b/src/control.rs @@ -1,5 +1,7 @@ use std::io::{self, Write}; -use {Color, Style}; + +use Style; +use color::Color; /// Extension to the `Write` trait. /// @@ -130,7 +132,7 @@ pub trait TermWrite { } /// Set foreground color. - fn color(&mut self, color: Color) -> io::Result { + fn color(&mut self, color: C) -> io::Result { let ansi = color.to_ansi_val(); self.csi(&[ b'3', @@ -146,7 +148,7 @@ pub trait TermWrite { } /// Set background color. - fn bg_color(&mut self, color: Color) -> io::Result { + fn bg_color(&mut self, color: C) -> io::Result { let ansi = color.to_ansi_val(); self.csi(&[ b'4', diff --git a/src/lib.rs b/src/lib.rs index a9f06b0..1ce93ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,8 +31,12 @@ pub use raw::{IntoRawMode, RawTerminal}; mod size; pub use size::terminal_size; -mod color; -pub use color::Color; +/// ANSI colors. +pub mod color; + +/// Deprecated reexport. +#[deprecated] +pub use color::Palette as Color; mod style; pub use style::Style;