427 lines
8.5 KiB
Rust
427 lines
8.5 KiB
Rust
use std::str::FromStr;
|
|
|
|
use serde::{de::Unexpected, Deserialize, Serialize};
|
|
use strum::IntoEnumIterator;
|
|
use thiserror::Error;
|
|
|
|
use crate::hlwm::StringParseError;
|
|
|
|
use super::hex::{HexError, ParseHex};
|
|
|
|
#[derive(Debug, Clone, 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::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 rgb(r: u8, g: u8, b: u8) -> Self {
|
|
Self::RGB { r, g, b }
|
|
}
|
|
}
|
|
|
|
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()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, strum::Display, strum::EnumIter, PartialEq, Eq)]
|
|
pub enum X11Color {
|
|
GhostWhite,
|
|
WhiteSmoke,
|
|
FloralWhite,
|
|
OldLace,
|
|
AntiqueWhite,
|
|
PapayaWhip,
|
|
BlanchedAlmond,
|
|
PeachPuff,
|
|
NavajoWhite,
|
|
LemonChiffon,
|
|
MintCream,
|
|
AliceBlue,
|
|
LavenderBlush,
|
|
MistyRose,
|
|
DarkSlateGray,
|
|
DarkSlateGrey,
|
|
DimGray,
|
|
DimGrey,
|
|
SlateGray,
|
|
SlateGrey,
|
|
LightSlateGray,
|
|
LightSlateGrey,
|
|
X11Gray,
|
|
X11Grey,
|
|
WebGray,
|
|
WebGrey,
|
|
LightGrey,
|
|
LightGray,
|
|
MidnightBlue,
|
|
NavyBlue,
|
|
CornflowerBlue,
|
|
DarkSlateBlue,
|
|
SlateBlue,
|
|
MediumSlateBlue,
|
|
LightSlateBlue,
|
|
MediumBlue,
|
|
RoyalBlue,
|
|
DodgerBlue,
|
|
DeepSkyBlue,
|
|
SkyBlue,
|
|
LightSkyBlue,
|
|
SteelBlue,
|
|
LightSteelBlue,
|
|
LightBlue,
|
|
PowderBlue,
|
|
PaleTurquoise,
|
|
DarkTurquoise,
|
|
MediumTurquoise,
|
|
LightCyan,
|
|
CadetBlue,
|
|
MediumAquamarine,
|
|
DarkGreen,
|
|
DarkOliveGreen,
|
|
DarkSeaGreen,
|
|
SeaGreen,
|
|
MediumSeaGreen,
|
|
LightSeaGreen,
|
|
PaleGreen,
|
|
SpringGreen,
|
|
LawnGreen,
|
|
X11Green,
|
|
WebGreen,
|
|
MediumSpringGreen,
|
|
GreenYellow,
|
|
LimeGreen,
|
|
YellowGreen,
|
|
ForestGreen,
|
|
OliveDrab,
|
|
DarkKhaki,
|
|
PaleGoldenrod,
|
|
LightGoldenrodYellow,
|
|
LightYellow,
|
|
LightGoldenrod,
|
|
DarkGoldenrod,
|
|
RosyBrown,
|
|
IndianRed,
|
|
SaddleBrown,
|
|
SandyBrown,
|
|
DarkSalmon,
|
|
LightSalmon,
|
|
DarkOrange,
|
|
LightCoral,
|
|
OrangeRed,
|
|
HotPink,
|
|
DeepPink,
|
|
LightPink,
|
|
PaleVioletRed,
|
|
X11Maroon,
|
|
WebMaroon,
|
|
MediumVioletRed,
|
|
VioletRed,
|
|
MediumOrchid,
|
|
DarkOrchid,
|
|
DarkViolet,
|
|
BlueViolet,
|
|
X11Purple,
|
|
WebPurple,
|
|
MediumPurple,
|
|
AntiqueWhite1,
|
|
AntiqueWhite2,
|
|
AntiqueWhite3,
|
|
AntiqueWhite4,
|
|
PeachPuff1,
|
|
PeachPuff2,
|
|
PeachPuff3,
|
|
PeachPuff4,
|
|
NavajoWhite1,
|
|
NavajoWhite2,
|
|
NavajoWhite3,
|
|
NavajoWhite4,
|
|
LemonChiffon1,
|
|
LemonChiffon2,
|
|
LemonChiffon3,
|
|
LemonChiffon4,
|
|
LavenderBlush1,
|
|
LavenderBlush2,
|
|
LavenderBlush3,
|
|
LavenderBlush4,
|
|
MistyRose1,
|
|
MistyRose2,
|
|
MistyRose3,
|
|
MistyRose4,
|
|
SlateBlue1,
|
|
SlateBlue2,
|
|
SlateBlue3,
|
|
SlateBlue4,
|
|
RoyalBlue1,
|
|
RoyalBlue2,
|
|
RoyalBlue3,
|
|
RoyalBlue4,
|
|
DodgerBlue1,
|
|
DodgerBlue2,
|
|
DodgerBlue3,
|
|
DodgerBlue4,
|
|
SteelBlue1,
|
|
SteelBlue2,
|
|
SteelBlue3,
|
|
SteelBlue4,
|
|
DeepSkyBlue1,
|
|
DeepSkyBlue2,
|
|
DeepSkyBlue3,
|
|
DeepSkyBlue4,
|
|
SkyBlue1,
|
|
SkyBlue2,
|
|
SkyBlue3,
|
|
SkyBlue4,
|
|
LightSkyBlue1,
|
|
LightSkyBlue2,
|
|
LightSkyBlue3,
|
|
LightSkyBlue4,
|
|
SlateGray1,
|
|
SlateGray2,
|
|
SlateGray3,
|
|
SlateGray4,
|
|
LightSteelBlue1,
|
|
LightSteelBlue2,
|
|
LightSteelBlue3,
|
|
LightSteelBlue4,
|
|
LightBlue1,
|
|
LightBlue2,
|
|
LightBlue3,
|
|
LightBlue4,
|
|
LightCyan1,
|
|
LightCyan2,
|
|
LightCyan3,
|
|
LightCyan4,
|
|
PaleTurquoise1,
|
|
PaleTurquoise2,
|
|
PaleTurquoise3,
|
|
PaleTurquoise4,
|
|
CadetBlue1,
|
|
CadetBlue2,
|
|
CadetBlue3,
|
|
CadetBlue4,
|
|
DarkSlateGray1,
|
|
DarkSlateGray2,
|
|
DarkSlateGray3,
|
|
DarkSlateGray4,
|
|
DarkSeaGreen1,
|
|
DarkSeaGreen2,
|
|
DarkSeaGreen3,
|
|
DarkSeaGreen4,
|
|
SeaGreen1,
|
|
SeaGreen2,
|
|
SeaGreen3,
|
|
SeaGreen4,
|
|
PaleGreen1,
|
|
PaleGreen2,
|
|
PaleGreen3,
|
|
PaleGreen4,
|
|
SpringGreen1,
|
|
SpringGreen2,
|
|
SpringGreen3,
|
|
SpringGreen4,
|
|
OliveDrab1,
|
|
OliveDrab2,
|
|
OliveDrab3,
|
|
OliveDrab4,
|
|
DarkOliveGreen1,
|
|
DarkOliveGreen2,
|
|
DarkOliveGreen3,
|
|
DarkOliveGreen4,
|
|
LightGoldenrod1,
|
|
LightGoldenrod2,
|
|
LightGoldenrod3,
|
|
LightGoldenrod4,
|
|
LightYellow1,
|
|
LightYellow2,
|
|
LightYellow3,
|
|
LightYellow4,
|
|
DarkGoldenrod1,
|
|
DarkGoldenrod2,
|
|
DarkGoldenrod3,
|
|
DarkGoldenrod4,
|
|
RosyBrown1,
|
|
RosyBrown2,
|
|
RosyBrown3,
|
|
RosyBrown4,
|
|
IndianRed1,
|
|
IndianRed2,
|
|
IndianRed3,
|
|
IndianRed4,
|
|
LightSalmon1,
|
|
LightSalmon2,
|
|
LightSalmon3,
|
|
LightSalmon4,
|
|
DarkOrange1,
|
|
DarkOrange2,
|
|
DarkOrange3,
|
|
DarkOrange4,
|
|
OrangeRed1,
|
|
OrangeRed2,
|
|
OrangeRed3,
|
|
OrangeRed4,
|
|
DeepPink1,
|
|
DeepPink2,
|
|
DeepPink3,
|
|
DeepPink4,
|
|
HotPink1,
|
|
HotPink2,
|
|
HotPink3,
|
|
HotPink4,
|
|
LightPink1,
|
|
LightPink2,
|
|
LightPink3,
|
|
LightPink4,
|
|
PaleVioletRed1,
|
|
PaleVioletRed2,
|
|
PaleVioletRed3,
|
|
PaleVioletRed4,
|
|
VioletRed1,
|
|
VioletRed2,
|
|
VioletRed3,
|
|
VioletRed4,
|
|
MediumOrchid1,
|
|
MediumOrchid2,
|
|
MediumOrchid3,
|
|
MediumOrchid4,
|
|
DarkOrchid1,
|
|
DarkOrchid2,
|
|
DarkOrchid3,
|
|
DarkOrchid4,
|
|
MediumPurple1,
|
|
MediumPurple2,
|
|
MediumPurple3,
|
|
MediumPurple4,
|
|
DarkGrey,
|
|
DarkGray,
|
|
DarkBlue,
|
|
DarkCyan,
|
|
DarkMagenta,
|
|
DarkRed,
|
|
LightGreen,
|
|
RebeccaPurple,
|
|
}
|
|
|
|
impl FromStr for X11Color {
|
|
type Err = StringParseError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
Self::iter()
|
|
.into_iter()
|
|
.find(|i| i.to_string() == s)
|
|
.ok_or(StringParseError::UnknownValue)
|
|
}
|
|
}
|
|
|
|
#[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));
|
|
}
|
|
}
|