diff --git a/imgui.cpp b/imgui.cpp index ec195d73..ab99c469 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8425,7 +8425,7 @@ bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags) if (t < 0.0f) return false; IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsKeyPressed) == 0); // Passing flags not supported by this function! - if (flags & ImGuiInputFlags_RepeatRateMask_) // Setting any _RepeatXXX option enables _Repeat + if (flags & (ImGuiInputFlags_RepeatRateMask_ | ImGuiInputFlags_RepeatUntilMask_)) // Setting any _RepeatXXX option enables _Repeat flags |= ImGuiInputFlags_Repeat; bool pressed = (t == 0.0f); @@ -8434,6 +8434,19 @@ bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags) float repeat_delay, repeat_rate; GetTypematicRepeatRate(flags, &repeat_delay, &repeat_rate); pressed = (t > repeat_delay) && GetKeyPressedAmount(key, repeat_delay, repeat_rate) > 0; + if (pressed && (flags & ImGuiInputFlags_RepeatUntilMask_)) + { + // Slightly bias 'key_pressed_time' as DownDuration is an accumulation of DeltaTime which we compare to an absolute time value. + // Ideally we'd replace DownDuration with KeyPressedTime but it would break user's code. + ImGuiContext& g = *GImGui; + double key_pressed_time = g.Time - t + 0.00001f; + if ((flags & ImGuiInputFlags_RepeatUntilKeyModsChange) && (g.LastKeyModsChangeTime > key_pressed_time)) + pressed = false; + if ((flags & ImGuiInputFlags_RepeatUntilKeyModsChangeFromNone) && (g.LastKeyModsChangeFromNoneTime > key_pressed_time)) + pressed = false; + if ((flags & ImGuiInputFlags_RepeatUntilOtherKeyPress) && (g.LastKeyboardKeyPressTime > key_pressed_time)) + pressed = false; + } } if (!pressed) return false; @@ -8760,11 +8773,16 @@ static void ImGui::UpdateKeyboardInputs() // - New backends (1.87+): send io.AddKeyEvent(ImGuiMod_XXX) -> -> (here) deriving io.KeyMods + io.KeyXXX from key array. // - Legacy backends: set io.KeyXXX bools -> (above) set key array from io.KeyXXX -> (here) deriving io.KeyMods + io.KeyXXX from key array. // So with legacy backends the 4 values will do a unnecessary back-and-forth but it makes the code simpler and future facing. + const ImGuiKeyChord prev_key_mods = io.KeyMods; io.KeyMods = GetMergedModsFromKeys(); io.KeyCtrl = (io.KeyMods & ImGuiMod_Ctrl) != 0; io.KeyShift = (io.KeyMods & ImGuiMod_Shift) != 0; io.KeyAlt = (io.KeyMods & ImGuiMod_Alt) != 0; io.KeySuper = (io.KeyMods & ImGuiMod_Super) != 0; + if (prev_key_mods != io.KeyMods) + g.LastKeyModsChangeTime = g.Time; + if (prev_key_mods != io.KeyMods && prev_key_mods == 0) + g.LastKeyModsChangeFromNoneTime = g.Time; // Clear gamepad data if disabled if ((io.BackendFlags & ImGuiBackendFlags_HasGamepad) == 0) @@ -8780,6 +8798,14 @@ static void ImGui::UpdateKeyboardInputs() ImGuiKeyData* key_data = &io.KeysData[i]; key_data->DownDurationPrev = key_data->DownDuration; key_data->DownDuration = key_data->Down ? (key_data->DownDuration < 0.0f ? 0.0f : key_data->DownDuration + io.DeltaTime) : -1.0f; + if (key_data->DownDuration == 0.0f) + { + ImGuiKey key = (ImGuiKey)(ImGuiKey_NamedKey_BEGIN + i); + if (IsKeyboardKey(key)) + g.LastKeyboardKeyPressTime = g.Time; + else if (key == ImGuiKey_ReservedForModCtrl || key == ImGuiKey_ReservedForModShift || key == ImGuiKey_ReservedForModAlt || key == ImGuiKey_ReservedForModSuper) + g.LastKeyboardKeyPressTime = g.Time; + } } // Update keys/input owner (named keys only): one entry per key @@ -9315,6 +9341,11 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags if (!SetShortcutRouting(key_chord, owner_id, flags)) return false; + // Default repeat behavior for Shortcut() + // So e.g. pressing Ctrl+W and releasing Ctrl while holding W will not trigger the W shortcut. + if ((flags & ImGuiInputFlags_RepeatUntilMask_) == 0) + flags |= ImGuiInputFlags_RepeatUntilKeyModsChange; + if (!IsKeyChordPressed(key_chord, owner_id, flags)) return false; IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByShortcut) == 0); // Passing flags not supported by this function! diff --git a/imgui.h b/imgui.h index 3909dfa1..6ccae945 100644 --- a/imgui.h +++ b/imgui.h @@ -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.1 WIP" -#define IMGUI_VERSION_NUM 19002 +#define IMGUI_VERSION_NUM 19003 #define IMGUI_HAS_TABLE /* diff --git a/imgui_internal.h b/imgui_internal.h index 9f8e7ac2..62abb536 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1411,6 +1411,13 @@ enum ImGuiInputFlags_ ImGuiInputFlags_RepeatRateNavMove = 1 << 2, // Repeat rate: Fast ImGuiInputFlags_RepeatRateNavTweak = 1 << 3, // Repeat rate: Faster + // Specify when repeating key pressed can be interrupted. + // In theory ImGuiInputFlags_RepeatUntilOtherKeyPress may be a desirable default, but it would break too many behavior so everything is opt-in. + ImGuiInputFlags_RepeatUntilRelease = 1 << 4, // Stop repeating when released (default for all functions except Shortcut). This only exists to allow overriding Shortcut() default behavior. + ImGuiInputFlags_RepeatUntilKeyModsChange = 1 << 5, // Stop repeating when released OR if keyboard mods are changed (default for Shortcut) + ImGuiInputFlags_RepeatUntilKeyModsChangeFromNone = 1 << 6, // Stop repeating when released OR if keyboard mods are leaving the None state. Allows going from Mod+Key to Key by releasing Mod. + ImGuiInputFlags_RepeatUntilOtherKeyPress = 1 << 7, // Stop repeating when released OR if any other keyboard key is pressed during the repeat + // Flags for SetItemKeyOwner() ImGuiInputFlags_CondHovered = 1 << 8, // Only set if item is hovered (default to both) ImGuiInputFlags_CondActive = 1 << 9, // Only set if item is active (default to both) @@ -1442,7 +1449,8 @@ enum ImGuiInputFlags_ // [Internal] Mask of which function support which flags ImGuiInputFlags_RepeatRateMask_ = ImGuiInputFlags_RepeatRateDefault | ImGuiInputFlags_RepeatRateNavMove | ImGuiInputFlags_RepeatRateNavTweak, - ImGuiInputFlags_RepeatMask_ = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_, + ImGuiInputFlags_RepeatUntilMask_ = ImGuiInputFlags_RepeatUntilRelease | ImGuiInputFlags_RepeatUntilKeyModsChange | ImGuiInputFlags_RepeatUntilKeyModsChangeFromNone | ImGuiInputFlags_RepeatUntilOtherKeyPress, + ImGuiInputFlags_RepeatMask_ = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_ | ImGuiInputFlags_RepeatUntilMask_, ImGuiInputFlags_SupportedByIsKeyPressed = ImGuiInputFlags_RepeatMask_, ImGuiInputFlags_SupportedByIsMouseClicked = ImGuiInputFlags_Repeat, ImGuiInputFlags_SupportedByShortcut = ImGuiInputFlags_RepeatMask_ | ImGuiInputFlags_RouteMask_ | ImGuiInputFlags_RouteExtraMask_, @@ -1941,6 +1949,9 @@ struct ImGuiContext // - The idea is that instead of "eating" a given key, we can link to an owner. // - Input query can then read input by specifying ImGuiKeyOwner_Any (== 0), ImGuiKeyOwner_None (== -1) or a custom ID. // - Routing is requested ahead of time for a given chord (Key + Mods) and granted in NewFrame(). + 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. 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) @@ -2227,6 +2238,8 @@ struct ImGuiContext LastActiveId = 0; LastActiveIdTimer = 0.0f; + LastKeyboardKeyPressTime = LastKeyModsChangeTime = LastKeyModsChangeFromNoneTime = -1.0; + ActiveIdUsingNavDirMask = 0x00; ActiveIdUsingAllKeyboardKeys = false; #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO