Skip to content

Add a key binding editor - #322

Open
MAX-WiRED wants to merge 1 commit into
uvcat7:betafrom
MAX-WiRED:feat/key-bindings-dialog
Open

MAX-WiRED wants to merge 1 commit into
uvcat7:betafrom
MAX-WiRED:feat/key-bindings-dialog

Conversation

@MAX-WiRED

@MAX-WiRED MAX-WiRED commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Changing a key binding means closing the editor, opening
settings/shortcuts.txt in a text editor, working out both the name of the
action and the notation the parser expects, and starting the program again.
The file is the only way in - Help → Shortcuts shows the bindings but cannot
change them.

The dialog

Edit → Key bindings… lists every action that can be bound, with its
current binding in a column against the right edge and a filter box above the
list.

  • Rebind waits for a key press or a mouse scroll and takes it as the new
    binding. Escape cancels the wait.
  • Clear removes the binding.
  • Save writes settings/shortcuts.txt, Reload reads it back, and
    Defaults restores the bindings the program ships with.

Nothing is written until Save, so Reload undoes a session of changes.

A key combination can only ever trigger one action, so binding one that is
already taken removes it from its previous owner and says which action lost it
in the message area.

Where the defaults come from

"Defaults" needs a copy of the shipped bindings that saving never overwrites,
so this adds bin/settings/shortcuts_default.txt - the current
shortcuts.txt with a two line header. bin/ is in .gitignore while
bin/settings/shortcuts.txt is tracked, so the new file needed git add -f
to go in beside it.

Keeping two copies means they can drift. Compiling the defaults into the binary
would avoid that; a data file was chosen because it keeps the two readable side
by side and uses the parser that already exists. Happy to switch if you would
rather not carry the duplicate.

Shortcuts

The interface grows what the dialog needs: walking the list of bindable actions
(getNumActions, getActionName, getActionCode), changing one
(setBinding, setScrollBinding, clearBindings), and the file operations
(saveToFile, reloadFromFile, restoreDefaults).

Reading the file moves out of the constructor into LoadFile(path), which the
constructor, the reload and the defaults all call. saveToFile writes key
names rather than the display notation, because the parser splits on + and
looks names up - a character like - would not survive the round trip.

Saving writes next to the executable, which is where the file is read from
today. On a packaged install that directory may not be writable; the failure is
reported rather than silent. Routing both through a per-user settings directory
would be a better answer, but that is a change to where every setting lives and
does not belong here.

WgSelectList

Two additions, both off unless asked for, so every other list looks the way it
did:

  • alignItemsLeft() - draws item text against the left edge instead of
    centred, which is what a long list of names wants;
  • addItem(text, rightText) - a second text drawn against the right edge. The
    right hand text is drawn first, so a long name is cut off by the ellipsis
    rather than running over the binding.

Testing

Built on Windows with clang-tidy and clang-format enforced, as the build does.

Walked the whole cycle: picked an unbound action, pressed Rebind, pressed F9 -
the row shows F9. Save wrote BACKGROUND_HIDE = F9 into
settings/shortcuts.txt in the format the parser reads back. Defaults dropped
it from the list, Reload brought it back from the saved file. Filtering narrows
the list as you type, and the bindings stay in their column.

Standalone now

This was one commit on top of the frame rate PR, which is where the Options
menu came from. That PR is closed, so the branch is rebased onto beta with
that commit dropped and the entry moved to the foot of the Edit menu, next
to the editor's other settings. Nothing here depends on anything unmerged.

The rebase turned up one thing worth mentioning: DialogId gained
DIALOG_KEY_BINDINGS but IdStrings in Dialog.cpp was never given the
matching name, so getName returned a null pointer for it and pinning the
dialog would have crashed on the next start. The name is in now. The array is
declared [NUM_DIALOG_IDS], so a short initializer is filled with nulls rather
than refused - happy to add a static_assert on a deduced size if you want the
next person to be told at compile time instead.

@sukibaby

sukibaby commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Definitely support this, but not in its current form which is dependent on a separate PR. I would like to see it be a free standing PR. Then I would gladly approve.

Rebinding a key means closing the editor, opening settings/shortcuts.txt
in a text editor, working out the name of the action and the notation
for the key, and starting the program again. The file is the only way in,
and Help -> Shortcuts is read only.

Options -> Key bindings... lists every action that can be bound, with
its current binding in a column of its own and a filter box above.
"Rebind" waits for a key press or a mouse scroll, Escape cancels the
wait, and "Clear" removes the binding. "Save" writes the file, "Reload"
reads it back, and "Defaults" restores the bindings the program ships
with.

Those defaults live in a new settings/shortcuts_default.txt, a copy of
the shipped shortcuts file that saving never touches, so "Defaults"
always has something to restore.

Shortcuts grows the functions the dialog needs: walking the list of
bindable actions, setting and clearing a binding, and reading the file
again. Loading moves out of the constructor into LoadFile, which the
reload and the defaults both use. A key combination can only trigger one
action, so binding one that is already taken takes it away from its
previous owner and says so in the message area.

WgSelectList gains left aligned items and a second text drawn against
the right edge, which is what puts the bindings in a column. Both are
off by default, so every other list looks the way it did.
@MAX-WiRED
MAX-WiRED force-pushed the feat/key-bindings-dialog branch from 1319f12 to acfab0e Compare September 6, 2026 17:27
@MAX-WiRED

Copy link
Copy Markdown
Contributor Author

*Updated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants