Skip to content

Commit 8a6c93e

Browse files
author
DavidQ
committed
Polish Input Mapping V2 gesture help and combo actions - PR_26140_102-polish-input-mapping-v2-gesture-help-and-combo-actions
1 parent f148cbe commit 8a6c93e

11 files changed

Lines changed: 266 additions & 66 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Input Mapping V2 Gesture Help And Combo Actions Report
2+
3+
PR: `PR_26140_102-polish-input-mapping-v2-gesture-help-and-combo-actions`
4+
5+
## Source Of Truth
6+
- Read `docs/dev/PROJECT_INSTRUCTIONS.md` before execution.
7+
- The active `docs/pr/BUILD_PR.md` did not describe this PR. Used the explicit PR102 user request as the build source of truth.
8+
- No schema changes were required.
9+
- No sample JSON files were touched.
10+
11+
## Changes
12+
- Added hover/title help to gesture descriptors with concrete use-case guidance for mouse drag, mouse drag release, wheel, keyboard combo, mouse combo, game controller combo, and cross-device combo.
13+
- Added a cross-device combo descriptor so Input Mapping V2 can present combo capture across enabled keyboard, mouse, and game controller sources.
14+
- Refined combo capture copy and flow so keyboard + keyboard, keyboard + mouse, game controller + keyboard, and game controller + mouse combos can add multiple captured inputs to one selected mapping/action.
15+
- Preserved gamepad capture behavior while improving combo polling so mocked or detected gamepad input can be paired with keyboard/mouse input.
16+
- Removed click-to-delete behavior from captured mapping tokens. Tokens are now display items, not delete controls.
17+
- Added Captured Mappings action controls after an `<hr>` separator:
18+
- `Delete Action`
19+
- `Delete Mappings`
20+
- existing `Delete All`
21+
- `Delete Action` removes the selected action tile.
22+
- `Delete Mappings` clears mappings from the selected action tile while keeping the action tile.
23+
- Reduced captured mapping action button border radius so taller buttons read as buttons instead of oval pills.
24+
25+
## Playwright Impact
26+
Playwright impacted: Yes.
27+
28+
Behavior validated:
29+
- Gesture hover/title help exists and includes use-case guidance.
30+
- Cross-device combo gesture is surfaced when multiple compatible devices are enabled.
31+
- `Shift + Mouse Right Button` combo can be represented.
32+
- `Joystick Button 1 + Keyboard Alt` combo can be represented with mocked gamepad plus keyboard input.
33+
- Clicking captured mapping tokens no longer deletes them.
34+
- `Delete Action` removes the selected action tile.
35+
- `Delete Mappings` clears mappings while preserving the selected action tile.
36+
- `<hr>` appears before the Captured Mappings action buttons.
37+
- Captured Mappings action buttons use reduced border radius.
38+
- Existing selected tile, combo display, and gamepad capture behavior remain covered by the Input Mapping V2 workspace tests.
39+
40+
Expected pass behavior:
41+
- All focused Input Mapping V2 assertions pass under `npm run test:workspace-v2`.
42+
- Gesture help is discoverable via title attributes.
43+
- Combo mappings show all captured inputs on the same selected action tile.
44+
45+
Expected fail behavior:
46+
- Tests fail if gesture help text is missing, tokens delete on click, combo pairings are not represented, or delete controls do not perform their scoped action.
47+
48+
## Validation
49+
- Targeted syntax/import validation: PASS.
50+
- Targeted engine input unit validation: PASS.
51+
- Focused Input Mapping V2 Playwright coverage: PASS, 2 tests.
52+
- `npm run test:workspace-v2`: PASS, 61 tests.
53+
- Playwright V8 coverage report for changed runtime JS: PASS, advisory only.
54+
- `git diff --check`: PASS, line-ending warnings only.
55+
- HTML inline script/style/handler scan: PASS, no matches.
56+
- Sample/JSON diff scan: PASS, no changed sample or JSON files.
57+
- Full samples smoke test: not run, per request and project instructions.
58+
59+
## Manual Validation
60+
1. Open Workspace Manager V2 and launch Input Mapping V2.
61+
2. Hover each visible gesture button and confirm the title explains the intended use case.
62+
3. Create an action, capture a keyboard combo such as `Ctrl + R`, then confirm both inputs display on one tile.
63+
4. Capture `Shift + Mouse Right Button` and confirm the combo displays without deleting when tokens are clicked.
64+
5. With a game controller available or mocked, capture `Game Controller Button 1 + Keyboard Alt`.
65+
6. Select a tile and use `Delete Mappings`; expected: mappings clear while the tile remains.
66+
7. Select a tile and use `Delete Action`; expected: the selected action tile is removed.
67+
8. Confirm `Delete All` is still available and not duplicated with `Delete Action` or `Delete Mappings`.
68+
69+
## Out Of Scope
70+
- Sample JSON alignment.
71+
- Full samples smoke test.
72+
- Schema changes.

