use ratatui::style::{Color, Modifier, Style};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ThemeKind {
DefaultDark,
Nord,
CatppuccinMocha,
Dracula,
}
impl ThemeKind {
pub const ALL: &'static [ThemeKind] = &[
ThemeKind::DefaultDark,
ThemeKind::Nord,
ThemeKind::CatppuccinMocha,
ThemeKind::Dracula,
];
pub fn name(&self) -> &'static str {
match self {
ThemeKind::DefaultDark => "default_dark",
ThemeKind::Nord => "nord",
ThemeKind::CatppuccinMocha => "catppuccin_mocha",
ThemeKind::Dracula => "dracula",
}
}
pub fn display_name(&self) -> &'static str {
match self {
ThemeKind::DefaultDark => "Default Dark",
ThemeKind::Nord => "Nord",
ThemeKind::CatppuccinMocha => "Catppuccin Mocha",
ThemeKind::Dracula => "Dracula",
}
}
pub fn from_name(name: &str) -> Self {
let s = name.to_lowercase();
let s = s.trim();
match s {
"nord" => ThemeKind::Nord,
"catppuccin_mocha" | "catppuccin" | "mocha" | "catppuccin mocha" => {
ThemeKind::CatppuccinMocha
}
"dracula" => ThemeKind::Dracula,
"default_dark" | "default" | "default dark" => ThemeKind::DefaultDark,
_ => ThemeKind::DefaultDark,
}
}
pub fn next(self) -> Self {
let i = Self::ALL.iter().position(|&k| k == self).unwrap_or(0);
let next = (i + 1) % Self::ALL.len();
Self::ALL[next]
}
}
#[derive(Debug, Clone)]
pub struct Theme {
pub name: String,
pub accent: Color,
pub accent_dim: Color,
pub bg: Color,
pub bg_highlight: Color,
pub bg_panel: Color,
pub fg: Color,
pub fg_dim: Color,
pub fg_muted: Color,
pub border: Color,
pub border_focused: Color,
pub tab_active_bg: Color,
pub tab_active_fg: Color,
pub error: Color,
pub warning: Color,
pub success: Color,
pub info: Color,
pub keyword: Color,
pub function: Color,
pub type_: Color,
pub string: Color,
pub number: Color,
pub comment: Color,
}
impl Theme {
pub fn default_dark() -> Self {
Self {
name: "Default Dark".into(),
accent: Color::Rgb(78, 191, 113), accent_dim: Color::Rgb(45, 110, 65),
bg: Color::Rgb(24, 24, 24),
bg_highlight: Color::Rgb(45, 45, 45),
bg_panel: Color::Rgb(32, 32, 32),
fg: Color::Rgb(230, 230, 230),
fg_dim: Color::Rgb(195, 195, 200),
fg_muted: Color::Rgb(140, 140, 145),
border: Color::Rgb(60, 60, 60),
border_focused: Color::Rgb(78, 191, 113),
tab_active_bg: Color::Rgb(88, 66, 134), tab_active_fg: Color::Rgb(255, 255, 255), error: Color::Rgb(244, 67, 54),
warning: Color::Rgb(255, 152, 0),
success: Color::Rgb(76, 175, 80),
info: Color::Rgb(33, 150, 243),
keyword: Color::Rgb(198, 120, 221), function: Color::Rgb(97, 175, 239), type_: Color::Rgb(229, 192, 123), string: Color::Rgb(152, 195, 121), number: Color::Rgb(209, 154, 102), comment: Color::Rgb(92, 99, 112), }
}
pub fn nord() -> Self {
Self {
name: "Nord".into(),
accent: Color::Rgb(136, 192, 208), accent_dim: Color::Rgb(94, 129, 172), bg: Color::Rgb(46, 52, 64), bg_highlight: Color::Rgb(59, 66, 82), bg_panel: Color::Rgb(67, 76, 94), fg: Color::Rgb(236, 239, 244), fg_dim: Color::Rgb(229, 233, 240), fg_muted: Color::Rgb(136, 142, 156), border: Color::Rgb(76, 86, 106), border_focused: Color::Rgb(136, 192, 208),
tab_active_bg: Color::Rgb(65, 89, 122), tab_active_fg: Color::Rgb(255, 255, 255), error: Color::Rgb(191, 97, 106), warning: Color::Rgb(235, 203, 139), success: Color::Rgb(163, 190, 140), info: Color::Rgb(129, 161, 193), keyword: Color::Rgb(180, 142, 173), function: Color::Rgb(136, 192, 208), type_: Color::Rgb(235, 203, 139), string: Color::Rgb(163, 190, 140), number: Color::Rgb(208, 135, 112), comment: Color::Rgb(76, 86, 106), }
}
pub fn catppuccin_mocha() -> Self {
Self {
name: "Catppuccin Mocha".into(),
accent: Color::Rgb(166, 227, 161), accent_dim: Color::Rgb(116, 199, 236), bg: Color::Rgb(30, 30, 46), bg_highlight: Color::Rgb(49, 50, 68), bg_panel: Color::Rgb(36, 39, 58), fg: Color::Rgb(205, 214, 244), fg_dim: Color::Rgb(205, 214, 244), fg_muted: Color::Rgb(147, 153, 178), border: Color::Rgb(69, 71, 90), border_focused: Color::Rgb(166, 227, 161),
tab_active_bg: Color::Rgb(126, 87, 194), tab_active_fg: Color::Rgb(255, 255, 255), error: Color::Rgb(243, 139, 168), warning: Color::Rgb(249, 226, 175), success: Color::Rgb(166, 227, 161), info: Color::Rgb(137, 180, 250), keyword: Color::Rgb(203, 166, 247), function: Color::Rgb(137, 180, 250), type_: Color::Rgb(249, 226, 175), string: Color::Rgb(166, 227, 161), number: Color::Rgb(250, 179, 135), comment: Color::Rgb(108, 112, 134), }
}
pub fn dracula() -> Self {
Self {
name: "Dracula".into(),
accent: Color::Rgb(80, 250, 123), accent_dim: Color::Rgb(139, 233, 253), bg: Color::Rgb(40, 42, 54), bg_highlight: Color::Rgb(68, 71, 90), bg_panel: Color::Rgb(33, 34, 44), fg: Color::Rgb(248, 248, 242), fg_dim: Color::Rgb(230, 230, 235),
fg_muted: Color::Rgb(139, 153, 195),
border: Color::Rgb(68, 71, 90), border_focused: Color::Rgb(80, 250, 123),
tab_active_bg: Color::Rgb(118, 92, 168), tab_active_fg: Color::Rgb(255, 255, 255), error: Color::Rgb(255, 85, 85), warning: Color::Rgb(255, 184, 108), success: Color::Rgb(80, 250, 123), info: Color::Rgb(139, 233, 253), keyword: Color::Rgb(255, 121, 198), function: Color::Rgb(80, 250, 123), type_: Color::Rgb(139, 233, 253), string: Color::Rgb(241, 250, 140), number: Color::Rgb(189, 147, 249), comment: Color::Rgb(98, 114, 164), }
}
pub fn from_kind(kind: ThemeKind) -> Self {
match kind {
ThemeKind::DefaultDark => Self::default_dark(),
ThemeKind::Nord => Self::nord(),
ThemeKind::CatppuccinMocha => Self::catppuccin_mocha(),
ThemeKind::Dracula => Self::dracula(),
}
}
pub fn from_name(name: &str) -> Self {
Self::from_kind(ThemeKind::from_name(name))
}
pub fn kind(&self) -> ThemeKind {
ThemeKind::from_name(&self.name)
}
pub fn style_accent(&self) -> Style {
Style::default().fg(self.accent)
}
pub fn style_accent_bold(&self) -> Style {
Style::default()
.fg(self.accent)
.add_modifier(Modifier::BOLD)
}
pub fn style_normal(&self) -> Style {
Style::default().fg(self.fg)
}
pub fn style_dim(&self) -> Style {
Style::default().fg(self.fg_dim)
}
pub fn style_muted(&self) -> Style {
Style::default().fg(self.fg_muted)
}
pub fn style_highlight(&self) -> Style {
Style::default().bg(self.bg_highlight)
}
pub fn style_selected(&self) -> Style {
Style::default()
.fg(self.fg)
.bg(self.bg_highlight)
.add_modifier(Modifier::BOLD)
}
pub fn style_border(&self) -> Style {
Style::default().fg(self.border)
}
pub fn style_tab_active(&self) -> Style {
Style::default()
.fg(self.tab_active_fg)
.bg(self.tab_active_bg)
.add_modifier(Modifier::BOLD)
}
pub fn style_border_glow(&self) -> Style {
Style::default().fg(self.accent).add_modifier(Modifier::DIM)
}
pub fn style_border_focused(&self) -> Style {
Style::default().fg(self.border_focused)
}
pub fn style_error(&self) -> Style {
Style::default().fg(self.error)
}
pub fn style_warning(&self) -> Style {
Style::default().fg(self.warning)
}
pub fn style_success(&self) -> Style {
Style::default().fg(self.success)
}
pub fn style_info(&self) -> Style {
Style::default().fg(self.info)
}
pub fn style_keyword(&self) -> Style {
Style::default().fg(self.keyword)
}
pub fn style_function(&self) -> Style {
Style::default().fg(self.function)
}
pub fn style_type(&self) -> Style {
Style::default().fg(self.type_)
}
pub fn style_string(&self) -> Style {
Style::default().fg(self.string)
}
pub fn style_number(&self) -> Style {
Style::default().fg(self.number)
}
pub fn style_comment(&self) -> Style {
Style::default().fg(self.comment)
}
}
impl Default for Theme {
fn default() -> Self {
Self::default_dark()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_theme_kind_from_name() {
assert_eq!(ThemeKind::from_name("default"), ThemeKind::DefaultDark);
assert_eq!(ThemeKind::from_name("default_dark"), ThemeKind::DefaultDark);
assert_eq!(ThemeKind::from_name("nord"), ThemeKind::Nord);
assert_eq!(ThemeKind::from_name("Nord"), ThemeKind::Nord);
assert_eq!(
ThemeKind::from_name("catppuccin_mocha"),
ThemeKind::CatppuccinMocha
);
assert_eq!(ThemeKind::from_name("mocha"), ThemeKind::CatppuccinMocha);
assert_eq!(ThemeKind::from_name("dracula"), ThemeKind::Dracula);
assert_eq!(ThemeKind::from_name("unknown"), ThemeKind::DefaultDark);
}
#[test]
fn test_theme_kind_next_cycles() {
let first = ThemeKind::DefaultDark;
let second = first.next();
assert_ne!(first, second);
let mut k = ThemeKind::DefaultDark;
for _ in 0..ThemeKind::ALL.len() {
k = k.next();
}
assert_eq!(k, ThemeKind::DefaultDark);
}
#[test]
fn test_theme_from_kind() {
let t = Theme::from_kind(ThemeKind::Nord);
assert_eq!(t.name, "Nord");
let t2 = Theme::from_name("dracula");
assert_eq!(t2.name, "Dracula");
}
}