221 lines
6.6 KiB
TypeScript
221 lines
6.6 KiB
TypeScript
import { getRealPoint } from "./dom";
|
|
import type { Point } from "./geometry";
|
|
|
|
interface IKeyState {
|
|
state: boolean;
|
|
prevState?: boolean;
|
|
|
|
released?: boolean;
|
|
pressed?: boolean;
|
|
repeated?: boolean;
|
|
held?: boolean;
|
|
|
|
changedAt: number;
|
|
eventAt: number;
|
|
repeatAt?: number;
|
|
}
|
|
namespace Input {
|
|
export enum KeyCode {
|
|
UP = 'ArrowUp',
|
|
DOWN = 'ArrowDown',
|
|
LEFT = 'ArrowLeft',
|
|
RIGHT = 'ArrowRight',
|
|
SPACE = 'Space',
|
|
SHIFT = 'Shift',
|
|
SHIFT_LEFT = 'ShiftLeft',
|
|
SHIFT_RIGHT = 'ShiftRight',
|
|
A = 'KeyA',
|
|
B = 'KeyB',
|
|
C = 'KeyC',
|
|
D = 'KeyD',
|
|
E = 'KeyE',
|
|
F = 'KeyF',
|
|
G = 'KeyG',
|
|
H = 'KeyH',
|
|
I = 'KeyI',
|
|
J = 'KeyJ',
|
|
K = 'KeyK',
|
|
L = 'KeyL',
|
|
M = 'KeyM',
|
|
N = 'KeyN',
|
|
O = 'KeyO',
|
|
P = 'KeyP',
|
|
Q = 'KeyQ',
|
|
R = 'KeyR',
|
|
S = 'KeyS',
|
|
T = 'KeyT',
|
|
U = 'KeyU',
|
|
V = 'KeyV',
|
|
W = 'KeyW',
|
|
X = 'KeyX',
|
|
Y = 'KeyY',
|
|
Z = 'KeyZ',
|
|
NUM_0 = 'Digit0',
|
|
NUM_1 = 'Digit1',
|
|
NUM_2 = 'Digit2',
|
|
NUM_3 = 'Digit3',
|
|
NUM_4 = 'Digit4',
|
|
NUM_5 = 'Digit5',
|
|
NUM_6 = 'Digit6',
|
|
NUM_7 = 'Digit7',
|
|
NUM_8 = 'Digit8',
|
|
NUM_9 = 'Digit9',
|
|
|
|
MOUSE_LEFT = 'Mouse0',
|
|
MOUSE_MIDDLE = 'Mouse1',
|
|
MOUSE_RIGHT = 'Mouse2',
|
|
};
|
|
|
|
export enum GamepadAxis {
|
|
LX = 0,
|
|
LY = 1,
|
|
RX = 2,
|
|
RY = 3,
|
|
}
|
|
|
|
export enum GamepadButton {
|
|
A = 0,
|
|
B = 1,
|
|
X = 2,
|
|
Y = 3,
|
|
LB = 4,
|
|
RB = 5,
|
|
LT = 6,
|
|
RT = 7,
|
|
SELECT = 8,
|
|
START = 9,
|
|
L3 = 10,
|
|
R3 = 11,
|
|
D_UP = 12,
|
|
D_DOWN = 13,
|
|
D_LEFT = 14,
|
|
D_RIGHT = 15,
|
|
HOME = 16,
|
|
}
|
|
|
|
const DEAD_ZONE = 0.05;
|
|
const KEYS: Partial<Record<string, IKeyState>> = {};
|
|
const mousePosition: Point = { x: 0, y: 0 };
|
|
let mainCanvas: HTMLCanvasElement | null = null;
|
|
let repeatIntervalMs = 10;
|
|
let repeatDelayMs = 300;
|
|
|
|
const onStateChange = (keyId: string, state: boolean) => {
|
|
console.debug(`[Input] ${state ? 'Pressed' : 'Released'} ${keyId}`);
|
|
const now = Date.now();
|
|
|
|
if (KEYS[keyId]) {
|
|
if (state !== KEYS[keyId].state) {
|
|
KEYS[keyId].changedAt = now;
|
|
}
|
|
KEYS[keyId].state = state;
|
|
KEYS[keyId].eventAt = now;
|
|
} else {
|
|
KEYS[keyId] = { state, changedAt: now, eventAt: now };
|
|
}
|
|
|
|
if (keyId === KeyCode.SHIFT_LEFT || keyId === KeyCode.SHIFT_RIGHT) {
|
|
const shiftState = Boolean(KEYS[KeyCode.SHIFT_LEFT]?.state || KEYS[KeyCode.SHIFT_RIGHT]?.state);
|
|
onStateChange(KeyCode.SHIFT, shiftState);
|
|
}
|
|
};
|
|
|
|
const onMouseChange = (e: MouseEvent) => {
|
|
if (mainCanvas) {
|
|
const point = getRealPoint(mainCanvas, e);
|
|
mousePosition.x = point.x;
|
|
mousePosition.y = point.y;
|
|
} else {
|
|
mousePosition.x = e.clientX;
|
|
mousePosition.y = e.clientY;
|
|
}
|
|
}
|
|
|
|
document.body.addEventListener('keydown', (e) => onStateChange(e.code as KeyCode, true));
|
|
document.body.addEventListener('keyup', (e) => onStateChange(e.code as KeyCode, false));
|
|
document.body.addEventListener('mousedown', (e) => {
|
|
onStateChange(`Mouse${e.button}`, true);
|
|
onMouseChange(e);
|
|
});
|
|
document.body.addEventListener('mouseup', (e) => {
|
|
onStateChange(`Mouse${e.button}`, false);
|
|
onMouseChange(e);
|
|
});
|
|
document.body.addEventListener('mousemove', (e) => onMouseChange(e));
|
|
document.body.addEventListener('contextmenu', (e) => e.preventDefault());
|
|
|
|
export const isPressed = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.pressed);
|
|
export const isReleased = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.released);
|
|
export const isRepeated = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.repeated);
|
|
export const isHeld = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.held);
|
|
|
|
export const hasPressed = () => Object.values(KEYS).some(k => k?.pressed);
|
|
export const hasReleased = () => Object.values(KEYS).some(k => k?.released);
|
|
export const hasRepeated = () => Object.values(KEYS).some(k => k?.repeated);
|
|
export const hasHeld = () => Object.values(KEYS).some(k => k?.held);
|
|
|
|
export const getGamepad = () => navigator.getGamepads().find(g => g != null);
|
|
|
|
export const getGamepadAxis = (i: GamepadAxis) => {
|
|
const value = getGamepad()?.axes[i] ?? 0;
|
|
return Math.abs(value) < DEAD_ZONE ? 0 : value;
|
|
}
|
|
export const getGamepadButton = (btn: GamepadButton) => getGamepad()?.buttons[btn]?.value ?? 0;
|
|
|
|
export const getLeft = () => isHeld(KeyCode.LEFT) ? 1 : Math.max(0, -getGamepadAxis(GamepadAxis.LX));
|
|
export const getRight = () => isHeld(KeyCode.RIGHT) ? 1 : Math.max(0, getGamepadAxis(GamepadAxis.LX));
|
|
|
|
export const getUp = () => isHeld(KeyCode.UP) ? 1 : Math.max(0, -getGamepadAxis(GamepadAxis.LY));
|
|
export const getDown = () => isHeld(KeyCode.DOWN) ? 1 : Math.max(0, getGamepadAxis(GamepadAxis.LY));
|
|
|
|
export const getHorizontal = () => (
|
|
(Number(isHeld(KeyCode.LEFT)) - Number(isHeld(KeyCode.RIGHT)))
|
|
+ getGamepadAxis(GamepadAxis.LX)
|
|
);
|
|
|
|
export const getVertical = () => (
|
|
(Number(isHeld(KeyCode.UP)) - Number(isHeld(KeyCode.DOWN)))
|
|
+ getGamepadAxis(GamepadAxis.LY)
|
|
);
|
|
|
|
export const getMousePosition = (): Point => ({ ...mousePosition });
|
|
|
|
export const setRepeatInterval = (intervalMs: number) => {
|
|
repeatIntervalMs = intervalMs;
|
|
}
|
|
|
|
export const setRepeatDelay = (delayMs: number) => {
|
|
repeatDelayMs = delayMs;
|
|
}
|
|
|
|
export const setMainCanvas = (canvas: HTMLCanvasElement | null) => {
|
|
mainCanvas = canvas;
|
|
}
|
|
|
|
export function updateKeys() {
|
|
for (const key of Object.values(KEYS)) {
|
|
if (!key) continue;
|
|
|
|
key.released = false;
|
|
key.pressed = false;
|
|
|
|
if (key.state) {
|
|
key.pressed = !key.held;
|
|
key.held = true;
|
|
} else {
|
|
key.released = key.held;
|
|
key.held = false;
|
|
}
|
|
|
|
const now = Date.now();
|
|
key.repeated = key.held && (now - (key.repeatAt ?? 0) > repeatIntervalMs) && (now - key.changedAt > repeatDelayMs);
|
|
if (key.repeated) {
|
|
key.repeatAt = now;
|
|
}
|
|
key.prevState = key.state;
|
|
}
|
|
}
|
|
}
|
|
|
|
export default Input;
|