freya_core/values/
color.rs1use 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 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 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 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 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 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 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 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}