use serde::Deserialize;
use std::collections::HashMap;
const VAR_MAP: &[(&str, &str)] = &[
("bg", "--color-black"),
("text", "--color-white"),
("text-muted", "--color-slate-400"),
("surface", "--color-slate-500"),
("progress", "--color-green-500"),
("accent-soft", "--color-indigo-400"),
("accent", "--color-indigo-500"),
("accent-alt", "--color-indigo-600"),
("accent-deep", "--color-indigo-900"),
("highlight", "--color-purple-600"),
("highlight-dark", "--color-purple-700"),
("danger", "--color-red-400"),
("raised", "--color-neutral-900"),
];
#[derive(Debug, Clone, PartialEq)]
pub enum ThemeKind {
Dark,
Light,
}
#[derive(Debug, Clone)]
pub struct Theme {
pub id: String,
pub name: String,
pub kind: ThemeKind,
pub vars: HashMap<String, String>,
}
impl Theme {
pub fn var(&self, key: &str) -> Option<&str> {
self.vars.get(key).map(String::as_str)
}
pub fn to_css(&self) -> String {
let mut out = format!(".theme-{} {{\n", self.id);
for (purpose, css_var) in VAR_MAP {
if let Some(val) = self.var(purpose) {
out.push_str(&format!(" {}: {};\n", css_var, val));
}
}
out.push('}');
out
}
}
#[derive(Deserialize)]
struct RawTheme {
name: String,
#[serde(flatten)]
vars: HashMap<String, String>,
}
#[derive(Deserialize)]
struct ThemeFile {
dark: HashMap<String, RawTheme>,
light: HashMap<String, RawTheme>,
}
pub fn load_themes() -> Vec<Theme> {
let path = std::env::var("KOPUZ_THEMES_PATH")
.map(std::path::PathBuf::from)
.unwrap_or_else(|_| {
std::env::current_exe()
.ok()
.and_then(|p| p.parent().map(|d| d.join("assets/themes.json")))
.unwrap_or_else(|| std::path::PathBuf::from("assets/themes.json"))
});
let json = match std::fs::read_to_string(&path) {
Ok(j) => j,
Err(e) => {
tracing::warn!("failed to read themes.json from {}: {e}", path.display());
return Vec::new();
}
};
let file: ThemeFile = match serde_json::from_str(&json) {
Ok(f) => f,
Err(e) => {
tracing::warn!("themes.json is malformed: {e}");
return Vec::new();
}
};
let mut themes = Vec::new();
for (id, raw) in file.dark {
themes.push(Theme {
id,
name: raw.name,
kind: ThemeKind::Dark,
vars: raw.vars,
});
}
for (id, raw) in file.light {
themes.push(Theme {
id,
name: raw.name,
kind: ThemeKind::Light,
vars: raw.vars,
});
}
themes
}
pub fn theme_map() -> HashMap<String, Theme> {
load_themes()
.into_iter()
.map(|t| (t.id.clone(), t))
.collect()
}
pub fn all_themes_css() -> String {
load_themes()
.iter()
.map(|t| t.to_css())
.collect::<Vec<_>>()
.join("\n\n")
}
pub fn custom_theme_to_css(id: &str, vars: &std::collections::HashMap<String, String>) -> String {
let theme = Theme {
id: id.to_string(),
name: String::new(),
kind: ThemeKind::Dark,
vars: vars.clone(),
};
theme.to_css()
}