Skip to content

Commit 19053cc

Browse files
committed
Add theming
1 parent 909de72 commit 19053cc

File tree

13 files changed

+603
-38
lines changed

13 files changed

+603
-38
lines changed

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ yal-core = { path = "./crates/yal-core" }
1616
fuzzy-matcher = "0.3.7"
1717

1818
[workspace]
19-
members = ["src-tauri","crates/yal-core", "crates/lightsky-sys", "crates/lightsky"]
19+
members = ["src-tauri","crates/yal-core", "crates/lightsky-sys", "crates/lightsky", "crates/yal-theme"]

crates/yal-core/src/lib.rs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,21 @@ pub enum AlignV {
1919

2020
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2121
pub struct AppConfig {
22-
// UI
22+
pub window: Option<WindowConfig>,
23+
pub theme: Option<String>,
24+
pub font: Option<FontConfig>,
25+
}
26+
27+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
28+
pub struct FontConfig {
2329
pub font: Option<String>,
2430
pub font_size: Option<f32>,
25-
pub bg_color: Option<String>,
26-
pub fg_color: Option<String>,
27-
pub bg_font_color: Option<String>,
28-
pub fg_font_color: Option<String>,
31+
}
2932

30-
// Window size
33+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
34+
pub struct WindowConfig {
3135
pub w_width: Option<f64>,
3236
pub w_height: Option<f64>,
33-
34-
// Alignment + margins (optional; defaults used if omitted)
3537
pub align_h: Option<AlignH>, // "left" | "center" | "right"
3638
pub align_v: Option<AlignV>, // "top" | "center" | "bottom"
3739
pub margin_x: Option<f64>, // px inset from left/right edges (default ~12)
@@ -41,6 +43,15 @@ pub struct AppConfig {
4143
pub w_radius: Option<f64>, // window corner radius in px (default ~0)
4244
}
4345

46+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
47+
pub struct Theme {
48+
pub name: Option<String>,
49+
pub bg_color: Option<String>, // background color
50+
pub fg_color: Option<String>, // foreground color used for highlighting
51+
pub bg_font_color: Option<String>, // font color used for background items
52+
pub fg_font_color: Option<String>, // font color used for foreground items
53+
}
54+
4455
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
4556
pub struct AppInfo {
4657
pub name: String,
@@ -59,6 +70,7 @@ pub struct WindowTarget {
5970
pub enum Command {
6071
App(AppInfo),
6172
Switch(WindowTarget),
73+
Theme(String),
6274
}
6375

6476
impl Display for Command {
@@ -78,13 +90,15 @@ impl Command {
7890
t.app_name.clone()
7991
}
8092
}
93+
Command::Theme(name) => name.clone(),
8194
}
8295
}
8396

8497
pub fn prefix(&self) -> &str {
8598
match self {
8699
Command::App(_) => "app",
87100
Command::Switch(_) => "switch",
101+
Command::Theme(_) => "theme",
88102
}
89103
}
90104
}
@@ -93,13 +107,16 @@ impl Command {
93107
pub enum CommandKind {
94108
App,
95109
Switch,
110+
Theme,
96111
}
97112

98113
impl CommandKind {
99114
pub fn is_kind(&self, cmd: &Command) -> bool {
100115
matches!(
101116
(self, cmd),
102-
(CommandKind::App, Command::App(_)) | (CommandKind::Switch, Command::Switch(_))
117+
(CommandKind::App, Command::App(_))
118+
| (CommandKind::Switch, Command::Switch(_))
119+
| (CommandKind::Theme, Command::Theme(_))
103120
)
104121
}
105122
}

crates/yal-theme/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "yal-theme"
3+
version = "0.0.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
yal-core = { path = "../yal-core" }

crates/yal-theme/src/lib.rs

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
use yal_core::Theme;
2+
3+
#[derive(Copy, Clone, Debug)]
4+
pub struct ThemeRef {
5+
pub name: &'static str,
6+
pub bg_color: &'static str,
7+
pub fg_color: &'static str,
8+
pub bg_font_color: &'static str,
9+
pub fg_font_color: &'static str,
10+
}
11+
12+
impl ThemeRef {
13+
pub const fn new(
14+
name: &'static str,
15+
bg_color: &'static str,
16+
fg_color: &'static str,
17+
bg_font_color: &'static str,
18+
fg_font_color: &'static str,
19+
) -> Self {
20+
Self {
21+
name,
22+
bg_color,
23+
fg_color,
24+
bg_font_color,
25+
fg_font_color,
26+
}
27+
}
28+
29+
pub fn to_owned(self) -> Theme {
30+
Theme {
31+
name: Some(self.name.to_string()),
32+
bg_color: Some(self.bg_color.to_string()),
33+
fg_color: Some(self.fg_color.to_string()),
34+
bg_font_color: Some(self.bg_font_color.to_string()),
35+
fg_font_color: Some(self.fg_font_color.to_string()),
36+
}
37+
}
38+
}
39+
40+
impl From<ThemeRef> for Theme {
41+
fn from(t: ThemeRef) -> Self {
42+
t.to_owned()
43+
}
44+
}
45+
46+
/* ------------------------------ Theme constants ------------------------------ */
47+
/* Notes:
48+
* - `fg_color` is treated as the *highlight background*.
49+
* - `bg_font_color` is normal text on `bg_color`.
50+
* - `fg_font_color` is text on the highlighted row (`fg_color`).
51+
* Tweak to taste per your UI.
52+
*/
53+
54+
// Monokai (classic)
55+
pub const MONOKAI: ThemeRef = ThemeRef::new(
56+
"monokai", "#272822", // bg
57+
"#49483E", // highlight bg
58+
"#F8F8F2", // text on bg
59+
"#FFFFFF", // text on highlight
60+
);
61+
62+
// Dracula
63+
pub const DRACULA: ThemeRef = ThemeRef::new("dracula", "#282A36", "#44475A", "#F8F8F2", "#FFFFFF");
64+
65+
// Catppuccin — Latte (light)
66+
pub const CATPPUCCIN_LATTE: ThemeRef = ThemeRef::new(
67+
"catppuccin-latte",
68+
"#EFF1F5", // base
69+
"#CCD0DA", // surface2-ish as highlight
70+
"#4C4F69", // text
71+
"#1E1E2E", // strong text on highlight
72+
);
73+
74+
// Catppuccin — Frappé (dark)
75+
pub const CATPPUCCIN_FRAPPE: ThemeRef = ThemeRef::new(
76+
"catppuccin-frappe",
77+
"#303446", // base
78+
"#414559", // surface1
79+
"#C6D0F5", // text
80+
"#E6E9EF", // text on highlight
81+
);
82+
83+
// Catppuccin — Macchiato (dark)
84+
pub const CATPPUCCIN_MACCHIATO: ThemeRef = ThemeRef::new(
85+
"catppuccin-macchiato",
86+
"#24273A",
87+
"#363A4F",
88+
"#CAD3F5",
89+
"#E8E8E8",
90+
);
91+
92+
// Catppuccin — Mocha (dark)
93+
pub const CATPPUCCIN_MOCHA: ThemeRef = ThemeRef::new(
94+
"catppuccin-mocha",
95+
"#1E1E2E",
96+
"#313244",
97+
"#CDD6F4",
98+
"#E6E6E6",
99+
);
100+
101+
// Solarized Dark
102+
pub const SOLARIZED_DARK: ThemeRef =
103+
ThemeRef::new("solarized-dark", "#002B36", "#073642", "#93A1A1", "#FDF6E3");
104+
105+
// Solarized Light
106+
pub const SOLARIZED_LIGHT: ThemeRef = ThemeRef::new(
107+
"solarized-light",
108+
"#FDF6E3",
109+
"#EEE8D5",
110+
"#586E75",
111+
"#073642",
112+
);
113+
114+
// Gruvbox Dark (hard-ish)
115+
pub const GRUVBOX_DARK: ThemeRef =
116+
ThemeRef::new("gruvbox-dark", "#282828", "#3C3836", "#EBDBB2", "#FBF1C7");
117+
118+
// Gruvbox Light
119+
pub const GRUVBOX_LIGHT: ThemeRef =
120+
ThemeRef::new("gruvbox-light", "#FBF1C7", "#EBDBB2", "#3C3836", "#282828");
121+
122+
// Nord
123+
pub const NORD: ThemeRef = ThemeRef::new("nord", "#2E3440", "#3B4252", "#D8DEE9", "#ECEFF4");
124+
125+
// One Dark
126+
pub const ONE_DARK: ThemeRef =
127+
ThemeRef::new("one-dark", "#282C34", "#3E4451", "#ABB2BF", "#FFFFFF");
128+
129+
// Tokyo Night (classic)
130+
pub const TOKYO_NIGHT: ThemeRef =
131+
ThemeRef::new("tokyo-night", "#1A1B26", "#2F334D", "#C0CAF5", "#E6E6E6");
132+
133+
// Tokyo Night Storm
134+
pub const TOKYO_NIGHT_STORM: ThemeRef = ThemeRef::new(
135+
"tokyo-night-storm",
136+
"#24283B",
137+
"#283457",
138+
"#C0CAF5",
139+
"#E6E6E6",
140+
);
141+
142+
/* --------------------------------- Registry --------------------------------- */
143+
144+
pub const ALL: &[ThemeRef] = &[
145+
MONOKAI,
146+
DRACULA,
147+
CATPPUCCIN_LATTE,
148+
CATPPUCCIN_FRAPPE,
149+
CATPPUCCIN_MACCHIATO,
150+
CATPPUCCIN_MOCHA,
151+
SOLARIZED_DARK,
152+
SOLARIZED_LIGHT,
153+
GRUVBOX_DARK,
154+
GRUVBOX_LIGHT,
155+
NORD,
156+
ONE_DARK,
157+
TOKYO_NIGHT,
158+
TOKYO_NIGHT_STORM,
159+
];
160+
161+
/// Case-insensitive lookup. Supports a few aliases.
162+
pub fn by_name(name: &str) -> Option<ThemeRef> {
163+
let n = name.trim().to_lowercase();
164+
let normalized = match n.as_str() {
165+
// aliases
166+
"one" | "onedark" | "one-dark-pro" => "one-dark",
167+
"tokyo" | "tokyonight" => "tokyo-night",
168+
"tokyo-storm" | "tokyonight-storm" => "tokyo-night-storm",
169+
"catppuccin" => "catppuccin-mocha", // default flavor
170+
"catppuccin-latte" | "latte" => "catppuccin-latte",
171+
"catppuccin-frappe" | "frappe" => "catppuccin-frappe",
172+
"catppuccin-macchiato" | "macchiato" => "catppuccin-macchiato",
173+
"catppuccin-mocha" | "mocha" => "catppuccin-mocha",
174+
other => other,
175+
};
176+
177+
ALL.iter().copied().find(|t| t.name == normalized)
178+
}
179+
180+
/// Owned theme list (useful for config UIs).
181+
pub fn list_owned() -> Vec<Theme> {
182+
ALL.iter().copied().map(Theme::from).collect()
183+
}
184+
185+
/* ----------------------------------- Tests ---------------------------------- */
186+
187+
#[cfg(test)]
188+
mod tests {
189+
use super::*;
190+
191+
#[test]
192+
fn from_ref_to_owned() {
193+
let t: Theme = MONOKAI.into();
194+
assert_eq!(t.name.as_deref(), Some("monokai"));
195+
assert_eq!(t.bg_color.as_deref(), Some("#272822"));
196+
assert_eq!(t.fg_color.as_deref(), Some("#49483E"));
197+
}
198+
199+
#[test]
200+
fn lookup_aliases() {
201+
assert_eq!(by_name("OneDark").unwrap().name, "one-dark");
202+
assert_eq!(by_name("catppuccin").unwrap().name, "catppuccin-mocha");
203+
assert_eq!(
204+
by_name("tokyonight-storm").unwrap().name,
205+
"tokyo-night-storm"
206+
);
207+
}
208+
}

src-tauri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ tauri-plugin-log = "2"
4040
log = "0.4.28"
4141
once_cell = "1.21.3"
4242
lightsky = { path = "../crates/lightsky" }
43+
yal-theme = { path = "../crates/yal-theme" }
4344

4445
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
4546
tauri-plugin-autostart = "2"

src-tauri/src/cmd.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ use yal_core::{AppInfo, Command, WindowTarget};
88
use crate::{ax::AX, cmd::app::get_app_info};
99

1010
mod app;
11+
pub mod theme;
1112

1213
#[tauri::command]
1314
pub fn run_cmd(app: tauri::AppHandle, cmd: Command) -> Result<(), String> {
1415
match cmd {
1516
Command::App(app_info) => run_app_cmd(app, app_info),
1617
Command::Switch(target) => run_switch_cmd(app, target),
18+
Command::Theme(theme) => run_theme_cmd(app, theme),
1719
}
1820
}
1921

@@ -30,6 +32,13 @@ fn run_switch_cmd(app: tauri::AppHandle, target: WindowTarget) -> Result<(), Str
3032
Ok(())
3133
}
3234

35+
fn run_theme_cmd(app: tauri::AppHandle, theme: String) -> Result<(), String> {
36+
let theme_manager = app.state::<Arc<RwLock<theme::ThemeManager>>>();
37+
let mut theme_manager = theme_manager.write().unwrap();
38+
theme_manager.apply_theme(&app, &theme);
39+
Ok(())
40+
}
41+
3342
pub fn get_cmds(app: &tauri::AppHandle) -> Vec<Command> {
3443
let ax = app.state::<Arc<RwLock<AX>>>();
3544
let ax = ax.read().unwrap();
@@ -53,5 +62,15 @@ pub fn get_cmds(app: &tauri::AppHandle) -> Vec<Command> {
5362
.map(Command::Switch)
5463
.collect::<Vec<Command>>();
5564

56-
[app_infos, switch_targets].concat()
65+
let themes = app
66+
.state::<Arc<RwLock<theme::ThemeManager>>>()
67+
.read()
68+
.unwrap()
69+
.load_themes()
70+
.into_iter()
71+
.filter_map(|t| t.name)
72+
.map(Command::Theme)
73+
.collect::<Vec<Command>>();
74+
75+
[app_infos, switch_targets, themes].concat()
5776
}

0 commit comments

Comments
 (0)