use ratatui::style::Color;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum ThemeName {
#[default]
Dark,
Light,
Ocean,
Neon,
}
impl ThemeName {
#[allow(dead_code)]
pub fn all() -> Vec<ThemeName> {
vec![
ThemeName::Dark,
ThemeName::Light,
ThemeName::Ocean,
ThemeName::Neon,
]
}
#[allow(dead_code)]
pub fn next(&self) -> ThemeName {
match self {
ThemeName::Dark => ThemeName::Light,
ThemeName::Light => ThemeName::Ocean,
ThemeName::Ocean => ThemeName::Neon,
ThemeName::Neon => ThemeName::Dark,
}
}
pub fn as_str(&self) -> &'static str {
match self {
ThemeName::Dark => "dark",
ThemeName::Light => "light",
ThemeName::Ocean => "ocean",
ThemeName::Neon => "neon",
}
}
}
impl std::str::FromStr for ThemeName {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"dark" => Ok(ThemeName::Dark),
"light" => Ok(ThemeName::Light),
"ocean" => Ok(ThemeName::Ocean),
"neon" => Ok(ThemeName::Neon),
_ => Err(format!("Unknown theme: {}", s)),
}
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct Theme {
pub name: ThemeName,
pub background: Color,
pub foreground: Color,
pub primary: Color,
pub secondary: Color,
pub accent: Color,
pub success: Color,
pub warning: Color,
pub error: Color,
pub muted: Color,
pub border: Color,
pub highlight: Color,
pub gauge_gradient: Vec<Color>,
}
impl Theme {
pub fn dark() -> Self {
Self {
name: ThemeName::Dark,
background: Color::Rgb(17, 17, 27),
foreground: Color::Rgb(205, 214, 244),
primary: Color::Rgb(137, 180, 250),
secondary: Color::Rgb(203, 166, 247),
accent: Color::Rgb(249, 226, 175),
success: Color::Rgb(166, 227, 161),
warning: Color::Rgb(249, 226, 175),
error: Color::Rgb(243, 139, 168),
muted: Color::Rgb(108, 112, 134),
border: Color::Rgb(69, 71, 90),
highlight: Color::Rgb(49, 50, 68),
gauge_gradient: vec![
Color::Rgb(166, 227, 161), Color::Rgb(249, 226, 175), Color::Rgb(250, 179, 135), Color::Rgb(243, 139, 168), ],
}
}
pub fn light() -> Self {
Self {
name: ThemeName::Light,
background: Color::Rgb(239, 241, 245),
foreground: Color::Rgb(76, 79, 105),
primary: Color::Rgb(30, 102, 245),
secondary: Color::Rgb(136, 57, 239),
accent: Color::Rgb(223, 142, 29),
success: Color::Rgb(64, 160, 43),
warning: Color::Rgb(223, 142, 29),
error: Color::Rgb(210, 15, 57),
muted: Color::Rgb(156, 160, 176),
border: Color::Rgb(188, 192, 204),
highlight: Color::Rgb(220, 224, 232),
gauge_gradient: vec![
Color::Rgb(64, 160, 43), Color::Rgb(223, 142, 29), Color::Rgb(254, 100, 11), Color::Rgb(210, 15, 57), ],
}
}
pub fn ocean() -> Self {
Self {
name: ThemeName::Ocean,
background: Color::Rgb(13, 17, 23),
foreground: Color::Rgb(201, 209, 217),
primary: Color::Rgb(88, 166, 255),
secondary: Color::Rgb(121, 192, 255),
accent: Color::Rgb(255, 166, 87),
success: Color::Rgb(63, 185, 80),
warning: Color::Rgb(210, 153, 34),
error: Color::Rgb(248, 81, 73),
muted: Color::Rgb(110, 118, 129),
border: Color::Rgb(48, 54, 61),
highlight: Color::Rgb(33, 38, 45),
gauge_gradient: vec![
Color::Rgb(63, 185, 80), Color::Rgb(210, 153, 34), Color::Rgb(255, 166, 87), Color::Rgb(248, 81, 73), ],
}
}
pub fn neon() -> Self {
Self {
name: ThemeName::Neon,
background: Color::Rgb(10, 10, 20),
foreground: Color::Rgb(230, 230, 250),
primary: Color::Rgb(0, 255, 255),
secondary: Color::Rgb(255, 0, 255),
accent: Color::Rgb(255, 255, 0),
success: Color::Rgb(0, 255, 128),
warning: Color::Rgb(255, 200, 0),
error: Color::Rgb(255, 50, 100),
muted: Color::Rgb(100, 100, 120),
border: Color::Rgb(60, 60, 80),
highlight: Color::Rgb(30, 30, 50),
gauge_gradient: vec![
Color::Rgb(0, 255, 128), Color::Rgb(255, 255, 0), Color::Rgb(255, 128, 0), Color::Rgb(255, 0, 128), ],
}
}
pub fn from_name(name: ThemeName) -> Self {
match name {
ThemeName::Dark => Self::dark(),
ThemeName::Light => Self::light(),
ThemeName::Ocean => Self::ocean(),
ThemeName::Neon => Self::neon(),
}
}
pub fn speed_color(&self, normalized: f64) -> Color {
let n = normalized.clamp(0.0, 1.0);
let idx = (n * (self.gauge_gradient.len() - 1) as f64) as usize;
let idx = idx.min(self.gauge_gradient.len() - 1);
self.gauge_gradient[idx]
}
}
impl Default for Theme {
fn default() -> Self {
Self::dark()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_theme_names() {
assert_eq!(ThemeName::Dark.as_str(), "dark");
assert_eq!(ThemeName::Light.as_str(), "light");
assert_eq!(ThemeName::Ocean.as_str(), "ocean");
assert_eq!(ThemeName::Neon.as_str(), "neon");
}
#[test]
fn test_theme_cycle() {
let theme = ThemeName::Dark;
assert_eq!(theme.next(), ThemeName::Light);
assert_eq!(ThemeName::Neon.next(), ThemeName::Dark);
}
#[test]
fn test_speed_color() {
let theme = Theme::dark();
let _ = theme.speed_color(0.0);
let _ = theme.speed_color(0.5);
let _ = theme.speed_color(1.0);
}
}