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!
PART B (for & ALT-style local shortcuts, lesser priority)
if (ImGui::Button("Refresh") || ImGui::IsShortcutPressed("F5") { .. }.That's it for now. Those are merely notes for myself. If there are
I tested this (in the opengl_example) on Mac and added a printf() in KeyCallback function and I got there everytime when pressing some key and holding ctrl, alt or command.
Sorry my request wasn't clear, the problem is with the CharCallback not the KeyCallback.
To enable CharCallback with modifiers in GLFW 3.1 you need change
glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback);
to
glfwSetCharModsCallback(window, ImGui_ImplGlfw_CharCallback);
And add the extra integer to the ImGui_ImplGlfw_CharCallback function.
It isn't done in the default ImGui example because this function was introduced in 3.1 which is not in some package repos.
Did the changes and Alt seems to work just fine. Ctrl seems to work in some cases but not in others.
Any specific pattern with CTRL working vs not working?
Do simple things like Ctrl+A, Ctrl+Shift+A, Ctrl+Alt+A works? Are the broken cases on specifc keys?
Thanks for looking, this is really helpful. This stupid app-side input problem has been the number one barrier for adding shortcuts so I am eager to solve it. I submitted a GLFW tentative patch for Windows but have no way to look into Mac right now.
For this specific use of shortcuts frankly I don't mind too much if some weird keys don't work seeing it is an application-side problem that can be fully solved later, I just would like basic support to work for most users.
So it seems Ctrl+A-Z doesn't work but 0-9 and other non A-Z keys (like /= etc) works fine.
Alt seems to work on all keys. Ctrl+Alt-.... behaves the same as Ctrl- only
Thank you, very useful. Taking it to glfw.
np :)
The first point in the list appears to be the most problematic. It looks like I may just need to redesign the IO API, which was desirable anyway, to switch to an event-registration system so e.g. have a AddKeyPress the same way we have a AddInputCharacter and the user may pass a optional stringified localized key name along with keypress.
May or not need to break compatibility with the user-glue code, very obviously if I can avoid it I'll try very hard to avoid it. I always liked how ImGui took this nice shortcut of taking a sample of input state because it makes initial integration with all sort of libraries easier, but we also need a way out of that.
Yeah that sounds. Good also something I have been thinking about for a while (sorry for being a bit of topic) is the versioning of the lib. Something I have started to come to like is "Semantic Versioning" http://semver.org I kinda like this approach as it's clear when breaking changes comes that may cause issue. This would of course break the current versioning and it's of course up to you to use or not but may be something to consider.
Sorry to jump on like this, but would it be possible to allow for more flexibility in the shortcut modifier? On OS X it's quite annoying whenever an application doesn't implement Cmd+A/S/V/N/X shortcuts, and chooses to go with Ctrl+whatever.
I already maintain a local patchjob of ImGui::InputTextEx that handles the OS X shortcut style and text editing behaviour (rather, I just #ifdef it away and include a copied version from a header file). It would be great if the new global shortcut system could allow a little leeway in not hardcoding for Ctrl so much?
Just a suggestion, of course.
How do you think it would work? I don't know enough about Mac experience to design that. My idea is that the user would pass in strings to the api, e.g. "CTRL+S". Then it could be the responsibility of user code to pass in "CMD+S" there? Or an optional/default behavior would be to allow to remap CTRL to CMD by default? (both for key testing and display).
Is the Command key a wholly different key? Does typical third API feed it as an modifier? GLFW has GLFW_KEY_LEFT_SUPER/GLFW_KEY_RIGHT_SUPER keys for the Command key and a keyboard modifier GLFW_MOD_SUPER so that's all ok. Is Super a good terminology that can adapt to both Command and Windows key?
_EDIT_ SDL2 has KMOD_GUI/KMOD_LGUI/KMOD_RGUI so I'll assume other API are supporting this as a modifiers keys as well.
Also - you could post your InputTextEx() mod for reference?
So the way I have done that in the past is like this:
https://github.com/emoon/rocket/blob/master/ogl_editor/src/Menu.c?ts=4#L26
EMGUI_KEY_COMMAND, EMGUI_KEY_CTRL
First part is the modifier on Mac and the other is on Windows/Linux. The thing is that while the Windows key isn't used that much on Windows actually for shortcuts inside programs that is certainly the case on Mac.
Ah, thanks for responding. I've changed a number of things actually:
ENABLE_OSX_TEXT_EDITING is defined to be 0)#if guards in there to handle it as well.(3) is done a bit hackily, since I'm not too familiar with the code, I did a move left word, select word right.
I know that these changes work on OS X, but since I don't have a windows machine to test on (and I'm not sure the rest of my code would work), I can only guarantee my changes to work... "in theory". It's all compile time checks too, so no performance hit either.
I also added a KeyCmd member in the ImGuiIO struct, which is basically the same as KeyAlt and the others. It's updated in the backend input event function as well.
Here's the diff: https://gist.github.com/zhiayang/5764aacd621ebbed6f0b
(um... i have a habit of changing the whitespace, so in case the diff is too unreadable there's the whole function below. terribly sorry)
Here's my edited version: https://gist.github.com/zhiayang/27b3f032a054e503484a
On InputTextEx( ) for Mac: regardless of the shortcut API above we should probably merge those three into master, add the KeyCmd member. They could be a runtime settings (for slightly better build coverage) and set the default differently based on __APPLE__ define. If someone wants to try making a clean version of that I'll merge else I'll give it a go sometimes.
For shortcuts, we could require the user to pass in different strings per os for apps caring about portability in this manner (not all imgui apps will do), but that would make user code more bulky. Or support a syntax like "CTRL|CMD+S", simple but a little weird. Or have a system where by default "CTRL+S" gets translated to "CMD+S" on Mac ("Mac" defined via a runtime flag again) BUT with a mechanism to enforce actual CTRL when desired, aka a different identifier for CTRL on Mac, e.g. "XCTRL". Or Control key stays "CTRL" and we have a different short identifier meaning "CTRL|CMD"., I don't know how to name that.
CTRL = Ctrl (Windows), Cmd (Mac)
XCTRL = Ctrl (Windows), Ctrl (Mac)
CMD = Cmd (Windows, nearly never used), Cmd (mac)
With R/L variants "LCTRL", "RCTRL".
Looking at Daniel's table it looks like the majority of occurrences of CTRL are to be CMD on Mac, but not all of them.
I'll have a go at a proper pull request for this, it shouldn't take long. A few things to clarify though:
__APPLE__. Does this bool go in ImGuiIO, or is somewhere else more appropriate?OSXAlikeBehaviour) or separate, eg. TextShortcutsUseCmdKey and DoubleClickSelectsWord?KeyCmd would be present in the ImGuiIO struct regardless of OS? Should it then be named something more agnostic, like KeySuper or KeyGui? If the answer to the first question is "no", then ignore all this.LSuper and RSuper in the ImGuiKey_ enum? And should the backends set this by default when handling events? Should these be set for Windows systems as well? (Actually idk if Windows passes down such keypresses to applications)is_word_boundary) based on whether it's next word forward or backward; this puts it in line with OS X behaviour, where the cursor sticks to the end of the word (before the space) if going forward, and sticks to the beginning of the word (after the space) if going backward. Linux does this too, only Windows (as usual) puts the cursor after the space, regardless of direction. Should this be merged as a local change (just a couple of lines), as a pull request to STB itself, or not at all?EDIT:
Thanks for sticking around I suppose, hope to hear your opinions on this. Should make for a more fluid and comfortable experience for OS X users, at least.
Late answer and happy new year :)
I'll have a go at a proper pull request for this, it shouldn't take long. A few things to clarify though:
Cmd/Ctrl should be a runtime setting defaulting based on APPLE. Does this bool go in ImGuiIO, or is somewhere else more appropriate?
I'll have to think a little further about that but for now you can put it in ImGuiIO. Down the line I expect user controllable behaviors for variety of things (eg: slider/drag behavior). Probably just keeping things in IO would just be simpler, but for some behavior it might make sense to Push/Pop the value and then perhaps the ImGuiStyle or another structure may feel more suited. For now let's not worry about that.
Should the double click text behaviour change as well? Would it always select by word when double clicking, or would this also be OS dependent? If the latter, should it be governed by the same setting as (1) (eg. OSXAlikeBehaviour) or separate, eg. TextShortcutsUseCmdKey and DoubleClickSelectsWord?
I suggest to make a different bool. Eg: InputTextShortcutsUsesSuperKey, InputTextDoubleClickSelectsWord.
I'm assuming KeyCmd would be present in the ImGuiIO struct regardless of OS? Should it then be named something more agnostic, like KeySuper or KeyGui? If the answer to the first question is "no", then ignore all this.
Yes, easier to always have in the structure regardless of OS. Let's call it KeySuper because glfw is the cool active thing in town for (with a comment mentioning it maps to Cmd/Windows key). People don't really use the Windows key ar all because it is awkward to use.
In a similar vein to (3), should there be new enum members for LSuper and RSuper in the ImGuiKey_ enum? And should the backends set this by default when handling events?
Yes, yes.
Should these be set for Windows systems as well? (Actually idk if Windows passes down such keypresses to applications)
I don't know either. I wouldn't bother setting them unless it looks trivial. If you don't have access to a Windows machine I'll do some basic tests but not worry if there's a hurdle.
I've made a change to stb_textedit as well, where it calls different functions (is_word_boundary) based on whether it's next word forward or backward; this puts it in line with OS X behaviour, where the cursor sticks to the end of the word (before the space) if going forward, and sticks to the beginning of the word (after the space) if going backward. Linux does this too, only Windows (as usual) puts the cursor after the space, regardless of direction. Should this be merged as a local change (just a couple of lines), as a pull request to STB itself, or not at all?
Ideally merged in STB itself with an additional flag. Sean probably won't merge soon but we can apply the patch locally.
Finally, is the current implementation of doubleclick word select (ie. move cursor to previous word, hold shift, move cursor to next word) satisfactory? I could look into properly moving the cursor, if needed.
Haven't looked yet, sorry.
Thanks for sticking around I suppose, hope to hear your opinions on this. Should make for a more fluid and comfortable experience for OS X users, at least.
Thanks for the help!
Instead of using the "&Save" syntax you could use "Save", VK_S, aka let the user pass in which indices you have to monitor in the keysDown array.
It does require that the user will also need to pass the correct index of which letter to highlight. Because you can't rely on any mapping between them. Then there is no overhead with text processing.
That would add an extra parameter to every function which would be practically a no-no.
It would be bearable if we only used it for MenuItem but ideally we want local shortcuts for keyboard activation for all widgets (e.g. Button)
I'm tempted to say to change the string param to a special struct that includes all the data for the & alt menu (plus the label/ID stuff ofcourse).
Which also has a constructor taking a char* so it won't break existing code. Which can then do the bit of parsing needed for the "###ID" stuff and you may as well add the & parsing as well. Also it may be an option to use && to specify a literal & and &&& for the version where the & itself is the shortcut.
Though the viability of that struct may depend on how the ImStr experiment #494 turns out.
On a slightly related note something that would be nice is keyboard navigation of menus (excluding the actual shortcuts) but perhaps that belongs in a separate issue.
Agree, it would make sense to aim for that support early on (there's a keyboard navigation task and menus could be worked on earlier). Tho up/down without alt+letter may feel incomplete?
On 13 Mar 2016, at 19:39, Daniel Collin [email protected] wrote:
On a slightly related note something that would be nice is keyboard navigation of menus (excluding the actual shortcuts) but perhaps that belongs in a separate issue.
―
Reply to this email directly or view it on GitHub.
I was thinking about popups in this case (at least on Mac if you have a popup and press arrow down you get keyboard focus and can navigate it with arrows and then enter to select an item)
But for regular menus that would be nice yes.
So the simple solution to my problem with obtaining translated characters when ALT/CTRL is pressed to use with shortcuts, is instead to include printables like A-Z 0-9 in the ImGuiKey_ enum and work with key events.
Pros:
Cons:
Is this the right thread to watch for things like cmd-a doing a select-all in a text box instead of ctrl-a on os x?
@xaxxon no. If there's a bug with Mac behavior in inputText() you can open a new thread to report. Try to be extra explicit because I don't use Mac myself, and may not know what is currently happening nor whats the expected behavior. I applied two users patches related to that so far and they seemed correct.
I forgot my local source is getting a bit long in the tooth. :) Thanks again for imgui! loving it.
If there's indeed a mapping mismatch for Mac the fix is probably trivial, so just let me know if there's anything dubious! Thanks :)
So (and this is somewhat of a delayed response to earlier messages, and may possibly be wholly irrelevant now)... I have seen two models for handling modifier-based shortcuts.
This uses a combination of characters prefacing the key, such as:
# Win (Windows logo key) - would operate as Mac Option/Command key.
! Alt
^ Control
+ Shift
e.g., ^!s for Control-Alt-s
There is also a keylist of non printable keys, to allow such things as:
^Numpad0 for Control-Numberpad-0
and even specifiers for LHS or RHS modifiers, e.g.,
>+s for RightShift-s
The other system I have come across is employed by Mousetrap, a JavaScript key binding library, which accept text strings such as
Mousetrap.bind('mod+s', _saveDraft);
(On Mac this ends up mapping to command+s whereas on Windows and Linux it maps to ctrl+s, although I don't quite see the logic there...) 'ctrl' and 'alt' are other alternatives, and it also accepts multi-key sequences as 'g o command+enter', which is probably a bit too much for your requirements.
And there would be a third method, which only occurs to me now since I went to look up the unicode symbols for the Macintosh modifier keys, and that would involve using unicode characters, specifically:
⌘ – ⌘ – ⌘ – the Command Key symbol (windows key)
⌥ – ⌥ – ⌥ – the Option Key symbol (alt key)
⇧ – ⇧ – ⇧ – the Shift Key symbol
(and I'm fairly sure there is a ^ unicode symbol used for Control in that range aswell).
And I'd personally favour a declaration system (if it does have to be a 1 string affair) of
"Cut\t^x"
(That being tab separation of a menu title from it's shortcut key). Not sure I can recall how one specifies 'x' as a shortcut for 'Cut' in conventional windows code TBH.
Any updates? I'm in the process of moving a game editor to use ImGui exclusively and right now I'm implementing shortcuts in a very hacky way. Is there still interest in official shortcut APIs?
There is still interest. I had to stop focusing on imgui last year to pursue another project so few things have moved, but hope to get back to it.
No worries, best of luck with your new project(s), and thanks for all the awesomeness. Let me know if there's any way I can help.
Why code hotkeys into Buttons instead of making them seperate?
if(ImGui::SmallButton("Delete item") || ImGui::HotkeyEntered(0, ImGui::Delete))
{
//...
}
//ImGui function:
bool ImGui::HotkeyEntered(ImKeyModifiers modifiers /* Alt, Shift, Ctrl, Meta */, ImKey key);
That's also possible and not conflicting with the need of having ALT-style local shortcuts defined and known by the button (since we expect a visual reaction to holding ALT and pressing the letter key).
My two cents from #2625 PR.
You will see #ifdef's to serve as an example. Please scroll down to the end of this comment for information how they can be avoided.
I read related all linked issues and never encountered pattern already present in ImGui:
#ifdef __APPLE__
ImGui::SetNextShortcut(ImGuiKey_A, ImGuiModKey_Cmd);
#else
ImGui::SetNextShortcut(ImGuiKey_A, ImGuiModKey_Ctrl);
#endif
if (ImGui::Buton("Select All"))
{
// ...
}
One may argue that define is not a convenient solution, a lot of repetition and platform dependent behavior. Another approach may be more convenient:
// in initialization
#ifdef __APPLE__
ImGui::DefineAction("SelectAll", ImGuiKey_A, ImGuiModKey_Cmd);
#else
ImGui::DefineAction("SelectAll", ImGuiKey_A, ImGuiModKey_Ctrl);
#endif
// in loop
ImGui::SetNextAction("SelectAll");
if (ImGui::Buton("Select All"))
{
// ...
}
Things can be pushed even further:
// in initialization, let's assume that acctionId has form `##!<id>`
// void DefineAction(const char* actionId, const char* label, int key, int mods);
#ifdef __APPLE__
ImGui::DefineAction("##!SelectAll", "Select All", ImGuiKey_A, ImGuiModKey_Cmd);
#else
ImGui::DefineAction("##!SelectAll", "Select All", ImGuiKey_A, ImGuiModKey_Ctrl);
#endif
// in loop
if (ImGui::Buton("##!SelectAll"))
{
// ...
}
To get rid of (most) ifdefs ImGui may hold some predefined actions:
// imgui.h
IMGUI_API const char* ImGuiAction_Copy;
IMGUI_API const char* ImGuiAction_Paste;
IMGUI_API const char* ImGuiAction_SelectAll;
// ...
// imgui.cpp
const char* ImGuiAction_Copy = "##!copy";
const char* ImGuiAction_Paste = "##!paste";
const char* ImGuiAction_SelectAll = "##!select_all";
// context initialization (probably will have form of static const table instead of series of DefineAction calls)
#ifdef __APPLE__
ImGui::DefineAction(ImGuiAction_Copy, "Copy", ImGuiKey_C, ImGuiModKey_Cmd);
ImGui::DefineAction(ImGuiAction_Paste, "Paste", ImGuiKey_V, ImGuiModKey_Cmd);
ImGui::DefineAction(ImGuiAction_SelectAll, "Select All", ImGuiKey_A, ImGuiModKey_Cmd);
// ...
#else
ImGui::DefineAction(ImGuiAction_Copy, "Copy", ImGuiKey_C, ImGuiModKey_Ctrl);
ImGui::DefineAction(ImGuiAction_Paste, "Paste", ImGuiKey_V, ImGuiModKey_Ctrl);
ImGui::DefineAction(ImGuiAction_SelectAll, "Select All", ImGuiKey_A, ImGuiModKey_Ctrl);
// ...
#endif
Approach with ImGui::SetNextAction() seems to be good compromise.
I assume actions can be queried for printable representation ImGui::GetActionShortcutName(ImGuiAction_Copy) -> "Ctrl + C", so it can be easily added to menu items.
Any plans or ideas? Is there any unofficial way to implement shortcuts? Thanks.
Above I made a draft of the idea how shortcuts can be approached. As it is my first idea I think it can get better after getting feedback.
I can pursue this direction and start working on implementation when @ocornut decide it fits to his plans for ImGui.
As a thought, if approaching a generalized solution for imgui, you might want to consider key chords in addition to basic bindings. e.g., just a key plus a modifier is insufficient for some UX designs. Various tools and editors have chords like CTRL+K, O (press control+k together, then press just o by itself, usually within some timeout period) that they might want to support. "Mash" or double-tap style inputs have become popular lately too (e.g. SHIFT, SHIFT to open a command palette in some editors). It can also be handy to be able to query if a key-chord is partially complete (for UX display purposes) and to specify timeouts (e.g., if there's both a CTRL+K, O bind and a CTRL+K bind, how long does the user have before pressing that o key before the CTRL+K bind is triggered?).
There's also then conflicting keybinds to consider. CTRL+P vs CTRL+SHIFT+P vs CTRL+ALT+P. You'd want to only trigger the most specific match, not both CTRL+P and CTRL+ALT+P for example.
And beyond that, there's also context. There's global bindings, window-specific bindings, possibly tab-specific bindings, control-specific bindings, and various modes an application might be in. This is perhaps best implemented in the imgui style via a binding stack or the like, I think.
On top of all that, of course, is key rebinding. Which flows well into the Mac vs Windows/Linux problems. My app's default "refresh document" key bind might just be F5 but some users might prefer SHIFT+F5, or might prefer that F5 toggle start/stop playback or something. If the app has to directly tie the action with the key bind, this basically shoves all the complexity of key remapping (including the platform-specific Command/Control stuff) onto the app developer as well as string-ifying those bindings for display in the menu items.
Sorry to drop in with another question instead of a real contribution, but I need to know if there is any way at all to create shortcuts with the current Dear ImGui. My present need is for a Ctrl-S = "Save" shortcut.
@JPGygax68 there is not. You have to manually handle these key presses in your application.
@rokups I am quite prepared to do that, but how? I found no symbolic definition for the key 'S', and SDL does not generate text input for it either.
if (io.KeyCtrl && ImGui::IsKeypressed(SDL_SCANCODE_S)
Thank you!
As an idea for a generalized approach without the need for string parsing (just pseudo code for now):
//ImGui.h
typedef ... ImGuiInputID; // opaque type, might be a hash or whatever
// Note: 'key' might just be an index into the ImGuiIO.KeysDown array
ImGuiInputID AddHotkey(unsigned key, ImGuiInputFlag locality); // locality = global / window / window-and-parent
ImGuiInputID ExtendHotkey(ImGuiInputID, unsigned key, bool delay = false);
bool IsHotkeyActive(ImGuiInputID id); // returns true if the given hotkey is currently pressed
void SetNextHotkey(ImGuiInputID id); // just convenient to merge Button/etc with IsHotkeyActive
With AddHotkey, the user declares a 'root' hotkey. With extend hotkey, this root hotkey can be extended with more keys. If delay is true, the extension will active if the given key is pressed after whatever hotkey was extended (within some timeout), instead of requiring simultaneous holding of the keys.
Example usage:
Begin(...);
//...
//Using ImGuiInputFlag_Window: All those hotkeys will only activate if the current window is focused:
auto key0 = AddHotkey(VK_SHIFT, ImGuiInputFlag_Window); // key0 = 'Shift'
auto key1 = ExtendHotkey(key0, VK_S); // key1 = 'Shift + S'
auto key2 = ExtendHotkey(key1, VK_ALT); // key2 = 'Shift + Alt + S'
auto key3 = ExtendHotkey(key0, VK_SHIFT, true); // key3 = 'Shift, Shift' (press shift twice within a certain timeout)
if(IsHotkeyActive(key2))
{
// This is only done if user pressed 'Shift + Alt + S'
}
SetNextHotkey(key1);
if(Button(...)) // Same as if(Button(...) || IsHotkeyActive(key1))
{
// This is done if the user pressed the button or 'Shift + S'
}
//...
End();
Internally, ImGuiInputID would represent/map to a state object, which itself contains a key -> state object* map for the hotkey extensions. To check whether a hotkey is active, one could simply compare the pointer to the state object represented by the ID with a single pointer stored in the window (or where ever it's most suitable depending on whether it's a global / window / window-and-parent kind of hotkey) - therefore, only a single hotkey will be active, so 'Shift + S' and 'Shift + Alt + S' can't be active at the same time (if they both belong to the same window):
//ImGui.cpp
struct InputState {
unsigned BackKey = 0;
InputState* Parent = nullptr;
InputState* extensions[512]; // TODO: similar array for delayed extensions
// Note: I use an array here for simplification, a dynamic container might be more reasonable here regarding memory usage
InputState(unsigned backKey = 0, InputState* parent = nullptr) : BackKey(backKey), Parent(parent) {}
InputState* ExtendHotkey(unsigned key)
{
InputState* ext = extensions[key];
if(ext == nullptr)
ext = extensions[key] = IM_NEW(InputState, key, this);
return ext;
}
};
struct ImGuiContext {
//...
Map<ImGuiInputID, InputState*> InputStates;
};
struct ImGuiWindow {
//...
InputState defaultState;
InputState* activeState = &defaultState;
};
InputState* GetInputState(ImGuiInputID id) { /* ... */ }
ImGuiInputID GetInputID(InputState* s) { /* ... */ }
bool IsHotkeyActive(ImGuiInputID id)
{
//TODO: Handle locality
return window->activeState == GetInputState(id);
}
ImGuiInputID AddHotkey(unsigned key, ImGuiInputFlag locality)
{
//TODO: Handle locality
return GetInputID(window->defaultState.ExtendHotkey(key));
}
ImGuiInputID ExtendHotkey(ImGuiInputID id, unsigned key, bool delay)
{
return GetInputID(GetInputState(id)->ExtendHotkey(key));
}
// Updating currently active state (might happen inside NewFrame, code is simplified):
InputState* active = window->activeState;
while(!io.KeyDown[active->BackKey])
active = active->Parent;
if(active == nullptr)
active = &window->defaultState;
//TODO: Handle delay
for(int key = 0; key < 512; key++)
{
if(io.KeyDown[key] && active->extensions[key] != nullptr)
{
active = active->extensions[key];
key = -1;
}
}
window->activeState = active;
That way, there's no need for string parsing, it's easy to use, user can create and extend his own hotkeys however they choose, and (except for the purpose of transforming an ImGuiInputID to a visual pleasing string) Dear ImGui doesn't even need to know anything about the underlying key mapping. In fact, actually implementing something like that should be rather easy - actually, I might look into getting some actually running code myself at the weekend, when I have some time for it :)
I have the similar idea about dear imgui shortcut system inspired by Qt QAction. The following is the API,s:
class IMGUI_API ImAction
{
public:
ImAction(const char* name, int shortcut, bool* selected = nullptr);
~ImAction();
const char* Name() const;
const char* ShortcutName() const;
bool isVisible();
bool IsEnabled() const;
bool IsCheckable() const;
bool IsChecked() const;
bool IsTriggered() const;
void setVisible(bool visible);
void setEnabled(bool enabled);
void setCheckable(bool checkable);
void setChecked(bool checked);
void Trigger();
bool Toggle(); // return true when checkable and checked
bool IsShortcutPressed(bool repeat = false); // return true when triggered
void Reset(); // reset triggered status
};
We can even create more helper functions.
bool MenuItem(ImAction* act, ...);
Please check https://gist.github.com/maxint/277e9d67deb54f64696b7de6a9d2d7f3 for the latest code.
The usage code snippets:
#include "imgui_action_example.h"
void ImActionExamples()
{
static bool copied = false;
ImActionManager::Instance().RemoveAll();
ImAction* action_copy = ImActionManager::Instance().Create("Copy", ImCtrl + 'C');
ImAction* action_paste = ImActionManager::Instance().Create("Paste", ImCtrl + 'V');
action_paste->setEnabled(copied);
ImActionManager::Instance().ResetAll();
if (ImGui::BeginPopupContextWindow())
{
ImGui::MenuItem(action_copy);
ImGui::MenuItem(action_paste);
ImGui::EndPopup();
}
if (action_copy->IsShortcutPressed())
{
copied = true;
// copy
}
if (action_paste->IsShortcutPressed())
{
// paste
}
}
I think ImGui should focus more on the shortcut system, and not on the overall action. So - a mechanism to detect key combinations, chords, mashing and a way to avoid potential conflicts. However, it should _not_ concern itself with the actual action assigned to the shortcut. So, for example, let us write:
if (ImGui::Shortcut({KEY_CTRL, KEY_K}, {KEY_O}) {
... //executes when Ctrl+K is pressed, followed by O
}
But holding the actual action(function) to be invoked, or behaving it the same way as - for example - pressing a visual button - that's the job of whoever uses ImGui in their code. When View is separated from a Controller, the content of the if statement is usually just a single controller function invocation. I have no problem duplicating _that_.
That's why, the current system with IsKeyPressed is not bad. It may need an upgrade for combinations and cords. But I don't think a completely new system is needed.
When chords are mixed in, shortcut API looks a loot like gesture recognition but with keyboard input instead of touch.
Thanks Sean for pointing out some processing details.
Most helpful comment
I think ImGui should focus more on the shortcut system, and not on the overall action. So - a mechanism to detect key combinations, chords, mashing and a way to avoid potential conflicts. However, it should _not_ concern itself with the actual action assigned to the shortcut. So, for example, let us write:
But holding the actual action(function) to be invoked, or behaving it the same way as - for example - pressing a visual button - that's the job of whoever uses ImGui in their code. When View is separated from a Controller, the content of the
ifstatement is usually just a single controller function invocation. I have no problem duplicating _that_.That's why, the current system with
IsKeyPressedis not bad. It may need an upgrade for combinations and cords. But I don't think a completely new system is needed.