Skip to content

Commit

Permalink
Shortcut: added Shortcut() function and ImGuiInputFlags in public API…
Browse files Browse the repository at this point in the history
… + Demo. (#456, #2637)
  • Loading branch information
ocornut committed Dec 8, 2022
1 parent 1dae7df commit 0949acb
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 45 deletions.
7 changes: 7 additions & 0 deletions docs/CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ Breaking changes:

Other changes:

- Inputs: added Shortcut() function (w/ routing policies) in public API. (#456, #2637)
Added ImGuiInputFlags_RouteFocused, ImGuiInputFlags_RouteGlobalXXX and other routing policies.
e.g. ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_C); with default policy:
- check that CTRL+C is pressed,
- and that current window is in focus stack,
- and that no other requests for CTRL+C have been made from deeper locations of the window/item stack.
- Fixed cases where CTRL+Tab or Modal can occasionally lead to the creation of ImDrawCmd with
zero triangles, which would makes the render loop of some backends assert (e.g. Metal with
debugging, Allegro). (#4857, #5937)
Expand All @@ -61,6 +67,7 @@ Other changes:
be implemented with public API.
- Fonts: added a 'void* UserData' field in ImFontAtlas, as a convenience for use by
applications using multiple font atlases.
- Demo: added "Inputs->Shortcut Routing" section.
- Demo: simplified "Inputs" section, moved contents to Metrics->Inputs.
- Debug Tools: Metrics: added "Inputs" section, moved from Demo for consistency.
- Misc: fixed parameters to IMGUI_DEBUG_LOG() not being dead-stripped when building
Expand Down
6 changes: 2 additions & 4 deletions imgui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8773,8 +8773,6 @@ void ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags)
}
}

// - Need to decide how to handle shortcut translations for Non-Mac <> Mac
// - Ideas: https://github.com/ocornut/imgui/issues/456#issuecomment-264390864
bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags)
{
ImGuiContext& g = *GImGui;
Expand All @@ -8796,7 +8794,7 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags
if (key == ImGuiKey_None)
key = ConvertSingleModFlagToKey(mods);

if (!IsKeyPressed(key, owner_id, (flags & (ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_))))
if (!IsKeyPressed(key, owner_id, (flags & (ImGuiInputFlags_Repeat | (ImGuiInputFlags)ImGuiInputFlags_RepeatRateMask_))))
return false;
IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByShortcut) == 0); // Passing flags not supported by this function!

