1use crate::{Color, color};
3
4use std::sync::LazyLock;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct Palette {
10 pub background: Color,
12 pub text: Color,
14 pub primary: Color,
16 pub success: Color,
18 pub warning: Color,
20 pub danger: Color,
22}
23
24impl Palette {
25 pub const LIGHT: Self = Self {
27 background: Color::WHITE,
28 text: Color::BLACK,
29 primary: color!(0x5865F2),
30 success: color!(0x12664f),
31 warning: color!(0xb77e33),
32 danger: color!(0xc3423f),
33 };
34
35 pub const DARK: Self = Self {
37 background: color!(0x2B2D31),
38 text: Color::from_rgb(0.90, 0.90, 0.90),
39 primary: color!(0x5865F2),
40 success: color!(0x12664f),
41 warning: color!(0xffc14e),
42 danger: color!(0xc3423f),
43 };
44
45 pub const DRACULA: Self = Self {
49 background: color!(0x282A36), text: color!(0xf8f8f2), primary: color!(0xbd93f9), success: color!(0x50fa7b), warning: color!(0xf1fa8c), danger: color!(0xff5555), };
56
57 pub const NORD: Self = Self {
61 background: color!(0x2e3440), text: color!(0xeceff4), primary: color!(0x8fbcbb), success: color!(0xa3be8c), warning: color!(0xebcb8b), danger: color!(0xbf616a), };
68
69 pub const SOLARIZED_LIGHT: Self = Self {
73 background: color!(0xfdf6e3), text: color!(0x657b83), primary: color!(0x2aa198), success: color!(0x859900), warning: color!(0xb58900), danger: color!(0xdc322f), };
80
81 pub const SOLARIZED_DARK: Self = Self {
85 background: color!(0x002b36), text: color!(0x839496), primary: color!(0x2aa198), success: color!(0x859900), warning: color!(0xb58900), danger: color!(0xdc322f), };
92
93 pub const GRUVBOX_LIGHT: Self = Self {
97 background: color!(0xfbf1c7), text: color!(0x282828), primary: color!(0x458588), success: color!(0x98971a), warning: color!(0xd79921), danger: color!(0xcc241d), };
104
105 pub const GRUVBOX_DARK: Self = Self {
109 background: color!(0x282828), text: color!(0xfbf1c7), primary: color!(0x458588), success: color!(0x98971a), warning: color!(0xd79921), danger: color!(0xcc241d), };
116
117 pub const CATPPUCCIN_LATTE: Self = Self {
121 background: color!(0xeff1f5), text: color!(0x4c4f69), primary: color!(0x1e66f5), success: color!(0x40a02b), warning: color!(0xdf8e1d), danger: color!(0xd20f39), };
128
129 pub const CATPPUCCIN_FRAPPE: Self = Self {
133 background: color!(0x303446), text: color!(0xc6d0f5), primary: color!(0x8caaee), success: color!(0xa6d189), warning: color!(0xe5c890), danger: color!(0xe78284), };
140
141 pub const CATPPUCCIN_MACCHIATO: Self = Self {
145 background: color!(0x24273a), text: color!(0xcad3f5), primary: color!(0x8aadf4), success: color!(0xa6da95), warning: color!(0xeed49f), danger: color!(0xed8796), };
152
153 pub const CATPPUCCIN_MOCHA: Self = Self {
157 background: color!(0x1e1e2e), text: color!(0xcdd6f4), primary: color!(0x89b4fa), success: color!(0xa6e3a1), warning: color!(0xf9e2af), danger: color!(0xf38ba8), };
164
165 pub const TOKYO_NIGHT: Self = Self {
169 background: color!(0x1a1b26), text: color!(0x9aa5ce), primary: color!(0x2ac3de), success: color!(0x9ece6a), warning: color!(0xe0af68), danger: color!(0xf7768e), };
176
177 pub const TOKYO_NIGHT_STORM: Self = Self {
181 background: color!(0x24283b), text: color!(0x9aa5ce), primary: color!(0x2ac3de), success: color!(0x9ece6a), warning: color!(0xe0af68), danger: color!(0xf7768e), };
188
189 pub const TOKYO_NIGHT_LIGHT: Self = Self {
193 background: color!(0xd5d6db), text: color!(0x565a6e), primary: color!(0x166775), success: color!(0x485e30), warning: color!(0x8f5e15), danger: color!(0x8c4351), };
200
201 pub const KANAGAWA_WAVE: Self = Self {
205 background: color!(0x1f1f28), text: color!(0xDCD7BA), primary: color!(0x7FB4CA), success: color!(0x76946A), warning: color!(0xff9e3b), danger: color!(0xC34043), };
212
213 pub const KANAGAWA_DRAGON: Self = Self {
217 background: color!(0x181616), text: color!(0xc5c9c5), primary: color!(0x223249), success: color!(0x8a9a7b), warning: color!(0xff9e3b), danger: color!(0xc4746e), };
224
225 pub const KANAGAWA_LOTUS: Self = Self {
229 background: color!(0xf2ecbc), text: color!(0x545464), primary: color!(0x4d699b), success: color!(0x6f894e), warning: color!(0xe98a00), danger: color!(0xc84053), };
236
237 pub const MOONFLY: Self = Self {
241 background: color!(0x080808), text: color!(0xbdbdbd), primary: color!(0x80a0ff), success: color!(0x8cc85f), warning: color!(0xe3c78a), danger: color!(0xff5454), };
248
249 pub const NIGHTFLY: Self = Self {
253 background: color!(0x011627), text: color!(0xbdc1c6), primary: color!(0x82aaff), success: color!(0xa1cd5e), warning: color!(0xe3d18a), danger: color!(0xfc514e), };
260
261 pub const OXOCARBON: Self = Self {
265 background: color!(0x232323),
266 text: color!(0xd0d0d0),
267 primary: color!(0x00b4ff),
268 success: color!(0x00c15a),
269 warning: color!(0xbe95ff), danger: color!(0xf62d0f),
271 };
272
273 pub const FERRA: Self = Self {
277 background: color!(0x2b292d),
278 text: color!(0xfecdb2),
279 primary: color!(0xd1d1e0),
280 success: color!(0xb1b695),
281 warning: color!(0xf5d76e), danger: color!(0xe06b75),
283 };
284}
285
286#[derive(Debug, Clone, Copy, PartialEq)]
288pub struct Extended {
289 pub background: Background,
291 pub primary: Primary,
293 pub secondary: Secondary,
295 pub success: Success,
297 pub warning: Warning,
299 pub danger: Danger,
301 pub is_dark: bool,
303}
304
305pub static EXTENDED_LIGHT: LazyLock<Extended> =
307 LazyLock::new(|| Extended::generate(Palette::LIGHT));
308
309pub static EXTENDED_DARK: LazyLock<Extended> = LazyLock::new(|| Extended::generate(Palette::DARK));
311
312pub static EXTENDED_DRACULA: LazyLock<Extended> =
314 LazyLock::new(|| Extended::generate(Palette::DRACULA));
315
316pub static EXTENDED_NORD: LazyLock<Extended> = LazyLock::new(|| Extended::generate(Palette::NORD));
318
319pub static EXTENDED_SOLARIZED_LIGHT: LazyLock<Extended> =
321 LazyLock::new(|| Extended::generate(Palette::SOLARIZED_LIGHT));
322
323pub static EXTENDED_SOLARIZED_DARK: LazyLock<Extended> =
325 LazyLock::new(|| Extended::generate(Palette::SOLARIZED_DARK));
326
327pub static EXTENDED_GRUVBOX_LIGHT: LazyLock<Extended> =
329 LazyLock::new(|| Extended::generate(Palette::GRUVBOX_LIGHT));
330
331pub static EXTENDED_GRUVBOX_DARK: LazyLock<Extended> =
333 LazyLock::new(|| Extended::generate(Palette::GRUVBOX_DARK));
334
335pub static EXTENDED_CATPPUCCIN_LATTE: LazyLock<Extended> =
337 LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_LATTE));
338
339pub static EXTENDED_CATPPUCCIN_FRAPPE: LazyLock<Extended> =
341 LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_FRAPPE));
342
343pub static EXTENDED_CATPPUCCIN_MACCHIATO: LazyLock<Extended> =
345 LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_MACCHIATO));
346
347pub static EXTENDED_CATPPUCCIN_MOCHA: LazyLock<Extended> =
349 LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_MOCHA));
350
351pub static EXTENDED_TOKYO_NIGHT: LazyLock<Extended> =
353 LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT));
354
355pub static EXTENDED_TOKYO_NIGHT_STORM: LazyLock<Extended> =
357 LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT_STORM));
358
359pub static EXTENDED_TOKYO_NIGHT_LIGHT: LazyLock<Extended> =
361 LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT_LIGHT));
362
363pub static EXTENDED_KANAGAWA_WAVE: LazyLock<Extended> =
365 LazyLock::new(|| Extended::generate(Palette::KANAGAWA_WAVE));
366
367pub static EXTENDED_KANAGAWA_DRAGON: LazyLock<Extended> =
369 LazyLock::new(|| Extended::generate(Palette::KANAGAWA_DRAGON));
370
371pub static EXTENDED_KANAGAWA_LOTUS: LazyLock<Extended> =
373 LazyLock::new(|| Extended::generate(Palette::KANAGAWA_LOTUS));
374
375pub static EXTENDED_MOONFLY: LazyLock<Extended> =
377 LazyLock::new(|| Extended::generate(Palette::MOONFLY));
378
379pub static EXTENDED_NIGHTFLY: LazyLock<Extended> =
381 LazyLock::new(|| Extended::generate(Palette::NIGHTFLY));
382
383pub static EXTENDED_OXOCARBON: LazyLock<Extended> =
385 LazyLock::new(|| Extended::generate(Palette::OXOCARBON));
386
387pub static EXTENDED_FERRA: LazyLock<Extended> =
389 LazyLock::new(|| Extended::generate(Palette::FERRA));
390
391impl Extended {
392 pub fn generate(palette: Palette) -> Self {
394 Self {
395 background: Background::new(palette.background, palette.text),
396 primary: Primary::generate(palette.primary, palette.background, palette.text),
397 secondary: Secondary::generate(palette.background, palette.text),
398 success: Success::generate(palette.success, palette.background, palette.text),
399 warning: Warning::generate(palette.warning, palette.background, palette.text),
400 danger: Danger::generate(palette.danger, palette.background, palette.text),
401 is_dark: is_dark(palette.background),
402 }
403 }
404}
405
406#[derive(Debug, Clone, Copy, PartialEq)]
408pub struct Pair {
409 pub color: Color,
411
412 pub text: Color,
418}
419
420impl Pair {
421 pub fn new(color: Color, text: Color) -> Self {
423 Self {
424 color,
425 text: readable(color, text),
426 }
427 }
428}
429
430#[derive(Debug, Clone, Copy, PartialEq)]
432pub struct Background {
433 pub base: Pair,
435 pub weakest: Pair,
437 pub weaker: Pair,
439 pub weak: Pair,
441 pub neutral: Pair,
443 pub strong: Pair,
445 pub stronger: Pair,
447 pub strongest: Pair,
449}
450
451impl Background {
452 pub fn new(base: Color, text: Color) -> Self {
454 let weakest = deviate(base, 0.03);
455 let weaker = deviate(base, 0.07);
456 let weak = deviate(base, 0.1);
457 let neutral = deviate(base, 0.125);
458 let strong = deviate(base, 0.15);
459 let stronger = deviate(base, 0.175);
460 let strongest = deviate(base, 0.20);
461
462 Self {
463 base: Pair::new(base, text),
464 weakest: Pair::new(weakest, text),
465 weaker: Pair::new(weaker, text),
466 weak: Pair::new(weak, text),
467 neutral: Pair::new(neutral, text),
468 strong: Pair::new(strong, text),
469 stronger: Pair::new(stronger, text),
470 strongest: Pair::new(strongest, text),
471 }
472 }
473}
474
475#[derive(Debug, Clone, Copy, PartialEq)]
477pub struct Primary {
478 pub base: Pair,
480 pub weak: Pair,
482 pub strong: Pair,
484}
485
486impl Primary {
487 pub fn generate(base: Color, background: Color, text: Color) -> Self {
489 let weak = mix(base, background, 0.4);
490 let strong = deviate(base, 0.1);
491
492 Self {
493 base: Pair::new(base, text),
494 weak: Pair::new(weak, text),
495 strong: Pair::new(strong, text),
496 }
497 }
498}
499
500#[derive(Debug, Clone, Copy, PartialEq)]
502pub struct Secondary {
503 pub base: Pair,
505 pub weak: Pair,
507 pub strong: Pair,
509}
510
511impl Secondary {
512 pub fn generate(base: Color, text: Color) -> Self {
514 let factor = if is_dark(base) { 0.2 } else { 0.4 };
515
516 let weak = mix(deviate(base, 0.1), text, factor);
517 let strong = mix(deviate(base, 0.3), text, factor);
518 let base = mix(deviate(base, 0.2), text, factor);
519
520 Self {
521 base: Pair::new(base, text),
522 weak: Pair::new(weak, text),
523 strong: Pair::new(strong, text),
524 }
525 }
526}
527
528#[derive(Debug, Clone, Copy, PartialEq)]
530pub struct Success {
531 pub base: Pair,
533 pub weak: Pair,
535 pub strong: Pair,
537}
538
539impl Success {
540 pub fn generate(base: Color, background: Color, text: Color) -> Self {
542 let weak = mix(base, background, 0.4);
543 let strong = deviate(base, 0.1);
544
545 Self {
546 base: Pair::new(base, text),
547 weak: Pair::new(weak, text),
548 strong: Pair::new(strong, text),
549 }
550 }
551}
552
553#[derive(Debug, Clone, Copy, PartialEq)]
555pub struct Warning {
556 pub base: Pair,
558 pub weak: Pair,
560 pub strong: Pair,
562}
563
564impl Warning {
565 pub fn generate(base: Color, background: Color, text: Color) -> Self {
567 let weak = mix(base, background, 0.4);
568 let strong = deviate(base, 0.1);
569
570 Self {
571 base: Pair::new(base, text),
572 weak: Pair::new(weak, text),
573 strong: Pair::new(strong, text),
574 }
575 }
576}
577
578#[derive(Debug, Clone, Copy, PartialEq)]
580pub struct Danger {
581 pub base: Pair,
583 pub weak: Pair,
585 pub strong: Pair,
587}
588
589impl Danger {
590 pub fn generate(base: Color, background: Color, text: Color) -> Self {
592 let weak = mix(base, background, 0.4);
593 let strong = deviate(base, 0.1);
594
595 Self {
596 base: Pair::new(base, text),
597 weak: Pair::new(weak, text),
598 strong: Pair::new(strong, text),
599 }
600 }
601}
602
603struct Oklch {
604 l: f32,
605 c: f32,
606 h: f32,
607 a: f32,
608}
609
610pub fn darken(color: Color, amount: f32) -> Color {
612 let mut oklch = to_oklch(color);
613
614 if oklch.c > 0.0 && oklch.c < (1.0 - oklch.l) / 2.0 {
616 oklch.c *= 1.0 + (0.2 / oklch.c).min(100.0) * amount;
618 }
619
620 oklch.l = if oklch.l - amount < 0.0 {
621 0.0
622 } else {
623 oklch.l - amount
624 };
625
626 from_oklch(oklch)
627}
628
629pub fn lighten(color: Color, amount: f32) -> Color {
631 let mut oklch = to_oklch(color);
632
633 oklch.c *= 1.0 + 2.0 * amount / oklch.l.max(0.05);
636
637 oklch.l = if oklch.l + amount > 1.0 {
638 1.0
639 } else {
640 oklch.l + amount
641 };
642
643 from_oklch(oklch)
644}
645
646pub fn deviate(color: Color, amount: f32) -> Color {
649 if is_dark(color) {
650 lighten(color, amount)
651 } else {
652 darken(color, amount)
653 }
654}
655
656pub fn mix(a: Color, b: Color, factor: f32) -> Color {
658 let b_amount = factor.clamp(0.0, 1.0);
659 let a_amount = 1.0 - b_amount;
660
661 let a_linear = a.into_linear().map(|c| c * a_amount);
662 let b_linear = b.into_linear().map(|c| c * b_amount);
663
664 Color::from_linear_rgba(
665 a_linear[0] + b_linear[0],
666 a_linear[1] + b_linear[1],
667 a_linear[2] + b_linear[2],
668 a_linear[3] + b_linear[3],
669 )
670}
671
672pub fn readable(background: Color, text: Color) -> Color {
675 if text.is_readable_on(background) {
676 return text;
677 }
678
679 let improve = if is_dark(background) { lighten } else { darken };
680
681 let candidate = improve(text, 0.1);
683
684 if candidate.is_readable_on(background) {
685 return candidate;
686 }
687
688 let candidate = improve(text, 0.2);
689
690 if candidate.is_readable_on(background) {
691 return candidate;
692 }
693
694 let white_contrast = background.relative_contrast(Color::WHITE);
695 let black_contrast = background.relative_contrast(Color::BLACK);
696
697 if white_contrast >= black_contrast {
698 mix(Color::WHITE, background, 0.05)
699 } else {
700 mix(Color::BLACK, background, 0.05)
701 }
702}
703
704pub fn is_dark(color: Color) -> bool {
706 to_oklch(color).l < 0.6
707}
708
709fn to_oklch(color: Color) -> Oklch {
711 let [r, g, b, alpha] = color.into_linear();
712
713 let l = 0.41222146 * r + 0.53633255 * g + 0.051445995 * b;
715 let m = 0.2119035 * r + 0.6806995 * g + 0.10739696 * b;
716 let s = 0.08830246 * r + 0.28171885 * g + 0.6299787 * b;
717
718 let l_ = l.cbrt();
720 let m_ = m.cbrt();
721 let s_ = s.cbrt();
722
723 let l = 0.21045426 * l_ + 0.7936178 * m_ - 0.004072047 * s_;
725 let a = 1.9779985 * l_ - 2.4285922 * m_ + 0.4505937 * s_;
726 let b = 0.025904037 * l_ + 0.78277177 * m_ - 0.80867577 * s_;
727
728 let c = (a * a + b * b).sqrt();
730 let h = b.atan2(a); Oklch { l, c, h, a: alpha }
733}
734
735fn from_oklch(oklch: Oklch) -> Color {
737 let Oklch { l, c, h, a: alpha } = oklch;
738
739 let a = c * h.cos();
740 let b = c * h.sin();
741
742 let l_ = l + 0.39633778 * a + 0.21580376 * b;
744 let m_ = l - 0.105561346 * a - 0.06385417 * b;
745 let s_ = l - 0.08948418 * a - 1.2914855 * b;
746
747 let l = l_ * l_ * l_;
749 let m = m_ * m_ * m_;
750 let s = s_ * s_ * s_;
751
752 let r = 4.0767417 * l - 3.3077116 * m + 0.23096994 * s;
753 let g = -1.268438 * l + 2.6097574 * m - 0.34131938 * s;
754 let b = -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s;
755
756 Color::from_linear_rgba(
757 r.clamp(0.0, 1.0),
758 g.clamp(0.0, 1.0),
759 b.clamp(0.0, 1.0),
760 alpha,
761 )
762}