freya_core/values/
color.rs

1use std::fmt;
2
3use freya_engine::prelude::*;
4
5use crate::parsing::{
6    Parse,
7    ParseError,
8};
9
10pub trait DisplayColor {
11    fn fmt_rgb(&self, f: &mut fmt::Formatter) -> fmt::Result;
12    fn fmt_hsl(&self, f: &mut fmt::Formatter) -> fmt::Result;
13}
14
15impl Parse for Color {
16    fn parse(value: &str) -> Result<Self, ParseError> {
17        match value {
18            "red" => Ok(Color::RED),
19            "green" => Ok(Color::GREEN),
20            "blue" => Ok(Color::BLUE),
21            "yellow" => Ok(Color::YELLOW),
22            "black" => Ok(Color::BLACK),
23            "gray" => Ok(Color::GRAY),
24            "white" => Ok(Color::WHITE),
25            "orange" => Ok(Color::from_rgb(255, 165, 0)),
26            "transparent" | "none" => Ok(Color::TRANSPARENT),
27            _ => {
28                if value.starts_with("hsl(") {
29                    parse_hsl(value)
30                } else if value.starts_with("rgb(") {
31                    parse_rgb(value)
32                } else if value.starts_with('#') {
33                    parse_hex_color(value)
34                } else {
35                    Err(ParseError)
36                }
37            }
38        }
39    }
40}
41
42impl DisplayColor for Color {
43    fn fmt_rgb(&self, f: &mut fmt::Formatter) -> fmt::Result {
44        write!(
45            f,
46            "rgb({}, {}, {}, {})",
47            self.r(),
48            self.g(),
49            self.b(),
50            self.a()
51        )
52    }
53
54    fn fmt_hsl(&self, f: &mut fmt::Formatter) -> fmt::Result {
55        // HSV to HSL conversion
56        let hsv = self.to_hsv();
57        let l = hsv.v - (hsv.v * hsv.s / 2.0);
58        let s = if l == 1.0 || l == 0.0 {
59            0.0
60        } else {
61            (hsv.v - l) / f32::min(l, 1.0 - l)
62        };
63
64        write!(
65            f,
66            "hsl({}deg, {}%, {}%, {}%)",
67            hsv.h,
68            s * 100.0,
69            l * 100.0,
70            (self.a() as f32 / 128.0) * 100.0
71        )
72    }
73}
74
75fn parse_rgb(color: &str) -> Result<Color, ParseError> {
76    if !color.ends_with(')') {
77        return Err(ParseError);
78    }
79
80    let color = color.replacen("rgb(", "", 1).replacen(')', "", 1);
81
82    let mut colors = color.split(',');
83
84    let r = colors
85        .next()
86        .ok_or(ParseError)?
87        .trim()
88        .parse::<u8>()
89        .map_err(|_| ParseError)?;
90    let g = colors
91        .next()
92        .ok_or(ParseError)?
93        .trim()
94        .parse::<u8>()
95        .map_err(|_| ParseError)?;
96    let b = colors
97        .next()
98        .ok_or(ParseError)?
99        .trim()
100        .parse::<u8>()
101        .map_err(|_| ParseError)?;
102    let a: Option<&str> = colors.next();
103
104    // There should not be more than 4 components.
105    if colors.next().is_some() {
106        return Err(ParseError);
107    }
108
109    let base_color = Color::from_rgb(r, g, b);
110
111    if let Some(a) = a {
112        Ok(base_color.with_a(parse_alpha(a)?))
113    } else {
114        Ok(base_color)
115    }
116}
117
118pub fn parse_alpha(value: &str) -> Result<u8, ParseError> {
119    let value = value.trim();
120    if let Ok(u8_alpha) = value.parse::<u8>() {
121        Ok(u8_alpha)
122    } else if let Ok(f32_alpha) = value.parse::<f32>() {
123        let a = (255.0 * f32_alpha).clamp(0.0, 255.0).round() as u8;
124        Ok(a)
125    } else {
126        Err(ParseError)
127    }
128}
129
130fn parse_hsl(color: &str) -> Result<Color, ParseError> {
131    if !color.ends_with(')') {
132        return Err(ParseError);
133    }
134
135    let color = color.replacen("hsl(", "", 1).replacen(')', "", 1);
136    let mut colors = color.split(',');
137
138    // Get each color component as a string
139    let h_str = colors.next().ok_or(ParseError)?.trim();
140    let s_str = colors.next().ok_or(ParseError)?.trim();
141    let l_str = colors.next().ok_or(ParseError)?.trim();
142    let a_str: Option<&str> = colors.next();
143
144    // Ensure correct units and lengths.
145    if colors.next().is_some()
146        || !h_str.ends_with("deg")
147        || !s_str.ends_with('%')
148        || !l_str.ends_with('%')
149    {
150        return Err(ParseError);
151    }
152
153    // S, L and A can end in percentage, otherwise its 0.0 - 1.0
154    let h = h_str
155        .replacen("deg", "", 1)
156        .parse::<f32>()
157        .map_err(|_| ParseError)?;
158    let mut s = s_str
159        .replacen('%', "", 1)
160        .parse::<f32>()
161        .map_err(|_| ParseError)?
162        / 100.0;
163    let mut l = l_str
164        .replacen('%', "", 1)
165        .parse::<f32>()
166        .map_err(|_| ParseError)?
167        / 100.0;
168
169    // HSL to HSV Conversion
170    l *= 2.0;
171    s *= if l <= 1.0 { l } else { 2.0 - l };
172    let v = (l + s) / 2.0;
173    s = (2.0 * s) / (l + s);
174    let hsv = HSV::from((h, s, v));
175
176    // Handle alpha formatting and convert to ARGB
177    if let Some(a_str) = a_str {
178        if !s_str.ends_with('%') {
179            return Err(ParseError);
180        }
181
182        let a = a_str
183            .trim()
184            .replace('%', "")
185            .parse::<f32>()
186            .map_err(|_| ParseError)?
187            / 100.0;
188
189        Ok(hsv.to_color((a * 255.0).round() as u8))
190    } else {
191        Ok(hsv.to_color(255))
192    }
193}
194
195fn parse_hex_color(color: &str) -> Result<Color, ParseError> {
196    if color.len() == 7 {
197        let r = u8::from_str_radix(&color[1..3], 16).map_err(|_| ParseError)?;
198        let g = u8::from_str_radix(&color[3..5], 16).map_err(|_| ParseError)?;
199        let b = u8::from_str_radix(&color[5..7], 16).map_err(|_| ParseError)?;
200        Ok(Color::from_rgb(r, g, b))
201    } else {
202        Err(ParseError)
203    }
204}