i3status_rs/themes/
color.rs

1use crate::errors::*;
2use serde::de::{self, Deserializer, Visitor};
3use serde::{Deserialize, Serialize, Serializer};
4use smart_default::SmartDefault;
5use std::fmt;
6use std::ops::Add;
7use std::str::FromStr;
8
9/// An RGBA color (red, green, blue, alpha).
10#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
11pub struct Rgba {
12    pub r: u8,
13    pub g: u8,
14    pub b: u8,
15    pub a: u8,
16}
17
18impl Rgba {
19    /// Create a new RGBA color.
20    ///
21    /// `r`: red component (0 to 255).
22    ///
23    /// `g`: green component (0 to 255).
24    ///
25    /// `b`: blue component (0 to 255).
26    ///
27    /// `a`: alpha component (0 to 255).
28    pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
29        Self { r, g, b, a }
30    }
31
32    /// Create a new RGBA color from the `hex` value.
33    ///
34    /// ```let cyan = Rgba::from_hex(0xffffff);```
35    pub fn from_hex(hex: u32) -> Self {
36        let [r, g, b, a] = hex.to_be_bytes();
37        Self { r, g, b, a }
38    }
39}
40
41impl Add for Rgba {
42    type Output = Self;
43    fn add(self, rhs: Self) -> Self::Output {
44        Rgba::new(
45            self.r.saturating_add(rhs.r),
46            self.g.saturating_add(rhs.g),
47            self.b.saturating_add(rhs.b),
48            self.a.saturating_add(rhs.a),
49        )
50    }
51}
52
53/// An HSVA color (hue, saturation, value, alpha).
54#[derive(Copy, Clone, Debug, Default)]
55pub struct Hsva {
56    pub h: f64,
57    pub s: f64,
58    pub v: f64,
59    pub a: u8,
60}
61
62impl Hsva {
63    /// Create a new HSVA color.
64    ///
65    /// `h`: hue component (0 to 360)
66    ///
67    /// `s`: saturation component (0 to 1)
68    ///
69    /// `v`: value component (0 to 1)
70    ///
71    /// `a`: alpha component (0 to 255).
72    pub fn new(h: f64, s: f64, v: f64, a: u8) -> Self {
73        Self { h, s, v, a }
74    }
75}
76
77impl PartialEq for Hsva {
78    fn eq(&self, other: &Self) -> bool {
79        approx(self.h, other.h)
80            && approx(self.s, other.s)
81            && approx(self.v, other.v)
82            && self.a == other.a
83    }
84}
85
86impl From<Rgba> for Hsva {
87    fn from(rgba: Rgba) -> Self {
88        let r = rgba.r as f64 / 255.0;
89        let g = rgba.g as f64 / 255.0;
90        let b = rgba.b as f64 / 255.0;
91
92        let min = r.min(g.min(b));
93        let max = r.max(g.max(b));
94        let delta = max - min;
95
96        let v = max;
97        let s = match max > 1e-3 {
98            true => delta / max,
99            false => 0.0,
100        };
101        let h = match delta == 0.0 {
102            true => 0.0,
103            false => {
104                if r == max {
105                    (g - b) / delta
106                } else if g == max {
107                    2.0 + (b - r) / delta
108                } else {
109                    4.0 + (r - g) / delta
110                }
111            }
112        };
113        let h2 = ((h * 60.0) + 360.0) % 360.0;
114
115        Self::new(h2, s, v, rgba.a)
116    }
117}
118
119impl From<Hsva> for Rgba {
120    fn from(hsva: Hsva) -> Self {
121        let range = (hsva.h / 60.0) as u8;
122        let c = hsva.v * hsva.s;
123        let x = c * (1.0 - (((hsva.h / 60.0) % 2.0) - 1.0).abs());
124        let m = hsva.v - c;
125
126        let cm_scaled = ((c + m) * 255.0) as u8;
127        let xm_scaled = ((x + m) * 255.0) as u8;
128        let m_scaled = (m * 255.0) as u8;
129
130        match range {
131            0 => Self::new(cm_scaled, xm_scaled, m_scaled, hsva.a),
132            1 => Self::new(xm_scaled, cm_scaled, m_scaled, hsva.a),
133            2 => Self::new(m_scaled, cm_scaled, xm_scaled, hsva.a),
134            3 => Self::new(m_scaled, xm_scaled, cm_scaled, hsva.a),
135            4 => Self::new(xm_scaled, m_scaled, cm_scaled, hsva.a),
136            _ => Self::new(cm_scaled, m_scaled, xm_scaled, hsva.a),
137        }
138    }
139}
140
141impl Add for Hsva {
142    type Output = Self;
143    fn add(self, rhs: Self) -> Self::Output {
144        Hsva::new(
145            (self.h + rhs.h) % 360.,
146            (self.s + rhs.s).clamp(0., 1.),
147            (self.v + rhs.v).clamp(0., 1.),
148            self.a.saturating_add(rhs.a),
149        )
150    }
151}
152
153pub fn approx(a: f64, b: f64) -> bool {
154    if a == b {
155        return true;
156    }
157    let eps = 1e-2;
158    let abs_a = a.abs();
159    let abs_b = b.abs();
160    let diff = (abs_a - abs_b).abs();
161    if a == 0.0 || b == 0.0 || abs_a + abs_b < f64::EPSILON {
162        diff < eps * f64::EPSILON
163    } else {
164        diff / (abs_a + abs_b).min(f64::MAX) < eps
165    }
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, SmartDefault)]
169pub enum Color {
170    #[default]
171    None,
172    Auto,
173    Rgba(Rgba),
174    Hsva(Hsva),
175}
176
177impl Color {
178    pub fn skip_ser(&self) -> bool {
179        matches!(self, Self::None | Self::Auto)
180    }
181}
182
183impl Add for Color {
184    type Output = Color;
185    fn add(self, rhs: Self) -> Self::Output {
186        match (self, rhs) {
187            // Do nothing
188            (x, Self::None | Self::Auto) | (Self::None | Self::Auto, x) => x,
189            // Hsva + Hsva => Hsva
190            (Color::Hsva(hsva1), Color::Hsva(hsva2)) => Color::Hsva(hsva1 + hsva2),
191            // Rgba + Rgba => Rgba
192            (Color::Rgba(rgba1), Color::Rgba(rgba2)) => Color::Rgba(rgba1 + rgba2),
193            // Hsva + Rgba => Hsva
194            // Rgba + Hsva => Hsva
195            (Color::Hsva(hsva), Color::Rgba(rgba)) | (Color::Rgba(rgba), Color::Hsva(hsva)) => {
196                Color::Hsva(hsva + rgba.into())
197            }
198        }
199    }
200}
201
202impl FromStr for Color {
203    type Err = Error;
204
205    fn from_str(color: &str) -> Result<Self, Self::Err> {
206        Ok(if color == "none" || color.is_empty() {
207            Color::None
208        } else if color == "auto" {
209            Color::Auto
210        } else if color.starts_with("hsv:") {
211            let err_msg = || format!("'{color}' is not a valid HSVA color");
212            let color = color.split_at(4).1;
213            let mut components = color.split(':').map(|x| x.parse::<f64>().or_error(err_msg));
214            let h = components.next().or_error(err_msg)??;
215            let s = components.next().or_error(err_msg)??;
216            let v = components.next().or_error(err_msg)??;
217            let a = components.next().unwrap_or(Ok(100.))?;
218            Color::Hsva(Hsva::new(h, s / 100., v / 100., (a / 100. * 255.) as u8))
219        } else if color.starts_with("x:") {
220            let name = color.split_at(2).1;
221            super::xresources::get_color(name)?
222                .or_error(|| format!("color '{name}' not defined in ~/.Xresources"))?
223                .parse()
224                .or_error(|| format!("invalid color definition '{name}'"))?
225        } else {
226            let err_msg = || format!("'{color}' is not a valid RGBA color");
227            let rgb = color.get(1..7).or_error(err_msg)?;
228            let a = color.get(7..9).unwrap_or("FF");
229            Color::Rgba(Rgba::from_hex(
230                (u32::from_str_radix(rgb, 16).or_error(err_msg)? << 8)
231                    + u32::from_str_radix(a, 16).or_error(err_msg)?,
232            ))
233        })
234    }
235}
236
237impl Serialize for Color {
238    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
239    where
240        S: Serializer,
241    {
242        let format_rgba =
243            |rgba: Rgba| format!("#{:02X}{:02X}{:02X}{:02X}", rgba.r, rgba.g, rgba.b, rgba.a);
244        match *self {
245            Self::None | Self::Auto => serializer.serialize_none(),
246            Self::Rgba(rgba) => serializer.serialize_str(&format_rgba(rgba)),
247            Self::Hsva(hsva) => serializer.serialize_str(&format_rgba(hsva.into())),
248        }
249    }
250}
251
252impl<'de> Deserialize<'de> for Color {
253    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
254    where
255        D: Deserializer<'de>,
256    {
257        struct ColorVisitor;
258
259        impl Visitor<'_> for ColorVisitor {
260            type Value = Color;
261
262            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
263                formatter.write_str("color")
264            }
265
266            fn visit_str<E>(self, s: &str) -> Result<Color, E>
267            where
268                E: de::Error,
269            {
270                s.parse().serde_error()
271            }
272        }
273
274        deserializer.deserialize_any(ColorVisitor)
275    }
276}