1use crate::{Color, NamedTheme, ThemeColors, ThemeVariants};
2use serde::Deserialize;
3use std::fs;
4use std::path::Path;
5
6#[derive(Debug, Deserialize)]
7struct ThemeFile {
8 name: String,
9 dark: Option<ThemeColorsToml>,
10 light: Option<ThemeColorsToml>,
11}
12
13#[derive(Debug, Deserialize)]
14struct ThemeColorsToml {
15 bg: String,
16 dialog_bg: String,
17 fg: String,
18 accent: String,
19 accent_secondary: String,
20 highlight: String,
21 muted: String,
22 success: String,
23 warning: String,
24 danger: String,
25 border: String,
26 selection_bg: String,
27 selection_fg: String,
28 graph_line: String,
29}
30
31fn convert_colors(colors: &ThemeColorsToml) -> Option<ThemeColors> {
32 Some(ThemeColors {
33 bg: Color::from_hex(&colors.bg)?,
34 dialog_bg: Color::from_hex(&colors.dialog_bg)?,
35 fg: Color::from_hex(&colors.fg)?,
36 accent: Color::from_hex(&colors.accent)?,
37 accent_secondary: Color::from_hex(&colors.accent_secondary)?,
38 highlight: Color::from_hex(&colors.highlight)?,
39 muted: Color::from_hex(&colors.muted)?,
40 success: Color::from_hex(&colors.success)?,
41 warning: Color::from_hex(&colors.warning)?,
42 danger: Color::from_hex(&colors.danger)?,
43 border: Color::from_hex(&colors.border)?,
44 selection_bg: Color::from_hex(&colors.selection_bg)?,
45 selection_fg: Color::from_hex(&colors.selection_fg)?,
46 graph_line: Color::from_hex(&colors.graph_line)?,
47 })
48}
49
50pub fn parse_theme_toml(id: &str, content: &str, is_builtin: bool) -> Option<NamedTheme> {
51 let theme_file: ThemeFile = toml::from_str(content).ok()?;
52
53 let dark = theme_file.dark.as_ref().and_then(convert_colors);
54 let light = theme_file.light.as_ref().and_then(convert_colors);
55
56 if dark.is_none() && light.is_none() {
57 return None;
58 }
59
60 Some(NamedTheme {
61 id: id.to_string(),
62 name: theme_file.name,
63 is_builtin,
64 variants: ThemeVariants { dark, light },
65 })
66}
67
68pub fn load_themes_from_dir(dir: &Path, is_builtin: bool) -> Vec<NamedTheme> {
69 if !dir.exists() {
70 return Vec::new();
71 }
72
73 let mut themes = Vec::new();
74
75 if let Ok(entries) = fs::read_dir(dir) {
76 for entry in entries.flatten() {
77 let path = entry.path();
78 if path.extension().is_some_and(|e| e == "toml") {
79 if let Ok(content) = fs::read_to_string(&path) {
80 let id = path
81 .file_stem()
82 .and_then(|s| s.to_str())
83 .unwrap_or("unknown")
84 .to_string();
85
86 if let Some(mut theme) = parse_theme_toml(&id, &content, is_builtin) {
87 if !is_builtin {
88 theme.name = format!("{} (user)", theme.name);
89 }
90 themes.push(theme);
91 }
92 }
93 }
94 }
95 }
96
97 themes
98}