contro-max
Version:
Game controls done in the best possible way!
152 lines (151 loc) • 5.56 kB
TypeScript
import { LiteralUnion } from 'type-fest';
import { GamepadButtonName } from '../gamepad';
import { AllKeyCodes } from './keyCodes';
import { StoreProvider } from './store';
/** @partial */
export interface InputCommandOptions {
/** @default true */
preventDefault?: boolean;
/**
* Temporary disable emitting events
* @default false
*/
disabled?: boolean;
}
/** resolved command from schema */
export type SchemaCommand = InputCommandOptions & {
keys: AllKeyCodes[];
gamepad: GamepadButtonName[];
};
export type KeysGroup = 'WASD' | 'Arrows' | 'Digits' | 'NumpadDigits' | 'AnyDigits' | 'Modifiers' | 'FKeys' | 'PageMoveKeys';
type KeyboardKey = AllKeyCodes | null;
export type SchemaCommandInput = [KeyboardKey | AllKeyCodes[], GamepadButtonName?, InputCommandOptions?] | null;
export type InputCommandsSchema = {
[category: string]: {
[command: string]: SchemaCommandInput;
};
};
type GamepadButtons = GamepadButtonName[];
export type InputGroupedCommandsSchema = {
[category: string]: {
[groupedCommand: string]: [KeysGroup | AllKeyCodes[], GamepadButtons, InputCommandOptions?];
};
};
export type InputSchema = {
/**
* By default, we're automatically listening to these keys and update movement vector. Specify null to disable for keyboard
* @default null
*/
movementKeymap?: 'WASD' | 'WASDArrows' | 'Arrows' | null;
};
export interface InputSchemaArg<T extends InputCommandsSchema, K extends InputGroupedCommandsSchema> extends InputSchema {
commands: T;
groupedCommands?: K;
}
export type MovementVector2d = {
x: number;
z: number;
};
export type MovementVector3d = {
x: number;
y: number;
z: number;
};
type SubPath<T, Key extends keyof T> = Key extends string ? T[Key] extends Record<string, any> ? `${Key}.${Exclude<keyof T[Key], keyof any[]> & string}` : never : never;
export type AllSchemaCommands<T extends InputCommandsSchema | InputGroupedCommandsSchema> = SubPath<T, keyof T>;
export type CommandEventArgument<T extends InputCommandsSchema> = {
command: AllSchemaCommands<T>;
};
export type GroupedCommandEventDeviceType = // TODO doc: should rely on index rather than button or key
{
type: 'keyboard';
key: AllKeyCodes;
} | {
type: 'gamepad';
gamepadIndex: number;
button: GamepadButtonName;
};
export type GroupCommandEventArgument<K extends InputGroupedCommandsSchema> = {
command: AllSchemaCommands<K>;
schema: InputGroupedCommandsSchema[''][''];
/** Keyboard key or gamepad button index */
index: number;
} & GroupedCommandEventDeviceType;
export type SourceType = {
/** Who emitted event: Gamepad with index or keyboard if `undefined` */
gamepadIndex?: number;
};
export type ControEvents<T extends InputCommandsSchema, K extends InputGroupedCommandsSchema, M extends '2d' | '3d'> = {
trigger: (arg0: CommandEventArgument<T>) => void;
triggerGrouped: (arg0: GroupCommandEventArgument<K>) => void;
release: (arg0: CommandEventArgument<T>) => void;
releaseGrouped: (arg0: GroupCommandEventArgument<K>) => void;
stickMovement: (arg0: {
stick: 'left' | 'right';
vector: MovementVector2d;
}) => void;
movementUpdate: (arg0: {
vector: M extends '3d' ? MovementVector3d : MovementVector2d;
soleVector: M extends '3d' ? MovementVector3d : MovementVector2d;
} & SourceType) => void;
/** usually to switch slots */
mouseWheel: (arg0: {
direction: -1 | 1;
}) => void;
pressedKeyOrButtonChanged: (arg0: ({
code: AllKeyCodes;
} | {
gamepadIndex: number;
button: GamepadButtonName;
}) & {
state: boolean;
}) => void;
userConfigResolve: () => void;
};
type KeyboardTarget = Record<'addEventListener' | 'removeEventListener', (...args: ['keydown' | 'keyup', (e: KeyboardEvent) => void]) => unknown>;
export interface CreateControlsSchemaOptions {
/** To what bind events
* @default window */
target?: KeyboardTarget;
/**
* Sometimes target is dynamic, so you use global target and filter events here
*
* @param e undefined if is gamepad
* @returns continue
*/
captureEvents?: (e?: KeyboardEvent) => boolean;
/**
* If true, then events that fired programmatically will be ignored
* @default false
*/
requireTrusted?: boolean;
/** Use additional DOM events to workaround certain bugs with keyboard
* @default true */
additionalEventHandlers?: boolean;
/**
* - `only-first-gamepad`: listen for first only connected gamepad
* - `only-last-gamepad`: listen for last only connected gamepad
* - `all-gamepads`: listen for buttons and sticks from all connected gamepads
* - `none`: temporary disabling gamepads support
* @default all-gamepads
*/
listenGamepadsStrategy?: 'only-first-gamepad' | 'only-last-gamepad' | 'all-gamepads' | 'none';
defaultControlOptions?: InputCommandOptions;
/**
* Default is precise enough
* @default 250 */
gamepadPollingInterval?: LiteralUnion<500 | 250, number>;
/**
* - all - emit buttons
* - split -
* @default all
*/
/**
* - all - sum movement from keyboard and all listening gamepads (controlled by `listenGamepadsStrategy`)
* - split - emit independent movement vectors for keyboard and each gamepads. Useful for split-screen games
* @default all
*/
emitMovement?: 'all' | 'split';
storeProvider?: StoreProvider;
}
export {};