feat: Move language combobox to Layers tab, update key selector on layout change

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mae PUGIN 2026-04-10 09:30:18 +02:00
parent 67a883dd0b
commit 88ab57bebe
5 changed files with 70 additions and 104 deletions

View file

@ -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::<KeySelectorBridge>().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);
}
});

View file

@ -44,7 +44,7 @@ pub fn init_models(
window.global::<SettingsBridge>().set_selected_layout_index(idx as i32);
// Key selector
let all_keys = build_key_entries();
let all_keys = build_key_entries_with_layout(&current);
window.global::<KeySelectorBridge>().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<VecModel<KeyEntry>> {
pub fn build_key_entries_with_layout(layout: &layout_remap::KeyboardLayout) -> Rc<VecModel<KeyEntry>> {
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"),

View file

@ -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::<SettingsBridge>().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::<crate::KeymapBridge>().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::<crate::KeySelectorBridge>().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();

View file

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

View file

@ -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;