152 lines
3.9 KiB
Rust
152 lines
3.9 KiB
Rust
|
use std::str::FromStr;
|
||
|
|
||
|
use serde::{de::Unexpected, Deserialize, Serialize};
|
||
|
use thiserror::Error;
|
||
|
|
||
|
use super::hex::{HexError, ParseHex};
|
||
|
|
||
|
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(),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, Clone, Error)]
|
||
|
pub enum ParseError {
|
||
|
#[error("length must be either 6 characters or 7 with '#' prefix")]
|
||
|
InvalidLength,
|
||
|
#[error("invalid hex value: [{0}]")]
|
||
|
HexError(#[from] HexError),
|
||
|
}
|
||
|
|
||
|
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::InvalidLength);
|
||
|
}
|
||
|
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));
|
||
|
}
|
||
|
}
|