KeSp_controller/original-src/ui_helpers.rs
Mae PUGIN 32ee3a6d26 feat: Complete KeSp Controller — Slint UI port
Full port of the KaSe/KeSp split keyboard configurator from egui to Slint:
- 6 tabs: Keymap, Advanced, Macros, Stats, Settings, Flash
- Responsive keyboard view with scale-to-fit and key rotations
- Key selector popup with categorized grid, MT/LT builders, hex input
- Combo key picker with inline keyboard visual
- Macro step builder with visual tags
- Serial communication via background threads + mpsc polling
- Heatmap overlay with blue-yellow-red gradient
- OTA flasher with prog port VID filtering and partition selector
- WPM polling, Tamagotchi, Autoshift controls
- Dracula theme matching egui version

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 20:40:34 +02:00

218 lines
7.9 KiB
Rust

use super::{BgResult, Instant};
impl super::KaSeApp {
#[allow(dead_code)]
pub(super) fn notify(&mut self, msg: &str) {
let timestamp = Instant::now();
let entry = (msg.to_string(), timestamp);
self.notifications.push(entry);
}
pub(super) fn get_heatmap_intensity(&self, row: usize, col: usize) -> f32 {
if !self.heatmap_on || self.heatmap_max == 0 {
return 0.0;
}
let row_data = self.heatmap_data.get(row);
let cell_option = row_data.and_then(|r| r.get(col));
let count = cell_option.copied().unwrap_or(0);
let count_float = count as f32;
let max_float = self.heatmap_max as f32;
let intensity = count_float / max_float;
intensity
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) fn load_heatmap(&mut self) {
self.busy = true;
let serial = self.serial.clone();
let tx = self.bg_tx.clone();
std::thread::spawn(move || {
let mut ser = serial.lock().unwrap_or_else(|p| p.into_inner());
let lines = ser.query_command("KEYSTATS?").unwrap_or_default();
let (data, max) = crate::parsers::parse_heatmap_lines(&lines);
let _ = tx.send(BgResult::HeatmapData(data, max));
});
}
#[cfg(target_arch = "wasm32")]
pub(super) fn load_heatmap(&mut self) {
if self.web_busy.get() { return; }
self.busy = true;
self.web_busy.set(true);
let serial = self.serial.clone();
let tx = self.bg_tx.clone();
let web_busy = self.web_busy.clone();
wasm_bindgen_futures::spawn_local(async move {
let handles = serial.borrow().io_handles();
if let Ok((reader, writer)) = handles {
let lines = crate::serial::query_command(&reader, &writer, "KEYSTATS?")
.await
.unwrap_or_default();
let (data, max) = crate::parsers::parse_heatmap_lines(&lines);
let _ = tx.send(BgResult::HeatmapData(data, max));
}
web_busy.set(false);
});
}
/// Apply a key selection from the key selector.
/// row == 951: new KO result key
/// row == 950: new KO trigger key
/// row 900-949: KO edit (900 + idx*10 + 0=trig, +1=result)
/// row >= 800: new leader result
/// row >= 700: new leader sequence key (append)
/// row >= 600: leader result edit (row-600 = leader index)
/// row >= 500: leader sequence key edit (row = 500 + idx*10 + seq_pos)
/// row >= 400: new combo result mode
/// row >= 300: combo result edit (row-300 = combo index)
/// row >= 200: macro step mode (add key as step)
/// row >= 100: TD mode (row-100 = td index, col = action slot)
/// row < 100: keymap mode
pub(super) fn apply_key_selection(&mut self, row: usize, col: usize, code: u16) {
if row == 951 {
// New KO result key
self.ko_new_res_key = code as u8;
self.ko_new_res_set = true;
self.status_msg = format!("KO result = {}", crate::keycode::hid_key_name(code as u8));
return;
}
if row == 950 {
// New KO trigger key
self.ko_new_trig_key = code as u8;
self.ko_new_trig_set = true;
self.status_msg = format!("KO trigger = {}", crate::keycode::hid_key_name(code as u8));
return;
}
if row >= 900 {
// KO edit: row = 900 + idx*10 + field (0=trig, 1=result)
let offset = row - 900;
let ko_idx = offset / 10;
let field = offset % 10;
let idx_valid = ko_idx < self.ko_data.len();
if idx_valid {
if field == 0 {
self.ko_data[ko_idx][0] = code as u8;
self.status_msg = format!("KO #{} trigger = 0x{:02X}", ko_idx, code);
} else {
self.ko_data[ko_idx][2] = code as u8;
self.status_msg = format!("KO #{} result = 0x{:02X}", ko_idx, code);
}
}
return;
}
if row >= 800 {
// New leader result
self.leader_new_result = code as u8;
self.leader_new_result_set = true;
self.status_msg = format!("Leader result = {}", crate::keycode::hid_key_name(code as u8));
return;
}
if row >= 700 {
// New leader sequence key (append)
let seq_not_full = self.leader_new_seq.len() < 4;
if seq_not_full {
self.leader_new_seq.push(code as u8);
let key_name = crate::keycode::hid_key_name(code as u8);
self.status_msg = format!("Leader seq + {}", key_name);
}
return;
}
if row >= 600 {
// Leader result edit (existing)
let leader_idx = row - 600;
let idx_valid = leader_idx < self.leader_data.len();
if idx_valid {
self.leader_data[leader_idx].result = code as u8;
self.status_msg = format!("Leader #{} result = 0x{:02X}", leader_idx, code);
}
return;
}
if row >= 500 {
// Leader sequence key edit (existing)
// row = 500 + leader_idx*10 + seq_pos
let offset = row - 500;
let leader_idx = offset / 10;
let seq_pos = offset % 10;
let idx_valid = leader_idx < self.leader_data.len();
if idx_valid {
let seq_valid = seq_pos < self.leader_data[leader_idx].sequence.len();
if seq_valid {
self.leader_data[leader_idx].sequence[seq_pos] = code as u8;
self.status_msg = format!("Leader #{} key {} = 0x{:02X}", leader_idx, seq_pos, code);
}
}
return;
}
if row >= 400 {
// New combo result mode
self.combo_new_result = code;
self.status_msg = format!("New combo result = 0x{:04X}", code);
return;
}
if row >= 300 {
// Combo result edit mode
let combo_idx = row - 300;
let idx_valid = combo_idx < self.combo_data.len();
if idx_valid {
self.combo_data[combo_idx].result = code;
self.status_msg = format!("Combo #{} result = 0x{:04X}", combo_idx, code);
}
return;
}
if row >= 200 {
// Macro step mode
self.apply_macro_step(code);
return;
}
if row >= 100 {
// TD mode
let td_idx = row - 100;
let idx_valid = td_idx < self.td_data.len();
let col_valid = col < 4;
if idx_valid && col_valid {
self.td_data[td_idx][col] = code;
self.status_msg = format!("TD {} action {} = 0x{:04X}", td_idx, col, code);
}
} else {
// Keymap mode - validate bounds BEFORE sending
let row_valid = row < self.keymap.len();
let col_valid = row_valid && col < self.keymap[row].len();
if col_valid {
let layer = self.current_layer as u8;
let row_byte = row as u8;
let col_byte = col as u8;
let cmd = crate::protocol::cmd_set_key(layer, row_byte, col_byte, code);
self.bg_send(&cmd);
self.keymap[row][col] = code;
self.status_msg = format!("[{},{}] = 0x{:04X}", row, col, code);
} else {
self.status_msg = format!("Invalid key position [{},{}]", row, col);
}
}
}
pub(super) fn get_key(&self, row: usize, col: usize) -> u16 {
let row_data = self.keymap.get(row);
let cell_option = row_data.and_then(|r| r.get(col));
let value = cell_option.copied();
value.unwrap_or(0)
}
}