-
Notifications
You must be signed in to change notification settings - Fork 2
Enable holdable onscreen controls with combo support #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR enables holdable onscreen controls with support for key combinations and improved touch handling for a gamepad-style interface. The changes enhance the user experience by allowing keys to be held for repeated actions and supporting multi-button combinations like SELECT+ENTER.
- Enhanced keyboard controls to track pressed keys and support key combinations with repeating behavior
- Converted mouse-based button interactions to pointer events for better touch device support
- Added combo key handling in the interface logic, specifically SELECT+ENTER for menu navigation
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| client/interface/types/index.ts | Updated interface props from mouse events to generic press events |
| client/interface/hooks/useKeyboardControls.ts | Complete rewrite to track pressed keys, handle repeating, and support combinations |
| client/interface/components/MenuButton.tsx | Converted from mouse to pointer events with proper touch handling |
| client/interface/components/GamepadButton.tsx | Converted from mouse to pointer events with proper touch handling |
| client/interface/components/DPad.tsx | Updated prop names to match new interface |
| client/interface/Interface.tsx | Modified action handler to process key combinations and multiple keys |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| void triggerAction(); | ||
| repeatIntervalRef.current = window.setInterval(() => { | ||
| void triggerAction(); | ||
| }, REPEAT_INTERVAL); |
Copilot
AI
Sep 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The repeat mechanism triggers actions without checking if keys are still pressed. If keys are released during the timeout/interval, actions will continue firing. Consider checking pressedKeysRef.current.length > 0 before calling triggerAction().
| void triggerAction(); | |
| repeatIntervalRef.current = window.setInterval(() => { | |
| void triggerAction(); | |
| }, REPEAT_INTERVAL); | |
| if (pressedKeysRef.current.length > 0) { | |
| void triggerAction(); | |
| repeatIntervalRef.current = window.setInterval(() => { | |
| if (pressedKeysRef.current.length > 0) { | |
| void triggerAction(); | |
| } else { | |
| stopRepeat(); | |
| } | |
| }, REPEAT_INTERVAL); | |
| } else { | |
| stopRepeat(); | |
| } |
| isPressedRef.current = false; | ||
|
|
||
| if (event.currentTarget.hasPointerCapture?.(event.pointerId)) { | ||
| event.currentTarget.releasePointerCapture(event.pointerId); |
Copilot
AI
Sep 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using optional chaining on hasPointerCapture but calling releasePointerCapture unconditionally. If hasPointerCapture doesn't exist, releasePointerCapture will also likely not exist and will throw an error. Wrap the releasePointerCapture call in a try-catch or check for its existence.
| event.currentTarget.releasePointerCapture(event.pointerId); | |
| if (event.currentTarget.releasePointerCapture) { | |
| event.currentTarget.releasePointerCapture(event.pointerId); | |
| } |
| isPressedRef.current = false; | ||
|
|
||
| if (event.currentTarget.hasPointerCapture?.(event.pointerId)) { | ||
| event.currentTarget.releasePointerCapture(event.pointerId); |
Copilot
AI
Sep 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using optional chaining on hasPointerCapture but calling releasePointerCapture unconditionally. If hasPointerCapture doesn't exist, releasePointerCapture will also likely not exist and will throw an error. Wrap the releasePointerCapture call in a try-catch or check for its existence.
| event.currentTarget.releasePointerCapture(event.pointerId); | |
| if (event.currentTarget.releasePointerCapture) { | |
| event.currentTarget.releasePointerCapture(event.pointerId); | |
| } |
client/interface/Interface.tsx
Outdated
| const comboKey = Array.from(keySet).sort().join("+"); | ||
| switch (comboKey) { | ||
| case "Enter+`": |
Copilot
AI
Sep 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The combo key generation uses string concatenation which could lead to ambiguous combinations. For example, keys 'A+B' and 'AB' would be indistinguishable. Consider using a more robust delimiter or key format to avoid potential conflicts.
| const comboKey = Array.from(keySet).sort().join("+"); | |
| switch (comboKey) { | |
| case "Enter+`": | |
| const comboKey = JSON.stringify(Array.from(keySet).sort()); | |
| switch (comboKey) { | |
| case JSON.stringify(["Enter", "`"].sort()): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| if (pressedKeysRef.current.length === 0) { | ||
| return; | ||
| } | ||
| pressedKeysRef.current.length = 0; |
Copilot
AI
Sep 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Setting array length to 0 is less explicit than using pressedKeysRef.current = [] or pressedKeysRef.current.splice(0). Consider using a more conventional array clearing method for better code clarity.
| window.removeEventListener("keyup", handleKeyUp); | ||
| window.removeEventListener("blur", handleBlur); | ||
| stopRepeat(); | ||
| pressedKeysRef.current.length = 0; |
Copilot
AI
Sep 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Setting array length to 0 is less explicit than using pressedKeysRef.current = [] or pressedKeysRef.current.splice(0). Consider using a more conventional array clearing method for better code clarity.
Summary
useKeyboardControlsto repeat held inputs and surface simultaneous key combinations to the interface logicTesting
https://chatgpt.com/codex/tasks/task_e_68cf1709948083278a204de2532d9a4d