1use super::CssColor;
7use crate::{context::Theme, frame::Color};
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub enum SystemColor {
16 AccentColor,
18 AccentColorText,
20 ActiveText,
22 ButtonBorder,
24 ButtonFace,
26 ButtonText,
28 Canvas,
30 CanvasText,
32 Field,
34 FieldText,
36 GrayText,
38 Highlight,
40 HighlightText,
42 LinkText,
44 Mark,
46 MarkText,
48 SelectedItem,
50 SelectedItemText,
52 VisitedText,
54}
55
56impl SystemColor {
57 pub(crate) fn parse(name: &str) -> Option<Self> {
61 let (_, system) = SYSTEM
62 .iter()
63 .find(|(keyword, _)| keyword.eq_ignore_ascii_case(name))?;
64 Some(*system)
65 }
66
67 pub(crate) const fn resolve(self, theme: &Theme) -> Color {
69 match self {
70 Self::Canvas => Color::Default,
71 Self::CanvasText | Self::ButtonText | Self::FieldText => theme.fg,
72 Self::AccentColor
73 | Self::ActiveText
74 | Self::Highlight
75 | Self::LinkText
76 | Self::SelectedItem => theme.accent,
77 Self::AccentColorText | Self::HighlightText | Self::MarkText | Self::SelectedItemText => {
78 theme.contrast
79 },
80 Self::ButtonFace | Self::Field => theme.surface,
81 Self::ButtonBorder | Self::GrayText | Self::VisitedText => theme.muted,
82 Self::Mark => theme.warn,
83 }
84 }
85}
86
87const LONGEST: usize = 20;
89
90pub(super) fn parse(name: &str) -> Option<CssColor> {
92 if name.is_empty() || name.len() > LONGEST || !name.is_ascii() {
93 return None;
94 }
95 let mut lower = [0_u8; LONGEST];
96 for (slot, byte) in lower.iter_mut().zip(name.bytes()) {
97 *slot = byte.to_ascii_lowercase();
98 }
99 let needle = std::str::from_utf8(&lower[..name.len()]).ok()?;
100 if needle == "transparent" {
101 return Some(CssColor::Rgba(0, 0, 0, 0.0));
102 }
103 if needle == "currentcolor" {
104 return Some(CssColor::Current);
105 }
106 if let Ok(index) = SYSTEM.binary_search_by(|(keyword, _)| keyword.cmp(&needle)) {
107 return Some(CssColor::System(SYSTEM[index].1));
108 }
109 let index = NAMED
110 .binary_search_by(|(candidate, _)| candidate.cmp(&needle))
111 .ok()?;
112 let rgb = NAMED[index].1;
113 Some(CssColor::rgb((rgb >> 16) as u8, (rgb >> 8) as u8, rgb as u8))
114}
115
116const SYSTEM: &[(&str, SystemColor)] = &[
119 ("accentcolor", SystemColor::AccentColor),
120 ("accentcolortext", SystemColor::AccentColorText),
121 ("activeborder", SystemColor::ButtonBorder),
122 ("activecaption", SystemColor::Canvas),
123 ("activetext", SystemColor::ActiveText),
124 ("appworkspace", SystemColor::Canvas),
125 ("background", SystemColor::Canvas),
126 ("buttonborder", SystemColor::ButtonBorder),
127 ("buttonface", SystemColor::ButtonFace),
128 ("buttonhighlight", SystemColor::ButtonFace),
129 ("buttonshadow", SystemColor::ButtonFace),
130 ("buttontext", SystemColor::ButtonText),
131 ("canvas", SystemColor::Canvas),
132 ("canvastext", SystemColor::CanvasText),
133 ("captiontext", SystemColor::CanvasText),
134 ("field", SystemColor::Field),
135 ("fieldtext", SystemColor::FieldText),
136 ("graytext", SystemColor::GrayText),
137 ("highlight", SystemColor::Highlight),
138 ("highlighttext", SystemColor::HighlightText),
139 ("inactiveborder", SystemColor::ButtonBorder),
140 ("inactivecaption", SystemColor::Canvas),
141 ("inactivecaptiontext", SystemColor::GrayText),
142 ("infobackground", SystemColor::Canvas),
143 ("infotext", SystemColor::CanvasText),
144 ("linktext", SystemColor::LinkText),
145 ("mark", SystemColor::Mark),
146 ("marktext", SystemColor::MarkText),
147 ("menu", SystemColor::Canvas),
148 ("menutext", SystemColor::CanvasText),
149 ("scrollbar", SystemColor::Canvas),
150 ("selecteditem", SystemColor::SelectedItem),
151 ("selecteditemtext", SystemColor::SelectedItemText),
152 ("threeddarkshadow", SystemColor::ButtonBorder),
153 ("threedface", SystemColor::ButtonFace),
154 ("threedhighlight", SystemColor::ButtonBorder),
155 ("threedlightshadow", SystemColor::ButtonBorder),
156 ("threedshadow", SystemColor::ButtonBorder),
157 ("visitedtext", SystemColor::VisitedText),
158 ("window", SystemColor::Canvas),
159 ("windowframe", SystemColor::ButtonBorder),
160 ("windowtext", SystemColor::CanvasText),
161];
162
163const NAMED: &[(&str, u32)] = &[
166 ("aliceblue", 0x00f0_f8ff),
167 ("antiquewhite", 0x00fa_ebd7),
168 ("aqua", 0x0000_ffff),
169 ("aquamarine", 0x007f_ffd4),
170 ("azure", 0x00f0_ffff),
171 ("beige", 0x00f5_f5dc),
172 ("bisque", 0x00ff_e4c4),
173 ("black", 0x0000_0000),
174 ("blanchedalmond", 0x00ff_ebcd),
175 ("blue", 0x0000_00ff),
176 ("blueviolet", 0x008a_2be2),
177 ("brown", 0x00a5_2a2a),
178 ("burlywood", 0x00de_b887),
179 ("cadetblue", 0x005f_9ea0),
180 ("chartreuse", 0x007f_ff00),
181 ("chocolate", 0x00d2_691e),
182 ("coral", 0x00ff_7f50),
183 ("cornflowerblue", 0x0064_95ed),
184 ("cornsilk", 0x00ff_f8dc),
185 ("crimson", 0x00dc_143c),
186 ("cyan", 0x0000_ffff),
187 ("darkblue", 0x0000_008b),
188 ("darkcyan", 0x0000_8b8b),
189 ("darkgoldenrod", 0x00b8_860b),
190 ("darkgray", 0x00a9_a9a9),
191 ("darkgreen", 0x0000_6400),
192 ("darkgrey", 0x00a9_a9a9),
193 ("darkkhaki", 0x00bd_b76b),
194 ("darkmagenta", 0x008b_008b),
195 ("darkolivegreen", 0x0055_6b2f),
196 ("darkorange", 0x00ff_8c00),
197 ("darkorchid", 0x0099_32cc),
198 ("darkred", 0x008b_0000),
199 ("darksalmon", 0x00e9_967a),
200 ("darkseagreen", 0x008f_bc8f),
201 ("darkslateblue", 0x0048_3d8b),
202 ("darkslategray", 0x002f_4f4f),
203 ("darkslategrey", 0x002f_4f4f),
204 ("darkturquoise", 0x0000_ced1),
205 ("darkviolet", 0x0094_00d3),
206 ("deeppink", 0x00ff_1493),
207 ("deepskyblue", 0x0000_bfff),
208 ("dimgray", 0x0069_6969),
209 ("dimgrey", 0x0069_6969),
210 ("dodgerblue", 0x001e_90ff),
211 ("firebrick", 0x00b2_2222),
212 ("floralwhite", 0x00ff_faf0),
213 ("forestgreen", 0x0022_8b22),
214 ("fuchsia", 0x00ff_00ff),
215 ("gainsboro", 0x00dc_dcdc),
216 ("ghostwhite", 0x00f8_f8ff),
217 ("gold", 0x00ff_d700),
218 ("goldenrod", 0x00da_a520),
219 ("gray", 0x0080_8080),
220 ("green", 0x0000_8000),
221 ("greenyellow", 0x00ad_ff2f),
222 ("grey", 0x0080_8080),
223 ("honeydew", 0x00f0_fff0),
224 ("hotpink", 0x00ff_69b4),
225 ("indianred", 0x00cd_5c5c),
226 ("indigo", 0x004b_0082),
227 ("ivory", 0x00ff_fff0),
228 ("khaki", 0x00f0_e68c),
229 ("lavender", 0x00e6_e6fa),
230 ("lavenderblush", 0x00ff_f0f5),
231 ("lawngreen", 0x007c_fc00),
232 ("lemonchiffon", 0x00ff_facd),
233 ("lightblue", 0x00ad_d8e6),
234 ("lightcoral", 0x00f0_8080),
235 ("lightcyan", 0x00e0_ffff),
236 ("lightgoldenrodyellow", 0x00fa_fad2),
237 ("lightgray", 0x00d3_d3d3),
238 ("lightgreen", 0x0090_ee90),
239 ("lightgrey", 0x00d3_d3d3),
240 ("lightpink", 0x00ff_b6c1),
241 ("lightsalmon", 0x00ff_a07a),
242 ("lightseagreen", 0x0020_b2aa),
243 ("lightskyblue", 0x0087_cefa),
244 ("lightslategray", 0x0077_8899),
245 ("lightslategrey", 0x0077_8899),
246 ("lightsteelblue", 0x00b0_c4de),
247 ("lightyellow", 0x00ff_ffe0),
248 ("lime", 0x0000_ff00),
249 ("limegreen", 0x0032_cd32),
250 ("linen", 0x00fa_f0e6),
251 ("magenta", 0x00ff_00ff),
252 ("maroon", 0x0080_0000),
253 ("mediumaquamarine", 0x0066_cdaa),
254 ("mediumblue", 0x0000_00cd),
255 ("mediumorchid", 0x00ba_55d3),
256 ("mediumpurple", 0x0093_70db),
257 ("mediumseagreen", 0x003c_b371),
258 ("mediumslateblue", 0x007b_68ee),
259 ("mediumspringgreen", 0x0000_fa9a),
260 ("mediumturquoise", 0x0048_d1cc),
261 ("mediumvioletred", 0x00c7_1585),
262 ("midnightblue", 0x0019_1970),
263 ("mintcream", 0x00f5_fffa),
264 ("mistyrose", 0x00ff_e4e1),
265 ("moccasin", 0x00ff_e4b5),
266 ("navajowhite", 0x00ff_dead),
267 ("navy", 0x0000_0080),
268 ("oldlace", 0x00fd_f5e6),
269 ("olive", 0x0080_8000),
270 ("olivedrab", 0x006b_8e23),
271 ("orange", 0x00ff_a500),
272 ("orangered", 0x00ff_4500),
273 ("orchid", 0x00da_70d6),
274 ("palegoldenrod", 0x00ee_e8aa),
275 ("palegreen", 0x0098_fb98),
276 ("paleturquoise", 0x00af_eeee),
277 ("palevioletred", 0x00db_7093),
278 ("papayawhip", 0x00ff_efd5),
279 ("peachpuff", 0x00ff_dab9),
280 ("peru", 0x00cd_853f),
281 ("pink", 0x00ff_c0cb),
282 ("plum", 0x00dd_a0dd),
283 ("powderblue", 0x00b0_e0e6),
284 ("purple", 0x0080_0080),
285 ("rebeccapurple", 0x0066_3399),
286 ("red", 0x00ff_0000),
287 ("rosybrown", 0x00bc_8f8f),
288 ("royalblue", 0x0041_69e1),
289 ("saddlebrown", 0x008b_4513),
290 ("salmon", 0x00fa_8072),
291 ("sandybrown", 0x00f4_a460),
292 ("seagreen", 0x002e_8b57),
293 ("seashell", 0x00ff_f5ee),
294 ("sienna", 0x00a0_522d),
295 ("silver", 0x00c0_c0c0),
296 ("skyblue", 0x0087_ceeb),
297 ("slateblue", 0x006a_5acd),
298 ("slategray", 0x0070_8090),
299 ("slategrey", 0x0070_8090),
300 ("snow", 0x00ff_fafa),
301 ("springgreen", 0x0000_ff7f),
302 ("steelblue", 0x0046_82b4),
303 ("tan", 0x00d2_b48c),
304 ("teal", 0x0000_8080),
305 ("thistle", 0x00d8_bfd8),
306 ("tomato", 0x00ff_6347),
307 ("turquoise", 0x0040_e0d0),
308 ("violet", 0x00ee_82ee),
309 ("wheat", 0x00f5_deb3),
310 ("white", 0x00ff_ffff),
311 ("whitesmoke", 0x00f5_f5f5),
312 ("yellow", 0x00ff_ff00),
313 ("yellowgreen", 0x009a_cd32),
314];
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[test]
321 fn tables_are_sorted_for_binary_search() {
322 assert!(NAMED.windows(2).all(|pair| pair[0].0 < pair[1].0));
323 assert!(SYSTEM.windows(2).all(|pair| pair[0].0 < pair[1].0));
324 }
325
326 #[test]
327 fn keywords_resolve_case_insensitively() {
328 assert_eq!(parse("rebeccapurple"), Some(CssColor::rgb(0x66, 0x33, 0x99)));
329 assert_eq!(parse("WHITE"), Some(CssColor::rgb(255, 255, 255)));
330 assert_eq!(parse("LightGoldenrodYellow"), Some(CssColor::rgb(0xfa, 0xfa, 0xd2)));
331 }
332
333 #[test]
334 fn special_keywords_keep_css_semantics() {
335 assert_eq!(parse("transparent"), Some(CssColor::Rgba(0, 0, 0, 0.0)));
336 assert_eq!(parse("CurrentColor"), Some(CssColor::Current));
337 }
338
339 #[test]
340 fn system_colors_parse_including_deprecated_aliases() {
341 assert_eq!(parse("Canvas"), Some(CssColor::System(SystemColor::Canvas)));
342 assert_eq!(parse("HIGHLIGHT"), Some(CssColor::System(SystemColor::Highlight)));
343 assert_eq!(parse("WindowText"), Some(CssColor::System(SystemColor::CanvasText)));
345 assert_eq!(parse("ThreeDShadow"), Some(CssColor::System(SystemColor::ButtonBorder)));
346 assert_eq!(SystemColor::parse("InfoBackground"), Some(SystemColor::Canvas));
347 }
348
349 #[test]
350 fn system_colors_resolve_through_the_theme() {
351 let theme = Theme::default();
352 assert_eq!(SystemColor::Canvas.resolve(&theme), Color::Default);
353 assert_eq!(SystemColor::CanvasText.resolve(&theme), theme.fg);
354 assert_eq!(SystemColor::LinkText.resolve(&theme), theme.accent);
355 assert_eq!(SystemColor::Mark.resolve(&theme), theme.warn);
356 assert_eq!(SystemColor::GrayText.resolve(&theme), theme.muted);
357 assert_eq!(SystemColor::HighlightText.resolve(&theme), theme.contrast);
358 assert_eq!(SystemColor::Field.resolve(&theme), theme.surface);
359 }
360
361 #[test]
362 fn junk_names_are_rejected() {
363 assert_eq!(parse(""), None);
364 assert_eq!(parse("nosuchcolorname"), None);
365 assert_eq!(parse("lightgoldenrodyellowish"), None);
366 assert_eq!(parse("wh\u{ef}te"), None);
367 }
368}