feat(prompts): a Keyboard face: the keys and mouse buttons bound, in texts, hints and menus #2435
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2435
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/keyboard-prompts"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Players on the keyboard and mouse see a controller's buttons in every prompt (the Switch's when no controller is connected), so they have to work out which key each one means. This adds a Keyboard face to the Button prompts system: the game's texts, hints and menus show the key or mouse button each action is bound to.
Part of #1007 (keyboard button prompts, from #2028).
What it does
How
nx2/prompt_keys.py(new, data only, standard library): which icon each key and stick shows, and the code points.nx2/prompt_draw.py: the keyboard face's glyphs. They are Kenney's Input Prompts Keyboard & Mouse font (CC0, the same 1.5 release as the controller faces), in its own folderthird_party/kenney_input_keyboardwith aPROVENANCE.mdand the release'sLicense.txt; the AppImage copies it. The word keys and the mice are drawn as non-crossing contours, so they fill the same under either winding rule.nx2/prompt_assets.py:faces_fontbuilds the keyboard face (the Switch's pad icons, plus each key's icon atKEY_BASE + key code, past every face's code points), and writes itshint.binper key code.host/prompts.c:BINDS), so key changes go through the same live path.cmap4_bindpoints a button at its key's icon on the keyboard face.text_codegives a text a code of its own for each (button, key) pair as it first appears (key_slot), and points it at the key's icon where the game keeps the font.host/nn_hid.c:hos_xinput_pads(), so Auto doesn't pick Keyboard for an XInput-only controller.gpu/vk/vk_menu.cpp: the Keyboard value and its hint;pad_namenames pad inputs by place on the keyboard face.Testing
In game, a build of this branch (81a5445f, the same code before the rebases onto #2448 and #1990) on Windows 11 (native, RTX 4060), keyboard and mouse, no controller. Driven with the control bridge (
HOS_CONTROL, headless,--useron a copy of the saves), into Hyrule Field from a save, then the pause menu:prompts: auto: controller "(none)": keyboard,keyboard icons, 16 buttons on these keys. The pause menu shows Q/R on the tabs, the mouse for Rotate, E for Select, Shift for Back; the HUD's item buttons show R and X.now the xbox icons in texts/now the keyboard icons in texts), the pause menu reopened showing each face's icons. Without #2583 the row says "(restart)" on native Windows, as for every face.Also:
tests/test_prompt_font_bind.pyis extended. On the keyboard face it checks: keys bound in the first or second slot, a key with no icon, the sticks, and text codes for each (button, key) pair. The other faces are unchanged. It passes, as doprompts_live,appimage_prompt_fontsandmenu_hooks.tests/run_all.py: the same results with and without the PR on this machine (its other failures are missing .NET and MSVC-target C++ headers).Screenshots
From the build of this branch, 1280×720 (the bridge's shots: the game's own picture).
Auto with no controller, the pause menu: Q/R tabs, the mouse for Rotate, E for Select, Shift for Back.
Keyboard with A rebound to F: Select reads F.
Xbox, for comparison (a pad face, as before this PR).
The field: the HUD's item buttons on R and X.
🤖 Generated with Claude Code
This is a well-built addition. The keyboard face sits on top of the live prompt system from #2366 without disturbing the controller faces. The code-point ranges are checked against each other in
tests/test_prompt_font_bind.py:KEY_BASEpast every face, slots and sticks clear ofBIND_BASE. Auto only falls back to Keyboard when the build actually madeprompts/keyboard/, and the textures and the Talk ring stay on the Switch's, which is the safe choice. At the PR's head I ranprompt,bound_keysandmenu_hitlocally (7 checks), and all pass. On GitHub only the title check has run so far; the other workflows need a maintainer to approve them.should fix,
nx2/prompt_keys.py:61,69-70,120-121,144,191-194: CONTRIBUTING (Code and data conventions) accepts the prompt packages (fontTools, Pillow, astc-encoder-py, texture2ddecoder) "fornx2/prompt_assets.pyandnx2/prompt_draw.pyalone", and asks reviewers to flag any other use. This new module importsfontTools.pens.*andfontTools.ttLib. The imports are inside functions, so the tests still import it on the standard library alone, which is good. Either move the drawing intonx2/prompt_draw.py, which is where drawing lives, or have the maintainer agree to addprompt_keys.pyto that list and update the sentence in CONTRIBUTING in this PR.should fix (testing): the in-game test installed the new icons into a v2.1.0 build as the Switch face's font. That shows the artwork, but not this PR's host code:
auto_face(host/prompts.c:119), the keyboard branch ofcmap4_bind,key_slotand the slots kept acrosslive_font(host/prompts.c:967on), and the stick rewrites intext_code. Those are the parts that write into the game's font in memory while it runs. Please run a build of this branch once: with Keyboard picked, with Auto and no controller, then plug a pad in and out, and rebind a key while a hint is on screen. Note the result in the description. Screenshots from that build would be better than the transplanted ones.question,
third_party/kenney_input/: CONTRIBUTING says not to modifythird_party/, and to add a new folder with its own provenance note rather than edit one. This adds an unmodified font from the same Kenney release to the existing folder and adds a line to itsPROVENANCE.md. That seems within the spirit of the rule, but it's the maintainer's call. If not,third_party/kenney_input_keyboard/with its ownPROVENANCE.mdandLicense.txtis the literal reading.minor,
host/prompts.c:53,967: the 320 slots are given out once per (button, key) pair for the whole run. Someone who rebinds a lot can use them up, and thenkey_slotsilently falls back to the Switch's icon. A singlehos_logthe first time it runs out would make that easy to diagnose.One heads-up: #1990, also open, raises
HOS_PAD_N, which is the stride of theBIND_BASEcodes this PR builds on. Whichever of the two lands second needs to re-checktext_codeand the slot/BIND_BASEoverlap test.Thanks for the careful review, @Joey-Magnus. Addressed in 81a5445f:
Prompt packages outside their files:
nx2/prompt_keys.pyis now data only (key → icon, the stick table, the code points) and imports nothing but the standard library. All the drawing (word keys, mice,keyboard_glyphs) moved intonx2/prompt_draw.py, so CONTRIBUTING's list stays as it is. The font built is glyph-for-glyph the same as before the move (checked on a v2.1.0 romfs).third_party/: I went with the literal reading. The Keyboard & Mouse font is inthird_party/kenney_input_keyboard/with its ownPROVENANCE.mdand the release'sLicense.txt; it's byte-identical to the one in the same 1.5A release as the controller fonts.third_party/kenney_input/is back as it was.scripts/linux/appimage.shcopies the new folder, andtests/test_appimage_prompt_fonts.pynow checks both.Slots running out:
key_slotlogs once the first time all 320 are given out, saying a key bound from then on shows the Switch's icon in texts until the next start.#1990: noted. Whichever lands second re-checks
text_codeand the slot/BIND_BASErange checks intest_prompt_font_bind.py. I'll do that here if #1990 goes in first.Testing: agreed, the in-game run so far covered the artwork, not this branch's host code. I'll run a build of this branch through the four cases you list (Keyboard picked, Auto with no controller, a pad plugged in and out, a key rebound with a hint on screen), then update the description with the results and screenshots from that build.
run_all.pyshows the same results with and without the PR on this machine; the prompt checks (prompt_font_bind,prompts_live,appimage_prompt_fonts) pass.@Joey-Magnus I ran a build of this branch (81a5445f) in game; the results and screenshots from that build are now in the description. Auto with no controller, Keyboard with a rebound key, and Xbox all show what they should at the start.
The live cases didn't run, for a reason outside this PR. @gab9281, this one may interest you (#2366): on native Windows 11 (RTX 4060, the v131072 game) the font part of the live prompts is never found. The textures' pool and the layouts are found:
But
prompts: the font at ...never comes, even after the pause menu and its pages have drawn their button icons. Sohos_prompts_live_parts()lacksHOS_PROMPTS_LIVE_TEXTS, the Button prompts row says "(restart)", and no change is applied live. The build logged the glyph hook as installed (oot_patches: the glyphs' hook in code_017.c).To rule this PR out, I served a font built without the keyboard face (
--faces switch,xbox,ps4,ps5,steamdeck, 33460 bytes, the size #2366 was tested at) to the same build: same result. Your Windows runs were under GE-Proton; this is native. My guess is that the face's stream there isn't the served file's size (base[1] != font.n && base[1] != font.n - 8inhos_prompts_glyph), but I haven't confirmed it. I can run a build with a log line there if that helps.I don't have any windows PC anymore, sadly 😅
This will turn main red on merge. #2448 landed today with
tests/test_prompt_auto_face.py:51, which requireschoose_faceto containface_of_name(name). This PR changes that line inhost/prompts.ctoauto_face(name). Fix the test to look forauto_face(name)inchoose_faceandface_of_name(name)inauto_face.Everything else looks right: all five
bind[HOS_SW_N]arrays are widened toBINDS, the enum-onlyvk_api.hchange needs no version bump, and the earlier points are addressed. Still pending: the promised in-game run (Auto with no pad, plugging a pad in and out, rebinding with a hint on screen) and a CI run, which hasn't happened on 81a5445f.No worries! I can't dig further on my side right now either. The log lines above are what a native Windows run shows. If someone with a Windows build wants to look, a log line in
hos_prompts_glyphcomparing the face's stream size (base[1]) withfont.nshould show whether it's the size check.@Joey-Magnus thanks, good catch. Rebased onto main (with #2448), and in b3bb8743
tests/test_prompt_auto_face.pynow looks forauto_face(name)inchoose_face(and inlive_face) andface_of_name(name)inauto_face. It also checksauto_faceitself: with no controller, the keyboard's face when the build made it and XInput has no pad either, else the Switch's; with a controller's name, that controller's face. It passes, as doprompt_font_bind,prompts_live,appimage_prompt_fontsandmenu_hooks. On this machinerun_all.pyshows the same results as main, apart fromtest_prepare_at_build's timing case, which failed once under the parallel run and passes on its own.On the in-game run: it's in the description, from a build of 81a5445f, which is the same code before this rebase. Auto with no controller, Keyboard with a rebound key, and Xbox all show what they should at the start. The live cases (rebinding with a hint on screen, plugging a pad in and out) didn't run: on native Windows the font part of #2366's live path is never found, with or without the keyboard face (see the comment above), and I have no controller here. CI still needs a maintainer's approval to run.
b3bb874 fixes what would have turned main red. The branch is rebased on #2448.
tests/test_prompt_auto_face.pynow looks forauto_face(name)inchoose_faceandlive_faceand forface_of_name(name)inauto_face, and it testsauto_face's keyboard cases. At the new head,test_prompt_auto_face,test_prompt_font_bind,test_prompts_live,test_appimage_prompt_fontsandtest_menu_hooksall pass for me, and the branch merges cleanly with main.The earlier points are done: the drawing is in
prompt_draw.py, the font is in its ownthird_party/kenney_input_keyboard/with provenance and license, andkey_slotlogs once when its codes run out. The in-game run covers Auto with no pad, a rebound key and a pad face. Plugging a pad in and out, and rebinding with a hint on screen, still weren't tested. Your comparison points to #2366's font lookup on native Windows rather than this PR; that deserves its own issue. What's left is a full CI run on b3bb874. The #1990HOS_PAD_Nheads-up still applies.This now conflicts with main in
gpu/vk/vk_menu.cpp,host/prompts.c,readme.md,tests/test_prompt_font_bind.py. Could you merge main into the branch (or rebase) when you get a chance? I'll take another look once it's up to date.Tested this branch (b3bb874) on native Windows 11 (RTX 5060 Ti, v131072), keyboard + mouse, through the control bridge:
The live part not finding the font on native Windows: found it. Your guess about the size check was close: the face's stream has the served file's size (55088 =
font.n), sohos_prompts_glyphread frombase + 8. But the font starts atbase: the bytes at+8were00 03 00 30, the OpenType header'sentrySelector/rangeShift, against the head4f54544f("OTTO"). It's in main's code from #2366, not this PR, so the fix is a separate PR: #2583.With that fix on top of this branch, Keyboard → Xbox → Keyboard while the game runs applies at once (
now the xbox icons in texts/now the keyboard icons in texts), and the pause menu reopened shows each face's icons.@hckrman101 rebased onto main (
55b976c0); the branch is at0a766623and merges cleanly. The conflicts were all with #1990's second gamepad binding:host/prompts.c: the keyboard branches incmap4_bindandtext_codenow come before #1990'sHOS_PAD_ART_Nlines (a paddle still shows its default's icon on the pad faces).HOS_PAD_ART_Nkeeps theBIND_BASEstride at 17, so the keyboard face's code points don't move. The overlap checks intest_prompt_font_bind.pypass with six faces.gpu/vk/vk_menu.cpp:pad_namekeeps #1990's paddle names; the table has one more row, and the keyboard face names pad inputs by place.tests/test_prompt_font_bind.py: #1990's paddle binding (Y on R4) and the keyboard face's keys are both tested.readme.md: both entries kept.prompt_font_bind,prompt_auto_face,prompts_live,appimage_prompt_fonts,menu_hooksandbound_keyspass.run_all.pymatches main here, apart from three .NET builder checks that fail under the parallel run on this machine and pass on their own.@polackangry thanks for testing it and for finding the font offset! I've added your live result with #2583 to the description.
github-sync referenced this pull request2026-11-12 06:22:54 +00:00