Skip to content

快捷键注册表

背景:快捷键分散在各模块中,没有统一注册和冲突检测。shortcut-registry 提供集中管理。 过程:定义 ShortcutRegistry 类 → 注册/注销 API → 冲突检测 → 全局键盘事件监听 → 快捷键设置 UI。


七月十日,上午。

Riku 打开应用,按下 Ctrl+Space。什么也没发生。

"快捷键不工作了," 他对 Jieling 说。

"哪个快捷键?"

"Ctrl+Space,切换动作模式。"

Riku 打开代码,搜索 Ctrl+Space。找到三处:

  1. main.ts 里注册了 Ctrl+Space → 切换动作模式
  2. scene.ts 里注册了 Ctrl+Space → 切换相机模式
  3. outfit.ts 里注册了 Ctrl+Space → 切换服装预设

"三个模块抢同一个快捷键," Riku 说,"最后一个注册的覆盖前面的。"

"所以 Ctrl+Space 现在是切换服装预设?"

"对。"

"这不行。"


注册表

Riku 打开 src/core/shortcut-registry.ts

核心思路:所有快捷键统一注册到一个 Registry,Registry 负责冲突检测和事件分发。

typescript
class ShortcutRegistry {
    private shortcuts: Map<string, ShortcutHandler>;
    private conflicts: Map<string, string[]>;
    
    constructor() {
        this.shortcuts = new Map();
        this.conflicts = new Map();
    }
    
    register(key: string, handler: ShortcutHandler, source: string): void {
        if (this.shortcuts.has(key)) {
            // 冲突!记录两个来源
            const existing = this.shortcuts.get(key)!;
            this.conflicts.set(key, [existing.source, source]);
            console.warn(`快捷键冲突: ${key} 被 ${existing.source} 和 ${source} 注册`);
        }
        
        this.shortcuts.set(key, { handler, source });
    }
    
    unregister(key: string, source: string): void {
        const existing = this.shortcuts.get(key);
        if (existing?.source === source) {
            this.shortcuts.delete(key);
        }
    }
}

"冲突检测是关键," Riku 说,"如果两个模块注册同一个快捷键,Registry 会记录冲突并打印警告。开发者在控制台就能看到。"

"但不会阻止注册?" Jieling 问。

"不会," Riku 说,"先注册的生效,后注册的被忽略。但警告会提醒开发者修复冲突。"


事件

Registry 监听全局键盘事件:

typescript
function _initKeyboardListener(): void {
    document.addEventListener('keydown', (e) => {
        const key = _buildKeyString(e); // 如 'Ctrl+Space'
        const shortcut = registry.get(key);
        
        if (shortcut) {
            e.preventDefault();
            shortcut.handler(e);
        }
    });
}

"_buildKeyString 把键盘事件转换成标准化的 key 字符串," Riku 解释,"比如 Ctrl+Shift+AAlt+SpaceF5。"

"修饰键顺序呢?" Jieling 问。

"按 Ctrl → Alt → Shift → 普通键排序," Riku 说,"保证 Ctrl+Shift+AShift+Ctrl+A 是同一个 key。"


设置

Riku 在 src/menus/settings-shortcuts.ts 里加了快捷键设置 UI:

  • 列出所有已注册的快捷键
  • 显示当前绑定的 key
  • 允许用户修改绑定
typescript
function buildShortcutsLevel(): PopupLevel {
    const shortcuts = shortcutRegistry.getAll();
    
    return {
        title: '快捷键设置',
        items: shortcuts.map(s => ({
            text: s.description,
            subText: s.key,
            onClick: () => _editShortcut(s),
        })),
    };
}

"用户可以修改绑定," Riku 说,"但不能删除快捷键。只能改 key。"

"为什么不能删除?" Jieling 问。

"因为快捷键是功能入口," Riku 说,"删除快捷键等于隐藏功能。用户找不到功能,会以为功能不存在。"


尾声

Riku 提交了代码。

feat: ShortcutRegistry 快捷键注册表(集中管理 + 冲突检测)
feat: 快捷键设置 UI(settings-shortcuts.ts)

他打开应用,按下 Ctrl+Space。现在它切换动作模式了。

"冲突修复了?" Jieling 问。

"对," Riku 说,"scene.ts 和 outfit.ts 的 Ctrl+Space 改成了其他 key。"

他站在窗边,看着远处的天际线。七月的阳光已经落下去了,路灯一盏一盏亮起来。

快捷键注册表。不是复杂的配置系统,只是一个 Map + 冲突检测。但它把"三个模块抢同一个快捷键"的问题解决了。

"快捷键是城市的交通规则," Riku 对自己说,"注册表是交通管理局。"


教训:快捷键管理不是"注册事件监听"那么简单。集中注册、冲突检测、标准化 key 字符串——这些是让快捷键"不打架"的基础。ShortcutRegistry 不是银弹,但它把"快捷键冲突"的问题从运行时提前到了注册时。