1use std::{collections::BTreeMap, fmt, path::PathBuf, str::FromStr};
2
3use serde::{Deserialize, Serialize};
4
5pub type HexColor = String;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
8#[serde(rename_all = "kebab-case")]
9pub enum ThemeMode {
10 Light,
11 #[default]
12 Dark,
13}
14
15impl ThemeMode {
16 pub const fn as_str(self) -> &'static str {
17 match self {
18 Self::Light => "light",
19 Self::Dark => "dark",
20 }
21 }
22
23 pub const fn default_background_tone(self) -> u8 {
24 match self {
25 Self::Light => 98,
26 Self::Dark => 10,
27 }
28 }
29
30 pub const fn default_surface_tone(self) -> u8 {
31 match self {
32 Self::Light => 95,
33 Self::Dark => 14,
34 }
35 }
36
37 pub const fn default_text_tone(self) -> u8 {
38 match self {
39 Self::Light => 12,
40 Self::Dark => 94,
41 }
42 }
43
44 pub const fn default_muted_text_tone(self) -> u8 {
45 match self {
46 Self::Light => 30,
47 Self::Dark => 80,
48 }
49 }
50}
51
52impl fmt::Display for ThemeMode {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 f.write_str(self.as_str())
55 }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
59#[serde(rename_all = "kebab-case")]
60pub enum ContrastStrategy {
61 #[default]
62 RelativeLuminance,
63 ApcaExperimental,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
67#[serde(rename_all = "kebab-case")]
68pub enum ChromaStrategy {
69 Subtle,
70 #[default]
71 Normal,
72 Vibrant,
73 Muted,
74 Industrial,
75}
76
77impl ChromaStrategy {
78 pub const fn as_str(self) -> &'static str {
79 match self {
80 Self::Subtle => "subtle",
81 Self::Normal => "normal",
82 Self::Vibrant => "vibrant",
83 Self::Muted => "muted",
84 Self::Industrial => "industrial",
85 }
86 }
87}
88
89impl fmt::Display for ChromaStrategy {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 f.write_str(self.as_str())
92 }
93}
94
95impl ContrastStrategy {
96 pub const fn as_str(self) -> &'static str {
97 match self {
98 Self::RelativeLuminance => "relative-luminance",
99 Self::ApcaExperimental => "apca-experimental",
100 }
101 }
102}
103
104impl fmt::Display for ContrastStrategy {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 f.write_str(self.as_str())
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
111#[serde(rename_all = "kebab-case")]
112pub enum RenderTarget {
113 Gtk,
114 Hyprland,
115 HyprlandLua,
116 Kitty,
117 Css,
118 Waybar,
119 Rofi,
120 Alacritty,
121 Foot,
122 Ghostty,
123 Editor,
124 Zed,
125}
126
127impl RenderTarget {
128 pub const MVP: [Self; 4] = [Self::Gtk, Self::Hyprland, Self::Kitty, Self::Css];
129
130 pub const fn as_str(self) -> &'static str {
131 match self {
132 Self::Gtk => "gtk",
133 Self::Hyprland => "hyprland",
134 Self::HyprlandLua => "hyprland-lua",
135 Self::Kitty => "kitty",
136 Self::Css => "css",
137 Self::Waybar => "waybar",
138 Self::Rofi => "rofi",
139 Self::Alacritty => "alacritty",
140 Self::Foot => "foot",
141 Self::Ghostty => "ghostty",
142 Self::Editor => "editor",
143 Self::Zed => "zed",
144 }
145 }
146
147 pub const fn file_name(self) -> &'static str {
148 match self {
149 Self::Gtk => "gtk.css",
150 Self::Hyprland => "hyprland.conf",
151 Self::HyprlandLua => "hypr-chromasync.lua",
152 Self::Kitty => "kitty.conf",
153 Self::Css => "theme.css",
154 Self::Waybar => "style.css",
155 Self::Rofi => "config.rasi",
156 Self::Alacritty => "alacritty.toml",
157 Self::Foot => "foot.ini",
158 Self::Ghostty => "chromasync.ghostty",
159 Self::Editor => "theme.json",
160 Self::Zed => "chromasync.json",
161 }
162 }
163}
164
165impl fmt::Display for RenderTarget {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 f.write_str(self.as_str())
168 }
169}
170
171#[derive(
172 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
173)]
174#[serde(rename_all = "snake_case")]
175pub enum PaletteFamilyName {
176 #[default]
177 Primary,
178 Secondary,
179 Tertiary,
180 Neutral,
181 NeutralVariant,
182 Error,
183 Success,
184 Warning,
185 Info,
186}
187
188impl PaletteFamilyName {
189 pub const ALL: [Self; 9] = [
190 Self::Primary,
191 Self::Secondary,
192 Self::Tertiary,
193 Self::Neutral,
194 Self::NeutralVariant,
195 Self::Error,
196 Self::Success,
197 Self::Warning,
198 Self::Info,
199 ];
200
201 pub const fn as_str(self) -> &'static str {
202 match self {
203 Self::Primary => "primary",
204 Self::Secondary => "secondary",
205 Self::Tertiary => "tertiary",
206 Self::Neutral => "neutral",
207 Self::NeutralVariant => "neutral_variant",
208 Self::Error => "error",
209 Self::Success => "success",
210 Self::Warning => "warning",
211 Self::Info => "info",
212 }
213 }
214}
215
216impl fmt::Display for PaletteFamilyName {
217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218 f.write_str(self.as_str())
219 }
220}
221
222impl FromStr for PaletteFamilyName {
223 type Err = ();
224
225 fn from_str(value: &str) -> Result<Self, Self::Err> {
226 match value {
227 "primary" => Ok(Self::Primary),
228 "secondary" => Ok(Self::Secondary),
229 "tertiary" => Ok(Self::Tertiary),
230 "neutral" => Ok(Self::Neutral),
231 "neutral_variant" => Ok(Self::NeutralVariant),
232 "error" => Ok(Self::Error),
233 "success" => Ok(Self::Success),
234 "warning" => Ok(Self::Warning),
235 "info" => Ok(Self::Info),
236 _ => Err(()),
237 }
238 }
239}
240
241#[derive(
242 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
243)]
244#[serde(rename_all = "snake_case")]
245pub enum SemanticTokenName {
246 #[default]
247 Bg,
248 BgSecondary,
249 Surface,
250 SurfaceElevated,
251 Text,
252 TextMuted,
253 Border,
254 BorderStrong,
255 Accent,
256 AccentHover,
257 AccentActive,
258 AccentFg,
259 Selection,
260 Link,
261 Success,
262 Warning,
263 Error,
264}
265
266impl SemanticTokenName {
267 pub const ALL: [Self; 17] = [
268 Self::Bg,
269 Self::BgSecondary,
270 Self::Surface,
271 Self::SurfaceElevated,
272 Self::Text,
273 Self::TextMuted,
274 Self::Border,
275 Self::BorderStrong,
276 Self::Accent,
277 Self::AccentHover,
278 Self::AccentActive,
279 Self::AccentFg,
280 Self::Selection,
281 Self::Link,
282 Self::Success,
283 Self::Warning,
284 Self::Error,
285 ];
286
287 pub const fn as_str(self) -> &'static str {
288 match self {
289 Self::Bg => "bg",
290 Self::BgSecondary => "bg_secondary",
291 Self::Surface => "surface",
292 Self::SurfaceElevated => "surface_elevated",
293 Self::Text => "text",
294 Self::TextMuted => "text_muted",
295 Self::Border => "border",
296 Self::BorderStrong => "border_strong",
297 Self::Accent => "accent",
298 Self::AccentHover => "accent_hover",
299 Self::AccentActive => "accent_active",
300 Self::AccentFg => "accent_fg",
301 Self::Selection => "selection",
302 Self::Link => "link",
303 Self::Success => "success",
304 Self::Warning => "warning",
305 Self::Error => "error",
306 }
307 }
308}
309
310impl fmt::Display for SemanticTokenName {
311 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312 f.write_str(self.as_str())
313 }
314}
315
316impl FromStr for SemanticTokenName {
317 type Err = ();
318
319 fn from_str(value: &str) -> Result<Self, Self::Err> {
320 match value {
321 "bg" => Ok(Self::Bg),
322 "bg_secondary" => Ok(Self::BgSecondary),
323 "surface" => Ok(Self::Surface),
324 "surface_elevated" => Ok(Self::SurfaceElevated),
325 "text" => Ok(Self::Text),
326 "text_muted" => Ok(Self::TextMuted),
327 "border" => Ok(Self::Border),
328 "border_strong" => Ok(Self::BorderStrong),
329 "accent" => Ok(Self::Accent),
330 "accent_hover" => Ok(Self::AccentHover),
331 "accent_active" => Ok(Self::AccentActive),
332 "accent_fg" => Ok(Self::AccentFg),
333 "selection" => Ok(Self::Selection),
334 "link" => Ok(Self::Link),
335 "success" => Ok(Self::Success),
336 "warning" => Ok(Self::Warning),
337 "error" => Ok(Self::Error),
338 _ => Err(()),
339 }
340 }
341}
342
343#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
344pub struct ToneSample {
345 pub tone: u8,
346 pub hex: HexColor,
347}
348
349#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
350pub struct PaletteFamily {
351 pub name: PaletteFamilyName,
352 pub hue: f32,
353 pub base_chroma: f32,
354 #[serde(default)]
355 pub tones: Vec<ToneSample>,
356 pub dominance: Option<f32>,
357 pub source_region: Option<String>,
358 pub seed_index: Option<usize>,
359}
360
361#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
362pub struct GeneratedPalette {
363 pub seed: HexColor,
364 pub mode: ThemeMode,
365 pub chroma: ChromaStrategy,
366 pub families: BTreeMap<PaletteFamilyName, PaletteFamily>,
367}
368
369#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
370pub struct GenerationRequest {
371 pub seed: Option<String>,
372 pub wallpaper: Option<PathBuf>,
373 pub template: Option<String>,
374 pub mode: ThemeMode,
375 #[serde(default)]
376 pub contrast: ContrastStrategy,
377 #[serde(default)]
378 pub chroma: ChromaStrategy,
379 #[serde(default)]
380 pub targets: Vec<String>,
381 pub output_dir: PathBuf,
382}
383
384#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
385pub struct GeneratedArtifact {
386 pub target: String,
387 pub file_name: String,
388 pub content: String,
389}
390
391#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
392pub struct GenerationContext {
393 pub mode: ThemeMode,
394 pub template_name: String,
395 pub chroma: ChromaStrategy,
396 pub output_dir: PathBuf,
397 pub seed: Option<String>,
398}
399
400#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
401pub struct ThemePack {
402 pub name: String,
403 pub version: String,
404 pub description: Option<String>,
405 pub author: Option<String>,
406 pub license: Option<String>,
407 pub homepage: Option<String>,
408 pub root_dir: PathBuf,
409 #[serde(default)]
410 pub template_dirs: Vec<PathBuf>,
411 #[serde(default)]
412 pub target_dirs: Vec<PathBuf>,
413}
414
415#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
416pub struct TemplateTokenRule {
417 pub family: PaletteFamilyName,
418 pub tone: f32,
419 pub chroma: Option<f32>,
420 pub chroma_scale: Option<f32>,
421}
422
423#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
424pub struct TemplateDefinition {
425 pub name: String,
426 pub mode: ThemeMode,
427 pub description: Option<String>,
428 #[serde(default)]
429 pub tokens: BTreeMap<SemanticTokenName, TemplateTokenRule>,
430}
431
432#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
433pub struct SemanticTokens {
434 pub bg: HexColor,
435 pub bg_secondary: HexColor,
436 pub surface: HexColor,
437 pub surface_elevated: HexColor,
438 pub text: HexColor,
439 pub text_muted: HexColor,
440 pub border: HexColor,
441 pub border_strong: HexColor,
442 pub accent: HexColor,
443 pub accent_hover: HexColor,
444 pub accent_active: HexColor,
445 pub accent_fg: HexColor,
446 pub selection: HexColor,
447 pub link: HexColor,
448 pub success: HexColor,
449 pub warning: HexColor,
450 pub error: HexColor,
451}