Skip to content

Commit fba5246

Browse files
committed
Fix Input Mapping V2 device and gesture wiring - PR_26140_115-fix-input-mapping-v2-device-gesture-wiring
1 parent 08603bb commit fba5246

8 files changed

Lines changed: 184 additions & 175 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Input Mapping V2 Device/Gesture Wiring Report
2+
3+
PR: PR_26140_115-fix-input-mapping-v2-device-gesture-wiring
4+
5+
## Summary
6+
- Renamed the center-column Capture accordion to `Input Device(s)` and Gestures to `Capture Gestures`, with no braces in either visible label.
7+
- Disabled and grayed `Input Device(s)` until the selected action has an existing tile, and disabled `Capture Gestures` until an input device is selected.
8+
- Enforced device-first capture flow: selecting an input device enables only that device's gesture group, and stale selected gestures no longer auto-arm when a device is clicked after capture completion.
9+
- Removed visible Mouse Wheel Left/Right gesture descriptors because PR_115 only requires visible/wired Wheel Up and Wheel Down; underlying wheel input support remains available through the engine where used by existing capture/combo paths.
10+
- Preserved selected action/tile behavior, token deletion, mapping scroll behavior, Export/Copy JSON, engine-backed combo capture, and game controller release capture through engine input services.
11+
12+
## Gesture Wiring
13+
- Keyboard: Press, Release, Hold, Combo are visible when Keyboard is selected and are covered by focused Playwright capture paths.
14+
- Mouse: Click, Double Click, Drag, Drag Release, Wheel Up, Wheel Down, Combo are visible when Mouse is selected and are covered by focused Playwright capture paths.
15+
- Game Controller: Btn Press, Btn Hold, Btn Release, Trigger, Stick, DPad, Combo are visible when a Game Controller input device is selected and are covered by focused Playwright capture paths.
16+
- Btn Release continues to capture on release, not press, via `src/engine/input/**` state consumed by Input Mapping V2.
17+
- Combo capture continues to use the engine-backed combo state path rather than a tool-local-only model.
18+
19+
## Validation
20+
- PASS: targeted syntax validation with `node --check` for changed engine/input, Input Mapping V2, and Playwright files.
21+
- PASS: targeted module import validation for changed engine/input and Input Mapping V2 JS modules.
22+
- PASS: focused Input Mapping V2 Playwright coverage: 11 passed.
23+
- PASS: `npm run test:workspace-v2`: 70 passed.
24+
- SKIPPED as requested: full samples smoke test.
25+
26+
## Schema/Samples
27+
- No schemas changed.
28+
- No sample JSON touched.

