mirror of
https://github.com/motajs/template.git
synced 2026-10-30 04:40:18 +08:00
refactor: 按键注册
This commit is contained in:
parent
343296c63c
commit
0b7eac364f
@ -4,28 +4,41 @@ import {
|
||||
IHotkeySystem,
|
||||
KeyCode
|
||||
} from '@motajs/system';
|
||||
import { IClientHotkey, IClientHotkeySave } from './types';
|
||||
import { IClientHotkey, IClientHotkeySave, IHotkeyCommandGroup } from './types';
|
||||
import { logger } from '@motajs/common';
|
||||
|
||||
export class ClientHotkey implements IClientHotkey {
|
||||
system: IHotkeySystem = new HotkeySystem();
|
||||
|
||||
/** 按键分组 */
|
||||
private readonly groups: Map<string, IHotkeyCommand[]> = new Map();
|
||||
private readonly groups: Map<string, IHotkeyCommandGroup> = new Map();
|
||||
|
||||
setGroup(group: string, commands: IHotkeyCommand[]): void {
|
||||
setGroup(group: string, name: string, commands: IHotkeyCommand[]): void {
|
||||
if (commands.length > 0) {
|
||||
if (this.groups.has(group)) {
|
||||
logger.warn(208, group);
|
||||
}
|
||||
this.groups.set(group, commands);
|
||||
this.groups.set(group, {
|
||||
name,
|
||||
commands
|
||||
});
|
||||
} else {
|
||||
this.groups.delete(group);
|
||||
}
|
||||
}
|
||||
|
||||
getGroup(group: string): IHotkeyCommand[] {
|
||||
return this.groups.get(group) ?? [];
|
||||
addCommandToGroup(group: string, command: IHotkeyCommand): void {
|
||||
const obj = this.groups.get(group);
|
||||
if (!obj) return;
|
||||
if (obj.commands.includes(command)) {
|
||||
logger.warn(210, command.command, group);
|
||||
return;
|
||||
}
|
||||
obj.commands.push(command);
|
||||
}
|
||||
|
||||
getGroup(group: string): IHotkeyCommandGroup | null {
|
||||
return this.groups.get(group) ?? null;
|
||||
}
|
||||
|
||||
getUngrouped(): Iterable<IHotkeyCommand> {
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
import { IHotkeyCommand, IHotkeySystem, KeyCode } from '@motajs/system';
|
||||
import { ISaveableContent } from '@user/data-common';
|
||||
|
||||
export interface IHotkeyCommandGroup {
|
||||
/** 按键分组的名称 */
|
||||
readonly name: string;
|
||||
/** 分组包含的按键指令列表 */
|
||||
readonly commands: IHotkeyCommand[];
|
||||
}
|
||||
|
||||
export interface IClientHotkeySave {
|
||||
/** 按键指令列表 */
|
||||
readonly keys: ReadonlyMap<string, readonly (readonly KeyCode[])[]>;
|
||||
@ -12,16 +19,24 @@ export interface IClientHotkey extends ISaveableContent<IClientHotkeySave> {
|
||||
|
||||
/**
|
||||
* 设置指定分组的按键指令列表,若不存在分组则创建,若传入空数组则删除
|
||||
* @param group 按键分组名
|
||||
* @param group 按键分组 id
|
||||
* @param name 按键分组名称,用于显示
|
||||
* @param commands 分组中包含的按键指令名
|
||||
*/
|
||||
setGroup(group: string, commands: IHotkeyCommand[]): void;
|
||||
setGroup(group: string, name: string, commands: IHotkeyCommand[]): void;
|
||||
|
||||
/**
|
||||
* 获取指定按键分组中包含的按键,如果不存在则返回空数组
|
||||
* 向按键分组中添加新的按键指令
|
||||
* @param group 按键分组 id
|
||||
* @param command 要添加的按键指令
|
||||
*/
|
||||
addCommandToGroup(group: string, command: IHotkeyCommand): void;
|
||||
|
||||
/**
|
||||
* 获取指定按键分组中包含的按键
|
||||
* @param group 按键分组名
|
||||
*/
|
||||
getGroup(group: string): IHotkeyCommand[];
|
||||
getGroup(group: string): IHotkeyCommandGroup | null;
|
||||
|
||||
/**
|
||||
* 获取未分组的按键列表
|
||||
|
||||
@ -299,7 +299,8 @@
|
||||
"206": "Cannot set command key combination since delivered index is invalid: expected positive integer, found $1.",
|
||||
"207": "Key combination count needs to be a positive integer, but got $1.",
|
||||
"208": "Duplicate hotkey command group name: $1, old one will be overwritten.",
|
||||
"209": "Cannot load key combinations for hotkey command $1 since no command named it.",
|
||||
"209": "Cannot load key combinations for hotkey command '$1' since no command named it.",
|
||||
"210": "Cannot add duplicate hotkey command '$1' to group '$2'.",
|
||||
"1001": "Event(setBlock): Unknown tile '$1' when setting block."
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// 测试按键指令对象的组合管理、模式切换与非法组合告警
|
||||
// 测试按键指令对象的构造缺省、组合增删、参数校验与模式切换行为
|
||||
import { logger } from '@motajs/common';
|
||||
import { afterAll, afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { HotkeyCommand, isAuxiliaryKey } from '../command';
|
||||
import { HotkeyCommand } from '../command';
|
||||
import { KeyCode } from '../keyCodes';
|
||||
import { HotkeyEmitMode, IHotkeyCommandInit } from '../type';
|
||||
|
||||
@ -30,27 +30,29 @@ function createInit(
|
||||
};
|
||||
}
|
||||
|
||||
// 构造一个用于测试的按键指令,显示名称与标识符一致
|
||||
function createCommand(
|
||||
command: string,
|
||||
init: IHotkeyCommandInit
|
||||
): HotkeyCommand {
|
||||
return new HotkeyCommand(command, command, init);
|
||||
}
|
||||
|
||||
describe('HotkeyCommand', () => {
|
||||
// 辅助键判定覆盖 Ctrl / Shift / Alt / Meta 四个按键
|
||||
it('recognizes auxiliary keys', () => {
|
||||
expect(isAuxiliaryKey(KeyCode.Ctrl)).toBe(true);
|
||||
expect(isAuxiliaryKey(KeyCode.Shift)).toBe(true);
|
||||
expect(isAuxiliaryKey(KeyCode.Alt)).toBe(true);
|
||||
expect(isAuxiliaryKey(KeyCode.Meta)).toBe(true);
|
||||
});
|
||||
|
||||
// 常规按键不会被判定为辅助键
|
||||
it('rejects regular keys as auxiliary', () => {
|
||||
expect(isAuxiliaryKey(KeyCode.KeyA)).toBe(false);
|
||||
expect(isAuxiliaryKey(KeyCode.Digit1)).toBe(false);
|
||||
expect(isAuxiliaryKey(KeyCode.Space)).toBe(false);
|
||||
});
|
||||
|
||||
// 构造器应用缺省值并将默认组合拷贝进指令
|
||||
it('applies defaults and copies the default combinations', () => {
|
||||
const defaults = [[KeyCode.Ctrl, KeyCode.KeyS]];
|
||||
const command = new HotkeyCommand('save', createInit({ defaults }));
|
||||
// 构造器应用缺省值并写入默认组合
|
||||
it('applies defaults on construction', () => {
|
||||
const command = createCommand(
|
||||
'save',
|
||||
createInit({
|
||||
priority: 7,
|
||||
defaults: [[KeyCode.Ctrl, KeyCode.KeyS]]
|
||||
})
|
||||
);
|
||||
|
||||
expect(command.command).toBe('save');
|
||||
expect(command.name).toBe('save');
|
||||
expect(command.priority).toBe(7);
|
||||
expect(command.block).toBe(false);
|
||||
expect(command.mode).toBe(HotkeyEmitMode.KeyUp);
|
||||
expect(command.timeout).toBe(0);
|
||||
expect(command.interval).toBe(0);
|
||||
@ -60,29 +62,71 @@ describe('HotkeyCommand', () => {
|
||||
KeyCode.Ctrl,
|
||||
KeyCode.KeyS
|
||||
]);
|
||||
});
|
||||
|
||||
// 未指定优先级时默认为 0
|
||||
it('defaults priority to zero', () => {
|
||||
const command = new HotkeyCommand('save', 'save', { defaults: [] });
|
||||
|
||||
expect(command.priority).toBe(0);
|
||||
});
|
||||
|
||||
// 构造器拷贝默认组合,调用方后续修改原数组不影响指令内部
|
||||
it('copies the default combinations', () => {
|
||||
const defaults = [[KeyCode.Ctrl, KeyCode.KeyS]];
|
||||
const command = createCommand('save', createInit({ defaults }));
|
||||
|
||||
defaults[0].push(KeyCode.KeyA);
|
||||
|
||||
expect(command.getKeyCombination(0)).toEqual([
|
||||
KeyCode.Ctrl,
|
||||
KeyCode.KeyS
|
||||
]);
|
||||
});
|
||||
|
||||
// getKeyCombination 返回副本,修改不会影响指令内部组合
|
||||
it('returns a copy from getKeyCombination', () => {
|
||||
const command = new HotkeyCommand(
|
||||
// 默认组合为空数组时既不告警,长度也为 0
|
||||
it('accepts an empty defaults list without warning', () => {
|
||||
const warn = vi.spyOn(logger, 'warn').mockImplementation(() => {});
|
||||
const command = createCommand('save', createInit());
|
||||
|
||||
expect(warn).not.toHaveBeenCalled();
|
||||
expect(command.length).toBe(0);
|
||||
});
|
||||
|
||||
// setKeys(0) 合法:不告警、缩容到空且迭代结果为空
|
||||
it('shrinks to zero combinations with setKeys(0)', () => {
|
||||
const warn = vi.spyOn(logger, 'warn').mockImplementation(() => {});
|
||||
const command = createCommand(
|
||||
'save',
|
||||
createInit({ defaults: [[KeyCode.KeyS]] })
|
||||
);
|
||||
const keys = command.getKeyCombination(0);
|
||||
keys.push(KeyCode.KeyA);
|
||||
|
||||
command.setKeys(0);
|
||||
|
||||
expect(warn).not.toHaveBeenCalled();
|
||||
expect(command.length).toBe(0);
|
||||
expect([...command.iterateKeyCombinations()]).toEqual([]);
|
||||
});
|
||||
|
||||
// setKeys 对负值与非整数各告警一次 207 并保持原状
|
||||
it('warns 207 for negative and non-integer counts in setKeys', () => {
|
||||
const warn = vi.spyOn(logger, 'warn').mockImplementation(() => {});
|
||||
const command = createCommand(
|
||||
'save',
|
||||
createInit({ defaults: [[KeyCode.KeyS]] })
|
||||
);
|
||||
|
||||
command.setKeys(-1);
|
||||
command.setKeys(1.5);
|
||||
|
||||
expect(warn.mock.calls.map(c => c[0])).toEqual([207, 207]);
|
||||
expect(command.length).toBe(1);
|
||||
expect(command.getKeyCombination(0)).toEqual([KeyCode.KeyS]);
|
||||
});
|
||||
|
||||
// setKeys 扩容补空组合、缩容截断并同步 length
|
||||
it('resizes combinations with setKeys', () => {
|
||||
const command = new HotkeyCommand('save', createInit());
|
||||
const command = createCommand('save', createInit());
|
||||
command.setKeys(2);
|
||||
command.setKeyCombination(0, [KeyCode.KeyS]);
|
||||
command.setKeyCombination(1, [KeyCode.KeyD]);
|
||||
@ -97,125 +141,134 @@ describe('HotkeyCommand', () => {
|
||||
expect(command.getKeyCombination(1)).toEqual([]);
|
||||
});
|
||||
|
||||
// setKeys 忽略负值与非整数
|
||||
it('ignores invalid counts in setKeys', () => {
|
||||
const command = new HotkeyCommand(
|
||||
'save',
|
||||
createInit({ defaults: [[KeyCode.KeyS]] })
|
||||
);
|
||||
command.setKeys(-1);
|
||||
command.setKeys(1.5);
|
||||
|
||||
expect(command.length).toBe(1);
|
||||
expect(command.getKeyCombination(0)).toEqual([KeyCode.KeyS]);
|
||||
});
|
||||
|
||||
// setKeyCombination 忽略越界索引
|
||||
it('ignores out-of-range combination indexes', () => {
|
||||
const command = new HotkeyCommand(
|
||||
'save',
|
||||
createInit({ defaults: [[KeyCode.KeyS]] })
|
||||
);
|
||||
command.setKeyCombination(3, [KeyCode.KeyA]);
|
||||
command.setKeyCombination(-1, [KeyCode.KeyD]);
|
||||
|
||||
expect(command.length).toBe(1);
|
||||
expect(command.getKeyCombination(0)).toEqual([KeyCode.KeyS]);
|
||||
expect(command.getKeyCombination(3)).toEqual([]);
|
||||
});
|
||||
|
||||
// iterateKeyCombinations 按索引升序产出并返回副本
|
||||
it('iterates combinations in order with copies', () => {
|
||||
const command = new HotkeyCommand(
|
||||
'save',
|
||||
createInit({
|
||||
defaults: [[KeyCode.KeyS], [KeyCode.Ctrl, KeyCode.KeyD]]
|
||||
})
|
||||
);
|
||||
const entries = [...command.iterateKeyCombinations()];
|
||||
|
||||
expect(entries.map(v => v[0])).toEqual([0, 1]);
|
||||
expect(entries[1][1]).toEqual([KeyCode.Ctrl, KeyCode.KeyD]);
|
||||
entries[0][1].push(KeyCode.KeyA);
|
||||
expect(command.getKeyCombination(0)).toEqual([KeyCode.KeyS]);
|
||||
});
|
||||
|
||||
// KeyUp 模式下两个常规键的组合会发一次 203 且组合仍可读取
|
||||
it('warns 203 once for an invalid KeyUp combination', () => {
|
||||
// setKeyCombination 对非整数、负数、越界索引各告警一次 206 并保持原状
|
||||
it('warns 206 for invalid combination indexes', () => {
|
||||
const warn = vi.spyOn(logger, 'warn').mockImplementation(() => {});
|
||||
const command = new HotkeyCommand(
|
||||
const command = createCommand(
|
||||
'save',
|
||||
createInit({ defaults: [[KeyCode.KeyS]] })
|
||||
);
|
||||
|
||||
command.setKeyCombination(1.5, [KeyCode.KeyA]);
|
||||
command.setKeyCombination(-1, [KeyCode.KeyA]);
|
||||
command.setKeyCombination(3, [KeyCode.KeyA]);
|
||||
|
||||
expect(warn.mock.calls.map(c => c[0])).toEqual([206, 206, 206]);
|
||||
expect(command.length).toBe(1);
|
||||
expect(command.getKeyCombination(0)).toEqual([KeyCode.KeyS]);
|
||||
});
|
||||
|
||||
// setKeyCombination 写入副本,调用方后续修改原数组不影响指令内部
|
||||
it('copies the delivered combination', () => {
|
||||
const command = createCommand(
|
||||
'save',
|
||||
createInit({ defaults: [[KeyCode.KeyS]] })
|
||||
);
|
||||
const keys: KeyCode[] = [KeyCode.Ctrl, KeyCode.KeyS];
|
||||
|
||||
command.setKeyCombination(0, keys);
|
||||
keys.push(KeyCode.KeyA);
|
||||
|
||||
expect(command.getKeyCombination(0)).toEqual([
|
||||
KeyCode.Ctrl,
|
||||
KeyCode.KeyS
|
||||
]);
|
||||
});
|
||||
|
||||
// KeyUp 模式接受包含多个常规键的组合且不告警
|
||||
it('accepts multiple regular keys without warning', () => {
|
||||
const warn = vi.spyOn(logger, 'warn').mockImplementation(() => {});
|
||||
const command = createCommand(
|
||||
'save',
|
||||
createInit({ defaults: [[KeyCode.KeyA, KeyCode.KeyB]] })
|
||||
);
|
||||
|
||||
expect(warn).toHaveBeenCalledTimes(1);
|
||||
expect(warn).toHaveBeenCalledWith(203, 'save');
|
||||
expect(warn).not.toHaveBeenCalled();
|
||||
expect(command.getKeyCombination(0)).toEqual([
|
||||
KeyCode.KeyA,
|
||||
KeyCode.KeyB
|
||||
]);
|
||||
});
|
||||
|
||||
// KeyUp 模式下单个常规键加辅助键不会告警
|
||||
it('accepts one regular key with auxiliary keys', () => {
|
||||
// KeyUp 模式接受多个辅助键或空组合且不告警
|
||||
it('accepts auxiliary and empty combinations without warning', () => {
|
||||
const warn = vi.spyOn(logger, 'warn').mockImplementation(() => {});
|
||||
new HotkeyCommand(
|
||||
'save',
|
||||
createInit({ defaults: [[KeyCode.Ctrl, KeyCode.KeyS]] })
|
||||
);
|
||||
new HotkeyCommand(
|
||||
'save',
|
||||
createCommand(
|
||||
'both',
|
||||
createInit({ defaults: [[KeyCode.Ctrl, KeyCode.Shift]] })
|
||||
);
|
||||
createCommand('empty', createInit({ defaults: [[]] }));
|
||||
|
||||
expect(warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// keyUp() 切换到 KeyUp 并对已有非法组合只发一次 203
|
||||
it('switches to KeyUp and warns once for invalid combinations', () => {
|
||||
const command = new HotkeyCommand(
|
||||
// getKeyCombination 越界返回空数组,每次调用都返回新的副本
|
||||
it('returns empty arrays out of range and fresh copies in range', () => {
|
||||
const command = createCommand(
|
||||
'save',
|
||||
createInit({
|
||||
mode: HotkeyEmitMode.Once,
|
||||
defaults: [
|
||||
[KeyCode.KeyA, KeyCode.KeyB],
|
||||
[KeyCode.KeyC, KeyCode.KeyD]
|
||||
]
|
||||
})
|
||||
createInit({ defaults: [[KeyCode.KeyS]] })
|
||||
);
|
||||
const warn = vi.spyOn(logger, 'warn').mockImplementation(() => {});
|
||||
command.keyUp(120);
|
||||
const first = command.getKeyCombination(0);
|
||||
const second = command.getKeyCombination(0);
|
||||
|
||||
expect(command.mode).toBe(HotkeyEmitMode.KeyUp);
|
||||
expect(command.timeout).toBe(120);
|
||||
expect(warn).toHaveBeenCalledTimes(1);
|
||||
expect(warn).toHaveBeenCalledWith(203, 'save');
|
||||
expect(command.getKeyCombination(5)).toEqual([]);
|
||||
expect(first).toEqual([KeyCode.KeyS]);
|
||||
expect(first).not.toBe(second);
|
||||
});
|
||||
|
||||
// 四个模式方法设置模式与时间参数并保留未传入的值
|
||||
it('updates mode and timing parameters', () => {
|
||||
const command = new HotkeyCommand(
|
||||
// iterateKeyCombinations 按索引升序产出,且每次迭代都产出新的副本
|
||||
it('iterates combinations in order with fresh copies', () => {
|
||||
const command = createCommand(
|
||||
'save',
|
||||
createInit({
|
||||
defaults: [[KeyCode.KeyS], [KeyCode.Ctrl, KeyCode.KeyD]]
|
||||
})
|
||||
);
|
||||
const entries = [...command.iterateKeyCombinations()];
|
||||
const again = [...command.iterateKeyCombinations()];
|
||||
|
||||
expect(entries.map(v => v[0])).toEqual([0, 1]);
|
||||
expect(entries[1][1]).toEqual([KeyCode.Ctrl, KeyCode.KeyD]);
|
||||
expect(entries[0][1]).not.toBe(again[0][1]);
|
||||
});
|
||||
|
||||
// keyUp 与 once 设置对应的模式与首次触发延时,并返回自身
|
||||
it('switches between KeyUp and Once with a timeout', () => {
|
||||
const command = createCommand(
|
||||
'save',
|
||||
createInit({ defaults: [[KeyCode.KeyS]] })
|
||||
);
|
||||
|
||||
command.continuous(30, 60);
|
||||
expect(command.mode).toBe(HotkeyEmitMode.Continuous);
|
||||
expect(command.interval).toBe(30);
|
||||
expect(command.timeout).toBe(60);
|
||||
expect(command.keyUp(120)).toBe(command);
|
||||
expect(command.mode).toBe(HotkeyEmitMode.KeyUp);
|
||||
expect(command.timeout).toBe(120);
|
||||
|
||||
command.throttle(10);
|
||||
expect(command.mode).toBe(HotkeyEmitMode.Throttle);
|
||||
expect(command.dt).toBe(10);
|
||||
expect(command.interval).toBe(30);
|
||||
expect(command.timeout).toBe(60);
|
||||
|
||||
command.once(15);
|
||||
expect(command.once(15)).toBe(command);
|
||||
expect(command.mode).toBe(HotkeyEmitMode.Once);
|
||||
expect(command.timeout).toBe(15);
|
||||
});
|
||||
|
||||
command.keyUp(5);
|
||||
expect(command.mode).toBe(HotkeyEmitMode.KeyUp);
|
||||
expect(command.timeout).toBe(5);
|
||||
// continuous 写入三个时间参数,缺省调用时三者归零,并返回自身
|
||||
it('sets the Continuous timing parameters', () => {
|
||||
const command = createCommand(
|
||||
'save',
|
||||
createInit({ defaults: [[KeyCode.KeyS]] })
|
||||
);
|
||||
|
||||
expect(command.continuous(30, 60, 90)).toBe(command);
|
||||
expect(command.mode).toBe(HotkeyEmitMode.Continuous);
|
||||
expect(command.interval).toBe(30);
|
||||
expect(command.dt).toBe(60);
|
||||
expect(command.timeout).toBe(90);
|
||||
|
||||
command.continuous();
|
||||
expect(command.interval).toBe(0);
|
||||
expect(command.dt).toBe(0);
|
||||
expect(command.timeout).toBe(0);
|
||||
});
|
||||
|
||||
// 直接构造时仍然保留传入的指令标识符
|
||||
it('keeps the command identifier passed to the constructor', () => {
|
||||
expect(createCommand('fire', createInit()).command).toBe('fire');
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,19 +2,6 @@ import { logger } from '@motajs/common';
|
||||
import { KeyCode } from './keyCodes';
|
||||
import { HotkeyEmitMode, IHotkeyCommand, IHotkeyCommandInit } from './type';
|
||||
|
||||
/**
|
||||
* 判断按键是否为辅助按键,即 Ctrl / Shift / Alt / Meta 之一
|
||||
* @param key 按键码
|
||||
*/
|
||||
export function isAuxiliaryKey(key: KeyCode): boolean {
|
||||
return (
|
||||
key === KeyCode.Ctrl ||
|
||||
key === KeyCode.Shift ||
|
||||
key === KeyCode.Alt ||
|
||||
key === KeyCode.Meta
|
||||
);
|
||||
}
|
||||
|
||||
export class HotkeyCommand implements IHotkeyCommand {
|
||||
readonly priority: number;
|
||||
readonly block: boolean;
|
||||
@ -29,10 +16,11 @@ export class HotkeyCommand implements IHotkeyCommand {
|
||||
|
||||
constructor(
|
||||
readonly command: string,
|
||||
readonly name: string,
|
||||
init: IHotkeyCommandInit
|
||||
) {
|
||||
this.command = command;
|
||||
this.priority = init.priority;
|
||||
this.priority = init.priority ?? 0;
|
||||
this.block = init.block ?? false;
|
||||
this.mode = init.mode ?? HotkeyEmitMode.KeyUp;
|
||||
this.timeout = init.timeout ?? 0;
|
||||
@ -50,7 +38,7 @@ export class HotkeyCommand implements IHotkeyCommand {
|
||||
logger.warn(207, 'float');
|
||||
return;
|
||||
}
|
||||
if (count <= 0) {
|
||||
if (count < 0) {
|
||||
logger.warn(207, 'negative integer');
|
||||
return;
|
||||
}
|
||||
@ -80,12 +68,6 @@ export class HotkeyCommand implements IHotkeyCommand {
|
||||
return;
|
||||
}
|
||||
this.keys[index] = keys.slice();
|
||||
if (this.mode === HotkeyEmitMode.KeyUp) {
|
||||
const regular = keys.filter(v => !isAuxiliaryKey(v));
|
||||
if (regular.length >= 2) {
|
||||
logger.warn(203, this.command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getKeyCombination(index: number): readonly KeyCode[] {
|
||||
@ -100,24 +82,27 @@ export class HotkeyCommand implements IHotkeyCommand {
|
||||
}
|
||||
}
|
||||
|
||||
keyUp(timeout: number = 0): void {
|
||||
keyUp(timeout: number = 0): this {
|
||||
this.mode = HotkeyEmitMode.KeyUp;
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
once(timeout: number = 0): void {
|
||||
once(timeout: number = 0): this {
|
||||
this.mode = HotkeyEmitMode.Once;
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
continuous(
|
||||
interval: number = 0,
|
||||
dt: number = 0,
|
||||
timeout: number = 0
|
||||
): void {
|
||||
): this {
|
||||
this.mode = HotkeyEmitMode.Continuous;
|
||||
this.interval = interval;
|
||||
this.dt = dt;
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { IExcitable, IExcitation } from '@motajs/animate';
|
||||
import { logger, matchArray } from '@motajs/common';
|
||||
import { HotkeyCommand, isAuxiliaryKey } from './command';
|
||||
import { logger } from '@motajs/common';
|
||||
import { HotkeyCommand } from './command';
|
||||
import { EVENT_KEY_CODE_MAP, KeyCode } from './keyCodes';
|
||||
import {
|
||||
HotkeyEmitMode,
|
||||
@ -23,7 +23,7 @@ interface IHotkeyScope {
|
||||
interface IHotkeyCommandPending {
|
||||
/** 按键指令对象 */
|
||||
readonly command: IHotkeyCommand;
|
||||
/** 按键组合被按下的时刻 */
|
||||
/** 本次触发计时的起点,按下触发时为按下时刻,KeyUp 触发时为抬起时刻 */
|
||||
start: number;
|
||||
/** 按键组合的索引 */
|
||||
keyIndex: number;
|
||||
@ -31,6 +31,8 @@ interface IHotkeyCommandPending {
|
||||
keys: readonly KeyCode[];
|
||||
/** 按键触发上下文 */
|
||||
context: IHotkeyEmitContext;
|
||||
/** 按键组合匹配时刻的条件判定结果 */
|
||||
matchedWhen: boolean;
|
||||
/** 当前已经触发了几次 */
|
||||
emitted: number;
|
||||
/** 上一次的触发时刻 */
|
||||
@ -124,18 +126,9 @@ export class HotkeySystem implements IHotkeySystem, IExcitable<number> {
|
||||
*/
|
||||
private matchKeyUp(keys: readonly KeyCode[], key: KeyCode): boolean {
|
||||
if (keys.length === 0) return false;
|
||||
// 抬起按键仅允许一个非辅助按键
|
||||
const [main, auxiliary] = matchArray(keys, v => isAuxiliaryKey(v));
|
||||
if (main.length > 1) return false;
|
||||
|
||||
// 如果只有一个按键,那么可以触发,哪怕它是辅助按键
|
||||
if (main.length === 0) {
|
||||
return auxiliary.length === 1 && auxiliary[0] === key;
|
||||
}
|
||||
|
||||
// 否则需要判断触发的按键
|
||||
if (main[0] !== key) return false;
|
||||
return auxiliary.every(v => this.holdingSet.has(v));
|
||||
// 最后一个按键为主按键,其余为辅助按键
|
||||
if (keys.at(-1) !== key) return false;
|
||||
return keys.slice(0, -1).every(v => this.holdingSet.has(v));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -165,6 +158,7 @@ export class HotkeySystem implements IHotkeySystem, IExcitable<number> {
|
||||
this.scopes.pop();
|
||||
this.holdingCmds.clear();
|
||||
this.waitTimeout.clear();
|
||||
return;
|
||||
}
|
||||
const index = this.scopes.findIndex(v => v.symbol === sym);
|
||||
if (index < 0) {
|
||||
@ -209,13 +203,17 @@ export class HotkeySystem implements IHotkeySystem, IExcitable<number> {
|
||||
return this.commands.get(command) ?? null;
|
||||
}
|
||||
|
||||
addCommand(command: string, init: IHotkeyCommandInit): IHotkeyCommand {
|
||||
addCommand(
|
||||
command: string,
|
||||
name: string,
|
||||
init: IHotkeyCommandInit
|
||||
): IHotkeyCommand {
|
||||
const existing = this.commands.get(command);
|
||||
if (existing) {
|
||||
logger.warn(204, command);
|
||||
return existing;
|
||||
}
|
||||
const instance = new HotkeyCommand(command, init);
|
||||
const instance = new HotkeyCommand(command, name, init);
|
||||
this.commands.set(command, instance);
|
||||
return instance;
|
||||
}
|
||||
@ -247,15 +245,16 @@ export class HotkeySystem implements IHotkeySystem, IExcitable<number> {
|
||||
|
||||
/**
|
||||
* 触发指定按键指令
|
||||
* @param command 按键指令对象
|
||||
* @param pending 按键指令的 pending 信息
|
||||
* @returns 是否成功执行指令
|
||||
*/
|
||||
private emit(pending: IHotkeyCommandPending) {
|
||||
private emit(pending: IHotkeyCommandPending): boolean {
|
||||
if (!pending.matchedWhen) return false;
|
||||
const command = pending.command;
|
||||
const scope = this.getScope();
|
||||
if (!scope) return;
|
||||
if (!scope) return false;
|
||||
const impl = scope.implements.get(command);
|
||||
if (!impl) return;
|
||||
if (!impl) return false;
|
||||
const info: IHotkeyEmitInfo = {
|
||||
mode: command.mode,
|
||||
command,
|
||||
@ -266,18 +265,33 @@ export class HotkeySystem implements IHotkeySystem, IExcitable<number> {
|
||||
impl(info);
|
||||
pending.emitted++;
|
||||
pending.lastEmit = this.excitation?.payload() ?? 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取首个能够触发按键指令的按键组合
|
||||
* 获取能够触发按键指令的按键组合,多组候选时仅返回辅助键精确匹配的那一组
|
||||
* @param command 按键指令
|
||||
* @param holding 在按下触发的按键之前,有哪些按键正在被按下
|
||||
* @param emit 按下的是哪个按键
|
||||
*/
|
||||
private findMatchKeys(
|
||||
command: IHotkeyCommand
|
||||
command: IHotkeyCommand,
|
||||
holding: Set<KeyCode>,
|
||||
emit: KeyCode
|
||||
): [index: number, readonly KeyCode[]] | [] {
|
||||
const candidates: [index: number, readonly KeyCode[]][] = [];
|
||||
for (const [index, keys] of command.iterateKeyCombinations()) {
|
||||
const match = keys.every(v => this.holdingSet.has(v));
|
||||
if (match) return [index, keys];
|
||||
if (keys.at(-1) !== emit) continue;
|
||||
if (!keys.slice(0, -1).every(v => holding.has(v))) continue;
|
||||
candidates.push([index, keys]);
|
||||
}
|
||||
if (candidates.length === 0) return [];
|
||||
if (candidates.length === 1) return candidates[0];
|
||||
// 多组候选时只认辅助键与当前按住键完全一致的那一组
|
||||
for (const candidate of candidates) {
|
||||
if (candidate[1].length - 1 === holding.size) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
@ -293,12 +307,15 @@ export class HotkeySystem implements IHotkeySystem, IExcitable<number> {
|
||||
index: number,
|
||||
keys: readonly KeyCode[]
|
||||
) {
|
||||
const scope = this.getScope();
|
||||
if (!scope) return;
|
||||
const pending: IHotkeyCommandPending = {
|
||||
command,
|
||||
start: this.excitation?.payload() ?? 0,
|
||||
keyIndex: index,
|
||||
keys,
|
||||
context: this.createContext(),
|
||||
matchedWhen: scope.condition?.() ?? true,
|
||||
lastEmit: -Infinity,
|
||||
emitted: 0
|
||||
};
|
||||
@ -309,7 +326,23 @@ export class HotkeySystem implements IHotkeySystem, IExcitable<number> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前是否应该进行默认行为阻断
|
||||
*/
|
||||
private shouldPrevent(): boolean {
|
||||
const scope = this.getScope();
|
||||
if (!scope) return false;
|
||||
for (const [command] of this.holdingCmds) {
|
||||
const impl = scope.implements.get(command);
|
||||
if (impl) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
down(key: KeyCode): boolean {
|
||||
const scope = this.getScope();
|
||||
if (!scope) return false;
|
||||
const beforeHolding = new Set(this.holdingSet);
|
||||
if (this.holdingSet.has(key)) {
|
||||
// 如果有按键可能触发,那么返回 true 来 preventDefault
|
||||
return this.holdingCmds.size > 0 || this.waitTimeout.size > 0;
|
||||
@ -318,64 +351,46 @@ export class HotkeySystem implements IHotkeySystem, IExcitable<number> {
|
||||
this.holdingSet.add(key);
|
||||
}
|
||||
for (const command of this.commands.values()) {
|
||||
const match = this.findMatchKeys(command);
|
||||
const match = this.findMatchKeys(command, beforeHolding, key);
|
||||
if (match.length === 0) continue;
|
||||
const [index, keys] = match;
|
||||
const pending = this.holdingCmds.get(command);
|
||||
|
||||
if (pending) {
|
||||
// 如果之前有过这个按键指令,那么直接修改现有状态
|
||||
pending.keyIndex = index;
|
||||
pending.keys = keys;
|
||||
pending.context = this.createContext();
|
||||
} else {
|
||||
// 如果之前没有这个按键指令,那么加入执行列表
|
||||
if (!pending) {
|
||||
// 如果之前没有这个按键指令,那么加入执行列表,如果有的话就不管了
|
||||
this.addPending(command, index, keys);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// 如果有按键可能触发,那么返回 true 来 preventDefault
|
||||
return this.holdingCmds.size > 0 || this.waitTimeout.size > 0;
|
||||
return this.shouldPrevent();
|
||||
}
|
||||
|
||||
up(key: KeyCode): boolean {
|
||||
const scope = this.getScope();
|
||||
if (!scope) return false;
|
||||
if (!this.holdingSet.has(key)) return false;
|
||||
this.holdingSet.delete(key);
|
||||
const index = this.holding.indexOf(key);
|
||||
this.holding.splice(index, 1);
|
||||
|
||||
const beforeWait = this.waitTimeout.size;
|
||||
|
||||
const toEmit: IHotkeyCommandPending[] = [];
|
||||
|
||||
// 需要快照
|
||||
for (const [command, pending] of new Map(this.holdingCmds)) {
|
||||
// 对于 KeyUp,如果有可以触发的,那么按规则触发
|
||||
if (command.mode === HotkeyEmitMode.KeyUp) {
|
||||
const emit = this.matchKeyUp(pending.keys, key);
|
||||
if (emit) {
|
||||
toEmit.push(pending);
|
||||
this.holdingCmds.delete(command);
|
||||
}
|
||||
if (!this.matchKeyUp(pending.keys, key)) continue;
|
||||
toEmit.push(pending);
|
||||
this.holdingCmds.delete(command);
|
||||
} else {
|
||||
// 对于其他按键,如果一个按下组合按键不再满足,那么需要删除或加入 timeout
|
||||
if (!this.matchKeys(pending.keys)) {
|
||||
// 松开一个按键,那么并不一定会导致按下指令不再触发
|
||||
const newMatch = this.findMatchKeys(command);
|
||||
if (newMatch.length === 0) {
|
||||
// 没有新的匹配,分两种情况
|
||||
if (pending.emitted === 0) {
|
||||
// 还没触发过,加入 timeout
|
||||
this.waitTimeout.add(pending);
|
||||
} else {
|
||||
// 已经触发过了,直接删除
|
||||
this.holdingCmds.delete(command);
|
||||
}
|
||||
} else {
|
||||
// 如果有新的匹配,那么直接修改状态
|
||||
const [index, keys] = newMatch;
|
||||
pending.context = this.createContext();
|
||||
pending.keyIndex = index;
|
||||
pending.keys = keys;
|
||||
}
|
||||
if (this.matchKeys(pending.keys)) continue;
|
||||
this.holdingCmds.delete(command);
|
||||
if (pending.emitted === 0) {
|
||||
this.waitTimeout.add(pending);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -386,18 +401,24 @@ export class HotkeySystem implements IHotkeySystem, IExcitable<number> {
|
||||
let blocking = -Infinity;
|
||||
for (const pending of toEmit) {
|
||||
if (pending.command.priority < blocking) break;
|
||||
if (!pending.matchedWhen) continue;
|
||||
// 没有实现的按键不会进行阻断
|
||||
const impl = scope.implements.get(pending.command);
|
||||
if (!impl) continue;
|
||||
if (pending.command.block) {
|
||||
blocking = pending.command.priority;
|
||||
}
|
||||
if (pending.command.timeout === 0) {
|
||||
this.emit(pending);
|
||||
} else {
|
||||
// KeyUp 的 timeout 从抬起时刻开始计时
|
||||
pending.start = this.excitation?.payload() ?? 0;
|
||||
this.waitTimeout.add(pending);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有按键可能触发,那么返回 true 来 preventDefault
|
||||
return this.holdingCmds.size > 0 || this.waitTimeout.size > 0;
|
||||
return this.waitTimeout.size > beforeWait || this.shouldPrevent();
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@ -430,22 +451,18 @@ export class HotkeySystem implements IHotkeySystem, IExcitable<number> {
|
||||
if (command.mode === HotkeyEmitMode.KeyUp) continue;
|
||||
// 首次触发
|
||||
const dt = payload - pending.start;
|
||||
const dt2 = payload - pending.lastEmit;
|
||||
if (pending.emitted === 0 && dt < command.timeout) {
|
||||
this.emit(pending);
|
||||
if (pending.emitted === 0) {
|
||||
if (dt >= command.timeout) this.emit(pending);
|
||||
continue;
|
||||
}
|
||||
if (command.mode === HotkeyEmitMode.Continuous) {
|
||||
if (pending.emitted === 1) {
|
||||
// 先看第二次触发
|
||||
if (dt2 >= command.interval) {
|
||||
this.emit(pending);
|
||||
}
|
||||
} else {
|
||||
// 其他的要看 dt 了
|
||||
if (dt2 >= command.dt) {
|
||||
this.emit(pending);
|
||||
}
|
||||
}
|
||||
if (command.mode !== HotkeyEmitMode.Continuous) continue;
|
||||
const dt2 = payload - pending.lastEmit;
|
||||
if (pending.emitted === 1) {
|
||||
// 先看第二次触发
|
||||
if (dt2 >= command.interval) this.emit(pending);
|
||||
} else {
|
||||
// 其他的要看 dt 了
|
||||
if (dt2 >= command.dt) this.emit(pending);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,13 +18,13 @@ export const enum HotkeyEmitMode {
|
||||
export interface IHotkeyEmitContext {
|
||||
/**
|
||||
* 指令触发时,当前所有正在按下的按键,按照按下的顺序排列。如果是抬起事件触发的,那么这里面并不会包含触发事件的那个主要按键。
|
||||
* 注意该成员的含义不是当前时刻的按下的按键,对于连续触发以及 `timeout`,依然会包含触发此指令的时刻按下的按键。
|
||||
* 注意该成员的含义不是当前时刻的按下的按键,对于连续触发以及 `timeout`,会包含匹配时刻所有按下的按键。
|
||||
* 也就是说,它并不是当前时刻按下的按键,不要用它获取当前时刻正在按下的按键。参考时间线:
|
||||
*
|
||||
* ```txt
|
||||
* ----------|---------------|---------->
|
||||
* | timeout |
|
||||
* 指令触发时刻 指令延时触发
|
||||
* 指令匹配时刻 指令延时触发
|
||||
* 记录的是这个 而不是这个
|
||||
* ```
|
||||
*/
|
||||
@ -53,10 +53,17 @@ export interface IHotkeyEmitInfo {
|
||||
}
|
||||
|
||||
export interface IHotkeyCommandInit {
|
||||
/** 按键指令优先级,目前仅对抬起触发的按键生效 */
|
||||
readonly priority: number;
|
||||
/** 默认按键组合,可设置多组 */
|
||||
/**
|
||||
* 默认按键组合,可设置多组。每组中,最后一个按键为触发指令的主按键,前面的属于辅助按键,
|
||||
* 必须先按下这些按键,再触发最后一个按键的按下或抬起才可能触发指令:
|
||||
*
|
||||
* - 对于按下事件,先按下主按键,再按下辅助按键不会触发。松开任意一个按键后,如果该指令的按键依然匹配,并不会继续触发。
|
||||
* - 对于抬起事件,必须在按下所有的辅助按键和主按键后,松开主按键才可触发,松开辅助按键不会触发。
|
||||
* - 如果触发一个主按键后,有多个按键组合对应的指令会被触发,那么只会触发辅助按键精确匹配的那一个。
|
||||
*/
|
||||
readonly defaults: KeyCode[][];
|
||||
/** 按键指令优先级,目前仅对抬起触发的按键生效,默认为 0 */
|
||||
readonly priority?: number;
|
||||
/** 是否阻断优先级精确比此按键指令更低的指令的执行,同优先级的会全部执行,并不会阻断,默认不阻断 */
|
||||
readonly block?: boolean;
|
||||
/** 按键触发方式,默认为抬起时触发 */
|
||||
@ -72,6 +79,9 @@ export interface IHotkeyCommandInit {
|
||||
export interface IHotkeyCommand {
|
||||
/** 按键指令的标识符 */
|
||||
readonly command: string;
|
||||
/** 按键指令的名称,用于显示 */
|
||||
readonly name: string;
|
||||
|
||||
/** 按键指令优先级,越高越先执行,如果同优先级,则无法保证顺序,目前仅对抬起触发的按键生效 */
|
||||
readonly priority: number;
|
||||
/** 触发后,是否阻断优先级比当前优先级更低的按键的触发 */
|
||||
@ -117,13 +127,13 @@ export interface IHotkeyCommand {
|
||||
* 抬起后,在经过 `timeout` 时间后触发一次
|
||||
* @param timeout 抬起后距离首次触发的时间间隔,单位毫秒,默认为 0
|
||||
*/
|
||||
keyUp(timeout?: number): void;
|
||||
keyUp(timeout?: number): this;
|
||||
|
||||
/**
|
||||
* 按下后,在经过 `timeout` 时间后只触发一次,不会连续触发
|
||||
* @param timeout 按下后距离首次触发的时间间隔,单位毫秒,默认为 0
|
||||
*/
|
||||
once(timeout?: number): void;
|
||||
once(timeout?: number): this;
|
||||
|
||||
/**
|
||||
* 当按下后,在经过 `timeout` 时间后进行一次触发,之后在松开前,会按照激励源的激励触发
|
||||
@ -132,7 +142,7 @@ export interface IHotkeyCommand {
|
||||
* @param dt 节流间隔,默认为 0,即每次激励源的激励都会触发
|
||||
* @param timeout 按下后距离首次触发的时间间隔,单位号码,默认为 0
|
||||
*/
|
||||
continuous(interval?: number, dt?: number, timeout?: number): void;
|
||||
continuous(interval?: number, dt?: number, timeout?: number): this;
|
||||
}
|
||||
|
||||
export interface IHotkeySystem {
|
||||
@ -167,9 +177,14 @@ export interface IHotkeySystem {
|
||||
/**
|
||||
* 添加新的按键指令
|
||||
* @param command 按键指令标识符
|
||||
* @param name 按键指令的名称,用于显示
|
||||
* @param init 指令初始化对象
|
||||
*/
|
||||
addCommand(command: string, init: IHotkeyCommandInit): IHotkeyCommand;
|
||||
addCommand(
|
||||
command: string,
|
||||
name: string,
|
||||
init: IHotkeyCommandInit
|
||||
): IHotkeyCommand;
|
||||
|
||||
/**
|
||||
* 删除指定按键指令
|
||||
|
||||
@ -1,20 +1,349 @@
|
||||
import { KeyCode } from '@motajs/system';
|
||||
import { HotkeyEmitMode, KeyCode } from '@motajs/system';
|
||||
import { IClientCore } from '@user/client-modules';
|
||||
|
||||
export function initializeHotkey(state: IClientCore) {
|
||||
const hotkey = state.hotkey;
|
||||
const system = hotkey.system;
|
||||
|
||||
system.excitedBy(state.rafExcitation);
|
||||
|
||||
//#region 通用按键
|
||||
|
||||
hotkey.setGroup('@system/common', [
|
||||
system.addCommand('ok', {
|
||||
priority: 0,
|
||||
defaults: [[KeyCode.Enter], [KeyCode.Space], [KeyCode.KeyC]]
|
||||
hotkey.setGroup('common', '通用按键', [
|
||||
system.addCommand('@common/ok', '确认', {
|
||||
defaults: [[KeyCode.Enter], [KeyCode.Space], [KeyCode.KeyC]],
|
||||
priority: 0
|
||||
}),
|
||||
system.addCommand('exit', {
|
||||
priority: 0,
|
||||
defaults: [[KeyCode.Escape], [KeyCode.KeyX]]
|
||||
system.addCommand('@common/exit', '退出/返回上一级', {
|
||||
defaults: [[KeyCode.Escape], [KeyCode.KeyX]],
|
||||
priority: 0
|
||||
}),
|
||||
system.addCommand('@common/select-up', '上移选择光标', {
|
||||
defaults: [[KeyCode.UpArrow]],
|
||||
priority: 0
|
||||
}),
|
||||
system.addCommand('@common/select-down', '下移选择光标', {
|
||||
defaults: [[KeyCode.DownArrow]],
|
||||
priority: 0
|
||||
})
|
||||
]);
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 游戏按键
|
||||
|
||||
hotkey.setGroup('game', '游戏按键', [
|
||||
system.addCommand('@game/up', '向上移动', {
|
||||
defaults: [[KeyCode.UpArrow]],
|
||||
mode: HotkeyEmitMode.Continuous
|
||||
}),
|
||||
system.addCommand('@game/right', '向右移动', {
|
||||
defaults: [[KeyCode.RightArrow]],
|
||||
mode: HotkeyEmitMode.Continuous
|
||||
}),
|
||||
system.addCommand('@game/down', '向下移动', {
|
||||
defaults: [[KeyCode.DownArrow]],
|
||||
mode: HotkeyEmitMode.Continuous
|
||||
}),
|
||||
system.addCommand('@game/left', '向左移动', {
|
||||
defaults: [[KeyCode.LeftArrow]],
|
||||
mode: HotkeyEmitMode.Continuous
|
||||
}),
|
||||
system.addCommand('@game/turn', '勇士转向', {
|
||||
defaults: [[KeyCode.KeyZ]]
|
||||
}),
|
||||
system.addCommand('@game/undo', '回退', {
|
||||
defaults: [[KeyCode.KeyA]]
|
||||
}),
|
||||
system.addCommand('@game/redo', '恢复', {
|
||||
defaults: [[KeyCode.KeyW]]
|
||||
}),
|
||||
system.addCommand('@game/get-next', '轻按', {
|
||||
defaults: [[KeyCode.Space], [KeyCode.Digit7]]
|
||||
}),
|
||||
system.addCommand('@game/quick-equip', '切换/保存套装', {
|
||||
defaults: [
|
||||
[KeyCode.Alt, KeyCode.Digit1],
|
||||
[KeyCode.Alt, KeyCode.Digit2],
|
||||
[KeyCode.Alt, KeyCode.Digit3],
|
||||
[KeyCode.Alt, KeyCode.Digit4],
|
||||
[KeyCode.Alt, KeyCode.Digit5],
|
||||
[KeyCode.Alt, KeyCode.Digit6],
|
||||
[KeyCode.Alt, KeyCode.Digit7],
|
||||
[KeyCode.Alt, KeyCode.Digit8],
|
||||
[KeyCode.Alt, KeyCode.Digit9],
|
||||
[KeyCode.Alt, KeyCode.Digit0]
|
||||
]
|
||||
})
|
||||
]);
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region UI 界面
|
||||
|
||||
hotkey.setGroup('ui', 'UI 界面', [
|
||||
system.addCommand('@ui/book', '怪物手册', {
|
||||
defaults: [[KeyCode.KeyX]]
|
||||
}),
|
||||
system.addCommand('@ui/save', '存档界面', {
|
||||
defaults: [[KeyCode.KeyS]]
|
||||
}),
|
||||
system.addCommand('@ui/load', '读档界面', {
|
||||
defaults: [[KeyCode.KeyD]]
|
||||
}),
|
||||
system.addCommand('@ui/fly', '楼层传送', {
|
||||
defaults: [[KeyCode.KeyG]]
|
||||
}),
|
||||
system.addCommand('@ui/menu', '游戏菜单', {
|
||||
defaults: [[KeyCode.Escape]]
|
||||
}),
|
||||
system.addCommand('@ui/view-map', '浏览地图', {
|
||||
defaults: [[KeyCode.PageDown], [KeyCode.PageUp]]
|
||||
}),
|
||||
system.addCommand('@ui/toolbox', '道具界面', {
|
||||
defaults: [[KeyCode.KeyT]]
|
||||
}),
|
||||
system.addCommand('@ui/command', '装备界面', {
|
||||
defaults: [[KeyCode.KeyQ]]
|
||||
}),
|
||||
system.addCommand('@ui/replay', '回放菜单', {
|
||||
defaults: [[KeyCode.KeyR]]
|
||||
}),
|
||||
system.addCommand('@ui/shop', '快捷商店', {
|
||||
defaults: [[KeyCode.KeyV]]
|
||||
}),
|
||||
system.addCommand('@ui/statistics', '数据统计', {
|
||||
defaults: [[KeyCode.Ctrl, KeyCode.KeyB]]
|
||||
})
|
||||
]);
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 系统按键
|
||||
|
||||
hotkey.setGroup('system', '系统按键', [
|
||||
system.addCommand('@system/restart', '回到开始界面', {
|
||||
defaults: [[KeyCode.KeyN]]
|
||||
}),
|
||||
system.addCommand('@system/comment', '评论区', {
|
||||
defaults: [[KeyCode.KeyP]]
|
||||
}),
|
||||
system.addCommand('@system/debug', '调试模式', {
|
||||
defaults: [[KeyCode.F8]]
|
||||
})
|
||||
]);
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 开始界面
|
||||
|
||||
hotkey.setGroup('@ui/start', '开始界面', [
|
||||
system.addCommand('@ui/start-up', '上移光标', {
|
||||
defaults: [[KeyCode.UpArrow]],
|
||||
mode: HotkeyEmitMode.Once
|
||||
}),
|
||||
system.addCommand('@ui/start-down', '下移光标', {
|
||||
defaults: [[KeyCode.DownArrow]],
|
||||
mode: HotkeyEmitMode.Once
|
||||
})
|
||||
]);
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 怪物手册
|
||||
|
||||
hotkey.setGroup('@ui/book', '怪物手册', [
|
||||
system.addCommand('@ui/book-up', '上移光标', {
|
||||
defaults: [[KeyCode.UpArrow]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/book-down', '下移光标', {
|
||||
defaults: [[KeyCode.DownArrow]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/book-page-up', '上移五个怪物', {
|
||||
defaults: [[KeyCode.LeftArrow], [KeyCode.PageUp]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/book-page-down', '下移五个怪物', {
|
||||
defaults: [[KeyCode.RightArrow], [KeyCode.PageDown]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
})
|
||||
]);
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 常规楼层传送
|
||||
|
||||
hotkey.setGroup('@ui/fly', '常规楼层传送', [
|
||||
system.addCommand('@ui/fly-prev', '上一张地图', {
|
||||
defaults: [[KeyCode.DownArrow]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/fly-next', '下一张地图', {
|
||||
defaults: [[KeyCode.UpArrow]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/fly-prev-10', '前十张地图', {
|
||||
defaults: [[KeyCode.LeftArrow], [KeyCode.PageDown]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/fly-next-10', '后十张地图', {
|
||||
defaults: [[KeyCode.RightArrow], [KeyCode.PageUp]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
})
|
||||
]);
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 平面楼层传送
|
||||
|
||||
hotkey.setGroup('@ui/plane-fly', '平面楼层传送', [
|
||||
system.addCommand('@ui/plane-fly-up', '上移地图', {
|
||||
defaults: [[KeyCode.UpArrow]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/plane-fly-right', '右移地图', {
|
||||
defaults: [[KeyCode.RightArrow]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/plane-fly-down', '下移地图', {
|
||||
defaults: [[KeyCode.DownArrow]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/plane-fly-left', '左移地图', {
|
||||
defaults: [[KeyCode.LeftArrow]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/plane-fly-prev', '上一张地图', {
|
||||
defaults: [[KeyCode.PageDown]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/plane-fly-next', '下一张地图', {
|
||||
defaults: [[KeyCode.PageUp]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/plane-fly-prev-10', '前十张地图', {
|
||||
defaults: [
|
||||
[KeyCode.Ctrl, KeyCode.PageDown],
|
||||
[KeyCode.Ctrl, KeyCode.LeftArrow]
|
||||
],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/plane-fly-next-10', '后十张地图', {
|
||||
defaults: [
|
||||
[KeyCode.Ctrl, KeyCode.PageUp],
|
||||
[KeyCode.Ctrl, KeyCode.RightArrow]
|
||||
],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
})
|
||||
]);
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 存档界面
|
||||
|
||||
hotkey.setGroup('@ui/save', '存档界面', [
|
||||
system.addCommand('@ui/save-up', '选择框向上', {
|
||||
defaults: [[KeyCode.UpArrow]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/save-up', '选择框向右', {
|
||||
defaults: [[KeyCode.RightArrow]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/save-up', '选择框向下', {
|
||||
defaults: [[KeyCode.DownArrow]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/save-up', '选择框向左', {
|
||||
defaults: [[KeyCode.LeftArrow]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/save-page-prev', '向前翻页', {
|
||||
defaults: [[KeyCode.PageUp]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/save-page-next', '向后翻页', {
|
||||
defaults: [[KeyCode.PageDown]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/save-page-prev', '向前十页', {
|
||||
defaults: [[KeyCode.Ctrl, KeyCode.PageUp]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/save-page-next', '向后十页', {
|
||||
defaults: [[KeyCode.Ctrl, KeyCode.PageDown]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
})
|
||||
]);
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 浏览地图
|
||||
|
||||
hotkey.setGroup('@ui/view-map', '浏览地图', [
|
||||
system.addCommand('@ui/view-map-prev', '上一张地图', {
|
||||
defaults: [[KeyCode.DownArrow], [KeyCode.PageDown]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/view-map-next', '下一张地图', {
|
||||
defaults: [[KeyCode.UpArrow], [KeyCode.PageUp]],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/view-map-prev-10', '前十张地图', {
|
||||
defaults: [
|
||||
[KeyCode.Ctrl, KeyCode.DownArrow],
|
||||
[KeyCode.Ctrl, KeyCode.PageDown]
|
||||
],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/view-map-next-10', '后十张地图', {
|
||||
defaults: [
|
||||
[KeyCode.Ctrl, KeyCode.UpArrow],
|
||||
[KeyCode.Ctrl, KeyCode.PageUp]
|
||||
],
|
||||
mode: HotkeyEmitMode.Continuous,
|
||||
interval: 500
|
||||
}),
|
||||
system.addCommand('@ui/view-map-book', '怪物手册', {
|
||||
defaults: [[KeyCode.KeyX]],
|
||||
priority: 10
|
||||
}),
|
||||
system.addCommand('@ui/view-map-fly', '传送至', {
|
||||
defaults: [[KeyCode.KeyG]],
|
||||
priority: 10
|
||||
}),
|
||||
system.addCommand('@ui/view-map-reset', '重置视角', {
|
||||
defaults: [[KeyCode.KeyR]],
|
||||
priority: 10
|
||||
})
|
||||
]);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user