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    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}