Make colors configurable from file

Added solarized dark color scheme for testing purposes.

Resolves #1.
This commit is contained in:
Joe Wilm 2016-10-15 15:56:27 -07:00
parent 71de5501c4
commit d4c1d51e36
5 changed files with 300 additions and 105 deletions

View File

@ -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'

View File

@ -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<D>(deserializer: &mut D) -> ::std::result::Result<Self, D::Error>
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<E>(&mut self, value: &str) -> ::std::result::Result<Self::Value, E>
where E: ::serde::de::Error
{
Ok(value.to_owned())
}
}
deserializer
.deserialize_f64(StringVisitor::<D>{ _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<Rgb, ()> {
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 {

View File

@ -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<glutin::Window>,
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<glutin::Window>,
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);
});
}
}

View File

@ -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<Cell>, glyph_cache: &mut GlyphCache) {
pub fn render_grid(&mut self, bg: &Rgb, grid: &Grid<Cell>, 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;

View File

@ -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<bool>,
/// 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);
}