Newer
Older
rpg / packages / engine / src / ui / menuInput.ts
/**
 * Чистое чтение навигации меню: без Pixi, тестируется изолированно.
 * Движок не привязан к раскладке — имена действий даёт сцена (биндятся игрой).
 * Порядок событий за тик: cancel → move → (adjust XOR confirm) → extra.
 */

/** Карта действий меню (все — имена действий InputManager). */
export interface MenuActionMap {
    up: string;
    down: string;
    /** Активировать выбранный пункт. */
    confirm: string;
    /** Назад / закрыть панель. */
    cancel: string;
    /** Подстройка значения (настройки); нажатые left/right подавляют confirm. */
    left?: string;
    right?: string;
    /** Прочие действия сцены (удаление сейва, открытие сумки и т.п.). */
    extra?: readonly string[];
}

export type MenuNavEvent =
    | { kind: 'move'; delta: -1 | 1; index: number }
    | { kind: 'adjust'; delta: -1 | 1; index: number }
    | { kind: 'confirm'; index: number }
    | { kind: 'cancel' }
    | { kind: 'action'; action: string; index: number };

/** Источник «нажато в этом тике» — InputManager (или заглушка в тестах). */
export interface MenuInputSource {
    isActionJustPressed(action: string): boolean;
}

/** Прочитать навигационные события за один тик. */
export function readMenuInput(input: MenuInputSource, index: number, map: MenuActionMap): MenuNavEvent[] {
    if (input.isActionJustPressed(map.cancel)) return [{ kind: 'cancel' }];
    const events: MenuNavEvent[] = [];
    if (input.isActionJustPressed(map.up)) events.push({ kind: 'move', delta: -1, index });
    else if (input.isActionJustPressed(map.down)) events.push({ kind: 'move', delta: 1, index });
    // left/right перекрывают confirm: в настройках Enter не должен давать двойной шаг
    const left = map.left !== undefined && input.isActionJustPressed(map.left);
    const right = map.right !== undefined && input.isActionJustPressed(map.right);
    if (left || right) events.push({ kind: 'adjust', delta: right ? 1 : -1, index });
    else if (input.isActionJustPressed(map.confirm)) events.push({ kind: 'confirm', index });
    for (const action of map.extra ?? []) {
        if (input.isActionJustPressed(action)) events.push({ kind: 'action', action, index });
    }
    return events;
}