diff --git a/src/key_selector.rs b/src/key_selector.rs index 9b04348..75155e9 100644 --- a/src/key_selector.rs +++ b/src/key_selector.rs @@ -8,18 +8,20 @@ use slint::{ComponentHandle, Model, SharedString}; use std::rc::Rc; pub fn setup(window: &MainWindow, ctx: &AppContext) { - setup_filter(window); + setup_filter(window, ctx); let apply_keycode = build_apply_keycode(window, ctx); let refresh_macro_display = crate::macros::make_refresh_display(window, ctx); let dispatch_keycode = build_dispatch_keycode(window, ctx, apply_keycode, refresh_macro_display); setup_callbacks(window, dispatch_keycode); } -fn setup_filter(window: &MainWindow) { - let all_keys = models::build_key_entries(); +fn setup_filter(window: &MainWindow, ctx: &AppContext) { + let keyboard_layout = ctx.keyboard_layout.clone(); let window_weak = window.as_weak(); window.global::().on_apply_filter(move |search| { if let Some(w) = window_weak.upgrade() { + let layout = *keyboard_layout.borrow(); + let all_keys = models::build_key_entries_with_layout(&layout); models::populate_key_categories(&w, &all_keys, &search); } }); diff --git a/src/models.rs b/src/models.rs index b41ef44..d2c6118 100644 --- a/src/models.rs +++ b/src/models.rs @@ -44,7 +44,7 @@ pub fn init_models( window.global::().set_selected_layout_index(idx as i32); // Key selector - let all_keys = build_key_entries(); + let all_keys = build_key_entries_with_layout(¤t); window.global::().set_all_keys(ModelRc::from(all_keys.clone())); populate_key_categories(window, &all_keys, ""); } @@ -113,84 +113,33 @@ pub fn update_keycap_labels( } } -pub fn build_key_entries() -> Rc> { +pub fn build_key_entries_with_layout(layout: &layout_remap::KeyboardLayout) -> Rc> { + let hid_entry = |code: u16, category: &str| -> KeyEntry { + let base = keycode::hid_key_name(code as u8); + let name = layout_remap::remap_key_label(layout, &base) + .map(|s| s.to_string()) + .unwrap_or(base); + KeyEntry { name: SharedString::from(name), code: code as i32, category: SharedString::from(category) } + }; let mut entries = Vec::new(); - for code in 0x04u16..=0x1D { - entries.push(KeyEntry { - name: SharedString::from(keycode::hid_key_name(code as u8)), - code: code as i32, - category: SharedString::from("Letter"), - }); - } - for code in 0x1Eu16..=0x27 { - entries.push(KeyEntry { - name: SharedString::from(keycode::hid_key_name(code as u8)), - code: code as i32, - category: SharedString::from("Number"), - }); - } - for code in [0x28u16, 0x29, 0x2A, 0x2B, 0x2C] { - entries.push(KeyEntry { - name: SharedString::from(keycode::hid_key_name(code as u8)), - code: code as i32, - category: SharedString::from("Control"), - }); - } - for code in 0x2Du16..=0x38 { - entries.push(KeyEntry { - name: SharedString::from(keycode::hid_key_name(code as u8)), - code: code as i32, - category: SharedString::from("Symbol"), - }); - } - for code in 0x3Au16..=0x45 { - entries.push(KeyEntry { - name: SharedString::from(keycode::hid_key_name(code as u8)), - code: code as i32, - category: SharedString::from("Function"), - }); - } + for code in 0x04u16..=0x1D { entries.push(hid_entry(code, "Letter")); } + for code in 0x1Eu16..=0x27 { entries.push(hid_entry(code, "Number")); } + for code in [0x28u16, 0x29, 0x2A, 0x2B, 0x2C] { entries.push(hid_entry(code, "Control")); } + for code in 0x2Du16..=0x38 { entries.push(hid_entry(code, "Symbol")); } + for code in 0x3Au16..=0x45 { entries.push(hid_entry(code, "Function")); } for code in [0x46u16, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52] { - entries.push(KeyEntry { - name: SharedString::from(keycode::hid_key_name(code as u8)), - code: code as i32, - category: SharedString::from("Navigation"), - }); - } - for code in 0xE0u16..=0xE7 { - entries.push(KeyEntry { - name: SharedString::from(keycode::hid_key_name(code as u8)), - code: code as i32, - category: SharedString::from("Modifier"), - }); + entries.push(hid_entry(code, "Navigation")); } + for code in 0xE0u16..=0xE7 { entries.push(hid_entry(code, "Modifier")); } entries.push(KeyEntry { name: SharedString::from("Caps Lock"), code: 0x39, category: SharedString::from("Control"), }); - for code in 0x53u16..=0x63 { - entries.push(KeyEntry { - name: SharedString::from(keycode::hid_key_name(code as u8)), - code: code as i32, - category: SharedString::from("Keypad"), - }); - } - for code in 0x68u16..=0x73 { - entries.push(KeyEntry { - name: SharedString::from(keycode::hid_key_name(code as u8)), - code: code as i32, - category: SharedString::from("Function"), - }); - } - for code in [0x7Fu16, 0x80, 0x81] { - entries.push(KeyEntry { - name: SharedString::from(keycode::hid_key_name(code as u8)), - code: code as i32, - category: SharedString::from("Media"), - }); - } + for code in 0x53u16..=0x63 { entries.push(hid_entry(code, "Keypad")); } + for code in 0x68u16..=0x73 { entries.push(hid_entry(code, "Function")); } + for code in [0x7Fu16, 0x80, 0x81] { entries.push(hid_entry(code, "Media")); } for (code, name) in [ (0x2900u16, "BT Next"), (0x2A00, "BT Prev"), (0x2B00, "BT Pair"), (0x2C00, "BT Disc"), (0x2E00, "USB/BT"), (0x2F00, "BT On/Off"), diff --git a/src/settings.rs b/src/settings.rs index 65a67e0..aaa8910 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -4,12 +4,48 @@ use crate::{config, MainWindow, SettingsBridge}; use slint::{ComponentHandle, SharedString}; pub fn setup(window: &MainWindow, ctx: &AppContext) { + setup_change_layout(window, ctx); setup_ota_browse(window); setup_ota_start(window, ctx); setup_config_export(window, ctx); setup_config_import(window, ctx); } +// --- Change keyboard layout (AZERTY / QWERTZ / ...) --- +fn setup_change_layout(window: &MainWindow, ctx: &AppContext) { + let keyboard_layout = ctx.keyboard_layout.clone(); + let keys = ctx.keys.clone(); + let current_keymap = ctx.current_keymap.clone(); + let window_weak = window.as_weak(); + + window.global::().on_change_layout(move |idx| { + let all = protocol::layout_remap::KeyboardLayout::all(); + let new_layout = all.get(idx as usize).copied().unwrap_or(all[0]); + + // Update shared state + *keyboard_layout.borrow_mut() = new_layout; + + // Persist to disk + let mut s = protocol::settings::load(); + s.keyboard_layout = new_layout.name().to_string(); + protocol::settings::save(&s); + + // Refresh keycap labels + key selector popup + if let Some(w) = window_weak.upgrade() { + let keycaps = w.global::().get_keycaps(); + let km = current_keymap.borrow(); + let k = keys.borrow(); + if !km.is_empty() { + crate::models::update_keycap_labels(&keycaps, &k, &km, &new_layout); + } + + let all_keys = crate::models::build_key_entries_with_layout(&new_layout); + w.global::().set_all_keys(slint::ModelRc::from(all_keys.clone())); + crate::models::populate_key_categories(&w, &all_keys, ""); + } + }); +} + // --- OTA: browse --- fn setup_ota_browse(window: &MainWindow) { let window_weak = window.as_weak(); diff --git a/ui/tabs/tab_keymap.slint b/ui/tabs/tab_keymap.slint index a211a73..f463385 100644 --- a/ui/tabs/tab_keymap.slint +++ b/ui/tabs/tab_keymap.slint @@ -1,7 +1,8 @@ import { DarkLineEdit } from "../components/dark_line_edit.slint"; +import { DarkComboBox } from "../components/dark_combo_box.slint"; import { Theme } from "../theme.slint"; import { DarkButton } from "../components/dark_button.slint"; -import { KeymapBridge, AppState, ConnectionState } from "../globals.slint"; +import { KeymapBridge, AppState, ConnectionState, SettingsBridge } from "../globals.slint"; import { KeyboardView } from "../components/keyboard_view.slint"; export component TabKeymap inherits Rectangle { @@ -98,6 +99,14 @@ export component TabKeymap inherits Rectangle { mouse-cursor: pointer; } } + + // Keyboard language + DarkComboBox { + width: 90px; + model: SettingsBridge.available-layouts; + current-index <=> SettingsBridge.selected-layout-index; + selected(value) => { SettingsBridge.change-layout(self.current-index); } + } } // Keyboard view diff --git a/ui/tabs/tab_settings.slint b/ui/tabs/tab_settings.slint index 9631024..e4548cf 100644 --- a/ui/tabs/tab_settings.slint +++ b/ui/tabs/tab_settings.slint @@ -1,6 +1,5 @@ import { Theme } from "../theme.slint"; import { DarkButton } from "../components/dark_button.slint"; -import { DarkComboBox } from "../components/dark_combo_box.slint"; import { SettingsBridge, AppState, ConnectionState } from "../globals.slint"; export component TabSettings inherits Rectangle { @@ -18,35 +17,6 @@ export component TabSettings inherits Rectangle { font-weight: 700; } - // Keyboard layout - Rectangle { - background: Theme.bg-secondary; - border-radius: 8px; - - HorizontalLayout { - padding: 16px; - spacing: 12px; - - VerticalLayout { - alignment: center; - Text { text: "Keyboard Layout"; color: Theme.fg-primary; font-size: 14px; } - Text { text: "Controls how keycodes are displayed"; color: Theme.fg-secondary; font-size: 11px; } - } - - Rectangle { horizontal-stretch: 1; } - - VerticalLayout { - alignment: center; - DarkComboBox { - width: 200px; - model: SettingsBridge.available-layouts; - current-index <=> SettingsBridge.selected-layout-index; - selected(value) => { SettingsBridge.change-layout(self.current-index); } - } - } - } - } - // OTA Firmware Update Rectangle { background: Theme.bg-secondary;