1#![allow(dead_code)]
8
9extern crate alloc;
10use alloc::string::String;
11use alloc::string::ToString;
12
13use crate::kernel::config::{self, KeyboardLayout};
14use crate::kernel::draw::{Color, Screen};
15use crate::kernel::text_box::TextBox;
16use crate::kernel::window_mgr::App;
17use crate::os_lib::json::{self};
18
19static ICON_APPEARANCE: &[u8] = include_bytes!("../../../icons/ic_fluent_color_20_regular.svg");
20static ICON_KEYBOARD: &[u8] = include_bytes!("../../../icons/ic_fluent_keyboard_20_regular.svg");
21static ICON_SYSTEM: &[u8] = include_bytes!("../../../icons/ic_fluent_settings_20_regular.svg");
22static ICON_NETWORK: &[u8] = include_bytes!("../../../icons/ic_fluent_settings_20_regular.svg");
23
24pub struct SettingsApp {
25 selected_layout: KeyboardLayout,
26 selected_theme: String,
27 selected_fonts: [String; 3],
29 selected_sound: bool,
30 repeat_first_delay: u32,
31 repeat_first_interval: u32,
32 repeat_second_count: u32,
33 repeat_second_delay: u32,
34 repeat_second_interval: u32,
35 selected_caps_ctrl: bool,
36 last_win_w: u32,
37 last_win_h: u32,
38 active_tab_idx: usize,
39 selected_auto_dhcp: bool,
40 focused_item_idx: Option<usize>,
41 ssid_box: TextBox,
43 password_box: TextBox,
44 wifi_focused: u8, wifi_status_msg: String,
46 wifi_connecting: bool,
47 status_msg: String,
49 status_is_error: bool,
51 custom_theme: config::ThemeConfig,
53 custom_edit_idx: usize,
54 enable_custom_colors: bool,
55 available_themes: alloc::vec::Vec<alloc::string::String>,
56}
57
58impl Default for SettingsApp {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64impl SettingsApp {
65 pub fn new() -> Self {
66 let current_config = config::get_config();
67
68 let (fd, fi, sc, sd, si) = unsafe {
69 (
70 crate::REPEAT_PARAMS.first_delay_ms,
71 crate::REPEAT_PARAMS.first_interval_ms,
72 crate::REPEAT_PARAMS.second_repeat_count,
73 crate::REPEAT_PARAMS.second_delay_ms,
74 crate::REPEAT_PARAMS.second_interval_ms,
75 )
76 };
77
78 let wifi_status = if crate::kernel::wifi::is_connected() {
79 let ip = crate::kernel::wifi::get_ip();
80 alloc::format!("Connected — {}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3])
81 } else {
82 String::from("Not connected")
83 };
84
85 let mut available_themes = alloc::vec![
86 String::from("default"),
87 String::from("earth"),
88 String::from("dracula"),
89 String::from("nord"),
90 String::from("sakura"),
91 ];
92
93 let fs = crate::kernel::fs::get_fs();
95 let files = fs.list_dir("/os/");
96 for f in files {
97 let name = f.get_filename();
98 if name.ends_with(".json") && name != "/os/config.json" && name != "/os/colorset.json" {
99 if let Some(_pos) = name.rfind('/') {
100 let tname = name.split('/').next_back().unwrap_or("");
101 let tname = tname.trim_end_matches(".json").to_string();
102 if !available_themes.contains(&tname) {
103 available_themes.push(tname);
104 }
105 }
106 }
107 }
108
109 Self {
110 selected_layout: current_config.keyboard_layout,
111 selected_theme: current_config.theme_name.clone(),
112 selected_fonts: current_config.font_names.clone(),
113 selected_sound: current_config.enable_sound,
114 repeat_first_delay: fd,
115 repeat_first_interval: fi,
116 repeat_second_count: sc,
117 repeat_second_delay: sd,
118 repeat_second_interval: si,
119 selected_caps_ctrl: current_config.swap_caps_ctrl,
120 last_win_w: 480,
121 last_win_h: 560,
122 active_tab_idx: 0,
123 selected_auto_dhcp: current_config.auto_dhcp,
124 focused_item_idx: None,
125 ssid_box: TextBox::with_text(¤t_config.wifi_ssid),
126 password_box: TextBox::with_text(¤t_config.wifi_password),
127 wifi_focused: 0,
128 wifi_status_msg: wifi_status,
129 wifi_connecting: false,
130 status_msg: String::new(),
131 status_is_error: false,
132 custom_theme: current_config.custom_theme,
133 custom_edit_idx: 0,
134 enable_custom_colors: current_config.enable_custom_colors,
135 available_themes,
136 }
137 }
138
139 fn get_item_count(&self) -> usize {
140 let tabs = self.tabs();
141 if self.active_tab_idx >= tabs.len() {
142 return 0;
143 }
144 match tabs[self.active_tab_idx].as_str() {
145 "ColorTheme" => {
146 self.available_themes.len() + 1 + 1 }
148 "CustomColor" => {
149 39 + 1 }
151 "FontOrder" => 3 + 1,
152 "Keyboard" => 15,
153 "System" => 5,
154 _ => 3,
155 }
156 }
157
158 fn draw_focus_rect(
159 &self,
160 screen: &Screen,
161 x: u32,
162 y: u32,
163 w: u32,
164 h: u32,
165 is_focused: bool,
166 focus_color: Color,
167 ) {
168 if is_focused {
169 screen.boxfill(
170 x.saturating_sub(2),
171 y.saturating_sub(2),
172 x + w + 2,
173 y + h + 2,
174 focus_color,
175 );
176 }
177 }
178
179 fn draw_text_field(
180 &self,
181 screen: &Screen,
182 x: u32,
183 y: u32,
184 w: u32,
185 tb: &TextBox,
186 focused: bool,
187 placeholder: &str,
188 mask: bool,
189 ) {
190 let t = &config::get_config().theme;
191 let border = if focused { t.active_window_border } else { t.browser_secondary };
192 let bg = if focused { t.browser_bg } else { t.ui_input_bg };
193 screen.boxfill(x, y, x + w, y + 28, Color(bg));
194 screen.boxfill(x, y, x + w, y + 1, Color(border));
196 screen.boxfill(x, y + 27, x + w, y + 28, Color(border));
197 screen.boxfill(x, y, x + 1, y + 28, Color(border));
198 screen.boxfill(x + w - 1, y, x + w, y + 28, Color(border));
199
200 let display = if tb.text.is_empty() && !focused {
201 String::from(placeholder)
202 } else if mask {
203 tb.text.chars().map(|_| '*').collect()
204 } else {
205 tb.text.clone()
206 };
207
208 let text_color = if tb.text.is_empty() && !focused {
209 t.fn_bar_dim_fg
210 } else {
211 t.login_text_fg
212 };
213 screen.draw_string_vector(x + 8, y + 7, &display, Color(text_color), 13);
214
215 if focused {
217 let mut prefix = String::new();
218 if mask {
219 for _ in 0..tb.cursor_pos {
220 prefix.push('*');
221 }
222 } else {
223 prefix = tb.text.chars().take(tb.cursor_pos).collect();
224 }
225 let pw = crate::kernel::vector_font::get_vector_string_width(&prefix, 13);
226 let cursor_x = x + 8 + pw;
227 screen.boxfill(
228 cursor_x,
229 y + 6,
230 cursor_x + 1,
231 y + 22,
232 Color(crate::kernel::ime::cursor_color()),
233 );
234 }
235 }
236
237 fn apply_and_save(&mut self) {
243 match self.try_apply_and_save() {
244 Ok(()) => {
245 self.status_msg = String::from("Saved.");
246 self.status_is_error = false;
247 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
248 crate::info!("[SYS] Settings: Config applied and saved!");
249 }
250 Err(e) => {
251 self.status_msg = alloc::format!("Save failed: {}", e);
252 self.status_is_error = true;
253 crate::error!("[SYS] Settings: failed to apply/save config: {}", e);
254 }
255 }
256 }
257
258 fn try_apply_and_save(&self) -> Result<(), &'static str> {
259 unsafe {
260 crate::REPEAT_PARAMS.first_delay_ms = self.repeat_first_delay;
261 crate::REPEAT_PARAMS.first_interval_ms = self.repeat_first_interval;
262 crate::REPEAT_PARAMS.second_repeat_count = self.repeat_second_count;
263 crate::REPEAT_PARAMS.second_delay_ms = self.repeat_second_delay;
264 crate::REPEAT_PARAMS.second_interval_ms = self.repeat_second_interval;
265 }
266
267 config::save_config_with_wifi(
268 self.selected_layout,
269 &self.selected_theme,
270 &self.selected_fonts,
271 self.selected_sound,
272 self.selected_caps_ctrl,
273 self.selected_auto_dhcp,
274 &self.ssid_box.text,
275 &self.password_box.text,
276 &self.custom_theme,
277 self.enable_custom_colors,
278 )?;
279
280 if self.enable_custom_colors {
281 let mut obj = alloc::collections::BTreeMap::new();
282 obj.insert(String::from("desktop_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.desktop_bg)));
283 obj.insert(String::from("terminal_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.terminal_bg)));
284 obj.insert(String::from("terminal_fg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.terminal_fg)));
285 obj.insert(String::from("active_window_border"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.active_window_border)));
286 obj.insert(String::from("non_active_window_border"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.non_active_window_border)));
287 obj.insert(String::from("window_title_bar"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.window_title_bar)));
288 obj.insert(String::from("window_title_fg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.window_title_fg)));
289 obj.insert(String::from("app_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.app_bg)));
290 obj.insert(String::from("app_fg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.app_fg)));
291 obj.insert(String::from("button_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.button_bg)));
292 obj.insert(String::from("button_fg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.button_fg)));
293 obj.insert(String::from("button_active_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.button_active_bg)));
294 obj.insert(String::from("button_active_fg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.button_active_fg)));
295 obj.insert(String::from("focus_highlight"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.focus_highlight)));
296 obj.insert(String::from("mouse_cursor"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.mouse_cursor)));
297 obj.insert(String::from("mouse_stroke"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.mouse_stroke)));
298 obj.insert(String::from("login_screen_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.login_screen_bg)));
299 obj.insert(String::from("login_dialog_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.login_dialog_bg)));
300 obj.insert(String::from("login_dialog_shadow"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.login_dialog_shadow)));
301 obj.insert(String::from("login_dialog_border"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.login_dialog_border)));
302 obj.insert(String::from("login_text_fg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.login_text_fg)));
303 obj.insert(String::from("login_field_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.login_field_bg)));
304 obj.insert(String::from("login_field_border"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.login_field_border)));
305 obj.insert(String::from("sysmon_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.sysmon_bg)));
306 obj.insert(String::from("sysmon_header_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.sysmon_header_bg)));
307 obj.insert(String::from("sysmon_accent"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.sysmon_accent)));
308 obj.insert(String::from("sysmon_text"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.sysmon_text)));
309 obj.insert(String::from("sysmon_value"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.sysmon_value)));
310 obj.insert(String::from("sysmon_border"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.sysmon_border)));
311 obj.insert(String::from("tab_area_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.tab_area_bg)));
312 obj.insert(String::from("tab_area_border"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.tab_area_border)));
313 obj.insert(String::from("tab_active_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.tab_active_bg)));
314 obj.insert(String::from("tab_active_fg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.tab_active_fg)));
315 obj.insert(String::from("tab_inactive_fg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.tab_inactive_fg)));
316 obj.insert(String::from("fn_bar_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.fn_bar_bg)));
317 obj.insert(String::from("fn_bar_cell_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.fn_bar_cell_bg)));
318 obj.insert(String::from("fn_bar_key_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.fn_bar_key_bg)));
319 obj.insert(String::from("fn_bar_key_fg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.fn_bar_key_fg)));
320 obj.insert(String::from("fn_bar_text_fg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.fn_bar_text_fg)));
321 obj.insert(String::from("fn_bar_dim_fg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.fn_bar_dim_fg)));
322 obj.insert(String::from("fn_bar_border"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.fn_bar_border)));
323 obj.insert(String::from("ime_bg"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.ime_bg)));
324 obj.insert(String::from("ime_border"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.ime_border)));
325 obj.insert(String::from("ime_text"), json::JsonValue::String(alloc::format!("0x{:08X}", self.custom_theme.ime_text)));
326
327 let json_str = json::to_string(&json::JsonValue::Object(obj));
328 let fs = crate::kernel::fs::get_fs();
329 fs.save_file("/os/colorset.json", json_str.as_bytes(), "system,colorset")?;
330 }
331
332 config::reload_and_apply()
333 }
334
335 fn get_color_rgb(c: u32) -> (u8, u8, u8) {
336 (
337 ((c >> 16) & 0xFF) as u8,
338 ((c >> 8) & 0xFF) as u8,
339 (c & 0xFF) as u8,
340 )
341 }
342
343 fn make_color_u32(r: u8, g: u8, b: u8) -> u32 {
344 0xFF000000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
345 }
346
347 fn get_custom_color_by_idx(&self, idx: usize) -> u32 {
348 match idx {
349 0 => self.custom_theme.desktop_bg,
350 1 => self.custom_theme.terminal_bg,
351 2 => self.custom_theme.terminal_fg,
352 3 => self.custom_theme.active_window_border,
353 4 => self.custom_theme.window_title_bar,
354 5 => self.custom_theme.app_bg,
355 6 => self.custom_theme.app_fg,
356 7 => self.custom_theme.non_active_window_border,
357 8 => self.custom_theme.mouse_cursor,
358 9 => self.custom_theme.login_screen_bg,
359 10 => self.custom_theme.sysmon_bg,
360 11 => self.custom_theme.tab_area_bg,
361 _ => self.custom_theme.fn_bar_bg,
362 }
363 }
364
365 fn set_custom_color_by_idx(&mut self, idx: usize, color: u32) {
366 match idx {
367 0 => self.custom_theme.desktop_bg = color,
368 1 => self.custom_theme.terminal_bg = color,
369 2 => self.custom_theme.terminal_fg = color,
370 3 => self.custom_theme.active_window_border = color,
371 4 => self.custom_theme.window_title_bar = color,
372 5 => self.custom_theme.app_bg = color,
373 6 => self.custom_theme.app_fg = color,
374 7 => self.custom_theme.non_active_window_border = color,
375 8 => self.custom_theme.mouse_cursor = color,
376 9 => self.custom_theme.login_screen_bg = color,
377 10 => self.custom_theme.sysmon_bg = color,
378 11 => self.custom_theme.tab_area_bg = color,
379 _ => self.custom_theme.fn_bar_bg = color,
380 }
381 }
382
383 fn adjust_channel(val: u8, dir: i32) -> u8 {
384 let step = 16i32;
385 let new_val = (val as i32 + dir * step).clamp(0, 255);
386 new_val as u8
387 }
388
389 fn get_custom_color_name(idx: usize) -> &'static str {
390 static CUSTOM_COLOR_NAMES: &[&str] = &[
391 "Desktop BG",
392 "Terminal BG",
393 "Terminal FG",
394 "Active Border",
395 "Title Bar",
396 "App BG",
397 "App FG",
398 "Inactive Border",
399 "Mouse Cursor",
400 "Login Screen BG",
401 "System Monitor BG",
402 "Tab Area BG",
403 "Function Bar BG",
404 ];
405 if idx < CUSTOM_COLOR_NAMES.len() {
406 CUSTOM_COLOR_NAMES[idx]
407 } else {
408 ""
409 }
410 }
411
412 fn get_theme_preview_colors(name: &str) -> (u32, u32, u32) {
413 let fs = crate::kernel::fs::get_fs();
414 let path = alloc::format!("/os/{}.json", name);
415 let mut desktop_bg = 0xFF2EAE3D; let mut terminal_bg = 0xFFF9F5EB;
417 let mut active_border = 0xFFE14D2A;
418
419 if let Some((_, content)) = fs.read_file(&path) {
420 if let Ok(content_str) = core::str::from_utf8(&content) {
421 if let Ok(json::JsonValue::Object(obj)) = json::parse(content_str) {
422 if let Some(v) = obj.get("desktop_bg") {
423 if let Some(c) = config::parse_hex_color(v) { desktop_bg = c; }
424 }
425 if let Some(v) = obj.get("terminal_bg") {
426 if let Some(c) = config::parse_hex_color(v) { terminal_bg = c; }
427 }
428 if let Some(v) = obj.get("active_window_border") {
429 if let Some(c) = config::parse_hex_color(v) { active_border = c; }
430 }
431 }
432 }
433 }
434 (desktop_bg, terminal_bg, active_border)
435 }
436
437 fn start_wifi_connect(&mut self) {
438 let ssid = self.ssid_box.text.clone();
439 let pass = self.password_box.text.clone();
440 if ssid.is_empty() {
441 self.wifi_status_msg = String::from("Error: SSID is empty");
442 return;
443 }
444 self.wifi_status_msg = String::from("Connecting...");
445 self.wifi_connecting = true;
446 let ok = crate::kernel::wifi::connect(&ssid, &pass);
447 if ok {
448 let ip = crate::kernel::wifi::get_ip();
449 self.wifi_status_msg =
450 alloc::format!("Connected — {}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]);
451 self.apply_and_save();
452 } else {
453 self.wifi_status_msg = String::from("Failed to connect");
454 }
455 self.wifi_connecting = false;
456 }
457
458 fn cycle_font(&mut self, slot: usize, dir: i32) {
459 if slot >= 3 {
460 return;
461 }
462 let list = crate::kernel::vector_font::AVAILABLE_FONTS;
463 let n = list.len() as i32;
464 if n == 0 {
465 return;
466 }
467 let cur = list
468 .iter()
469 .position(|f| *f == self.selected_fonts[slot])
470 .unwrap_or(0) as i32;
471 let next = ((cur + dir) % n + n) % n;
472 self.selected_fonts[slot] = String::from(list[next as usize]);
473 }
474
475 fn apply_theme_colorset(&mut self, theme_name: &str) -> Result<(), &'static str> {
480 let fs = crate::kernel::fs::get_fs();
481 let src_path = alloc::format!("/os/{}.json", theme_name);
482 let (_, data) = fs
483 .read_file(&src_path)
484 .ok_or("theme colorset file not found")?;
485 fs.save_file("/os/colorset.json", &data, "system,colorset")?;
486 config::reload_and_apply()
487 }
488
489 fn select_theme(&mut self, theme_name: &str) {
491 self.selected_theme = String::from(theme_name);
492 self.enable_custom_colors = false;
493
494 match self.apply_theme_colorset(theme_name) {
495 Ok(()) => {
496 self.status_msg = String::new();
497 self.status_is_error = false;
498 }
499 Err(e) => {
500 self.status_msg = alloc::format!("Theme change failed: {}", e);
501 self.status_is_error = true;
502 crate::error!("[SYS] Settings: theme '{}' not applied: {}", theme_name, e);
503 }
504 }
505 self.custom_theme = config::get_config().custom_theme;
506
507 let mut wm = crate::kernel::window_mgr::get_instance();
508 wm.dirty = true;
509 }
510
511 fn toggle_custom_colors(&mut self) {
513 self.enable_custom_colors = !self.enable_custom_colors;
514 unsafe {
515 if let Some(ref mut conf) = config::GLOBAL_CONFIG {
516 conf.enable_custom_colors = self.enable_custom_colors;
517 if self.enable_custom_colors {
518 conf.theme = self.custom_theme;
519 }
520 }
521 }
522 if !self.enable_custom_colors {
523 let name = self.selected_theme.clone();
525 if let Err(e) = self.apply_theme_colorset(&name) {
526 self.status_msg = alloc::format!("Theme restore failed: {}", e);
527 self.status_is_error = true;
528 crate::error!("[SYS] Settings: theme '{}' not restored: {}", name, e);
529 }
530 }
531 let mut wm = crate::kernel::window_mgr::get_instance();
532 wm.dirty = true;
533 }
534
535 fn handle_action(&mut self, idx: usize) {
536 let tabs = self.tabs();
537 if self.active_tab_idx >= tabs.len() {
538 return;
539 }
540 match tabs[self.active_tab_idx].as_str() {
541 "ColorTheme" => {
542 let n_themes = self.available_themes.len();
543 if idx < n_themes {
544 let theme_name = self.available_themes[idx].clone();
545 self.select_theme(&theme_name);
546 } else if idx == n_themes {
547 self.toggle_custom_colors();
548 } else {
549 self.apply_and_save();
550 }
551 }
552 "CustomColor" => {
553 if idx < 39 {
554 let color_idx = idx / 3;
555 let chan_idx = idx % 3;
556 let color_val = self.get_custom_color_by_idx(color_idx);
557 let (mut r, mut g, mut b) = Self::get_color_rgb(color_val);
558 match chan_idx {
559 0 => r = Self::adjust_channel(r, 1),
560 1 => g = Self::adjust_channel(g, 1),
561 _ => b = Self::adjust_channel(b, 1),
562 }
563 let new_c = Self::make_color_u32(r, g, b);
564 self.set_custom_color_by_idx(color_idx, new_c);
565 if self.enable_custom_colors {
566 unsafe {
567 if let Some(ref mut conf) = config::GLOBAL_CONFIG {
568 conf.custom_theme = self.custom_theme;
569 conf.theme = self.custom_theme;
570 }
571 }
572 let mut wm = crate::kernel::window_mgr::get_instance();
573 wm.dirty = true;
574 }
575 } else {
576 self.apply_and_save();
577 }
578 }
579 "FontOrder" => {
580 if idx < 3 {
581 self.cycle_font(idx, 1);
582 } else {
583 self.apply_and_save();
584 }
585 }
586 "Keyboard" => match idx {
587 0 => self.selected_layout = KeyboardLayout::Jis,
588 1 => self.selected_layout = KeyboardLayout::Us,
589 2 => self.repeat_first_delay = self.repeat_first_delay.saturating_sub(50).max(50),
590 3 => self.repeat_first_delay = self.repeat_first_delay.saturating_add(50).min(2000),
591 4 => {
592 self.repeat_first_interval = self.repeat_first_interval.saturating_sub(5).max(5)
593 }
594 5 => {
595 self.repeat_first_interval =
596 self.repeat_first_interval.saturating_add(5).min(500)
597 }
598 6 => self.repeat_second_count = self.repeat_second_count.saturating_sub(1),
599 7 => self.repeat_second_count = self.repeat_second_count.saturating_add(1).min(100),
600 8 => self.repeat_second_delay = self.repeat_second_delay.saturating_sub(50).max(50),
601 9 => {
602 self.repeat_second_delay = self.repeat_second_delay.saturating_add(50).min(2000)
603 }
604 10 => {
605 self.repeat_second_interval =
606 self.repeat_second_interval.saturating_sub(5).max(5)
607 }
608 11 => {
609 self.repeat_second_interval =
610 self.repeat_second_interval.saturating_add(5).min(500)
611 }
612 12 => self.selected_caps_ctrl = true,
613 13 => self.selected_caps_ctrl = false,
614 14 => self.apply_and_save(),
615 _ => {}
616 },
617 "System" => match idx {
618 0 => self.selected_sound = true,
619 1 => self.selected_sound = false,
620 2 => self.selected_auto_dhcp = true,
621 3 => self.selected_auto_dhcp = false,
622 4 => self.apply_and_save(),
623 _ => {}
624 },
625 _ => {}
626 }
627 }
628}
629
630impl App for SettingsApp {
631 fn name(&self) -> &str {
632 "settings"
633 }
634
635 fn tabs(&self) -> alloc::vec::Vec<alloc::string::String> {
636 if self.enable_custom_colors {
637 alloc::vec![
638 String::from("ColorTheme"),
639 String::from("CustomColor"),
640 String::from("FontOrder"),
641 String::from("Keyboard"),
642 String::from("System"),
643 String::from("Network"),
644 ]
645 } else {
646 alloc::vec![
647 String::from("ColorTheme"),
648 String::from("FontOrder"),
649 String::from("Keyboard"),
650 String::from("System"),
651 String::from("Network"),
652 ]
653 }
654 }
655
656 fn active_tab(&self) -> usize {
657 self.active_tab_idx
658 }
659
660 fn switch_tab(&mut self, idx: usize) {
661 let max_tabs = self.tabs().len();
662 self.active_tab_idx = if idx < max_tabs { idx } else { 0 };
663 self.focused_item_idx = None;
664 self.wifi_focused = 0;
665 }
666
667 fn draw(&mut self, screen: &Screen, win_x: u32, win_y: u32, win_w: u32, win_h: u32) {
668 self.last_win_w = win_w;
669 self.last_win_h = win_h;
670
671 let theme = &config::get_config().theme;
672 let bg = Color(theme.app_bg);
673 let fg = Color(theme.app_fg);
674 let btn_bg = Color(theme.button_bg);
675 let btn_fg = Color(theme.button_fg);
676 let btn_act_bg = Color(theme.button_active_bg);
677 let btn_act_fg = Color(theme.button_active_fg);
678 let focus_c = Color(theme.focus_highlight);
679
680 screen.boxfill(win_x, win_y, win_x + win_w, win_y + win_h, bg);
681
682 let tabs = self.tabs();
683 let tab_name = if self.active_tab_idx < tabs.len() {
684 tabs[self.active_tab_idx].as_str()
685 } else {
686 ""
687 };
688
689 let (tab_title, icon) = match tab_name {
690 "ColorTheme" => ("COLOR THEME SETTINGS", ICON_APPEARANCE),
691 "CustomColor" => ("CUSTOM COLOR CONFIG", ICON_APPEARANCE),
692 "FontOrder" => ("FONT FALLBACK ORDER", ICON_APPEARANCE),
693 "Keyboard" => ("KEYBOARD SETTINGS", ICON_KEYBOARD),
694 "System" => ("SYSTEM SETTINGS", ICON_SYSTEM),
695 _ => ("NETWORK SETTINGS", ICON_NETWORK),
696 };
697 screen.draw_svg_icon(win_x + 15, win_y + 12, icon, 24, fg);
698 screen.draw_string_vector(win_x + 45, win_y + 15, tab_title, fg, 18);
699 screen.boxfill(
700 win_x + 15,
701 win_y + 40,
702 win_x + win_w - 15,
703 win_y + 41,
704 Color(theme.fn_bar_border),
705 );
706
707 if tab_name == "ColorTheme" {
708 screen.draw_string_vector(win_x + 15, win_y + 55, "Color Theme Presets (JSON):", fg, 15);
710
711 let n_themes = self.available_themes.len();
712 for i in 0..n_themes {
713 let card_x = win_x + 15 + (i as u32 % 2) * 205;
714 let card_y = win_y + 80 + (i as u32 / 2) * 75;
715 let is_focused = self.focused_item_idx == Some(i);
716
717 let theme_name = &self.available_themes[i];
718 let is_selected = !self.enable_custom_colors && self.selected_theme == *theme_name;
719 self.draw_focus_rect(screen, card_x, card_y, 195, 65, is_focused, focus_c);
720 let border_color = if is_selected {
721 btn_act_bg
722 } else {
723 Color(theme.fn_bar_border)
724 };
725 screen.boxfill(card_x, card_y, card_x + 195, card_y + 65, border_color);
726 screen.boxfill(
727 card_x + 2,
728 card_y + 2,
729 card_x + 193,
730 card_y + 63,
731 Color(theme.login_dialog_bg),
732 );
733
734 screen.draw_string_vector(card_x + 10, card_y + 8, theme_name, Color(theme.app_fg), 13);
735
736 let (dbg, tbg, aborder) = Self::get_theme_preview_colors(theme_name);
738 screen.boxfill(card_x + 10, card_y + 32, card_x + 45, card_y + 53, Color(dbg));
739 screen.boxfill(card_x + 55, card_y + 32, card_x + 90, card_y + 53, Color(tbg));
740 screen.boxfill(card_x + 100, card_y + 32, card_x + 135, card_y + 53, Color(aborder));
741 }
742
743 let check_y = win_y + 80 + (n_themes as u32).div_ceil(2) * 75 + 10;
745 let check_idx = n_themes;
746 let is_check_focused = self.focused_item_idx == Some(check_idx);
747 self.draw_focus_rect(
748 screen,
749 win_x + 15,
750 check_y,
751 280,
752 28,
753 is_check_focused,
754 focus_c,
755 );
756
757 screen.boxfill(
758 win_x + 15,
759 check_y + 4,
760 win_x + 35,
761 check_y + 24,
762 Color(theme.fn_bar_border),
763 );
764 if self.enable_custom_colors {
765 screen.boxfill(
766 win_x + 17,
767 check_y + 6,
768 win_x + 33,
769 check_y + 22,
770 Color(theme.success_fg),
771 );
772 } else {
773 screen.boxfill(
774 win_x + 17,
775 check_y + 6,
776 win_x + 33,
777 check_y + 22,
778 Color(theme.login_dialog_bg),
779 );
780 }
781 screen.draw_string_vector(win_x + 45, check_y + 6, "Enable Custom Colors / Edit Colors", fg, 13);
782 } else if tab_name == "CustomColor" {
783 screen.draw_string_vector(win_x + 15, win_y + 55, "Custom Colors (2 Columns List):", fg, 14);
785
786 for i in 0..13usize {
787 let is_right_col = i >= 7;
788 let col_idx = if is_right_col { i - 7 } else { i };
789 let col_x_offset = if is_right_col { 390u32 } else { 0u32 };
790 let row_y = win_y + 80 + col_idx as u32 * 35;
791
792 let color_name = Self::get_custom_color_name(i);
793 screen.draw_string_vector(win_x + 15 + col_x_offset, row_y + 6, color_name, fg, 11);
794
795 let color_val = self.get_custom_color_by_idx(i);
796 let (r, g, b) = Self::get_color_rgb(color_val);
797 let channels = [r, g, b];
798
799 for chan_idx in 0..3usize {
800 let cx = win_x + 140 + col_x_offset + (chan_idx as u32) * 72;
801 let focus_idx = i * 3 + chan_idx;
802 let is_focused = self.focused_item_idx == Some(focus_idx);
803
804 screen.boxfill(cx, row_y, cx + 14, row_y + 24, btn_bg);
806 screen.draw_string_vector(cx + 4, row_y + 4, "<", btn_fg, 12);
807
808 self.draw_focus_rect(screen, cx + 18, row_y, 32, 24, is_focused, focus_c);
810 screen.boxfill(cx + 18, row_y, cx + 50, row_y + 24, btn_act_bg);
811 let val_str = alloc::format!("{:02X}", channels[chan_idx]);
812 screen.draw_string_vector(cx + 20, row_y + 5, &val_str, btn_act_fg, 10);
813
814 screen.boxfill(cx + 54, row_y, cx + 68, row_y + 24, btn_bg);
816 screen.draw_string_vector(cx + 59, row_y + 4, ">", btn_fg, 12);
817 }
818
819 let px = win_x + 358 + col_x_offset;
821 screen.boxfill(px - 1, row_y - 1, px + 21, row_y + 25, Color(theme.fn_bar_border));
822 screen.boxfill(px, row_y, px + 20, row_y + 24, Color(color_val));
823 }
824 } else if tab_name == "FontOrder" {
825 screen.draw_string_vector(win_x + 15, win_y + 55, "Font fallback order:", fg, 16);
827 let labels = ["1st", "2nd", "3rd"];
828 for slot in 0..3usize {
829 let by = win_y + 85 + slot as u32 * 36;
830 let is_focused = self.focused_item_idx == Some(slot);
831 screen.draw_string_vector(win_x + 15, by + 6, labels[slot], fg, 14);
832 screen.boxfill(win_x + 48, by, win_x + 74, by + 28, btn_bg);
833 screen.draw_string_vector(win_x + 56, by + 5, "<", btn_fg, 16);
834 self.draw_focus_rect(screen, win_x + 80, by, 224, 28, is_focused, focus_c);
835 screen.boxfill(win_x + 80, by, win_x + 304, by + 28, btn_act_bg);
836 screen.draw_string_vector(
837 win_x + 88,
838 by + 6,
839 &self.selected_fonts[slot],
840 btn_act_fg,
841 14,
842 );
843 screen.boxfill(win_x + 310, by, win_x + 336, by + 28, btn_bg);
844 screen.draw_string_vector(win_x + 318, by + 5, ">", btn_fg, 16);
845 }
846 } else if tab_name == "Keyboard" {
847 screen.draw_string_vector(win_x + 15, win_y + 55, "Keyboard Layout:", fg, 16);
849
850 let is_jis = self.selected_layout == KeyboardLayout::Jis;
851 let (jis_bg, jis_fg) = if is_jis {
852 (btn_act_bg, btn_act_fg)
853 } else {
854 (btn_bg, btn_fg)
855 };
856 let (us_bg, us_fg) = if !is_jis {
857 (btn_act_bg, btn_act_fg)
858 } else {
859 (btn_bg, btn_fg)
860 };
861
862 self.draw_focus_rect(
863 screen,
864 win_x + 160,
865 win_y + 50,
866 90,
867 26,
868 self.focused_item_idx == Some(0),
869 focus_c,
870 );
871 screen.boxfill(win_x + 160, win_y + 50, win_x + 250, win_y + 76, jis_bg);
872 screen.draw_string_vector(win_x + 185, win_y + 55, "JIS", jis_fg, 14);
873
874 self.draw_focus_rect(
875 screen,
876 win_x + 265,
877 win_y + 50,
878 90,
879 26,
880 self.focused_item_idx == Some(1),
881 focus_c,
882 );
883 screen.boxfill(win_x + 265, win_y + 50, win_x + 355, win_y + 76, us_bg);
884 screen.draw_string_vector(win_x + 295, win_y + 55, "US", us_fg, 14);
885
886 let repeat_items = [
887 (
888 "1st Delay:",
889 self.repeat_first_delay,
890 "ms",
891 2usize,
892 win_y + 90,
893 ),
894 (
895 "1st Interval:",
896 self.repeat_first_interval,
897 "ms",
898 4,
899 win_y + 130,
900 ),
901 (
902 "2nd Count:",
903 self.repeat_second_count,
904 "times",
905 6,
906 win_y + 170,
907 ),
908 ("2nd Delay:", self.repeat_second_delay, "ms", 8, win_y + 210),
909 (
910 "2nd Interval:",
911 self.repeat_second_interval,
912 "ms",
913 10,
914 win_y + 250,
915 ),
916 ];
917 for (label, val, unit, base_idx, by) in repeat_items.iter() {
918 screen.draw_string_vector(win_x + 15, *by + 5, label, fg, 16);
919 let is_minus_focused = self.focused_item_idx == Some(*base_idx);
920 self.draw_focus_rect(screen, win_x + 160, *by, 30, 26, is_minus_focused, focus_c);
921 screen.boxfill(win_x + 160, *by, win_x + 190, *by + 26, btn_bg);
922 screen.draw_string_vector(win_x + 170, *by + 5, "-", btn_fg, 16);
923 let val_str = alloc::format!("{} {}", val, unit);
924 screen.draw_string_vector(win_x + 200, *by + 5, &val_str, fg, 16);
925 let is_plus_focused = self.focused_item_idx == Some(*base_idx + 1);
926 self.draw_focus_rect(screen, win_x + 280, *by, 30, 26, is_plus_focused, focus_c);
927 screen.boxfill(win_x + 280, *by, win_x + 310, *by + 26, btn_bg);
928 screen.draw_string_vector(win_x + 290, *by + 5, "+", btn_fg, 16);
929 }
930
931 screen.draw_string_vector(win_x + 15, win_y + 295, "CapsLock as Ctrl:", fg, 16);
932 let (on_bg, on_fg) = if self.selected_caps_ctrl {
933 (btn_act_bg, btn_act_fg)
934 } else {
935 (btn_bg, btn_fg)
936 };
937 let (off_bg, off_fg) = if !self.selected_caps_ctrl {
938 (Color(theme.error_fg), btn_act_fg)
939 } else {
940 (btn_bg, btn_fg)
941 };
942 self.draw_focus_rect(
943 screen,
944 win_x + 160,
945 win_y + 290,
946 70,
947 26,
948 self.focused_item_idx == Some(12),
949 focus_c,
950 );
951 screen.boxfill(win_x + 160, win_y + 290, win_x + 230, win_y + 316, on_bg);
952 screen.draw_string_vector(win_x + 185, win_y + 295, "ON", on_fg, 14);
953 self.draw_focus_rect(
954 screen,
955 win_x + 245,
956 win_y + 290,
957 70,
958 26,
959 self.focused_item_idx == Some(13),
960 focus_c,
961 );
962 screen.boxfill(win_x + 245, win_y + 290, win_x + 315, win_y + 316, off_bg);
963 screen.draw_string_vector(win_x + 268, win_y + 295, "OFF", off_fg, 14);
964 } else if tab_name == "System" {
965 screen.draw_string_vector(win_x + 15, win_y + 55, "System Sound:", fg, 16);
967 let (on_bg, on_fg) = if self.selected_sound {
968 (Color(theme.success_fg), btn_act_fg)
969 } else {
970 (btn_bg, btn_fg)
971 };
972 let (off_bg, off_fg) = if !self.selected_sound {
973 (Color(theme.error_fg), btn_act_fg)
974 } else {
975 (btn_bg, btn_fg)
976 };
977 self.draw_focus_rect(
978 screen,
979 win_x + 160,
980 win_y + 50,
981 70,
982 26,
983 self.focused_item_idx == Some(0),
984 focus_c,
985 );
986 screen.boxfill(win_x + 160, win_y + 50, win_x + 230, win_y + 76, on_bg);
987 screen.draw_string_vector(win_x + 185, win_y + 55, "ON", on_fg, 14);
988 self.draw_focus_rect(
989 screen,
990 win_x + 245,
991 win_y + 50,
992 70,
993 26,
994 self.focused_item_idx == Some(1),
995 focus_c,
996 );
997 screen.boxfill(win_x + 245, win_y + 50, win_x + 315, win_y + 76, off_bg);
998 screen.draw_string_vector(win_x + 268, win_y + 55, "OFF", off_fg, 14);
999
1000 screen.draw_string_vector(win_x + 15, win_y + 95, "Auto DHCP on Boot:", fg, 16);
1001 let (dhcp_on_bg, dhcp_on_fg) = if self.selected_auto_dhcp {
1002 (Color(theme.success_fg), btn_act_fg)
1003 } else {
1004 (btn_bg, btn_fg)
1005 };
1006 let (dhcp_off_bg, dhcp_off_fg) = if !self.selected_auto_dhcp {
1007 (Color(theme.error_fg), btn_act_fg)
1008 } else {
1009 (btn_bg, btn_fg)
1010 };
1011 self.draw_focus_rect(
1012 screen,
1013 win_x + 160,
1014 win_y + 90,
1015 70,
1016 26,
1017 self.focused_item_idx == Some(2),
1018 focus_c,
1019 );
1020 screen.boxfill(
1021 win_x + 160,
1022 win_y + 90,
1023 win_x + 230,
1024 win_y + 116,
1025 dhcp_on_bg,
1026 );
1027 screen.draw_string_vector(win_x + 185, win_y + 95, "ON", dhcp_on_fg, 14);
1028 self.draw_focus_rect(
1029 screen,
1030 win_x + 245,
1031 win_y + 90,
1032 70,
1033 26,
1034 self.focused_item_idx == Some(3),
1035 focus_c,
1036 );
1037 screen.boxfill(
1038 win_x + 245,
1039 win_y + 90,
1040 win_x + 315,
1041 win_y + 116,
1042 dhcp_off_bg,
1043 );
1044 screen.draw_string_vector(win_x + 268, win_y + 95, "OFF", dhcp_off_fg, 14);
1045 } else {
1046 let fw = win_w.saturating_sub(30);
1048
1049 let connected = crate::kernel::wifi::is_connected();
1050 let status_color = if connected {
1051 Color(theme.success_fg)
1052 } else {
1053 Color(theme.error_fg)
1054 };
1055 let status_dot = if connected {
1056 "● Connected"
1057 } else {
1058 "● Not connected"
1059 };
1060 screen.draw_string_vector(win_x + 15, win_y + 55, status_dot, status_color, 15);
1061
1062 if crate::kernel::wifi::is_connected() {
1063 let ip = crate::kernel::wifi::get_ip();
1064 let ip_str = alloc::format!("IP: {}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]);
1065 screen.draw_string_vector(win_x + 15, win_y + 78, &ip_str, fg, 14);
1066 let ssid = crate::kernel::wifi::get_ssid();
1067 let ssid_str = alloc::format!("SSID: {}", ssid);
1068 screen.draw_string_vector(win_x + 15, win_y + 98, &ssid_str, fg, 14);
1069 }
1070
1071 if !self.wifi_status_msg.is_empty() {
1072 screen.draw_string_vector(
1073 win_x + 15,
1074 win_y + 122,
1075 &self.wifi_status_msg,
1076 Color(theme.warning_fg),
1077 13,
1078 );
1079 }
1080
1081 screen.draw_string_vector(win_x + 15, win_y + 152, "SSID:", fg, 14);
1082 let ssid_focused = self.wifi_focused == 1;
1083 self.draw_text_field(
1084 screen,
1085 win_x + 15,
1086 win_y + 170,
1087 fw,
1088 &self.ssid_box,
1089 ssid_focused,
1090 "Enter Wi-Fi network name",
1091 false,
1092 );
1093
1094 screen.draw_string_vector(win_x + 15, win_y + 212, "Password:", fg, 14);
1095 let pass_focused = self.wifi_focused == 2;
1096 self.draw_text_field(
1097 screen,
1098 win_x + 15,
1099 win_y + 230,
1100 fw,
1101 &self.password_box,
1102 pass_focused,
1103 "Enter password",
1104 true,
1105 );
1106
1107 let btn_x0 = win_x + 15;
1108 let btn_y0 = win_y + 278;
1109 let btn_w = 160u32;
1110 let btn_bg_c = if self.wifi_connecting {
1111 Color(theme.fn_bar_dim_fg)
1112 } else {
1113 Color(theme.success_fg)
1114 };
1115 screen.boxfill(btn_x0, btn_y0, btn_x0 + btn_w, btn_y0 + 34, btn_bg_c);
1116 screen.draw_string_vector(
1117 btn_x0 + 22,
1118 btn_y0 + 9,
1119 "Connect / Save",
1120 btn_act_fg,
1121 14,
1122 );
1123
1124 if connected {
1125 let disc_x = btn_x0 + btn_w + 12;
1126 screen.boxfill(disc_x, btn_y0, disc_x + 130, btn_y0 + 34, Color(theme.error_fg));
1127 screen.draw_string_vector(
1128 disc_x + 10,
1129 btn_y0 + 9,
1130 "Disconnect",
1131 btn_act_fg,
1132 14,
1133 );
1134 }
1135
1136 screen.draw_string_vector(
1137 win_x + 15,
1138 win_y + 325,
1139 "Click a field and type to edit.",
1140 Color(theme.fn_bar_dim_fg),
1141 12,
1142 );
1143 }
1144
1145 if !self.status_msg.is_empty() {
1147 let color = if self.status_is_error {
1148 Color(theme.error_fg)
1149 } else {
1150 Color(theme.success_fg)
1151 };
1152 screen.draw_string_vector(win_x + 15, win_y + win_h - 66, &self.status_msg, color, 13);
1153 }
1154
1155 if tab_name != "Network" {
1157 let btn_x0 = win_x + (win_w - 200) / 2;
1158 let btn_y0 = win_y + win_h - 45;
1159 let apply_idx = self.get_item_count() - 1;
1160 self.draw_focus_rect(
1161 screen,
1162 btn_x0,
1163 btn_y0,
1164 200,
1165 32,
1166 self.focused_item_idx == Some(apply_idx),
1167 focus_c,
1168 );
1169 screen.boxfill(btn_x0, btn_y0, btn_x0 + 200, btn_y0 + 32, Color(theme.success_fg));
1170 screen.draw_string_vector(
1171 btn_x0 + 42,
1172 btn_y0 + 8,
1173 "Apply & Save",
1174 btn_act_fg,
1175 15,
1176 );
1177 }
1178 }
1179
1180 fn on_mouse(
1181 &mut self,
1182 local_x: i32,
1183 local_y: i32,
1184 btn_left: bool,
1185 _btn_right: bool,
1186 _wheel: i32,
1187 ) {
1188 if !btn_left {
1189 return;
1190 }
1191 let x = local_x;
1192 let y = local_y;
1193 let ww = self.last_win_w as i32;
1194 let wh = self.last_win_h as i32;
1195
1196 let tabs = self.tabs();
1197 if self.active_tab_idx >= tabs.len() {
1198 return;
1199 }
1200 let tab_name = tabs[self.active_tab_idx].as_str();
1201
1202 match tab_name {
1203 "ColorTheme" => {
1204 let n_themes = self.available_themes.len();
1205 for i in 0..n_themes {
1206 let cx = 15 + (i as i32 % 2) * 205;
1207 let cy = 80 + (i as i32 / 2) * 75;
1208 if x >= cx && x < cx + 195 && y >= cy && y < cy + 65 {
1209 let theme_name = self.available_themes[i].clone();
1210 self.select_theme(&theme_name);
1211 return;
1212 }
1213 }
1214
1215 let check_y = 80 + ((n_themes as i32 + 1) / 2) * 75 + 10;
1217 if (15..350).contains(&x) && (check_y..check_y + 28).contains(&y) {
1218 self.toggle_custom_colors();
1219 return;
1220 }
1221
1222 let bx = (ww - 200) / 2;
1223 let by = wh - 45;
1224 if x >= bx && x < bx + 200 && y >= by && y < by + 32 {
1225 self.apply_and_save();
1226 }
1227 }
1228 "CustomColor" => {
1229 for i in 0..13usize {
1230 let is_right_col = i >= 7;
1231 let col_idx = if is_right_col { i - 7 } else { i };
1232 let col_x_offset = if is_right_col { 390 } else { 0 };
1233 let row_y = 80 + col_idx as i32 * 35;
1234
1235 if y >= row_y && y < row_y + 26 {
1236 let color_val = self.get_custom_color_by_idx(i);
1237 let (mut r, mut g, mut b) = Self::get_color_rgb(color_val);
1238
1239 for chan_idx in 0..3usize {
1240 let cx = 140 + col_x_offset + (chan_idx as i32) * 72;
1241 if x >= cx && x < cx + 14 {
1243 match chan_idx {
1244 0 => r = Self::adjust_channel(r, -1),
1245 1 => g = Self::adjust_channel(g, -1),
1246 _ => b = Self::adjust_channel(b, -1),
1247 }
1248 let new_c = Self::make_color_u32(r, g, b);
1249 self.set_custom_color_by_idx(i, new_c);
1250 if self.enable_custom_colors {
1251 unsafe {
1252 if let Some(ref mut conf) = config::GLOBAL_CONFIG {
1253 conf.custom_theme = self.custom_theme;
1254 conf.theme = self.custom_theme;
1255 }
1256 }
1257 let mut wm = crate::kernel::window_mgr::get_instance();
1258 wm.dirty = true;
1259 }
1260 return;
1261 }
1262 if x >= cx + 54 && x < cx + 68 {
1264 match chan_idx {
1265 0 => r = Self::adjust_channel(r, 1),
1266 1 => g = Self::adjust_channel(g, 1),
1267 _ => b = Self::adjust_channel(b, 1),
1268 }
1269 let new_c = Self::make_color_u32(r, g, b);
1270 self.set_custom_color_by_idx(i, new_c);
1271 if self.enable_custom_colors {
1272 unsafe {
1273 if let Some(ref mut conf) = config::GLOBAL_CONFIG {
1274 conf.custom_theme = self.custom_theme;
1275 conf.theme = self.custom_theme;
1276 }
1277 }
1278 let mut wm = crate::kernel::window_mgr::get_instance();
1279 wm.dirty = true;
1280 }
1281 return;
1282 }
1283 }
1284 }
1285 }
1286
1287 let bx = (ww - 200) / 2;
1288 let by = wh - 45;
1289 if x >= bx && x < bx + 200 && y >= by && y < by + 32 {
1290 self.apply_and_save();
1291 }
1292 }
1293 "FontOrder" => {
1294 for slot in 0..3usize {
1295 let by = 85 + slot as i32 * 36;
1296 if y >= by && y < by + 28 {
1297 if (48..74).contains(&x) {
1298 self.cycle_font(slot, -1);
1299 return;
1300 }
1301 if (80..304).contains(&x) || (310..336).contains(&x) {
1302 self.cycle_font(slot, 1);
1303 return;
1304 }
1305 }
1306 }
1307 let bx = (ww - 200) / 2;
1308 let by = wh - 45;
1309 if x >= bx && x < bx + 200 && y >= by && y < by + 32 {
1310 self.apply_and_save();
1311 }
1312 }
1313 "Keyboard" => {
1314 if (160..250).contains(&x) && (50..76).contains(&y) {
1315 self.selected_layout = KeyboardLayout::Jis;
1316 return;
1317 }
1318 if (265..355).contains(&x) && (50..76).contains(&y) {
1319 self.selected_layout = KeyboardLayout::Us;
1320 return;
1321 }
1322 let rows: [(i32, &mut u32, u32, u32, u32); 5] = unsafe {
1323 let s = &mut *(self as *mut Self);
1324 [
1325 (90, &mut s.repeat_first_delay, 50, 2000, 50),
1326 (130, &mut s.repeat_first_interval, 5, 500, 5),
1327 (170, &mut s.repeat_second_count, 0, 100, 1),
1328 (210, &mut s.repeat_second_delay, 50, 2000, 50),
1329 (250, &mut s.repeat_second_interval, 5, 500, 5),
1330 ]
1331 };
1332 for (row_y, val, min, max, step) in rows {
1333 if y >= row_y && y < row_y + 26 {
1334 if (160..190).contains(&x) {
1335 *val = val.saturating_sub(step).max(min);
1336 return;
1337 }
1338 if (280..310).contains(&x) {
1339 *val = (*val + step).min(max);
1340 return;
1341 }
1342 }
1343 }
1344 if (290..316).contains(&y) {
1345 if (160..230).contains(&x) {
1346 self.selected_caps_ctrl = true;
1347 return;
1348 }
1349 if (245..315).contains(&x) {
1350 self.selected_caps_ctrl = false;
1351 return;
1352 }
1353 }
1354 let bx = (ww - 200) / 2;
1355 let by = wh - 45;
1356 if x >= bx && x < bx + 200 && y >= by && y < by + 32 {
1357 self.apply_and_save();
1358 }
1359 }
1360 "System" => {
1361 if (50..76).contains(&y) {
1362 if (160..230).contains(&x) {
1363 self.selected_sound = true;
1364 return;
1365 }
1366 if (245..315).contains(&x) {
1367 self.selected_sound = false;
1368 return;
1369 }
1370 }
1371 if (90..116).contains(&y) {
1372 if (160..230).contains(&x) {
1373 self.selected_auto_dhcp = true;
1374 return;
1375 }
1376 if (245..315).contains(&x) {
1377 self.selected_auto_dhcp = false;
1378 return;
1379 }
1380 }
1381 let bx = (ww - 200) / 2;
1382 let by = wh - 45;
1383 if x >= bx && x < bx + 200 && y >= by && y < by + 32 {
1384 self.apply_and_save();
1385 }
1386 }
1387 _ => {
1388 let fw = (ww - 30).max(0);
1389 if x >= 15 && x < 15 + fw && (170..198).contains(&y) {
1390 self.wifi_focused = 1;
1391 return;
1392 }
1393 if x >= 15 && x < 15 + fw && (230..258).contains(&y) {
1394 self.wifi_focused = 2;
1395 return;
1396 }
1397 if (15..175).contains(&x) && (278..312).contains(&y) {
1398 self.wifi_focused = 0;
1399 self.start_wifi_connect();
1400 return;
1401 }
1402 if crate::kernel::wifi::is_connected()
1403 && (187..317).contains(&x)
1404 && (278..312).contains(&y)
1405 {
1406 crate::kernel::wifi::disconnect();
1407 self.wifi_status_msg = String::from("Disconnected");
1408 self.wifi_focused = 0;
1409 return;
1410 }
1411 self.wifi_focused = 0;
1412 }
1413 }
1414 }
1415
1416 fn on_key(&mut self, keycode: u8, pressed: bool, ascii: Option<char>) {
1417 if !pressed {
1418 return;
1419 }
1420
1421 let tabs = self.tabs();
1422 if self.active_tab_idx >= tabs.len() {
1423 return;
1424 }
1425 let tab_name = tabs[self.active_tab_idx].as_str();
1426
1427 if tab_name == "Network" {
1428 match self.wifi_focused {
1429 1 => {
1430 if keycode == 0x0F {
1431 self.wifi_focused = 2;
1432 } else if keycode == 0x1C {
1433 self.start_wifi_connect();
1434 } else {
1435 let _ = self.ssid_box.handle_key(keycode, ascii);
1436 }
1437 }
1438 2 => {
1439 if keycode == 0x0F {
1440 self.wifi_focused = 1;
1441 } else if keycode == 0x1C {
1442 self.start_wifi_connect();
1443 } else {
1444 let _ = self.password_box.handle_key(keycode, ascii);
1445 }
1446 }
1447 _ => {
1448 if keycode == 0x0F {
1449 self.wifi_focused = 1;
1450 }
1451 }
1452 }
1453 return;
1454 }
1455
1456 let count = self.get_item_count();
1457 if ascii == Some('\t') {
1458 if let Some(idx) = self.focused_item_idx {
1459 self.focused_item_idx = Some((idx + 1) % count);
1460 } else {
1461 self.focused_item_idx = Some(0);
1462 }
1463 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1464 } else if ascii == Some('\n') {
1465 if let Some(idx) = self.focused_item_idx {
1466 self.handle_action(idx);
1467 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1468 }
1469 }
1470 }
1471}