src/engine/input/InputCapabilityDescriptors.js

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -65,24 +65,25 @@ const DEVICE_DEFINITIONS = Object.freeze([
6565
]);
6666

6767
const GESTURE_DEFINITIONS = Object.freeze([
68-
keyboardGesture('KeyboardPress', 'Press', 'Keyboard key press', 'keyboard'),
69-
keyboardGesture('KeyboardRelease', 'Release', 'Keyboard key release', 'keyboard'),
70-
keyboardGesture('KeyboardHold', 'Hold', 'Keyboard key hold', 'keyboard'),
71-
comboGesture('KeyboardCombo', 'Keyboard', ['keyboard']),
72-
mouseGesture('MouseClick', 'Click', 'Mouse click', 'mouse'),
73-
mouseGesture('MouseDoubleClick', 'Double Click', 'Mouse double click', 'mouse'),
74-
pointerGesture('MousePrimaryDrag', 'Drag', 'Mouse drag', 'mouse'),
75-
pointerGesture('MousePrimaryDragRelease', 'Drag Release', 'Mouse drag release', 'mouse'),
76-
wheelGesture('MouseWheelUp', 'Wheel Up', 'Mouse wheel up'),
77-
wheelGesture('MouseWheelDown', 'Wheel Down', 'Mouse wheel down'),
78-
wheelGesture('MouseWheelLeft', 'Wheel Left', 'Mouse wheel left'),
79-
wheelGesture('MouseWheelRight', 'Wheel Right', 'Mouse wheel right'),
80-
comboGesture('MouseCombo', 'Mouse', ['mouse']),
81-
gameControllerGesture('GameControllerButton', 'Button', 'Game controller button', 'gameController'),
82-
gameControllerGesture('GameControllerTrigger', 'Trigger', 'Game controller trigger', 'gameController'),
83-
gameControllerGesture('GameControllerStick', 'Stick', 'Game controller stick', 'gameController'),
84-
gameControllerGesture('GameControllerDPad', 'DPad', 'Game controller DPad', 'gameController'),
85-
comboGesture('GameControllerCombo', 'Game Controller', ['gameController'])
68+
keyboardGesture('KeyboardPress', 'Press', 'Keyboard key press. Use for one-shot actions such as jump, confirm, or fire.', 'keyboard'),
69+
keyboardGesture('KeyboardRelease', 'Release', 'Keyboard key release. Use for actions that trigger when a key is lifted.', 'keyboard'),
70+
keyboardGesture('KeyboardHold', 'Hold', 'Keyboard key hold. Use for continuous movement while a key remains down.', 'keyboard'),
71+
comboGesture('KeyboardCombo', 'Keyboard', ['keyboard'], 'Keyboard Combo. Use for shortcuts such as Ctrl + R. Capture two keyboard inputs for one selected action.'),
72+
mouseGesture('MouseClick', 'Click', 'Mouse click. Use for direct pointing actions such as select, confirm, or fire.', 'mouse'),
73+
mouseGesture('MouseDoubleClick', 'Double Click', 'Mouse double click. Use for quick repeated pointer actions such as open, focus, or inspect.', 'mouse'),
74+
pointerGesture('MousePrimaryDrag', 'Drag', 'Mouse Drag. Use for continuous movement while held, such as panning or scrolling a map.', 'mouse'),
75+
pointerGesture('MousePrimaryDragRelease', 'Drag Release', 'Mouse Drag Release. Use for completed drag gestures, such as box-selecting objects from start/end positions.', 'mouse'),
76+
wheelGesture('MouseWheelUp', 'Wheel Up', 'Mouse Wheel Up. Use for zoom, scrolling, or cycling selections.'),
77+
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.'),
80+
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.'),
81+
gameControllerGesture('GameControllerButton', 'Button', 'Game controller button. Use for face buttons, shoulder buttons, and digital controller controls.', 'gameController'),
82+
gameControllerGesture('GameControllerTrigger', 'Trigger', 'Game controller trigger. Use for analog trigger actions such as accelerate, brake, or charge.', 'gameController'),
83+
gameControllerGesture('GameControllerStick', 'Stick', 'Game controller stick. Use for analog movement, aiming, or steering.', 'gameController'),
84+
gameControllerGesture('GameControllerDPad', 'DPad', 'Game controller DPad. Use for directional menu or movement actions.', 'gameController'),
85+
comboGesture('GameControllerCombo', 'Game Controller', ['gameController'], 'Game Controller Combo. Use for controller combinations such as Button 1 + Button 2.'),
86+
crossDeviceComboGesture()
8687
]);
8788

