Shortcut: ImGuiInputFlags_RouteFocused policy can filter Shortcuts conflicting with character input when an item is active. (#456)

This commit is contained in:
ocornut 2024-01-16 17:51:14 +01:00
parent 80d5cb1ab1
commit 5fdcdf7080
3 changed files with 46 additions and 5 deletions

View File

@ -3596,6 +3596,14 @@ void ImGui::Initialize()
g.Viewports.push_back(viewport); g.Viewports.push_back(viewport);
g.TempBuffer.resize(1024 * 3 + 1, 0); 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 #ifdef IMGUI_HAS_DOCK
#endif #endif
@ -8308,13 +8316,11 @@ ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord)
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable; ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable;
ImGuiKeyRoutingData* routing_data; ImGuiKeyRoutingData* routing_data;
if (key_chord & ImGuiMod_Shortcut)
key_chord = ConvertShortcutMod(key_chord);
ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);
if (key == ImGuiKey_None) if (key == ImGuiKey_None)
key = ConvertSingleModFlagToKey(&g, mods); 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. // 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). // Subsequent elements will be contiguous in memory as list is sorted/rebuilt in NewFrame).
@ -8378,6 +8384,24 @@ static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInput
return 0; 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). // Request a desired route for an input chord (key + mods).
// Return true if the route is available this frame. // 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. // - Routes and key ownership are attributed at the beginning of next frame based on best score and mod state.
@ -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() flags |= ImGuiInputFlags_RouteGlobalHigh; // IMPORTANT: This is the default for SetShortcutRouting() but NOT Shortcut()
else else
IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteMask_)); // Check that only 1 routing flag is used 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 // [DEBUG] Debug break requested by user
if (g.DebugBreakInShortcutRouting != 0 && g.DebugBreakInShortcutRouting == ConvertShortcutMod(key_chord)) if (g.DebugBreakInShortcutRouting == key_chord)
IM_DEBUG_BREAK(); IM_DEBUG_BREAK();
if (flags & ImGuiInputFlags_RouteUnlessBgFocused) if (flags & ImGuiInputFlags_RouteUnlessBgFocused)
@ -8408,6 +8434,18 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI
return true; 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); 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); 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) if (score == 255)
@ -8435,6 +8473,8 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI
bool ImGui::TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id) bool ImGui::TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id)
{ {
const ImGuiID routing_id = GetRoutingIdFromOwnerId(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. ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); // FIXME: Could avoid creating entry.
return routing_data->RoutingCurr == routing_id; return routing_data->RoutingCurr == routing_id;
} }

View File

@ -24,7 +24,7 @@
// Library Version // Library Version
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') // (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 "1.90.2 WIP"
#define IMGUI_VERSION_NUM 19012 #define IMGUI_VERSION_NUM 19013
#define IMGUI_HAS_TABLE #define IMGUI_HAS_TABLE
/* /*

View File

@ -1974,6 +1974,7 @@ struct ImGuiContext
double LastKeyModsChangeTime; // Record the last time key mods changed (affect repeat delay when using shortcut logic) 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 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. 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]; ImGuiKeyOwnerData KeysOwnerData[ImGuiKey_NamedKey_COUNT];
ImGuiKeyRoutingTable KeysRoutingTable; 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) ImU32 ActiveIdUsingNavDirMask; // Active widget will want to read those nav move requests (e.g. can activate a button and move away from it)