mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-06 07:21:33 -07:00
fix(controller): save remaps per profile, gate modals on enabled (#69)
This commit is contained in:
@@ -93,6 +93,7 @@ function createControllerConfig(
|
||||
...(buttonIndexOverrides ?? {}),
|
||||
}),
|
||||
},
|
||||
profiles: {},
|
||||
...restOverrides,
|
||||
};
|
||||
}
|
||||
@@ -449,6 +450,60 @@ test('gamepad controller maps left stick horizontal movement to token selection
|
||||
assert.deepEqual(calls, [1, 1, -1]);
|
||||
});
|
||||
|
||||
test('gamepad controller uses active controller profile bindings before global bindings', () => {
|
||||
let lookupToggles = 0;
|
||||
const buttons = Array.from({ length: 12 }, () => ({
|
||||
value: 0,
|
||||
pressed: false,
|
||||
touched: false,
|
||||
}));
|
||||
buttons[11] = { value: 1, pressed: true, touched: true };
|
||||
|
||||
const controller = createGamepadController({
|
||||
getGamepads: () => [createGamepad('pad-profile', { buttons })],
|
||||
getConfig: () =>
|
||||
({
|
||||
...createControllerConfig({
|
||||
bindings: {
|
||||
toggleLookup: { kind: 'button', buttonIndex: 0 },
|
||||
},
|
||||
}),
|
||||
profiles: {
|
||||
'pad-profile': {
|
||||
label: 'Profile Pad',
|
||||
buttonIndices: DEFAULT_BUTTON_INDICES,
|
||||
bindings: {
|
||||
...createControllerConfig().bindings,
|
||||
toggleLookup: { kind: 'button', buttonIndex: 11 },
|
||||
},
|
||||
},
|
||||
},
|
||||
}) as ResolvedControllerConfig,
|
||||
getKeyboardModeEnabled: () => true,
|
||||
getLookupWindowOpen: () => false,
|
||||
getInteractionBlocked: () => false,
|
||||
toggleKeyboardMode: () => {},
|
||||
toggleLookup: () => {
|
||||
lookupToggles += 1;
|
||||
},
|
||||
closeLookup: () => {},
|
||||
moveSelection: () => {},
|
||||
mineCard: () => {},
|
||||
quitMpv: () => {},
|
||||
previousAudio: () => {},
|
||||
nextAudio: () => {},
|
||||
playCurrentAudio: () => {},
|
||||
toggleMpvPause: () => {},
|
||||
scrollPopup: () => {},
|
||||
jumpPopup: () => {},
|
||||
onState: () => {},
|
||||
});
|
||||
|
||||
controller.poll(0);
|
||||
|
||||
assert.equal(lookupToggles, 1);
|
||||
});
|
||||
|
||||
test('gamepad controller maps L1 play-current, R1 next-audio, and popup navigation', () => {
|
||||
const calls: string[] = [];
|
||||
const scrollCalls: number[] = [];
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
ResolvedControllerConfig,
|
||||
ResolvedControllerDiscreteBinding,
|
||||
} from '../../types';
|
||||
import { resolveControllerConfigForGamepad } from '../controller-profile-config.js';
|
||||
|
||||
type ControllerButtonState = {
|
||||
value: number;
|
||||
@@ -410,87 +411,101 @@ export function createGamepadController(options: GamepadControllerOptions) {
|
||||
resetHeldAction(jumpHold);
|
||||
}
|
||||
|
||||
let interactionAllowed =
|
||||
config.enabled && options.getKeyboardModeEnabled() && !options.getInteractionBlocked();
|
||||
if (config.enabled) {
|
||||
const activeConfig = resolveControllerConfigForGamepad(config, activeGamepad.id);
|
||||
|
||||
if (activeConfig.enabled) {
|
||||
handleActionEdge(
|
||||
'toggleKeyboardOnlyMode',
|
||||
config.bindings.toggleKeyboardOnlyMode,
|
||||
activeConfig.bindings.toggleKeyboardOnlyMode,
|
||||
activeGamepad,
|
||||
config,
|
||||
activeConfig,
|
||||
options.toggleKeyboardMode,
|
||||
);
|
||||
}
|
||||
|
||||
interactionAllowed =
|
||||
config.enabled && options.getKeyboardModeEnabled() && !options.getInteractionBlocked();
|
||||
const interactionAllowed =
|
||||
activeConfig.enabled && options.getKeyboardModeEnabled() && !options.getInteractionBlocked();
|
||||
|
||||
if (!interactionAllowed) {
|
||||
syncBlockedInteractionState(activeGamepad, config, now);
|
||||
syncBlockedInteractionState(activeGamepad, activeConfig, now);
|
||||
return;
|
||||
}
|
||||
|
||||
handleActionEdge(
|
||||
'toggleLookup',
|
||||
config.bindings.toggleLookup,
|
||||
activeConfig.bindings.toggleLookup,
|
||||
activeGamepad,
|
||||
config,
|
||||
activeConfig,
|
||||
options.toggleLookup,
|
||||
);
|
||||
handleActionEdge(
|
||||
'closeLookup',
|
||||
config.bindings.closeLookup,
|
||||
activeConfig.bindings.closeLookup,
|
||||
activeGamepad,
|
||||
config,
|
||||
activeConfig,
|
||||
options.closeLookup,
|
||||
);
|
||||
handleActionEdge('mineCard', config.bindings.mineCard, activeGamepad, config, options.mineCard);
|
||||
handleActionEdge('quitMpv', config.bindings.quitMpv, activeGamepad, config, options.quitMpv);
|
||||
handleActionEdge(
|
||||
'mineCard',
|
||||
activeConfig.bindings.mineCard,
|
||||
activeGamepad,
|
||||
activeConfig,
|
||||
options.mineCard,
|
||||
);
|
||||
handleActionEdge(
|
||||
'quitMpv',
|
||||
activeConfig.bindings.quitMpv,
|
||||
activeGamepad,
|
||||
activeConfig,
|
||||
options.quitMpv,
|
||||
);
|
||||
|
||||
const activationThreshold = Math.max(config.stickDeadzone, 0.55);
|
||||
const activationThreshold = Math.max(activeConfig.stickDeadzone, 0.55);
|
||||
|
||||
if (options.getLookupWindowOpen()) {
|
||||
handleActionEdge(
|
||||
'previousAudio',
|
||||
config.bindings.previousAudio,
|
||||
activeConfig.bindings.previousAudio,
|
||||
activeGamepad,
|
||||
config,
|
||||
activeConfig,
|
||||
options.previousAudio,
|
||||
);
|
||||
handleActionEdge(
|
||||
'nextAudio',
|
||||
config.bindings.nextAudio,
|
||||
activeConfig.bindings.nextAudio,
|
||||
activeGamepad,
|
||||
config,
|
||||
activeConfig,
|
||||
options.nextAudio,
|
||||
);
|
||||
handleActionEdge(
|
||||
'playCurrentAudio',
|
||||
config.bindings.playCurrentAudio,
|
||||
activeConfig.bindings.playCurrentAudio,
|
||||
activeGamepad,
|
||||
config,
|
||||
activeConfig,
|
||||
options.playCurrentAudio,
|
||||
);
|
||||
|
||||
const primaryScroll = resolveAxisBindingValue(
|
||||
activeGamepad,
|
||||
config.bindings.leftStickVertical,
|
||||
config.triggerDeadzone,
|
||||
config.stickDeadzone,
|
||||
activeConfig.bindings.leftStickVertical,
|
||||
activeConfig.triggerDeadzone,
|
||||
activeConfig.stickDeadzone,
|
||||
);
|
||||
if (elapsedMs > 0 && Math.abs(primaryScroll) >= config.stickDeadzone) {
|
||||
options.scrollPopup((primaryScroll * config.scrollPixelsPerSecond * elapsedMs) / 1000);
|
||||
if (elapsedMs > 0 && Math.abs(primaryScroll) >= activeConfig.stickDeadzone) {
|
||||
options.scrollPopup(
|
||||
(primaryScroll * activeConfig.scrollPixelsPerSecond * elapsedMs) / 1000,
|
||||
);
|
||||
}
|
||||
|
||||
handleJumpAxis(
|
||||
resolveAxisBindingValue(
|
||||
activeGamepad,
|
||||
config.bindings.rightStickVertical,
|
||||
config.triggerDeadzone,
|
||||
activeConfig.bindings.rightStickVertical,
|
||||
activeConfig.triggerDeadzone,
|
||||
activationThreshold,
|
||||
),
|
||||
now,
|
||||
config,
|
||||
activeConfig,
|
||||
);
|
||||
} else {
|
||||
resetHeldAction(jumpHold);
|
||||
@@ -498,21 +513,21 @@ export function createGamepadController(options: GamepadControllerOptions) {
|
||||
|
||||
handleActionEdge(
|
||||
'toggleMpvPause',
|
||||
config.bindings.toggleMpvPause,
|
||||
activeConfig.bindings.toggleMpvPause,
|
||||
activeGamepad,
|
||||
config,
|
||||
activeConfig,
|
||||
options.toggleMpvPause,
|
||||
);
|
||||
|
||||
handleSelectionAxis(
|
||||
resolveAxisBindingValue(
|
||||
activeGamepad,
|
||||
config.bindings.leftStickHorizontal,
|
||||
config.triggerDeadzone,
|
||||
activeConfig.bindings.leftStickHorizontal,
|
||||
activeConfig.triggerDeadzone,
|
||||
activationThreshold,
|
||||
),
|
||||
now,
|
||||
config,
|
||||
activeConfig,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -987,7 +987,7 @@ test('keyboard mode: configured controller select binding opens locally without
|
||||
|
||||
assert.equal(openControllerSelectCount(), 1);
|
||||
assert.deepEqual(testGlobals.sessionActions, []);
|
||||
assert.deepEqual(testGlobals.openedModalNotifications, ['controller-select']);
|
||||
assert.deepEqual(testGlobals.openedModalNotifications, []);
|
||||
} finally {
|
||||
testGlobals.restore();
|
||||
}
|
||||
@@ -1017,7 +1017,7 @@ test('keyboard mode: configured controller debug binding opens locally without d
|
||||
|
||||
assert.equal(openControllerDebugCount(), 1);
|
||||
assert.deepEqual(testGlobals.sessionActions, []);
|
||||
assert.deepEqual(testGlobals.openedModalNotifications, ['controller-debug']);
|
||||
assert.deepEqual(testGlobals.openedModalNotifications, []);
|
||||
} finally {
|
||||
testGlobals.restore();
|
||||
}
|
||||
@@ -1049,7 +1049,7 @@ test('keyboard mode: configured controller debug binding is not swallowed while
|
||||
|
||||
assert.equal(openControllerDebugCount(), 1);
|
||||
assert.deepEqual(testGlobals.sessionActions, []);
|
||||
assert.deepEqual(testGlobals.openedModalNotifications, ['controller-debug']);
|
||||
assert.deepEqual(testGlobals.openedModalNotifications, []);
|
||||
} finally {
|
||||
testGlobals.restore();
|
||||
}
|
||||
|
||||
@@ -203,13 +203,11 @@ export function createKeyboardHandlers(
|
||||
}
|
||||
|
||||
if (binding.actionType === 'session-action' && binding.actionId === 'openControllerSelect') {
|
||||
window.electronAPI.notifyOverlayModalOpened('controller-select');
|
||||
options.openControllerSelectModal?.();
|
||||
return;
|
||||
}
|
||||
|
||||
if (binding.actionType === 'session-action' && binding.actionId === 'openControllerDebug') {
|
||||
window.electronAPI.notifyOverlayModalOpened('controller-debug');
|
||||
options.openControllerDebugModal?.();
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user