2018-01-08 15:11:12 +00:00
|
|
|
/// One of the 8 base colors.
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
|
|
pub enum BaseColor {
|
|
|
|
/// Black color
|
|
|
|
///
|
|
|
|
/// Color #0
|
|
|
|
Black,
|
|
|
|
/// Red color
|
|
|
|
///
|
|
|
|
/// Color #1
|
|
|
|
Red,
|
|
|
|
/// Green color
|
|
|
|
///
|
|
|
|
/// Color #2
|
|
|
|
Green,
|
|
|
|
/// Yellow color (Red + Green)
|
|
|
|
///
|
|
|
|
/// Color #3
|
|
|
|
Yellow,
|
|
|
|
/// Blue color
|
|
|
|
///
|
|
|
|
/// Color #4
|
|
|
|
Blue,
|
|
|
|
/// Magenta color (Red + Blue)
|
|
|
|
///
|
|
|
|
/// Color #5
|
|
|
|
Magenta,
|
|
|
|
/// Cyan color (Green + Blue)
|
|
|
|
///
|
|
|
|
/// Color #6
|
|
|
|
Cyan,
|
|
|
|
/// White color (Red + Green + Blue)
|
|
|
|
///
|
|
|
|
/// Color #7
|
|
|
|
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!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents a color used by the theme.
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
|
|
pub enum Color {
|
|
|
|
/// Represents a color, preset by terminal.
|
|
|
|
TerminalDefault,
|
2018-01-17 17:35:57 +00:00
|
|
|
|
2018-01-08 15:11:12 +00:00
|
|
|
/// One of the 8 base colors.
|
2018-01-21 19:06:33 +00:00
|
|
|
///
|
|
|
|
/// Note: the actual color used depends on the terminal configuration.
|
2018-01-08 15:11:12 +00:00
|
|
|
Dark(BaseColor),
|
2018-01-17 17:35:57 +00:00
|
|
|
|
2018-01-08 15:11:12 +00:00
|
|
|
/// Lighter version of a base color.
|
2018-01-21 19:06:33 +00:00
|
|
|
///
|
|
|
|
/// Note: the actual color used depends on the terminal configuration.
|
2018-01-08 15:11:12 +00:00
|
|
|
Light(BaseColor),
|
2018-01-17 17:35:57 +00:00
|
|
|
|
2018-01-08 15:11:12 +00:00
|
|
|
/// True-color, 24-bit.
|
|
|
|
Rgb(u8, u8, u8),
|
2018-01-17 17:35:57 +00:00
|
|
|
|
2018-01-08 15:11:12 +00:00
|
|
|
/// Low-resolution
|
|
|
|
///
|
|
|
|
/// Each value should be `<= 5` (you'll get panics otherwise).
|
|
|
|
///
|
|
|
|
/// These 216 possible colors are part of the default color palette.
|
|
|
|
RgbLowRes(u8, u8, u8),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Color {
|
|
|
|
/// Creates a color from its ID in the 256 colors list.
|
|
|
|
///
|
|
|
|
/// * Colors 0-7 are base dark colors.
|
|
|
|
/// * Colors 8-15 are base light colors.
|
2018-01-15 00:57:46 +00:00
|
|
|
/// * Colors 16-231 are rgb colors with 6 values per channel (216 colors).
|
|
|
|
/// * Colors 232-255 are grayscale colors.
|
2018-01-08 15:11:12 +00:00
|
|
|
pub fn from_256colors(n: u8) -> Self {
|
|
|
|
if n < 8 {
|
|
|
|
Color::Dark(BaseColor::from(n))
|
|
|
|
} else if n < 16 {
|
|
|
|
Color::Light(BaseColor::from(n))
|
2018-01-15 00:57:46 +00:00
|
|
|
} else if n >= 232 {
|
|
|
|
let n = n - 232;
|
|
|
|
let value = 8 + 10 * n;
|
|
|
|
|
|
|
|
// TODO: returns a Grayscale value?
|
|
|
|
Color::Rgb(value, value, value)
|
2018-01-08 15:11:12 +00:00
|
|
|
} else {
|
|
|
|
let n = n - 16;
|
2018-01-28 01:26:03 +00:00
|
|
|
// We support 6*6*6 = 216 colors here
|
|
|
|
assert!(n < 216);
|
2018-01-08 15:11:12 +00:00
|
|
|
|
|
|
|
let r = n / 36;
|
|
|
|
let g = (n % 36) / 6;
|
|
|
|
let b = n % 6;
|
|
|
|
|
2018-01-28 01:26:03 +00:00
|
|
|
// Each color is in the range [0, 5] (6 possible values)
|
|
|
|
assert!(r < 6);
|
|
|
|
assert!(g < 6);
|
|
|
|
assert!(b < 6);
|
2018-01-15 00:57:46 +00:00
|
|
|
|
2018-01-08 15:11:12 +00:00
|
|
|
Color::RgbLowRes(r, g, b)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-11 06:24:58 +00:00
|
|
|
/// Parse a string into a color.
|
|
|
|
///
|
|
|
|
/// Examples:
|
|
|
|
/// * `"red"` becomes `Color::Dark(BaseColor::Red)`
|
|
|
|
/// * `"light green"` becomes `Color::Light(BaseColor::Green)`
|
|
|
|
/// * `"default"` becomes `Color::TerminalDefault`
|
|
|
|
/// * `"#123456"` becomes `Color::Rgb(0x12, 0x34, 0x56)`
|
2018-01-08 15:11:12 +00:00
|
|
|
pub(crate) 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..];
|
|
|
|
// Compute per-color length, and amplitude
|
|
|
|
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;
|
2018-06-11 06:24:58 +00:00
|
|
|
|
2018-01-08 15:11:12 +00:00
|
|
|
Some(Color::Rgb(r as u8, g as u8, b as u8))
|
|
|
|
} else if value.len() == 3 {
|
|
|
|
// RGB values between 0 and 5 maybe?
|
2018-06-11 06:24:58 +00:00
|
|
|
// Like 050 for green
|
2018-01-08 15:11:12 +00:00
|
|
|
let rgb: Vec<_> =
|
|
|
|
value.chars().map(|c| c as i16 - '0' as i16).collect();
|
2018-06-11 06:24:58 +00:00
|
|
|
|
|
|
|
assert_eq!(rgb.len(), 3);
|
2018-01-08 15:11:12 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Loads a hexadecimal code
|
|
|
|
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
|
|
|
|
}
|
2018-01-28 01:31:43 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_256_colors() {
|
|
|
|
// Make sure Color::from_256colors never panics
|
|
|
|
use super::Color;
|
|
|
|
|
|
|
|
// TODO: use inclusive range when it gets stable
|
|
|
|
for i in 0..256u16 {
|
|
|
|
Color::from_256colors(i as u8);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|