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> = LazyLock::new(|| Extended::generate(Palette::DARK));
311
312/// The built-in Dracula variant of an [`Extended`] palette.
313pub static EXTENDED_DRACULA: LazyLock<Extended> =
314    LazyLock::new(|| Extended::generate(Palette::DRACULA));
315
316/// The built-in Nord variant of an [`Extended`] palette.
317pub static EXTENDED_NORD: LazyLock<Extended> = LazyLock::new(|| Extended::generate(Palette::NORD));
318
319/// The built-in Solarized Light variant of an [`Extended`] palette.
320pub static EXTENDED_SOLARIZED_LIGHT: LazyLock<Extended> =
321    LazyLock::new(|| Extended::generate(Palette::SOLARIZED_LIGHT));
322
323/// The built-in Solarized Dark variant of an [`Extended`] palette.
324pub static EXTENDED_SOLARIZED_DARK: LazyLock<Extended> =
325    LazyLock::new(|| Extended::generate(Palette::SOLARIZED_DARK));
326
327/// The built-in Gruvbox Light variant of an [`Extended`] palette.
328pub static EXTENDED_GRUVBOX_LIGHT: LazyLock<Extended> =
329    LazyLock::new(|| Extended::generate(Palette::GRUVBOX_LIGHT));
330
331/// The built-in Gruvbox Dark variant of an [`Extended`] palette.
332pub static EXTENDED_GRUVBOX_DARK: LazyLock<Extended> =
333    LazyLock::new(|| Extended::generate(Palette::GRUVBOX_DARK));
334
335/// The built-in Catppuccin Latte variant of an [`Extended`] palette.
336pub static EXTENDED_CATPPUCCIN_LATTE: LazyLock<Extended> =
337    LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_LATTE));
338
339/// The built-in Catppuccin Frappé variant of an [`Extended`] palette.
340pub static EXTENDED_CATPPUCCIN_FRAPPE: LazyLock<Extended> =
341    LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_FRAPPE));
342
343/// The built-in Catppuccin Macchiato variant of an [`Extended`] palette.
344pub static EXTENDED_CATPPUCCIN_MACCHIATO: LazyLock<Extended> =
345    LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_MACCHIATO));
346
347/// The built-in Catppuccin Mocha variant of an [`Extended`] palette.
348pub static EXTENDED_CATPPUCCIN_MOCHA: LazyLock<Extended> =
349    LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_MOCHA));
350
351/// The built-in Tokyo Night variant of an [`Extended`] palette.
352pub static EXTENDED_TOKYO_NIGHT: LazyLock<Extended> =
353    LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT));
354
355/// The built-in Tokyo Night Storm variant of an [`Extended`] palette.
356pub static EXTENDED_TOKYO_NIGHT_STORM: LazyLock<Extended> =
357    LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT_STORM));
358
359/// The built-in Tokyo Night variant of an [`Extended`] palette.
360pub static EXTENDED_TOKYO_NIGHT_LIGHT: LazyLock<Extended> =
361    LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT_LIGHT));
362
363/// The built-in Kanagawa Wave variant of an [`Extended`] palette.
364pub static EXTENDED_KANAGAWA_WAVE: LazyLock<Extended> =
365    LazyLock::new(|| Extended::generate(Palette::KANAGAWA_WAVE));
366
367/// The built-in Kanagawa Dragon variant of an [`Extended`] palette.
368pub static EXTENDED_KANAGAWA_DRAGON: LazyLock<Extended> =
369    LazyLock::new(|| Extended::generate(Palette::KANAGAWA_DRAGON));
370
371/// The built-in Kanagawa Lotus variant of an [`Extended`] palette.
372pub static EXTENDED_KANAGAWA_LOTUS: LazyLock<Extended> =
373    LazyLock::new(|| Extended::generate(Palette::KANAGAWA_LOTUS));
374
375/// The built-in Moonfly variant of an [`Extended`] palette.
376pub static EXTENDED_MOONFLY: LazyLock<Extended> =
377    LazyLock::new(|| Extended::generate(Palette::MOONFLY));
378
379/// The built-in Nightfly variant of an [`Extended`] palette.
380pub static EXTENDED_NIGHTFLY: LazyLock<Extended> =
381    LazyLock::new(|| Extended::generate(Palette::NIGHTFLY));
382
383/// The built-in Oxocarbon variant of an [`Extended`] palette.
384pub static EXTENDED_OXOCARBON: LazyLock<Extended> =
385    LazyLock::new(|| Extended::generate(Palette::OXOCARBON));
386
387/// The built-in Ferra variant of an [`Extended`] palette.
388pub static EXTENDED_FERRA: LazyLock<Extended> =
389    LazyLock::new(|| Extended::generate(Palette::FERRA));
390
391impl Extended {
392    /// Generates an [`Extended`] palette from a simple [`Palette`].
393    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/// A pair of background and text colors.
407#[derive(Debug, Clone, Copy, PartialEq)]
408pub struct Pair {
409    /// The background color.
410    pub color: Color,
411
412    /// The text color.
413    ///
414    /// It's guaranteed to be readable on top of the background [`color`].
415    ///
416    /// [`color`]: Self::color
417    pub text: Color,
418}
419
420impl Pair {
421    /// Creates a new [`Pair`] from a background [`Color`] and some text [`Color`].
422    pub fn new(color: Color, text: Color) -> Self {
423        Self {
424            color,
425            text: readable(color, text),
426        }
427    }
428}
429
430/// A set of background colors.
431#[derive(Debug, Clone, Copy, PartialEq)]
432pub struct Background {
433    /// The base background color.
434    pub base: Pair,
435    /// The weakest version of the base background color.
436    pub weakest: Pair,
437    /// A weaker version of the base background color.
438    pub weaker: Pair,
439    /// A weak version of the base background color.
440    pub weak: Pair,
441    /// A neutral version of the base background color, between weak and strong.
442    pub neutral: Pair,
443    /// A strong version of the base background color.
444    pub strong: Pair,
445    /// A stronger version of the base background color.
446    pub stronger: Pair,
447    /// The strongest version of the base background color.
448    pub strongest: Pair,
449}
450
451impl Background {
452    /// Generates a set of [`Background`] colors from the base and text colors.
453    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/// A set of primary colors.
476#[derive(Debug, Clone, Copy, PartialEq)]
477pub struct Primary {
478    /// The base primary color.
479    pub base: Pair,
480    /// A weaker version of the base primary color.
481    pub weak: Pair,
482    /// A stronger version of the base primary color.
483    pub strong: Pair,
484}
485
486impl Primary {
487    /// Generates a set of [`Primary`] colors from the base, background, and text colors.
488    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/// A set of secondary colors.
501#[derive(Debug, Clone, Copy, PartialEq)]
502pub struct Secondary {
503    /// The base secondary color.
504    pub base: Pair,
505    /// A weaker version of the base secondary color.
506    pub weak: Pair,
507    /// A stronger version of the base secondary color.
508    pub strong: Pair,
509}
510
511impl Secondary {
512    /// Generates a set of [`Secondary`] colors from the base and text colors.
513    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/// A set of success colors.
529#[derive(Debug, Clone, Copy, PartialEq)]
530pub struct Success {
531    /// The base success color.
532    pub base: Pair,
533    /// A weaker version of the base success color.
534    pub weak: Pair,
535    /// A stronger version of the base success color.
536    pub strong: Pair,
537}
538
539impl Success {
540    /// Generates a set of [`Success`] colors from the base, background, and text colors.
541    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/// A set of warning colors.
554#[derive(Debug, Clone, Copy, PartialEq)]
555pub struct Warning {
556    /// The base warning color.
557    pub base: Pair,
558    /// A weaker version of the base warning color.
559    pub weak: Pair,
560    /// A stronger version of the base warning color.
561    pub strong: Pair,
562}
563
564impl Warning {
565    /// Generates a set of [`Warning`] colors from the base, background, and text colors.
566    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/// A set of danger colors.
579#[derive(Debug, Clone, Copy, PartialEq)]
580pub struct Danger {
581    /// The base danger color.
582    pub base: Pair,
583    /// A weaker version of the base danger color.
584    pub weak: Pair,
585    /// A stronger version of the base danger color.
586    pub strong: Pair,
587}
588
589impl Danger {
590    /// Generates a set of [`Danger`] colors from the base, background, and text colors.
591    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
610/// Darkens a [`Color`] by the given factor.
611pub fn darken(color: Color, amount: f32) -> Color {
612    let mut oklch = to_oklch(color);
613
614    // We try to bump the chroma a bit for more colorful palettes
615    if oklch.c > 0.0 && oklch.c < (1.0 - oklch.l) / 2.0 {
616        // Formula empirically and cluelessly derived
617        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
629/// Lightens a [`Color`] by the given factor.
630pub fn lighten(color: Color, amount: f32) -> Color {
631    let mut oklch = to_oklch(color);
632
633    // We try to bump the chroma a bit for more colorful palettes
634    // Formula empirically and cluelessly derived
635    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
646/// Deviates a [`Color`] by the given factor. Lightens if the [`Color`] is
647/// dark, darkens otherwise.
648pub 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
656/// Mixes two colors by the given factor.
657pub 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
672/// Computes a [`Color`] from the given text color that is
673/// readable on top of the given background color.
674pub 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    // TODO: Compute factor from relative contrast value
682    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
704/// Returns true if the [`Color`] is dark.
705pub fn is_dark(color: Color) -> bool {
706    to_oklch(color).l < 0.6
707}
708
709// https://en.wikipedia.org/wiki/Oklab_color_space#Conversions_between_color_spaces
710fn to_oklch(color: Color) -> Oklch {
711    let [r, g, b, alpha] = color.into_linear();
712
713    // linear RGB → LMS
714    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    // Nonlinear transform (cube root)
719    let l_ = l.cbrt();
720    let m_ = m.cbrt();
721    let s_ = s.cbrt();
722
723    // LMS → Oklab
724    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    // Oklab → Oklch
729    let c = (a * a + b * b).sqrt();
730    let h = b.atan2(a); // radians
731
732    Oklch { l, c, h, a: alpha }
733}
734
735// https://en.wikipedia.org/wiki/Oklab_color_space#Conversions_between_color_spaces
736fn 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    // Oklab → LMS (nonlinear)
743    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    // Cubing back
748    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}