Skip to content

Commit 9a1c339

Browse files
committed
Simplify Input Mapping V2 mouse gestures - PR_26140_100-simplify-input-mapping-v2-mouse-gestures
1 parent dd692ac commit 9a1c339

7 files changed

Lines changed: 148 additions & 11 deletions

File tree

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Input Mapping V2 Mouse Gesture Simplification Report
2+
3+
## Scope
4+
- PR: PR_26140_100-simplify-input-mapping-v2-mouse-gestures
5+
- Source of truth: user PR_100 request. `docs/pr/BUILD_PR.md` still points at an unrelated Level 18 overlay runtime hardening rebase, so this workflow used the explicit PR_100 request after reading `docs/dev/PROJECT_INSTRUCTIONS.md`.
6+
7+
## Changes Applied
8+
- Removed the visible `Drag Rectangle` mouse gesture from Input Mapping V2 gesture descriptors.
9+
- Preserved `Drag Release` as the visible drag completion gesture.
10+
- Preserved internal `PointerDragState` rectangle/bounds tracking and attached the current pointer drag snapshot to Drag Release runtime captures.
11+
- Added bounds detail to Drag Release captured token title text without changing the persisted Input Mapping V2 schema payload.
12+
- Stopped gesture button pointer events from bubbling into the tool-level engine input listener so clicking `Drag Release` does not overwrite the drag bounds it is capturing.
13+
- Preserved wheel gesture options for `Wheel Up`, `Wheel Down`, `Wheel Left`, and `Wheel Right`.
14+
- Confirmed raw `Mouse Move X/Y` is not exposed as a selectable mapping option.
15+
- Tightened Gesture/Capture spacing and reduced Capture button heights.
16+
17+
## Contracts And Constraints
18+
- No schema changes.
19+
- No sample JSON changes.
20+
- No full samples smoke test; skipped per request because this is scoped to Input Mapping V2 and engine input descriptors.
21+
- Drag bounds are runtime/tool-local metadata (`input.pointerDrag`) and hover/title detail only; they are intentionally not persisted in the strict schema payload.
22+
23+
## Validation
24+
- `node --check src/engine/input/InputCapabilityDescriptors.js`: PASS
25+
- `node --check src/engine/input/PointerDragState.js`: PASS
26+
- `node --check tools/input-mapping-v2/js/services/EngineInputSourceService.js`: PASS
27+
- `node --check tools/input-mapping-v2/js/controls/GestureListControl.js`: PASS
28+
- `node --check tests/input/InputService.test.mjs`: PASS
29+
- `node --check tests/playwright/tools/WorkspaceManagerV2.spec.mjs`: PASS
30+
- `node -e "const test = await import('./tests/input/InputService.test.mjs'); test.run();"`: PASS
31+
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "Input Mapping V2"`: PASS, 2 tests after compact-height alignment fix
32+
- `npm run test:workspace-v2`: PASS, 61 tests
33+
- `git diff --check`: PASS, line-ending warnings only
34+
- HTML inline script/style/handler scan: PASS, no matches
35+
- Sample/JSON diff scan: PASS, no changed sample or JSON files
36+
- Playwright V8 coverage report: PASS, changed runtime JS listed in `docs/dev/reports/playwright_v8_coverage_report.txt` and `coverage_changed_js_guardrail.txt`
37+
- Full samples smoke test: not run, per request
38+
- Delta ZIP verification: PASS, 13 files, nonzero size
39+
40+
## Playwright Coverage
41+
- Confirms `Drag Rectangle` is not visible.
42+
- Confirms `Drag Release` remains visible and maps to `MousePrimaryDragRelease`.
43+
- Confirms Drag Release token title includes drag bounds from `PointerDragState`.
44+
- Confirms `Mouse Wheel Up`, `Mouse Wheel Down`, `Mouse Wheel Left`, and `Mouse Wheel Right` remain visible/capturable.
45+
- Confirms raw `Mouse Move X/Y` is not visible as a mapping option.
46+
- Confirms compact Gesture/Capture spacing and reduced capture button heights.
47+
48+
## Manual Validation
49+
1. Open `tools/input-mapping-v2/index.html`.
50+
2. In Gestures, confirm Mouse shows Drag Release but not Drag Rectangle.
51+
3. Confirm Wheel Up, Wheel Down, Wheel Left, and Wheel Right are still shown.
52+
4. Confirm no raw Mouse Move X/Y option appears.
53+
5. Drag in the page, then click Drag Release and hover the captured token; bounds should be shown in the title text.
54+
6. Confirm Capture buttons are shorter and the spacing from Gestures/Capture into Captured Mappings is tighter.

src/engine/input/InputCapabilityDescriptors.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ const GESTURE_DEFINITIONS = Object.freeze([
8181
mouseGesture('MouseDoubleClick', 'Double Click', 'Mouse double click', 'mouse'),
8282
pointerGesture('MousePrimaryDrag', 'Drag', 'Mouse drag', 'mouse'),
8383
pointerGesture('MousePrimaryDragRelease', 'Drag Release', 'Mouse drag release', 'mouse'),
84-
pointerGesture('MousePrimaryDragRectangle', 'Drag Rectangle', 'Mouse drag rectangle', 'mouse'),
8584
wheelGesture('MouseWheelUp', 'Wheel Up', 'Mouse wheel up', ['mouse', 'wheel']),
8685
wheelGesture('MouseWheelDown', 'Wheel Down', 'Mouse wheel down', ['mouse', 'wheel']),
8786
wheelGesture('MouseWheelLeft', 'Wheel Left', 'Mouse wheel left', ['mouse', 'wheel']),

tests/input/InputService.test.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,14 @@ export function run() {
7777
const dragRectangle = input.getPointerDragDescriptor('MousePrimaryDragRectangle');
7878
assert(dragRectangle.label === 'Mouse Drag Rectangle', 'Pointer drag descriptors should expose rectangle gestures.');
7979
assert(dragRectangle.source === 'mouse', 'Pointer drag descriptors should remain schema-compatible mouse inputs.');
80+
const dragRelease = input.getPointerDragDescriptor('MousePrimaryDragRelease');
81+
assert(dragRelease.snapshot.dragBounds.width === 40, 'Drag release descriptors should carry drag bounds width.');
82+
assert(dragRelease.snapshot.dragBounds.height === 40, 'Drag release descriptors should carry drag bounds height.');
8083
const capabilities = input.getInputDeviceCapabilities({ gamepadCount: 0 });
8184
assert(capabilities.some((device) => device.label === 'VR Controller'), 'Input capabilities should expose safe VR controller descriptors.');
8285
const gestures = input.getInputGestureDescriptors({ enabledDeviceIds: ['keyboard', 'mouse', 'wheel'] });
8386
assert(gestures.some((gesture) => gesture.binding === 'MouseWheelUp'), 'Input gestures should expose wheel descriptors when wheel is enabled.');
87+
assert(!gestures.some((gesture) => gesture.binding === 'MousePrimaryDragRectangle'), 'Visible input gestures should not expose Drag Rectangle.');
8488

8589
input.detach();
8690
}

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,9 +1515,26 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15151515
await expect(page.locator("#inputMappingV2DeviceList")).toContainText("InputService + MouseState + PointerDragState");
15161516
await expect(page.locator("#inputMappingV2DeviceList")).toContainText("InputService + GamepadState + GamepadInputAdapter");
15171517
await expect(page.locator("#inputMappingV2GestureList")).toContainText("Press");
1518-
await expect(page.locator("#inputMappingV2GestureList")).toContainText("Drag Rectangle");
15191518
await expect(page.locator("#inputMappingV2GestureList")).toContainText("Wheel Up");
1519+
await expect(page.locator("#inputMappingV2GestureList")).toContainText("Wheel Down");
1520+
await expect(page.locator("#inputMappingV2GestureList")).toContainText("Wheel Left");
1521+
await expect(page.locator("#inputMappingV2GestureList")).toContainText("Wheel Right");
1522+
await expect(page.locator("#inputMappingV2GestureList")).toContainText("Drag Release");
1523+
await expect(page.locator("#inputMappingV2GestureList")).not.toContainText("Drag Rectangle");
1524+
await expect(page.locator("#inputMappingV2GestureList")).not.toContainText(/Mouse Move [XY]/);
15201525
await expect(page.locator("#inputMappingV2GestureList")).toContainText("Button");
1526+
const compactAccordionSpacing = await page.locator("#gestureSetupContent, #captureInputContent").evaluateAll((contents) => (
1527+
contents.map((content) => ({
1528+
columnGap: getComputedStyle(content).columnGap,
1529+
id: content.id,
1530+
paddingBottom: getComputedStyle(content).paddingBottom,
1531+
rowGap: getComputedStyle(content).rowGap
1532+
}))
1533+
));
1534+
expect(compactAccordionSpacing).toEqual(expect.arrayContaining([
1535+
expect.objectContaining({ id: "gestureSetupContent", paddingBottom: "6px" }),
1536+
expect.objectContaining({ id: "captureInputContent", paddingBottom: "6px", rowGap: "6px" })
1537+
]));
15211538
const gestureFlowLayout = await page.locator("#inputMappingV2GestureList").evaluate((container) => {
15221539
const wantedGroups = ["Keyboard", "Mouse", "Game Controller"];
15231540
const groupLayouts = wantedGroups.map((label) => {
@@ -1567,6 +1584,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15671584
const rectFor = (selector) => {
15681585
const box = content.querySelector(selector).getBoundingClientRect();
15691586
return {
1587+
height: Math.round(box.height),
15701588
left: Math.round(box.left),
15711589
top: Math.round(box.top),
15721590
width: Math.round(box.width)
@@ -1581,7 +1599,9 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15811599
};
15821600
});
15831601
expect(captureFlowLayout.keyboard.width).toBeGreaterThan(110);
1602+
expect(captureFlowLayout.keyboard.height).toBeLessThanOrEqual(36);
15841603
expect(captureFlowLayout.mouse.width).toBeGreaterThan(110);
1604+
expect(captureFlowLayout.mouse.height).toBeLessThanOrEqual(36);
15851605
expect(captureFlowLayout.mouse.left).toBeGreaterThan(captureFlowLayout.keyboard.left);
15861606
expect(Math.abs(captureFlowLayout.mouse.top - captureFlowLayout.keyboard.top)).toBeLessThanOrEqual(1);
15871607
expect(captureFlowLayout.note.top).toBeGreaterThan(captureFlowLayout.keyboard.top);
@@ -1703,8 +1723,38 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17031723
}));
17041724
});
17051725
await expect(page.locator("#inputMappingV2CaptureMouseButton")).not.toHaveClass(/is-capturing/);
1726+
await page.evaluate(() => {
1727+
window.dispatchEvent(new PointerEvent("pointerdown", {
1728+
bubbles: true,
1729+
button: 0,
1730+
cancelable: true,
1731+
clientX: 12,
1732+
clientY: 16,
1733+
pointerId: 9,
1734+
pointerType: "mouse"
1735+
}));
1736+
window.dispatchEvent(new PointerEvent("pointermove", {
1737+
bubbles: true,
1738+
button: 0,
1739+
buttons: 1,
1740+
cancelable: true,
1741+
clientX: 52,
1742+
clientY: 66,
1743+
pointerId: 9,
1744+
pointerType: "mouse"
1745+
}));
1746+
window.dispatchEvent(new PointerEvent("pointerup", {
1747+
bubbles: true,
1748+
button: 0,
1749+
buttons: 0,
1750+
cancelable: true,
1751+
clientX: 72,
1752+
clientY: 96,
1753+
pointerId: 9,
1754+
pointerType: "mouse"
1755+
}));
1756+
});
17061757
await page.locator(".input-mapping-v2__gesture-button", { hasText: "Drag Release" }).click();
1707-
await page.locator(".input-mapping-v2__gesture-button", { hasText: "Drag Rectangle" }).click();
17081758
await expect(page.locator(".input-mapping-v2__mapping-card")).toHaveCount(1);
17091759
const mappingTileBox = await page.locator(".input-mapping-v2__mapping-card").first().boundingBox();
17101760
expect(Math.round(mappingTileBox.width)).toBe(225);
@@ -1714,13 +1764,14 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17141764
await expect(page.locator("#previewOutput")).toContainText("Keyboard KeyD");
17151765
await expect(page.locator("#previewOutput")).toContainText("Mouse Middle Button");
17161766
await expect(page.locator("#previewOutput")).toContainText("Mouse Drag Release");
1717-
await expect(page.locator("#previewOutput")).toContainText("Mouse Drag Rectangle");
1767+
await expect(page.locator("#previewOutput")).not.toContainText("Mouse Drag Rectangle");
1768+
await expect(page.locator(".input-mapping-v2__input-token", { hasText: "Mouse Drag Release" })).toHaveAttribute("title", /Bounds: x 12, y 16, width 60, height 80/);
17181769
await expect(page.locator("#inspectorOutput")).toContainText('"action": "moveLeft"');
17191770
await expect(page.locator("#inspectorOutput")).toContainText('"binding": "KeyA"');
17201771
await expect(page.locator("#inspectorOutput")).toContainText('"binding": "KeyD"');
17211772
await expect(page.locator("#inspectorOutput")).toContainText('"binding": "MouseButton1"');
17221773
await expect(page.locator("#inspectorOutput")).toContainText('"binding": "MousePrimaryDragRelease"');
1723-
await expect(page.locator("#inspectorOutput")).toContainText('"binding": "MousePrimaryDragRectangle"');
1774+
await expect(page.locator("#inspectorOutput")).not.toContainText('"binding": "MousePrimaryDragRectangle"');
17241775
await page.locator(".input-mapping-v2__input-token", { hasText: "Keyboard KeyA" }).click();
17251776
await expect(page.locator(".input-mapping-v2__input-token", { hasText: "Keyboard KeyA" })).toHaveCount(0);
17261777
await expect(page.locator(".input-mapping-v2__input-token", { hasText: "Keyboard KeyD" })).toHaveCount(1);
@@ -1798,12 +1849,14 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17981849
const gamepadButtonLayout = await page.locator(".input-mapping-v2__gamepad-capture-button").evaluateAll((buttons) => buttons.map((button) => {
17991850
const box = button.getBoundingClientRect();
18001851
return {
1852+
height: Math.round(box.height),
18011853
left: Math.round(box.left),
18021854
top: Math.round(box.top),
18031855
width: Math.round(box.width)
18041856
};
18051857
}));
18061858
expect(gamepadButtonLayout.every((entry) => entry.width > 170)).toBe(true);
1859+
expect(gamepadButtonLayout.every((entry) => entry.height <= 60)).toBe(true);
18071860
expect(new Set(gamepadButtonLayout.map((entry) => entry.left)).size).toBe(2);
18081861
expect(Math.abs(gamepadButtonLayout[1].top - gamepadButtonLayout[0].top)).toBeLessThanOrEqual(1);
18091862
await page.locator(".input-mapping-v2__mapping-card[data-input-mapping-tile-action-id='moveLeft']").click({ position: { x: 12, y: 12 } });

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ export class GestureListControl {
4444
button.dataset.inputMappingGestureBinding = gesture.binding;
4545
button.textContent = gesture.label;
4646
button.title = gesture.title;
47+
["pointerdown", "pointermove", "pointerup", "pointercancel"].forEach((eventName) => {
48+
button.addEventListener(eventName, (event) => {
49+
event.stopPropagation();
50+
});
51+
});
4752
button.addEventListener("click", () => {
4853
this.onGestureSelected(gesture);
4954
});

tools/input-mapping-v2/js/services/EngineInputSourceService.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,9 @@ export class EngineInputSourceService {
152152
binding: descriptor.binding,
153153
displayLabelLines: descriptor.displayLabelLines,
154154
label: descriptor.label,
155-
title: descriptor.title,
156-
engine: descriptor.engine
155+
title: pointerDragTitle(descriptor),
156+
engine: descriptor.engine,
157+
pointerDrag: descriptor.snapshot
157158
}
158159
};
159160
}
@@ -418,6 +419,17 @@ function formatGamepadDeviceLabel(gamepad) {
418419
return gamepadDeviceInfo(gamepad).statusLabel;
419420
}
420421

422+
function pointerDragTitle(descriptor) {
423+
const bounds = descriptor.snapshot?.dragBounds;
424+
if (!bounds) {
425+
return descriptor.title;
426+
}
427+
return [
428+
descriptor.title,
429+
`Bounds: x ${bounds.x}, y ${bounds.y}, width ${bounds.width}, height ${bounds.height}`
430+
].join("\n");
431+
}
432+
421433
function gamepadDeviceInfo(gamepad) {
422434
const index = Number.isInteger(Number(gamepad?.index)) ? Number(gamepad.index) : 0;
423435
const id = normalizeWhitespace(gamepad?.id || "");

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
flex-direction: row;
3838
flex-wrap: wrap;
3939
align-content: start;
40-
gap: 8px;
40+
align-items: flex-start;
41+
gap: 6px 8px;
4142
}
4243

4344
.input-mapping-v2 #inputMappingV2SelectedActionLabel {
@@ -67,7 +68,7 @@
6768
}
6869

6970
.input-mapping-v2__gamepad-capture-button {
70-
min-height: 74px;
71+
min-height: 50px;
7172
white-space: pre-line;
7273
}
7374

@@ -82,7 +83,7 @@
8283
width: 100%;
8384
border: 0;
8485
border-top: 1px solid var(--tool-starter-line);
85-
margin: 2px 0;
86+
margin: 0;
8687
}
8788

8889
.input-mapping-v2 #inputMappingV2CaptureMessage {
@@ -116,7 +117,7 @@
116117
.input-mapping-v2__device-list,
117118
.input-mapping-v2__gesture-groups {
118119
display: grid;
119-
gap: 10px;
120+
gap: 8px;
120121
}
121122

122123
.input-mapping-v2__gesture-groups {
@@ -238,6 +239,15 @@
238239
min-height: 30px;
239240
}
240241

242+
.input-mapping-v2 #gestureSetupContent,
243+
.input-mapping-v2 #captureInputContent {
244+
padding-bottom: 6px;
245+
}
246+
247+
.input-mapping-v2 #captureInputContent .input-mapping-v2__capture-button {
248+
min-height: 34px;
249+
}
250+
241251
.input-mapping-v2__export-menu {
242252
margin: 0;
243253
}

0 commit comments

Comments
 (0)