Expand Down Expand Up @@ -11182,7 +11180,7 @@ void ImGui::NavUpdateCreateMoveRequest()
g.NavMoveScrollFlags = ImGuiScrollFlags_None;
if (window && !g.NavWindowingTarget && !(window->Flags & ImGuiWindowFlags_NoNavInputs))
{
const ImGuiInputFlags repeat_mode = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateNavMove;
const ImGuiInputFlags repeat_mode = ImGuiInputFlags_Repeat | (ImGuiInputFlags)ImGuiInputFlags_RepeatRateNavMove;
if (!IsActiveIdUsingNavDir(ImGuiDir_Left) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadLeft, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_LeftArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Left; }
if (!IsActiveIdUsingNavDir(ImGuiDir_Right) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadRight, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_RightArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Right; }
if (!IsActiveIdUsingNavDir(ImGuiDir_Up) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadUp, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_UpArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Up; }
Expand Down
52 changes: 48 additions & 4 deletions imgui.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
// Library Version
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM > 12345')
#define IMGUI_VERSION "1.89.2 WIP"
#define IMGUI_VERSION_NUM 18915
#define IMGUI_VERSION_NUM 18916
#define IMGUI_HAS_TABLE

/*
Expand Down Expand Up @@ -192,6 +192,7 @@ typedef int ImGuiComboFlags; // -> enum ImGuiComboFlags_ // Flags: f
typedef int ImGuiDragDropFlags; // -> enum ImGuiDragDropFlags_ // Flags: for BeginDragDropSource(), AcceptDragDropPayload()
typedef int ImGuiFocusedFlags; // -> enum ImGuiFocusedFlags_ // Flags: for IsWindowFocused()
typedef int ImGuiHoveredFlags; // -> enum ImGuiHoveredFlags_ // Flags: for IsItemHovered(), IsWindowHovered() etc.
typedef int ImGuiInputFlags; // -> enum ImGuiInputFlags_ // Flags: for Shortcut() (+ upcoming advanced versions of IsKeyPressed()/IsMouseClicked()/SetKeyOwner()/SetItemKeyOwner() currently in imgui_internal.h)
typedef int ImGuiInputTextFlags; // -> enum ImGuiInputTextFlags_ // Flags: for InputText(), InputTextMultiline()
typedef int ImGuiKeyChord; // -> ImGuiKey | ImGuiMod_XXX // Flags: for storage only for now: an ImGuiKey optionally OR-ed with one or more ImGuiMod_XXX values.
typedef int ImGuiPopupFlags; // -> enum ImGuiPopupFlags_ // Flags: for OpenPopup*(), BeginPopupContext*(), IsPopupOpen()
Expand Down Expand Up @@ -894,6 +895,21 @@ namespace ImGui
IMGUI_API const char* GetKeyName(ImGuiKey key); // [DEBUG] returns English name of the key. Those names a provided for debugging purpose and are not meant to be saved persistently not compared.
IMGUI_API void SetNextFrameWantCaptureKeyboard(bool want_capture_keyboard); // Override io.WantCaptureKeyboard flag next frame (said flag is left for your application to handle, typically when true it instructs your app to ignore inputs). e.g. force capture keyboard when your widget is being hovered. This is equivalent to setting "io.WantCaptureKeyboard = want_capture_keyboard"; after the next NewFrame() call.

// Inputs Utilities: Shortcut testing (with Routing Resolution)
// - ImGuiKeyChord = a ImGuiKey optionally OR-red with ImGuiMod_Alt/ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Super/ImGuiMod_Shortcut.
// ImGuiKey_C (accepted by functions taking ImGuiKey or ImGuiKeyChord)
// ImGuiKey_C | ImGuiMod_Ctrl (accepted by functions taking ImGuiKeyChord)
// ONLY ImGuiMod_XXX values are legal to 'OR' with an ImGuiKey. You CANNOT 'OR' two ImGuiKey values.
// - The general idea of routing is that multiple locations may register interest in a shortcut,
// and only one location will be granted access to the shortcut.
// - The default routing policy (ImGuiInputFlags_RouteFocused) checks for current window being in
// the focus stack, and route the shortcut to the deepest requesting window in the focus stack.
// - Consider Shortcut() to be a widget: the calling location matters + it has side-effects as shortcut routes are
// registered into the system (for it to be able to pick the best one). This is why this is not called 'IsShortcutPressed()'.
// - If this is called for a specific widget, pass its ID as 'owner_id' in order for key ownership and routing priorities
// to be honored (e.g. with default ImGuiInputFlags_RouteFocused, the highest priority is given to active item).
IMGUI_API bool Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0);

// Inputs Utilities: Mouse specific
// - To refer to a mouse button, you may use named enums in your code e.g. ImGuiMouseButton_Left, ImGuiMouseButton_Right.
// - You can also use regular integer: it is forever guaranteed that 0=Left, 1=Right, 2=Middle.
Expand Down Expand Up @@ -1449,11 +1465,11 @@ enum ImGuiKey : int
// In practice: it's complicated; mods are often provided from different sources. Keyboard layout, IME, sticky keys and
// backends tend to interfere and break that equivalence. The safer decision is to relay that ambiguity down to the end-user...
ImGuiMod_None = 0,
ImGuiMod_Ctrl = 1 << 12,
ImGuiMod_Shift = 1 << 13,
ImGuiMod_Ctrl = 1 << 12, // Ctrl
ImGuiMod_Shift = 1 << 13, // Shift
ImGuiMod_Alt = 1 << 14, // Option/Menu
ImGuiMod_Super = 1 << 15, // Cmd/Super/Windows
ImGuiMod_Shortcut = 1 << 11, // Alias for Ctrl (non-macOS) OR Super (macOS).
ImGuiMod_Shortcut = 1 << 11, // Alias for Ctrl (non-macOS) _or_ Super (macOS).
ImGuiMod_Mask_ = 0xF800, // 5-bits

// [Internal] Prior to 1.87 we required user to fill io.KeysDown[512] using their own native index + the io.KeyMap[] array.
Expand All @@ -1475,6 +1491,34 @@ enum ImGuiKey : int
#endif
};

// Flags for Shortcut()
// (+ for upcoming advanced versions of IsKeyPressed()/IsMouseClicked()/SetKeyOwner()/SetItemKeyOwner() that are currently in imgui_internal.h)
enum ImGuiInputFlags_
{
ImGuiInputFlags_None = 0,
ImGuiInputFlags_Repeat = 1 << 0, // Return true on successive repeats. Default for legacy IsKeyPressed(). NOT Default for legacy IsMouseClicked(). MUST BE == 1.

// Routing policies for Shortcut() + low-level SetShortcutRouting()
// - The general idea is that several callers register interest in a shortcut, and only one owner gets it.
// - When a policy (other than _RouteAlways) is set, Shortcut() will register itself with SetShortcutRouting(),
// allowing the system to decide where to route the input among other route-aware calls.
// - Shortcut() uses ImGuiInputFlags_RouteFocused by default: meaning that a simple Shortcut() poll
// will register a route and only succeed when parent window is in the focus stack and if no-one
// with a higher priority is claiming the shortcut.
// - Using ImGuiInputFlags_RouteAlways is roughly equivalent to doing e.g. IsKeyPressed(key) + testing mods.
// - Priorities: GlobalHigh > Focused (when owner is active item) > Global > Focused (when focused window) > GlobalLow.

// Policies (can select only 1 policy among all available)
ImGuiInputFlags_RouteFocused = 1 << 8, // (Default) Register focused route: Accept inputs if window is in focus stack. Deep-most focused window takes inputs. ActiveId takes inputs over deep-most focused window.
ImGuiInputFlags_RouteGlobalLow = 1 << 9, // Register route globally (lowest priority: unless a focused window or active item registered the route) -> recommended Global priority.
ImGuiInputFlags_RouteGlobal = 1 << 10, // Register route globally (medium priority: unless an active item registered the route, e.g. CTRL+A registered by InputText).
ImGuiInputFlags_RouteGlobalHigh = 1 << 11, // Register route globally (highest priority: unlikely you need to use that: will interfere with every active items)
ImGuiInputFlags_RouteAlways = 1 << 12, // Do not register route, poll keys directly.

// Policies Options
ImGuiInputFlags_RouteUnlessBgFocused= 1 << 13, // Global routes will not be applied if underlying background/void is focused (== no Dear ImGui windows are focused). Useful for overlay applications.
};

#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
// OBSOLETED in 1.88 (from July 2022): ImGuiNavInput and io.NavInputs[].
// Official backends between 1.60 and 1.86: will keep working and feed gamepad inputs as long as IMGUI_DISABLE_OBSOLETE_KEYIO is not set.
Expand Down
90 changes: 83 additions & 7 deletions imgui_demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5742,7 +5742,7 @@ static void ShowDemoWindowInputs()
ImGui::Text("io.WantSetMousePos: %d", io.WantSetMousePos);
ImGui::Text("io.NavActive: %d, io.NavVisible: %d", io.NavActive, io.NavVisible);

IMGUI_DEMO_MARKER("Inputs & Focus/Outputs/Capture override");
IMGUI_DEMO_MARKER("Inputs & Focus/Outputs/WantCapture override");
if (ImGui::TreeNode("WantCapture override"))
{
HelpMarker(
Expand All @@ -5767,6 +5767,76 @@ static void ShowDemoWindowInputs()
ImGui::TreePop();
}

// Demonstrate using Shortcut() and Routing Policies.
// The general flow is:
// - Code interested in a chord (e.g. "Ctrl+A") declares their intent.
// - Multiple locations may be interested in same chord! Routing helps find a winner.
// - Every frame, we resolve all claims and assign one owner if the modifiers are matching.
// - The lower-level function is 'bool SetShortcutRouting()', returns true when caller got the route.
// - Most of the times, SetShortcutRouting() is not called directly. User mostly calls Shortcut() with routing flags.
// - If you call Shortcut() WITHOUT any routing option, it uses ImGuiInputFlags_RouteFocused.
// TL;DR: Most uses will simply be:
// - Shortcut(ImGuiMod_Ctrl | ImGuiKey_A); // Use ImGuiInputFlags_RouteFocused policy.
if (ImGui::TreeNode("Shortcut Routing"))
{
const float line_height = ImGui::GetTextLineHeightWithSpacing();
const ImGuiKeyChord key_chord = ImGuiMod_Ctrl | ImGuiKey_A;
static ImGuiInputFlags repeat_flags = ImGuiInputFlags_Repeat;
static ImGuiInputFlags routing_flags = ImGuiInputFlags_RouteFocused;
ImGui::CheckboxFlags("ImGuiInputFlags_Repeat", &repeat_flags, ImGuiInputFlags_Repeat);
ImGui::RadioButton("ImGuiInputFlags_RouteFocused (default)", &routing_flags, ImGuiInputFlags_RouteFocused);
ImGui::RadioButton("ImGuiInputFlags_RouteAlways", &routing_flags, ImGuiInputFlags_RouteAlways);
ImGui::RadioButton("ImGuiInputFlags_RouteGlobal", &routing_flags, ImGuiInputFlags_RouteGlobal);
ImGui::RadioButton("ImGuiInputFlags_RouteGlobalHigh", &routing_flags, ImGuiInputFlags_RouteGlobalHigh);
ImGui::RadioButton("ImGuiInputFlags_RouteGlobalLow", &routing_flags, ImGuiInputFlags_RouteGlobalLow);
const ImGuiInputFlags flags = repeat_flags | routing_flags; // Merged flags

ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, 0, flags) ? "PRESSED" : "...");

ImGui::BeginChild("WindowA", ImVec2(-FLT_MIN, line_height * 18), true);
ImGui::Text("Press CTRL+A and see who receives it!");
ImGui::Separator();

// 1: Window polling for CTRL+A
ImGui::Text("(in WindowA)");
ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, 0, flags) ? "PRESSED" : "...");

// 2: InputText also polling for CTRL+A: it always uses _RouteFocused internally (gets priority when active)
char str[16] = "Press CTRL+A";
ImGui::Spacing();
ImGui::InputText("InputTextB", str, IM_ARRAYSIZE(str), ImGuiInputTextFlags_ReadOnly);
ImGuiID item_id = ImGui::GetItemID();
ImGui::SameLine(); HelpMarker("Internal widgets always use _RouteFocused");
ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, item_id, flags) ? "PRESSED" : "...");

// 3: Dummy child is not claiming the route: focusing them shouldn't steal route away from WindowA
ImGui::BeginChild("ChildD", ImVec2(-FLT_MIN, line_height * 4), true);
ImGui::Text("(in ChildD: not using same Shortcut)");
ImGui::Text("IsWindowFocused: %d", ImGui::IsWindowFocused());
ImGui::EndChild();

// 4: Child window polling for CTRL+A. It is deeper than WindowA and gets priority when focused.
ImGui::BeginChild("ChildE", ImVec2(-FLT_MIN, line_height * 4), true);
ImGui::Text("(in ChildE: using same Shortcut)");
ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, 0, flags) ? "PRESSED" : "...");
ImGui::EndChild();

// 5: In a popup
if (ImGui::Button("Open Popup"))
ImGui::OpenPopup("PopupF");
if (ImGui::BeginPopup("PopupF"))
{
ImGui::Text("(in PopupF)");
ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, 0, flags) ? "PRESSED" : "...");
ImGui::InputText("InputTextG", str, IM_ARRAYSIZE(str), ImGuiInputTextFlags_ReadOnly);
ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, ImGui::GetItemID(), flags) ? "PRESSED" : "...");
ImGui::EndPopup();
}
ImGui::EndChild();

ImGui::TreePop();
}

// Display mouse cursors
IMGUI_DEMO_MARKER("Inputs & Focus/Mouse Cursors");
if (ImGui::TreeNode("Mouse Cursors"))
Expand Down Expand Up @@ -7024,12 +7094,15 @@ static void ShowExampleAppLayout(bool* p_open)
{
if (ImGui::BeginMenu("File"))
{
if (ImGui::MenuItem("Close")) *p_open = false;
if (ImGui::MenuItem("Close", "Ctrl+W")) { *p_open = false; }
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}

if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_W))
*p_open = false;

// Left
static int selected = 0;
{
Expand Down Expand Up @@ -7737,11 +7810,14 @@ struct MyDocument
ImGui::PushStyleColor(ImGuiCol_Text, doc->Color);
ImGui::TextWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
ImGui::PopStyleColor();
if (ImGui::Button("Modify", ImVec2(100, 0)))
if (ImGui::Button("Modify (Ctrl+M)") || ImGui::Shortcut(ImGuiMod_Shortcut | ImGuiKey_M))
doc->Dirty = true;
ImGui::SameLine();
if (ImGui::Button("Save", ImVec2(100, 0)))
if (ImGui::Button("Save (Ctrl+S)") || ImGui::Shortcut(ImGuiMod_Shortcut | ImGuiKey_S))
doc->DoSave();
ImGui::SameLine();
if (ImGui::Button("Close (Ctrl+W)") || ImGui::Shortcut(ImGuiMod_Shortcut | ImGuiKey_W))
doc->DoQueueClose();
ImGui::ColorEdit3("color", &doc->Color.x); // Useful to test drag and drop and hold-dragged-to-open-tab behavior.
ImGui::PopID();
}
Expand All @@ -7754,9 +7830,9 @@ struct MyDocument

char buf[256];
sprintf(buf, "Save %s", doc->Name);
if (ImGui::MenuItem(buf, "CTRL+S", false, doc->Open))
if (ImGui::MenuItem(buf, "Ctrl+S", false, doc->Open))
doc->DoSave();
if (ImGui::MenuItem("Close", "CTRL+W", false, doc->Open))
if (ImGui::MenuItem("Close", "Ctrl+W", false, doc->Open))
doc->DoQueueClose();
ImGui::EndPopup();
}
Expand Down Expand Up @@ -7834,7 +7910,7 @@ void ShowExampleAppDocuments(bool* p_open)
if (ImGui::MenuItem("Close All Documents", NULL, false, open_count > 0))
for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++)
app.Documents[doc_n].DoQueueClose();
if (ImGui::MenuItem("Exit", "Ctrl+F4") && p_open)
if (ImGui::MenuItem("Exit") && p_open)
*p_open = false;
ImGui::EndMenu();
}
Expand Down
Loading

0 comments on commit 0949acb

Please sign in to comment.