refactor: simplify input handling?
This commit is contained in:
@@ -1,26 +1,25 @@
|
||||
import Phaser from 'phaser';
|
||||
import type { IGameContext, PromptEvent } from 'boardgame-core';
|
||||
|
||||
export interface InputMapperOptions<TState extends Record<string, unknown>> {
|
||||
scene: Phaser.Scene;
|
||||
commands: IGameContext<TState>['commands'];
|
||||
activePrompt: { current: PromptEvent | null };
|
||||
onSubmitPrompt: (input: string) => string | null;
|
||||
// ─── InputMapper ───────────────────────────────────────────────────────────
|
||||
|
||||
export interface InputMapperOptions {
|
||||
onSubmit: (input: string) => string | null;
|
||||
}
|
||||
|
||||
export class InputMapper<TState extends Record<string, unknown>> {
|
||||
export class InputMapper {
|
||||
private scene: Phaser.Scene;
|
||||
private commands: IGameContext<TState>['commands'];
|
||||
private activePrompt: { current: PromptEvent | null };
|
||||
private onSubmitPrompt: (input: string) => string | null;
|
||||
private onSubmit: (input: string) => string | null;
|
||||
private pointerDownCallback: ((pointer: Phaser.Input.Pointer) => void) | null = null;
|
||||
private isWaitingForPrompt = false;
|
||||
/** Track interactive objects registered via mapObjectClick for cleanup */
|
||||
private trackedObjects: Array<{
|
||||
obj: Phaser.GameObjects.GameObject;
|
||||
handler: () => void;
|
||||
}> = [];
|
||||
|
||||
constructor(options: InputMapperOptions<TState>) {
|
||||
this.scene = options.scene;
|
||||
this.commands = options.commands;
|
||||
this.activePrompt = options.activePrompt;
|
||||
this.onSubmitPrompt = options.onSubmitPrompt;
|
||||
constructor(scene: Phaser.Scene, options: InputMapperOptions) {
|
||||
this.scene = scene;
|
||||
this.onSubmit = options.onSubmit;
|
||||
}
|
||||
|
||||
mapGridClick(
|
||||
@@ -42,9 +41,7 @@ export class InputMapper<TState extends Record<string, unknown>> {
|
||||
|
||||
const cmd = onCellClick(col, row);
|
||||
if (cmd) {
|
||||
// 总是尝试提交到 prompt
|
||||
// 如果没有活跃 prompt,onSubmitPrompt 会返回错误,我们忽略它
|
||||
this.onSubmitPrompt(cmd);
|
||||
this.onSubmit(cmd);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -60,42 +57,61 @@ export class InputMapper<TState extends Record<string, unknown>> {
|
||||
if ('setInteractive' in obj && typeof (obj as any).setInteractive === 'function') {
|
||||
const interactiveObj = obj as any;
|
||||
interactiveObj.setInteractive({ useHandCursor: true });
|
||||
interactiveObj.on('pointerdown', () => {
|
||||
|
||||
const handler = () => {
|
||||
const cmd = onClick(obj as unknown as T);
|
||||
if (cmd) {
|
||||
this.commands.run(cmd);
|
||||
this.onSubmit(cmd);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
interactiveObj.on('pointerdown', handler);
|
||||
this.trackedObjects.push({ obj, handler });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
// Remove global pointerdown listener from mapGridClick
|
||||
if (this.pointerDownCallback) {
|
||||
this.scene.input.off('pointerdown', this.pointerDownCallback);
|
||||
this.pointerDownCallback = null;
|
||||
}
|
||||
|
||||
// Remove per-object pointerdown listeners from mapObjectClick
|
||||
for (const { obj, handler } of this.trackedObjects) {
|
||||
if ('off' in obj && typeof (obj as any).off === 'function') {
|
||||
(obj as any).off('pointerdown', handler);
|
||||
}
|
||||
}
|
||||
this.trackedObjects = [];
|
||||
}
|
||||
}
|
||||
|
||||
export interface PromptHandlerOptions<TState extends Record<string, unknown>> {
|
||||
scene: Phaser.Scene;
|
||||
commands: IGameContext<TState>['commands'];
|
||||
export function createInputMapper(
|
||||
scene: Phaser.Scene,
|
||||
options: InputMapperOptions,
|
||||
): InputMapper {
|
||||
return new InputMapper(scene, options);
|
||||
}
|
||||
|
||||
// ─── PromptHandler ─────────────────────────────────────────────────────────
|
||||
|
||||
export interface PromptHandlerOptions {
|
||||
commands: IGameContext<any>['commands'];
|
||||
onPrompt: (prompt: PromptEvent) => void;
|
||||
onCancel: (reason?: string) => void;
|
||||
}
|
||||
|
||||
export class PromptHandler<TState extends Record<string, unknown>> {
|
||||
private scene: Phaser.Scene;
|
||||
private commands: IGameContext<TState>['commands'];
|
||||
export class PromptHandler {
|
||||
private commands: IGameContext<any>['commands'];
|
||||
private onPrompt: (prompt: PromptEvent) => void;
|
||||
private onCancel: (reason?: string) => void;
|
||||
private activePrompt: PromptEvent | null = null;
|
||||
private isListening = false;
|
||||
private pendingInput: string | null = null;
|
||||
|
||||
constructor(options: PromptHandlerOptions<TState>) {
|
||||
this.scene = options.scene;
|
||||
constructor(options: PromptHandlerOptions) {
|
||||
this.commands = options.commands;
|
||||
this.onPrompt = options.onPrompt;
|
||||
this.onCancel = options.onCancel;
|
||||
@@ -112,7 +128,7 @@ export class PromptHandler<TState extends Record<string, unknown>> {
|
||||
this.commands.promptQueue.pop()
|
||||
.then((promptEvent) => {
|
||||
this.activePrompt = promptEvent;
|
||||
|
||||
|
||||
// 如果有等待的输入,自动提交
|
||||
if (this.pendingInput) {
|
||||
const input = this.pendingInput;
|
||||
@@ -121,10 +137,13 @@ export class PromptHandler<TState extends Record<string, unknown>> {
|
||||
if (error === null) {
|
||||
this.activePrompt = null;
|
||||
this.listenForPrompt();
|
||||
} else {
|
||||
// 提交失败,把 prompt 交给 UI 显示错误
|
||||
this.onPrompt(promptEvent);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.onPrompt(promptEvent);
|
||||
})
|
||||
.catch((reason) => {
|
||||
@@ -133,16 +152,19 @@ export class PromptHandler<TState extends Record<string, unknown>> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit an input string to the current prompt.
|
||||
* @returns null on success (input accepted), error string on validation failure
|
||||
*/
|
||||
submit(input: string): string | null {
|
||||
if (!this.activePrompt) {
|
||||
// 没有活跃 prompt,保存为待处理输入
|
||||
this.pendingInput = input;
|
||||
return null; // 返回 null 表示已接受,会等待
|
||||
return null;
|
||||
}
|
||||
|
||||
const error = this.activePrompt.tryCommit(input);
|
||||
if (error === null) {
|
||||
// 提交成功,重置并监听下一个 prompt
|
||||
this.activePrompt = null;
|
||||
this.listenForPrompt();
|
||||
}
|
||||
@@ -170,22 +192,8 @@ export class PromptHandler<TState extends Record<string, unknown>> {
|
||||
}
|
||||
}
|
||||
|
||||
export function createInputMapper<TState extends Record<string, unknown>>(
|
||||
scene: Phaser.Scene,
|
||||
commands: IGameContext<TState>['commands'],
|
||||
activePrompt: { current: PromptEvent | null },
|
||||
onSubmitPrompt: (input: string) => string | null,
|
||||
): InputMapper<TState> {
|
||||
return new InputMapper({ scene, commands, activePrompt, onSubmitPrompt });
|
||||
}
|
||||
|
||||
export function createPromptHandler<TState extends Record<string, unknown>>(
|
||||
scene: Phaser.Scene,
|
||||
commands: IGameContext<TState>['commands'],
|
||||
callbacks: {
|
||||
onPrompt: (prompt: PromptEvent) => void;
|
||||
onCancel: (reason?: string) => void;
|
||||
},
|
||||
): PromptHandler<TState> {
|
||||
return new PromptHandler({ scene, commands, ...callbacks });
|
||||
export function createPromptHandler(
|
||||
options: PromptHandlerOptions,
|
||||
): PromptHandler {
|
||||
return new PromptHandler(options);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { PromptEvent, CommandSchema, CommandParamSchema } from 'boardgame-c
|
||||
|
||||
interface PromptDialogProps {
|
||||
prompt: PromptEvent | null;
|
||||
onSubmit: (input: string) => void;
|
||||
onSubmit: (input: string) => string | null | void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
@@ -36,11 +36,10 @@ export function PromptDialog({ prompt, onSubmit, onCancel }: PromptDialogProps)
|
||||
const fieldValues = schemaToFields(prompt.schema).map(f => values[f.label] || '');
|
||||
const cmdString = [prompt.schema.name, ...fieldValues].join(' ');
|
||||
|
||||
const err = prompt.tryCommit(cmdString);
|
||||
if (err) {
|
||||
const err = onSubmit(cmdString);
|
||||
if (err != null) {
|
||||
setError(err);
|
||||
} else {
|
||||
onSubmit(cmdString);
|
||||
setValues({});
|
||||
setError(null);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user