1#![allow(clippy::useless_attribute)]
4
5use num_derive::*;
6#[cfg(feature = "use_serde")]
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8pub use wezterm_color_types::{LinearRgba, SrgbaTuple};
9use wezterm_dynamic::{FromDynamic, FromDynamicOptions, ToDynamic, Value};
10
11#[derive(Debug, Clone, Copy, FromPrimitive, PartialEq, Eq, FromDynamic, ToDynamic)]
12#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
13#[repr(u8)]
14pub enum AnsiColor {
17 Black = 0,
19 Maroon,
21 Green,
23 Olive,
25 Navy,
27 Purple,
29 Teal,
31 Silver,
33 Grey,
35 Red,
37 Lime,
39 Yellow,
41 Blue,
43 Fuchsia,
45 Aqua,
47 White,
49}
50
51impl From<AnsiColor> for u8 {
52 fn from(col: AnsiColor) -> u8 {
53 col as u8
54 }
55}
56
57#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash)]
60pub struct RgbColor {
61 bits: u32,
62}
63
64impl Into<SrgbaTuple> for RgbColor {
65 fn into(self) -> SrgbaTuple {
66 self.to_tuple_rgba()
67 }
68}
69
70impl RgbColor {
71 pub const fn new_8bpc(red: u8, green: u8, blue: u8) -> Self {
74 Self {
75 bits: ((red as u32) << 16) | ((green as u32) << 8) | blue as u32,
76 }
77 }
78
79 pub fn new_f32(red: f32, green: f32, blue: f32) -> Self {
82 let red = (red * 255.) as u8;
83 let green = (green * 255.) as u8;
84 let blue = (blue * 255.) as u8;
85 Self::new_8bpc(red, green, blue)
86 }
87
88 pub fn to_tuple_rgb8(self) -> (u8, u8, u8) {
91 (
92 (self.bits >> 16) as u8,
93 (self.bits >> 8) as u8,
94 self.bits as u8,
95 )
96 }
97
98 pub fn to_tuple_rgba(self) -> SrgbaTuple {
102 SrgbaTuple(
103 (self.bits >> 16) as u8 as f32 / 255.0,
104 (self.bits >> 8) as u8 as f32 / 255.0,
105 self.bits as u8 as f32 / 255.0,
106 1.0,
107 )
108 }
109
110 pub fn to_linear_tuple_rgba(self) -> LinearRgba {
114 self.to_tuple_rgba().to_linear()
115 }
116
117 pub fn from_named(name: &str) -> Option<RgbColor> {
122 Some(SrgbaTuple::from_named(name)?.into())
123 }
124
125 pub fn to_rgb_string(self) -> String {
127 let (red, green, blue) = self.to_tuple_rgb8();
128 format!("#{:02x}{:02x}{:02x}", red, green, blue)
129 }
130
131 pub fn to_x11_16bit_rgb_string(self) -> String {
133 let (red, green, blue) = self.to_tuple_rgb8();
134 format!(
135 "rgb:{:02x}{:02x}/{:02x}{:02x}/{:02x}{:02x}",
136 red, red, green, green, blue, blue
137 )
138 }
139
140 pub fn from_rgb_str(s: &str) -> Option<RgbColor> {
147 let srgb: SrgbaTuple = s.parse().ok()?;
148 Some(srgb.into())
149 }
150
151 pub fn from_named_or_rgb_string(s: &str) -> Option<Self> {
162 RgbColor::from_rgb_str(&s).or_else(|| RgbColor::from_named(&s))
163 }
164}
165
166impl From<SrgbaTuple> for RgbColor {
167 fn from(srgb: SrgbaTuple) -> RgbColor {
168 let SrgbaTuple(r, g, b, _) = srgb;
169 Self::new_f32(r, g, b)
170 }
171}
172
173#[cfg(feature = "use_serde")]
181impl Serialize for RgbColor {
182 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
183 where
184 S: Serializer,
185 {
186 let s = self.to_rgb_string();
187 s.serialize(serializer)
188 }
189}
190
191#[cfg(feature = "use_serde")]
192impl<'de> Deserialize<'de> for RgbColor {
193 fn deserialize<D>(deserializer: D) -> Result<RgbColor, D::Error>
194 where
195 D: Deserializer<'de>,
196 {
197 let s = String::deserialize(deserializer)?;
198 RgbColor::from_named_or_rgb_string(&s)
199 .ok_or_else(|| format!("unknown color name: {}", s))
200 .map_err(serde::de::Error::custom)
201 }
202}
203
204impl ToDynamic for RgbColor {
205 fn to_dynamic(&self) -> Value {
206 self.to_rgb_string().to_dynamic()
207 }
208}
209
210impl FromDynamic for RgbColor {
211 fn from_dynamic(
212 value: &Value,
213 options: FromDynamicOptions,
214 ) -> Result<Self, wezterm_dynamic::Error> {
215 let s = String::from_dynamic(value, options)?;
216 Ok(RgbColor::from_named_or_rgb_string(&s)
217 .ok_or_else(|| format!("unknown color name: {}", s))?)
218 }
219}
220
221pub type PaletteIndex = u8;
223
224#[derive(Debug, Clone, Copy, Eq, PartialEq)]
229pub enum ColorSpec {
230 Default,
231 PaletteIndex(PaletteIndex),
233 TrueColor(SrgbaTuple),
234}
235
236impl Default for ColorSpec {
237 fn default() -> Self {
238 ColorSpec::Default
239 }
240}
241
242impl From<AnsiColor> for ColorSpec {
243 fn from(col: AnsiColor) -> Self {
244 ColorSpec::PaletteIndex(col as u8)
245 }
246}
247
248impl From<RgbColor> for ColorSpec {
249 fn from(col: RgbColor) -> Self {
250 ColorSpec::TrueColor(col.into())
251 }
252}
253
254impl From<SrgbaTuple> for ColorSpec {
255 fn from(col: SrgbaTuple) -> Self {
256 ColorSpec::TrueColor(col)
257 }
258}
259
260#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
265#[derive(Debug, Clone, Copy, Eq, PartialEq, FromDynamic, ToDynamic, Hash)]
266pub enum ColorAttribute {
267 TrueColorWithPaletteFallback(SrgbaTuple, PaletteIndex),
269 TrueColorWithDefaultFallback(SrgbaTuple),
271 PaletteIndex(PaletteIndex),
273 Default,
275}
276
277impl Default for ColorAttribute {
278 fn default() -> Self {
279 ColorAttribute::Default
280 }
281}
282
283impl From<AnsiColor> for ColorAttribute {
284 fn from(col: AnsiColor) -> Self {
285 ColorAttribute::PaletteIndex(col as u8)
286 }
287}
288
289impl From<ColorSpec> for ColorAttribute {
290 fn from(spec: ColorSpec) -> Self {
291 match spec {
292 ColorSpec::Default => ColorAttribute::Default,
293 ColorSpec::PaletteIndex(idx) => ColorAttribute::PaletteIndex(idx),
294 ColorSpec::TrueColor(color) => ColorAttribute::TrueColorWithDefaultFallback(color),
295 }
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302 #[test]
303 fn from_hsl() {
304 let foo = RgbColor::from_rgb_str("hsl:235 100 50").unwrap();
305 assert_eq!(foo.to_rgb_string(), "#0015ff");
306 }
307
308 #[test]
309 fn from_rgb() {
310 assert!(RgbColor::from_rgb_str("").is_none());
311 assert!(RgbColor::from_rgb_str("#xyxyxy").is_none());
312
313 let black = RgbColor::from_rgb_str("#FFF").unwrap();
314 assert_eq!(black.to_tuple_rgb8(), (0xf0, 0xf0, 0xf0));
315
316 let black = RgbColor::from_rgb_str("#000000").unwrap();
317 assert_eq!(black.to_tuple_rgb8(), (0, 0, 0));
318
319 let grey = RgbColor::from_rgb_str("rgb:D6/D6/D6").unwrap();
320 assert_eq!(grey.to_tuple_rgb8(), (0xd6, 0xd6, 0xd6));
321
322 let grey = RgbColor::from_rgb_str("rgb:f0f0/f0f0/f0f0").unwrap();
323 assert_eq!(grey.to_tuple_rgb8(), (0xf0, 0xf0, 0xf0));
324 }
325
326 #[cfg(feature = "use_serde")]
327 #[test]
328 fn roundtrip_rgbcolor() {
329 let data = varbincode::serialize(&RgbColor::from_named("DarkGreen").unwrap()).unwrap();
330 eprintln!("serialized as {:?}", data);
331 let _decoded: RgbColor = varbincode::deserialize(data.as_slice()).unwrap();
332 }
333}