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!(0xb77e33),
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 weaker: Pair,
457    /// A weak version of the base background color.
458    pub weak: Pair,
459    /// A neutral version of the base background color, between weak and strong.
460    pub neutral: Pair,
461    /// A strong version of the base background color.
462    pub strong: Pair,
463    /// A stronger version of the base background color.
464    pub stronger: Pair,
465    /// The strongest version of the base background color.
466    pub strongest: Pair,
467}
468
469impl Background {
470    /// Generates a set of [`Background`] colors from the base and text colors.
471    pub fn new(base: Color, text: Color) -> Self {
472        let weakest = deviate(base, 0.03);
473        let weaker = deviate(base, 0.07);
474        let weak = deviate(base, 0.1);
475        let neutral = deviate(base, 0.125);
476        let strong = deviate(base, 0.15);
477        let stronger = deviate(base, 0.175);
478        let strongest = deviate(base, 0.20);
479
480        Self {
481            base: Pair::new(base, text),
482            weakest: Pair::new(weakest, text),
483            weaker: Pair::new(weaker, text),
484            weak: Pair::new(weak, text),
485            neutral: Pair::new(neutral, text),
486            strong: Pair::new(strong, text),
487            stronger: Pair::new(stronger, text),
488            strongest: Pair::new(strongest, text),
489        }
490    }
491}
492
493/// A set of primary colors.
494#[derive(Debug, Clone, Copy, PartialEq)]
495pub struct Primary {
496    /// The base primary color.
497    pub base: Pair,
498    /// A weaker version of the base primary color.
499    pub weak: Pair,
500    /// A stronger version of the base primary color.
501    pub strong: Pair,
502}
503
504impl Primary {
505    /// Generates a set of [`Primary`] colors from the base, background, and text colors.
506    pub fn generate(base: Color, background: Color, text: Color) -> Self {
507        let weak = mix(base, background, 0.4);
508        let strong = deviate(base, 0.1);
509
510        Self {
511            base: Pair::new(base, text),
512            weak: Pair::new(weak, text),
513            strong: Pair::new(strong, text),
514        }
515    }
516}
517
518/// A set of secondary colors.
519#[derive(Debug, Clone, Copy, PartialEq)]
520pub struct Secondary {
521    /// The base secondary color.
522    pub base: Pair,
523    /// A weaker version of the base secondary color.
524    pub weak: Pair,
525    /// A stronger version of the base secondary color.
526    pub strong: Pair,
527}
528
529impl Secondary {
530    /// Generates a set of [`Secondary`] colors from the base and text colors.
531    pub fn generate(base: Color, text: Color) -> Self {
532        let factor = if is_dark(base) { 0.2 } else { 0.4 };
533
534        let weak = mix(deviate(base, 0.1), text, factor);
535        let strong = mix(deviate(base, 0.3), text, factor);
536        let base = mix(deviate(base, 0.2), text, factor);
537
538        Self {
539            base: Pair::new(base, text),
540            weak: Pair::new(weak, text),
541            strong: Pair::new(strong, text),
542        }
543    }
544}
545
546/// A set of success colors.
547#[derive(Debug, Clone, Copy, PartialEq)]
548pub struct Success {
549    /// The base success color.
550    pub base: Pair,
551    /// A weaker version of the base success color.
552    pub weak: Pair,
553    /// A stronger version of the base success color.
554    pub strong: Pair,
555}
556
557impl Success {
558    /// Generates a set of [`Success`] colors from the base, background, and text colors.
559    pub fn generate(base: Color, background: Color, text: Color) -> Self {
560        let weak = mix(base, background, 0.4);
561        let strong = deviate(base, 0.1);
562
563        Self {
564            base: Pair::new(base, text),
565            weak: Pair::new(weak, text),
566            strong: Pair::new(strong, text),
567        }
568    }
569}
570
571/// A set of warning colors.
572#[derive(Debug, Clone, Copy, PartialEq)]
573pub struct Warning {
574    /// The base warning color.
575    pub base: Pair,
576    /// A weaker version of the base warning color.
577    pub weak: Pair,
578    /// A stronger version of the base warning color.
579    pub strong: Pair,
580}
581
582impl Warning {
583    /// Generates a set of [`Warning`] colors from the base, background, and text colors.
584    pub fn generate(base: Color, background: Color, text: Color) -> Self {
585        let weak = mix(base, background, 0.4);
586        let strong = deviate(base, 0.1);
587
588        Self {
589            base: Pair::new(base, text),
590            weak: Pair::new(weak, text),
591            strong: Pair::new(strong, text),
592        }
593    }
594}
595
596/// A set of danger colors.
597#[derive(Debug, Clone, Copy, PartialEq)]
598pub struct Danger {
599    /// The base danger color.
600    pub base: Pair,
601    /// A weaker version of the base danger color.
602    pub weak: Pair,
603    /// A stronger version of the base danger color.
604    pub strong: Pair,
605}
606
607impl Danger {
608    /// Generates a set of [`Danger`] colors from the base, background, and text colors.
609    pub fn generate(base: Color, background: Color, text: Color) -> Self {
610        let weak = mix(base, background, 0.4);
611        let strong = deviate(base, 0.1);
612
613        Self {
614            base: Pair::new(base, text),
615            weak: Pair::new(weak, text),
616            strong: Pair::new(strong, text),
617        }
618    }
619}
620
621struct Oklch {
622    l: f32,
623    c: f32,
624    h: f32,
625    a: f32,
626}
627
628fn darken(color: Color, amount: f32) -> Color {
629    let mut oklch = to_oklch(color);
630
631    // We try to bump the chroma a bit for more colorful palettes
632    if oklch.c > 0.0 && oklch.c < (1.0 - oklch.l) / 2.0 {
633        // Formula empirically and cluelessly derived
634        oklch.c *= 1.0 + (0.2 / oklch.c).min(100.0) * amount;
635    }
636
637    oklch.l = if oklch.l - amount < 0.0 {
638        0.0
639    } else {
640        oklch.l - amount
641    };
642
643    from_oklch(oklch)
644}
645
646fn lighten(color: Color, amount: f32) -> Color {
647    let mut oklch = to_oklch(color);
648
649    // We try to bump the chroma a bit for more colorful palettes
650    // Formula empirically and cluelessly derived
651    oklch.c *= 1.0 + 2.0 * amount / oklch.l.max(0.05);
652
653    oklch.l = if oklch.l + amount > 1.0 {
654        1.0
655    } else {
656        oklch.l + amount
657    };
658
659    from_oklch(oklch)
660}
661
662fn deviate(color: Color, amount: f32) -> Color {
663    if is_dark(color) {
664        lighten(color, amount)
665    } else {
666        darken(color, amount)
667    }
668}
669
670fn mix(a: Color, b: Color, factor: f32) -> Color {
671    let b_amount = factor.clamp(0.0, 1.0);
672    let a_amount = 1.0 - b_amount;
673
674    let a_linear = a.into_linear().map(|c| c * a_amount);
675    let b_linear = b.into_linear().map(|c| c * b_amount);
676
677    Color::from_linear_rgba(
678        a_linear[0] + b_linear[0],
679        a_linear[1] + b_linear[1],
680        a_linear[2] + b_linear[2],
681        a_linear[3] + b_linear[3],
682    )
683}
684
685fn readable(background: Color, text: Color) -> Color {
686    if is_readable(background, text) {
687        return text;
688    }
689
690    let improve = if is_dark(background) { lighten } else { darken };
691
692    // TODO: Compute factor from relative contrast value
693    let candidate = improve(text, 0.1);
694
695    if is_readable(background, candidate) {
696        return candidate;
697    }
698
699    let candidate = improve(text, 0.2);
700
701    if is_readable(background, candidate) {
702        return candidate;
703    }
704
705    let white_contrast = relative_contrast(background, Color::WHITE);
706    let black_contrast = relative_contrast(background, Color::BLACK);
707
708    if white_contrast >= black_contrast {
709        mix(Color::WHITE, background, 0.05)
710    } else {
711        mix(Color::BLACK, background, 0.05)
712    }
713}
714
715fn is_dark(color: Color) -> bool {
716    to_oklch(color).l < 0.6
717}
718
719fn is_readable(a: Color, b: Color) -> bool {
720    relative_contrast(a, b) >= 6.0
721}
722
723// https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
724fn relative_contrast(a: Color, b: Color) -> f32 {
725    let lum_a = a.relative_luminance();
726    let lum_b = b.relative_luminance();
727    (lum_a.max(lum_b) + 0.05) / (lum_a.min(lum_b) + 0.05)
728}
729
730// https://en.wikipedia.org/wiki/Oklab_color_space#Conversions_between_color_spaces
731fn to_oklch(color: Color) -> Oklch {
732    let [r, g, b, alpha] = color.into_linear();
733
734    // linear RGB → LMS
735    let l = 0.41222146 * r + 0.53633255 * g + 0.051445995 * b;
736    let m = 0.2119035 * r + 0.6806995 * g + 0.10739696 * b;
737    let s = 0.08830246 * r + 0.28171885 * g + 0.6299787 * b;
738
739    // Nonlinear transform (cube root)
740    let l_ = l.cbrt();
741    let m_ = m.cbrt();
742    let s_ = s.cbrt();
743
744    // LMS → Oklab
745    let l = 0.21045426 * l_ + 0.7936178 * m_ - 0.004072047 * s_;
746    let a = 1.9779985 * l_ - 2.4285922 * m_ + 0.4505937 * s_;
747    let b = 0.025904037 * l_ + 0.78277177 * m_ - 0.80867577 * s_;
748
749    // Oklab → Oklch
750    let c = (a * a + b * b).sqrt();
751    let h = b.atan2(a); // radians
752
753    Oklch { l, c, h, a: alpha }
754}
755
756// https://en.wikipedia.org/wiki/Oklab_color_space#Conversions_between_color_spaces
757fn from_oklch(oklch: Oklch) -> Color {
758    let Oklch { l, c, h, a: alpha } = oklch;
759
760    let a = c * h.cos();
761    let b = c * h.sin();
762
763    // Oklab → LMS (nonlinear)
764    let l_ = l + 0.39633778 * a + 0.21580376 * b;
765    let m_ = l - 0.105561346 * a - 0.06385417 * b;
766    let s_ = l - 0.08948418 * a - 1.2914855 * b;
767
768    // Cubing back
769    let l = l_ * l_ * l_;
770    let m = m_ * m_ * m_;
771    let s = s_ * s_ * s_;
772
773    let r = 4.0767417 * l - 3.3077116 * m + 0.23096994 * s;
774    let g = -1.268438 * l + 2.6097574 * m - 0.34131938 * s;
775    let b = -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s;
776
777    Color::from_linear_rgba(
778        r.clamp(0.0, 1.0),
779        g.clamp(0.0, 1.0),
780        b.clamp(0.0, 1.0),
781        alpha,
782    )
783}