#![allow(clippy::useless_attribute)]
use num_derive::*;
#[cfg(feature = "use_serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub use wezterm_color_types::{LinearRgba, SrgbaTuple};
use wezterm_dynamic::{FromDynamic, FromDynamicOptions, ToDynamic, Value};
#[derive(Debug, Clone, Copy, FromPrimitive, PartialEq, Eq, FromDynamic, ToDynamic)]
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
#[repr(u8)]
pub enum AnsiColor {
Black = 0,
Maroon,
Green,
Olive,
Navy,
Purple,
Teal,
Silver,
Grey,
Red,
Lime,
Yellow,
Blue,
Fuchsia,
Aqua,
White,
}
impl From<AnsiColor> for u8 {
fn from(col: AnsiColor) -> u8 {
col as u8
}
}
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash)]
pub struct RgbColor {
bits: u32,
}
impl Into<SrgbaTuple> for RgbColor {
fn into(self) -> SrgbaTuple {
self.to_tuple_rgba()
}
}
impl RgbColor {
pub const fn new_8bpc(red: u8, green: u8, blue: u8) -> Self {
Self {
bits: ((red as u32) << 16) | ((green as u32) << 8) | blue as u32,
}
}
pub fn new_f32(red: f32, green: f32, blue: f32) -> Self {
let red = (red * 255.) as u8;
let green = (green * 255.) as u8;
let blue = (blue * 255.) as u8;
Self::new_8bpc(red, green, blue)
}
pub fn to_tuple_rgb8(self) -> (u8, u8, u8) {
(
(self.bits >> 16) as u8,
(self.bits >> 8) as u8,
self.bits as u8,
)
}
pub fn to_tuple_rgba(self) -> SrgbaTuple {
SrgbaTuple(
(self.bits >> 16) as u8 as f32 / 255.0,
(self.bits >> 8) as u8 as f32 / 255.0,
self.bits as u8 as f32 / 255.0,
1.0,
)
}
pub fn to_linear_tuple_rgba(self) -> LinearRgba {
self.to_tuple_rgba().to_linear()
}
pub fn from_named(name: &str) -> Option<RgbColor> {
Some(SrgbaTuple::from_named(name)?.into())
}
pub fn to_rgb_string(self) -> String {
let (red, green, blue) = self.to_tuple_rgb8();
format!("#{:02x}{:02x}{:02x}", red, green, blue)
}
pub fn to_x11_16bit_rgb_string(self) -> String {
let (red, green, blue) = self.to_tuple_rgb8();
format!(
"rgb:{:02x}{:02x}/{:02x}{:02x}/{:02x}{:02x}",
red, red, green, green, blue, blue
)
}
pub fn from_rgb_str(s: &str) -> Option<RgbColor> {
let srgb: SrgbaTuple = s.parse().ok()?;
Some(srgb.into())
}
pub fn from_named_or_rgb_string(s: &str) -> Option<Self> {
RgbColor::from_rgb_str(&s).or_else(|| RgbColor::from_named(&s))
}
}
impl From<SrgbaTuple> for RgbColor {
fn from(srgb: SrgbaTuple) -> RgbColor {
let SrgbaTuple(r, g, b, _) = srgb;
Self::new_f32(r, g, b)
}
}
#[cfg(feature = "use_serde")]
impl Serialize for RgbColor {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = self.to_rgb_string();
s.serialize(serializer)
}
}
#[cfg(feature = "use_serde")]
impl<'de> Deserialize<'de> for RgbColor {
fn deserialize<D>(deserializer: D) -> Result<RgbColor, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
RgbColor::from_named_or_rgb_string(&s)
.ok_or_else(|| format!("unknown color name: {}", s))
.map_err(serde::de::Error::custom)
}
}
impl ToDynamic for RgbColor {
fn to_dynamic(&self) -> Value {
self.to_rgb_string().to_dynamic()
}
}
impl FromDynamic for RgbColor {
fn from_dynamic(
value: &Value,
options: FromDynamicOptions,
) -> Result<Self, wezterm_dynamic::Error> {
let s = String::from_dynamic(value, options)?;
Ok(RgbColor::from_named_or_rgb_string(&s)
.ok_or_else(|| format!("unknown color name: {}", s))?)
}
}
pub type PaletteIndex = u8;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ColorSpec {
Default,
PaletteIndex(PaletteIndex),
TrueColor(SrgbaTuple),
}
impl Default for ColorSpec {
fn default() -> Self {
ColorSpec::Default
}
}
impl From<AnsiColor> for ColorSpec {
fn from(col: AnsiColor) -> Self {
ColorSpec::PaletteIndex(col as u8)
}
}
impl From<RgbColor> for ColorSpec {
fn from(col: RgbColor) -> Self {
ColorSpec::TrueColor(col.into())
}
}
impl From<SrgbaTuple> for ColorSpec {
fn from(col: SrgbaTuple) -> Self {
ColorSpec::TrueColor(col)
}
}
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, Eq, PartialEq, FromDynamic, ToDynamic, Hash)]
pub enum ColorAttribute {
TrueColorWithPaletteFallback(SrgbaTuple, PaletteIndex),
TrueColorWithDefaultFallback(SrgbaTuple),
PaletteIndex(PaletteIndex),
Default,
}
impl Default for ColorAttribute {
fn default() -> Self {
ColorAttribute::Default
}
}
impl From<AnsiColor> for ColorAttribute {
fn from(col: AnsiColor) -> Self {
ColorAttribute::PaletteIndex(col as u8)
}
}
impl From<ColorSpec> for ColorAttribute {
fn from(spec: ColorSpec) -> Self {
match spec {
ColorSpec::Default => ColorAttribute::Default,
ColorSpec::PaletteIndex(idx) => ColorAttribute::PaletteIndex(idx),
ColorSpec::TrueColor(color) => ColorAttribute::TrueColorWithDefaultFallback(color),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_hsl() {
let foo = RgbColor::from_rgb_str("hsl:235 100 50").unwrap();
assert_eq!(foo.to_rgb_string(), "#0015ff");
}
#[test]
fn from_rgb() {
assert!(RgbColor::from_rgb_str("").is_none());
assert!(RgbColor::from_rgb_str("#xyxyxy").is_none());
let black = RgbColor::from_rgb_str("#FFF").unwrap();
assert_eq!(black.to_tuple_rgb8(), (0xf0, 0xf0, 0xf0));
let black = RgbColor::from_rgb_str("#000000").unwrap();
assert_eq!(black.to_tuple_rgb8(), (0, 0, 0));
let grey = RgbColor::from_rgb_str("rgb:D6/D6/D6").unwrap();
assert_eq!(grey.to_tuple_rgb8(), (0xd6, 0xd6, 0xd6));
let grey = RgbColor::from_rgb_str("rgb:f0f0/f0f0/f0f0").unwrap();
assert_eq!(grey.to_tuple_rgb8(), (0xf0, 0xf0, 0xf0));
}
#[cfg(feature = "use_serde")]
#[test]
fn roundtrip_rgbcolor() {
let data = varbincode::serialize(&RgbColor::from_named("DarkGreen").unwrap()).unwrap();
eprintln!("serialized as {:?}", data);
let _decoded: RgbColor = varbincode::deserialize(data.as_slice()).unwrap();
}
}