可视化键盘

基本信息

属性 值
实现类 com.blamejared.controlling.client.gui.GuiVisualKeyboard
打开方式 控制界面上的 “Key Map” 按钮(ID 1007),或列表行内的键盘图标按钮(ID 2002)
面板分页 Page 私有枚举:3 页
修饰键按钮 来自 KeyModifier.VALUES,4 个
来源 代码改编自 Keyboard Wizard / KeyboardWizard-Legacy-Forge-1.7.10,作者 MrNerdy42 与 Tapio,MIT 许可

功能

可视化键盘是一个覆盖在控制设置界面上的浮层,用鼠标点选代替"按下按键"来完成绑定, 因此不需要真的去按键盘。文件头注明改编自 Keyboard Wizard 项目(见仓库 THIRD_PARTY_NOTICES.md)。

面板内的键位不是贴图,而是按标准键盘布局用 Gui.drawRect 逐个矩形绘制出来的。

Page —— 分页(3 值)

Page 是 GuiVisualKeyboard 内的私有枚举,初始为 Page.MAIN。 面板右上角有 3 个分页按钮,对应 i18n options.visualKeyboardMain / options.visualKeyboardNumpad / options.visualKeyboardAux。

取值 标签 行数 内容
MAIN 主板 6 Esc + F1–F12、数字/符号行、Tab 字母行、Caps 字母行、Shift 字母行、Ctrl/Win/Alt/空格行
NUMPAD 小键盘 4 Num / * -;7 8 9 +;4 5 6 Enter;1 2 3 .
AUX 辅助键 4 PrtSc ScrLk Pause Ins Home;PgUp Del End PgDn Up;Left Down Right F13 F14;F15 F16 F17 F18 F19

MAIN 页各行的完整键位(含 Keyboard.KEY_* 常量与宽度单位):

行 键位(宽度单位)
1 Esc、F1–F12(各 1.0)
2 `、1–0、-、=(各 1.0)、Back(2.0)
3 Tab(1.5)、Q W E R T Y U I O P、[、](各 1.0)、\(1.5)
4 Caps(1.75)、A S D F G H J K L、;、'(各 1.0)、Enter(2.25)
5 LShift(2.25)、Z X C V B N M、,、.、/(各 1.0)、RShift(2.75)
6 LCtrl、LWin、LAlt(各 1.25)、Space(7.5)、RAlt、RWin、RCtrl(各 1.25)

MAIN 与 AUX 页包含左右成对的 Ctrl/Shift/Alt/Win 键,与 修饰键枚举 的"左右键合并为同一逻辑修饰键"设计对应。

布局计算

layout(screen) 每次绘制前重算,面板按屏幕尺寸自适应:

量 计算方式
maxWidth max(220, screen.width - 24)
keyboardWidth 先 min(560, maxWidth - 16),再 max(180, panelWidth - 16)
panelWidth min(screen.width - 8, keyboardWidth + 16)
keyGap keyboardWidth < 360 ? 2 : 4
keyHeight max(14, min(24, (screen.height - 116) / 7))
headerHeight 57(标题 + 提示 + 修饰键按钮行)
footerHeight 24
键盘区行数 MAIN 为 6,其余为 4

修饰键按钮

面板标题下方有"Modifier"标签(options.visualKeyboardModifier),其后横排 4 个按钮, 直接由 KeyModifier.VALUES 生成,标签取 getDisplayName(),即 None / Ctrl / Shift / Alt。

点击按钮即 screen.setVisualKeyboardModifier(modifier)。当前生效的修饰键按钮高亮为选中色。 None 按钮的作用就是退回裸键绑定。

修饰键的可推断性:如果玩家此刻真的按着某个修饰键, GuiNewControls.getVisualKeyboardModifier() 会返回 KeyModifier.getActiveModifier() 而非面板记录的值。 所以玩家可以直接按住 Ctrl 再点主键,无需先点 Ctrl 按钮。

键位颜色状态

每个 KeyButton 的底色按固定优先级选取:

优先级 状态 颜色 触发条件
1 禁用 0xFF303030 modifier.matches(keyCode) —— 当前修饰键对应的物理键,不能绑成自己
2 选中 0xFFD4A928 该键是当前正在编辑的绑定(键码相同且修饰键相同)
3 冲突 0xFF8A2525 该键码 + 当前修饰键下有 >1 个绑定
4 已绑定 0xFF236B23 该键码 + 当前修饰键下有 1 个绑定
5 悬停 0xFF888888 鼠标位于该键上
6 普通 0xFF555555 其余

其他面板色:面板底 0xEE101010,边框 0xFF777777,文字 0xFFFFFF, 禁用态文字 0xA0A0A0。

绑定归属的统计口径见 getMatchingKeyBindings:遍历 mc.gameSettings.keyBindings, 要求键码相同、分类不以 .hidden 结尾、且该绑定的修饰键等于当前修饰键 (非 ComboKeyBinding 的绑定一律按 NONE 计)。

因此在 Ctrl 面板下 G 显示为"未绑定"是正常的——Ctrl + G 归在 Ctrl 页, 裸 G 才在 None 页显示为已绑定。

键名标签

getKeyLabel(keyCode, fallback) 的取值优先级:

  1. 若未加载 lwjgl3ify → 用硬编码标签;
  2. 若 shouldUseFixedLabel(keyCode) 命中 → 用硬编码标签;
  3. 否则取 Keyboard.getKeyName(keyCode),若为 null 或以 "Key " 开头 → 退回硬编码标签。

shouldUseFixedLabel 固定了 29 个键码(Esc、Back、Tab、Caps、Enter、LShift、RShift、 LCtrl、RCtrl、LMenu、RMenu、LMeta、RMeta、Space、NumLock、SysRq、Scroll、Insert、Delete、 Home、End、PgUp、PgDn、Up、Down、Left、Right、小键盘 Enter), 避免在 LWJGL2 与 LWJGL3 下显示不一致。

Loader.isModLoaded("lwjgl3ify") 是一个可选依赖检测:装了就用 LWJGL3 的原生键名, 没装就用自绘标签,保证两种环境都能正常显示。

点击行为

mouseClicked 的判定顺序:

  1. 点击不在面板内 → 返回 false,交还给底层列表;
  2. 面板内且非左键(mouseButton != 0)→ 返回 true 直接吞掉,避免误操作;
  3. 命中分页按钮 → 切换 Page;
  4. 命中修饰键按钮 → 设置当前修饰键;
  5. 命中某个可用键 → 分两种情况:
    • 当前没有选中待编辑的绑定 → 用 getMatchingKeyBindings(...) 反查, 若有结果则 showKeyBinding(第一个匹配项),即跳转到列表中的那一条;
    • 已选中待编辑的绑定 → selectVisualKeyboardKey(key.keyCode) 完成绑定;
  6. 其余情况(面板内空白处)→ 返回 true 吞掉点击。

"点击面板外"这一约定来自界面提示 options.visualKeyboardHint: “Click a key to bind. Click outside to bind a mouse button.” (点键完成绑定,点面板外则改为绑定鼠标键)。

相关条目