Skip to main content

chromasync_types/
lib.rs

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    Gtk3,
115    Gtk4,
116    Hyprland,
117    HyprlandLua,
118    Kitty,
119    Css,
120    Waybar,
121    Rofi,
122    Alacritty,
123    Foot,
124    Ghostty,
125    Editor,
126    Zed,
127    Chromium,
128    GoogleChrome,
129    HeliumBrowser,
130    #[serde(rename = "kcolorscheme")]
131    KColorScheme,
132    Micro,
133    Qt5,
134    Qt6,
135    #[serde(rename = "vscode")]
136    VsCode,
137    #[serde(rename = "vscode-insiders")]
138    VsCodeInsiders,
139}
140
141impl RenderTarget {
142    pub const MVP: [Self; 4] = [Self::Gtk, Self::Hyprland, Self::Kitty, Self::Css];
143
144    pub const fn as_str(self) -> &'static str {
145        match self {
146            Self::Gtk => "gtk",
147            Self::Gtk3 => "gtk3",
148            Self::Gtk4 => "gtk4",
149            Self::Hyprland => "hyprland",
150            Self::HyprlandLua => "hyprland-lua",
151            Self::Kitty => "kitty",
152            Self::Css => "css",
153            Self::Waybar => "waybar",
154            Self::Rofi => "rofi",
155            Self::Alacritty => "alacritty",
156            Self::Foot => "foot",
157            Self::Ghostty => "ghostty",
158            Self::Editor => "editor",
159            Self::Zed => "zed",
160            Self::Chromium => "chromium",
161            Self::GoogleChrome => "google-chrome",
162            Self::HeliumBrowser => "helium-browser",
163            Self::KColorScheme => "kcolorscheme",
164            Self::Micro => "micro",
165            Self::Qt5 => "qt5",
166            Self::Qt6 => "qt6",
167            Self::VsCode => "vscode",
168            Self::VsCodeInsiders => "vscode-insiders",
169        }
170    }
171
172    pub const fn file_name(self) -> &'static str {
173        match self {
174            Self::Gtk => "gtk.css",
175            Self::Gtk3 | Self::Gtk4 => "gtk.css",
176            Self::Hyprland => "hyprland.conf",
177            Self::HyprlandLua => "hypr-chromasync.lua",
178            Self::Kitty => "kitty.conf",
179            Self::Css => "theme.css",
180            Self::Waybar => "style.css",
181            Self::Rofi => "config.rasi",
182            Self::Alacritty => "alacritty.toml",
183            Self::Foot => "foot.ini",
184            Self::Ghostty => "chromasync.ghostty",
185            Self::Editor => "theme.json",
186            Self::Zed => "chromasync.json",
187            Self::Chromium | Self::GoogleChrome | Self::HeliumBrowser => "manifest.json",
188            Self::KColorScheme => "chromasync.colors",
189            Self::Micro => "chromasync.micro",
190            Self::Qt5 | Self::Qt6 => "chromasync.conf",
191            Self::VsCode | Self::VsCodeInsiders => "package.json",
192        }
193    }
194}
195
196impl fmt::Display for RenderTarget {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        f.write_str(self.as_str())
199    }
200}
201
202#[derive(
203    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
204)]
205#[serde(rename_all = "snake_case")]
206pub enum PaletteFamilyName {
207    #[default]
208    Primary,
209    Secondary,
210    Tertiary,
211    Neutral,
212    NeutralVariant,
213    Error,
214    Success,
215    Warning,
216    Info,
217}
218
219impl PaletteFamilyName {
220    pub const ALL: [Self; 9] = [
221        Self::Primary,
222        Self::Secondary,
223        Self::Tertiary,
224        Self::Neutral,
225        Self::NeutralVariant,
226        Self::Error,
227        Self::Success,
228        Self::Warning,
229        Self::Info,
230    ];
231
232    pub const fn as_str(self) -> &'static str {
233        match self {
234            Self::Primary => "primary",
235            Self::Secondary => "secondary",
236            Self::Tertiary => "tertiary",
237            Self::Neutral => "neutral",
238            Self::NeutralVariant => "neutral_variant",
239            Self::Error => "error",
240            Self::Success => "success",
241            Self::Warning => "warning",
242            Self::Info => "info",
243        }
244    }
245}
246
247impl fmt::Display for PaletteFamilyName {
248    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249        f.write_str(self.as_str())
250    }
251}
252
253impl FromStr for PaletteFamilyName {
254    type Err = ();
255
256    fn from_str(value: &str) -> Result<Self, Self::Err> {
257        match value {
258            "primary" => Ok(Self::Primary),
259            "secondary" => Ok(Self::Secondary),
260            "tertiary" => Ok(Self::Tertiary),
261            "neutral" => Ok(Self::Neutral),
262            "neutral_variant" => Ok(Self::NeutralVariant),
263            "error" => Ok(Self::Error),
264            "success" => Ok(Self::Success),
265            "warning" => Ok(Self::Warning),
266            "info" => Ok(Self::Info),
267            _ => Err(()),
268        }
269    }
270}
271
272#[derive(
273    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
274)]
275#[serde(rename_all = "snake_case")]
276pub enum SemanticTokenName {
277    #[default]
278    Bg,
279    BgSecondary,
280    Surface,
281    SurfaceElevated,
282    Text,
283    TextMuted,
284    Border,
285    BorderStrong,
286    Accent,
287    AccentHover,
288    AccentActive,
289    AccentFg,
290    Selection,
291    Link,
292    Success,
293    Warning,
294    Error,
295}
296
297impl SemanticTokenName {
298    pub const ALL: [Self; 17] = [
299        Self::Bg,
300        Self::BgSecondary,
301        Self::Surface,
302        Self::SurfaceElevated,
303        Self::Text,
304        Self::TextMuted,
305        Self::Border,
306        Self::BorderStrong,
307        Self::Accent,
308        Self::AccentHover,
309        Self::AccentActive,
310        Self::AccentFg,
311        Self::Selection,
312        Self::Link,
313        Self::Success,
314        Self::Warning,
315        Self::Error,
316    ];
317
318    pub const fn as_str(self) -> &'static str {
319        match self {
320            Self::Bg => "bg",
321            Self::BgSecondary => "bg_secondary",
322            Self::Surface => "surface",
323            Self::SurfaceElevated => "surface_elevated",
324            Self::Text => "text",
325            Self::TextMuted => "text_muted",
326            Self::Border => "border",
327            Self::BorderStrong => "border_strong",
328            Self::Accent => "accent",
329            Self::AccentHover => "accent_hover",
330            Self::AccentActive => "accent_active",
331            Self::AccentFg => "accent_fg",
332            Self::Selection => "selection",
333            Self::Link => "link",
334            Self::Success => "success",
335            Self::Warning => "warning",
336            Self::Error => "error",
337        }
338    }
339}
340
341impl fmt::Display for SemanticTokenName {
342    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343        f.write_str(self.as_str())
344    }
345}
346
347impl FromStr for SemanticTokenName {
348    type Err = ();
349
350    fn from_str(value: &str) -> Result<Self, Self::Err> {
351        match value {
352            "bg" => Ok(Self::Bg),
353            "bg_secondary" => Ok(Self::BgSecondary),
354            "surface" => Ok(Self::Surface),
355            "surface_elevated" => Ok(Self::SurfaceElevated),
356            "text" => Ok(Self::Text),
357            "text_muted" => Ok(Self::TextMuted),
358            "border" => Ok(Self::Border),
359            "border_strong" => Ok(Self::BorderStrong),
360            "accent" => Ok(Self::Accent),
361            "accent_hover" => Ok(Self::AccentHover),
362            "accent_active" => Ok(Self::AccentActive),
363            "accent_fg" => Ok(Self::AccentFg),
364            "selection" => Ok(Self::Selection),
365            "link" => Ok(Self::Link),
366            "success" => Ok(Self::Success),
367            "warning" => Ok(Self::Warning),
368            "error" => Ok(Self::Error),
369            _ => Err(()),
370        }
371    }
372}
373
374#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
375pub struct ToneSample {
376    pub tone: u8,
377    pub hex: HexColor,
378}
379
380#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
381pub struct PaletteFamily {
382    pub name: PaletteFamilyName,
383    pub hue: f32,
384    pub base_chroma: f32,
385    #[serde(default)]
386    pub tones: Vec<ToneSample>,
387    pub dominance: Option<f32>,
388    pub source_region: Option<String>,
389    pub seed_index: Option<usize>,
390}
391
392#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
393pub struct GeneratedPalette {
394    pub seed: HexColor,
395    pub mode: ThemeMode,
396    pub chroma: ChromaStrategy,
397    pub families: BTreeMap<PaletteFamilyName, PaletteFamily>,
398}
399
400#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
401pub struct GenerationRequest {
402    pub seed: Option<String>,
403    pub wallpaper: Option<PathBuf>,
404    pub template: Option<String>,
405    pub mode: ThemeMode,
406    #[serde(default)]
407    pub contrast: ContrastStrategy,
408    #[serde(default)]
409    pub chroma: ChromaStrategy,
410    #[serde(default)]
411    pub targets: Vec<String>,
412    pub output_dir: PathBuf,
413}
414
415#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
416pub struct GeneratedArtifact {
417    pub target: String,
418    pub file_name: String,
419    pub content: String,
420}
421
422#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
423pub struct GenerationContext {
424    pub mode: ThemeMode,
425    pub template_name: String,
426    pub chroma: ChromaStrategy,
427    pub output_dir: PathBuf,
428    pub seed: Option<String>,
429}
430
431#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
432pub struct ThemePack {
433    pub name: String,
434    pub version: String,
435    pub description: Option<String>,
436    pub author: Option<String>,
437    pub license: Option<String>,
438    pub homepage: Option<String>,
439    pub root_dir: PathBuf,
440    #[serde(default)]
441    pub template_dirs: Vec<PathBuf>,
442    #[serde(default)]
443    pub target_dirs: Vec<PathBuf>,
444}
445
446#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
447pub struct TemplateTokenRule {
448    pub family: PaletteFamilyName,
449    pub tone: f32,
450    pub chroma: Option<f32>,
451    pub chroma_scale: Option<f32>,
452}
453
454#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
455pub struct TemplateDefinition {
456    pub name: String,
457    pub mode: ThemeMode,
458    pub description: Option<String>,
459    #[serde(default)]
460    pub tokens: BTreeMap<SemanticTokenName, TemplateTokenRule>,
461}
462
463#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
464pub struct SemanticTokens {
465    pub bg: HexColor,
466    pub bg_secondary: HexColor,
467    pub surface: HexColor,
468    pub surface_elevated: HexColor,
469    pub text: HexColor,
470    pub text_muted: HexColor,
471    pub border: HexColor,
472    pub border_strong: HexColor,
473    pub accent: HexColor,
474    pub accent_hover: HexColor,
475    pub accent_active: HexColor,
476    pub accent_fg: HexColor,
477    pub selection: HexColor,
478    pub link: HexColor,
479    pub success: HexColor,
480    pub warning: HexColor,
481    pub error: HexColor,
482}