use ratatui::style::Color;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Theme {
Latte,
Frappe,
Macchiato,
#[default]
Mocha,
}
impl Theme {
pub const ALL: [Theme; 4] = [Theme::Latte, Theme::Frappe, Theme::Macchiato, Theme::Mocha];
pub fn label(self) -> &'static str {
match self {
Theme::Latte => "Catppuccin Latte",
Theme::Frappe => "Catppuccin Frappé",
Theme::Macchiato => "Catppuccin Macchiato",
Theme::Mocha => "Catppuccin Mocha",
}
}
pub fn colors(self) -> Colors {
let p = match self {
Theme::Latte => &LATTE,
Theme::Frappe => &FRAPPE,
Theme::Macchiato => &MACCHIATO,
Theme::Mocha => &MOCHA,
};
let tint = if p.dark { 0.3 } else { 0.2 };
Colors {
bg: p.base,
fg: p.text,
subtle: p.subtext0,
muted: p.overlay1,
border: p.overlay0,
accent: p.lavender,
selection: p.surface0,
popup_bg: p.mantle,
primary: p.blue,
highlighted: p.peach,
insert: p.green,
error: p.red,
warning: p.yellow,
search: p.yellow,
own_bubble: mix(p.base, p.blue, tint),
own_meta: mix(p.text, p.blue, 0.5),
other_bubble: p.surface0,
other_meta: p.subtext0,
names: [p.red, p.peach, p.mauve, p.green, p.teal, p.blue, p.pink],
}
}
}
#[derive(Clone, Copy)]
pub struct Colors {
pub bg: Color,
pub fg: Color,
pub subtle: Color,
pub muted: Color,
pub border: Color,
pub accent: Color,
pub selection: Color,
pub popup_bg: Color,
pub primary: Color,
pub highlighted: Color,
pub insert: Color,
pub error: Color,
pub warning: Color,
pub search: Color,
pub own_bubble: Color,
pub own_meta: Color,
pub other_bubble: Color,
pub other_meta: Color,
pub names: [Color; 7],
}
struct Palette {
dark: bool,
red: Color,
peach: Color,
yellow: Color,
green: Color,
teal: Color,
blue: Color,
lavender: Color,
mauve: Color,
pink: Color,
text: Color,
subtext0: Color,
overlay1: Color,
overlay0: Color,
surface0: Color,
base: Color,
mantle: Color,
}
const LATTE: Palette = Palette {
dark: false,
red: rgb(0xd20f39),
peach: rgb(0xfe640b),
yellow: rgb(0xdf8e1d),
green: rgb(0x40a02b),
teal: rgb(0x179299),
blue: rgb(0x1e66f5),
lavender: rgb(0x7287fd),
mauve: rgb(0x8839ef),
pink: rgb(0xea76cb),
text: rgb(0x4c4f69),
subtext0: rgb(0x6c6f85),
overlay1: rgb(0x8c8fa1),
overlay0: rgb(0x9ca0b0),
surface0: rgb(0xccd0da),
base: rgb(0xeff1f5),
mantle: rgb(0xe6e9ef),
};
const FRAPPE: Palette = Palette {
dark: true,
red: rgb(0xe78284),
peach: rgb(0xef9f76),
yellow: rgb(0xe5c890),
green: rgb(0xa6d189),
teal: rgb(0x81c8be),
blue: rgb(0x8caaee),
lavender: rgb(0xbabbf1),
mauve: rgb(0xca9ee6),
pink: rgb(0xf4b8e4),
text: rgb(0xc6d0f5),
subtext0: rgb(0xa5adce),
overlay1: rgb(0x838ba7),
overlay0: rgb(0x737994),
surface0: rgb(0x414559),
base: rgb(0x303446),
mantle: rgb(0x292c3c),
};
const MACCHIATO: Palette = Palette {
dark: true,
red: rgb(0xed8796),
peach: rgb(0xf5a97f),
yellow: rgb(0xeed49f),
green: rgb(0xa6da95),
teal: rgb(0x8bd5ca),
blue: rgb(0x8aadf4),
lavender: rgb(0xb7bdf8),
mauve: rgb(0xc6a0f6),
pink: rgb(0xf5bde6),
text: rgb(0xcad3f5),
subtext0: rgb(0xa5adcb),
overlay1: rgb(0x8087a2),
overlay0: rgb(0x6e738d),
surface0: rgb(0x363a4f),
base: rgb(0x24273a),
mantle: rgb(0x1e2030),
};
const MOCHA: Palette = Palette {
dark: true,
red: rgb(0xf38ba8),
peach: rgb(0xfab387),
yellow: rgb(0xf9e2af),
green: rgb(0xa6e3a1),
teal: rgb(0x94e2d5),
blue: rgb(0x89b4fa),
lavender: rgb(0xb4befe),
mauve: rgb(0xcba6f7),
pink: rgb(0xf5c2e7),
text: rgb(0xcdd6f4),
subtext0: rgb(0xa6adc8),
overlay1: rgb(0x7f849c),
overlay0: rgb(0x6c7086),
surface0: rgb(0x313244),
base: rgb(0x1e1e2e),
mantle: rgb(0x181825),
};
const fn rgb(hex: u32) -> Color {
Color::Rgb((hex >> 16) as u8, (hex >> 8) as u8, hex as u8)
}
fn mix(a: Color, b: Color, amount: f32) -> Color {
let (Color::Rgb(ar, ag, ab), Color::Rgb(br, bg, bb)) = (a, b) else {
return a;
};
let channel =
|x: u8, y: u8| (f32::from(x) + (f32::from(y) - f32::from(x)) * amount).round() as u8;
Color::Rgb(channel(ar, br), channel(ag, bg), channel(ab, bb))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mocha_is_the_default() {
assert_eq!(Theme::default(), Theme::Mocha);
assert_eq!(Theme::Mocha.colors().bg, Color::Rgb(0x1e, 0x1e, 0x2e));
}
#[test]
fn own_bubbles_are_tinted_between_base_and_blue() {
let bubble = Theme::Mocha.colors().own_bubble;
assert_eq!(bubble, Color::Rgb(0x3e, 0x4b, 0x6b));
}
}