src/engine/input/InputCapabilityDescriptors.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,6 @@ const GESTURE_DEFINITIONS = Object.freeze([
7575
pointerGesture('MousePrimaryDragRelease', 'Drag Release', 'Mouse Drag Release. Use for completed drag gestures, such as box-selecting objects from start/end positions.', 'mouse'),
7676
wheelGesture('MouseWheelUp', 'Wheel Up', 'Mouse Wheel Up. Use for zoom, scrolling, or cycling selections.'),
7777
wheelGesture('MouseWheelDown', 'Wheel Down', 'Mouse Wheel Down. Use for zoom, scrolling, or cycling selections.'),
78-
wheelGesture('MouseWheelLeft', 'Wheel Left', 'Mouse Wheel Left. Use for horizontal scrolling or cycling selections.'),
79-
wheelGesture('MouseWheelRight', 'Wheel Right', 'Mouse Wheel Right. Use for horizontal scrolling or cycling selections.'),
8078
comboGesture('MouseCombo', 'Mouse', ['mouse'], 'Mouse Combo. Use for combinations such as Shift + Mouse Right Button. Capture keyboard, mouse, wheel, or controller inputs for one selected action.'),
8179
gameControllerGesture('GameControllerButtonPress', 'Btn Press', 'Game controller button press. Use when an action should trigger on the transition from up to down.', 'gameController'),
8280
gameControllerGesture('GameControllerButtonHold', 'Btn Hold', 'Game controller button hold. Use while a game controller button remains held.', 'gameController'),

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 110 additions & 155 deletions
Large diffs are not rendered by default.

tools/input-mapping-v2/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ <h2 class="tools-platform-frame__eyebrow">Input Sources and Action Mapping</h2>
105105
<section class="tool-starter__panel tool-starter__panel--center" aria-label="Captured mappings">
106106
<section class="accordion-v2 tool-starter__accordion is-open" data-accordion-v2-open="true">
107107
<div class="accordion-v2__header input-mapping-v2__capture-header" aria-expanded="true" aria-controls="captureInputContent">
108-
<span>Capture</span>
108+
<span>Input Device(s)</span>
109109
<div class="input-mapping-v2__capture-header-actions">
110110
<label class="input-mapping-v2__context-toggle" for="inputMappingV2DisableContextCheckbox">
111111
<input id="inputMappingV2DisableContextCheckbox" type="checkbox">
@@ -133,7 +133,7 @@ <h2 class="tools-platform-frame__eyebrow">Input Sources and Action Mapping</h2>
133133

134134
<section class="accordion-v2 tool-starter__accordion is-open" data-accordion-v2-open="true">
135135
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="gestureSetupContent">
136-
<span>Gestures</span>
136+
<span>Capture Gestures</span>
137137
<span class="accordion-v2__icon" aria-hidden="true">+</span>
138138
</button>
139139
<div id="gestureSetupContent" class="accordion-v2__content">

tools/input-mapping-v2/js/ToolStarterApp.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ export class ToolStarterApp {
153153
deleteSelectedAction() {
154154
const result = this.state.deleteSelectedAction();
155155
this.statusLog[result.ok ? "ok" : "warn"](result.message);
156+
this.clearSelectedCaptureDevice();
157+
this.clearCapture();
156158
this.refreshActions();
157159
}
158160

@@ -176,8 +178,9 @@ export class ToolStarterApp {
176178
this.cancelCapture("Keyboard");
177179
return;
178180
}
181+
const hadSelectedCaptureDevice = this.selectedCaptureSource === "keyboard" && this.selectedCaptureId === "keyboard";
179182
this.selectCaptureDevice("keyboard", "keyboard", "Keyboard");
180-
if (!this.selectedGestureForCaptureSource("keyboard")) {
183+
if (!hadSelectedCaptureDevice || !this.selectedGestureForCaptureSource("keyboard")) {
181184
this.capture.showMessage(`Keyboard capture device selected for ${this.state.selectedActionLabel()}. Choose a Keyboard gesture to capture.`);
182185
this.statusLog.ok(`Keyboard capture device selected for ${this.state.selectedActionLabel()}.`);
183186
this.refreshActions();
@@ -204,8 +207,9 @@ export class ToolStarterApp {
204207
this.cancelCapture("Mouse");
205208
return;
206209
}
210+
const hadSelectedCaptureDevice = this.selectedCaptureSource === "mouse" && this.selectedCaptureId === "mouse";
207211
this.selectCaptureDevice("mouse", "mouse", "Mouse");
208-
if (!this.selectedGestureForCaptureSource("mouse")) {
212+
if (!hadSelectedCaptureDevice || !this.selectedGestureForCaptureSource("mouse")) {
209213
this.capture.showMessage(`Mouse capture device selected for ${this.state.selectedActionLabel()}. Choose a Mouse gesture to capture.`);
210214
this.statusLog.ok(`Mouse capture device selected for ${this.state.selectedActionLabel()}.`);
211215
this.refreshActions();
@@ -256,8 +260,9 @@ export class ToolStarterApp {
256260
this.cancelCapture(`Gamepad ${selectedIndex}`);
257261
return;
258262
}
263+
const hadSelectedCaptureDevice = this.selectedCaptureSource === "gamepad" && this.selectedCaptureId === captureId;
259264
this.selectCaptureDevice("gamepad", captureId, `Gamepad ${selectedIndex}`);
260-
if (!this.selectedGestureForCaptureSource("gamepad")) {
265+
if (!hadSelectedCaptureDevice || !this.selectedGestureForCaptureSource("gamepad")) {
261266
this.capture.showMessage(`Gamepad ${selectedIndex} capture device selected for ${this.state.selectedActionLabel()}. Choose a Game Controller gesture to capture.`);
262267
this.statusLog.ok(`Gamepad ${selectedIndex} capture device selected for ${this.state.selectedActionLabel()}.`);
263268
this.refreshActions();
@@ -1076,18 +1081,20 @@ export class ToolStarterApp {
10761081
const gestures = this.engineInputSources.gestures(this.enabledDeviceIds);
10771082
const actions = this.state.actions();
10781083
const selectedAction = actions.find((action) => action.id === this.state.selectedActionId);
1084+
const hasSelectedActionTile = this.state.selectedActionHasTile();
10791085
this.actionNav.setToolActionsEnabled(true);
10801086
this.exportControl.setEnabled(true);
10811087
this.actionSelection.render(actions, this.state.selectedActionId);
10821088
this.deviceList.render(devices, this.enabledDeviceIds, gamepadStatus.haptics, this.selectedRumbleSettings());
1083-
this.gestureList.render(gestures, this.selectedGesture?.binding ?? "", this.captureGestureSourceFilter());
1089+
this.gestureList.render(gestures, this.selectedGesture?.binding ?? "", this.captureGestureSourceFilter(), hasSelectedActionTile);
10841090
this.capture.render(
10851091
this.state.selectedActionLabel(),
10861092
gamepadStatus.gamepads,
10871093
selectedAction?.inputs ?? [],
10881094
this.captureMode,
10891095
this.captureAvailability(),
1090-
this.selectedCaptureId || this.activeCaptureId
1096+
this.selectedCaptureId || this.activeCaptureId,
1097+
hasSelectedActionTile
10911098
);
10921099
this.preview.render(
10931100
this.engineInputSources.actionsWithActiveInputState(actions),

tools/input-mapping-v2/js/controls/CaptureControl.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,18 @@ export class CaptureControl {
3838
this.refreshGamepadsButton.addEventListener("click", onRefreshGamepads);
3939
}
4040

41-
render(actionLabel, gamepads = [], selectedInputs = [], captureMode = "", captureAvailability = allCaptureAvailable(), selectedCaptureId = "") {
41+
render(actionLabel, gamepads = [], selectedInputs = [], captureMode = "", captureAvailability = allCaptureAvailable(), selectedCaptureId = "", canSelectInputDevice = true) {
4242
this.selectedCaptureId = selectedCaptureId;
4343
this.selectedActionLabel.textContent = `Selected action: ${actionLabel}`;
4444
const canHighlightUsedInputs = true;
45-
this.setCaptureButtonEnabled(this.captureKeyboardButton, captureAvailability.keyboard, "keyboard");
46-
this.setCaptureButtonEnabled(this.captureMouseButton, captureAvailability.mouse, "mouse");
45+
this.setSectionDisabled(!canSelectInputDevice);
46+
this.setCaptureButtonEnabled(this.captureKeyboardButton, canSelectInputDevice && captureAvailability.keyboard, "keyboard");
47+
this.setCaptureButtonEnabled(this.captureMouseButton, canSelectInputDevice && captureAvailability.mouse, "mouse");
48+
this.refreshGamepadsButton.disabled = !canSelectInputDevice;
49+
this.refreshGamepadsButton.ariaDisabled = this.refreshGamepadsButton.disabled ? "true" : "false";
4750
this.captureKeyboardButton.classList.toggle("has-used-input", canHighlightUsedInputs && selectedInputs.some(usesKeyboard));
4851
this.captureMouseButton.classList.toggle("has-used-input", canHighlightUsedInputs && selectedInputs.some(usesMouse));
49-
this.renderGamepadButtons(gamepads, selectedInputs, canHighlightUsedInputs, captureAvailability.gamepad);
52+
this.renderGamepadButtons(gamepads, selectedInputs, canHighlightUsedInputs, canSelectInputDevice && captureAvailability.gamepad);
5053
this.applySelectedCaptureState();
5154
this.applyCaptureState();
5255
}
@@ -152,6 +155,12 @@ export class CaptureControl {
152155
});
153156
}
154157

158+
setSectionDisabled(isDisabled) {
159+
this.captureKeyboardButton
160+
.closest(".tool-starter__accordion")
161+
?.classList.toggle("input-mapping-v2__section-disabled", isDisabled);
162+
}
163+
155164
allCaptureButtons() {
156165
return [
157166
this.captureKeyboardButton,

tools/input-mapping-v2/js/controls/GestureListControl.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ export class GestureListControl {
88
this.onGestureSelected = onGestureSelected;
99
}
1010

11-
render(gestures, selectedGestureBinding = "", selectedCaptureSource = "") {
11+
render(gestures, selectedGestureBinding = "", selectedCaptureSource = "", canCaptureGesture = true) {
12+
this.setSectionDisabled(!canCaptureGesture || !selectedCaptureSource);
1213
if (!gestures.length) {
1314
const empty = document.createElement("p");
1415
empty.className = "tool-starter__hint";
@@ -18,12 +19,12 @@ export class GestureListControl {
1819
}
1920

2021
const groups = groupGestures(gestures);
21-
this.container.replaceChildren(...groups.map((group) => this.createGestureGroup(group, selectedGestureBinding, selectedCaptureSource)));
22+
this.container.replaceChildren(...groups.map((group) => this.createGestureGroup(group, selectedGestureBinding, selectedCaptureSource, canCaptureGesture)));
2223
}
2324

24-
createGestureGroup(group, selectedGestureBinding, selectedCaptureSource) {
25+
createGestureGroup(group, selectedGestureBinding, selectedCaptureSource, canCaptureGesture) {
2526
const section = document.createElement("article");
26-
const isDisabled = Boolean(selectedCaptureSource) && group.source !== selectedCaptureSource;
27+
const isDisabled = !canCaptureGesture || !selectedCaptureSource || group.source !== selectedCaptureSource;
2728
section.className = `input-mapping-v2__gesture-group${isDisabled ? " is-disabled" : ""}`;
2829
section.dataset.inputMappingGestureDevice = group.deviceLabel;
2930
section.dataset.inputMappingGestureSource = group.source;
@@ -33,17 +34,17 @@ export class GestureListControl {
3334

3435
const list = document.createElement("div");
3536
list.className = "input-mapping-v2__gesture-list";
36-
list.append(...group.gestures.map((gesture) => this.createGestureButton(gesture, selectedGestureBinding, selectedCaptureSource)));
37+
list.append(...group.gestures.map((gesture) => this.createGestureButton(gesture, selectedGestureBinding, selectedCaptureSource, canCaptureGesture)));
3738

3839
section.append(title, list);
3940
return section;
4041
}
4142

42-
createGestureButton(gesture, selectedGestureBinding, selectedCaptureSource) {
43+
createGestureButton(gesture, selectedGestureBinding, selectedCaptureSource, canCaptureGesture) {
4344
const button = document.createElement("button");
4445
button.type = "button";
4546
const isSelected = gesture.binding === selectedGestureBinding;
46-
const isDisabled = Boolean(selectedCaptureSource) && gesture.source !== selectedCaptureSource;
47+
const isDisabled = !canCaptureGesture || !selectedCaptureSource || gesture.source !== selectedCaptureSource;
4748
button.className = `input-mapping-v2__gesture-button${isSelected ? " is-selected" : ""}${isDisabled ? " is-disabled" : ""}`;
4849
button.dataset.inputMappingGestureBinding = gesture.binding;
4950
button.dataset.inputMappingGestureSource = gesture.source;
@@ -65,6 +66,12 @@ export class GestureListControl {
6566
});
6667
return button;
6768
}
69+
70+
setSectionDisabled(isDisabled) {
71+
this.container
72+
.closest(".tool-starter__accordion")
73+
?.classList.toggle("input-mapping-v2__section-disabled", isDisabled);
74+
}
6875
}
6976

7077
function groupGestures(gestures) {

tools/input-mapping-v2/styles/inputMappingV2.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@
7171
font-weight: 700;
7272
}
7373

74+
.input-mapping-v2__section-disabled > .accordion-v2__header,
75+
.input-mapping-v2__section-disabled > .accordion-v2__content {
76+
opacity: 0.58;
77+
}
78+
7479
.input-mapping-v2 #inputMappingV2CaptureKeyboardButton,
7580
.input-mapping-v2 #inputMappingV2CaptureMouseButton,
7681
.input-mapping-v2__gamepad-capture-button {

0 commit comments

Comments
 (0)