Skip to content

Commit

Permalink
Shortcut: ImGuiInputFlags_RouteFocused policy can filter Shortcuts co…
Browse files Browse the repository at this point in the history
…nflicting with character input when an item is active. (#456)
  • Loading branch information
ocornut committed Jan 16, 2024
1 parent 80d5cb1 commit 5fdcdf7
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 5 deletions.
48 changes: 44 additions & 4 deletions imgui.cpp
Expand Up @@ -3596,6 +3596,14 @@ void ImGui::Initialize()
g.Viewports.push_back(viewport);
g.TempBuffer.resize(1024 * 3 + 1, 0);

// Build KeysMayBeCharInput[] lookup table (1 bool per named key)
for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
if ((key >= ImGuiKey_0 && key <= ImGuiKey_9) || (key >= ImGuiKey_A && key <= ImGuiKey_Z) || (key >= ImGuiKey_Keypad0 && key <= ImGuiKey_Keypad9)
|| key == ImGuiKey_Tab || key == ImGuiKey_Space || key == ImGuiKey_Apostrophe || key == ImGuiKey_Comma || key == ImGuiKey_Minus || key == ImGuiKey_Period
|| key == ImGuiKey_Slash || key == ImGuiKey_Semicolon || key == ImGuiKey_Equal || key == ImGuiKey_LeftBracket || key == ImGuiKey_RightBracket || key == ImGuiKey_GraveAccent
|| key == ImGuiKey_KeypadDecimal || key == ImGuiKey_KeypadDivide || key == ImGuiKey_KeypadMultiply || key == ImGuiKey_KeypadSubtract || key == ImGuiKey_KeypadAdd || key == ImGuiKey_KeypadEqual)
g.KeysMayBeCharInput.SetBit(key);

#ifdef IMGUI_HAS_DOCK
#endif

Expand Down Expand Up @@ -8308,13 +8316,11 @@ ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord)
ImGuiContext& g = *GImGui;
ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable;
ImGuiKeyRoutingData* routing_data;
if (key_chord & ImGuiMod_Shortcut)
key_chord = ConvertShortcutMod(key_chord);
ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);
if (key == ImGuiKey_None)
key = ConvertSingleModFlagToKey(&g, mods);
IM_ASSERT(IsNamedKey(key));
IM_ASSERT(IsNamedKey(key) && (key_chord & ImGuiMod_Shortcut) == 0); // Please call ConvertShortcutMod() in calling function.

// Get (in the majority of case, the linked list will have one element so this should be 2 reads.
// Subsequent elements will be contiguous in memory as list is sorted/rebuilt in NewFrame).
Expand Down Expand Up @@ -8378,6 +8384,24 @@ static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInput
return 0;
}

// We need this to filter some Shortcut() routes when an item e.g. an InputText() is active
// e.g. ImGuiKey_G won't be considered a shortcut when item is active, but ImGuiMod|ImGuiKey_G can be.
static bool IsKeyChordPotentiallyCharInput(ImGuiKeyChord key_chord)
{
// Mimic 'ignore_char_inputs' logic in InputText()
ImGuiContext& g = *GImGui;

// When the right mods are pressed it cannot be a char input so we won't filter the shortcut out.
ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);
const bool ignore_char_inputs = ((mods & ImGuiMod_Ctrl) && !(mods & ImGuiMod_Alt)) || (g.IO.ConfigMacOSXBehaviors && (mods & ImGuiMod_Super));
if (ignore_char_inputs)
return false;

// Return true for A-Z, 0-9 and other keys associated to char inputs. Other keys such as F1-F12 won't be filtered.
ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
return g.KeysMayBeCharInput.TestBit(key);
}

// Request a desired route for an input chord (key + mods).
// Return true if the route is available this frame.
// - Routes and key ownership are attributed at the beginning of next frame based on best score and mod state.
Expand All @@ -8392,9 +8416,11 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI
flags |= ImGuiInputFlags_RouteGlobalHigh; // IMPORTANT: This is the default for SetShortcutRouting() but NOT Shortcut()
else
IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteMask_)); // Check that only 1 routing flag is used
if (key_chord & ImGuiMod_Shortcut)
key_chord = ConvertShortcutMod(key_chord);

// [DEBUG] Debug break requested by user
if (g.DebugBreakInShortcutRouting != 0 && g.DebugBreakInShortcutRouting == ConvertShortcutMod(key_chord))
if (g.DebugBreakInShortcutRouting == key_chord)
IM_DEBUG_BREAK();

if (flags & ImGuiInputFlags_RouteUnlessBgFocused)
Expand All @@ -8408,6 +8434,18 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI
return true;
}

// Specific culling for shortcuts with no modifiers when there's an active id.
// e.g. Shortcut(ImGuiKey_G) also generates 'g' character, should not trigger when InputText() is active.
// but Shortcut(Ctrl+G) should generally trigger when InputText() is active.
// TL;DR: lettered shortcut with no mods or with only Alt mod will not trigger while an item reading text input is active.
// (We cannot filter based on io.InputQueueCharacters[] contents because of trickling and key<>chars submission order are undefined)
if ((flags & ImGuiInputFlags_RouteFocused) && (g.ActiveId != 0 && g.ActiveId != owner_id))
if (g.IO.WantTextInput && IsKeyChordPotentiallyCharInput(key_chord))
{
IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, owner_id=0x%08X, flags=%04X) -> filtered as potential char input\n", GetKeyChordName(key_chord), owner_id, flags);
return false;
}

const int score = CalcRoutingScore(g.CurrentFocusScopeId, owner_id, flags);
IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, owner_id=0x%08X, flags=%04X) -> score %d\n", GetKeyChordName(key_chord), owner_id, flags, score);
if (score == 255)
Expand Down Expand Up @@ -8435,6 +8473,8 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI
bool ImGui::TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id)
{
const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id);
if (key_chord & ImGuiMod_Shortcut)
key_chord = ConvertShortcutMod(key_chord);
ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); // FIXME: Could avoid creating entry.
return routing_data->RoutingCurr == routing_id;
}
Expand Down
2 changes: 1 addition & 1 deletion imgui.h
Expand Up @@ -24,7 +24,7 @@
// Library Version
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345')
#define IMGUI_VERSION "1.90.2 WIP"
#define IMGUI_VERSION_NUM 19012
#define IMGUI_VERSION_NUM 19013
#define IMGUI_HAS_TABLE

/*
Expand Down
1 change: 1 addition & 0 deletions imgui_internal.h
Expand Up @@ -1974,6 +1974,7 @@ struct ImGuiContext
double LastKeyModsChangeTime; // Record the last time key mods changed (affect repeat delay when using shortcut logic)
double LastKeyModsChangeFromNoneTime; // Record the last time key mods changed away from being 0 (affect repeat delay when using shortcut logic)
double LastKeyboardKeyPressTime; // Record the last time a keyboard key (ignore mouse/gamepad ones) was pressed.
ImBitArrayForNamedKeys KeysMayBeCharInput; // Lookup to tell if a key can emit char input, see IsKeyChordPotentiallyCharInput(). sizeof() = 20 bytes
ImGuiKeyOwnerData KeysOwnerData[ImGuiKey_NamedKey_COUNT];
ImGuiKeyRoutingTable KeysRoutingTable;
ImU32 ActiveIdUsingNavDirMask; // Active widget will want to read those nav move requests (e.g. can activate a button and move away from it)
Expand Down

0 comments on commit 5fdcdf7

Please sign in to comment.