1use std::{collections::HashMap, fmt::Display};
2
3use gpui::{hsla, Hsla, SharedString};
4use serde::{de::Error, Deserialize, Deserializer};
5
6use anyhow::Result;
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 hue(&self, hue: f32) -> Self;
46 fn saturation(&self, saturation: f32) -> Self;
48 fn lightness(&self, lightness: f32) -> Self;
50
51 fn to_hex(&self) -> String;
53 fn parse_hex(hex: &str) -> Result<Self>;
55}
56
57impl Colorize for Hsla {
58 fn opacity(&self, factor: f32) -> Self {
59 Self {
60 a: self.a * factor.clamp(0.0, 1.0),
61 ..*self
62 }
63 }
64
65 fn divide(&self, divisor: f32) -> Self {
66 Self {
67 a: divisor,
68 ..*self
69 }
70 }
71
72 fn invert(&self) -> Self {
73 Self {
74 h: 1.0 - self.h,
75 s: 1.0 - self.s,
76 l: 1.0 - self.l,
77 a: self.a,
78 }
79 }
80
81 fn invert_l(&self) -> Self {
82 Self {
83 l: 1.0 - self.l,
84 ..*self
85 }
86 }
87
88 fn lighten(&self, factor: f32) -> Self {
89 let l = self.l * (1.0 + factor.clamp(0.0, 1.0));
90
91 Hsla { l, ..*self }
92 }
93
94 fn darken(&self, factor: f32) -> Self {
95 let l = self.l * (1.0 - factor.clamp(0.0, 1.0));
96
97 Self { l, ..*self }
98 }
99
100 fn apply(&self, new_color: Self) -> Self {
101 Hsla {
102 h: new_color.h,
103 s: new_color.s,
104 l: self.l,
105 a: self.a,
106 }
107 }
108
109 fn mix(&self, other: Self, factor: f32) -> Self {
112 let factor = factor.clamp(0.0, 1.0);
113 let inv = 1.0 - factor;
114
115 #[inline]
116 fn lerp_hue(a: f32, b: f32, t: f32) -> f32 {
117 let diff = (b - a + 180.0).rem_euclid(360.) - 180.;
118 (a + diff * t).rem_euclid(360.0)
119 }
120
121 Hsla {
122 h: lerp_hue(self.h * 360., other.h * 360., factor) / 360.,
123 s: self.s * factor + other.s * inv,
124 l: self.l * factor + other.l * inv,
125 a: self.a * factor + other.a * inv,
126 }
127 }
128
129 fn to_hex(&self) -> String {
130 let rgb = self.to_rgb();
131
132 if rgb.a < 1. {
133 return format!(
134 "#{:02X}{:02X}{:02X}{:02X}",
135 ((rgb.r * 255.) as u32),
136 ((rgb.g * 255.) as u32),
137 ((rgb.b * 255.) as u32),
138 ((self.a * 255.) as u32)
139 );
140 }
141
142 format!(
143 "#{:02X}{:02X}{:02X}",
144 ((rgb.r * 255.) as u32),
145 ((rgb.g * 255.) as u32),
146 ((rgb.b * 255.) as u32)
147 )
148 }
149
150 fn parse_hex(hex: &str) -> Result<Self> {
151 let hex = hex.trim_start_matches('#');
152 let len = hex.len();
153 if len != 6 && len != 8 {
154 return Err(anyhow::anyhow!("invalid hex color"));
155 }
156
157 let r = u8::from_str_radix(&hex[0..2], 16)? as f32 / 255.;
158 let g = u8::from_str_radix(&hex[2..4], 16)? as f32 / 255.;
159 let b = u8::from_str_radix(&hex[4..6], 16)? as f32 / 255.;
160 let a = if len == 8 {
161 u8::from_str_radix(&hex[6..8], 16)? as f32 / 255.
162 } else {
163 1.
164 };
165
166 let v = gpui::Rgba { r, g, b, a };
167 let color: Hsla = v.into();
168 Ok(color)
169 }
170
171 fn hue(&self, hue: f32) -> Self {
172 let mut color = *self;
173 color.h = hue.clamp(0., 1.);
174 color
175 }
176
177 fn saturation(&self, saturation: f32) -> Self {
178 let mut color = *self;
179 color.s = saturation.clamp(0., 1.);
180 color
181 }
182
183 fn lightness(&self, lightness: f32) -> Self {
184 let mut color = *self;
185 color.l = lightness.clamp(0., 1.);
186 color
187 }
188}
189
190pub(crate) static DEFAULT_COLORS: once_cell::sync::Lazy<ShadcnColors> =
191 once_cell::sync::Lazy::new(|| {
192 serde_json::from_str(include_str!("./default-colors.json"))
193 .expect("failed to parse default-colors.json")
194 });
195
196type ColorScales = HashMap<usize, ShadcnColor>;
197
198mod color_scales {
199 use std::collections::HashMap;
200
201 use super::{ColorScales, ShadcnColor};
202
203 use serde::de::{Deserialize, Deserializer};
204
205 pub fn deserialize<'de, D>(deserializer: D) -> Result<ColorScales, D::Error>
206 where
207 D: Deserializer<'de>,
208 {
209 let mut map = HashMap::new();
210 for color in Vec::<ShadcnColor>::deserialize(deserializer)? {
211 map.insert(color.scale, color);
212 }
213 Ok(map)
214 }
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
219pub enum ColorName {
220 Gray,
221 Red,
222 Orange,
223 Amber,
224 Yellow,
225 Lime,
226 Green,
227 Emerald,
228 Teal,
229 Cyan,
230 Sky,
231 Blue,
232 Indigo,
233 Violet,
234 Purple,
235 Fuchsia,
236 Pink,
237 Rose,
238}
239
240impl Display for ColorName {
241 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242 write!(f, "{:?}", self)
243 }
244}
245
246impl From<&str> for ColorName {
247 fn from(value: &str) -> Self {
248 match value.to_lowercase().as_str() {
249 "gray" => ColorName::Gray,
250 "red" => ColorName::Red,
251 "orange" => ColorName::Orange,
252 "amber" => ColorName::Amber,
253 "yellow" => ColorName::Yellow,
254 "lime" => ColorName::Lime,
255 "green" => ColorName::Green,
256 "emerald" => ColorName::Emerald,
257 "teal" => ColorName::Teal,
258 "cyan" => ColorName::Cyan,
259 "sky" => ColorName::Sky,
260 "blue" => ColorName::Blue,
261 "indigo" => ColorName::Indigo,
262 "violet" => ColorName::Violet,
263 "purple" => ColorName::Purple,
264 "fuchsia" => ColorName::Fuchsia,
265 "pink" => ColorName::Pink,
266 "rose" => ColorName::Rose,
267 _ => ColorName::Gray,
268 }
269 }
270}
271
272impl From<SharedString> for ColorName {
273 fn from(value: SharedString) -> Self {
274 value.as_ref().into()
275 }
276}
277
278impl ColorName {
279 pub fn all() -> [Self; 18] {
281 [
282 ColorName::Gray,
283 ColorName::Red,
284 ColorName::Orange,
285 ColorName::Amber,
286 ColorName::Yellow,
287 ColorName::Lime,
288 ColorName::Green,
289 ColorName::Emerald,
290 ColorName::Teal,
291 ColorName::Cyan,
292 ColorName::Sky,
293 ColorName::Blue,
294 ColorName::Indigo,
295 ColorName::Violet,
296 ColorName::Purple,
297 ColorName::Fuchsia,
298 ColorName::Pink,
299 ColorName::Rose,
300 ]
301 }
302
303 pub fn scale(&self, scale: usize) -> Hsla {
308 let colors = match self {
309 ColorName::Gray => &DEFAULT_COLORS.gray,
310 ColorName::Red => &DEFAULT_COLORS.red,
311 ColorName::Orange => &DEFAULT_COLORS.orange,
312 ColorName::Amber => &DEFAULT_COLORS.amber,
313 ColorName::Yellow => &DEFAULT_COLORS.yellow,
314 ColorName::Lime => &DEFAULT_COLORS.lime,
315 ColorName::Green => &DEFAULT_COLORS.green,
316 ColorName::Emerald => &DEFAULT_COLORS.emerald,
317 ColorName::Teal => &DEFAULT_COLORS.teal,
318 ColorName::Cyan => &DEFAULT_COLORS.cyan,
319 ColorName::Sky => &DEFAULT_COLORS.sky,
320 ColorName::Blue => &DEFAULT_COLORS.blue,
321 ColorName::Indigo => &DEFAULT_COLORS.indigo,
322 ColorName::Violet => &DEFAULT_COLORS.violet,
323 ColorName::Purple => &DEFAULT_COLORS.purple,
324 ColorName::Fuchsia => &DEFAULT_COLORS.fuchsia,
325 ColorName::Pink => &DEFAULT_COLORS.pink,
326 ColorName::Rose => &DEFAULT_COLORS.rose,
327 };
328
329 if let Some(color) = colors.get(&scale) {
330 color.hsla
331 } else {
332 colors.get(&500).unwrap().hsla
333 }
334 }
335}
336
337#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
338pub(crate) struct ShadcnColors {
339 pub(crate) black: ShadcnColor,
340 pub(crate) white: ShadcnColor,
341 #[serde(with = "color_scales")]
342 pub(crate) slate: ColorScales,
343 #[serde(with = "color_scales")]
344 pub(crate) gray: ColorScales,
345 #[serde(with = "color_scales")]
346 pub(crate) zinc: ColorScales,
347 #[serde(with = "color_scales")]
348 pub(crate) neutral: ColorScales,
349 #[serde(with = "color_scales")]
350 pub(crate) stone: ColorScales,
351 #[serde(with = "color_scales")]
352 pub(crate) red: ColorScales,
353 #[serde(with = "color_scales")]
354 pub(crate) orange: ColorScales,
355 #[serde(with = "color_scales")]
356 pub(crate) amber: ColorScales,
357 #[serde(with = "color_scales")]
358 pub(crate) yellow: ColorScales,
359 #[serde(with = "color_scales")]
360 pub(crate) lime: ColorScales,
361 #[serde(with = "color_scales")]
362 pub(crate) green: ColorScales,
363 #[serde(with = "color_scales")]
364 pub(crate) emerald: ColorScales,
365 #[serde(with = "color_scales")]
366 pub(crate) teal: ColorScales,
367 #[serde(with = "color_scales")]
368 pub(crate) cyan: ColorScales,
369 #[serde(with = "color_scales")]
370 pub(crate) sky: ColorScales,
371 #[serde(with = "color_scales")]
372 pub(crate) blue: ColorScales,
373 #[serde(with = "color_scales")]
374 pub(crate) indigo: ColorScales,
375 #[serde(with = "color_scales")]
376 pub(crate) violet: ColorScales,
377 #[serde(with = "color_scales")]
378 pub(crate) purple: ColorScales,
379 #[serde(with = "color_scales")]
380 pub(crate) fuchsia: ColorScales,
381 #[serde(with = "color_scales")]
382 pub(crate) pink: ColorScales,
383 #[serde(with = "color_scales")]
384 pub(crate) rose: ColorScales,
385}
386
387#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Deserialize)]
388pub(crate) struct ShadcnColor {
389 #[serde(default)]
390 pub(crate) scale: usize,
391 #[serde(deserialize_with = "from_hsl_channel", alias = "hslChannel")]
392 pub(crate) hsla: Hsla,
393}
394
395fn from_hsl_channel<'de, D>(deserializer: D) -> Result<Hsla, D::Error>
397where
398 D: Deserializer<'de>,
399{
400 let s: String = Deserialize::deserialize(deserializer).unwrap();
401
402 let mut parts = s.split_whitespace();
403 if parts.clone().count() != 3 {
404 return Err(D::Error::custom(
405 "expected hslChannel has 3 parts, e.g: '210 40% 98%'",
406 ));
407 }
408
409 fn parse_number(s: &str) -> f32 {
410 s.trim_end_matches('%')
411 .parse()
412 .expect("failed to parse number")
413 }
414
415 let (h, s, l) = (
416 parse_number(parts.next().unwrap()),
417 parse_number(parts.next().unwrap()),
418 parse_number(parts.next().unwrap()),
419 );
420
421 Ok(hsl(h, s, l))
422}
423
424macro_rules! color_method {
425 ($color:tt, $scale:tt) => {
426 paste::paste! {
427 #[inline]
428 #[allow(unused)]
429 pub fn [<$color _ $scale>]() -> Hsla {
430 if let Some(color) = DEFAULT_COLORS.$color.get(&($scale as usize)) {
431 return color.hsla;
432 }
433
434 black()
435 }
436 }
437 };
438}
439
440macro_rules! color_methods {
441 ($color:tt) => {
442 paste::paste! {
443 #[inline]
450 pub fn [<$color>](scale: usize) -> Hsla {
451 if let Some(color) = DEFAULT_COLORS.$color.get(&scale) {
452 return color.hsla;
453 }
454
455 black()
456 }
457 }
458
459 color_method!($color, 50);
460 color_method!($color, 100);
461 color_method!($color, 200);
462 color_method!($color, 300);
463 color_method!($color, 400);
464 color_method!($color, 500);
465 color_method!($color, 600);
466 color_method!($color, 700);
467 color_method!($color, 800);
468 color_method!($color, 900);
469 color_method!($color, 950);
470 };
471}
472
473pub fn black() -> Hsla {
474 DEFAULT_COLORS.black.hsla
475}
476
477pub fn white() -> Hsla {
478 DEFAULT_COLORS.white.hsla
479}
480
481color_methods!(slate);
482color_methods!(gray);
483color_methods!(zinc);
484color_methods!(neutral);
485color_methods!(stone);
486color_methods!(red);
487color_methods!(orange);
488color_methods!(amber);
489color_methods!(yellow);
490color_methods!(lime);
491color_methods!(green);
492color_methods!(emerald);
493color_methods!(teal);
494color_methods!(cyan);
495color_methods!(sky);
496color_methods!(blue);
497color_methods!(indigo);
498color_methods!(violet);
499color_methods!(purple);
500color_methods!(fuchsia);
501color_methods!(pink);
502color_methods!(rose);
503
504#[cfg(test)]
505mod tests {
506 use gpui::{rgb, rgba};
507
508 use super::*;
509
510 #[test]
511 fn test_default_colors() {
512 assert_eq!(white(), hsl(0.0, 0.0, 100.0));
513 assert_eq!(black(), hsl(0.0, 0.0, 0.0));
514
515 assert_eq!(slate_50(), hsl(210.0, 40.0, 98.0));
516 assert_eq!(slate_100(), hsl(210.0, 40.0, 96.1));
517 assert_eq!(slate_900(), hsl(222.2, 47.4, 11.2));
518
519 assert_eq!(red_50(), hsl(0.0, 85.7, 97.3));
520 assert_eq!(yellow_100(), hsl(54.9, 96.7, 88.0));
521 assert_eq!(green_200(), hsl(141.0, 78.9, 85.1));
522 assert_eq!(cyan_300(), hsl(187.0, 92.4, 69.0));
523 assert_eq!(blue_400(), hsl(213.1, 93.9, 67.8));
524 assert_eq!(indigo_500(), hsl(238.7, 83.5, 66.7));
525 }
526
527 #[test]
528 fn test_to_hex_string() {
529 let color: Hsla = rgb(0xf8fafc).into();
530 assert_eq!(color.to_hex(), "#F8FAFC");
531
532 let color: Hsla = rgb(0xfef2f2).into();
533 assert_eq!(color.to_hex(), "#FEF2F2");
534
535 let color: Hsla = rgba(0x0413fcaa).into();
536 assert_eq!(color.to_hex(), "#0413FCAA");
537 }
538
539 #[test]
540 fn test_from_hex_string() {
541 let color: Hsla = Hsla::parse_hex("#F8FAFC").unwrap();
542 assert_eq!(color, rgb(0xf8fafc).into());
543
544 let color: Hsla = Hsla::parse_hex("#FEF2F2").unwrap();
545 assert_eq!(color, rgb(0xfef2f2).into());
546
547 let color: Hsla = Hsla::parse_hex("#0413FCAA").unwrap();
548 assert_eq!(color, rgba(0x0413fcaa).into());
549 }
550
551 #[test]
552 fn test_lighten() {
553 let color = super::hsl(240.0, 5.0, 30.0);
554 let color = color.lighten(0.5);
555 assert_eq!(color.l, 0.45000002);
556 let color = color.lighten(0.5);
557 assert_eq!(color.l, 0.675);
558 let color = color.lighten(0.1);
559 assert_eq!(color.l, 0.7425);
560 }
561
562 #[test]
563 fn test_darken() {
564 let color = super::hsl(240.0, 5.0, 96.0);
565 let color = color.darken(0.5);
566 assert_eq!(color.l, 0.48);
567 let color = color.darken(0.5);
568 assert_eq!(color.l, 0.24);
569 }
570
571 #[test]
572 fn test_mix() {
573 let red = Hsla::parse_hex("#FF0000").unwrap();
574 let blue = Hsla::parse_hex("#0000FF").unwrap();
575 let green = Hsla::parse_hex("#00FF00").unwrap();
576 let yellow = Hsla::parse_hex("#FFFF00").unwrap();
577
578 assert_eq!(red.mix(blue, 0.5).to_hex(), "#FF00FF");
579 assert_eq!(green.mix(red, 0.5).to_hex(), "#FFFF00");
580 assert_eq!(blue.mix(yellow, 0.2).to_hex(), "#0098FF");
581 }
582
583 #[test]
584 fn test_color_name() {
585 assert_eq!(ColorName::Purple.to_string(), "Purple");
586 assert_eq!(format!("{}", ColorName::Green), "Green");
587 assert_eq!(format!("{:?}", ColorName::Yellow), "Yellow");
588
589 let color = ColorName::Green;
590 assert_eq!(color.scale(500).to_hex(), "#21C55E");
591 assert_eq!(color.scale(1500).to_hex(), "#21C55E");
592
593 for name in ColorName::all().iter() {
594 let name1: ColorName = name.to_string().as_str().into();
595 assert_eq!(name1, *name);
596 }
597 }
598
599 #[test]
600 fn test_h_s_l() {
601 let color = hsl(260., 94., 80.);
602 assert_eq!(color.hue(200. / 360.), hsl(200., 94., 80.));
603 assert_eq!(color.saturation(74. / 100.), hsl(260., 74., 80.));
604 assert_eq!(color.lightness(74. / 100.), hsl(260., 94., 74.));
605 }
606}