hlctl/src/hlwm/color.rs

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));
}
}