Skip to content

feat(desktop): the keyboard shortcuts' list binds its keys again - #98

Open
chinzu00 wants to merge 3 commits into
artcraft-android:mainfrom
chinzu00:feat/remappable-shortcuts
Open

chinzu00 wants to merge 3 commits into
artcraft-android:mainfrom
chinzu00:feat/remappable-shortcuts

Conversation

@chinzu00

@chinzu00 chinzu00 commented Nov 7, 2026 •

Copy link
Copy Markdown

Summary

The canvas's shortcuts were a match in the core: which press did what was fixed, and the list in
Help could only show it. They are a table now, and the list binds a row's keys when they are
clicked. Press the new key to bind it, Backspace to clear it, Esc to give up; Reset restores the
app's own. The bindings are kept between runs with the other preferences.

This is the desktop half of #81 (customizable shortcuts); the core side is exported for Android in
KeyBindings, but no Android UI for it is in this PR.

What changed

Core — crates/core/src/keys.rs

  • KeyAction names what a press can do (Undo, Paint, Layers, …). Brush size and arrow nudging stay
    out: they take a step from the modifiers rather than being one command.
  • KeyBinding/KeyBindings hold the presses each action answers to. They start from the shortcuts'
    table itself, so the list and the keys cannot drift apart.
    bind() takes a press from whatever had it, unbind() clears an action, reset() restores the
    start, read_line()/kept_lines() read and write them in the app's settings file.
  • key_shortcut_bound() reads the presses as they are now, keeping the reading the canvas has
    always had: the exact press, then with Ctrl the same press without Shift, then the key alone, then
    the presses that take a step. Ctrl+Alt (AltGr) still types rather than binding, and Ctrl+Space
    still does nothing.
  • key_shortcut() and key_shortcut_list() keep their signatures for the Android bindings and mean
    "with the presses the app starts with"; key_shortcut_with()/key_shortcut_list_with() take
    bindings instead. ShortcutRow gained action, so a caller knows which row can be bound.

Desktop — desktop/src/

  • prefs.rs: Prefs::keys, read from and written to interface as key_undo ctrl+z,ctrl+shift+z
    lines (an empty value is unbound).
  • ui.rs: the canvas's keys go through prefs.keys; the shortcuts panel takes the keys while it is
    open, so a key being chosen does not also paint or undo.
  • shortcuts.rs: a shortcut's row is one target — its keys and its name light together while the
    pointer is over either, and a click anywhere on the row asks for a key ("Press a key…"). The list
    shows what the bindings have now ("Unbound" when an action has none), and rises into place: a
    150 ms fade and a 16-point lift, easing out. Reset and a hint line sit by Done.

Testing

  • cargo fmt --all --check and cargo clippy --workspace --all-targets -- -D warnings pass.
  • cargo test --workspace passes: 566 tests. On this machine the two plugin_filter tests that use
    a real GPU fail as they always have here (a_filter_opens_redraws_as_it_slides_and_cancel_puts_the_layer_back,
    reset_preview_and_undo_keep_it_open_at_its_start); CI's runner has no GPU and skips them.
  • New tests in keys.rs: the presses bound do what they did before they could be bound; binding a
    key adds it without changing the rest; a press bound again is taken from what had it; an action
    unbound has no keys and does nothing; the bindings keep and come back as they were (settings-file
    round trip); a press is written and read back.
  • Tried by hand on Windows with the release build: opened Help › Keyboard shortcuts, clicked Erase's
    keys, pressed F (row read E F, interface gained key_erase e,f), pressed Done and F
    selected the eraser; a binding saved before a restart was shown again after it.
  • Held Ctrl with no other key: the list came up after 0.9 s and went on release; turned
    Shortcut list on Ctrl held off in Prefs (shortcuts_on_ctrl false in interface) and held
    Ctrl again: nothing came up.
  • The rise: a capture 20 ms in shows the panel half-faded and 16 points low; 0.3 s later it is
    solid and in place. Hovering a row's name lights its keys too, and clicking the name asks for
    the key.

