use std::fs::File;
use std::io;
use std::io::Read;
use std::path::Path;
use toml;
#[derive(Clone, Copy, Debug)]
pub enum Effect {
Simple,
Reverse,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ColorPair {
pub front: Color,
pub back: Color,
}
impl ColorPair {
pub fn invert(&self) -> Self {
ColorPair {
front: self.back,
back: self.front,
}
}
pub fn from_256colors(front: u8, back: u8) -> Self {
Self {
front: Color::from_256colors(front),
back: Color::from_256colors(back),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ColorStyle {
TerminalDefault,
Background,
Shadow,
Primary,
Secondary,
Tertiary,
TitlePrimary,
TitleSecondary,
Highlight,
HighlightInactive,
Custom {
front: Color,
back: Color,
},
}
impl ColorStyle {
pub fn resolve(&self, theme: &Theme) -> ColorPair {
let c = &theme.colors;
let (front, back) = match *self {
ColorStyle::TerminalDefault => (
Color::TerminalDefault,
Color::TerminalDefault,
),
ColorStyle::Background => (c.view, c.background),
ColorStyle::Shadow => (c.shadow, c.shadow),
ColorStyle::Primary => (c.primary, c.view),
ColorStyle::Secondary => (c.secondary, c.view),
ColorStyle::Tertiary => (c.tertiary, c.view),
ColorStyle::TitlePrimary => (c.title_primary, c.view),
ColorStyle::TitleSecondary => (c.title_secondary, c.view),
ColorStyle::Highlight => (c.view, c.highlight),
ColorStyle::HighlightInactive => (c.view, c.highlight_inactive),
ColorStyle::Custom { front, back } => (front, back),
};
ColorPair { front, back }
}
}
#[derive(Clone, Debug)]
pub struct Theme {
pub shadow: bool,
pub borders: BorderStyle,
pub colors: Palette,
}
impl Default for Theme {
fn default() -> Self {
Theme {
shadow: true,
borders: BorderStyle::Simple,
colors: Palette {
background: Color::Dark(BaseColor::Blue),
shadow: Color::Dark(BaseColor::Black),
view: Color::Dark(BaseColor::White),
primary: Color::Dark(BaseColor::Black),
secondary: Color::Dark(BaseColor::Blue),
tertiary: Color::Light(BaseColor::White),
title_primary: Color::Dark(BaseColor::Red),
title_secondary: Color::Dark(BaseColor::Yellow),
highlight: Color::Dark(BaseColor::Red),
highlight_inactive: Color::Dark(BaseColor::Blue),
},
}
}
}
impl Theme {
fn load(&mut self, table: &toml::value::Table) {
if let Some(&toml::Value::Boolean(shadow)) = table.get("shadow") {
self.shadow = shadow;
}
if let Some(&toml::Value::String(ref borders)) = table.get("borders") {
self.borders = BorderStyle::from(borders);
}
if let Some(&toml::Value::Table(ref table)) = table.get("colors") {
self.colors.load(table);
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BorderStyle {
Simple,
Outset,
None,
}
impl BorderStyle {
fn from(s: &str) -> Self {
if s == "simple" {
BorderStyle::Simple
} else if s == "outset" {
BorderStyle::Outset
} else {
BorderStyle::None
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct Palette {
pub background: Color,
pub shadow: Color,
pub view: Color,
pub primary: Color,
pub secondary: Color,
pub tertiary: Color,
pub title_primary: Color,
pub title_secondary: Color,
pub highlight: Color,
pub highlight_inactive: Color,
}
impl Palette {
fn load(&mut self, table: &toml::value::Table) {
load_color(&mut self.background, table.get("background"));
load_color(&mut self.shadow, table.get("shadow"));
load_color(&mut self.view, table.get("view"));
load_color(&mut self.primary, table.get("primary"));
load_color(&mut self.secondary, table.get("secondary"));
load_color(&mut self.tertiary, table.get("tertiary"));
load_color(&mut self.title_primary, table.get("title_primary"));
load_color(&mut self.title_secondary, table.get("title_secondary"));
load_color(&mut self.highlight, table.get("highlight"));
load_color(
&mut self.highlight_inactive,
table.get("highlight_inactive"),
);
}
}
fn load_color(target: &mut Color, value: Option<&toml::Value>) -> bool {
if let Some(value) = value {
match *value {
toml::Value::String(ref value) => {
if let Some(color) = Color::parse(value) {
*target = color;
true
} else {
false
}
}
toml::Value::Array(ref array) => {
array.iter().any(|item| load_color(target, Some(item)))
}
_ => false,
}
} else {
false
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BaseColor {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
}
impl From<u8> for BaseColor {
fn from(n: u8) -> Self {
match n % 8 {
0 => BaseColor::Black,
1 => BaseColor::Red,
2 => BaseColor::Green,
3 => BaseColor::Yellow,
4 => BaseColor::Blue,
5 => BaseColor::Magenta,
6 => BaseColor::Cyan,
7 => BaseColor::White,
_ => unreachable!(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Color {
TerminalDefault,
Dark(BaseColor),
Light(BaseColor),
Rgb(u8, u8, u8),
RgbLowRes(u8, u8, u8),
}
#[derive(Debug)]
pub enum Error {
Io(io::Error),
Parse(toml::de::Error),
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Error::Io(err)
}
}
impl From<toml::de::Error> for Error {
fn from(err: toml::de::Error) -> Self {
Error::Parse(err)
}
}
impl Color {
pub fn from_256colors(n: u8) -> Self {
if n < 8 {
Color::Dark(BaseColor::from(n))
} else if n < 16 {
Color::Light(BaseColor::from(n))
} else {
let n = n - 16;
let r = n / 36;
let g = (n % 36) / 6;
let b = n % 6;
Color::RgbLowRes(r, g, b)
}
}
fn parse(value: &str) -> Option<Self> {
Some(match value {
"black" => Color::Dark(BaseColor::Black),
"red" => Color::Dark(BaseColor::Red),
"green" => Color::Dark(BaseColor::Green),
"yellow" => Color::Dark(BaseColor::Yellow),
"blue" => Color::Dark(BaseColor::Blue),
"magenta" => Color::Dark(BaseColor::Magenta),
"cyan" => Color::Dark(BaseColor::Cyan),
"white" => Color::Dark(BaseColor::White),
"light black" => Color::Light(BaseColor::Black),
"light red" => Color::Light(BaseColor::Red),
"light green" => Color::Light(BaseColor::Green),
"light yellow" => Color::Light(BaseColor::Yellow),
"light blue" => Color::Light(BaseColor::Blue),
"light magenta" => Color::Light(BaseColor::Magenta),
"light cyan" => Color::Light(BaseColor::Cyan),
"light white" => Color::Light(BaseColor::White),
"default" => Color::TerminalDefault,
value => return Color::parse_special(value),
})
}
fn parse_special(value: &str) -> Option<Color> {
if value.starts_with('#') {
let value = &value[1..];
let (l, multiplier) = match value.len() {
6 => (2, 1),
3 => (1, 17),
_ => panic!("Cannot parse color: {}", value),
};
let r = load_hex(&value[0..l]) * multiplier;
let g = load_hex(&value[l..2 * l]) * multiplier;
let b = load_hex(&value[2 * l..3 * l]) * multiplier;
Some(Color::Rgb(r as u8, g as u8, b as u8))
} else if value.len() == 3 {
let rgb: Vec<_> =
value.chars().map(|c| c as i16 - '0' as i16).collect();
if rgb.iter().all(|&i| i >= 0 && i < 6) {
Some(
Color::RgbLowRes(rgb[0] as u8, rgb[1] as u8, rgb[2] as u8),
)
} else {
None
}
} else {
None
}
}
}
pub fn load_theme_file<P: AsRef<Path>>(filename: P) -> Result<Theme, Error> {
let content = {
let mut content = String::new();
let mut file = try!(File::open(filename));
try!(file.read_to_string(&mut content));
content
};
load_theme(&content)
}
pub fn load_theme(content: &str) -> Result<Theme, Error> {
let table = toml::de::from_str(content)?;
let mut theme = Theme::default();
theme.load(&table);
Ok(theme)
}
pub fn load_default() -> Theme {
Theme::default()
}
fn load_hex(s: &str) -> u16 {
let mut sum = 0;
for c in s.chars() {
sum *= 16;
sum += match c {
n @ '0'...'9' => n as i16 - '0' as i16,
n @ 'a'...'f' => n as i16 - 'a' as i16 + 10,
n @ 'A'...'F' => n as i16 - 'A' as i16 + 10,
_ => 0,
};
}
sum as u16
}