Skip to main content

azul_css/props/basic/
color.rs

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