feat: Editable Tap Dance + combo creation validation
Tap Dance: - Each TD action is now a clickable button that opens the key selector - Clicking an action -> pick key from popup -> sends TD_SET binary to firmware - Labels show 1-tap / 2-tap / 3-tap / hold columns Combo creation: - Added validation: "Pick both keys first" message if keys not selected - Debug log for COMBOSET command Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d69d931ea9
commit
4cfe2fbb7b
3 changed files with 82 additions and 4 deletions
48
src/main.rs
48
src/main.rs
|
|
@ -672,6 +672,7 @@ fn main() {
|
||||||
let dispatch_keycode = {
|
let dispatch_keycode = {
|
||||||
let apply_keycode = apply_keycode.clone();
|
let apply_keycode = apply_keycode.clone();
|
||||||
let keys_arc = keys_arc.clone();
|
let keys_arc = keys_arc.clone();
|
||||||
|
let serial = serial.clone();
|
||||||
let macro_steps = macro_steps.clone();
|
let macro_steps = macro_steps.clone();
|
||||||
let refresh_macro_display = refresh_macro_display.clone();
|
let refresh_macro_display = refresh_macro_display.clone();
|
||||||
let window_weak = window.as_weak();
|
let window_weak = window.as_weak();
|
||||||
|
|
@ -738,6 +739,41 @@ fn main() {
|
||||||
}
|
}
|
||||||
if count < 4 { adv.set_new_leader_seq_count(count + 1); }
|
if count < 4 { adv.set_new_leader_seq_count(count + 1); }
|
||||||
}
|
}
|
||||||
|
"td-action" => {
|
||||||
|
let adv = w.global::<AdvancedBridge>();
|
||||||
|
let td_idx = adv.get_editing_td_index();
|
||||||
|
let slot = adv.get_editing_td_slot() as usize;
|
||||||
|
if td_idx >= 0 && slot < 4 {
|
||||||
|
// Update model in place
|
||||||
|
let tds = adv.get_tap_dances();
|
||||||
|
for i in 0..tds.row_count() {
|
||||||
|
let td = tds.row_data(i).unwrap();
|
||||||
|
if td.index == td_idx {
|
||||||
|
let actions = td.actions;
|
||||||
|
let mut a = actions.row_data(slot).unwrap();
|
||||||
|
a.name = name.clone();
|
||||||
|
a.code = code as i32;
|
||||||
|
actions.set_row_data(slot, a);
|
||||||
|
|
||||||
|
// Collect all 4 action codes and send to firmware
|
||||||
|
let mut codes = [0u16; 4];
|
||||||
|
for j in 0..4.min(actions.row_count()) {
|
||||||
|
codes[j] = actions.row_data(j).unwrap().code as u16;
|
||||||
|
}
|
||||||
|
let payload = logic::binary_protocol::td_set_payload(td_idx as u8, &codes);
|
||||||
|
let serial = serial.clone();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let mut ser = serial.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
|
let _ = ser.send_binary(logic::binary_protocol::cmd::TD_SET, &payload);
|
||||||
|
});
|
||||||
|
w.global::<AppState>().set_status_text(
|
||||||
|
SharedString::from(format!("TD{} slot {} = {}", td_idx, slot, name))
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
"macro-step" => {
|
"macro-step" => {
|
||||||
// Add key press (Down + Up) to macro steps
|
// Add key press (Down + Up) to macro steps
|
||||||
let mut steps = macro_steps.borrow_mut();
|
let mut steps = macro_steps.borrow_mut();
|
||||||
|
|
@ -1018,7 +1054,14 @@ fn main() {
|
||||||
let r2 = adv.get_new_combo_r2() as u8;
|
let r2 = adv.get_new_combo_r2() as u8;
|
||||||
let c2 = adv.get_new_combo_c2() as u8;
|
let c2 = adv.get_new_combo_c2() as u8;
|
||||||
let result = adv.get_new_combo_result_code() as u8;
|
let result = adv.get_new_combo_result_code() as u8;
|
||||||
|
let key1_name = adv.get_new_combo_key1_name();
|
||||||
|
let key2_name = adv.get_new_combo_key2_name();
|
||||||
|
if key1_name == "Pick..." || key2_name == "Pick..." {
|
||||||
|
w.global::<AppState>().set_status_text("Pick both keys first".into());
|
||||||
|
return;
|
||||||
|
}
|
||||||
let cmd = logic::protocol::cmd_comboset(255, r1, c1, r2, c2, result);
|
let cmd = logic::protocol::cmd_comboset(255, r1, c1, r2, c2, result);
|
||||||
|
eprintln!("COMBOSET: {}", cmd);
|
||||||
let serial = serial.clone();
|
let serial = serial.clone();
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
|
|
@ -1481,7 +1524,10 @@ fn main() {
|
||||||
.map(|(i, actions)| TapDanceData {
|
.map(|(i, actions)| TapDanceData {
|
||||||
index: i as i32,
|
index: i as i32,
|
||||||
actions: ModelRc::from(Rc::new(VecModel::from(
|
actions: ModelRc::from(Rc::new(VecModel::from(
|
||||||
actions.iter().map(|&a| SharedString::from(keycode::decode_keycode(a))).collect::<Vec<_>>()
|
actions.iter().map(|&a| TapDanceAction {
|
||||||
|
name: SharedString::from(keycode::decode_keycode(a)),
|
||||||
|
code: a as i32,
|
||||||
|
}).collect::<Vec<_>>()
|
||||||
))),
|
))),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
|
||||||
|
|
@ -121,9 +121,14 @@ export global StatsBridge {
|
||||||
|
|
||||||
// ---- Advanced ----
|
// ---- Advanced ----
|
||||||
|
|
||||||
|
export struct TapDanceAction {
|
||||||
|
name: string,
|
||||||
|
code: int,
|
||||||
|
}
|
||||||
|
|
||||||
export struct TapDanceData {
|
export struct TapDanceData {
|
||||||
index: int,
|
index: int,
|
||||||
actions: [string],
|
actions: [TapDanceAction],
|
||||||
}
|
}
|
||||||
|
|
||||||
export struct ComboData {
|
export struct ComboData {
|
||||||
|
|
@ -183,6 +188,11 @@ export global AdvancedBridge {
|
||||||
in-out property <int> new-leader-result-code: 0;
|
in-out property <int> new-leader-result-code: 0;
|
||||||
in-out property <string> new-leader-result-name: "Pick...";
|
in-out property <string> new-leader-result-name: "Pick...";
|
||||||
in-out property <int> new-leader-mod-idx: 0;
|
in-out property <int> new-leader-mod-idx: 0;
|
||||||
|
// TD editing: stores which TD slot+action is being edited
|
||||||
|
in-out property <int> editing-td-index: -1;
|
||||||
|
in-out property <int> editing-td-slot: -1; // 0-3 = which action
|
||||||
|
callback save-td(int, int, int, int, int); // index, a1, a2, a3, a4 (keycodes)
|
||||||
|
callback edit-td-action(int, int); // td_index, action_slot -> opens popup
|
||||||
callback refresh-advanced();
|
callback refresh-advanced();
|
||||||
callback delete-combo(int);
|
callback delete-combo(int);
|
||||||
callback delete-leader(int);
|
callback delete-leader(int);
|
||||||
|
|
|
||||||
|
|
@ -88,14 +88,36 @@ export component TabAdvanced inherits Rectangle {
|
||||||
spacing: 4px;
|
spacing: 4px;
|
||||||
SectionHeader { text: "Tap Dance"; }
|
SectionHeader { text: "Tap Dance"; }
|
||||||
if AdvancedBridge.tap-dances.length == 0 : Text { text: "No tap dances"; color: Theme.fg-secondary; font-size: 11px; }
|
if AdvancedBridge.tap-dances.length == 0 : Text { text: "No tap dances"; color: Theme.fg-secondary; font-size: 11px; }
|
||||||
|
|
||||||
|
Text { text: "1-tap 2-tap 3-tap hold"; color: Theme.fg-secondary; font-size: 10px; }
|
||||||
|
|
||||||
for td in AdvancedBridge.tap-dances : Rectangle {
|
for td in AdvancedBridge.tap-dances : Rectangle {
|
||||||
background: Theme.bg-primary;
|
background: Theme.bg-primary;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
padding-left: 8px; padding-right: 8px; spacing: 8px;
|
padding-left: 8px; padding-right: 8px; spacing: 4px;
|
||||||
Text { text: "TD" + td.index; color: Theme.accent-purple; font-size: 11px; vertical-alignment: center; width: 35px; }
|
Text { text: "TD" + td.index; color: Theme.accent-purple; font-size: 11px; vertical-alignment: center; width: 35px; }
|
||||||
for action in td.actions : Text { text: action; color: Theme.fg-primary; font-size: 11px; vertical-alignment: center; }
|
for action[a-idx] in td.actions : Rectangle {
|
||||||
|
width: 60px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: td-action-ta.has-hover ? Theme.button-hover : Theme.button-bg;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: Theme.button-border;
|
||||||
|
|
||||||
|
Text { text: action.name; color: Theme.fg-primary; font-size: 10px; horizontal-alignment: center; vertical-alignment: center; }
|
||||||
|
|
||||||
|
td-action-ta := TouchArea {
|
||||||
|
clicked => {
|
||||||
|
AdvancedBridge.editing-td-index = td.index;
|
||||||
|
AdvancedBridge.editing-td-slot = a-idx;
|
||||||
|
KeymapBridge.selector-target = "td-action";
|
||||||
|
KeymapBridge.key-selector-open = true;
|
||||||
|
}
|
||||||
|
mouse-cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue