1use std::{collections::HashMap, fmt::Display};
2
3use rgpui::{Hsla, SharedString, hsla};
4use serde::{Deserialize, Deserializer, de::Error as _};
5
6use anyhow::{Error, Result, anyhow};
7
8#[inline]
14pub fn hsl(h: f32, s: f32, l: f32) -> Hsla {
15 hsla(h / 360., s / 100.0, l / 100.0, 1.0)
16}
17
18pub trait Colorize: Sized {
19 fn opacity(&self, opacity: f32) -> Self;
23 fn divide(&self, divisor: f32) -> Self;
27 fn invert(&self) -> Self;
29 fn invert_l(&self) -> Self;
31 fn lighten(&self, amount: f32) -> Self;
35 fn darken(&self, amount: f32) -> Self;
39 fn apply(&self, base_color: Self) -> Self;
41
42 fn mix(&self, other: Self, factor: f32) -> Self;
44 fn mix_oklab(&self, other: Self, factor: f32) -> Self;
48 fn hue(&self, hue: f32) -> Self;
50 fn saturation(&self, saturation: f32) -> Self;
52 fn lightness(&self, lightness: f32) -> Self;
54
55 fn to_hex(&self) -> String;
57 fn parse_hex(hex: &str) -> Result<Self>;
59}
60
61mod oklab {
63 use rgpui::Rgba;
64
65 #[inline]
67 fn to_linear(c: f32) -> f32 {
68 if c <= 0.04045 {
69 c / 12.92
70 } else {
71 ((c + 0.055) / 1.055).powf(2.4)
72 }
73 }
74
75 #[inline]
77 fn from_linear(c: f32) -> f32 {
78 if c <= 0.0031308 {
79 c * 12.92
80 } else {
81 1.055 * c.powf(1.0 / 2.4) - 0.055
82 }
83 }
84
85 #[allow(non_snake_case)]
87 pub fn rgb_to_oklab(rgb: Rgba) -> (f32, f32, f32) {
88 let lr = to_linear(rgb.r);
90 let lg = to_linear(rgb.g);
91 let lb = to_linear(rgb.b);
92
93 let l = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb;
95 let m = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb;
96 let s = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb;
97
98 let l_ = l.cbrt();
100 let m_ = m.cbrt();
101 let s_ = s.cbrt();
102
103 let L = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_;
104 let a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_;
105 let b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_;
106
107 (L, a, b)
108 }
109
110 #[allow(non_snake_case)]
112 pub fn oklab_to_rgb(L: f32, a: f32, b: f32) -> Rgba {
113 let l_ = L + 0.3963377774 * a + 0.2158037573 * b;
115 let m_ = L - 0.1055613458 * a - 0.0638541728 * b;
116 let s_ = L - 0.0894841775 * a - 1.2914855480 * b;
117
118 let l = l_ * l_ * l_;
119 let m = m_ * m_ * m_;
120 let s = s_ * s_ * s_;
121
122 let lr = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
124 let lg = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
125 let lb = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
126
127 Rgba {
129 r: from_linear(lr).clamp(0.0, 1.0),
130 g: from_linear(lg).clamp(0.0, 1.0),
131 b: from_linear(lb).clamp(0.0, 1.0),
132 a: 1.0,
133 }
134 }
135}
136
137impl Colorize for Hsla {
138 fn opacity(&self, factor: f32) -> Self {
139 Self {
140 a: self.a * factor.clamp(0.0, 1.0),
141 ..*self
142 }
143 }
144
145 fn divide(&self, divisor: f32) -> Self {
146 Self {
147 a: divisor,
148 ..*self
149 }
150 }
151
152 fn invert(&self) -> Self {
153 Self {
154 h: 1.0 - self.h,
155 s: 1.0 - self.s,
156 l: 1.0 - self.l,
157 a: self.a,
158 }
159 }
160
161 fn invert_l(&self) -> Self {
162 Self {
163 l: 1.0 - self.l,
164 ..*self
165 }
166 }
167
168 fn lighten(&self, factor: f32) -> Self {
169 let l = self.l * (1.0 + factor.clamp(0.0, 1.0));
170
171 Hsla { l, ..*self }
172 }
173
174 fn darken(&self, factor: f32) -> Self {
175 let l = self.l * (1.0 - factor.clamp(0.0, 1.0));
176
177 Self { l, ..*self }
178 }
179
180 fn apply(&self, new_color: Self) -> Self {
181 Hsla {
182 h: new_color.h,
183 s: new_color.s,
184 l: self.l,
185 a: self.a,
186 }
187 }
188
189 fn mix(&self, other: Self, factor: f32) -> Self {
192 let factor = factor.clamp(0.0, 1.0);
193 let inv = 1.0 - factor;
194
195 #[inline]
196 fn lerp_hue(a: f32, b: f32, t: f32) -> f32 {
197 let diff = (b - a + 180.0).rem_euclid(360.) - 180.;
198 (a + diff * t).rem_euclid(360.0)
199 }
200
201 Hsla {
202 h: lerp_hue(self.h * 360., other.h * 360., factor) / 360.,
203 s: self.s * factor + other.s * inv,
204 l: self.l * factor + other.l * inv,
205 a: self.a * factor + other.a * inv,
206 }
207 }
208
209 #[allow(non_snake_case)]
210 fn mix_oklab(&self, other: Self, factor: f32) -> Self {
211 let factor = factor.clamp(0.0, 1.0);
212 let inv = 1.0 - factor;
213
214 let result_alpha = self.a * factor + other.a * inv;
216
217 if result_alpha == 0.0 {
219 return Self {
220 h: 0.0,
221 s: 0.0,
222 l: 0.0,
223 a: 0.0,
224 };
225 }
226
227 let rgb1 = self.to_rgb();
229 let rgb2 = other.to_rgb();
230
231 let (l1, a1, b1) = oklab::rgb_to_oklab(rgb1);
233 let (l2, a2, b2) = oklab::rgb_to_oklab(rgb2);
234
235 let alpha1 = self.a;
238 let alpha2 = other.a;
239
240 let l1_pm = l1 * alpha1;
242 let a1_pm = a1 * alpha1;
243 let b1_pm = b1 * alpha1;
244
245 let l2_pm = l2 * alpha2;
246 let a2_pm = a2 * alpha2;
247 let b2_pm = b2 * alpha2;
248
249 let L_pm = l1_pm * factor + l2_pm * inv;
251 let a_pm = a1_pm * factor + a2_pm * inv;
252 let b_pm = b1_pm * factor + b2_pm * inv;
253
254 let L = L_pm / result_alpha;
256 let a = a_pm / result_alpha;
257 let b = b_pm / result_alpha;
258
259 let mut rgb = oklab::oklab_to_rgb(L, a, b);
261 rgb.a = result_alpha;
262
263 rgb.into()
265 }
266
267 fn to_hex(&self) -> String {
268 let rgb = self.to_rgb();
269
270 if rgb.a < 1. {
271 return format!(
272 "#{:02X}{:02X}{:02X}{:02X}",
273 ((rgb.r * 255.) as u32),
274 ((rgb.g * 255.) as u32),
275 ((rgb.b * 255.) as u32),
276 ((self.a * 255.) as u32)
277 );
278 }
279
280 format!(
281 "#{:02X}{:02X}{:02X}",
282 ((rgb.r * 255.) as u32),
283 ((rgb.g * 255.) as u32),
284 ((rgb.b * 255.) as u32)
285 )
286 }
287
288 fn parse_hex(hex: &str) -> Result<Self> {
289 let hex = hex.trim_start_matches('#');
290 let len = hex.len();
291 if len != 6 && len != 8 {
292 return Err(anyhow::anyhow!("invalid hex color"));
293 }
294
295 let r = u8::from_str_radix(&hex[0..2], 16)? as f32 / 255.;
296 let g = u8::from_str_radix(&hex[2..4], 16)? as f32 / 255.;
297 let b = u8::from_str_radix(&hex[4..6], 16)? as f32 / 255.;
298 let a = if len == 8 {
299 u8::from_str_radix(&hex[6..8], 16)? as f32 / 255.
300 } else {
301 1.
302 };
303
304 let v = rgpui::Rgba { r, g, b, a };
305 let color: Hsla = v.into();
306 Ok(color)
307 }
308
309 fn hue(&self, hue: f32) -> Self {
310 let mut color = *self;
311 color.h = hue.clamp(0., 1.);
312 color
313 }
314
315 fn saturation(&self, saturation: f32) -> Self {
316 let mut color = *self;
317 color.s = saturation.clamp(0., 1.);
318 color
319 }
320
321 fn lightness(&self, lightness: f32) -> Self {
322 let mut color = *self;
323 color.l = lightness.clamp(0., 1.);
324 color
325 }
326}
327
328pub(crate) static DEFAULT_COLORS: once_cell::sync::Lazy<ShadcnColors> =
329 once_cell::sync::Lazy::new(|| {
330 serde_json::from_str(include_str!("./default-colors.json"))
331 .expect("failed to parse default-colors.json")
332 });
333
334type ColorScales = HashMap<usize, ShadcnColor>;
335
336mod color_scales {
337 use std::collections::HashMap;
338
339 use super::{ColorScales, ShadcnColor};
340
341 use serde::de::{Deserialize, Deserializer};
342
343 pub fn deserialize<'de, D>(deserializer: D) -> Result<ColorScales, D::Error>
344 where
345 D: Deserializer<'de>,
346 {
347 let mut map = HashMap::new();
348 for color in Vec::<ShadcnColor>::deserialize(deserializer)? {
349 map.insert(color.scale, color);
350 }
351 Ok(map)
352 }
353}
354
355#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
357pub enum ColorName {
358 White,
359 Black,
360 Neutral,
361 Gray,
362 Red,
363 Orange,
364 Amber,
365 Yellow,
366 Lime,
367 Green,
368 Emerald,
369 Teal,
370 Cyan,
371 Sky,
372 Blue,
373 Indigo,
374 Violet,
375 Purple,
376 Fuchsia,
377 Pink,
378 Rose,
379}
380
381impl Display for ColorName {
382 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
383 write!(f, "{:?}", self)
384 }
385}
386
387impl TryFrom<&str> for ColorName {
389 type Error = anyhow::Error;
390 fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
391 match value.to_lowercase().as_str() {
392 "white" => Ok(ColorName::White),
393 "black" => Ok(ColorName::Black),
394 "neutral" => Ok(ColorName::Neutral),
395 "gray" => Ok(ColorName::Gray),
396 "red" => Ok(ColorName::Red),
397 "orange" => Ok(ColorName::Orange),
398 "amber" => Ok(ColorName::Amber),
399 "yellow" => Ok(ColorName::Yellow),
400 "lime" => Ok(ColorName::Lime),
401 "green" => Ok(ColorName::Green),
402 "emerald" => Ok(ColorName::Emerald),
403 "teal" => Ok(ColorName::Teal),
404 "cyan" => Ok(ColorName::Cyan),
405 "sky" => Ok(ColorName::Sky),
406 "blue" => Ok(ColorName::Blue),
407 "indigo" => Ok(ColorName::Indigo),
408 "violet" => Ok(ColorName::Violet),
409 "purple" => Ok(ColorName::Purple),
410 "fuchsia" => Ok(ColorName::Fuchsia),
411 "pink" => Ok(ColorName::Pink),
412 "rose" => Ok(ColorName::Rose),
413 _ => Err(anyhow::anyhow!("Invalid color name")),
414 }
415 }
416}
417
418impl TryFrom<SharedString> for ColorName {
419 type Error = anyhow::Error;
420 fn try_from(value: SharedString) -> std::result::Result<Self, Self::Error> {
421 value.as_ref().try_into()
422 }
423}
424
425impl ColorName {
426 pub fn all() -> [Self; 19] {
428 [
429 ColorName::Neutral,
430 ColorName::Gray,
431 ColorName::Red,
432 ColorName::Orange,
433 ColorName::Amber,
434 ColorName::Yellow,
435 ColorName::Lime,
436 ColorName::Green,
437 ColorName::Emerald,
438 ColorName::Teal,
439 ColorName::Cyan,
440 ColorName::Sky,
441 ColorName::Blue,
442 ColorName::Indigo,
443 ColorName::Violet,
444 ColorName::Purple,
445 ColorName::Fuchsia,
446 ColorName::Pink,
447 ColorName::Rose,
448 ]
449 }
450
451 pub fn scale(&self, scale: usize) -> Hsla {
456 if self == &ColorName::White {
457 return DEFAULT_COLORS.white.hsla;
458 }
459 if self == &ColorName::Black {
460 return DEFAULT_COLORS.black.hsla;
461 }
462
463 let colors = match self {
464 ColorName::Neutral => &DEFAULT_COLORS.neutral,
465 ColorName::Gray => &DEFAULT_COLORS.gray,
466 ColorName::Red => &DEFAULT_COLORS.red,
467 ColorName::Orange => &DEFAULT_COLORS.orange,
468 ColorName::Amber => &DEFAULT_COLORS.amber,
469 ColorName::Yellow => &DEFAULT_COLORS.yellow,
470 ColorName::Lime => &DEFAULT_COLORS.lime,
471 ColorName::Green => &DEFAULT_COLORS.green,
472 ColorName::Emerald => &DEFAULT_COLORS.emerald,
473 ColorName::Teal => &DEFAULT_COLORS.teal,
474 ColorName::Cyan => &DEFAULT_COLORS.cyan,
475 ColorName::Sky => &DEFAULT_COLORS.sky,
476 ColorName::Blue => &DEFAULT_COLORS.blue,
477 ColorName::Indigo => &DEFAULT_COLORS.indigo,
478 ColorName::Violet => &DEFAULT_COLORS.violet,
479 ColorName::Purple => &DEFAULT_COLORS.purple,
480 ColorName::Fuchsia => &DEFAULT_COLORS.fuchsia,
481 ColorName::Pink => &DEFAULT_COLORS.pink,
482 ColorName::Rose => &DEFAULT_COLORS.rose,
483 _ => unreachable!(),
484 };
485
486 if let Some(color) = colors.get(&scale) {
487 color.hsla
488 } else {
489 colors.get(&500).unwrap().hsla
490 }
491 }
492}
493
494#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
495pub(crate) struct ShadcnColors {
496 pub(crate) black: ShadcnColor,
497 pub(crate) white: ShadcnColor,
498 #[serde(with = "color_scales")]
499 pub(crate) slate: ColorScales,
500 #[serde(with = "color_scales")]
501 pub(crate) gray: ColorScales,
502 #[serde(with = "color_scales")]
503 pub(crate) zinc: ColorScales,
504 #[serde(with = "color_scales")]
505 pub(crate) neutral: ColorScales,
506 #[serde(with = "color_scales")]
507 pub(crate) stone: ColorScales,
508 #[serde(with = "color_scales")]
509 pub(crate) red: ColorScales,
510 #[serde(with = "color_scales")]
511 pub(crate) orange: ColorScales,
512 #[serde(with = "color_scales")]
513 pub(crate) amber: ColorScales,
514 #[serde(with = "color_scales")]
515 pub(crate) yellow: ColorScales,
516 #[serde(with = "color_scales")]
517 pub(crate) lime: ColorScales,
518 #[serde(with = "color_scales")]
519 pub(crate) green: ColorScales,
520 #[serde(with = "color_scales")]
521 pub(crate) emerald: ColorScales,
522 #[serde(with = "color_scales")]
523 pub(crate) teal: ColorScales,
524 #[serde(with = "color_scales")]
525 pub(crate) cyan: ColorScales,
526 #[serde(with = "color_scales")]
527 pub(crate) sky: ColorScales,
528 #[serde(with = "color_scales")]
529 pub(crate) blue: ColorScales,
530 #[serde(with = "color_scales")]
531 pub(crate) indigo: ColorScales,
532 #[serde(with = "color_scales")]
533 pub(crate) violet: ColorScales,
534 #[serde(with = "color_scales")]
535 pub(crate) purple: ColorScales,
536 #[serde(with = "color_scales")]
537 pub(crate) fuchsia: ColorScales,
538 #[serde(with = "color_scales")]
539 pub(crate) pink: ColorScales,
540 #[serde(with = "color_scales")]
541 pub(crate) rose: ColorScales,
542}
543
544#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Deserialize)]
545pub(crate) struct ShadcnColor {
546 #[serde(default)]
547 pub(crate) scale: usize,
548 #[serde(deserialize_with = "from_hsl_channel", alias = "hslChannel")]
549 pub(crate) hsla: Hsla,
550}
551
552fn from_hsl_channel<'de, D>(deserializer: D) -> Result<Hsla, D::Error>
554where
555 D: Deserializer<'de>,
556{
557 let s: String = Deserialize::deserialize(deserializer).unwrap();
558
559 let mut parts = s.split_whitespace();
560 if parts.clone().count() != 3 {
561 return Err(D::Error::custom(
562 "expected hslChannel has 3 parts, e.g: '210 40% 98%'",
563 ));
564 }
565
566 fn parse_number(s: &str) -> f32 {
567 s.trim_end_matches('%')
568 .parse()
569 .expect("failed to parse number")
570 }
571
572 let (h, s, l) = (
573 parse_number(parts.next().unwrap()),
574 parse_number(parts.next().unwrap()),
575 parse_number(parts.next().unwrap()),
576 );
577
578 Ok(hsl(h, s, l))
579}
580
581macro_rules! color_method {
582 ($color:tt, $scale:tt) => {
583 paste::paste! {
584 #[inline]
585 #[allow(unused)]
586 pub fn [<$color _ $scale>]() -> Hsla {
587 if let Some(color) = DEFAULT_COLORS.$color.get(&($scale as usize)) {
588 return color.hsla;
589 }
590
591 black()
592 }
593 }
594 };
595}
596
597macro_rules! color_methods {
598 ($color:tt) => {
599 paste::paste! {
600 #[inline]
607 pub fn [<$color>](scale: usize) -> Hsla {
608 if let Some(color) = DEFAULT_COLORS.$color.get(&scale) {
609 return color.hsla;
610 }
611
612 black()
613 }
614 }
615
616 color_method!($color, 50);
617 color_method!($color, 100);
618 color_method!($color, 200);
619 color_method!($color, 300);
620 color_method!($color, 400);
621 color_method!($color, 500);
622 color_method!($color, 600);
623 color_method!($color, 700);
624 color_method!($color, 800);
625 color_method!($color, 900);
626 color_method!($color, 950);
627 };
628}
629
630pub fn black() -> Hsla {
631 DEFAULT_COLORS.black.hsla
632}
633
634pub fn white() -> Hsla {
635 DEFAULT_COLORS.white.hsla
636}
637
638color_methods!(slate);
639color_methods!(gray);
640color_methods!(zinc);
641color_methods!(neutral);
642color_methods!(stone);
643color_methods!(red);
644color_methods!(orange);
645color_methods!(amber);
646color_methods!(yellow);
647color_methods!(lime);
648color_methods!(green);
649color_methods!(emerald);
650color_methods!(teal);
651color_methods!(cyan);
652color_methods!(sky);
653color_methods!(blue);
654color_methods!(indigo);
655color_methods!(violet);
656color_methods!(purple);
657color_methods!(fuchsia);
658color_methods!(pink);
659color_methods!(rose);
660
661pub fn try_parse_color(color: &str) -> Result<Hsla> {
676 if color.starts_with("#") {
677 let rgba = rgpui::Rgba::try_from(color)?;
678 return Ok(rgba.into());
679 }
680
681 let mut name = String::new();
682 let mut scale = None;
683 let mut opacity = None;
684 let mut state = 0;
686 let mut part = String::new();
687
688 for c in color.chars() {
689 match c {
690 '-' if state == 0 => {
691 name = std::mem::take(&mut part);
692 state = 1;
693 }
694 '/' if state <= 1 => {
695 if state == 0 {
696 name = std::mem::take(&mut part);
697 } else if state == 1 {
698 scale = part.parse::<usize>().ok();
699 part.clear();
700 }
701 state = 2;
702 }
703 _ => part.push(c),
704 }
705 }
706
707 match state {
708 0 => name = part,
709 1 => scale = part.parse::<usize>().ok(),
710 2 => opacity = part.parse::<f32>().ok(),
711 _ => {}
712 }
713
714 if name.is_empty() {
715 return Err(anyhow!("Empty color name"));
716 }
717
718 let mut hsla = match name.as_str() {
719 "black" => Ok::<Hsla, Error>(crate::black()),
720 "white" => Ok(crate::white()),
721 _ => {
722 let color_name = ColorName::try_from(name.as_str())?;
723 if let Some(scale) = scale {
724 Ok(color_name.scale(scale))
725 } else {
726 Ok(color_name.scale(500))
727 }
728 }
729 }?;
730
731 if let Some(opacity) = opacity {
732 if opacity > 100. {
733 return Err(anyhow!("Invalid color opacity"));
734 }
735 hsla = hsla.opacity(opacity / 100.);
736 }
737
738 Ok(hsla)
739}
740
741#[cfg(test)]
742mod tests {
743 use rgpui::{rgb, rgba};
744
745 use super::*;
746
747 #[test]
748 fn test_default_colors() {
749 assert_eq!(white(), hsl(0.0, 0.0, 100.0));
750 assert_eq!(black(), hsl(0.0, 0.0, 0.0));
751
752 assert_eq!(slate_50(), hsl(210.0, 40.0, 98.0));
753 assert_eq!(slate_100(), hsl(210.0, 40.0, 96.1));
754 assert_eq!(slate_900(), hsl(222.2, 47.4, 11.2));
755
756 assert_eq!(red_50(), hsl(0.0, 85.7, 97.3));
757 assert_eq!(yellow_100(), hsl(54.9, 96.7, 88.0));
758 assert_eq!(green_200(), hsl(141.0, 78.9, 85.1));
759 assert_eq!(cyan_300(), hsl(187.0, 92.4, 69.0));
760 assert_eq!(blue_400(), hsl(213.1, 93.9, 67.8));
761 assert_eq!(indigo_500(), hsl(238.7, 83.5, 66.7));
762 }
763
764 #[test]
765 fn test_to_hex_string() {
766 let color: Hsla = rgb(0xf8fafc).into();
767 assert_eq!(color.to_hex(), "#F8FAFC");
768
769 let color: Hsla = rgb(0xfef2f2).into();
770 assert_eq!(color.to_hex(), "#FEF2F2");
771
772 let color: Hsla = rgba(0x0413fcaa).into();
773 assert_eq!(color.to_hex(), "#0413FCAA");
774 }
775
776 #[test]
777 fn test_from_hex_string() {
778 let color: Hsla = Hsla::parse_hex("#F8FAFC").unwrap();
779 assert_eq!(color, rgb(0xf8fafc).into());
780
781 let color: Hsla = Hsla::parse_hex("#FEF2F2").unwrap();
782 assert_eq!(color, rgb(0xfef2f2).into());
783
784 let color: Hsla = Hsla::parse_hex("#0413FCAA").unwrap();
785 assert_eq!(color, rgba(0x0413fcaa).into());
786 }
787
788 #[test]
789 fn test_lighten() {
790 let color = super::hsl(240.0, 5.0, 30.0);
791 let color = color.lighten(0.5);
792 assert_eq!(color.l, 0.45000002);
793 let color = color.lighten(0.5);
794 assert_eq!(color.l, 0.675);
795 let color = color.lighten(0.1);
796 assert_eq!(color.l, 0.7425);
797 }
798
799 #[test]
800 fn test_darken() {
801 let color = super::hsl(240.0, 5.0, 96.0);
802 let color = color.darken(0.5);
803 assert_eq!(color.l, 0.48);
804 let color = color.darken(0.5);
805 assert_eq!(color.l, 0.24);
806 }
807
808 #[test]
809 fn test_mix() {
810 let red = Hsla::parse_hex("#FF0000").unwrap();
811 let blue = Hsla::parse_hex("#0000FF").unwrap();
812 let green = Hsla::parse_hex("#00FF00").unwrap();
813 let yellow = Hsla::parse_hex("#FFFF00").unwrap();
814
815 assert_eq!(red.mix(blue, 0.5).to_hex(), "#FF00FF");
816 assert_eq!(green.mix(red, 0.5).to_hex(), "#FFFF00");
817 assert_eq!(blue.mix(yellow, 0.2).to_hex(), "#0098FF");
818 }
819
820 #[test]
821 fn test_mix_oklab() {
822 let red = Hsla::parse_hex("#FF0000").unwrap();
823 let blue = Hsla::parse_hex("#0000FF").unwrap();
824 let transparent = rgpui::Hsla {
825 h: 0.0,
826 s: 0.0,
827 l: 0.0,
828 a: 0.0,
829 };
830
831 let result = red.mix_oklab(transparent, 0.2);
834 assert!((result.a - 0.2).abs() < 0.01); let rgb_result = result.to_rgb();
838 let rgb_red = red.to_rgb();
839 assert!(
841 (rgb_result.r - rgb_red.r).abs() < 0.05,
842 "Red channel should be preserved"
843 );
844 assert!(rgb_result.g < 0.05, "Green channel should be near 0");
845 assert!(rgb_result.b < 0.05, "Blue channel should be near 0");
846
847 let purple = red.mix_oklab(blue, 0.5);
849 let purple_hsl = red.mix(blue, 0.5);
851 assert_ne!(purple.to_hex(), purple_hsl.to_hex());
852
853 let result_0 = red.mix_oklab(blue, 0.0);
855 let result_1 = red.mix_oklab(blue, 1.0);
856
857 let rgb_0 = result_0.to_rgb();
859 let rgb_blue = blue.to_rgb();
860 assert!((rgb_0.r - rgb_blue.r).abs() < 0.01);
861 assert!((rgb_0.g - rgb_blue.g).abs() < 0.01);
862 assert!((rgb_0.b - rgb_blue.b).abs() < 0.01);
863
864 let rgb_1 = result_1.to_rgb();
865 let rgb_red = red.to_rgb();
866 assert!((rgb_1.r - rgb_red.r).abs() < 0.01);
867 assert!((rgb_1.g - rgb_red.g).abs() < 0.01);
868 assert!((rgb_1.b - rgb_red.b).abs() < 0.01);
869 }
870
871 #[test]
872 fn test_color_name() {
873 assert_eq!(ColorName::Purple.to_string(), "Purple");
874 assert_eq!(format!("{}", ColorName::Green), "Green");
875 assert_eq!(format!("{:?}", ColorName::Yellow), "Yellow");
876
877 let color = ColorName::Green;
878 assert_eq!(color.scale(500).to_hex(), "#21C55E");
879 assert_eq!(color.scale(1500).to_hex(), "#21C55E");
880
881 for name in ColorName::all().iter() {
882 let name1: ColorName = name.to_string().as_str().try_into().unwrap();
883 assert_eq!(name1, *name);
884 }
885 }
886
887 #[test]
888 fn test_h_s_l() {
889 let color = hsl(260., 94., 80.);
890 assert_eq!(color.hue(200. / 360.), hsl(200., 94., 80.));
891 assert_eq!(color.saturation(74. / 100.), hsl(260., 74., 80.));
892 assert_eq!(color.lightness(74. / 100.), hsl(260., 94., 74.));
893 }
894
895 #[test]
896 fn test_try_parse_color() {
897 assert_eq!(
898 try_parse_color("#F2F200").ok(),
899 Some(hsla(0.16666667, 1., 0.4745098, 1.0))
900 );
901 assert_eq!(
902 try_parse_color("#00f21888").ok(),
903 Some(hsla(0.34986225, 1.0, 0.4745098, 0.53333336))
904 );
905 assert_eq!(try_parse_color("black").ok(), Some(crate::black()));
906 assert_eq!(try_parse_color("white-800").ok(), Some(crate::white()));
907 assert_eq!(try_parse_color("red").ok(), Some(crate::red_500()));
908 assert_eq!(try_parse_color("blue-600").ok(), Some(crate::blue_600()));
909 assert_eq!(
910 try_parse_color("pink/33").ok(),
911 Some(crate::pink_500().opacity(0.33))
912 );
913 assert_eq!(
914 try_parse_color("orange-300/66").ok(),
915 Some(crate::orange_300().opacity(0.66))
916 );
917 }
918}