KeSp_controller/ui/tabs/tab_flasher.slint
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

215 lines
6.8 KiB
Text

import { Button, ComboBox, LineEdit } from "std-widgets.slint";
import { Theme } from "../theme.slint";
import { FlasherBridge } from "../globals.slint";
export component TabFlasher inherits Rectangle {
background: Theme.bg-primary;
VerticalLayout {
padding: 20px;
spacing: 16px;
alignment: start;
Text {
text: "Firmware Flasher";
color: Theme.fg-primary;
font-size: 20px;
font-weight: 700;
}
Text {
text: "Flash firmware via ESP32 programming port (CH340/CP2102).";
color: Theme.fg-secondary;
font-size: 12px;
wrap: word-wrap;
}
// Port selection
Rectangle {
background: Theme.bg-secondary;
border-radius: 8px;
VerticalLayout {
padding: 16px;
spacing: 10px;
Text {
text: "Programming Port (CH340/CP210x only)";
color: Theme.accent-cyan;
font-size: 14px;
font-weight: 600;
}
HorizontalLayout {
spacing: 8px;
if FlasherBridge.prog-ports.length > 0 : ComboBox {
horizontal-stretch: 1;
model: FlasherBridge.prog-ports;
selected(value) => {
FlasherBridge.selected-prog-port = value;
}
}
if FlasherBridge.prog-ports.length == 0 : LineEdit {
horizontal-stretch: 1;
text <=> FlasherBridge.selected-prog-port;
placeholder-text: "/dev/ttyUSB0";
}
Button {
text: "Refresh";
clicked => { FlasherBridge.refresh-prog-ports(); }
}
}
if FlasherBridge.prog-ports.length == 0 : Text {
text: "No CH340/CP210x port detected. Enter path manually or plug in the programming cable.";
color: Theme.accent-yellow;
font-size: 11px;
wrap: word-wrap;
}
}
}
// Flash target partition
Rectangle {
background: Theme.bg-secondary;
border-radius: 8px;
VerticalLayout {
padding: 16px;
spacing: 10px;
Text {
text: "Target Partition";
color: Theme.accent-cyan;
font-size: 14px;
font-weight: 600;
}
ComboBox {
model: ["factory (0x20000)", "ota_0 (0x220000)"];
current-index <=> FlasherBridge.flash-offset-index;
}
}
}
// Firmware file
Rectangle {
background: Theme.bg-secondary;
border-radius: 8px;
VerticalLayout {
padding: 16px;
spacing: 10px;
Text {
text: "Firmware File";
color: Theme.accent-cyan;
font-size: 14px;
font-weight: 600;
}
HorizontalLayout {
spacing: 8px;
Text {
horizontal-stretch: 1;
text: FlasherBridge.firmware-path != "" ? FlasherBridge.firmware-path : "No file selected";
color: FlasherBridge.firmware-path != "" ? Theme.fg-primary : Theme.fg-secondary;
font-size: 12px;
vertical-alignment: center;
overflow: elide;
}
Button {
text: "Browse...";
clicked => { FlasherBridge.browse-firmware(); }
}
}
}
}
// Flash button + progress
Rectangle {
background: Theme.bg-secondary;
border-radius: 8px;
VerticalLayout {
padding: 16px;
spacing: 10px;
HorizontalLayout {
spacing: 12px;
Button {
text: FlasherBridge.flashing ? "Flashing..." : "Flash Firmware";
enabled: !FlasherBridge.flashing
&& FlasherBridge.firmware-path != ""
&& FlasherBridge.selected-prog-port != "";
clicked => { FlasherBridge.flash(); }
}
Text {
text: FlasherBridge.flash-status;
color: Theme.fg-primary;
font-size: 12px;
vertical-alignment: center;
horizontal-stretch: 1;
}
}
if FlasherBridge.flashing || FlasherBridge.flash-progress > 0 : Rectangle {
height: 20px;
background: Theme.bg-primary;
border-radius: 4px;
Rectangle {
x: 0;
width: parent.width * clamp(FlasherBridge.flash-progress, 0, 1);
height: 100%;
background: FlasherBridge.flash-progress >= 1.0 ? Theme.accent-green : Theme.accent-purple;
border-radius: 4px;
}
Text {
text: round(FlasherBridge.flash-progress * 100) + "%";
color: Theme.fg-primary;
font-size: 11px;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
}
// Warning
Rectangle {
background: #ff555520;
border-radius: 8px;
border-width: 1px;
border-color: Theme.accent-red;
VerticalLayout {
padding: 12px;
spacing: 4px;
Text {
text: "Warning";
color: Theme.accent-red;
font-size: 13px;
font-weight: 600;
}
Text {
text: "Do not disconnect the keyboard during flashing. The device will reboot automatically when done.";
color: Theme.fg-secondary;
font-size: 11px;
wrap: word-wrap;
}
}
}
Rectangle { vertical-stretch: 1; }
}
}