1use serde::{Deserialize, Serialize};
2
3use crate::ThemeError;
4
5#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize)]
7pub struct Color {
8 pub r: u8,
9 pub g: u8,
10 pub b: u8,
11 pub a: u8,
12}
13
14impl Color {
15 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
17 Self { r, g, b, a: 255 }
18 }
19
20 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
22 Self { r, g, b, a }
23 }
24
25 pub fn from_hex_str(s: &str) -> Result<Self, ThemeError> {
27 let s = s
28 .strip_prefix('#')
29 .ok_or_else(|| ThemeError::BadHex(s.to_owned()))?;
30 fn hex2(b: &[u8]) -> Option<u8> {
31 let hi = (b[0] as char).to_digit(16)? as u8;
32 let lo = (b[1] as char).to_digit(16)? as u8;
33 Some(hi << 4 | lo)
34 }
35 fn expand(nibble: u8) -> u8 {
36 nibble << 4 | nibble
37 }
38 match s.len() {
39 3 => {
40 let b = s.as_bytes();
41 let r = (b[0] as char).to_digit(16).map(|n| expand(n as u8));
42 let g = (b[1] as char).to_digit(16).map(|n| expand(n as u8));
43 let b_ = (b[2] as char).to_digit(16).map(|n| expand(n as u8));
44 match (r, g, b_) {
45 (Some(r), Some(g), Some(b)) => Ok(Color::rgb(r, g, b)),
46 _ => Err(ThemeError::BadHex(format!("#{s}"))),
47 }
48 }
49 6 => {
50 let b = s.as_bytes();
51 match (hex2(&b[0..2]), hex2(&b[2..4]), hex2(&b[4..6])) {
52 (Some(r), Some(g), Some(b)) => Ok(Color::rgb(r, g, b)),
53 _ => Err(ThemeError::BadHex(format!("#{s}"))),
54 }
55 }
56 8 => {
57 let b = s.as_bytes();
58 match (
59 hex2(&b[0..2]),
60 hex2(&b[2..4]),
61 hex2(&b[4..6]),
62 hex2(&b[6..8]),
63 ) {
64 (Some(r), Some(g), Some(b), Some(a)) => Ok(Color::rgba(r, g, b, a)),
65 _ => Err(ThemeError::BadHex(format!("#{s}"))),
66 }
67 }
68 _ => Err(ThemeError::BadHex(format!("#{s}"))),
69 }
70 }
71}
72
73#[derive(Clone, Debug)]
75pub(crate) enum ColorRef {
76 Palette(String),
77 Literal(Color),
78}
79
80impl<'de> Deserialize<'de> for ColorRef {
81 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
82 let s = String::deserialize(d)?;
83 if let Some(name) = s.strip_prefix('$') {
84 Ok(ColorRef::Palette(name.to_owned()))
85 } else {
86 Color::from_hex_str(&s)
87 .map(ColorRef::Literal)
88 .map_err(serde::de::Error::custom)
89 }
90 }
91}
92
93#[derive(Clone, Debug)]
95pub(crate) struct LiteralColor(pub Color);
96
97impl<'de> Deserialize<'de> for LiteralColor {
98 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
99 let s = String::deserialize(d)?;
100 Color::from_hex_str(&s)
101 .map(LiteralColor)
102 .map_err(serde::de::Error::custom)
103 }
104}
105
106impl ColorRef {
107 pub(crate) fn resolve(
109 self,
110 palette: &std::collections::HashMap<String, Color>,
111 ) -> Result<Color, ThemeError> {
112 match self {
113 ColorRef::Literal(c) => Ok(c),
114 ColorRef::Palette(name) => palette
115 .get(&name)
116 .copied()
117 .ok_or(ThemeError::UnresolvedPalette(name)),
118 }
119 }
120}
121
122#[derive(Clone, Debug, Deserialize)]
124pub(crate) struct RawPalette(pub std::collections::HashMap<String, LiteralColor>);