8889
export function inputDeviceCapabilities({
@@ -157,6 +158,7 @@ export function inputGestureDescriptors({
157158
const enabled = new Set(enabledDeviceIds);
158159
return GESTURE_DEFINITIONS.filter((gesture) => (
159160
gesture.requiredDeviceIds.every((deviceId) => enabled.has(deviceId))
161+
&& (!gesture.anyOfDeviceIds || gesture.anyOfDeviceIds.filter((deviceId) => enabled.has(deviceId)).length >= gesture.minimumEnabledDevices)
160162
&& (gesture.captureKind !== 'wheel' || wheelAvailable || advancedModeAvailable)
161163
)).map((gesture) => ({ ...gesture }));
162164
}
@@ -243,7 +245,7 @@ function wheelGesture(binding, label, title) {
243245
};
244246
}
245247

246-
function comboGesture(binding, deviceLabel, requiredDeviceIds) {
248+
function comboGesture(binding, deviceLabel, requiredDeviceIds, title) {
247249
return {
248250
binding,
249251
captureKind: 'combo',
@@ -253,7 +255,23 @@ function comboGesture(binding, deviceLabel, requiredDeviceIds) {
253255
label: 'Combo',
254256
requiredDeviceIds,
255257
source: requiredDeviceIds[0] === 'gameController' ? 'gamepad' : requiredDeviceIds[0],
256-
title: `${deviceLabel} combo`
258+
title
259+
};
260+
}
261+
262+
function crossDeviceComboGesture() {
263+
return {
264+
binding: 'CrossDeviceCombo',
265+
captureKind: 'combo',
266+
deviceLabel: 'Cross-device',
267+
displayLabelLines: ['Cross-device', 'Combo'],
268+
engine: 'InputService Combo',
269+
label: 'Combo',
270+
anyOfDeviceIds: ['keyboard', 'mouse', 'gameController'],
271+
minimumEnabledDevices: 2,
272+
requiredDeviceIds: [],
273+
source: 'keyboard',
274+
title: 'Cross-device Combo. Use for combinations such as Joystick Button 1 + Keyboard Alt. Capture any two keyboard, mouse, wheel, or game controller inputs for one selected action.'
257275
};
258276
}
259277

tests/input/InputService.test.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ export function run() {
8585
assert(!capabilities.some((device) => device.label === 'Wheel'), 'Input capabilities should treat wheel as mouse input instead of a device.');
8686
const gestures = input.getInputGestureDescriptors({ enabledDeviceIds: ['keyboard', 'mouse'] });
8787
assert(gestures.some((gesture) => gesture.binding === 'MouseWheelUp'), 'Input gestures should expose wheel descriptors through Mouse when wheel is available.');
88+
assert(gestures.some((gesture) => gesture.binding === 'CrossDeviceCombo'), 'Input gestures should expose cross-device combos when two combo-capable devices are enabled.');
89+
assert(
90+
gestures.find((gesture) => gesture.binding === 'MousePrimaryDrag').title.includes('continuous movement while held'),
91+
'Gesture descriptors should expose use-case title help.'
92+
);
8893
const gesturesWithoutWheelSupport = input.getInputGestureDescriptors({ enabledDeviceIds: ['keyboard', 'mouse'], wheelAvailable: false });
8994
assert(!gesturesWithoutWheelSupport.some((gesture) => gesture.binding === 'MouseWheelUp'), 'Input gestures should hide wheel descriptors when wheel support is unavailable.');
9095
assert(!gestures.some((gesture) => gesture.binding === 'MousePrimaryDragRectangle'), 'Visible input gestures should not expose Drag Rectangle.');

0 commit comments

Comments
 (0)