Description
Writing down notes about implementing a shortcut system. I have started using menus more extensively in my own applications, and the lack of support for shortcuts has become a little annoying.
Without shortcuts the user is required to duplicate code to handle menu items and shortcuts for a same action. Consider that MenuItem() can takes both a "checked" and "enabled" parameter which may need to be fetch/computed from your application state, duplicating that code can be pretty annoying and ImGui strive to reduce redundancy so it's quite a flaw to have to do that.
In the old Menus API #126 thread I said we'd need;
Support local keyboard shortcuts, We can use Windows syntax of using & (e.g. "&Save") for convenience.
And
Support general "global" shortcuts (e.g. "CTRL+S"). As a design goal of ImGui we want to avoid code and state duplication, so I'd like the ImGui system to handle shortcuts for the user. It will be optional but likely available by default. So the program can have a single entry point for "Save" whether it is activated via clicking in the menu or via pressing the shortcut. The way it would work is that when a global shortcut scheme is activated, the menu functions always notify the user code to develop its content so ImGui can parse and execute the shortcuts as they are declared, but the actual menu is not layed out nor rendered. The shortcut scheme can be disabled on a per-menu/window/global basis. In particular, procedurally generated menus that may have infinite depth will need to be able to disable the global shortcut scheme. In its "closed" state, the system has to be as lightweight as if the user were testing a bunch of shortcuts themselves. The scope of shortcuts can be dependent on factor such as if the parent window is focused so they aren't always "global". The user should also be able to display the label for a shortcut in the menu without letting ImGui handle the shortcut itself.
So here is a rough list of what I think we need. Unfortunately some of those will need the user to update their ImGui integration to provide the necessary inputs, but there won't be any breakage.
PART A, for regular shortcuts (typically global shortcuts, but they can be local to a window)
EDIT Not needed!
- [ ] We are going to need translated characters, aka the "A" in "CTRL+A" or "ALT+A" is a translated character. So the end-user application needs to feed those inputs probably via io.AddInputCharacter() and this isn't really a problem for ImGui to solve. However, and that's very surprising, retrieve this information under Windows is NOT straightforward. The WM_CHAR message isn't sent when ALT or CTRL are pressed. WM_SYSCHAR is only sent when ALT is pressed. No CHAR messages are sent when CTRL is pressed. By adding this in a Windows message handler I seem to be able to retrieve those characters.
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
if (GetKeyState(VK_CONTROL) & 0x8000)
{
BYTE keys[256];
memset(keys, 0, sizeof(keys));
keys[VK_SHIFT] = (GetKeyState(VK_SHIFT) & 0x8000) ? 0x80 : 0;
WCHAR buf[4];
const int scancode = (lParam >> 16) & 0x1ff;
const int buf_sz = ToUnicodeEx(wParam, scancode, keys, buf, 4, 0, NULL);
for (int n = 0; n < buf_sz; n++)
io.AddInputCharacters(buf[n]);
}
return 1;
It is a little scary but appears to work. End-user would need a similar mechanism which is rather annoying. For Windows messages user can copy demo code if they are pointed to it. GLFW has been patched in the 3.1 branch to support the ALT key but not the CTRL key yet (would be nice if it does so), what that means is that support for those inputs won't be widespread in most applications soon and it is only likely to be widely available in GLFW when 3.2 ships (also means that support for CTRL key would better be pushed in GLFW before they release 3.2!). Or user can freely do their own cheap conversions if they don't care about funky localisation things.
I'd be curious to know whether GLFW 3.1 gives you character inputs in ImGui_ImplGlfw_CharCallback when ALT or CTRL are pressed on MacOS, Linux, etc. See following rely for instructions on how to perform the test. If you can do a test let me know which version of GLFW you are using. Thanks!
- Need to add all non-printable to the ImGuiKey_ enum: functions keys, insert, keypad stuff.. Unfortunately I didn't add those initially, my bad :( (Been thinking about adding a "Tester" to the demo code that helps you verify your ImGui integration, test things like clipping rectangle, texture changes, all sort of inputs. So this tester could list the keys and make sure you can input them.)
- We are going to need to parse shortcuts string efficiently. Encoding should fit in 32-bits (e.g. 16-bit value, 1 bit to select translated character or key, 3 bits modifiers, 2 bits scope: window, window-and-parent, global). Maybe just parsing on the spot (hardcoded stricmp for prefixes such as "CTRL+") is simpler and faster but we need some sort of fast handling of all the named keys that aren't character (e..g F5, HOME, etc.) so this may make the encoding slower. Also figure out a syntax to specify shortcut scope, or this may be not in the string but rather specified in the callee function. If parsing is slow, perhaps hash/FNV1a the string and cache parsed result as a single u32 stored in a ImGuiStorage which is a contiguous sorted map, touching O(N log N) 8-bytes entries. Either way both are rather easy to try.
- Function that checks if an encoded shortcut is pressed. May or not be merged with the parsing function. Trivial.
- Menus need to be able to run in a mode where nothing renders but the global shortcuts are still processed. As per my old comment above, this may be optional and not the default, and the user needs to be able to disable the feature to allow for recursive menu. Perhaps the feature not spreading to sub-menu automatically would make more sense?
PART B (for & ALT-style local shortcuts, lesser priority)
- Menus needs to process the & ALT-style local shortcuts. The behavior little different from the other shortcuts and may imply keyboard controllable menu, at least support for the Enter key.
- We are going to need be able to parse and render the "&Save" syntax for & ALT-style local shortcuts. The text size calculation and text renderer will need a flag to support this feature. For size calculation, we can treat & as zero-width. For rendering, we need to query the width of the next character and draw an underscore below the baseline. We might or not need to handle inhibition with the \ character to be able to render regular & in this mode. All that would be normally easy BUT one problem is that CalcTextSizeA() is often a major bottleneck in very large UI and thus we can't afford to make it slower so those feature would have to be designed accordingly and will need to alter the low-level text rendering API. (Unrelated to this we want to allow centered and right text alignment on a per-line basis and this would have an effect on the text rendering API as well).
- How about adding support for shortcuts to regular widgets like Buttons? Problem with & ALT-style is that it may have a small overhead with text-processing. But may be a nice default and thus we may design the feature expecting it to be always on. For CTRL-style shortcut, probably best to avoid encoding them in a label and just accept that the user can do their own shortcut checking aka
if (ImGui::Button("Refresh") || ImGui::IsShortcutPressed("F5") { .. }
.
That's it for now. Those are merely notes for myself. If there are