Skip to main content

fastpack_gui/
preferences.rs

1use std::path::PathBuf;
2
3use fastpack_core::types::config::PackerConfig;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
7pub enum Language {
8    #[default]
9    En,
10    /// French.
11    Fr,
12    /// Spanish.
13    Es,
14    /// German.
15    De,
16    /// Italian.
17    It,
18    /// Portuguese.
19    Pt,
20    /// Japanese.
21    Ja,
22    /// Simplified Chinese.
23    Zh,
24    /// Korean.
25    Ko,
26}
27
28impl Language {
29    /// Return the BCP-47 locale code for this language.
30    pub fn code(self) -> &'static str {
31        match self {
32            Self::En => "en",
33            Self::Fr => "fr",
34            Self::Es => "es",
35            Self::De => "de",
36            Self::It => "it",
37            Self::Pt => "pt",
38            Self::Ja => "ja",
39            Self::Zh => "zh",
40            Self::Ko => "ko",
41        }
42    }
43
44    /// Return the native display name for this language.
45    pub fn display(self) -> &'static str {
46        match self {
47            Self::En => "English",
48            Self::Fr => "Français",
49            Self::Es => "Español",
50            Self::De => "Deutsch",
51            Self::It => "Italiano",
52            Self::Pt => "Português",
53            Self::Ja => "日本語",
54            Self::Zh => "中文(简体)",
55            Self::Ko => "한국어",
56        }
57    }
58
59    /// All supported languages in menu order.
60    pub const ALL: &'static [Language] = &[
61        Self::En,
62        Self::Fr,
63        Self::Es,
64        Self::De,
65        Self::It,
66        Self::Pt,
67        Self::Ja,
68        Self::Zh,
69        Self::Ko,
70    ];
71}
72
73/// A single configurable keyboard shortcut.
74#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75pub struct KeyBind {
76    pub key: String,
77    #[serde(default)]
78    pub ctrl: bool,
79    #[serde(default)]
80    pub shift: bool,
81    #[serde(default)]
82    pub alt: bool,
83}
84
85impl KeyBind {
86    pub fn ctrl(key: &str) -> Self {
87        Self {
88            key: key.to_owned(),
89            ctrl: true,
90            shift: false,
91            alt: false,
92        }
93    }
94
95    pub fn bare(key: &str) -> Self {
96        Self {
97            key: key.to_owned(),
98            ctrl: false,
99            shift: false,
100            alt: false,
101        }
102    }
103
104    pub fn display(&self) -> String {
105        let mut parts: Vec<&str> = Vec::new();
106        if self.ctrl {
107            parts.push("Ctrl");
108        }
109        if self.alt {
110            parts.push("Alt");
111        }
112        if self.shift {
113            parts.push("Shift");
114        }
115        parts.push(self.key.as_str());
116        parts.join("+")
117    }
118
119    pub fn default_new_project() -> Self {
120        Self::ctrl("N")
121    }
122    pub fn default_open_project() -> Self {
123        Self::ctrl("O")
124    }
125    pub fn default_save_project() -> Self {
126        Self::ctrl("S")
127    }
128    pub fn default_export() -> Self {
129        Self::ctrl("P")
130    }
131    pub fn default_anim_preview() -> Self {
132        Self::bare("P")
133    }
134}
135
136/// All configurable keyboard shortcuts.
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct Keybinds {
139    #[serde(default = "KeyBind::default_new_project")]
140    pub new_project: KeyBind,
141    #[serde(default = "KeyBind::default_open_project")]
142    pub open_project: KeyBind,
143    #[serde(default = "KeyBind::default_save_project")]
144    pub save_project: KeyBind,
145    #[serde(default = "KeyBind::default_export")]
146    pub export: KeyBind,
147    #[serde(default = "KeyBind::default_anim_preview")]
148    pub anim_preview: KeyBind,
149}
150
151impl Default for Keybinds {
152    fn default() -> Self {
153        Self {
154            new_project: KeyBind::default_new_project(),
155            open_project: KeyBind::default_open_project(),
156            save_project: KeyBind::default_save_project(),
157            export: KeyBind::default_export(),
158            anim_preview: KeyBind::default_anim_preview(),
159        }
160    }
161}
162
163/// Persistent user preferences saved to `~/.config/FastPack/prefs.toml`.
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct Preferences {
166    /// Enable the dark UI theme.
167    #[serde(default = "default_true")]
168    pub dark_mode: bool,
169    /// Check for a newer release at startup.
170    #[serde(default = "default_true")]
171    pub auto_check_updates: bool,
172    /// Default project configuration applied to new projects.
173    #[serde(default)]
174    pub default_config: PackerConfig,
175    /// UI display language.
176    #[serde(default)]
177    pub language: Language,
178    /// Keyboard shortcuts.
179    #[serde(default)]
180    pub keybinds: Keybinds,
181    /// UI scale multiplier applied on top of system DPI (1.0 = system default).
182    #[serde(default = "default_ui_scale")]
183    pub ui_scale: f32,
184}
185
186fn default_true() -> bool {
187    true
188}
189
190fn default_ui_scale() -> f32 {
191    1.0
192}
193
194impl Default for Preferences {
195    fn default() -> Self {
196        Self {
197            dark_mode: true,
198            auto_check_updates: true,
199            default_config: PackerConfig::default(),
200            language: Language::En,
201            keybinds: Keybinds::default(),
202            ui_scale: 1.0,
203        }
204    }
205}
206
207impl Preferences {
208    /// Load preferences from disk, returning defaults if the file is missing or unreadable.
209    pub fn load() -> Self {
210        prefs_path()
211            .and_then(|p| std::fs::read_to_string(p).ok())
212            .and_then(|text| toml::from_str(&text).ok())
213            .unwrap_or_default()
214    }
215
216    /// Persist the current preferences to disk.
217    pub fn save(&self) {
218        let Some(path) = prefs_path() else { return };
219        if let Some(parent) = path.parent() {
220            let _ = std::fs::create_dir_all(parent);
221        }
222        if let Ok(text) = toml::to_string_pretty(self) {
223            let _ = std::fs::write(path, text.as_bytes());
224        }
225    }
226}
227
228fn prefs_path() -> Option<PathBuf> {
229    dirs::config_dir().map(|d| d.join("FastPack").join("prefs.toml"))
230}