Files
g3/crates/g3-cli/src/theme.rs
Dhanji Prasanna 0973b83d3a fix build warnings
2025-10-08 14:06:25 +11:00

147 lines
5.6 KiB
Rust

use ratatui::style::Color;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
use anyhow::Result;
/// Color theme configuration for the retro TUI
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ColorTheme {
/// Name of the theme
pub name: String,
/// Main terminal text color (for general output)
pub terminal_green: ColorValue,
/// Warning/system messages color
pub terminal_amber: ColorValue,
/// Border and dim text color
pub terminal_dim_green: ColorValue,
/// Background color
pub terminal_bg: ColorValue,
/// Highlight/emphasis color
pub terminal_cyan: ColorValue,
/// Error/negative diff color
pub terminal_red: ColorValue,
/// READY status color
pub terminal_pale_blue: ColorValue,
/// PROCESSING status color
pub terminal_dark_amber: ColorValue,
/// Bright/punchy text color
pub terminal_white: ColorValue,
/// Success status color (for tool completions)
pub terminal_success: ColorValue,
}
/// Represents a color value that can be serialized/deserialized
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ColorValue {
/// RGB color with r, g, b components
Rgb { r: u8, g: u8, b: u8 },
/// Named color
Named(String),
}
impl ColorValue {
/// Convert to ratatui Color
pub fn to_color(&self) -> Color {
match self {
ColorValue::Rgb { r, g, b } => Color::Rgb(*r, *g, *b),
ColorValue::Named(name) => match name.to_lowercase().as_str() {
"black" => Color::Black,
"red" => Color::Red,
"green" => Color::Green,
"yellow" => Color::Yellow,
"blue" => Color::Blue,
"magenta" => Color::Magenta,
"cyan" => Color::Cyan,
"gray" | "grey" => Color::Gray,
"darkgray" | "darkgrey" => Color::DarkGray,
"lightred" => Color::LightRed,
"lightgreen" => Color::LightGreen,
"lightyellow" => Color::LightYellow,
"lightblue" => Color::LightBlue,
"lightmagenta" => Color::LightMagenta,
"lightcyan" => Color::LightCyan,
"white" => Color::White,
_ => Color::White, // Default fallback
},
}
}
}
impl ColorTheme {
/// Load a theme from a JSON file
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let content = fs::read_to_string(path)?;
let theme: ColorTheme = serde_json::from_str(&content)?;
Ok(theme)
}
/// Get the default retro sci-fi theme (inspired by Alien terminals)
pub fn default() -> Self {
ColorTheme {
name: "Retro Sci-Fi".to_string(),
terminal_green: ColorValue::Rgb { r: 136, g: 244, b: 152 },
terminal_amber: ColorValue::Rgb { r: 242, g: 204, b: 148 },
terminal_dim_green: ColorValue::Rgb { r: 154, g: 174, b: 135 },
terminal_bg: ColorValue::Rgb { r: 0, g: 10, b: 0 },
terminal_cyan: ColorValue::Rgb { r: 0, g: 255, b: 255 },
terminal_red: ColorValue::Rgb { r: 239, g: 119, b: 109 },
terminal_pale_blue: ColorValue::Rgb { r: 173, g: 234, b: 251 },
terminal_dark_amber: ColorValue::Rgb { r: 204, g: 119, b: 34 },
terminal_white: ColorValue::Rgb { r: 218, g: 218, b: 219 },
terminal_success: ColorValue::Rgb { r: 136, g: 244, b: 152 }, // Same as terminal_green for retro theme
}
}
/// Get the Dracula theme
pub fn dracula() -> Self {
ColorTheme {
name: "Dracula".to_string(),
terminal_green: ColorValue::Rgb { r: 248, g: 248, b: 242 }, // Use Dracula foreground (white) for main text
terminal_amber: ColorValue::Rgb { r: 255, g: 184, b: 108 }, // Dracula orange
terminal_dim_green: ColorValue::Rgb { r: 98, g: 114, b: 164 }, // Dracula comment
terminal_bg: ColorValue::Rgb { r: 40, g: 42, b: 54 }, // Dracula background
terminal_cyan: ColorValue::Rgb { r: 139, g: 233, b: 253 }, // Dracula cyan
terminal_red: ColorValue::Rgb { r: 255, g: 85, b: 85 }, // Dracula red
terminal_pale_blue: ColorValue::Rgb { r: 189, g: 147, b: 249 }, // Dracula purple
terminal_dark_amber: ColorValue::Rgb { r: 255, g: 121, b: 198 }, // Dracula pink
terminal_white: ColorValue::Rgb { r: 248, g: 248, b: 242 }, // Dracula foreground
terminal_success: ColorValue::Rgb { r: 80, g: 250, b: 123 }, // Dracula green for success
}
}
/// Get a theme by name or from file
pub fn load(theme_name: Option<&str>) -> Result<Self> {
match theme_name {
None => Ok(Self::default()),
Some("default") | Some("retro") => Ok(Self::default()),
Some("dracula") => Ok(Self::dracula()),
Some(path) => {
// Try to load from file
if Path::new(path).exists() {
Self::from_file(path)
} else {
// Try to find in standard locations
let home = dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?;
let theme_file = home.join(".config").join("g3").join("themes").join(format!("{}.json", path));
if theme_file.exists() {
Self::from_file(theme_file)
} else {
Err(anyhow::anyhow!("Theme '{}' not found", path))
}
}
}
}
}
}