Skip to main content

azul_css/props/basic/
color.rs

1//! CSS color types and parser.
2//!
3//! Core types: [`ColorU`] (u8 RGBA), [`ColorF`] (f32 RGBA), [`ColorOrSystem`]
4//! (concrete color or runtime system-theme reference). The parser supports hex,
5//! `rgb()`/`rgba()`, `hsl()`/`hsla()`, CSS named colors, and `system:*` syntax.
6
7use alloc::string::{String, ToString};
8use core::fmt;
9use crate::corety::AzString;
10use crate::props::basic::error::{ParseFloatError, ParseIntError};
11
12use crate::{
13    impl_option,
14    props::basic::{
15        direction::{
16            parse_direction, CssDirectionParseError, CssDirectionParseErrorOwned, Direction,
17        },
18        length::{PercentageParseError, PercentageValue},
19    },
20};
21
22/// u8-based color, range 0 to 255 (similar to webrenders ColorU)
23#[derive(Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Hash)]
24#[repr(C)]
25pub struct ColorU {
26    pub r: u8,
27    pub g: u8,
28    pub b: u8,
29    pub a: u8,
30}
31
32impl_option!(
33    ColorU,
34    OptionColorU,
35    [Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Hash]
36);
37
38impl Default for ColorU {
39    fn default() -> Self {
40        ColorU::BLACK
41    }
42}
43
44impl fmt::Display for ColorU {
45    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46        write!(
47            f,
48            "rgba({}, {}, {}, {})",
49            self.r,
50            self.g,
51            self.b,
52            self.a as f32 / 255.0
53        )
54    }
55}
56
57impl ColorU {
58    pub const ALPHA_TRANSPARENT: u8 = 0;
59    pub const ALPHA_OPAQUE: u8 = 255;
60    pub const RED: ColorU = ColorU {
61        r: 255,
62        g: 0,
63        b: 0,
64        a: Self::ALPHA_OPAQUE,
65    };
66    pub const GREEN: ColorU = ColorU {
67        r: 0,
68        g: 255,
69        b: 0,
70        a: Self::ALPHA_OPAQUE,
71    };
72    pub const BLUE: ColorU = ColorU {
73        r: 0,
74        g: 0,
75        b: 255,
76        a: Self::ALPHA_OPAQUE,
77    };
78    pub const WHITE: ColorU = ColorU {
79        r: 255,
80        g: 255,
81        b: 255,
82        a: Self::ALPHA_OPAQUE,
83    };
84    pub const BLACK: ColorU = ColorU {
85        r: 0,
86        g: 0,
87        b: 0,
88        a: Self::ALPHA_OPAQUE,
89    };
90    pub const TRANSPARENT: ColorU = ColorU {
91        r: 0,
92        g: 0,
93        b: 0,
94        a: Self::ALPHA_TRANSPARENT,
95    };
96
97    // Additional common colors
98    pub const YELLOW: ColorU = ColorU { r: 255, g: 255, b: 0, a: Self::ALPHA_OPAQUE };
99    pub const CYAN: ColorU = ColorU { r: 0, g: 255, b: 255, a: Self::ALPHA_OPAQUE };
100    pub const MAGENTA: ColorU = ColorU { r: 255, g: 0, b: 255, a: Self::ALPHA_OPAQUE };
101    pub const ORANGE: ColorU = ColorU { r: 255, g: 165, b: 0, a: Self::ALPHA_OPAQUE };
102    pub const PINK: ColorU = ColorU { r: 255, g: 192, b: 203, a: Self::ALPHA_OPAQUE };
103    pub const PURPLE: ColorU = ColorU { r: 128, g: 0, b: 128, a: Self::ALPHA_OPAQUE };
104    pub const BROWN: ColorU = ColorU { r: 139, g: 69, b: 19, a: Self::ALPHA_OPAQUE };
105    pub const GRAY: ColorU = ColorU { r: 128, g: 128, b: 128, a: Self::ALPHA_OPAQUE };
106    pub const LIGHT_GRAY: ColorU = ColorU { r: 211, g: 211, b: 211, a: Self::ALPHA_OPAQUE };
107    pub const DARK_GRAY: ColorU = ColorU { r: 64, g: 64, b: 64, a: Self::ALPHA_OPAQUE };
108    pub const NAVY: ColorU = ColorU { r: 0, g: 0, b: 128, a: Self::ALPHA_OPAQUE };
109    pub const TEAL: ColorU = ColorU { r: 0, g: 128, b: 128, a: Self::ALPHA_OPAQUE };
110    pub const OLIVE: ColorU = ColorU { r: 128, g: 128, b: 0, a: Self::ALPHA_OPAQUE };
111    pub const MAROON: ColorU = ColorU { r: 128, g: 0, b: 0, a: Self::ALPHA_OPAQUE };
112    pub const LIME: ColorU = ColorU { r: 0, g: 255, b: 0, a: Self::ALPHA_OPAQUE };
113    pub const AQUA: ColorU = ColorU { r: 0, g: 255, b: 255, a: Self::ALPHA_OPAQUE };
114    pub const SILVER: ColorU = ColorU { r: 192, g: 192, b: 192, a: Self::ALPHA_OPAQUE };
115    pub const FUCHSIA: ColorU = ColorU { r: 255, g: 0, b: 255, a: Self::ALPHA_OPAQUE };
116    pub const INDIGO: ColorU = ColorU { r: 75, g: 0, b: 130, a: Self::ALPHA_OPAQUE };
117    pub const GOLD: ColorU = ColorU { r: 255, g: 215, b: 0, a: Self::ALPHA_OPAQUE };
118    pub const CORAL: ColorU = ColorU { r: 255, g: 127, b: 80, a: Self::ALPHA_OPAQUE };
119    pub const SALMON: ColorU = ColorU { r: 250, g: 128, b: 114, a: Self::ALPHA_OPAQUE };
120    pub const TURQUOISE: ColorU = ColorU { r: 64, g: 224, b: 208, a: Self::ALPHA_OPAQUE };
121    pub const VIOLET: ColorU = ColorU { r: 238, g: 130, b: 238, a: Self::ALPHA_OPAQUE };
122    pub const CRIMSON: ColorU = ColorU { r: 220, g: 20, b: 60, a: Self::ALPHA_OPAQUE };
123    pub const CHOCOLATE: ColorU = ColorU { r: 210, g: 105, b: 30, a: Self::ALPHA_OPAQUE };
124    pub const SKY_BLUE: ColorU = ColorU { r: 135, g: 206, b: 235, a: Self::ALPHA_OPAQUE };
125    pub const FOREST_GREEN: ColorU = ColorU { r: 34, g: 139, b: 34, a: Self::ALPHA_OPAQUE };
126    pub const SEA_GREEN: ColorU = ColorU { r: 46, g: 139, b: 87, a: Self::ALPHA_OPAQUE };
127    pub const SLATE_GRAY: ColorU = ColorU { r: 112, g: 128, b: 144, a: Self::ALPHA_OPAQUE };
128    pub const MIDNIGHT_BLUE: ColorU = ColorU { r: 25, g: 25, b: 112, a: Self::ALPHA_OPAQUE };
129    pub const DARK_RED: ColorU = ColorU { r: 139, g: 0, b: 0, a: Self::ALPHA_OPAQUE };
130    pub const DARK_GREEN: ColorU = ColorU { r: 0, g: 100, b: 0, a: Self::ALPHA_OPAQUE };
131    pub const DARK_BLUE: ColorU = ColorU { r: 0, g: 0, b: 139, a: Self::ALPHA_OPAQUE };
132    pub const LIGHT_BLUE: ColorU = ColorU { r: 173, g: 216, b: 230, a: Self::ALPHA_OPAQUE };
133    pub const LIGHT_GREEN: ColorU = ColorU { r: 144, g: 238, b: 144, a: Self::ALPHA_OPAQUE };
134    pub const LIGHT_YELLOW: ColorU = ColorU { r: 255, g: 255, b: 224, a: Self::ALPHA_OPAQUE };
135    pub const LIGHT_PINK: ColorU = ColorU { r: 255, g: 182, b: 193, a: Self::ALPHA_OPAQUE };
136
137    // Constructor functions for C API (become AzColorU_red(), AzColorU_cyan(), etc.)
138    pub fn red() -> Self { Self::RED }
139    pub fn green() -> Self { Self::GREEN }
140    pub fn blue() -> Self { Self::BLUE }
141    pub fn white() -> Self { Self::WHITE }
142    pub fn black() -> Self { Self::BLACK }
143    pub fn transparent() -> Self { Self::TRANSPARENT }
144    pub fn yellow() -> Self { Self::YELLOW }
145    pub fn cyan() -> Self { Self::CYAN }
146    pub fn magenta() -> Self { Self::MAGENTA }
147    pub fn orange() -> Self { Self::ORANGE }
148    pub fn pink() -> Self { Self::PINK }
149    pub fn purple() -> Self { Self::PURPLE }
150    pub fn brown() -> Self { Self::BROWN }
151    pub fn gray() -> Self { Self::GRAY }
152    pub fn light_gray() -> Self { Self::LIGHT_GRAY }
153    pub fn dark_gray() -> Self { Self::DARK_GRAY }
154    pub fn navy() -> Self { Self::NAVY }
155    pub fn teal() -> Self { Self::TEAL }
156    pub fn olive() -> Self { Self::OLIVE }
157    pub fn maroon() -> Self { Self::MAROON }
158    pub fn lime() -> Self { Self::LIME }
159    pub fn aqua() -> Self { Self::AQUA }
160    pub fn silver() -> Self { Self::SILVER }
161    pub fn fuchsia() -> Self { Self::FUCHSIA }
162    pub fn indigo() -> Self { Self::INDIGO }
163    pub fn gold() -> Self { Self::GOLD }
164    pub fn coral() -> Self { Self::CORAL }
165    pub fn salmon() -> Self { Self::SALMON }
166    pub fn turquoise() -> Self { Self::TURQUOISE }
167    pub fn violet() -> Self { Self::VIOLET }
168    pub fn crimson() -> Self { Self::CRIMSON }
169    pub fn chocolate() -> Self { Self::CHOCOLATE }
170    pub fn sky_blue() -> Self { Self::SKY_BLUE }
171    pub fn forest_green() -> Self { Self::FOREST_GREEN }
172    pub fn sea_green() -> Self { Self::SEA_GREEN }
173    pub fn slate_gray() -> Self { Self::SLATE_GRAY }
174    pub fn midnight_blue() -> Self { Self::MIDNIGHT_BLUE }
175    pub fn dark_red() -> Self { Self::DARK_RED }
176    pub fn dark_green() -> Self { Self::DARK_GREEN }
177    pub fn dark_blue() -> Self { Self::DARK_BLUE }
178    pub fn light_blue() -> Self { Self::LIGHT_BLUE }
179    pub fn light_green() -> Self { Self::LIGHT_GREEN }
180    pub fn light_yellow() -> Self { Self::LIGHT_YELLOW }
181    pub fn light_pink() -> Self { Self::LIGHT_PINK }
182
183    /// Creates a new color with RGBA values.
184    pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
185        Self { r, g, b, a }
186    }
187    /// Creates a new color with RGB values (alpha = 255).
188    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
189        Self { r, g, b, a: 255 }
190    }
191    /// Alias for `rgba` - kept for internal compatibility, not exposed in FFI.
192    #[inline(always)]
193    pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
194        Self::rgba(r, g, b, a)
195    }
196    /// Alias for `rgb` - kept for internal compatibility, not exposed in FFI.
197    #[inline(always)]
198    pub const fn new_rgb(r: u8, g: u8, b: u8) -> Self {
199        Self::rgb(r, g, b)
200    }
201
202    /// Linearly interpolate all four RGBA channels between `self` and `other`.
203    /// `t = 0.0` returns `self`, `t = 1.0` returns `other`.
204    pub fn interpolate(&self, other: &Self, t: f32) -> Self {
205        Self {
206            r: libm::roundf(self.r as f32 + (other.r as f32 - self.r as f32) * t) as u8,
207            g: libm::roundf(self.g as f32 + (other.g as f32 - self.g as f32) * t) as u8,
208            b: libm::roundf(self.b as f32 + (other.b as f32 - self.b as f32) * t) as u8,
209            a: libm::roundf(self.a as f32 + (other.a as f32 - self.a as f32) * t) as u8,
210        }
211    }
212    
213    /// Lighten a color by a percentage (0.0 to 1.0).
214    /// Returns a new color blended towards white, preserving the original alpha.
215    pub fn lighten(&self, amount: f32) -> Self {
216        let mut c = self.interpolate(&Self::WHITE, amount.clamp(0.0, 1.0));
217        c.a = self.a;
218        c
219    }
220
221    /// Darken a color by a percentage (0.0 to 1.0).
222    /// Returns a new color blended towards black, preserving the original alpha.
223    pub fn darken(&self, amount: f32) -> Self {
224        let mut c = self.interpolate(&Self::BLACK, amount.clamp(0.0, 1.0));
225        c.a = self.a;
226        c
227    }
228    
229    /// Mix two colors together with a given ratio (0.0 = self, 1.0 = other).
230    pub fn mix(&self, other: &Self, ratio: f32) -> Self {
231        self.interpolate(other, ratio.clamp(0.0, 1.0))
232    }
233    
234    /// Create a hover variant (slightly lighter for dark colors, darker for light colors).
235    /// This is useful for button hover states.
236    pub fn hover_variant(&self) -> Self {
237        let luminance = self.relative_luminance();
238        if luminance > 0.5 {
239            self.darken(0.08)
240        } else {
241            self.lighten(0.12)
242        }
243    }
244
245    /// Create an active/pressed variant (darker than hover).
246    /// This is useful for button active states.
247    pub fn active_variant(&self) -> Self {
248        let luminance = self.relative_luminance();
249        if luminance > 0.5 {
250            self.darken(0.15)
251        } else {
252            self.lighten(0.05)
253        }
254    }
255    
256    /// Calculate approximate luminance (0.0 = black, 1.0 = white).
257    ///
258    /// **Note:** This applies BT.709 coefficients directly to gamma-encoded sRGB
259    /// values without linearizing first, so it is only an approximation.
260    /// For accurate results (e.g. WCAG contrast checks), use [`relative_luminance()`].
261    pub fn luminance(&self) -> f32 {
262        let r = (self.r as f32) / 255.0;
263        let g = (self.g as f32) / 255.0;
264        let b = (self.b as f32) / 255.0;
265        0.2126 * r + 0.7152 * g + 0.0722 * b
266    }
267
268    /// Returns white or black text color for best contrast on this background.
269    pub fn contrast_text(&self) -> Self {
270        self.best_contrast_text()
271    }
272    
273    // ============================================================
274    // WCAG Accessibility and Contrast Helpers
275    // Based on W3C WCAG 2.1 guidelines and Chromium research
276    // ============================================================
277    
278    /// Converts a single sRGB channel to linear RGB.
279    /// Used for accurate luminance and contrast calculations.
280    fn srgb_to_linear(c: f32) -> f32 {
281        if c <= 0.03928 {
282            c / 12.92
283        } else {
284            libm::powf((c + 0.055) / 1.055, 2.4)
285        }
286    }
287    
288    /// Calculate relative luminance per WCAG 2.1 specification.
289    /// Returns a value between 0.0 (darkest) and 1.0 (lightest).
290    /// Uses the sRGB to linear conversion for accurate results.
291    pub fn relative_luminance(&self) -> f32 {
292        let r = Self::srgb_to_linear((self.r as f32) / 255.0);
293        let g = Self::srgb_to_linear((self.g as f32) / 255.0);
294        let b = Self::srgb_to_linear((self.b as f32) / 255.0);
295        0.2126 * r + 0.7152 * g + 0.0722 * b
296    }
297    
298    /// Calculate the contrast ratio between this color and another.
299    /// Returns a value between 1.0 (no contrast) and 21.0 (max contrast).
300    /// 
301    /// WCAG 2.1 requirements:
302    /// - AA normal text: >= 4.5:1
303    /// - AA large text: >= 3.0:1
304    /// - AAA normal text: >= 7.0:1
305    /// - AAA large text: >= 4.5:1
306    pub fn contrast_ratio(&self, other: &Self) -> f32 {
307        let l1 = self.relative_luminance();
308        let l2 = other.relative_luminance();
309        let lighter = if l1 > l2 { l1 } else { l2 };
310        let darker = if l1 > l2 { l2 } else { l1 };
311        (lighter + 0.05) / (darker + 0.05)
312    }
313    
314    /// Check if the contrast ratio meets WCAG AA requirements for normal text (>= 4.5:1).
315    pub fn meets_wcag_aa(&self, other: &Self) -> bool {
316        self.contrast_ratio(other) >= 4.5
317    }
318    
319    /// Check if the contrast ratio meets WCAG AA requirements for large text (>= 3.0:1).
320    /// Large text is defined as 18pt+ or 14pt+ bold.
321    pub fn meets_wcag_aa_large(&self, other: &Self) -> bool {
322        self.contrast_ratio(other) >= 3.0
323    }
324    
325    /// Check if the contrast ratio meets WCAG AAA requirements for normal text (>= 7.0:1).
326    pub fn meets_wcag_aaa(&self, other: &Self) -> bool {
327        self.contrast_ratio(other) >= 7.0
328    }
329    
330    /// Check if the contrast ratio meets WCAG AAA requirements for large text (>= 4.5:1).
331    pub fn meets_wcag_aaa_large(&self, other: &Self) -> bool {
332        self.contrast_ratio(other) >= 4.5
333    }
334    
335    /// Returns true if this color is considered "light" (relative luminance > 0.5).
336    /// Useful for determining if dark or light text should be used.
337    pub fn is_light(&self) -> bool {
338        self.relative_luminance() > 0.5
339    }
340
341    /// Returns true if this color is considered "dark" (relative luminance <= 0.5).
342    pub fn is_dark(&self) -> bool {
343        self.relative_luminance() <= 0.5
344    }
345    
346    /// Suggest the best text color (black or white) for this background,
347    /// ensuring WCAG AA compliance for normal text.
348    /// 
349    /// If neither black nor white meets AA requirements (unlikely), 
350    /// returns the one with higher contrast.
351    pub fn best_contrast_text(&self) -> Self {
352        let white_contrast = self.contrast_ratio(&Self::WHITE);
353        let black_contrast = self.contrast_ratio(&Self::BLACK);
354        
355        if white_contrast >= black_contrast {
356            Self::WHITE
357        } else {
358            Self::BLACK
359        }
360    }
361    
362    /// Adjust the color to ensure it meets the minimum contrast ratio against a background.
363    /// Lightens or darkens the color as needed.
364    /// 
365    /// Returns the original color if it already meets the requirement,
366    /// otherwise returns an adjusted color that meets the minimum contrast.
367    pub fn ensure_contrast(&self, background: &Self, min_ratio: f32) -> Self {
368        let current_ratio = self.contrast_ratio(background);
369        if current_ratio >= min_ratio {
370            return *self;
371        }
372        
373        // Determine if we should lighten or darken
374        let bg_luminance = background.relative_luminance();
375        let should_lighten = bg_luminance < 0.5;
376        
377        // Binary search for the right amount
378        let mut low = 0.0f32;
379        let mut high = 1.0f32;
380        let mut result = *self;
381        
382        for _ in 0..16 {
383            let mid = (low + high) / 2.0;
384            let candidate = if should_lighten {
385                self.lighten(mid)
386            } else {
387                self.darken(mid)
388            };
389            
390            if candidate.contrast_ratio(background) >= min_ratio {
391                result = candidate;
392                high = mid;
393            } else {
394                low = mid;
395            }
396        }
397        
398        result
399    }
400    
401    /// Calculate the APCA (Accessible Perceptual Contrast Algorithm) contrast.
402    /// This is a newer algorithm that may replace WCAG contrast in future standards.
403    /// Returns a value between -108 (white on black) and 106 (black on white).
404    ///
405    /// **Note:** This is an approximation — it reuses the WCAG piecewise sRGB
406    /// linearization and BT.709 luminance coefficients rather than the APCA-specific
407    /// TRC exponents and coefficients from the full 0.0.98G specification.
408    ///
409    /// The sign indicates polarity (negative = light text on dark bg).
410    /// For most purposes, use the absolute value.
411    pub fn apca_contrast(&self, background: &Self) -> f32 {
412        // Convert to Y (luminance) using sRGB TRC
413        let text_y = self.relative_luminance();
414        let bg_y = background.relative_luminance();
415        
416        // Soft clamp
417        let text_y = if text_y < 0.0 { 0.0 } else { text_y };
418        let bg_y = if bg_y < 0.0 { 0.0 } else { bg_y };
419        
420        // APCA 0.0.98G constants
421        const NORMBLKTXT: f32 = 0.56;
422        const NORMWHT: f32 = 0.57;
423        const REVTXT: f32 = 0.62;
424        const REVWHT: f32 = 0.65;
425        const BLKTHRS: f32 = 0.022;
426        const SCALEBLKT: f32 = 1.414;
427        const SCALEWHT: f32 = 1.14;
428        
429        // Clamp black levels
430        let txt_clamp = if text_y < BLKTHRS { 
431            text_y + libm::powf(BLKTHRS - text_y, SCALEBLKT)
432        } else { 
433            text_y 
434        };
435        let bg_clamp = if bg_y < BLKTHRS { 
436            bg_y + libm::powf(BLKTHRS - bg_y, SCALEBLKT)
437        } else { 
438            bg_y 
439        };
440        
441        // Calculate contrast
442        if bg_clamp > txt_clamp {
443            // Dark text on light bg
444            let s = (libm::powf(bg_clamp, NORMWHT) - libm::powf(txt_clamp, NORMBLKTXT)) * SCALEWHT;
445            if s < 0.1 { 0.0 } else { s * 100.0 }
446        } else {
447            // Light text on dark bg
448            let s = (libm::powf(bg_clamp, REVWHT) - libm::powf(txt_clamp, REVTXT)) * SCALEWHT;
449            if s > -0.1 { 0.0 } else { s * 100.0 }
450        }
451    }
452    
453    /// Check if the APCA contrast meets the recommended minimum for body text (|Lc| >= 60).
454    pub fn meets_apca_body(&self, background: &Self) -> bool {
455        libm::fabsf(self.apca_contrast(background)) >= 60.0
456    }
457    
458    /// Check if the APCA contrast meets the minimum for large text (|Lc| >= 45).
459    pub fn meets_apca_large(&self, background: &Self) -> bool {
460        libm::fabsf(self.apca_contrast(background)) >= 45.0
461    }
462    
463    /// Set the alpha channel while keeping RGB values.
464    pub fn with_alpha(&self, a: u8) -> Self {
465        Self { r: self.r, g: self.g, b: self.b, a }
466    }
467    
468    /// Set the alpha as a float (0.0 to 1.0).
469    pub fn with_alpha_f32(&self, a: f32) -> Self {
470        self.with_alpha((a.clamp(0.0, 1.0) * 255.0) as u8)
471    }
472    
473    /// Invert the color (keeping alpha).
474    pub fn invert(&self) -> Self {
475        Self {
476            r: 255 - self.r,
477            g: 255 - self.g,
478            b: 255 - self.b,
479            a: self.a,
480        }
481    }
482    
483    /// Convert to grayscale using luminance weights.
484    pub fn to_grayscale(&self) -> Self {
485        let gray = (0.299 * self.r as f32 + 0.587 * self.g as f32 + 0.114 * self.b as f32) as u8;
486        Self { r: gray, g: gray, b: gray, a: self.a }
487    }
488
489    /// Returns `true` if the alpha channel is not fully opaque (i.e. `a != 255`).
490    pub const fn has_alpha(&self) -> bool {
491        self.a != Self::ALPHA_OPAQUE
492    }
493
494    /// Format the color as an 8-digit lowercase hex string (e.g. `#ff0000ff`).
495    pub fn to_hash(&self) -> String {
496        format!("#{:02x}{:02x}{:02x}{:02x}", self.r, self.g, self.b, self.a)
497    }
498
499    // ============================================================
500    // Elementary OS color palette (with shade parameter 100-900)
501    // ============================================================
502
503    /// Strawberry color palette (shade: 100, 300, 500, 700, 900)
504    pub fn strawberry(shade: usize) -> Self {
505        match shade {
506            0..=200 => Self::rgb(0xff, 0x8c, 0x82),   // 100: #ff8c82
507            201..=400 => Self::rgb(0xed, 0x53, 0x53), // 300: #ed5353
508            401..=600 => Self::rgb(0xc6, 0x26, 0x2e), // 500: #c6262e
509            601..=800 => Self::rgb(0xa1, 0x07, 0x05), // 700: #a10705
510            _ => Self::rgb(0x7a, 0x00, 0x00),         // 900: #7a0000
511        }
512    }
513
514    /// Orange color palette (shade: 100, 300, 500, 700, 900)
515    pub fn palette_orange(shade: usize) -> Self {
516        match shade {
517            0..=200 => Self::rgb(0xff, 0xc2, 0x7d),   // 100: #ffc27d
518            201..=400 => Self::rgb(0xff, 0xa1, 0x54), // 300: #ffa154
519            401..=600 => Self::rgb(0xf3, 0x73, 0x29), // 500: #f37329
520            601..=800 => Self::rgb(0xcc, 0x3b, 0x02), // 700: #cc3b02
521            _ => Self::rgb(0xa6, 0x21, 0x00),         // 900: #a62100
522        }
523    }
524
525    /// Banana color palette (shade: 100, 300, 500, 700, 900)
526    pub fn banana(shade: usize) -> Self {
527        match shade {
528            0..=200 => Self::rgb(0xff, 0xf3, 0x94),   // 100: #fff394
529            201..=400 => Self::rgb(0xff, 0xe1, 0x6b), // 300: #ffe16b
530            401..=600 => Self::rgb(0xf9, 0xc4, 0x40), // 500: #f9c440
531            601..=800 => Self::rgb(0xd4, 0x8e, 0x15), // 700: #d48e15
532            _ => Self::rgb(0xad, 0x5f, 0x00),         // 900: #ad5f00
533        }
534    }
535
536    /// Lime color palette (shade: 100, 300, 500, 700, 900)
537    pub fn palette_lime(shade: usize) -> Self {
538        match shade {
539            0..=200 => Self::rgb(0xd1, 0xff, 0x82),   // 100: #d1ff82
540            201..=400 => Self::rgb(0x9b, 0xdb, 0x4d), // 300: #9bdb4d
541            401..=600 => Self::rgb(0x68, 0xb7, 0x23), // 500: #68b723
542            601..=800 => Self::rgb(0x3a, 0x91, 0x04), // 700: #3a9104
543            _ => Self::rgb(0x20, 0x6b, 0x00),         // 900: #206b00
544        }
545    }
546
547    /// Mint color palette (shade: 100, 300, 500, 700, 900)
548    pub fn mint(shade: usize) -> Self {
549        match shade {
550            0..=200 => Self::rgb(0x89, 0xff, 0xdd),   // 100: #89ffdd
551            201..=400 => Self::rgb(0x43, 0xd6, 0xb5), // 300: #43d6b5
552            401..=600 => Self::rgb(0x28, 0xbc, 0xa3), // 500: #28bca3
553            601..=800 => Self::rgb(0x0e, 0x9a, 0x83), // 700: #0e9a83
554            _ => Self::rgb(0x00, 0x73, 0x67),         // 900: #007367
555        }
556    }
557
558    /// Blueberry color palette (shade: 100, 300, 500, 700, 900)
559    pub fn blueberry(shade: usize) -> Self {
560        match shade {
561            0..=200 => Self::rgb(0x8c, 0xd5, 0xff),   // 100: #8cd5ff
562            201..=400 => Self::rgb(0x64, 0xba, 0xff), // 300: #64baff
563            401..=600 => Self::rgb(0x36, 0x89, 0xe6), // 500: #3689e6
564            601..=800 => Self::rgb(0x0d, 0x52, 0xbf), // 700: #0d52bf
565            _ => Self::rgb(0x00, 0x2e, 0x99),         // 900: #002e99
566        }
567    }
568
569    /// Grape color palette (shade: 100, 300, 500, 700, 900)
570    pub fn grape(shade: usize) -> Self {
571        match shade {
572            0..=200 => Self::rgb(0xe4, 0xc6, 0xfa),   // 100: #e4c6fa
573            201..=400 => Self::rgb(0xcd, 0x9e, 0xf7), // 300: #cd9ef7
574            401..=600 => Self::rgb(0xa5, 0x6d, 0xe2), // 500: #a56de2
575            601..=800 => Self::rgb(0x72, 0x39, 0xb3), // 700: #7239b3
576            _ => Self::rgb(0x45, 0x29, 0x81),         // 900: #452981
577        }
578    }
579
580    /// Bubblegum color palette (shade: 100, 300, 500, 700, 900)
581    pub fn bubblegum(shade: usize) -> Self {
582        match shade {
583            0..=200 => Self::rgb(0xfe, 0x9a, 0xb8),   // 100: #fe9ab8
584            201..=400 => Self::rgb(0xf4, 0x67, 0x9d), // 300: #f4679d
585            401..=600 => Self::rgb(0xde, 0x3e, 0x80), // 500: #de3e80
586            601..=800 => Self::rgb(0xbc, 0x24, 0x5d), // 700: #bc245d
587            _ => Self::rgb(0x91, 0x0e, 0x38),         // 900: #910e38
588        }
589    }
590
591    /// Cocoa color palette (shade: 100, 300, 500, 700, 900)
592    pub fn cocoa(shade: usize) -> Self {
593        match shade {
594            0..=200 => Self::rgb(0xa3, 0x90, 0x7c),   // 100: #a3907c
595            201..=400 => Self::rgb(0x8a, 0x71, 0x5e), // 300: #8a715e
596            401..=600 => Self::rgb(0x71, 0x53, 0x44), // 500: #715344
597            601..=800 => Self::rgb(0x57, 0x39, 0x2d), // 700: #57392d
598            _ => Self::rgb(0x3d, 0x21, 0x1b),         // 900: #3d211b
599        }
600    }
601
602    /// Silver color palette (shade: 100, 300, 500, 700, 900)
603    pub fn palette_silver(shade: usize) -> Self {
604        match shade {
605            0..=200 => Self::rgb(0xfa, 0xfa, 0xfa),   // 100: #fafafa
606            201..=400 => Self::rgb(0xd4, 0xd4, 0xd4), // 300: #d4d4d4
607            401..=600 => Self::rgb(0xab, 0xac, 0xae), // 500: #abacae
608            601..=800 => Self::rgb(0x7e, 0x80, 0x87), // 700: #7e8087
609            _ => Self::rgb(0x55, 0x57, 0x61),         // 900: #555761
610        }
611    }
612
613    /// Slate color palette (shade: 100, 300, 500, 700, 900)
614    pub fn slate(shade: usize) -> Self {
615        match shade {
616            0..=200 => Self::rgb(0x95, 0xa3, 0xab),   // 100: #95a3ab
617            201..=400 => Self::rgb(0x66, 0x78, 0x85), // 300: #667885
618            401..=600 => Self::rgb(0x48, 0x5a, 0x6c), // 500: #485a6c
619            601..=800 => Self::rgb(0x27, 0x34, 0x45), // 700: #273445
620            _ => Self::rgb(0x0e, 0x14, 0x1f),         // 900: #0e141f
621        }
622    }
623
624    /// Dark color palette (shade: 100, 300, 500, 700, 900)
625    pub fn dark(shade: usize) -> Self {
626        match shade {
627            0..=200 => Self::rgb(0x66, 0x66, 0x66),   // 100: #666
628            201..=400 => Self::rgb(0x4d, 0x4d, 0x4d), // 300: #4d4d4d
629            401..=600 => Self::rgb(0x33, 0x33, 0x33), // 500: #333
630            601..=800 => Self::rgb(0x1a, 0x1a, 0x1a), // 700: #1a1a1a
631            _ => Self::rgb(0x00, 0x00, 0x00),         // 900: #000
632        }
633    }
634
635    // ============================================================
636    // Apple System Colors (light and dark variants)
637    // ============================================================
638
639    /// Apple Red (light mode)
640    pub fn apple_red() -> Self { Self::rgb(255, 59, 48) }
641    /// Apple Red (dark mode)
642    pub fn apple_red_dark() -> Self { Self::rgb(255, 69, 58) }
643    /// Apple Orange (light mode)
644    pub fn apple_orange() -> Self { Self::rgb(255, 149, 0) }
645    /// Apple Orange (dark mode)
646    pub fn apple_orange_dark() -> Self { Self::rgb(255, 159, 10) }
647    /// Apple Yellow (light mode)
648    pub fn apple_yellow() -> Self { Self::rgb(255, 204, 0) }
649    /// Apple Yellow (dark mode)
650    pub fn apple_yellow_dark() -> Self { Self::rgb(255, 214, 10) }
651    /// Apple Green (light mode)
652    pub fn apple_green() -> Self { Self::rgb(40, 205, 65) }
653    /// Apple Green (dark mode)
654    pub fn apple_green_dark() -> Self { Self::rgb(40, 215, 75) }
655    /// Apple Mint (light mode)
656    pub fn apple_mint() -> Self { Self::rgb(0, 199, 190) }
657    /// Apple Mint (dark mode)
658    pub fn apple_mint_dark() -> Self { Self::rgb(102, 212, 207) }
659    /// Apple Teal (light mode)
660    pub fn apple_teal() -> Self { Self::rgb(89, 173, 196) }
661    /// Apple Teal (dark mode)
662    pub fn apple_teal_dark() -> Self { Self::rgb(106, 196, 220) }
663    /// Apple Cyan (light mode)
664    pub fn apple_cyan() -> Self { Self::rgb(85, 190, 240) }
665    /// Apple Cyan (dark mode)
666    pub fn apple_cyan_dark() -> Self { Self::rgb(90, 200, 245) }
667    /// Apple Blue (light mode)
668    pub fn apple_blue() -> Self { Self::rgb(0, 122, 255) }
669    /// Apple Blue (dark mode)
670    pub fn apple_blue_dark() -> Self { Self::rgb(10, 132, 255) }
671    /// Apple Indigo (light mode)
672    pub fn apple_indigo() -> Self { Self::rgb(88, 86, 214) }
673    /// Apple Indigo (dark mode)
674    pub fn apple_indigo_dark() -> Self { Self::rgb(94, 92, 230) }
675    /// Apple Purple (light mode)
676    pub fn apple_purple() -> Self { Self::rgb(175, 82, 222) }
677    /// Apple Purple (dark mode)
678    pub fn apple_purple_dark() -> Self { Self::rgb(191, 90, 242) }
679    /// Apple Pink (light mode)
680    pub fn apple_pink() -> Self { Self::rgb(255, 45, 85) }
681    /// Apple Pink (dark mode)
682    pub fn apple_pink_dark() -> Self { Self::rgb(255, 55, 95) }
683    /// Apple Brown (light mode)
684    pub fn apple_brown() -> Self { Self::rgb(162, 132, 94) }
685    /// Apple Brown (dark mode)
686    pub fn apple_brown_dark() -> Self { Self::rgb(172, 142, 104) }
687    /// Apple Gray (light mode)
688    pub fn apple_gray() -> Self { Self::rgb(142, 142, 147) }
689    /// Apple Gray (dark mode)
690    pub fn apple_gray_dark() -> Self { Self::rgb(152, 152, 157) }
691
692    // ============================================================
693    // Bootstrap-style semantic button colors
694    // These provide consistent button styling across platforms
695    // ============================================================
696
697    /// Primary button color (blue) - used for main actions
698    pub fn bootstrap_primary() -> Self { Self::rgb(13, 110, 253) }
699    pub fn bootstrap_primary_hover() -> Self { Self::rgb(11, 94, 215) }
700    pub fn bootstrap_primary_active() -> Self { Self::rgb(10, 88, 202) }
701    
702    /// Secondary button color (gray) - used for secondary actions
703    pub fn bootstrap_secondary() -> Self { Self::rgb(108, 117, 125) }
704    pub fn bootstrap_secondary_hover() -> Self { Self::rgb(92, 99, 106) }
705    pub fn bootstrap_secondary_active() -> Self { Self::rgb(86, 94, 100) }
706    
707    /// Success button color (green) - used for confirmations
708    pub fn bootstrap_success() -> Self { Self::rgb(25, 135, 84) }
709    pub fn bootstrap_success_hover() -> Self { Self::rgb(21, 115, 71) }
710    pub fn bootstrap_success_active() -> Self { Self::rgb(20, 108, 67) }
711    
712    /// Danger button color (red) - used for destructive actions
713    pub fn bootstrap_danger() -> Self { Self::rgb(220, 53, 69) }
714    pub fn bootstrap_danger_hover() -> Self { Self::rgb(187, 45, 59) }
715    pub fn bootstrap_danger_active() -> Self { Self::rgb(176, 42, 55) }
716    
717    /// Warning button color (yellow) - used for warnings, uses BLACK text
718    pub fn bootstrap_warning() -> Self { Self::rgb(255, 193, 7) }
719    pub fn bootstrap_warning_hover() -> Self { Self::rgb(255, 202, 44) }
720    pub fn bootstrap_warning_active() -> Self { Self::rgb(255, 205, 57) }
721    
722    /// Info button color (teal/cyan) - used for informational actions
723    pub fn bootstrap_info() -> Self { Self::rgb(13, 202, 240) }
724    pub fn bootstrap_info_hover() -> Self { Self::rgb(49, 210, 242) }
725    pub fn bootstrap_info_active() -> Self { Self::rgb(61, 213, 243) }
726    
727    /// Light button color - used for light-themed buttons
728    pub fn bootstrap_light() -> Self { Self::rgb(248, 249, 250) }
729    pub fn bootstrap_light_hover() -> Self { Self::rgb(233, 236, 239) }
730    pub fn bootstrap_light_active() -> Self { Self::rgb(218, 222, 226) }
731    
732    /// Dark button color - used for dark-themed buttons
733    pub fn bootstrap_dark() -> Self { Self::rgb(33, 37, 41) }
734    pub fn bootstrap_dark_hover() -> Self { Self::rgb(66, 70, 73) }
735    pub fn bootstrap_dark_active() -> Self { Self::rgb(78, 81, 84) }
736    
737    /// Link button text color
738    pub fn bootstrap_link() -> Self { Self::rgb(13, 110, 253) }
739    pub fn bootstrap_link_hover() -> Self { Self::rgb(10, 88, 202) }
740}
741
742/// f32-based color, range 0.0 to 1.0 (similar to webrenders ColorF)
743#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
744pub struct ColorF {
745    pub r: f32,
746    pub g: f32,
747    pub b: f32,
748    pub a: f32,
749}
750
751impl Default for ColorF {
752    fn default() -> Self {
753        ColorF::BLACK
754    }
755}
756
757impl fmt::Display for ColorF {
758    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
759        write!(
760            f,
761            "rgba({}, {}, {}, {})",
762            self.r * 255.0,
763            self.g * 255.0,
764            self.b * 255.0,
765            self.a
766        )
767    }
768}
769
770impl ColorF {
771    pub const ALPHA_TRANSPARENT: f32 = 0.0;
772    pub const ALPHA_OPAQUE: f32 = 1.0;
773    pub const WHITE: ColorF = ColorF {
774        r: 1.0,
775        g: 1.0,
776        b: 1.0,
777        a: Self::ALPHA_OPAQUE,
778    };
779    pub const BLACK: ColorF = ColorF {
780        r: 0.0,
781        g: 0.0,
782        b: 0.0,
783        a: Self::ALPHA_OPAQUE,
784    };
785    pub const TRANSPARENT: ColorF = ColorF {
786        r: 0.0,
787        g: 0.0,
788        b: 0.0,
789        a: Self::ALPHA_TRANSPARENT,
790    };
791}
792
793impl From<ColorU> for ColorF {
794    fn from(input: ColorU) -> ColorF {
795        ColorF {
796            r: (input.r as f32) / 255.0,
797            g: (input.g as f32) / 255.0,
798            b: (input.b as f32) / 255.0,
799            a: (input.a as f32) / 255.0,
800        }
801    }
802}
803
804impl From<ColorF> for ColorU {
805    fn from(input: ColorF) -> ColorU {
806        ColorU {
807            r: (input.r.min(1.0) * 255.0) as u8,
808            g: (input.g.min(1.0) * 255.0) as u8,
809            b: (input.b.min(1.0) * 255.0) as u8,
810            a: (input.a.min(1.0) * 255.0) as u8,
811        }
812    }
813}
814
815/// A color reference that can be either a concrete color or a system color.
816/// System colors are lazily evaluated at runtime based on the user's system theme.
817/// 
818/// CSS syntax: `system:accent`, `system:text`, `system:background`, etc.
819#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
820#[repr(C, u8)]
821pub enum ColorOrSystem {
822    /// A concrete RGBA color value.
823    Color(ColorU),
824    /// A reference to a system color, resolved at runtime.
825    System(SystemColorRef),
826}
827
828impl Default for ColorOrSystem {
829    fn default() -> Self {
830        ColorOrSystem::Color(ColorU::BLACK)
831    }
832}
833
834impl From<ColorU> for ColorOrSystem {
835    fn from(color: ColorU) -> Self {
836        ColorOrSystem::Color(color)
837    }
838}
839
840impl ColorOrSystem {
841    /// Create a new ColorOrSystem from a concrete color.
842    pub const fn color(c: ColorU) -> Self {
843        ColorOrSystem::Color(c)
844    }
845    
846    /// Create a new ColorOrSystem from a system color reference.
847    pub const fn system(s: SystemColorRef) -> Self {
848        ColorOrSystem::System(s)
849    }
850    
851    /// Resolve the color against a SystemColors struct.
852    /// Returns the system color if available, or falls back to the provided default.
853    pub fn resolve(&self, system_colors: &crate::system::SystemColors, fallback: ColorU) -> ColorU {
854        match self {
855            ColorOrSystem::Color(c) => *c,
856            ColorOrSystem::System(ref_type) => ref_type.resolve(system_colors, fallback),
857        }
858    }
859    
860    /// Returns the concrete color if available, or a default fallback for system colors.
861    /// Use this when SystemColors is not available (e.g., during rendering setup).
862    pub fn to_color_u_with_fallback(&self, fallback: ColorU) -> ColorU {
863        match self {
864            ColorOrSystem::Color(c) => *c,
865            ColorOrSystem::System(_) => fallback,
866        }
867    }
868    
869    /// Returns the concrete color if available, or a gray fallback for system colors.
870    pub fn to_color_u_default(&self) -> ColorU {
871        self.to_color_u_with_fallback(ColorU { r: 128, g: 128, b: 128, a: 255 })
872    }
873}
874
875/// Reference to a specific system color.
876/// These are resolved at runtime based on the user's system preferences.
877#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
878#[repr(C)]
879pub enum SystemColorRef {
880    /// System text color (e.g., black on light theme, white on dark)
881    Text,
882    /// System background color
883    Background,
884    /// System accent color (user-selected highlight color)
885    Accent,
886    /// Text color when on accent background
887    AccentText,
888    /// Button face background color
889    ButtonFace,
890    /// Button text color
891    ButtonText,
892    /// Window/panel background color
893    WindowBackground,
894    /// Selection/highlight background color
895    SelectionBackground,
896    /// Text color when selected
897    SelectionText,
898}
899
900impl SystemColorRef {
901    /// Resolve this system color reference against actual system colors.
902    pub fn resolve(&self, colors: &crate::system::SystemColors, fallback: ColorU) -> ColorU {
903        match self {
904            SystemColorRef::Text => colors.text.as_option().copied().unwrap_or(fallback),
905            SystemColorRef::Background => colors.background.as_option().copied().unwrap_or(fallback),
906            SystemColorRef::Accent => colors.accent.as_option().copied().unwrap_or(fallback),
907            SystemColorRef::AccentText => colors.accent_text.as_option().copied().unwrap_or(fallback),
908            SystemColorRef::ButtonFace => colors.button_face.as_option().copied().unwrap_or(fallback),
909            SystemColorRef::ButtonText => colors.button_text.as_option().copied().unwrap_or(fallback),
910            SystemColorRef::WindowBackground => colors.window_background.as_option().copied().unwrap_or(fallback),
911            SystemColorRef::SelectionBackground => colors.selection_background.as_option().copied().unwrap_or(fallback),
912            SystemColorRef::SelectionText => colors.selection_text.as_option().copied().unwrap_or(fallback),
913        }
914    }
915    
916    /// Get the CSS syntax for this system color reference.
917    pub fn as_css_str(&self) -> &'static str {
918        match self {
919            SystemColorRef::Text => "system:text",
920            SystemColorRef::Background => "system:background",
921            SystemColorRef::Accent => "system:accent",
922            SystemColorRef::AccentText => "system:accent-text",
923            SystemColorRef::ButtonFace => "system:button-face",
924            SystemColorRef::ButtonText => "system:button-text",
925            SystemColorRef::WindowBackground => "system:window-background",
926            SystemColorRef::SelectionBackground => "system:selection-background",
927            SystemColorRef::SelectionText => "system:selection-text",
928        }
929    }
930}
931
932// --- PARSER ---
933
934#[derive(Debug, Copy, Clone, PartialEq)]
935#[repr(C)]
936pub enum CssColorComponent {
937    Red,
938    Green,
939    Blue,
940    Hue,
941    Saturation,
942    Lightness,
943    Alpha,
944}
945
946#[derive(Clone, PartialEq)]
947pub enum CssColorParseError<'a> {
948    InvalidColor(&'a str),
949    InvalidFunctionName(&'a str),
950    InvalidColorComponent(u8),
951    IntValueParseErr(ParseIntError),
952    FloatValueParseErr(ParseFloatError),
953    FloatValueOutOfRange(f32),
954    MissingColorComponent(CssColorComponent),
955    ExtraArguments(&'a str),
956    UnclosedColor(&'a str),
957    EmptyInput,
958    DirectionParseError(CssDirectionParseError<'a>),
959    UnsupportedDirection(&'a str),
960    InvalidPercentage(PercentageParseError),
961}
962
963impl_debug_as_display!(CssColorParseError<'a>);
964impl_display! {CssColorParseError<'a>, {
965    InvalidColor(i) => format!("Invalid CSS color: \"{}\"", i),
966    InvalidFunctionName(i) => format!("Invalid function name, expected one of: \"rgb\", \"rgba\", \"hsl\", \"hsla\" got: \"{}\"", i),
967    InvalidColorComponent(i) => format!("Invalid color component when parsing CSS color: \"{}\"", i),
968    IntValueParseErr(e) => format!("CSS color component: Value not in range between 00 - FF: \"{}\"", e),
969    FloatValueParseErr(e) => format!("CSS color component: Value cannot be parsed as floating point number: \"{}\"", e),
970    FloatValueOutOfRange(v) => format!("CSS color component: Value not in range between 0.0 - 1.0: \"{}\"", v),
971    MissingColorComponent(c) => format!("CSS color is missing {:?} component", c),
972    ExtraArguments(a) => format!("Extra argument to CSS color: \"{}\"", a),
973    EmptyInput => format!("Empty color string."),
974    UnclosedColor(i) => format!("Unclosed color: \"{}\"", i),
975    DirectionParseError(e) => format!("Could not parse direction argument for CSS color: \"{}\"", e),
976    UnsupportedDirection(d) => format!("Unsupported direction type for CSS color: \"{}\"", d),
977    InvalidPercentage(p) => format!("Invalid percentage when parsing CSS color: \"{}\"", p),
978}}
979
980impl<'a> From<ParseIntError> for CssColorParseError<'a> {
981    fn from(e: ParseIntError) -> Self {
982        CssColorParseError::IntValueParseErr(e)
983    }
984}
985impl<'a> From<ParseFloatError> for CssColorParseError<'a> {
986    fn from(e: ParseFloatError) -> Self {
987        CssColorParseError::FloatValueParseErr(e)
988    }
989}
990impl<'a> From<core::num::ParseIntError> for CssColorParseError<'a> {
991    fn from(e: core::num::ParseIntError) -> Self {
992        CssColorParseError::IntValueParseErr(ParseIntError::from(e))
993    }
994}
995impl<'a> From<core::num::ParseFloatError> for CssColorParseError<'a> {
996    fn from(e: core::num::ParseFloatError) -> Self {
997        CssColorParseError::FloatValueParseErr(ParseFloatError::from(e))
998    }
999}
1000impl_from!(
1001    CssDirectionParseError<'a>,
1002    CssColorParseError::DirectionParseError
1003);
1004
1005#[derive(Debug, Clone, PartialEq)]
1006#[repr(C, u8)]
1007pub enum CssColorParseErrorOwned {
1008    InvalidColor(AzString),
1009    InvalidFunctionName(AzString),
1010    InvalidColorComponent(u8),
1011    IntValueParseErr(ParseIntError),
1012    FloatValueParseErr(ParseFloatError),
1013    FloatValueOutOfRange(f32),
1014    MissingColorComponent(CssColorComponent),
1015    ExtraArguments(AzString),
1016    UnclosedColor(AzString),
1017    EmptyInput,
1018    DirectionParseError(CssDirectionParseErrorOwned),
1019    UnsupportedDirection(AzString),
1020    InvalidPercentage(PercentageParseError),
1021}
1022
1023impl<'a> CssColorParseError<'a> {
1024    pub fn to_contained(&self) -> CssColorParseErrorOwned {
1025        match self {
1026            CssColorParseError::InvalidColor(s) => {
1027                CssColorParseErrorOwned::InvalidColor(s.to_string().into())
1028            }
1029            CssColorParseError::InvalidFunctionName(s) => {
1030                CssColorParseErrorOwned::InvalidFunctionName(s.to_string().into())
1031            }
1032            CssColorParseError::InvalidColorComponent(n) => {
1033                CssColorParseErrorOwned::InvalidColorComponent(*n)
1034            }
1035            CssColorParseError::IntValueParseErr(e) => {
1036                CssColorParseErrorOwned::IntValueParseErr((*e))
1037            }
1038            CssColorParseError::FloatValueParseErr(e) => {
1039                CssColorParseErrorOwned::FloatValueParseErr((*e))
1040            }
1041            CssColorParseError::FloatValueOutOfRange(n) => {
1042                CssColorParseErrorOwned::FloatValueOutOfRange(*n)
1043            }
1044            CssColorParseError::MissingColorComponent(c) => {
1045                CssColorParseErrorOwned::MissingColorComponent(*c)
1046            }
1047            CssColorParseError::ExtraArguments(s) => {
1048                CssColorParseErrorOwned::ExtraArguments(s.to_string().into())
1049            }
1050            CssColorParseError::UnclosedColor(s) => {
1051                CssColorParseErrorOwned::UnclosedColor(s.to_string().into())
1052            }
1053            CssColorParseError::EmptyInput => CssColorParseErrorOwned::EmptyInput,
1054            CssColorParseError::DirectionParseError(e) => {
1055                CssColorParseErrorOwned::DirectionParseError(e.to_contained())
1056            }
1057            CssColorParseError::UnsupportedDirection(s) => {
1058                CssColorParseErrorOwned::UnsupportedDirection(s.to_string().into())
1059            }
1060            CssColorParseError::InvalidPercentage(e) => {
1061                CssColorParseErrorOwned::InvalidPercentage(e.clone())
1062            }
1063        }
1064    }
1065}
1066
1067impl CssColorParseErrorOwned {
1068    pub fn to_shared<'a>(&'a self) -> CssColorParseError<'a> {
1069        match self {
1070            CssColorParseErrorOwned::InvalidColor(s) => CssColorParseError::InvalidColor(s),
1071            CssColorParseErrorOwned::InvalidFunctionName(s) => {
1072                CssColorParseError::InvalidFunctionName(s)
1073            }
1074            CssColorParseErrorOwned::InvalidColorComponent(n) => {
1075                CssColorParseError::InvalidColorComponent(*n)
1076            }
1077            CssColorParseErrorOwned::IntValueParseErr(e) => {
1078                CssColorParseError::IntValueParseErr(*e)
1079            }
1080            CssColorParseErrorOwned::FloatValueParseErr(e) => {
1081                CssColorParseError::FloatValueParseErr(*e)
1082            }
1083            CssColorParseErrorOwned::FloatValueOutOfRange(n) => {
1084                CssColorParseError::FloatValueOutOfRange(*n)
1085            }
1086            CssColorParseErrorOwned::MissingColorComponent(c) => {
1087                CssColorParseError::MissingColorComponent(*c)
1088            }
1089            CssColorParseErrorOwned::ExtraArguments(s) => CssColorParseError::ExtraArguments(s),
1090            CssColorParseErrorOwned::UnclosedColor(s) => CssColorParseError::UnclosedColor(s),
1091            CssColorParseErrorOwned::EmptyInput => CssColorParseError::EmptyInput,
1092            CssColorParseErrorOwned::DirectionParseError(e) => {
1093                CssColorParseError::DirectionParseError(e.to_shared())
1094            }
1095            CssColorParseErrorOwned::UnsupportedDirection(s) => {
1096                CssColorParseError::UnsupportedDirection(s)
1097            }
1098            CssColorParseErrorOwned::InvalidPercentage(e) => {
1099                CssColorParseError::InvalidPercentage(e.clone())
1100            }
1101        }
1102    }
1103}
1104
1105#[cfg(feature = "parser")]
1106pub fn parse_css_color<'a>(input: &'a str) -> Result<ColorU, CssColorParseError<'a>> {
1107    let input = input.trim();
1108    if input.starts_with('#') {
1109        parse_color_no_hash(&input[1..])
1110    } else {
1111        use crate::props::basic::parse::{parse_parentheses, ParenthesisParseError};
1112        match parse_parentheses(input, &["rgba", "rgb", "hsla", "hsl"]) {
1113            Ok((stopword, inner_value)) => match stopword {
1114                "rgba" => parse_color_rgb(inner_value, true),
1115                "rgb" => parse_color_rgb(inner_value, false),
1116                "hsla" => parse_color_hsl(inner_value, true),
1117                "hsl" => parse_color_hsl(inner_value, false),
1118                _ => unreachable!(),
1119            },
1120            Err(e) => match e {
1121                ParenthesisParseError::UnclosedBraces => {
1122                    Err(CssColorParseError::UnclosedColor(input))
1123                }
1124                ParenthesisParseError::EmptyInput => Err(CssColorParseError::EmptyInput),
1125                ParenthesisParseError::StopWordNotFound(stopword) => {
1126                    Err(CssColorParseError::InvalidFunctionName(stopword))
1127                }
1128                ParenthesisParseError::NoClosingBraceFound => {
1129                    Err(CssColorParseError::UnclosedColor(input))
1130                }
1131                ParenthesisParseError::NoOpeningBraceFound => parse_color_builtin(input),
1132            },
1133        }
1134    }
1135}
1136
1137/// Parse a color that can be either a concrete color or a system color reference.
1138/// 
1139/// Supports all standard CSS color formats plus:
1140/// - `system:accent` - System accent/highlight color
1141/// - `system:text` - System text color
1142/// - `system:background` - System background color
1143/// - `system:selection-background` - Selection/highlight background
1144/// - `system:selection-text` - Text color when selected
1145/// - `system:button-face` - Button background color
1146/// - `system:button-text` - Button text color
1147/// - `system:window-background` - Window background color
1148/// - `system:accent-text` - Text color on accent background
1149#[cfg(feature = "parser")]
1150pub fn parse_color_or_system<'a>(input: &'a str) -> Result<ColorOrSystem, CssColorParseError<'a>> {
1151    let input = input.trim();
1152    
1153    // Check for system color syntax: "system:name"
1154    if let Some(system_name) = input.strip_prefix("system:") {
1155        let system_ref = match system_name.trim() {
1156            "text" => SystemColorRef::Text,
1157            "background" => SystemColorRef::Background,
1158            "accent" => SystemColorRef::Accent,
1159            "accent-text" => SystemColorRef::AccentText,
1160            "button-face" => SystemColorRef::ButtonFace,
1161            "button-text" => SystemColorRef::ButtonText,
1162            "window-background" => SystemColorRef::WindowBackground,
1163            "selection-background" => SystemColorRef::SelectionBackground,
1164            "selection-text" => SystemColorRef::SelectionText,
1165            _ => return Err(CssColorParseError::InvalidColor(input)),
1166        };
1167        return Ok(ColorOrSystem::System(system_ref));
1168    }
1169    
1170    // Otherwise parse as regular color
1171    parse_css_color(input).map(ColorOrSystem::Color)
1172}
1173
1174#[cfg(feature = "parser")]
1175fn parse_color_no_hash<'a>(input: &'a str) -> Result<ColorU, CssColorParseError<'a>> {
1176    #[inline]
1177    fn from_hex<'a>(c: u8) -> Result<u8, CssColorParseError<'a>> {
1178        match c {
1179            b'0'..=b'9' => Ok(c - b'0'),
1180            b'a'..=b'f' => Ok(c - b'a' + 10),
1181            b'A'..=b'F' => Ok(c - b'A' + 10),
1182            _ => Err(CssColorParseError::InvalidColorComponent(c)),
1183        }
1184    }
1185
1186    match input.len() {
1187        3 => {
1188            let mut bytes = input.bytes();
1189            let r = bytes.next().unwrap();
1190            let g = bytes.next().unwrap();
1191            let b = bytes.next().unwrap();
1192            Ok(ColorU::new_rgb(
1193                from_hex(r)? * 17,
1194                from_hex(g)? * 17,
1195                from_hex(b)? * 17,
1196            ))
1197        }
1198        4 => {
1199            let mut bytes = input.bytes();
1200            let r = bytes.next().unwrap();
1201            let g = bytes.next().unwrap();
1202            let b = bytes.next().unwrap();
1203            let a = bytes.next().unwrap();
1204            Ok(ColorU::new(
1205                from_hex(r)? * 17,
1206                from_hex(g)? * 17,
1207                from_hex(b)? * 17,
1208                from_hex(a)? * 17,
1209            ))
1210        }
1211        6 => {
1212            let val = u32::from_str_radix(input, 16)?;
1213            Ok(ColorU::new_rgb(
1214                ((val >> 16) & 0xFF) as u8,
1215                ((val >> 8) & 0xFF) as u8,
1216                (val & 0xFF) as u8,
1217            ))
1218        }
1219        8 => {
1220            let val = u32::from_str_radix(input, 16)?;
1221            Ok(ColorU::new(
1222                ((val >> 24) & 0xFF) as u8,
1223                ((val >> 16) & 0xFF) as u8,
1224                ((val >> 8) & 0xFF) as u8,
1225                (val & 0xFF) as u8,
1226            ))
1227        }
1228        _ => Err(CssColorParseError::InvalidColor(input)),
1229    }
1230}
1231
1232#[cfg(feature = "parser")]
1233fn parse_color_rgb<'a>(
1234    input: &'a str,
1235    parse_alpha: bool,
1236) -> Result<ColorU, CssColorParseError<'a>> {
1237    let mut components = input.split(',').map(|c| c.trim());
1238    let rgb_color = parse_color_rgb_components(&mut components)?;
1239    let a = if parse_alpha {
1240        parse_alpha_component(&mut components)?
1241    } else {
1242        255
1243    };
1244    if let Some(arg) = components.next() {
1245        return Err(CssColorParseError::ExtraArguments(arg));
1246    }
1247    Ok(ColorU { a, ..rgb_color })
1248}
1249
1250#[cfg(feature = "parser")]
1251fn parse_color_rgb_components<'a>(
1252    components: &mut dyn Iterator<Item = &'a str>,
1253) -> Result<ColorU, CssColorParseError<'a>> {
1254    #[inline]
1255    fn component_from_str<'a>(
1256        components: &mut dyn Iterator<Item = &'a str>,
1257        which: CssColorComponent,
1258    ) -> Result<u8, CssColorParseError<'a>> {
1259        let c = components
1260            .next()
1261            .ok_or(CssColorParseError::MissingColorComponent(which))?;
1262        if c.is_empty() {
1263            return Err(CssColorParseError::MissingColorComponent(which));
1264        }
1265        Ok(c.parse::<u8>()?)
1266    }
1267    Ok(ColorU {
1268        r: component_from_str(components, CssColorComponent::Red)?,
1269        g: component_from_str(components, CssColorComponent::Green)?,
1270        b: component_from_str(components, CssColorComponent::Blue)?,
1271        a: 255,
1272    })
1273}
1274
1275#[cfg(feature = "parser")]
1276fn parse_color_hsl<'a>(
1277    input: &'a str,
1278    parse_alpha: bool,
1279) -> Result<ColorU, CssColorParseError<'a>> {
1280    let mut components = input.split(',').map(|c| c.trim());
1281    let rgb_color = parse_color_hsl_components(&mut components)?;
1282    let a = if parse_alpha {
1283        parse_alpha_component(&mut components)?
1284    } else {
1285        255
1286    };
1287    if let Some(arg) = components.next() {
1288        return Err(CssColorParseError::ExtraArguments(arg));
1289    }
1290    Ok(ColorU { a, ..rgb_color })
1291}
1292
1293#[cfg(feature = "parser")]
1294fn parse_color_hsl_components<'a>(
1295    components: &mut dyn Iterator<Item = &'a str>,
1296) -> Result<ColorU, CssColorParseError<'a>> {
1297    #[inline]
1298    fn angle_from_str<'a>(
1299        components: &mut dyn Iterator<Item = &'a str>,
1300        which: CssColorComponent,
1301    ) -> Result<f32, CssColorParseError<'a>> {
1302        let c = components
1303            .next()
1304            .ok_or(CssColorParseError::MissingColorComponent(which))?;
1305        if c.is_empty() {
1306            return Err(CssColorParseError::MissingColorComponent(which));
1307        }
1308        let dir = parse_direction(c)?;
1309        match dir {
1310            Direction::Angle(deg) => Ok(deg.to_degrees()),
1311            Direction::FromTo(_) => Err(CssColorParseError::UnsupportedDirection(c)),
1312        }
1313    }
1314
1315    #[inline]
1316    fn percent_from_str<'a>(
1317        components: &mut dyn Iterator<Item = &'a str>,
1318        which: CssColorComponent,
1319    ) -> Result<f32, CssColorParseError<'a>> {
1320        use crate::props::basic::parse_percentage_value;
1321
1322        let c = components
1323            .next()
1324            .ok_or(CssColorParseError::MissingColorComponent(which))?;
1325        if c.is_empty() {
1326            return Err(CssColorParseError::MissingColorComponent(which));
1327        }
1328
1329        // Modern CSS allows both percentage and unitless values for HSL
1330        Ok(parse_percentage_value(c)
1331            .map_err(CssColorParseError::InvalidPercentage)?
1332            .normalized()
1333            * 100.0)
1334    }
1335
1336    #[inline]
1337    fn hsl_to_rgb(h: f32, s: f32, l: f32) -> (u8, u8, u8) {
1338        let s = s / 100.0;
1339        let l = l / 100.0;
1340        let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
1341        let h_prime = h / 60.0;
1342        let x = c * (1.0 - ((h_prime % 2.0) - 1.0).abs());
1343        let (r1, g1, b1) = if (0.0..1.0).contains(&h_prime) {
1344            (c, x, 0.0)
1345        } else if (1.0..2.0).contains(&h_prime) {
1346            (x, c, 0.0)
1347        } else if (2.0..3.0).contains(&h_prime) {
1348            (0.0, c, x)
1349        } else if (3.0..4.0).contains(&h_prime) {
1350            (0.0, x, c)
1351        } else if (4.0..5.0).contains(&h_prime) {
1352            (x, 0.0, c)
1353        } else {
1354            (c, 0.0, x)
1355        };
1356        let m = l - c / 2.0;
1357        (
1358            ((r1 + m) * 255.0) as u8,
1359            ((g1 + m) * 255.0) as u8,
1360            ((b1 + m) * 255.0) as u8,
1361        )
1362    }
1363
1364    let (h, s, l) = (
1365        angle_from_str(components, CssColorComponent::Hue)?,
1366        percent_from_str(components, CssColorComponent::Saturation)?,
1367        percent_from_str(components, CssColorComponent::Lightness)?,
1368    );
1369
1370    let (r, g, b) = hsl_to_rgb(h, s, l);
1371    Ok(ColorU { r, g, b, a: 255 })
1372}
1373
1374#[cfg(feature = "parser")]
1375fn parse_alpha_component<'a>(
1376    components: &mut dyn Iterator<Item = &'a str>,
1377) -> Result<u8, CssColorParseError<'a>> {
1378    let a_str = components
1379        .next()
1380        .ok_or(CssColorParseError::MissingColorComponent(
1381            CssColorComponent::Alpha,
1382        ))?;
1383    if a_str.is_empty() {
1384        return Err(CssColorParseError::MissingColorComponent(
1385            CssColorComponent::Alpha,
1386        ));
1387    }
1388    let a = a_str.parse::<f32>()?;
1389    if !(0.0..=1.0).contains(&a) {
1390        return Err(CssColorParseError::FloatValueOutOfRange(a));
1391    }
1392    Ok((a * 255.0).round() as u8)
1393}
1394
1395#[cfg(feature = "parser")]
1396fn parse_color_builtin<'a>(input: &'a str) -> Result<ColorU, CssColorParseError<'a>> {
1397    let (r, g, b, a) = match input.to_lowercase().as_str() {
1398        "aliceblue" => (240, 248, 255, 255),
1399        "antiquewhite" => (250, 235, 215, 255),
1400        "aqua" => (0, 255, 255, 255),
1401        "aquamarine" => (127, 255, 212, 255),
1402        "azure" => (240, 255, 255, 255),
1403        "beige" => (245, 245, 220, 255),
1404        "bisque" => (255, 228, 196, 255),
1405        "black" => (0, 0, 0, 255),
1406        "blanchedalmond" => (255, 235, 205, 255),
1407        "blue" => (0, 0, 255, 255),
1408        "blueviolet" => (138, 43, 226, 255),
1409        "brown" => (165, 42, 42, 255),
1410        "burlywood" => (222, 184, 135, 255),
1411        "cadetblue" => (95, 158, 160, 255),
1412        "chartreuse" => (127, 255, 0, 255),
1413        "chocolate" => (210, 105, 30, 255),
1414        "coral" => (255, 127, 80, 255),
1415        "cornflowerblue" => (100, 149, 237, 255),
1416        "cornsilk" => (255, 248, 220, 255),
1417        "crimson" => (220, 20, 60, 255),
1418        "cyan" => (0, 255, 255, 255),
1419        "darkblue" => (0, 0, 139, 255),
1420        "darkcyan" => (0, 139, 139, 255),
1421        "darkgoldenrod" => (184, 134, 11, 255),
1422        "darkgray" | "darkgrey" => (169, 169, 169, 255),
1423        "darkgreen" => (0, 100, 0, 255),
1424        "darkkhaki" => (189, 183, 107, 255),
1425        "darkmagenta" => (139, 0, 139, 255),
1426        "darkolivegreen" => (85, 107, 47, 255),
1427        "darkorange" => (255, 140, 0, 255),
1428        "darkorchid" => (153, 50, 204, 255),
1429        "darkred" => (139, 0, 0, 255),
1430        "darksalmon" => (233, 150, 122, 255),
1431        "darkseagreen" => (143, 188, 143, 255),
1432        "darkslateblue" => (72, 61, 139, 255),
1433        "darkslategray" | "darkslategrey" => (47, 79, 79, 255),
1434        "darkturquoise" => (0, 206, 209, 255),
1435        "darkviolet" => (148, 0, 211, 255),
1436        "deeppink" => (255, 20, 147, 255),
1437        "deepskyblue" => (0, 191, 255, 255),
1438        "dimgray" | "dimgrey" => (105, 105, 105, 255),
1439        "dodgerblue" => (30, 144, 255, 255),
1440        "firebrick" => (178, 34, 34, 255),
1441        "floralwhite" => (255, 250, 240, 255),
1442        "forestgreen" => (34, 139, 34, 255),
1443        "fuchsia" => (255, 0, 255, 255),
1444        "gainsboro" => (220, 220, 220, 255),
1445        "ghostwhite" => (248, 248, 255, 255),
1446        "gold" => (255, 215, 0, 255),
1447        "goldenrod" => (218, 165, 32, 255),
1448        "gray" | "grey" => (128, 128, 128, 255),
1449        "green" => (0, 128, 0, 255),
1450        "greenyellow" => (173, 255, 47, 255),
1451        "honeydew" => (240, 255, 240, 255),
1452        "hotpink" => (255, 105, 180, 255),
1453        "indianred" => (205, 92, 92, 255),
1454        "indigo" => (75, 0, 130, 255),
1455        "ivory" => (255, 255, 240, 255),
1456        "khaki" => (240, 230, 140, 255),
1457        "lavender" => (230, 230, 250, 255),
1458        "lavenderblush" => (255, 240, 245, 255),
1459        "lawngreen" => (124, 252, 0, 255),
1460        "lemonchiffon" => (255, 250, 205, 255),
1461        "lightblue" => (173, 216, 230, 255),
1462        "lightcoral" => (240, 128, 128, 255),
1463        "lightcyan" => (224, 255, 255, 255),
1464        "lightgoldenrodyellow" => (250, 250, 210, 255),
1465        "lightgray" | "lightgrey" => (211, 211, 211, 255),
1466        "lightgreen" => (144, 238, 144, 255),
1467        "lightpink" => (255, 182, 193, 255),
1468        "lightsalmon" => (255, 160, 122, 255),
1469        "lightseagreen" => (32, 178, 170, 255),
1470        "lightskyblue" => (135, 206, 250, 255),
1471        "lightslategray" | "lightslategrey" => (119, 136, 153, 255),
1472        "lightsteelblue" => (176, 196, 222, 255),
1473        "lightyellow" => (255, 255, 224, 255),
1474        "lime" => (0, 255, 0, 255),
1475        "limegreen" => (50, 205, 50, 255),
1476        "linen" => (250, 240, 230, 255),
1477        "magenta" => (255, 0, 255, 255),
1478        "maroon" => (128, 0, 0, 255),
1479        "mediumaquamarine" => (102, 205, 170, 255),
1480        "mediumblue" => (0, 0, 205, 255),
1481        "mediumorchid" => (186, 85, 211, 255),
1482        "mediumpurple" => (147, 112, 219, 255),
1483        "mediumseagreen" => (60, 179, 113, 255),
1484        "mediumslateblue" => (123, 104, 238, 255),
1485        "mediumspringgreen" => (0, 250, 154, 255),
1486        "mediumturquoise" => (72, 209, 204, 255),
1487        "mediumvioletred" => (199, 21, 133, 255),
1488        "midnightblue" => (25, 25, 112, 255),
1489        "mintcream" => (245, 255, 250, 255),
1490        "mistyrose" => (255, 228, 225, 255),
1491        "moccasin" => (255, 228, 181, 255),
1492        "navajowhite" => (255, 222, 173, 255),
1493        "navy" => (0, 0, 128, 255),
1494        "oldlace" => (253, 245, 230, 255),
1495        "olive" => (128, 128, 0, 255),
1496        "olivedrab" => (107, 142, 35, 255),
1497        "orange" => (255, 165, 0, 255),
1498        "orangered" => (255, 69, 0, 255),
1499        "orchid" => (218, 112, 214, 255),
1500        "palegoldenrod" => (238, 232, 170, 255),
1501        "palegreen" => (152, 251, 152, 255),
1502        "paleturquoise" => (175, 238, 238, 255),
1503        "palevioletred" => (219, 112, 147, 255),
1504        "papayawhip" => (255, 239, 213, 255),
1505        "peachpuff" => (255, 218, 185, 255),
1506        "peru" => (205, 133, 63, 255),
1507        "pink" => (255, 192, 203, 255),
1508        "plum" => (221, 160, 221, 255),
1509        "powderblue" => (176, 224, 230, 255),
1510        "purple" => (128, 0, 128, 255),
1511        "rebeccapurple" => (102, 51, 153, 255),
1512        "red" => (255, 0, 0, 255),
1513        "rosybrown" => (188, 143, 143, 255),
1514        "royalblue" => (65, 105, 225, 255),
1515        "saddlebrown" => (139, 69, 19, 255),
1516        "salmon" => (250, 128, 114, 255),
1517        "sandybrown" => (244, 164, 96, 255),
1518        "seagreen" => (46, 139, 87, 255),
1519        "seashell" => (255, 245, 238, 255),
1520        "sienna" => (160, 82, 45, 255),
1521        "silver" => (192, 192, 192, 255),
1522        "skyblue" => (135, 206, 235, 255),
1523        "slateblue" => (106, 90, 205, 255),
1524        "slategray" | "slategrey" => (112, 128, 144, 255),
1525        "snow" => (255, 250, 250, 255),
1526        "springgreen" => (0, 255, 127, 255),
1527        "steelblue" => (70, 130, 180, 255),
1528        "tan" => (210, 180, 140, 255),
1529        "teal" => (0, 128, 128, 255),
1530        "thistle" => (216, 191, 216, 255),
1531        "tomato" => (255, 99, 71, 255),
1532        "transparent" => (0, 0, 0, 0),
1533        "turquoise" => (64, 224, 208, 255),
1534        "violet" => (238, 130, 238, 255),
1535        "wheat" => (245, 222, 179, 255),
1536        "white" => (255, 255, 255, 255),
1537        "whitesmoke" => (245, 245, 245, 255),
1538        "yellow" => (255, 255, 0, 255),
1539        "yellowgreen" => (154, 205, 50, 255),
1540        _ => return Err(CssColorParseError::InvalidColor(input)),
1541    };
1542    Ok(ColorU { r, g, b, a })
1543}
1544
1545#[cfg(all(test, feature = "parser"))]
1546mod tests {
1547    use super::*;
1548
1549    #[test]
1550    fn test_parse_color_keywords() {
1551        assert_eq!(parse_css_color("red").unwrap(), ColorU::RED);
1552        assert_eq!(parse_css_color("blue").unwrap(), ColorU::BLUE);
1553        assert_eq!(parse_css_color("transparent").unwrap(), ColorU::TRANSPARENT);
1554        assert_eq!(
1555            parse_css_color("rebeccapurple").unwrap(),
1556            ColorU::new_rgb(102, 51, 153)
1557        );
1558    }
1559
1560    #[test]
1561    fn test_parse_color_hex() {
1562        // 3-digit
1563        assert_eq!(parse_css_color("#f00").unwrap(), ColorU::RED);
1564        // 4-digit
1565        assert_eq!(
1566            parse_css_color("#f008").unwrap(),
1567            ColorU::new(255, 0, 0, 136)
1568        );
1569        // 6-digit
1570        assert_eq!(parse_css_color("#00ff00").unwrap(), ColorU::GREEN);
1571        // 8-digit
1572        assert_eq!(
1573            parse_css_color("#0000ff80").unwrap(),
1574            ColorU::new(0, 0, 255, 128)
1575        );
1576        // Uppercase
1577        assert_eq!(
1578            parse_css_color("#FFC0CB").unwrap(),
1579            ColorU::new_rgb(255, 192, 203)
1580        ); // Pink
1581    }
1582
1583    #[test]
1584    fn test_parse_color_rgb() {
1585        assert_eq!(parse_css_color("rgb(255, 0, 0)").unwrap(), ColorU::RED);
1586        assert_eq!(
1587            parse_css_color("rgba(0, 255, 0, 0.5)").unwrap(),
1588            ColorU::new(0, 255, 0, 128)
1589        );
1590        assert_eq!(
1591            parse_css_color("rgba(10, 20, 30, 1)").unwrap(),
1592            ColorU::new_rgb(10, 20, 30)
1593        );
1594        assert_eq!(parse_css_color("rgb( 0 , 0 , 0 )").unwrap(), ColorU::BLACK);
1595    }
1596
1597    #[test]
1598    fn test_parse_color_hsl() {
1599        assert_eq!(parse_css_color("hsl(0, 100%, 50%)").unwrap(), ColorU::RED);
1600        assert_eq!(
1601            parse_css_color("hsl(120, 100%, 50%)").unwrap(),
1602            ColorU::GREEN
1603        );
1604        assert_eq!(
1605            parse_css_color("hsla(240, 100%, 50%, 0.5)").unwrap(),
1606            ColorU::new(0, 0, 255, 128)
1607        );
1608        assert_eq!(parse_css_color("hsl(0, 0%, 0%)").unwrap(), ColorU::BLACK);
1609    }
1610
1611    #[test]
1612    fn test_parse_color_errors() {
1613        assert!(parse_css_color("redd").is_err());
1614        assert!(parse_css_color("#12345").is_err()); // Invalid length
1615        assert!(parse_css_color("#ggg").is_err()); // Invalid hex digit
1616        assert!(parse_css_color("rgb(255, 0)").is_err()); // Missing component
1617        assert!(parse_css_color("rgba(255, 0, 0, 2)").is_err()); // Alpha out of range
1618        assert!(parse_css_color("rgb(256, 0, 0)").is_err()); // Value out of range
1619                                                             // Modern CSS allows both hsl(0, 100%, 50%) and hsl(0 100 50)
1620        assert!(parse_css_color("hsl(0, 100, 50%)").is_ok()); // Valid in modern CSS
1621        assert!(parse_css_color("rgb(255 0 0)").is_err()); // Missing commas (this implementation
1622                                                           // requires commas)
1623    }
1624
1625    #[test]
1626    fn test_parse_system_colors() {
1627        // Test parsing system color syntax
1628        assert_eq!(
1629            parse_color_or_system("system:accent").unwrap(),
1630            ColorOrSystem::System(SystemColorRef::Accent)
1631        );
1632        assert_eq!(
1633            parse_color_or_system("system:text").unwrap(),
1634            ColorOrSystem::System(SystemColorRef::Text)
1635        );
1636        assert_eq!(
1637            parse_color_or_system("system:background").unwrap(),
1638            ColorOrSystem::System(SystemColorRef::Background)
1639        );
1640        assert_eq!(
1641            parse_color_or_system("system:selection-background").unwrap(),
1642            ColorOrSystem::System(SystemColorRef::SelectionBackground)
1643        );
1644        assert_eq!(
1645            parse_color_or_system("system:selection-text").unwrap(),
1646            ColorOrSystem::System(SystemColorRef::SelectionText)
1647        );
1648        assert_eq!(
1649            parse_color_or_system("system:accent-text").unwrap(),
1650            ColorOrSystem::System(SystemColorRef::AccentText)
1651        );
1652        assert_eq!(
1653            parse_color_or_system("system:button-face").unwrap(),
1654            ColorOrSystem::System(SystemColorRef::ButtonFace)
1655        );
1656        assert_eq!(
1657            parse_color_or_system("system:button-text").unwrap(),
1658            ColorOrSystem::System(SystemColorRef::ButtonText)
1659        );
1660        assert_eq!(
1661            parse_color_or_system("system:window-background").unwrap(),
1662            ColorOrSystem::System(SystemColorRef::WindowBackground)
1663        );
1664        
1665        // Invalid system color should error
1666        assert!(parse_color_or_system("system:invalid").is_err());
1667        
1668        // Regular colors should still work
1669        assert_eq!(
1670            parse_color_or_system("red").unwrap(),
1671            ColorOrSystem::Color(ColorU::RED)
1672        );
1673        assert_eq!(
1674            parse_color_or_system("#ff0000").unwrap(),
1675            ColorOrSystem::Color(ColorU::RED)
1676        );
1677    }
1678
1679    #[test]
1680    fn test_system_color_resolution() {
1681        use crate::system::SystemColors;
1682        
1683        let system_colors = SystemColors {
1684            text: OptionColorU::Some(ColorU::BLACK),
1685            secondary_text: OptionColorU::None,
1686            tertiary_text: OptionColorU::None,
1687            background: OptionColorU::Some(ColorU::WHITE),
1688            accent: OptionColorU::Some(ColorU::new_rgb(0, 122, 255)), // macOS blue
1689            accent_text: OptionColorU::Some(ColorU::WHITE),
1690            button_face: OptionColorU::Some(ColorU::new_rgb(240, 240, 240)),
1691            button_text: OptionColorU::Some(ColorU::BLACK),
1692            disabled_text: OptionColorU::None,
1693            window_background: OptionColorU::Some(ColorU::WHITE),
1694            under_page_background: OptionColorU::None,
1695            selection_background: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
1696            selection_text: OptionColorU::Some(ColorU::WHITE),
1697            selection_background_inactive: OptionColorU::None,
1698            selection_text_inactive: OptionColorU::None,
1699            link: OptionColorU::None,
1700            separator: OptionColorU::None,
1701            grid: OptionColorU::None,
1702            find_highlight: OptionColorU::None,
1703            sidebar_background: OptionColorU::None,
1704            sidebar_selection: OptionColorU::None,
1705        };
1706        
1707        // Test resolution of system colors
1708        let accent_ref = ColorOrSystem::System(SystemColorRef::Accent);
1709        let resolved = accent_ref.resolve(&system_colors, ColorU::GRAY);
1710        assert_eq!(resolved, ColorU::new_rgb(0, 122, 255));
1711        
1712        // Test resolution with fallback when color is not set
1713        let empty_colors = SystemColors::default();
1714        let resolved_fallback = accent_ref.resolve(&empty_colors, ColorU::GRAY);
1715        assert_eq!(resolved_fallback, ColorU::GRAY);
1716        
1717        // Test that concrete colors just return themselves
1718        let concrete = ColorOrSystem::Color(ColorU::RED);
1719        let resolved_concrete = concrete.resolve(&system_colors, ColorU::GRAY);
1720        assert_eq!(resolved_concrete, ColorU::RED);
1721    }
1722
1723    #[test]
1724    fn test_system_color_css_str() {
1725        assert_eq!(SystemColorRef::Accent.as_css_str(), "system:accent");
1726        assert_eq!(SystemColorRef::Text.as_css_str(), "system:text");
1727        assert_eq!(SystemColorRef::Background.as_css_str(), "system:background");
1728        assert_eq!(SystemColorRef::SelectionBackground.as_css_str(), "system:selection-background");
1729    }
1730}