iced_core/theme/
palette.rs

1//! Define the colors of a theme.
2use crate::{Color, color};
3
4use std::sync::LazyLock;
5
6/// A color palette.
7#[derive(Debug, Clone, Copy, PartialEq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct Palette {
10    /// The background [`Color`] of the [`Palette`].
11    pub background: Color,
12    /// The text [`Color`] of the [`Palette`].
13    pub text: Color,
14    /// The primary [`Color`] of the [`Palette`].
15    pub primary: Color,
16    /// The success [`Color`] of the [`Palette`].
17    pub success: Color,
18    /// The warning [`Color`] of the [`Palette`].
19    pub warning: Color,
20    /// The danger [`Color`] of the [`Palette`].
21    pub danger: Color,
22}
23
24impl Palette {
25    /// The built-in light variant of a [`Palette`].
26    pub const LIGHT: Self = Self {
27        background: Color::WHITE,
28        text: Color::BLACK,
29        primary: color!(0x5865F2),
30        success: color!(0x12664f),
31        warning: color!(0xffc14e),
32        danger: color!(0xc3423f),
33    };
34
35    /// The built-in dark variant of a [`Palette`].
36    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    /// The built-in [Dracula] variant of a [`Palette`].
46    ///
47    /// [Dracula]: https://draculatheme.com
48    pub const DRACULA: Self = Self {
49        background: color!(0x282A36), // BACKGROUND
50        text: color!(0xf8f8f2),       // FOREGROUND
51        primary: color!(0xbd93f9),    // PURPLE
52        success: color!(0x50fa7b),    // GREEN
53        warning: color!(0xf1fa8c),    // YELLOW
54        danger: color!(0xff5555),     // RED
55    };
56
57    /// The built-in [Nord] variant of a [`Palette`].
58    ///
59    /// [Nord]: https://www.nordtheme.com/docs/colors-and-palettes
60    pub const NORD: Self = Self {
61        background: color!(0x2e3440), // nord0
62        text: color!(0xeceff4),       // nord6
63        primary: color!(0x8fbcbb),    // nord7
64        success: color!(0xa3be8c),    // nord14
65        warning: color!(0xebcb8b),    // nord13
66        danger: color!(0xbf616a),     // nord11
67    };
68
69    /// The built-in [Solarized] Light variant of a [`Palette`].
70    ///
71    /// [Solarized]: https://ethanschoonover.com/solarized
72    pub const SOLARIZED_LIGHT: Self = Self {
73        background: color!(0xfdf6e3), // base3
74        text: color!(0x657b83),       // base00
75        primary: color!(0x2aa198),    // cyan
76        success: color!(0x859900),    // green
77        warning: color!(0xb58900),    // yellow
78        danger: color!(0xdc322f),     // red
79    };
80
81    /// The built-in [Solarized] Dark variant of a [`Palette`].
82    ///
83    /// [Solarized]: https://ethanschoonover.com/solarized
84    pub const SOLARIZED_DARK: Self = Self {
85        background: color!(0x002b36), // base03
86        text: color!(0x839496),       // base0
87        primary: color!(0x2aa198),    // cyan
88        success: color!(0x859900),    // green
89        warning: color!(0xb58900),    // yellow
90        danger: color!(0xdc322f),     // red
91    };
92
93    /// The built-in [Gruvbox] Light variant of a [`Palette`].
94    ///
95    /// [Gruvbox]: https://github.com/morhetz/gruvbox
96    pub const GRUVBOX_LIGHT: Self = Self {
97        background: color!(0xfbf1c7), // light BG_0
98        text: color!(0x282828),       // light FG0_29
99        primary: color!(0x458588),    // light BLUE_4
100        success: color!(0x98971a),    // light GREEN_2
101        warning: color!(0xd79921),    // light YELLOW_3
102        danger: color!(0xcc241d),     // light RED_1
103    };
104
105    /// The built-in [Gruvbox] Dark variant of a [`Palette`].
106    ///
107    /// [Gruvbox]: https://github.com/morhetz/gruvbox
108    pub const GRUVBOX_DARK: Self = Self {
109        background: color!(0x282828), // dark BG_0
110        text: color!(0xfbf1c7),       // dark FG0_29
111        primary: color!(0x458588),    // dark BLUE_4
112        success: color!(0x98971a),    // dark GREEN_2
113        warning: color!(0xd79921),    // dark YELLOW_3
114        danger: color!(0xcc241d),     // dark RED_1
115    };
116
117    /// The built-in [Catppuccin] Latte variant of a [`Palette`].
118    ///
119    /// [Catppuccin]: https://github.com/catppuccin/catppuccin
120    pub const CATPPUCCIN_LATTE: Self = Self {
121        background: color!(0xeff1f5), // Base
122        text: color!(0x4c4f69),       // Text
123        primary: color!(0x1e66f5),    // Blue
124        success: color!(0x40a02b),    // Green
125        warning: color!(0xdf8e1d),    // Yellow
126        danger: color!(0xd20f39),     // Red
127    };
128
129    /// The built-in [Catppuccin] Frappé variant of a [`Palette`].
130    ///
131    /// [Catppuccin]: https://github.com/catppuccin/catppuccin
132    pub const CATPPUCCIN_FRAPPE: Self = Self {
133        background: color!(0x303446), // Base
134        text: color!(0xc6d0f5),       // Text
135        primary: color!(0x8caaee),    // Blue
136        success: color!(0xa6d189),    // Green
137        warning: color!(0xe5c890),    // Yellow
138        danger: color!(0xe78284),     // Red
139    };
140
141    /// The built-in [Catppuccin] Macchiato variant of a [`Palette`].
142    ///
143    /// [Catppuccin]: https://github.com/catppuccin/catppuccin
144    pub const CATPPUCCIN_MACCHIATO: Self = Self {
145        background: color!(0x24273a), // Base
146        text: color!(0xcad3f5),       // Text
147        primary: color!(0x8aadf4),    // Blue
148        success: color!(0xa6da95),    // Green
149        warning: color!(0xeed49f),    // Yellow
150        danger: color!(0xed8796),     // Red
151    };
152
153    /// The built-in [Catppuccin] Mocha variant of a [`Palette`].
154    ///
155    /// [Catppuccin]: https://github.com/catppuccin/catppuccin
156    pub const CATPPUCCIN_MOCHA: Self = Self {
157        background: color!(0x1e1e2e), // Base
158        text: color!(0xcdd6f4),       // Text
159        primary: color!(0x89b4fa),    // Blue
160        success: color!(0xa6e3a1),    // Green
161        warning: color!(0xf9e2af),    // Yellow
162        danger: color!(0xf38ba8),     // Red
163    };
164
165    /// The built-in [Tokyo Night] variant of a [`Palette`].
166    ///
167    /// [Tokyo Night]: https://github.com/enkia/tokyo-night-vscode-theme
168    pub const TOKYO_NIGHT: Self = Self {
169        background: color!(0x1a1b26), // Background (Night)
170        text: color!(0x9aa5ce),       // Text
171        primary: color!(0x2ac3de),    // Blue
172        success: color!(0x9ece6a),    // Green
173        warning: color!(0xe0af68),    // Yellow
174        danger: color!(0xf7768e),     // Red
175    };
176
177    /// The built-in [Tokyo Night] Storm variant of a [`Palette`].
178    ///
179    /// [Tokyo Night]: https://github.com/enkia/tokyo-night-vscode-theme
180    pub const TOKYO_NIGHT_STORM: Self = Self {
181        background: color!(0x24283b), // Background (Storm)
182        text: color!(0x9aa5ce),       // Text
183        primary: color!(0x2ac3de),    // Blue
184        success: color!(0x9ece6a),    // Green
185        warning: color!(0xe0af68),    // Yellow
186        danger: color!(0xf7768e),     // Red
187    };
188
189    /// The built-in [Tokyo Night] Light variant of a [`Palette`].
190    ///
191    /// [Tokyo Night]: https://github.com/enkia/tokyo-night-vscode-theme
192    pub const TOKYO_NIGHT_LIGHT: Self = Self {
193        background: color!(0xd5d6db), // Background
194        text: color!(0x565a6e),       // Text
195        primary: color!(0x166775),    // Blue
196        success: color!(0x485e30),    // Green
197        warning: color!(0x8f5e15),    // Yellow
198        danger: color!(0x8c4351),     // Red
199    };
200
201    /// The built-in [Kanagawa] Wave variant of a [`Palette`].
202    ///
203    /// [Kanagawa]: https://github.com/rebelot/kanagawa.nvim
204    pub const KANAGAWA_WAVE: Self = Self {
205        background: color!(0x1f1f28), // Sumi Ink 3
206        text: color!(0xDCD7BA),       // Fuji White
207        primary: color!(0x7FB4CA),    // Wave Blue
208        success: color!(0x76946A),    // Autumn Green
209        warning: color!(0xff9e3b),    // Ronin Yellow
210        danger: color!(0xC34043),     // Autumn Red
211    };
212
213    /// The built-in [Kanagawa] Dragon variant of a [`Palette`].
214    ///
215    /// [Kanagawa]: https://github.com/rebelot/kanagawa.nvim
216    pub const KANAGAWA_DRAGON: Self = Self {
217        background: color!(0x181616), // Dragon Black 3
218        text: color!(0xc5c9c5),       // Dragon White
219        primary: color!(0x223249),    // Wave Blue 1
220        success: color!(0x8a9a7b),    // Dragon Green 2
221        warning: color!(0xff9e3b),    // Ronin Yellow
222        danger: color!(0xc4746e),     // Dragon Red
223    };
224
225    /// The built-in [Kanagawa] Lotus variant of a [`Palette`].
226    ///
227    /// [Kanagawa]: https://github.com/rebelot/kanagawa.nvim
228    pub const KANAGAWA_LOTUS: Self = Self {
229        background: color!(0xf2ecbc), // Lotus White 3
230        text: color!(0x545464),       // Lotus Ink 1
231        primary: color!(0x4d699b),    // Lotus Blue
232        success: color!(0x6f894e),    // Lotus Green
233        warning: color!(0xe98a00),    // Lotus Orange 2
234        danger: color!(0xc84053),     // Lotus Red
235    };
236
237    /// The built-in [Moonfly] variant of a [`Palette`].
238    ///
239    /// [Moonfly]: https://github.com/bluz71/vim-moonfly-colors
240    pub const MOONFLY: Self = Self {
241        background: color!(0x080808), // Background
242        text: color!(0xbdbdbd),       // Foreground
243        primary: color!(0x80a0ff),    // Blue (normal)
244        success: color!(0x8cc85f),    // Green (normal)
245        warning: color!(0xe3c78a),    // Yellow (normal)
246        danger: color!(0xff5454),     // Red (normal)
247    };
248
249    /// The built-in [Nightfly] variant of a [`Palette`].
250    ///
251    /// [Nightfly]: https://github.com/bluz71/vim-nightfly-colors
252    pub const NIGHTFLY: Self = Self {
253        background: color!(0x011627), // Background
254        text: color!(0xbdc1c6),       // Foreground
255        primary: color!(0x82aaff),    // Blue (normal)
256        success: color!(0xa1cd5e),    // Green (normal)
257        warning: color!(0xe3d18a),    // Yellow (normal)
258        danger: color!(0xfc514e),     // Red (normal)
259    };
260
261    /// The built-in [Oxocarbon] variant of a [`Palette`].
262    ///
263    /// [Oxocarbon]: https://github.com/nyoom-engineering/oxocarbon.nvim
264    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), // Base 14
270        danger: color!(0xf62d0f),
271    };
272
273    /// The built-in [Ferra] variant of a [`Palette`].
274    ///
275    /// [Ferra]: https://github.com/casperstorm/ferra
276    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), // Honey
282        danger: color!(0xe06b75),
283    };
284}
285
286/// An extended set of colors generated from a [`Palette`].
287#[derive(Debug, Clone, Copy, PartialEq)]
288pub struct Extended {
289    /// The set of background colors.
290    pub background: Background,
291    /// The set of primary colors.
292    pub primary: Primary,
293    /// The set of secondary colors.
294    pub secondary: Secondary,
295    /// The set of success colors.
296    pub success: Success,
297    /// The set of warning colors.
298    pub warning: Warning,
299    /// The set of danger colors.
300    pub danger: Danger,
301    /// Whether the palette is dark or not.
302    pub is_dark: bool,
303}
304
305/// The built-in light variant of an [`Extended`] palette.
306pub static EXTENDED_LIGHT: LazyLock<Extended> =
307    LazyLock::new(|| Extended::generate(Palette::LIGHT));
308
309/// The built-in dark variant of an [`Extended`] palette.
310pub static EXTENDED_DARK: LazyLock<Extended> =
311    LazyLock::new(|| Extended::generate(Palette::DARK));
312
313/// The built-in Dracula variant of an [`Extended`] palette.
314pub static EXTENDED_DRACULA: LazyLock<Extended> =
315    LazyLock::new(|| Extended::generate(Palette::DRACULA));
316
317/// The built-in Nord variant of an [`Extended`] palette.
318pub static EXTENDED_NORD: LazyLock<Extended> =
319    LazyLock::new(|| Extended::generate(Palette::NORD));
320
321/// The built-in Solarized Light variant of an [`Extended`] palette.
322pub static EXTENDED_SOLARIZED_LIGHT: LazyLock<Extended> =
323    LazyLock::new(|| Extended::generate(Palette::SOLARIZED_LIGHT));
324
325/// The built-in Solarized Dark variant of an [`Extended`] palette.
326pub static EXTENDED_SOLARIZED_DARK: LazyLock<Extended> =
327    LazyLock::new(|| Extended::generate(Palette::SOLARIZED_DARK));
328
329/// The built-in Gruvbox Light variant of an [`Extended`] palette.
330pub static EXTENDED_GRUVBOX_LIGHT: LazyLock<Extended> =
331    LazyLock::new(|| Extended::generate(Palette::GRUVBOX_LIGHT));
332
333/// The built-in Gruvbox Dark variant of an [`Extended`] palette.
334pub static EXTENDED_GRUVBOX_DARK: LazyLock<Extended> =
335    LazyLock::new(|| Extended::generate(Palette::GRUVBOX_DARK));
336
337/// The built-in Catppuccin Latte variant of an [`Extended`] palette.
338pub static EXTENDED_CATPPUCCIN_LATTE: LazyLock<Extended> =
339    LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_LATTE));
340
341/// The built-in Catppuccin Frappé variant of an [`Extended`] palette.
342pub static EXTENDED_CATPPUCCIN_FRAPPE: LazyLock<Extended> =
343    LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_FRAPPE));
344
345/// The built-in Catppuccin Macchiato variant of an [`Extended`] palette.
346pub static EXTENDED_CATPPUCCIN_MACCHIATO: LazyLock<Extended> =
347    LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_MACCHIATO));
348
349/// The built-in Catppuccin Mocha variant of an [`Extended`] palette.
350pub static EXTENDED_CATPPUCCIN_MOCHA: LazyLock<Extended> =
351    LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_MOCHA));
352
353/// The built-in Tokyo Night variant of an [`Extended`] palette.
354pub static EXTENDED_TOKYO_NIGHT: LazyLock<Extended> =
355    LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT));
356
357/// The built-in Tokyo Night Storm variant of an [`Extended`] palette.
358pub static EXTENDED_TOKYO_NIGHT_STORM: LazyLock<Extended> =
359    LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT_STORM));
360
361/// The built-in Tokyo Night variant of an [`Extended`] palette.
362pub static EXTENDED_TOKYO_NIGHT_LIGHT: LazyLock<Extended> =
363    LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT_LIGHT));
364
365/// The built-in Kanagawa Wave variant of an [`Extended`] palette.
366pub static EXTENDED_KANAGAWA_WAVE: LazyLock<Extended> =
367    LazyLock::new(|| Extended::generate(Palette::KANAGAWA_WAVE));
368
369/// The built-in Kanagawa Dragon variant of an [`Extended`] palette.
370pub static EXTENDED_KANAGAWA_DRAGON: LazyLock<Extended> =
371    LazyLock::new(|| Extended::generate(Palette::KANAGAWA_DRAGON));
372
373/// The built-in Kanagawa Lotus variant of an [`Extended`] palette.
374pub static EXTENDED_KANAGAWA_LOTUS: LazyLock<Extended> =
375    LazyLock::new(|| Extended::generate(Palette::KANAGAWA_LOTUS));
376
377/// The built-in Moonfly variant of an [`Extended`] palette.
378pub static EXTENDED_MOONFLY: LazyLock<Extended> =
379    LazyLock::new(|| Extended::generate(Palette::MOONFLY));
380
381/// The built-in Nightfly variant of an [`Extended`] palette.
382pub static EXTENDED_NIGHTFLY: LazyLock<Extended> =
383    LazyLock::new(|| Extended::generate(Palette::NIGHTFLY));
384
385/// The built-in Oxocarbon variant of an [`Extended`] palette.
386pub static EXTENDED_OXOCARBON: LazyLock<Extended> =
387    LazyLock::new(|| Extended::generate(Palette::OXOCARBON));
388
389/// The built-in Ferra variant of an [`Extended`] palette.
390pub static EXTENDED_FERRA: LazyLock<Extended> =
391    LazyLock::new(|| Extended::generate(Palette::FERRA));
392
393impl Extended {
394    /// Generates an [`Extended`] palette from a simple [`Palette`].
395    pub fn generate(palette: Palette) -> Self {
396        Self {
397            background: Background::new(palette.background, palette.text),
398            primary: Primary::generate(
399                palette.primary,
400                palette.background,
401                palette.text,
402            ),
403            secondary: Secondary::generate(palette.background, palette.text),
404            success: Success::generate(
405                palette.success,
406                palette.background,
407                palette.text,
408            ),
409            warning: Warning::generate(
410                palette.warning,
411                palette.background,
412                palette.text,
413            ),
414            danger: Danger::generate(
415                palette.danger,
416                palette.background,
417                palette.text,
418            ),
419            is_dark: is_dark(palette.background),
420        }
421    }
422}
423
424/// A pair of background and text colors.
425#[derive(Debug, Clone, Copy, PartialEq)]
426pub struct Pair {
427    /// The background color.
428    pub color: Color,
429
430    /// The text color.
431    ///
432    /// It's guaranteed to be readable on top of the background [`color`].
433    ///
434    /// [`color`]: Self::color
435    pub text: Color,
436}
437
438impl Pair {
439    /// Creates a new [`Pair`] from a background [`Color`] and some text [`Color`].
440    pub fn new(color: Color, text: Color) -> Self {
441        Self {
442            color,
443            text: readable(color, text),
444        }
445    }
446}
447
448/// A set of background colors.
449#[derive(Debug, Clone, Copy, PartialEq)]
450pub struct Background {
451    /// The base background color.
452    pub base: Pair,
453    /// The weakest version of the base background color.
454    pub weakest: Pair,
455    /// A weaker version of the base background color.
456    pub weak: Pair,
457    /// A stronger version of the base background color.
458    pub strong: Pair,
459    /// The strongest version of the base background color.
460    pub strongest: Pair,
461}
462
463impl Background {
464    /// Generates a set of [`Background`] colors from the base and text colors.
465    pub fn new(base: Color, text: Color) -> Self {
466        let weakest = deviate(base, 0.03);
467        let weak = muted(deviate(base, 0.1));
468        let strong = muted(deviate(base, 0.2));
469        let strongest = muted(deviate(base, 0.3));
470
471        Self {
472            base: Pair::new(base, text),
473            weakest: Pair::new(weakest, text),
474            weak: Pair::new(weak, text),
475            strong: Pair::new(strong, text),
476            strongest: Pair::new(strongest, text),
477        }
478    }
479}
480
481/// A set of primary colors.
482#[derive(Debug, Clone, Copy, PartialEq)]
483pub struct Primary {
484    /// The base primary color.
485    pub base: Pair,
486    /// A weaker version of the base primary color.
487    pub weak: Pair,
488    /// A stronger version of the base primary color.
489    pub strong: Pair,
490}
491
492impl Primary {
493    /// Generates a set of [`Primary`] colors from the base, background, and text colors.
494    pub fn generate(base: Color, background: Color, text: Color) -> Self {
495        let weak = mix(base, background, 0.4);
496        let strong = deviate(base, 0.1);
497
498        Self {
499            base: Pair::new(base, text),
500            weak: Pair::new(weak, text),
501            strong: Pair::new(strong, text),
502        }
503    }
504}
505
506/// A set of secondary colors.
507#[derive(Debug, Clone, Copy, PartialEq)]
508pub struct Secondary {
509    /// The base secondary color.
510    pub base: Pair,
511    /// A weaker version of the base secondary color.
512    pub weak: Pair,
513    /// A stronger version of the base secondary color.
514    pub strong: Pair,
515}
516
517impl Secondary {
518    /// Generates a set of [`Secondary`] colors from the base and text colors.
519    pub fn generate(base: Color, text: Color) -> Self {
520        let base = mix(base, text, 0.2);
521        let weak = mix(base, text, 0.1);
522        let strong = mix(base, text, 0.3);
523
524        Self {
525            base: Pair::new(base, text),
526            weak: Pair::new(weak, text),
527            strong: Pair::new(strong, text),
528        }
529    }
530}
531
532/// A set of success colors.
533#[derive(Debug, Clone, Copy, PartialEq)]
534pub struct Success {
535    /// The base success color.
536    pub base: Pair,
537    /// A weaker version of the base success color.
538    pub weak: Pair,
539    /// A stronger version of the base success color.
540    pub strong: Pair,
541}
542
543impl Success {
544    /// Generates a set of [`Success`] colors from the base, background, and text colors.
545    pub fn generate(base: Color, background: Color, text: Color) -> Self {
546        let weak = mix(base, background, 0.4);
547        let strong = deviate(base, 0.1);
548
549        Self {
550            base: Pair::new(base, text),
551            weak: Pair::new(weak, text),
552            strong: Pair::new(strong, text),
553        }
554    }
555}
556
557/// A set of warning colors.
558#[derive(Debug, Clone, Copy, PartialEq)]
559pub struct Warning {
560    /// The base warning color.
561    pub base: Pair,
562    /// A weaker version of the base warning color.
563    pub weak: Pair,
564    /// A stronger version of the base warning color.
565    pub strong: Pair,
566}
567
568impl Warning {
569    /// Generates a set of [`Warning`] colors from the base, background, and text colors.
570    pub fn generate(base: Color, background: Color, text: Color) -> Self {
571        let weak = mix(base, background, 0.4);
572        let strong = deviate(base, 0.1);
573
574        Self {
575            base: Pair::new(base, text),
576            weak: Pair::new(weak, text),
577            strong: Pair::new(strong, text),
578        }
579    }
580}
581
582/// A set of danger colors.
583#[derive(Debug, Clone, Copy, PartialEq)]
584pub struct Danger {
585    /// The base danger color.
586    pub base: Pair,
587    /// A weaker version of the base danger color.
588    pub weak: Pair,
589    /// A stronger version of the base danger color.
590    pub strong: Pair,
591}
592
593impl Danger {
594    /// Generates a set of [`Danger`] colors from the base, background, and text colors.
595    pub fn generate(base: Color, background: Color, text: Color) -> Self {
596        let weak = mix(base, background, 0.4);
597        let strong = deviate(base, 0.1);
598
599        Self {
600            base: Pair::new(base, text),
601            weak: Pair::new(weak, text),
602            strong: Pair::new(strong, text),
603        }
604    }
605}
606
607struct Hsl {
608    h: f32,
609    s: f32,
610    l: f32,
611    a: f32,
612}
613
614fn darken(color: Color, amount: f32) -> Color {
615    let mut hsl = to_hsl(color);
616
617    hsl.l = if hsl.l - amount < 0.0 {
618        0.0
619    } else {
620        hsl.l - amount
621    };
622
623    from_hsl(hsl)
624}
625
626fn lighten(color: Color, amount: f32) -> Color {
627    let mut hsl = to_hsl(color);
628
629    hsl.l = if hsl.l + amount > 1.0 {
630        1.0
631    } else {
632        hsl.l + amount
633    };
634
635    from_hsl(hsl)
636}
637
638fn deviate(color: Color, amount: f32) -> Color {
639    if is_dark(color) {
640        lighten(color, amount)
641    } else {
642        darken(color, amount * 0.8)
643    }
644}
645
646fn muted(color: Color) -> Color {
647    let mut hsl = to_hsl(color);
648
649    hsl.s = hsl.s.min(0.5);
650
651    from_hsl(hsl)
652}
653
654fn mix(a: Color, b: Color, factor: f32) -> Color {
655    let b_amount = factor.clamp(0.0, 1.0);
656    let a_amount = 1.0 - b_amount;
657
658    let a_linear = a.into_linear().map(|c| c * a_amount);
659    let b_linear = b.into_linear().map(|c| c * b_amount);
660
661    Color::from_linear_rgba(
662        a_linear[0] + b_linear[0],
663        a_linear[1] + b_linear[1],
664        a_linear[2] + b_linear[2],
665        a_linear[3] + b_linear[3],
666    )
667}
668
669fn readable(background: Color, text: Color) -> Color {
670    if is_readable(background, text) {
671        return text;
672    }
673
674    let improve = if is_dark(background) { lighten } else { darken };
675
676    // TODO: Compute factor from relative contrast value
677    let candidate = improve(text, 0.1);
678
679    if is_readable(background, candidate) {
680        return candidate;
681    }
682
683    let white_contrast = relative_contrast(background, Color::WHITE);
684    let black_contrast = relative_contrast(background, Color::BLACK);
685
686    if white_contrast >= black_contrast {
687        mix(Color::WHITE, background, 0.05)
688    } else {
689        mix(Color::BLACK, background, 0.05)
690    }
691}
692
693fn is_dark(color: Color) -> bool {
694    to_hsl(color).l < 0.6
695}
696
697fn is_readable(a: Color, b: Color) -> bool {
698    relative_contrast(a, b) >= 7.0
699}
700
701// https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
702fn relative_contrast(a: Color, b: Color) -> f32 {
703    let lum_a = relative_luminance(a);
704    let lum_b = relative_luminance(b);
705    (lum_a.max(lum_b) + 0.05) / (lum_a.min(lum_b) + 0.05)
706}
707
708// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
709fn relative_luminance(color: Color) -> f32 {
710    let linear = color.into_linear();
711    0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2]
712}
713
714// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
715fn to_hsl(color: Color) -> Hsl {
716    let x_max = color.r.max(color.g).max(color.b);
717    let x_min = color.r.min(color.g).min(color.b);
718    let c = x_max - x_min;
719    let l = x_max.midpoint(x_min);
720
721    let h = if c == 0.0 {
722        0.0
723    } else if x_max == color.r {
724        60.0 * ((color.g - color.b) / c).rem_euclid(6.0)
725    } else if x_max == color.g {
726        60.0 * (((color.b - color.r) / c) + 2.0)
727    } else {
728        // x_max == color.b
729        60.0 * (((color.r - color.g) / c) + 4.0)
730    };
731
732    let s = if l == 0.0 || l == 1.0 {
733        0.0
734    } else {
735        (x_max - l) / l.min(1.0 - l)
736    };
737
738    Hsl {
739        h,
740        s,
741        l,
742        a: color.a,
743    }
744}
745
746// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
747fn from_hsl(hsl: Hsl) -> Color {
748    let c = (1.0 - (2.0 * hsl.l - 1.0).abs()) * hsl.s;
749    let h = hsl.h / 60.0;
750    let x = c * (1.0 - (h.rem_euclid(2.0) - 1.0).abs());
751
752    let (r1, g1, b1) = if h < 1.0 {
753        (c, x, 0.0)
754    } else if h < 2.0 {
755        (x, c, 0.0)
756    } else if h < 3.0 {
757        (0.0, c, x)
758    } else if h < 4.0 {
759        (0.0, x, c)
760    } else if h < 5.0 {
761        (x, 0.0, c)
762    } else {
763        // h < 6.0
764        (c, 0.0, x)
765    };
766
767    let m = hsl.l - (c / 2.0);
768
769    Color {
770        r: r1 + m,
771        g: g1 + m,
772        b: b1 + m,
773        a: hsl.a,
774    }
775}