Ctrl (⌘) held alone shows it

That way of showing the list never worked: Shortcuts::show counted "another key is down" as
!keys_down.is_empty(), and egui puts the modifier keys themselves in that set (Key::ControlLeft
and its kind), so the hold was spoiled on its first frame. What spoils the hold now is a key that
is not a modifier (Shift, Ctrl, Alt or Super, either side); a click or a scroll still does. The
list comes up after 0.9 s and rises in.

A new Prefs switch, Shortcut list on Ctrl held (shortcuts_on_ctrl, on by default), turns it
off for anyone who wants the list from Help alone. It is kept with the other preferences, so a new
line joins the file: shortcuts_on_ctrl true or false.

Part of #81 — the desktop side; the Android UI for the same bindings is still to come.

The canvas's shortcuts were a match in the core: which press does what was
fixed, and the list in Help could only show it. They are a table now, and the
list binds a row's keys when they are clicked: press the new key, Backspace
takes them away, Esc gives up, Reset puts the app's back.

- core (keys.rs): KeyAction names what a press can do; KeyBinding/KeyBindings
  carry the presses each action answers to, starting from the shortcuts' table
  itself so the list and the keys cannot drift apart. bind() takes a press from
  whatever had it, unbind() clears an action, reset() restores the start, and
  read_line()/kept_lines() write them into the app's settings file.
- core: key_shortcut_bound() reads the presses as they are now, keeping the
  reading the canvas has always had: an exact press, then with Ctrl the same
  without Shift, then the key alone, then the presses that take a step (brush
  size, arrows). key_shortcut() and key_shortcut_list() keep their signatures
  for the Android bindings and mean "with the presses the app starts with";
  key_shortcut_with()/key_shortcut_list_with() take bindings instead.
- desktop: Prefs keeps the bindings in the interface file (key_undo ctrl+z,...),
  and the canvas's keys go through them. The Keyboard shortcuts panel paints a
  row's keys as a target: hovered they light, clicked they ask for a key, and
  the list shows the presses the bindings have ("Unbound" when there are none).

Testing: cargo fmt --all --check and cargo clippy --workspace --all-targets --
-D warnings pass; cargo test --workspace passes (566; the two GPU filter tests
in plugin_filter are the ones this machine fails outside CI, as DEV-SETUP
notes). Tried by hand on Windows: bound Erase to F in the release app, saw the
row change and interface gain key_erase e,f, and F then chose the eraser; a
binding saved before a restart was shown again after it.
egui puts the modifier keys themselves in keys_down (Key::ControlLeft, Key::ShiftLeft and their
kind), so the test for another key being down was true on the hold's first frame and the list never
came up.

- shortcuts.rs: what spoils the hold is a key that is not a modifier (Shift, Ctrl, Alt or Super,
  either side); a click or a scroll still does.
- Prefs: "Shortcut list on Ctrl held" (shortcuts_on_ctrl, on by default). Off, the list is asked
  for from Help alone; the setting is kept with the others.

Testing: fmt, clippy and the tests pass as before. Held Ctrl with no other key on Windows: the list
came up after a second and went on release. Turned the switch off (shortcuts_on_ctrl false in the
interface file) and held Ctrl again: nothing came up.
… and lights whole rows

- The list fades and rises 16 points into place over 150 ms, easing out, however it was asked for
  (Help or Ctrl held); the scrim fades with it. It used to appear in one frame.
- Ctrl (⌘) held alone shows it after 900 ms rather than a second.
- A shortcut's row is one target: its keys and its name light together while the pointer is over
  either, and a click anywhere on the row asks for the new key.

Testing: fmt, clippy and the desktop tests pass. On Windows, holding Ctrl gave a half-faded panel
mid-rise and the settled one 16 points higher; hovering the name of a row lit its keys too.
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.

1 participant