Backends: Win32: Update to use io.AddEventKey() will full key map (2625)
diff --git a/backends/imgui_impl_win32.cpp b/backends/imgui_impl_win32.cpp
index c34cd46..e91dd2f 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 @@
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
+// 2022-01-05: Inputs: calling new io.AddKeyEvent() API (1.87+), support for full keys 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 @@
// 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,45 @@
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)
+{
+ ImGuiIOKeyEvent io_event;
+ io_event.Key = key;
+ io_event.Down = down;
+ io_event.NativeKeycode = native_keycode;
+ io_event.NativeScancode = native_scancode;
+ ImGui::GetIO().AddKeyEvent(&io_event);
+}
+
+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 +339,12 @@
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 +360,122 @@
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 +562,39 @@
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 768fe16..a04b6a7 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