hlctl/src/hlwm/color/mod.rs

146 lines
3.8 KiB
Rust

use std::str::FromStr;
use serde::{de::Unexpected, Deserialize, Serialize};
use super::{hex::ParseHex, parser::ParseError};
mod x11;
pub use x11::X11Color;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Color {
RGB { r: u8, g: u8, b: u8 },
X11(X11Color),
}
impl Serialize for Color {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ExpectedColor;
impl serde::de::Expected for ExpectedColor {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("an X11 color string or a hex color string")
}
}
let str_val: String = Deserialize::deserialize(deserializer)?;
Ok(Color::from_str(&str_val).map_err(|_| {
serde::de::Error::invalid_value(Unexpected::Str(&str_val), &ExpectedColor)
})?)
}
}
impl Default for Color {
fn default() -> Self {
Self::RGB {
r: Default::default(),
g: Default::default(),
b: Default::default(),
}
}
}
impl FromStr for Color {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(x11) = X11Color::from_str(s) {
return Ok(Self::X11(x11));
}
Self::from_hex(s)
}
}
impl Color {
pub const BLACK: Color = Color::from_rgb(0, 0, 0);
pub fn from_hex(hex: &str) -> Result<Self, ParseError> {
let expected_len = if hex.starts_with('#') { 7 } else { 6 };
if hex.len() != expected_len {
return Err(ParseError::InvalidValue {
value: hex.to_string(),
expected: "a 6 digit hex value",
});
}
let hex = hex.strip_prefix('#').unwrap_or(hex);
Ok(Self::RGB {
r: (&hex[0..2]).parse_hex()?,
g: (&hex[2..4]).parse_hex()?,
b: (&hex[4..6]).parse_hex()?,
})
}
pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Self::RGB { r, g, b }
}
pub const fn to_rgb(&self) -> (u8, u8, u8) {
match self {
Color::RGB { r, g, b } => (*r, *g, *b),
Color::X11(xc) => xc.to_rgb(),
}
}
}
impl std::fmt::Display for Color {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Color::RGB { r, g, b } => write!(f, "#{r:02X}{g:02X}{b:02X}"),
Color::X11(color) => f.write_str(&color.to_string()),
}
}
}
impl From<Color> for cnx::text::Color {
fn from(value: Color) -> Self {
let (r, g, b) = value.to_rgb();
cnx::text::Color::from_rgb(r, g, b)
}
}
#[cfg(test)]
mod test {
use serde::{Deserialize, Serialize};
use super::{Color, X11Color};
#[derive(Debug, Serialize, Deserialize)]
pub struct ColorContainer {
color: Color,
}
fn color_serialize_deserialize(color: Color) {
let original_color = color.clone();
let color_str =
toml::to_string_pretty(&ColorContainer { color }).expect("serialization failure");
let color_parsed: ColorContainer =
toml::from_str(color_str.as_str()).expect("deserialization failure");
assert_eq!(original_color, color_parsed.color);
}
#[test]
fn color_serialize_deserialize_rgb() {
color_serialize_deserialize(Color::RGB {
r: 128,
g: 128,
b: 128,
});
}
#[test]
fn color_serialize_deserialize_x11() {
color_serialize_deserialize(Color::X11(X11Color::HotPink2));
}
}