mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 03:16:46 -07:00
feat(controller): add inline remap modal with descriptor-based bindings (#21)
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
- Added Chrome Gamepad API controller support for keyboard-only overlay mode.
|
||||
- Added configurable controller bindings for lookup, mining, popup navigation, Yomitan audio, mpv pause, and d-pad fallback navigation.
|
||||
- Added smooth, slower popup scrolling for controller navigation.
|
||||
- Added `Alt+C` controller selection and `Alt+Shift+C` controller debug modals, with preferred controller persistence and live raw input inspection.
|
||||
- Expanded `Alt+C` into a controller config/remap modal with preferred-controller saving, inline learn mode, and kept `Alt+Shift+C` for raw input debugging.
|
||||
- Added a transient in-overlay controller-detected indicator when a controller is first found.
|
||||
- Fixed cleanup of stale keyboard-only token highlights when keyboard-only mode is disabled or when the Yomitan popup closes.
|
||||
- Added an enforced `verify:config-example` gate so checked-in example config artifacts cannot drift silently.
|
||||
|
||||
@@ -514,8 +514,10 @@ Important behavior:
|
||||
- Controller input is only active while keyboard-only mode is enabled.
|
||||
- Keyboard-only mode continues to work normally without a controller.
|
||||
- By default SubMiner uses the first connected controller.
|
||||
- `Alt+C` opens the controller selection modal and saves the selected controller for future launches.
|
||||
- `Alt+C` opens the controller config modal, where you can save the selected controller and remap actions inline.
|
||||
- Click `Learn`, then press the next fresh button, trigger, or stick direction you want to bind for that overlay action.
|
||||
- `Alt+Shift+C` opens a live debug modal showing raw axes/button values plus a ready-to-copy `buttonIndices` config block.
|
||||
- `controller.buttonIndices` is a semantic reference/legacy mapping. Changing it does not rewrite the raw numeric descriptor values already stored under `controller.bindings`.
|
||||
- Turning keyboard-only mode off clears the keyboard-only token highlight state.
|
||||
- Closing the Yomitan popup clears the temporary native text-selection fill, but keeps controller token selection active.
|
||||
|
||||
@@ -547,19 +549,19 @@ Important behavior:
|
||||
"rightTrigger": 7
|
||||
},
|
||||
"bindings": {
|
||||
"toggleLookup": "buttonSouth",
|
||||
"closeLookup": "buttonEast",
|
||||
"toggleKeyboardOnlyMode": "buttonNorth",
|
||||
"mineCard": "buttonWest",
|
||||
"quitMpv": "select",
|
||||
"previousAudio": "none",
|
||||
"nextAudio": "rightShoulder",
|
||||
"playCurrentAudio": "leftShoulder",
|
||||
"toggleMpvPause": "leftStickPress",
|
||||
"leftStickHorizontal": "leftStickX",
|
||||
"leftStickVertical": "leftStickY",
|
||||
"rightStickHorizontal": "rightStickX",
|
||||
"rightStickVertical": "rightStickY"
|
||||
"toggleLookup": { "kind": "button", "buttonIndex": 0 },
|
||||
"closeLookup": { "kind": "button", "buttonIndex": 1 },
|
||||
"toggleKeyboardOnlyMode": { "kind": "button", "buttonIndex": 3 },
|
||||
"mineCard": { "kind": "button", "buttonIndex": 2 },
|
||||
"quitMpv": { "kind": "button", "buttonIndex": 6 },
|
||||
"previousAudio": { "kind": "none" },
|
||||
"nextAudio": { "kind": "button", "buttonIndex": 5 },
|
||||
"playCurrentAudio": { "kind": "button", "buttonIndex": 4 },
|
||||
"toggleMpvPause": { "kind": "button", "buttonIndex": 9 },
|
||||
"leftStickHorizontal": { "kind": "axis", "axisIndex": 0, "dpadFallback": "horizontal" },
|
||||
"leftStickVertical": { "kind": "axis", "axisIndex": 1, "dpadFallback": "vertical" },
|
||||
"rightStickHorizontal": { "kind": "axis", "axisIndex": 3, "dpadFallback": "none" },
|
||||
"rightStickVertical": { "kind": "axis", "axisIndex": 4, "dpadFallback": "none" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -581,10 +583,28 @@ Default logical mapping:
|
||||
- `L3`: toggle mpv pause
|
||||
- `L2` / `R2`: unbound by default
|
||||
|
||||
Discrete bindings may use raw button indices or raw axis directions, and analog bindings use raw axis indices with optional D-pad fallback. The `Alt+C` learn flow writes those descriptors for you, so manual edits are only needed when you want to script or copy exact mappings.
|
||||
|
||||
If you bind a discrete action to an axis manually, include `direction`:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"controller": {
|
||||
"bindings": {
|
||||
"toggleLookup": { "kind": "axis", "axisIndex": 5, "direction": "positive" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Treat `controller.buttonIndices` as reference-only unless you are still using legacy semantic bindings or copying values from the debug modal. Updating `controller.buttonIndices` alone does not rewrite the hardcoded raw numeric values already present in `controller.bindings`. If you need a real remap, prefer the `Alt+C` learn flow so both the source and the descriptor shape stay correct.
|
||||
|
||||
If you choose to bind `L2` or `R2` manually, set `triggerInputMode` to `analog` and tune `triggerDeadzone` when your controller reports triggers as analog values instead of digital pressed/not-pressed buttons. `auto` accepts either style and remains the default.
|
||||
|
||||
If your controller reports non-standard raw button numbers, override `controller.buttonIndices` using values from the `Alt+Shift+C` debug modal.
|
||||
|
||||
If you update this controller documentation or the generated controller examples, run `bun run docs:test` and `bun run docs:build` before merging.
|
||||
|
||||
Tune `scrollPixelsPerSecond`, `horizontalJumpPixels`, deadzones, repeat timing, and `buttonIndices` to match your controller. See [config.example.jsonc](/config.example.jsonc) for the full generated comments for every controller field.
|
||||
|
||||
### Manual Card Update Shortcuts
|
||||
|
||||
@@ -53,13 +53,13 @@
|
||||
// ==========================================
|
||||
// Controller Support
|
||||
// Gamepad support for the visible overlay while keyboard-only mode is active.
|
||||
// Use the selection modal to save a preferred controller by id for future launches.
|
||||
// Use Alt+C to pick a preferred controller and remap actions inline with learn mode.
|
||||
// Trigger input mode can be auto, digital-only, or analog-thresholded depending on the controller.
|
||||
// Override controller.buttonIndices when your pad reports non-standard raw button numbers.
|
||||
// ==========================================
|
||||
"controller": {
|
||||
"enabled": true, // Enable overlay controller support through the Chrome Gamepad API. Values: true | false
|
||||
"preferredGamepadId": "", // Preferred controller id saved from the controller selection modal.
|
||||
"preferredGamepadId": "", // Preferred controller id saved from the controller config modal.
|
||||
"preferredGamepadLabel": "", // Preferred controller display label saved for diagnostics.
|
||||
"smoothScroll": true, // Use smooth scrolling for controller-driven popup scroll input. Values: true | false
|
||||
"scrollPixelsPerSecond": 900, // Base popup scroll speed for controller stick input.
|
||||
@@ -81,22 +81,64 @@
|
||||
"rightStickPress": 10, // Raw button index used for controller R3 input.
|
||||
"leftTrigger": 6, // Raw button index used for controller L2 input.
|
||||
"rightTrigger": 7 // Raw button index used for controller R2 input.
|
||||
}, // Button indices setting.
|
||||
}, // Semantic button-name reference mapping used for legacy configs and debug output. Updating it does not rewrite existing raw binding descriptors.
|
||||
"bindings": {
|
||||
"toggleLookup": "buttonSouth", // Controller binding for toggling lookup. Values: none | select | buttonSouth | buttonEast | buttonNorth | buttonWest | leftShoulder | rightShoulder | leftStickPress | rightStickPress | leftTrigger | rightTrigger
|
||||
"closeLookup": "buttonEast", // Controller binding for closing lookup. Values: none | select | buttonSouth | buttonEast | buttonNorth | buttonWest | leftShoulder | rightShoulder | leftStickPress | rightStickPress | leftTrigger | rightTrigger
|
||||
"toggleKeyboardOnlyMode": "buttonNorth", // Controller binding for toggling keyboard-only mode. Values: none | select | buttonSouth | buttonEast | buttonNorth | buttonWest | leftShoulder | rightShoulder | leftStickPress | rightStickPress | leftTrigger | rightTrigger
|
||||
"mineCard": "buttonWest", // Controller binding for mining the active card. Values: none | select | buttonSouth | buttonEast | buttonNorth | buttonWest | leftShoulder | rightShoulder | leftStickPress | rightStickPress | leftTrigger | rightTrigger
|
||||
"quitMpv": "select", // Controller binding for quitting mpv. Values: none | select | buttonSouth | buttonEast | buttonNorth | buttonWest | leftShoulder | rightShoulder | leftStickPress | rightStickPress | leftTrigger | rightTrigger
|
||||
"previousAudio": "none", // Controller binding for previous Yomitan audio. Values: none | select | buttonSouth | buttonEast | buttonNorth | buttonWest | leftShoulder | rightShoulder | leftStickPress | rightStickPress | leftTrigger | rightTrigger
|
||||
"nextAudio": "rightShoulder", // Controller binding for next Yomitan audio. Values: none | select | buttonSouth | buttonEast | buttonNorth | buttonWest | leftShoulder | rightShoulder | leftStickPress | rightStickPress | leftTrigger | rightTrigger
|
||||
"playCurrentAudio": "leftShoulder", // Controller binding for playing the current Yomitan audio. Values: none | select | buttonSouth | buttonEast | buttonNorth | buttonWest | leftShoulder | rightShoulder | leftStickPress | rightStickPress | leftTrigger | rightTrigger
|
||||
"toggleMpvPause": "leftStickPress", // Controller binding for toggling mpv play/pause. Values: none | select | buttonSouth | buttonEast | buttonNorth | buttonWest | leftShoulder | rightShoulder | leftStickPress | rightStickPress | leftTrigger | rightTrigger
|
||||
"leftStickHorizontal": "leftStickX", // Axis binding used for left/right token selection. Values: leftStickX | leftStickY | rightStickX | rightStickY
|
||||
"leftStickVertical": "leftStickY", // Axis binding used for primary popup scrolling. Values: leftStickX | leftStickY | rightStickX | rightStickY
|
||||
"rightStickHorizontal": "rightStickX", // Axis binding reserved for alternate right-stick mappings. Values: leftStickX | leftStickY | rightStickX | rightStickY
|
||||
"rightStickVertical": "rightStickY" // Axis binding used for popup page jumps. Values: leftStickX | leftStickY | rightStickX | rightStickY
|
||||
} // Bindings setting.
|
||||
"toggleLookup": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 0 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for toggling lookup. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"closeLookup": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 1 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for closing lookup. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"toggleKeyboardOnlyMode": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 3 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for toggling keyboard-only mode. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"mineCard": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 2 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for mining the active card. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"quitMpv": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 6 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for quitting mpv. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"previousAudio": {
|
||||
"kind": "none" // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
}, // Controller binding descriptor for previous Yomitan audio. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"nextAudio": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 5 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for next Yomitan audio. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"playCurrentAudio": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 4 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for playing the current Yomitan audio. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"toggleMpvPause": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 9 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for toggling mpv play/pause. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"leftStickHorizontal": {
|
||||
"kind": "axis", // Analog binding input source kind. Values: none | axis
|
||||
"axisIndex": 0, // Raw axis index captured for this analog controller action.
|
||||
"dpadFallback": "horizontal" // Optional D-pad fallback used when this analog controller action should also read D-pad input. Values: none | horizontal | vertical
|
||||
}, // Axis binding descriptor used for left/right token selection. Use Alt+C learn mode or set a raw axis descriptor manually.
|
||||
"leftStickVertical": {
|
||||
"kind": "axis", // Analog binding input source kind. Values: none | axis
|
||||
"axisIndex": 1, // Raw axis index captured for this analog controller action.
|
||||
"dpadFallback": "vertical" // Optional D-pad fallback used when this analog controller action should also read D-pad input. Values: none | horizontal | vertical
|
||||
}, // Axis binding descriptor used for primary popup scrolling. Use Alt+C learn mode or set a raw axis descriptor manually.
|
||||
"rightStickHorizontal": {
|
||||
"kind": "axis", // Analog binding input source kind. Values: none | axis
|
||||
"axisIndex": 3, // Raw axis index captured for this analog controller action.
|
||||
"dpadFallback": "none" // Optional D-pad fallback used when this analog controller action should also read D-pad input. Values: none | horizontal | vertical
|
||||
}, // Axis binding descriptor reserved for alternate right-stick mappings. Use Alt+C learn mode or set a raw axis descriptor manually.
|
||||
"rightStickVertical": {
|
||||
"kind": "axis", // Analog binding input source kind. Values: none | axis
|
||||
"axisIndex": 4, // Raw axis index captured for this analog controller action.
|
||||
"dpadFallback": "none" // Optional D-pad fallback used when this analog controller action should also read D-pad input. Values: none | horizontal | vertical
|
||||
} // Axis binding descriptor used for popup page jumps. Use Alt+C learn mode or set a raw axis descriptor manually.
|
||||
} // Raw controller binding descriptors saved by Alt+C learn mode. For discrete axis bindings, kind "axis" requires axisIndex and direction.
|
||||
}, // Gamepad support for the visible overlay while keyboard-only mode is active.
|
||||
|
||||
// ==========================================
|
||||
|
||||
@@ -75,8 +75,8 @@ These overlay-local shortcuts are fixed and open controller utilities for the Ch
|
||||
|
||||
| Shortcut | Action | Configurable |
|
||||
| ------------- | ------------------------------ | ------------ |
|
||||
| `Alt+C` | Open controller selection modal | Fixed |
|
||||
| `Alt+Shift+C` | Open controller debug modal | Fixed |
|
||||
| `Alt+C` | Open controller config + remap modal | Fixed |
|
||||
| `Alt+Shift+C` | Open controller debug modal | Fixed |
|
||||
|
||||
Controller input only drives the overlay while keyboard-only mode is enabled. The controller mapping and tuning live under the top-level `controller` config block; keyboard-only mode still works normally without a controller.
|
||||
|
||||
|
||||
@@ -254,10 +254,12 @@ SubMiner supports gamepad/controller input for couch-friendly usage via the Chro
|
||||
|
||||
1. Connect a controller before or after launching SubMiner.
|
||||
2. Enable keyboard-only mode — press `Y` on the controller (default binding) or use the overlay keybinding.
|
||||
3. Use the left stick to navigate subtitle tokens and the right stick to scroll the Yomitan popup.
|
||||
4. Press `A` to look up the selected word, `X` to mine a card, `B` to close the popup.
|
||||
3. Press `Alt+C` in the overlay to pick the controller you want to save and remap any action inline.
|
||||
4. Click `Learn` on the overlay action you want, then press the matching button, trigger, or stick direction on the controller.
|
||||
5. Use the left stick to navigate subtitle tokens and scroll the popup; use the right stick vertically for popup page jumps.
|
||||
6. Press `A` to look up the selected word, `X` to mine a card, `B` to close the popup.
|
||||
|
||||
By default SubMiner uses the first connected controller. Press `Alt+C` in the overlay to open the controller selection modal and persist your preferred controller across sessions. Press `Alt+Shift+C` to open a live debug modal showing raw axes and button values.
|
||||
By default SubMiner uses the first connected controller. `Alt+C` opens the controller config modal, where you can save the preferred controller and remap bindings inline. `Alt+Shift+C` still opens the live debug modal with raw axes/button values for non-standard pads.
|
||||
|
||||
### Default Button Mapping
|
||||
|
||||
@@ -278,10 +280,11 @@ By default SubMiner uses the first connected controller. Press `Alt+C` in the ov
|
||||
| Input | Action |
|
||||
| ----- | ------ |
|
||||
| Left stick horizontal | Move token selection left/right |
|
||||
| Left stick vertical | Smooth scroll Yomitan popup |
|
||||
| Right stick horizontal | Jump inside popup (horizontal) |
|
||||
| Right stick vertical | Smooth scroll popup (vertical) |
|
||||
| D-pad | Fallback for stick navigation |
|
||||
| Left stick vertical | Scroll Yomitan popup |
|
||||
| Right stick vertical | Jump through Yomitan popup |
|
||||
| D-pad | Fallback for stick navigation when configured |
|
||||
|
||||
Learn mode ignores already-held inputs and waits for the next fresh button press or axis direction, which avoids accidental captures when you open the modal mid-input.
|
||||
|
||||
All button and axis mappings are configurable under the `controller` config block. See [Configuration — Controller Support](/configuration#controller-support) for the full options.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user