1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub struct HcColors {
31 pub bg: u32,
32 pub text: u32,
33 pub highlight: u32,
35 pub highlight_text: u32,
36 pub button: u32,
37 pub button_text: u32,
38 pub disabled: u32,
39 pub link: u32,
40}
41
42impl HcColors {
43 pub const BLACK: HcColors = HcColors {
45 bg: 0x000000,
46 text: 0xFFFFFF,
47 highlight: 0x1AEBFF,
48 highlight_text: 0x000000,
49 button: 0x000000,
50 button_text: 0xFFFFFF,
51 disabled: 0x3FF23F,
52 link: 0xFFFF00,
53 };
54 pub const WHITE: HcColors = HcColors {
56 bg: 0xFFFFFF,
57 text: 0x000000,
58 highlight: 0x37006E,
59 highlight_text: 0xFFFFFF,
60 button: 0xFFFFFF,
61 button_text: 0x000000,
62 disabled: 0x600000,
63 link: 0x00009F,
64 };
65}
66
67pub const FONT_HINT_NONE: u8 = 0;
69pub const FONT_HINT_SLIGHT: u8 = 1;
70pub const FONT_HINT_MEDIUM: u8 = 2;
71pub const FONT_HINT_FULL: u8 = 3;
72pub const FONT_RGBA_NONE: u8 = 0;
74pub const FONT_RGBA_RGB: u8 = 1;
75pub const FONT_RGBA_BGR: u8 = 2;
76pub const FONT_RGBA_VRGB: u8 = 3;
77pub const FONT_RGBA_VBGR: u8 = 4;
78
79pub fn font_hint_parse(s: &str) -> Option<u8> {
81 match s.trim().trim_start_matches("hint") {
82 "none" => Some(FONT_HINT_NONE),
83 "slight" => Some(FONT_HINT_SLIGHT),
84 "medium" => Some(FONT_HINT_MEDIUM),
85 "full" => Some(FONT_HINT_FULL),
86 _ => None,
87 }
88}
89
90pub fn font_rgba_parse(s: &str) -> Option<u8> {
92 match s.trim() {
93 "none" | "grayscale" | "greyscale" => Some(FONT_RGBA_NONE),
94 "rgb" => Some(FONT_RGBA_RGB),
95 "bgr" => Some(FONT_RGBA_BGR),
96 "vrgb" => Some(FONT_RGBA_VRGB),
97 "vbgr" => Some(FONT_RGBA_VBGR),
98 _ => None,
99 }
100}
101
102pub const TB_BTN_CLOSE: u8 = 1;
104pub const TB_BTN_MIN: u8 = 2;
105pub const TB_BTN_MAX: u8 = 3;
106pub const TB_ACT_NONE: u8 = 0;
108pub const TB_ACT_MAXIMIZE: u8 = 1;
110pub const TB_ACT_MINIMIZE: u8 = 2;
111pub const TB_ACT_LOWER: u8 = 3;
113pub const TB_ACT_MENU: u8 = 4;
115
116#[derive(Clone, Copy, Debug, PartialEq, Eq)]
118pub struct Titlebar {
119 pub buttons: Option<([u8; 3], [u8; 3])>,
122 pub double: u8,
124 pub middle: u8,
125 pub right: u8,
126}
127
128impl Default for Titlebar {
129 fn default() -> Self {
131 Titlebar { buttons: None, double: TB_ACT_MAXIMIZE, middle: TB_ACT_NONE, right: TB_ACT_MENU }
132 }
133}
134
135pub fn titlebar_layout_parse(s: &str) -> ([u8; 3], [u8; 3]) {
140 let (l, r) = s.split_once(':').unwrap_or((s, ""));
141 let mut seen = 0u8;
142 let mut side = |part: &str| {
143 let mut out = [0u8; 3];
144 let mut n = 0;
145 for name in part.split(',') {
146 let b = match name.trim() {
147 "close" => TB_BTN_CLOSE,
148 "minimize" => TB_BTN_MIN,
149 "maximize" => TB_BTN_MAX,
150 _ => continue,
151 };
152 if seen & (1 << b) == 0 && n < 3 {
153 seen |= 1 << b;
154 out[n] = b;
155 n += 1;
156 }
157 }
158 out
159 };
160 let left = side(l);
161 (left, side(r))
162}
163
164pub fn titlebar_action_parse(s: &str) -> Option<u8> {
168 match s.trim() {
169 "toggle-maximize" | "toggle-maximize-horizontally" | "toggle-maximize-vertically" => Some(TB_ACT_MAXIMIZE),
170 "minimize" => Some(TB_ACT_MINIMIZE),
171 "lower" => Some(TB_ACT_LOWER),
172 "menu" => Some(TB_ACT_MENU),
173 "none" | "toggle-shade" => Some(TB_ACT_NONE),
174 _ => None,
175 }
176}
177
178#[derive(Clone, Copy, Debug, PartialEq)]
180pub struct SysSettings {
181 pub dark: Option<bool>,
183 pub accent: Option<u32>,
185 pub high_contrast: bool,
186 pub contrast_colors: Option<HcColors>,
188 pub reduce_motion: bool,
189 pub text_scale: u32,
191 pub double_click_ms: u32,
192 pub caret_blink_ms: u32,
194 pub font_aa: Option<bool>,
196 pub font_hinting: Option<u8>,
198 pub font_rgba: Option<u8>,
200 pub titlebar: Titlebar,
202}
203
204impl Default for SysSettings {
205 fn default() -> Self {
206 SysSettings {
207 dark: None,
208 accent: None,
209 high_contrast: false,
210 contrast_colors: None,
211 reduce_motion: false,
212 text_scale: 100,
213 double_click_ms: 500,
214 caret_blink_ms: 530,
215 font_aa: None,
216 font_hinting: None,
217 font_rgba: None,
218 titlebar: Titlebar::default(),
219 }
220 }
221}
222
223pub fn sys_settings() -> SysSettings {
225 let mut s = SysSettings::default();
226 #[cfg(any(windows, target_os = "linux"))]
227 crate::sys_settings_os(&mut s);
228 #[cfg(any(target_arch = "wasm32", target_os = "android"))]
230 crate::web::sys_settings_host(&mut s);
231 env_overrides(&mut s);
232 s
233}
234
235pub fn sys_settings_env() -> SysSettings {
237 let mut s = SysSettings::default();
238 env_overrides(&mut s);
239 s
240}
241
242fn env_overrides(s: &mut SysSettings) {
243 let num = |n: &str| crate::env(n).and_then(|v| v.trim().parse::<u32>().ok());
244 match crate::env("jfgui_THEME").as_deref() {
245 Some("dark") => s.dark = Some(true),
246 Some("light") => s.dark = Some(false),
247 _ => {}
248 }
249 if let Some(v) = crate::env("jfgui_ACCENT") {
250 if let Ok(c) = u32::from_str_radix(v.trim().trim_start_matches('#'), 16) {
251 s.accent = Some(c & 0xFF_FFFF);
252 }
253 }
254 match crate::env("jfgui_CONTRAST").as_deref() {
255 Some("high") | Some("1") => s.high_contrast = true,
256 Some("normal") | Some("0") => {
257 s.high_contrast = false;
258 s.contrast_colors = None;
259 }
260 _ => {}
261 }
262 if let Some(v) = num("jfgui_REDUCE_MOTION") {
263 s.reduce_motion = v != 0;
264 }
265 if let Some(v) = num("jfgui_TEXT_SCALE") {
266 s.text_scale = v.clamp(50, 300);
267 }
268 if let Some(v) = num("jfgui_DOUBLE_CLICK_MS") {
269 s.double_click_ms = v.clamp(100, 5000);
270 }
271 if let Some(v) = num("jfgui_CARET_BLINK_MS") {
272 s.caret_blink_ms = v.min(5000);
273 }
274 if let Some(v) = num("jfgui_FONT_AA") {
275 s.font_aa = Some(v != 0);
276 }
277 if let Some(v) = crate::env("jfgui_FONT_HINTING").as_deref().and_then(font_hint_parse) {
278 s.font_hinting = Some(v);
279 }
280 if let Some(v) = crate::env("jfgui_FONT_RGBA").as_deref().and_then(font_rgba_parse) {
281 s.font_rgba = Some(v);
282 }
283 if let Some(v) = crate::env("jfgui_BUTTON_LAYOUT") {
284 s.titlebar.buttons = Some(titlebar_layout_parse(&v));
285 }
286 let act = |n: &str| crate::env(n).as_deref().and_then(titlebar_action_parse);
287 if let Some(a) = act("jfgui_TITLEBAR_DOUBLE_CLICK") {
288 s.titlebar.double = a;
289 }
290 if let Some(a) = act("jfgui_TITLEBAR_MIDDLE_CLICK") {
291 s.titlebar.middle = a;
292 }
293 if let Some(a) = act("jfgui_TITLEBAR_RIGHT_CLICK") {
294 s.titlebar.right = a;
295 }
296}