Skip to content

Commit dd692ac

Browse files
author
DavidQ
committed
Polish Input Mapping V2 gesture and capture flow - PR_26140_099-polish-input-mapping-v2-gesture-capture-flow
1 parent 4685fdf commit dd692ac

3 files changed

Lines changed: 144 additions & 22 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Input Mapping V2 Gesture Capture Flow Report
2+
3+
## Scope
4+
- PR: PR_26140_099-polish-input-mapping-v2-gesture-capture-flow
5+
- Source of truth: user PR_099 request. `docs/pr/BUILD_PR.md` still points at an unrelated Level 18 overlay runtime hardening rebase, so this workflow used the explicit PR_099 request after reading `docs/dev/PROJECT_INSTRUCTIONS.md`.
6+
7+
## Changes Applied
8+
- Updated Input Mapping V2 gesture groups to use a responsive card grid so Keyboard, Mouse, and Game Controller gesture cards can sit side by side at wide widths and wrap at narrow widths.
9+
- Updated gesture button layout inside each gesture card to use responsive wrapped columns, allowing buttons to flow side by side and top-to-bottom.
10+
- Updated the Capture accordion to use a wrapping row flow for capture sections, with Keyboard/Mouse and game controller capture controls able to share horizontal space.
11+
- Kept the capture note, HR separator, and Refresh Gamepads control as full-width bottom rows, with Refresh Gamepads remaining the bottom-most DOM and visual item.
12+
- Preserved PR_098 nav, layout, and labels; gamepad auto-poll; capture highlighting/cancel/timeout behavior; and captured mapping tile behavior.
13+
- Added focused Playwright geometry assertions for gesture card flow, gesture button wrapping, capture button flow, and bottom anchoring of note/HR/Refresh.
14+
15+
## Contracts And Constraints
16+
- No schema changes.
17+
- No sample JSON changes.
18+
- No runtime JavaScript changes; this PR is CSS plus Playwright coverage.
19+
- External JS/CSS-only HTML restriction preserved; changed Input Mapping HTML was not modified in this PR and the scan found no inline script/style/event-handler matches.
20+
- Full samples smoke test was skipped per request and because this scoped UI polish does not broadly affect sample loading/runtime.
21+
22+
## Validation
23+
- `node --check tools/input-mapping-v2/js/bootstrap.js`: PASS
24+
- `node --check tools/input-mapping-v2/js/ToolStarterApp.js`: PASS
25+
- `node --check tools/input-mapping-v2/js/controls/GestureListControl.js`: PASS
26+
- `node --check tools/input-mapping-v2/js/controls/CaptureControl.js`: PASS
27+
- `node --check tests/playwright/tools/WorkspaceManagerV2.spec.mjs`: PASS
28+
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "Input Mapping V2"`: PASS, 2 tests after CSS correction
29+
- `npm run test:workspace-v2`: PASS, 61 tests
30+
- `git diff --check`: PASS, line-ending warnings only
31+
- `rg --pcre2 -n "<script(?![^>]*src=)|<style| on[a-zA-Z]+=" tools/input-mapping-v2/index.html tools/input-mapping-v2/how_to_use.html`: PASS, no matches
32+
- Sample/JSON diff scan: PASS, no changed sample or JSON files
33+
- Full samples smoke test: not run, per request
34+
- Delta ZIP verification: PASS, 7 files, nonzero size
35+
36+
## Manual Validation
37+
1. Open `tools/input-mapping-v2/index.html` at a wide desktop viewport.
38+
2. Confirm the `Gestures` accordion shows Keyboard, Mouse, and Game Controller cards side by side where space allows.
39+
3. Confirm gesture buttons wrap within cards in side-by-side rows, then narrow the viewport and confirm they wrap down cleanly.
40+
4. Confirm the `Capture` accordion shows capture controls in a horizontal wrapping flow where space allows.
41+
5. Confirm the capture note remains above the HR, and `Refresh Gamepads` remains the bottom-most control.
42+
6. Click Capture Keyboard and Capture Mouse to confirm highlight/cancel behavior still works.
43+
7. Connect or mock gamepads and confirm auto-poll capture buttons still appear and remain assignable.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,6 +1518,35 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15181518
await expect(page.locator("#inputMappingV2GestureList")).toContainText("Drag Rectangle");
15191519
await expect(page.locator("#inputMappingV2GestureList")).toContainText("Wheel Up");
15201520
await expect(page.locator("#inputMappingV2GestureList")).toContainText("Button");
1521+
const gestureFlowLayout = await page.locator("#inputMappingV2GestureList").evaluate((container) => {
1522+
const wantedGroups = ["Keyboard", "Mouse", "Game Controller"];
1523+
const groupLayouts = wantedGroups.map((label) => {
1524+
const group = Array.from(container.querySelectorAll(".input-mapping-v2__gesture-group"))
1525+
.find((candidate) => candidate.textContent.includes(label));
1526+
const box = group.getBoundingClientRect();
1527+
const buttons = Array.from(group.querySelectorAll(".input-mapping-v2__gesture-button")).map((button) => {
1528+
const buttonBox = button.getBoundingClientRect();
1529+
return {
1530+
left: Math.round(buttonBox.left),
1531+
top: Math.round(buttonBox.top)
1532+
};
1533+
});
1534+
return {
1535+
label,
1536+
left: Math.round(box.left),
1537+
top: Math.round(box.top),
1538+
width: Math.round(box.width),
1539+
buttons
1540+
};
1541+
});
1542+
return { groupLayouts };
1543+
});
1544+
expect(gestureFlowLayout.groupLayouts.every((entry) => entry.width >= 210)).toBe(true);
1545+
expect(Math.max(...gestureFlowLayout.groupLayouts.map((entry) => entry.top)) - Math.min(...gestureFlowLayout.groupLayouts.map((entry) => entry.top))).toBeLessThanOrEqual(1);
1546+
expect(new Set(gestureFlowLayout.groupLayouts.map((entry) => entry.left)).size).toBe(3);
1547+
const keyboardGestureButtons = gestureFlowLayout.groupLayouts.find((entry) => entry.label === "Keyboard").buttons;
1548+
expect(new Set(keyboardGestureButtons.map((entry) => entry.left)).size).toBeGreaterThan(1);
1549+
expect(new Set(keyboardGestureButtons.map((entry) => entry.top)).size).toBeGreaterThan(1);
15211550
await page.locator(".input-mapping-v2__device-card[data-input-mapping-device-id='wheel'] input").uncheck();
15221551
await expect(page.locator(".input-mapping-v2__gesture-button", { hasText: "Wheel Up" })).toHaveCount(0);
15231552
await page.locator(".input-mapping-v2__device-card[data-input-mapping-device-id='wheel'] input").check();
@@ -1534,24 +1563,30 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15341563
await expect(page.locator("#previewOutput")).toContainText("No inputs captured yet.");
15351564
await expect(page.locator(".input-mapping-v2__mapping-card")).toHaveCount(0);
15361565
expect(await page.locator("#previewOutput").evaluate((node) => getComputedStyle(node).overflowY)).toBe("auto");
1537-
const captureButtonLayout = await page.locator("#captureInputContent").evaluate((content) => {
1538-
const buttons = [
1539-
content.querySelector("#inputMappingV2CaptureKeyboardButton"),
1540-
content.querySelector("#inputMappingV2CaptureMouseButton"),
1541-
content.querySelector("#inputMappingV2RefreshGamepadsButton")
1542-
];
1543-
return buttons.map((button) => {
1544-
const box = button.getBoundingClientRect();
1566+
const captureFlowLayout = await page.locator("#captureInputContent").evaluate((content) => {
1567+
const rectFor = (selector) => {
1568+
const box = content.querySelector(selector).getBoundingClientRect();
15451569
return {
15461570
left: Math.round(box.left),
15471571
top: Math.round(box.top),
15481572
width: Math.round(box.width)
15491573
};
1550-
});
1551-
});
1552-
expect(captureButtonLayout.every((entry) => entry.width > 250)).toBe(true);
1553-
expect(new Set(captureButtonLayout.map((entry) => entry.left)).size).toBe(1);
1554-
expect(captureButtonLayout[1].top).toBeGreaterThan(captureButtonLayout[0].top);
1574+
};
1575+
return {
1576+
keyboard: rectFor("#inputMappingV2CaptureKeyboardButton"),
1577+
mouse: rectFor("#inputMappingV2CaptureMouseButton"),
1578+
note: rectFor("#inputMappingV2CaptureMessage"),
1579+
refresh: rectFor("#inputMappingV2RefreshGamepadsButton"),
1580+
separator: rectFor(".input-mapping-v2__capture-separator")
1581+
};
1582+
});
1583+
expect(captureFlowLayout.keyboard.width).toBeGreaterThan(110);
1584+
expect(captureFlowLayout.mouse.width).toBeGreaterThan(110);
1585+
expect(captureFlowLayout.mouse.left).toBeGreaterThan(captureFlowLayout.keyboard.left);
1586+
expect(Math.abs(captureFlowLayout.mouse.top - captureFlowLayout.keyboard.top)).toBeLessThanOrEqual(1);
1587+
expect(captureFlowLayout.note.top).toBeGreaterThan(captureFlowLayout.keyboard.top);
1588+
expect(captureFlowLayout.separator.top).toBeGreaterThan(captureFlowLayout.note.top);
1589+
expect(captureFlowLayout.refresh.top).toBeGreaterThan(captureFlowLayout.separator.top);
15551590
const captureBottom = await page.locator("#captureInputContent").evaluate((content) => {
15561591
const refresh = content.querySelector("#inputMappingV2RefreshGamepadsButton");
15571592
return {
@@ -1560,7 +1595,6 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15601595
separatorTag: refresh?.previousElementSibling?.tagName
15611596
};
15621597
});
1563-
expect(captureButtonLayout[2].top).toBeGreaterThan(captureButtonLayout[1].top);
15641598
expect(captureBottom).toEqual({
15651599
lastElementId: "inputMappingV2RefreshGamepadsButton",
15661600
separatorClass: "input-mapping-v2__capture-separator",
@@ -1769,9 +1803,9 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17691803
width: Math.round(box.width)
17701804
};
17711805
}));
1772-
expect(gamepadButtonLayout.every((entry) => entry.width > 250)).toBe(true);
1773-
expect(new Set(gamepadButtonLayout.map((entry) => entry.left)).size).toBe(1);
1774-
expect(gamepadButtonLayout[1].top).toBeGreaterThan(gamepadButtonLayout[0].top);
1806+
expect(gamepadButtonLayout.every((entry) => entry.width > 170)).toBe(true);
1807+
expect(new Set(gamepadButtonLayout.map((entry) => entry.left)).size).toBe(2);
1808+
expect(Math.abs(gamepadButtonLayout[1].top - gamepadButtonLayout[0].top)).toBeLessThanOrEqual(1);
17751809
await page.locator(".input-mapping-v2__mapping-card[data-input-mapping-tile-action-id='moveLeft']").click({ position: { x: 12, y: 12 } });
17761810
await expect(page.locator("#inputMappingV2ActionSelect")).toHaveValue("moveLeft");
17771811
await page.locator(".input-mapping-v2__gamepad-capture-button[data-input-mapping-gamepad-index='1']").click();

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

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
}
1313

1414
.input-mapping-v2 #captureInputContent .input-mapping-v2__button-row {
15-
grid-template-columns: 1fr;
15+
display: flex;
16+
flex-wrap: wrap;
17+
flex: 1 1 260px;
18+
order: 1;
1619
}
1720

1821
.input-mapping-v2__checkbox-field {
@@ -25,11 +28,29 @@
2528

2629
.input-mapping-v2 #inputMappingV2CaptureKeyboardButton,
2730
.input-mapping-v2 #inputMappingV2CaptureMouseButton,
28-
.input-mapping-v2 #inputMappingV2RefreshGamepadsButton,
2931
.input-mapping-v2__gamepad-capture-button {
3032
width: 100%;
3133
}
3234

35+
.input-mapping-v2 #captureInputContent {
36+
display: flex;
37+
flex-direction: row;
38+
flex-wrap: wrap;
39+
align-content: start;
40+
gap: 8px;
41+
}
42+
43+
.input-mapping-v2 #inputMappingV2SelectedActionLabel {
44+
flex: 1 0 100%;
45+
min-width: 100%;
46+
order: 0;
47+
}
48+
49+
.input-mapping-v2 #captureInputContent .input-mapping-v2__capture-button {
50+
flex: 1 1 120px;
51+
width: auto;
52+
}
53+
3354
.input-mapping-v2__capture-button.is-capturing,
3455
.input-mapping-v2__gamepad-capture-button.is-capturing {
3556
border-color: var(--tool-starter-accent) !important;
@@ -39,7 +60,9 @@
3960

4061
.input-mapping-v2__gamepad-buttons {
4162
display: grid;
42-
grid-template-columns: 1fr;
63+
flex: 1 1 320px;
64+
order: 1;
65+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
4366
gap: 8px;
4467
}
4568

@@ -53,12 +76,29 @@
5376
}
5477

5578
.input-mapping-v2__capture-separator {
79+
flex: 1 0 100%;
80+
min-width: 100%;
81+
order: 21;
5682
width: 100%;
5783
border: 0;
5884
border-top: 1px solid var(--tool-starter-line);
5985
margin: 2px 0;
6086
}
6187

88+
.input-mapping-v2 #inputMappingV2CaptureMessage {
89+
flex: 1 0 100%;
90+
min-width: 100%;
91+
margin: 0;
92+
order: 20;
93+
}
94+
95+
.input-mapping-v2 #inputMappingV2RefreshGamepadsButton {
96+
flex: 1 0 100%;
97+
min-width: 100%;
98+
order: 22;
99+
width: 100%;
100+
}
101+
62102
.input-mapping-v2 .tool-starter__panel--center,
63103
.input-mapping-v2 .tool-starter__panel--right {
64104
overflow: hidden;
@@ -79,6 +119,11 @@
79119
gap: 10px;
80120
}
81121

122+
.input-mapping-v2__gesture-groups {
123+
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
124+
align-items: start;
125+
}
126+
82127
.input-mapping-v2__mapping-list {
83128
grid-template-columns: repeat(auto-fill, 225px);
84129
grid-auto-rows: 225px;
@@ -184,8 +229,8 @@
184229
}
185230

186231
.input-mapping-v2__gesture-list {
187-
display: flex;
188-
flex-wrap: wrap;
232+
display: grid;
233+
grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
189234
gap: 8px;
190235
}
191236

0 commit comments

Comments
 (0)