From 746c9f76e4330417a6ffca539877a501826c00ed Mon Sep 17 00:00:00 2001 From: thedmd Date: Wed, 2 May 2018 20:42:37 +0200 Subject: [PATCH] Backends: Win32: Update to use io.AddEventKey() will full key map (#2625, #4858) --- backends/imgui_impl_win32.cpp | 237 +++++++++++++++++++++++++++------- backends/imgui_impl_win32.h | 6 +- 2 files changed, 196 insertions(+), 47 deletions(-) diff --git a/backends/imgui_impl_win32.cpp b/backends/imgui_impl_win32.cpp index c34cd460a..ebd8a313f 100644 --- a/backends/imgui_impl_win32.cpp +++ b/backends/imgui_impl_win32.cpp @@ -3,9 +3,9 @@ // Implemented features: // [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui) -// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. -// [X] Platform: Keyboard arrays indexed using VK_* Virtual Key Codes, e.g. ImGui::IsKeyPressed(VK_SPACE). +// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set] // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. +// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -33,6 +33,7 @@ typedef DWORD (WINAPI *PFN_XInputGetState)(DWORD, XINPUT_STATE*); // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2022-01-10: Inputs: calling new io.AddKeyEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range. // 2021-12-16: Inputs: Fill VK_LCONTROL/VK_RCONTROL/VK_LSHIFT/VK_RSHIFT/VK_LMENU/VK_RMENU for completeness. // 2021-08-17: Calling io.AddFocusEvent() on WM_SETFOCUS/WM_KILLFOCUS messages. // 2021-08-02: Inputs: Fixed keyboard modifiers being reported when host window doesn't have focus. @@ -125,30 +126,6 @@ bool ImGui_ImplWin32_Init(void* hwnd) // Set platform dependent data in viewport ImGui::GetMainViewport()->PlatformHandleRaw = (void*)hwnd; - // Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeysDown[] array that we will update during the application lifetime. - io.KeyMap[ImGuiKey_Tab] = VK_TAB; - io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; - io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; - io.KeyMap[ImGuiKey_UpArrow] = VK_UP; - io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN; - io.KeyMap[ImGuiKey_PageUp] = VK_PRIOR; - io.KeyMap[ImGuiKey_PageDown] = VK_NEXT; - io.KeyMap[ImGuiKey_Home] = VK_HOME; - io.KeyMap[ImGuiKey_End] = VK_END; - io.KeyMap[ImGuiKey_Insert] = VK_INSERT; - io.KeyMap[ImGuiKey_Delete] = VK_DELETE; - io.KeyMap[ImGuiKey_Backspace] = VK_BACK; - io.KeyMap[ImGuiKey_Space] = VK_SPACE; - io.KeyMap[ImGuiKey_Enter] = VK_RETURN; - io.KeyMap[ImGuiKey_Escape] = VK_ESCAPE; - io.KeyMap[ImGuiKey_KeypadEnter] = VK_RETURN; - io.KeyMap[ImGuiKey_A] = 'A'; - io.KeyMap[ImGuiKey_C] = 'C'; - io.KeyMap[ImGuiKey_V] = 'V'; - io.KeyMap[ImGuiKey_X] = 'X'; - io.KeyMap[ImGuiKey_Y] = 'Y'; - io.KeyMap[ImGuiKey_Z] = 'Z'; - // Dynamically load XInput library #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD const char* xinput_dll_names[] = @@ -222,6 +199,43 @@ static bool ImGui_ImplWin32_UpdateMouseCursor() return true; } +static bool IsVkDown(int vk) +{ + return (::GetKeyState(vk) & 0x8000) != 0; +} + +static void ImGui_ImplWin32_AddKeyEvent(ImGuiKey key, bool down, int native_keycode, int native_scancode = -1) +{ + ImGuiIO& io = ImGui::GetIO(); + io.AddKeyEvent(key, down); + io.SetKeyEventNativeData(key, native_keycode, native_scancode); // To support legacy indexing (<1.87 user code) + IM_UNUSED(native_scancode); +} + +static void ImGui_ImplWin32_ProcessKeyEventsWorkarounds() +{ + // Left & right Shift keys: when both are pressed together, Windows tend to not generate the WM_KEYUP event for the first released one. + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) && !IsVkDown(VK_LSHIFT)) + ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftShift, false, VK_LSHIFT); + if (ImGui::IsKeyDown(ImGuiKey_RightShift) && !IsVkDown(VK_RSHIFT)) + ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightShift, false, VK_RSHIFT); + + // Sometimes WM_KEYUP for Win key is not passed down to the app (e.g. for Win+V on some setups, according to GLFW). + if (ImGui::IsKeyDown(ImGuiKey_LeftSuper) && !IsVkDown(VK_LWIN)) + ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftSuper, false, VK_LWIN); + if (ImGui::IsKeyDown(ImGuiKey_RightSuper) && !IsVkDown(VK_RWIN)) + ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightSuper, false, VK_RWIN); +} + +static void ImGui_ImplWin32_UpdateKeyModifiers() +{ + ImGuiIO& io = ImGui::GetIO(); + io.KeyShift = IsVkDown(VK_LSHIFT) || IsVkDown(VK_RSHIFT); + io.KeyCtrl = IsVkDown(VK_LCONTROL) || IsVkDown(VK_RCONTROL); + io.KeyAlt = IsVkDown(VK_LMENU) || IsVkDown(VK_RMENU); + io.KeySuper = IsVkDown(VK_LWIN) || IsVkDown(VK_RWIN); +} + static void ImGui_ImplWin32_UpdateMousePos() { ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); @@ -323,6 +337,12 @@ void ImGui_ImplWin32_NewFrame() io.DeltaTime = (float)(current_time - bd->Time) / bd->TicksPerSecond; bd->Time = current_time; + // Process workarounds for known Windows key handling issues + ImGui_ImplWin32_ProcessKeyEventsWorkarounds(); + + // Update key modifiers + ImGui_ImplWin32_UpdateKeyModifiers(); + // Update OS mouse position ImGui_ImplWin32_UpdateMousePos(); @@ -338,6 +358,122 @@ void ImGui_ImplWin32_NewFrame() ImGui_ImplWin32_UpdateGamepads(); } +// There is no distinct VK_xxx for keypad enter, instead it is VK_RETURN + KF_EXTENDED, we assign it an arbitrary value to make code more readable (VK_ codes go up to 255) +#define IM_VK_KEYPAD_ENTER (VK_RETURN + 256) + +// Map VK_xxx to ImGuiKey_xxx. +static ImGuiKey ImGui_ImplWin32_VirtualKeyToImGuiKey(WPARAM wParam) +{ + switch (wParam) + { + case VK_TAB: return ImGuiKey_Tab; + case VK_LEFT: return ImGuiKey_LeftArrow; + case VK_RIGHT: return ImGuiKey_RightArrow; + case VK_UP: return ImGuiKey_UpArrow; + case VK_DOWN: return ImGuiKey_DownArrow; + case VK_PRIOR: return ImGuiKey_PageUp; + case VK_NEXT: return ImGuiKey_PageDown; + case VK_HOME: return ImGuiKey_Home; + case VK_END: return ImGuiKey_End; + case VK_INSERT: return ImGuiKey_Insert; + case VK_DELETE: return ImGuiKey_Delete; + case VK_BACK: return ImGuiKey_Backspace; + case VK_SPACE: return ImGuiKey_Space; + case VK_RETURN: return ImGuiKey_Enter; + case VK_ESCAPE: return ImGuiKey_Escape; + case VK_OEM_7: return ImGuiKey_Apostrophe; + case VK_OEM_COMMA: return ImGuiKey_Comma; + case VK_OEM_MINUS: return ImGuiKey_Minus; + case VK_OEM_PERIOD: return ImGuiKey_Period; + case VK_OEM_2: return ImGuiKey_Slash; + case VK_OEM_1: return ImGuiKey_Semicolon; + case VK_OEM_PLUS: return ImGuiKey_Equal; + case VK_OEM_4: return ImGuiKey_LeftBracket; + case VK_OEM_5: return ImGuiKey_Backslash; + case VK_OEM_6: return ImGuiKey_RightBracket; + case VK_OEM_3: return ImGuiKey_GraveAccent; + case VK_CAPITAL: return ImGuiKey_CapsLock; + case VK_SCROLL: return ImGuiKey_ScrollLock; + case VK_NUMLOCK: return ImGuiKey_NumLock; + case VK_SNAPSHOT: return ImGuiKey_PrintScreen; + case VK_PAUSE: return ImGuiKey_Pause; + case VK_NUMPAD0: return ImGuiKey_Keypad0; + case VK_NUMPAD1: return ImGuiKey_Keypad1; + case VK_NUMPAD2: return ImGuiKey_Keypad2; + case VK_NUMPAD3: return ImGuiKey_Keypad3; + case VK_NUMPAD4: return ImGuiKey_Keypad4; + case VK_NUMPAD5: return ImGuiKey_Keypad5; + case VK_NUMPAD6: return ImGuiKey_Keypad6; + case VK_NUMPAD7: return ImGuiKey_Keypad7; + case VK_NUMPAD8: return ImGuiKey_Keypad8; + case VK_NUMPAD9: return ImGuiKey_Keypad9; + case VK_DECIMAL: return ImGuiKey_KeypadDecimal; + case VK_DIVIDE: return ImGuiKey_KeypadDivide; + case VK_MULTIPLY: return ImGuiKey_KeypadMultiply; + case VK_SUBTRACT: return ImGuiKey_KeypadSubtract; + case VK_ADD: return ImGuiKey_KeypadAdd; + case IM_VK_KEYPAD_ENTER: return ImGuiKey_KeypadEnter; + case VK_LSHIFT: return ImGuiKey_LeftShift; + case VK_LCONTROL: return ImGuiKey_LeftControl; + case VK_LMENU: return ImGuiKey_LeftAlt; + case VK_LWIN: return ImGuiKey_LeftSuper; + case VK_RSHIFT: return ImGuiKey_RightShift; + case VK_RCONTROL: return ImGuiKey_RightControl; + case VK_RMENU: return ImGuiKey_RightAlt; + case VK_RWIN: return ImGuiKey_RightSuper; + case VK_APPS: return ImGuiKey_Menu; + case '0': return ImGuiKey_0; + case '1': return ImGuiKey_1; + case '2': return ImGuiKey_2; + case '3': return ImGuiKey_3; + case '4': return ImGuiKey_4; + case '5': return ImGuiKey_5; + case '6': return ImGuiKey_6; + case '7': return ImGuiKey_7; + case '8': return ImGuiKey_8; + case '9': return ImGuiKey_9; + case 'A': return ImGuiKey_A; + case 'B': return ImGuiKey_B; + case 'C': return ImGuiKey_C; + case 'D': return ImGuiKey_D; + case 'E': return ImGuiKey_E; + case 'F': return ImGuiKey_F; + case 'G': return ImGuiKey_G; + case 'H': return ImGuiKey_H; + case 'I': return ImGuiKey_I; + case 'J': return ImGuiKey_J; + case 'K': return ImGuiKey_K; + case 'L': return ImGuiKey_L; + case 'M': return ImGuiKey_M; + case 'N': return ImGuiKey_N; + case 'O': return ImGuiKey_O; + case 'P': return ImGuiKey_P; + case 'Q': return ImGuiKey_Q; + case 'R': return ImGuiKey_R; + case 'S': return ImGuiKey_S; + case 'T': return ImGuiKey_T; + case 'U': return ImGuiKey_U; + case 'V': return ImGuiKey_V; + case 'W': return ImGuiKey_W; + case 'X': return ImGuiKey_X; + case 'Y': return ImGuiKey_Y; + case 'Z': return ImGuiKey_Z; + case VK_F1: return ImGuiKey_F1; + case VK_F2: return ImGuiKey_F2; + case VK_F3: return ImGuiKey_F3; + case VK_F4: return ImGuiKey_F4; + case VK_F5: return ImGuiKey_F5; + case VK_F6: return ImGuiKey_F6; + case VK_F7: return ImGuiKey_F7; + case VK_F8: return ImGuiKey_F8; + case VK_F9: return ImGuiKey_F9; + case VK_F10: return ImGuiKey_F10; + case VK_F11: return ImGuiKey_F11; + case VK_F12: return ImGuiKey_F12; + default: return ImGuiKey_None; + } +} + // Allow compilation with old Windows SDK. MinGW doesn't have default _WIN32_WINNT/WINVER versions. #ifndef WM_MOUSEHWHEEL #define WM_MOUSEHWHEEL 0x020E @@ -424,26 +560,39 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA case WM_SYSKEYDOWN: case WM_SYSKEYUP: { - bool down = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); + const bool is_key_down = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN); + if (wParam < 256) - io.KeysDown[wParam] = down; - if (wParam == VK_CONTROL) { - io.KeysDown[VK_LCONTROL] = ((::GetKeyState(VK_LCONTROL) & 0x8000) != 0); - io.KeysDown[VK_RCONTROL] = ((::GetKeyState(VK_RCONTROL) & 0x8000) != 0); - io.KeyCtrl = io.KeysDown[VK_LCONTROL] || io.KeysDown[VK_RCONTROL]; - } - if (wParam == VK_SHIFT) - { - io.KeysDown[VK_LSHIFT] = ((::GetKeyState(VK_LSHIFT) & 0x8000) != 0); - io.KeysDown[VK_RSHIFT] = ((::GetKeyState(VK_RSHIFT) & 0x8000) != 0); - io.KeyShift = io.KeysDown[VK_LSHIFT] || io.KeysDown[VK_RSHIFT]; - } - if (wParam == VK_MENU) - { - io.KeysDown[VK_LMENU] = ((::GetKeyState(VK_LMENU) & 0x8000) != 0); - io.KeysDown[VK_RMENU] = ((::GetKeyState(VK_RMENU) & 0x8000) != 0); - io.KeyAlt = io.KeysDown[VK_LMENU] || io.KeysDown[VK_RMENU]; + // Obtain virtual key code + // (keypad enter doesn't have its own... VK_RETURN with KF_EXTENDED flag means keypad enter, see IM_VK_KEYPAD_ENTER definition for details, it is mapped to ImGuiKey_KeyPadEnter.) + int vk = (int)wParam; + if ((wParam == VK_RETURN) && (HIWORD(lParam) & KF_EXTENDED)) + vk = IM_VK_KEYPAD_ENTER; + + // Submit key event + const ImGuiKey key = ImGui_ImplWin32_VirtualKeyToImGuiKey(vk); + const int scancode = (int)LOBYTE(HIWORD(lParam)); + if (key != ImGuiKey_None) + ImGui_ImplWin32_AddKeyEvent(key, is_key_down, vk, scancode); + + // Submit individual left/right modifier events + if (vk == VK_SHIFT) + { + // Important: Shift keys tend to get stuck when pressed together, missing key-up events are corrected in ImGui_ImplWin32_ProcessKeyEventsWorkarounds() + if (IsVkDown(VK_LSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftShift, is_key_down, VK_LSHIFT, scancode); } + if (IsVkDown(VK_RSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightShift, is_key_down, VK_RSHIFT, scancode); } + } + else if (vk == VK_CONTROL) + { + if (IsVkDown(VK_LCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftControl, is_key_down, VK_LCONTROL, scancode); } + if (IsVkDown(VK_RCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightControl, is_key_down, VK_RCONTROL, scancode); } + } + else if (vk == VK_MENU) + { + if (IsVkDown(VK_LMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftAlt, is_key_down, VK_LMENU, scancode); } + if (IsVkDown(VK_RMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightAlt, is_key_down, VK_RMENU, scancode); } + } } return 0; } diff --git a/backends/imgui_impl_win32.h b/backends/imgui_impl_win32.h index 768fe1694..a04b6a712 100644 --- a/backends/imgui_impl_win32.h +++ b/backends/imgui_impl_win32.h @@ -3,11 +3,11 @@ // Implemented features: // [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui) -// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. -// [X] Platform: Keyboard arrays indexed using VK_* Virtual Key Codes, e.g. ImGui::IsKeyPressed(VK_SPACE). +// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set] // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. +// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. -// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. // Read online: https://github.com/ocornut/imgui/tree/master/docs