From d4c1d51e36626b1682b96e746bb32632dadcac2c Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sat, 15 Oct 2016 15:56:27 -0700 Subject: [PATCH] Make colors configurable from file Added solarized dark color scheme for testing purposes. Resolves #1. --- alacritty.yml | 79 ++++++++++++++------- src/config.rs | 163 +++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 26 +++++-- src/renderer/mod.rs | 18 ++--- src/term.rs | 119 ++++++++++++++------------------ 5 files changed, 300 insertions(+), 105 deletions(-) diff --git a/alacritty.yml b/alacritty.yml index 7e3d81a1..56c3b686 100644 --- a/alacritty.yml +++ b/alacritty.yml @@ -7,47 +7,78 @@ dpi: # Font configuration font: - family: DejaVu Sans Mono - style: Book + family: Menlo + style: Regular + bold_style: Bold + italic_style: Italic # Point size of the font size: 11.0 # Offset is the extra space around each character. offset.y can be thought of # as modifying the linespacing, and offset.x as modifying the letter spacing. offset: - x: 2.0 - y: -7.0 + x: -1.0 + y: 1.0 # Should display the render timer render_timer: false -# Colors +# Colors (Tomorrow Night Bright) colors: # Default colors - default: - background: 0x000000 - foreground: 0xeaeaea + primary: + background: '0x000000' + foreground: '0xeaeaea' # Normal colors normal: - black: 0x000000 - red: 0xd54e53 - green: 0xb9ca4a - yellow: 0xe6c547 - blue: 0x7aa6da - magenta: 0xc397d8 - cyan: 0x70c0ba - white: 0x424242 + black: '0x000000' + red: '0xd54e53' + green: '0xb9ca4a' + yellow: '0xe6c547' + blue: '0x7aa6da' + magenta: '0xc397d8' + cyan: '0x70c0ba' + white: '0x424242' # Bright colors bright: - black: 0x666666 - red: 0xff3334 - green: 0x9ec400 - yellow: 0xe7c547 - blue: 0x7aa6da - magenta: 0xb77ee0 - cyan: 0x54ced6 - white: 0x2a2a2a + black: '0x666666' + red: '0xff3334' + green: '0x9ec400' + yellow: '0xe7c547' + blue: '0x7aa6da' + magenta: '0xb77ee0' + cyan: '0x54ced6' + white: '0x2a2a2a' # Display tabs using this many cells tabspaces: 8 + +# Colors (Solarized Dark) +# colors: +# # Default colors +# primary: +# background: '0x002b36' +# foreground: '0x839496' +# +# # Normal colors +# normal: +# black: '0x073642' +# red: '0xdc322f' +# green: '0x859900' +# yellow: '0xb58900' +# blue: '0x268bd2' +# magenta: '0xd33682' +# cyan: '0x2aa198' +# white: '0xeee8d5' +# +# # Bright colors +# bright: +# black: '0x002b36' +# red: '0xcb4b16' +# green: '0x586e75' +# yellow: '0x657b83' +# blue: '0x839496' +# magenta: '0x6c71c4' +# cyan: '0x93a1a1' +# white: '0xfdf6e3' diff --git a/src/config.rs b/src/config.rs index 3c4d25c6..56f96e80 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,9 +8,10 @@ use std::fs; use std::io::{self, Read}; use std::path::{Path, PathBuf}; +use ::Rgb; use font::Size; use serde_yaml; -use serde; +use serde::{self, Error as SerdeError}; /// Top-level config type #[derive(Debug, Deserialize, Default)] @@ -26,6 +27,10 @@ pub struct Config { /// Should show render timer #[serde(default)] render_timer: bool, + + /// The standard ANSI colors to use + #[serde(default)] + colors: Colors, } /// Errors occurring during config loading @@ -44,6 +49,124 @@ pub enum Error { Yaml(serde_yaml::Error), } +#[derive(Debug, Deserialize)] +pub struct Colors { + primary: PrimaryColors, + normal: AnsiColors, + bright: AnsiColors, +} + +#[derive(Debug, Deserialize)] +pub struct PrimaryColors { + background: Rgb, + foreground: Rgb, +} + +impl Default for Colors { + fn default() -> Colors { + Colors { + primary: PrimaryColors { + background: Rgb { r: 0, g: 0, b: 0 }, + foreground: Rgb { r: 0xea, g: 0xea, b: 0xea }, + }, + normal: AnsiColors { + black: Rgb {r: 0x00, g: 0x00, b: 0x00}, + red: Rgb {r: 0xd5, g: 0x4e, b: 0x53}, + green: Rgb {r: 0xb9, g: 0xca, b: 0x4a}, + yellow: Rgb {r: 0xe6, g: 0xc5, b: 0x47}, + blue: Rgb {r: 0x7a, g: 0xa6, b: 0xda}, + magenta: Rgb {r: 0xc3, g: 0x97, b: 0xd8}, + cyan: Rgb {r: 0x70, g: 0xc0, b: 0xba}, + white: Rgb {r: 0x42, g: 0x42, b: 0x42}, + }, + bright: AnsiColors { + black: Rgb {r: 0x66, g: 0x66, b: 0x66}, + red: Rgb {r: 0xff, g: 0x33, b: 0x34}, + green: Rgb {r: 0x9e, g: 0xc4, b: 0x00}, + yellow: Rgb {r: 0xe7, g: 0xc5, b: 0x47}, + blue: Rgb {r: 0x7a, g: 0xa6, b: 0xda}, + magenta: Rgb {r: 0xb7, g: 0x7e, b: 0xe0}, + cyan: Rgb {r: 0x54, g: 0xce, b: 0xd6}, + white: Rgb {r: 0x2a, g: 0x2a, b: 0x2a}, + } + } + } +} + +/// The normal or bright colors section of config +#[derive(Debug, Deserialize)] +pub struct AnsiColors { + black: Rgb, + red: Rgb, + green: Rgb, + yellow: Rgb, + blue: Rgb, + magenta: Rgb, + cyan: Rgb, + white: Rgb, +} + +impl serde::de::Deserialize for Rgb { + fn deserialize(deserializer: &mut D) -> ::std::result::Result + where D: serde::de::Deserializer + { + use std::marker::PhantomData; + + struct StringVisitor<__D> { + _marker: PhantomData<__D>, + } + + impl<__D> ::serde::de::Visitor for StringVisitor<__D> + where __D: ::serde::de::Deserializer + { + type Value = String; + + fn visit_str(&mut self, value: &str) -> ::std::result::Result + where E: ::serde::de::Error + { + Ok(value.to_owned()) + } + } + + deserializer + .deserialize_f64(StringVisitor::{ _marker: PhantomData }) + .and_then(|v| { + Rgb::from_str(&v[..]) + .map_err(|_| D::Error::custom("failed to parse rgb; expect 0xrrggbb")) + }) + } +} + +impl Rgb { + fn from_str(s: &str) -> ::std::result::Result { + let mut chars = s.chars(); + let mut rgb = Rgb::default(); + + macro_rules! component { + ($($c:ident),*) => { + $( + match chars.next().unwrap().to_digit(16) { + Some(val) => rgb.$c = (val as u8) << 4, + None => return Err(()) + } + + match chars.next().unwrap().to_digit(16) { + Some(val) => rgb.$c |= val as u8, + None => return Err(()) + } + )* + } + } + + if chars.next().unwrap() != '0' { return Err(()); } + if chars.next().unwrap() != 'x' { return Err(()); } + + component!(r, g, b); + + Ok(rgb) + } +} + impl ::std::error::Error for Error { fn cause(&self) -> Option<&::std::error::Error> { match *self { @@ -132,6 +255,44 @@ impl Config { } } + /// Get list of colors + /// + /// The ordering returned here is expected by the terminal. Colors are simply indexed in this + /// array for performance. + pub fn color_list(&self) -> [Rgb; 16] { + let colors = &self.colors; + + [ + // Normals + colors.normal.black, + colors.normal.red, + colors.normal.green, + colors.normal.yellow, + colors.normal.blue, + colors.normal.magenta, + colors.normal.cyan, + colors.normal.white, + + // Brights + colors.bright.black, + colors.bright.red, + colors.bright.green, + colors.bright.yellow, + colors.bright.blue, + colors.bright.magenta, + colors.bright.cyan, + colors.bright.white, + ] + } + + pub fn fg_color(&self) -> Rgb { + self.colors.primary.foreground + } + + pub fn bg_color(&self) -> Rgb { + self.colors.primary.background + } + /// Get font config #[inline] pub fn font(&self) -> &Font { diff --git a/src/main.rs b/src/main.rs index afc60a66..9639fd29 100644 --- a/src/main.rs +++ b/src/main.rs @@ -179,7 +179,13 @@ fn main() { println!("Cell Size: ({} x {})", cell_width, cell_height); - let terminal = Term::new(width as f32, height as f32, cell_width as f32, cell_height as f32); + let terminal = Term::new( + &config, + width as f32, + height as f32, + cell_width as f32, + cell_height as f32 + ); let pty_io = terminal.tty().reader(); let (tx, rx) = mpsc::channel(); @@ -208,6 +214,7 @@ fn main() { window.clone(), renderer, glyph_cache, + config.bg_color(), render_timer, rx ); @@ -241,13 +248,14 @@ fn main() { println!("Goodbye"); } - - struct Display { window: Arc, renderer: QuadRenderer, glyph_cache: GlyphCache, render_timer: bool, + clear_red: f32, + clear_blue: f32, + clear_green: f32, rx: mpsc::Receiver<(u32, u32)>, meter: Meter, } @@ -256,6 +264,7 @@ impl Display { pub fn new(window: Arc, renderer: QuadRenderer, glyph_cache: GlyphCache, + clear_color: Rgb, render_timer: bool, rx: mpsc::Receiver<(u32, u32)>) -> Display @@ -265,6 +274,9 @@ impl Display { renderer: renderer, glyph_cache: glyph_cache, render_timer: render_timer, + clear_red: clear_color.r as f32 / 255.0, + clear_blue: clear_color.g as f32 / 255.0, + clear_green: clear_color.b as f32 / 255.0, rx: rx, meter: Meter::new(), } @@ -285,7 +297,7 @@ impl Display { // TODO should be built into renderer unsafe { - gl::ClearColor(0.0, 0.0, 0.00, 1.0); + gl::ClearColor(self.clear_red, self.clear_blue, self.clear_green, 1.0); gl::Clear(gl::COLOR_BUFFER_BIT); } @@ -310,7 +322,8 @@ impl Display { let size_info = terminal.size_info().clone(); self.renderer.with_api(&size_info, |mut api| { // Draw the grid - api.render_grid(&terminal.render_grid(), glyph_cache); + let bg = terminal.bg; + api.render_grid(&bg, &terminal.render_grid(), glyph_cache); }); } @@ -319,7 +332,8 @@ impl Display { let timing = format!("{:.3} usec", self.meter.average()); let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 }; self.renderer.with_api(terminal.size_info(), |mut api| { - api.render_string(&timing[..], glyph_cache, &color); + let bg = terminal.bg; + api.render_string(&bg, &timing[..], glyph_cache, &color); }); } } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 23157456..7ddf1d01 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -610,11 +610,13 @@ impl<'a> RenderApi<'a> { } /// Render a string in a predefined location. Used for printing render time for profiling and /// optimization. - pub fn render_string(&mut self, - s: &str, - glyph_cache: &mut GlyphCache, - color: &Rgb) - { + pub fn render_string( + &mut self, + bg: &Rgb, + s: &str, + glyph_cache: &mut GlyphCache, + color: &Rgb, + ) { let row = 40.0; let mut col = 100.0; @@ -629,7 +631,7 @@ impl<'a> RenderApi<'a> { let cell = Cell { c: c, fg: *color, - bg: term::DEFAULT_BG, + bg: *bg, flags: cell::INVERSE, }; self.add_render_item(row, col, &cell, glyph); @@ -656,12 +658,12 @@ impl<'a> RenderApi<'a> { } } - pub fn render_grid(&mut self, grid: &Grid, glyph_cache: &mut GlyphCache) { + pub fn render_grid(&mut self, bg: &Rgb, grid: &Grid, glyph_cache: &mut GlyphCache) { for (i, line) in grid.lines().enumerate() { for (j, cell) in line.cells().enumerate() { // Skip empty cells if cell.c == ' ' && - cell.bg == term::DEFAULT_BG && + cell.bg == *bg && !cell.flags.contains(cell::INVERSE) { continue; diff --git a/src/term.rs b/src/term.rs index 7a80c6fb..898251d7 100644 --- a/src/term.rs +++ b/src/term.rs @@ -21,6 +21,7 @@ use ansi::{self, Attr, Handler}; use grid::{Grid, ClearRegion}; use index::{Cursor, Column, Line}; use tty; +use config::Config; use ::Rgb; @@ -89,7 +90,7 @@ pub mod cell { } } - #[derive(Clone, Debug)] + #[derive(Clone, Debug, Copy)] pub struct Cell { pub c: char, pub fg: Rgb, @@ -123,28 +124,6 @@ pub mod cell { pub use self::cell::Cell; -/// tomorrow night bright -/// -/// because contrast -pub static COLORS: &'static [Rgb] = &[ - Rgb {r: 0x00, g: 0x00, b: 0x00}, // Black - Rgb {r: 0xd5, g: 0x4e, b: 0x53}, // Red - Rgb {r: 0xb9, g: 0xca, b: 0x4a}, // Green - Rgb {r: 0xe6, g: 0xc5, b: 0x47}, // Yellow - Rgb {r: 0x7a, g: 0xa6, b: 0xda}, // Blue - Rgb {r: 0xc3, g: 0x97, b: 0xd8}, // Magenta - Rgb {r: 0x70, g: 0xc0, b: 0xba}, // Cyan - Rgb {r: 0x42, g: 0x42, b: 0x42}, // White - Rgb {r: 0x66, g: 0x66, b: 0x66}, // Bright black - Rgb {r: 0xff, g: 0x33, b: 0x34}, // Bright red - Rgb {r: 0x9e, g: 0xc4, b: 0x00}, // Bright green - Rgb {r: 0xe7, g: 0xc5, b: 0x47}, // Bright yellow - Rgb {r: 0x7a, g: 0xa6, b: 0xda}, // Bright blue - Rgb {r: 0xb7, g: 0x7e, b: 0xe0}, // Bright magenta - Rgb {r: 0x54, g: 0xce, b: 0xd6}, // Bright cyan - Rgb {r: 0x2a, g: 0x2a, b: 0x2a}, // Bright white -]; - pub mod mode { bitflags! { pub flags TermMode: u8 { @@ -165,8 +144,6 @@ pub mod mode { pub use self::mode::TermMode; -pub const DEFAULT_FG: Rgb = Rgb { r: 0xea, g: 0xea, b: 0xea}; -pub const DEFAULT_BG: Rgb = Rgb { r: 0, g: 0, b: 0}; pub const TAB_SPACES: usize = 8; pub struct Term { @@ -189,17 +166,14 @@ pub struct Term { alt_cursor: Cursor, /// Active foreground color - fg: Rgb, + pub fg: Rgb, /// Active background color - bg: Rgb, + pub bg: Rgb, /// Tabstops tabs: Vec, - /// Cell attributes - attr: cell::Flags, - /// Mode flags mode: TermMode, @@ -212,6 +186,12 @@ pub struct Term { /// Template cell template_cell: Cell, + /// Empty cell + empty_cell: Cell, + + /// Text colors + colors: [Rgb; 16], + pub dirty: bool, } @@ -244,7 +224,13 @@ impl SizeInfo { } impl Term { - pub fn new(width: f32, height: f32, cell_width: f32, cell_height: f32) -> Term { + pub fn new( + config: &Config, + width: f32, + height: f32, + cell_width: f32, + cell_height: f32 + ) -> Term { let size = SizeInfo { width: width as f32, height: height as f32, @@ -252,17 +238,19 @@ impl Term { cell_height: cell_height as f32, }; - let mut template = Cell::new(' '); template.flags = cell::Flags::empty(); - template.bg = DEFAULT_BG; - template.fg = DEFAULT_FG; + template.bg = config.bg_color(); + template.fg = config.fg_color(); let num_cols = size.cols(); let num_lines = size.lines(); println!("num_cols, num_lines = {}, {}", num_cols, num_lines); + println!("bg: {:?}, fg: {:?}", template.bg, template.fg); + println!("colors: {:?}", config.color_list()); + let grid = Grid::new(num_lines, num_cols, &Cell::new(' ')); let tty = tty::new(*num_lines as u8, *num_cols as u8); @@ -284,15 +272,16 @@ impl Term { alt: false, cursor: Cursor::default(), alt_cursor: Cursor::default(), - fg: DEFAULT_FG, - bg: DEFAULT_BG, + fg: config.fg_color(), + bg: config.bg_color(), tty: tty, tabs: tabs, - attr: cell::Flags::empty(), mode: Default::default(), scroll_region: scroll_region, size_info: size, template_cell: template, + empty_cell: template, + colors: config.color_list(), } } @@ -348,7 +337,7 @@ impl Term { self.tabs[0] = false; // Make sure bottom of terminal is clear - let template = self.template_cell.clone(); + let template = self.empty_cell.clone(); self.grid.clear_region((self.cursor.line).., |c| c.reset(&template)); self.alt_grid.clear_region((self.cursor.line).., |c| c.reset(&template)); @@ -384,7 +373,7 @@ impl Term { ::std::mem::swap(&mut self.cursor, &mut self.alt_cursor); if self.alt { - let template = self.template_cell.clone(); + let template = self.empty_cell.clone(); self.grid.clear(|c| c.reset(&template)); } } @@ -397,7 +386,7 @@ impl Term { debug_println!("scroll_down: {}", lines); // Copy of cell template; can't have it borrowed when calling clear/scroll - let template = self.template_cell.clone(); + let template = self.empty_cell.clone(); // Clear `lines` lines at bottom of area { @@ -422,7 +411,7 @@ impl Term { debug_println!("scroll_up: {}", lines); // Copy of cell template; can't have it borrowed when calling clear/scroll - let template = self.template_cell.clone(); + let template = self.empty_cell.clone(); // Clear `lines` lines starting from origin to origin + lines { @@ -473,10 +462,8 @@ impl ansi::Handler for Term { } let cell = &mut self.grid[&self.cursor]; + *cell = self.template_cell; cell.c = c; - cell.fg = self.fg; - cell.bg = self.bg; - cell.flags = self.attr; self.cursor.col += 1; } @@ -520,7 +507,7 @@ impl ansi::Handler for Term { // Cells were just moved out towards the end of the line; fill in // between source and dest with blanks. - let template = self.template_cell.clone(); + let template = self.empty_cell.clone(); for c in &mut line[source..destination] { c.reset(&template); } @@ -666,7 +653,7 @@ impl ansi::Handler for Term { let end = start + count; let row = &mut self.grid[self.cursor.line]; - let template = self.template_cell.clone(); + let template = self.empty_cell.clone(); for c in &mut row[start..end] { c.reset(&template); } @@ -692,7 +679,7 @@ impl ansi::Handler for Term { } // Clear last `count` cells in line. If deleting 1 char, need to delete 1 cell. - let template = self.template_cell.clone(); + let template = self.empty_cell.clone(); let end = self.size_info.cols() - count; for c in &mut line[end..] { c.reset(&template); @@ -722,7 +709,7 @@ impl ansi::Handler for Term { #[inline] fn clear_line(&mut self, mode: ansi::LineClearMode) { debug_println!("clear_line: {:?}", mode); - let template = self.template_cell.clone(); + let template = self.empty_cell.clone(); match mode { ansi::LineClearMode::Right => { let row = &mut self.grid[self.cursor.line]; @@ -748,7 +735,7 @@ impl ansi::Handler for Term { #[inline] fn clear_screen(&mut self, mode: ansi::ClearMode) { debug_println!("clear_screen: {:?}", mode); - let template = self.template_cell.clone(); + let template = self.empty_cell.clone(); match mode { ansi::ClearMode::Below => { for row in &mut self.grid[self.cursor.line..] { @@ -793,36 +780,36 @@ impl ansi::Handler for Term { debug_println!("Set Attribute: {:?}", attr); match attr { Attr::DefaultForeground => { - self.fg = DEFAULT_FG; + self.template_cell.fg = self.fg; }, Attr::DefaultBackground => { - self.bg = DEFAULT_BG; + self.template_cell.bg = self.bg; }, Attr::Foreground(named_color) => { - self.fg = COLORS[named_color as usize]; + self.template_cell.fg = self.colors[named_color as usize]; }, Attr::Background(named_color) => { - self.bg = COLORS[named_color as usize]; + self.template_cell.bg = self.colors[named_color as usize]; }, Attr::ForegroundSpec(rgb) => { - self.fg = rgb; + self.template_cell.fg = rgb; }, Attr::BackgroundSpec(rgb) => { - self.bg = rgb; + self.template_cell.bg = rgb; }, Attr::Reset => { - self.fg = DEFAULT_FG; - self.bg = DEFAULT_BG; - self.attr = cell::Flags::empty(); + self.template_cell.fg = self.fg; + self.template_cell.bg = self.bg; + self.template_cell.flags = cell::Flags::empty(); }, - Attr::Reverse => self.attr.insert(cell::INVERSE), - Attr::CancelReverse => self.attr.remove(cell::INVERSE), - Attr::Bold => self.attr.insert(cell::BOLD), - Attr::CancelBoldDim => self.attr.remove(cell::BOLD), - Attr::Italic => self.attr.insert(cell::ITALIC), - Attr::CancelItalic => self.attr.remove(cell::ITALIC), - Attr::Underscore => self.attr.insert(cell::UNDERLINE), - Attr::CancelUnderline => self.attr.remove(cell::UNDERLINE), + Attr::Reverse => self.template_cell.flags.insert(cell::INVERSE), + Attr::CancelReverse => self.template_cell.flags.remove(cell::INVERSE), + Attr::Bold => self.template_cell.flags.insert(cell::BOLD), + Attr::CancelBoldDim => self.template_cell.flags.remove(cell::BOLD), + Attr::Italic => self.template_cell.flags.insert(cell::ITALIC), + Attr::CancelItalic => self.template_cell.flags.remove(cell::ITALIC), + Attr::Underscore => self.template_cell.flags.insert(cell::UNDERLINE), + Attr::CancelUnderline => self.template_cell.flags.remove(cell::UNDERLINE), _ => { debug_println!("Term got unhandled attr: {:?}", attr); }