Skip to content

Commit

Permalink
Backends: Win32: rework to add ImGui_ImplWin32_WndProcHandlerEx() not…
Browse files Browse the repository at this point in the history
… using current context (experimental). (#8069, #6293, #5856, #586)

+ GetIOEx(). Amend fedf45c + cba656a. Amend 416cfdb.
  • Loading branch information
ocornut committed Oct 28, 2024
1 parent d67e2ee commit 3b8c7d0
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 45 deletions.
94 changes: 50 additions & 44 deletions backends/imgui_impl_win32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,16 @@ static ImGui_ImplWin32_Data* ImGui_ImplWin32_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplWin32_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;
}
static ImGui_ImplWin32_Data* ImGui_ImplWin32_GetBackendData(ImGuiIO& io)
{
return (ImGui_ImplWin32_Data*)io.BackendPlatformUserData;
}

// Functions
static void ImGui_ImplWin32_UpdateKeyboardCodePage()
static void ImGui_ImplWin32_UpdateKeyboardCodePage(ImGuiIO& io)
{
// Retrieve keyboard code page, required for handling of non-Unicode Windows.
ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData();
ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io);
HKL keyboard_layout = ::GetKeyboardLayout(0);
LCID keyboard_lcid = MAKELCID(HIWORD(keyboard_layout), SORT_DEFAULT);
if (::GetLocaleInfoA(keyboard_lcid, (LOCALE_RETURN_NUMBER | LOCALE_IDEFAULTANSICODEPAGE), (LPSTR)&bd->KeyboardCodePage, sizeof(bd->KeyboardCodePage)) == 0)
Expand Down Expand Up @@ -168,7 +172,7 @@ static bool ImGui_ImplWin32_InitEx(void* hwnd, bool platform_has_own_dc)
bd->TicksPerSecond = perf_frequency;
bd->Time = perf_counter;
bd->LastMouseCursor = ImGuiMouseCursor_COUNT;
ImGui_ImplWin32_UpdateKeyboardCodePage();
ImGui_ImplWin32_UpdateKeyboardCodePage(io);

// Set platform dependent data in viewport
ImGuiViewport* main_viewport = ImGui::GetMainViewport();
Expand Down Expand Up @@ -228,13 +232,11 @@ void ImGui_ImplWin32_Shutdown()
IM_DELETE(bd);
}

static bool ImGui_ImplWin32_UpdateMouseCursor()
static bool ImGui_ImplWin32_UpdateMouseCursor(ImGuiIO& io, ImGuiMouseCursor imgui_cursor)
{
ImGuiIO& io = ImGui::GetIO();
if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
return false;

ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor)
{
// Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
Expand Down Expand Up @@ -266,42 +268,39 @@ 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)
static void ImGui_ImplWin32_AddKeyEvent(ImGuiIO& io, 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()
static void ImGui_ImplWin32_ProcessKeyEventsWorkarounds(ImGuiIO& io)
{
// 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);
ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftShift, false, VK_LSHIFT);
if (ImGui::IsKeyDown(ImGuiKey_RightShift) && !IsVkDown(VK_RSHIFT))
ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightShift, false, VK_RSHIFT);
ImGui_ImplWin32_AddKeyEvent(io, 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);
ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftSuper, false, VK_LWIN);
if (ImGui::IsKeyDown(ImGuiKey_RightSuper) && !IsVkDown(VK_RWIN))
ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightSuper, false, VK_RWIN);
ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightSuper, false, VK_RWIN);
}

static void ImGui_ImplWin32_UpdateKeyModifiers()
static void ImGui_ImplWin32_UpdateKeyModifiers(ImGuiIO& io)
{
ImGuiIO& io = ImGui::GetIO();
io.AddKeyEvent(ImGuiMod_Ctrl, IsVkDown(VK_CONTROL));
io.AddKeyEvent(ImGuiMod_Shift, IsVkDown(VK_SHIFT));
io.AddKeyEvent(ImGuiMod_Alt, IsVkDown(VK_MENU));
io.AddKeyEvent(ImGuiMod_Super, IsVkDown(VK_LWIN) || IsVkDown(VK_RWIN));
}

static void ImGui_ImplWin32_UpdateMouseData()
static void ImGui_ImplWin32_UpdateMouseData(ImGuiIO& io)
{
ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData();
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io);
IM_ASSERT(bd->hWnd != 0);

HWND focused_window = ::GetForegroundWindow();
Expand All @@ -328,11 +327,10 @@ static void ImGui_ImplWin32_UpdateMouseData()
}

// Gamepad navigation mapping
static void ImGui_ImplWin32_UpdateGamepads()
static void ImGui_ImplWin32_UpdateGamepads(ImGuiIO& io)
{
#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData();
ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io);
//if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
// return;

Expand Down Expand Up @@ -381,7 +379,9 @@ static void ImGui_ImplWin32_UpdateGamepads()
MAP_ANALOG(ImGuiKey_GamepadRStickDown, gamepad.sThumbRY, -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768);
#undef MAP_BUTTON
#undef MAP_ANALOG
#endif // #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD
#else // #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD
IM_UNUSED(io);
#endif
}

void ImGui_ImplWin32_NewFrame()
Expand All @@ -402,21 +402,21 @@ void ImGui_ImplWin32_NewFrame()
bd->Time = current_time;

// Update OS mouse position
ImGui_ImplWin32_UpdateMouseData();
ImGui_ImplWin32_UpdateMouseData(io);

// Process workarounds for known Windows key handling issues
ImGui_ImplWin32_ProcessKeyEventsWorkarounds();
ImGui_ImplWin32_ProcessKeyEventsWorkarounds(io);

// Update OS mouse cursor with the cursor requested by imgui
ImGuiMouseCursor mouse_cursor = io.MouseDrawCursor ? ImGuiMouseCursor_None : ImGui::GetMouseCursor();
if (bd->LastMouseCursor != mouse_cursor)
{
bd->LastMouseCursor = mouse_cursor;
ImGui_ImplWin32_UpdateMouseCursor();
ImGui_ImplWin32_UpdateMouseCursor(io, mouse_cursor);
}

// Update game controllers (if enabled and available)
ImGui_ImplWin32_UpdateGamepads();
ImGui_ImplWin32_UpdateGamepads(io);
}

// Map VK_xxx to ImGuiKey_xxx.
Expand Down Expand Up @@ -578,21 +578,27 @@ static ImGuiMouseSource ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo()
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to Dear ImGui, and hide them from your application based on those two flags.
// PS: In this Win32 handler, we use the capture API (GetCapture/SetCapture/ReleaseCapture) to be able to read mouse coordinates when dragging mouse outside of our window bounds.
// PS: We treat DBLCLK messages as regular mouse down messages, so this code will work on windows classes that have the CS_DBLCLKS flag set. Our own example app code doesn't set this flag.

// Copy this line into your .cpp file to forward declare the function:
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
// Copy either line into your .cpp file to forward declare the function:
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Use ImGui::GetCurrentContext()
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ImGuiIO& io); // Doesn't use ImGui::GetCurrentContext()

IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Most backends don't have silent checks like this one, but we need it because WndProc are called early in CreateWindow().
// We silently allow both context or just only backend data to be nullptr.
ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData();
if (bd == nullptr)
if (ImGui::GetCurrentContext() == NULL)
return 0;
ImGuiIO& io = ImGui::GetIO();
return ImGui_ImplWin32_WndProcHandlerEx(hwnd, msg, wParam, lParam, ImGui::GetIO());
}

// This version is in theory thread-safe in the sense that no path should access ImGui::GetCurrentContext().
IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, ImGuiIO& io)
{
ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io);
if (bd == NULL)
return 0;
switch (msg)
{
case WM_MOUSEMOVE:
Expand Down Expand Up @@ -653,7 +659,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA
if (msg == WM_MBUTTONDOWN || msg == WM_MBUTTONDBLCLK) { button = 2; }
if (msg == WM_XBUTTONDOWN || msg == WM_XBUTTONDBLCLK) { button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1) ? 3 : 4; }
if (bd->MouseButtonsDown == 0 && ::GetCapture() == nullptr)
::SetCapture(hwnd);
::SetCapture(hwnd); // Allow us to read mouse coordinates when dragging mouse outside of our window bounds.
bd->MouseButtonsDown |= 1 << button;
io.AddMouseSourceEvent(mouse_source);
io.AddMouseButtonEvent(button, true);
Expand Down Expand Up @@ -692,7 +698,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA
if (wParam < 256)
{
// Submit modifiers
ImGui_ImplWin32_UpdateKeyModifiers();
ImGui_ImplWin32_UpdateKeyModifiers(io);

// Obtain virtual key code and convert to ImGuiKey
const ImGuiKey key = ImGui_ImplWin32_KeyEventToImGuiKey(wParam, lParam);
Expand All @@ -701,28 +707,28 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA

// Special behavior for VK_SNAPSHOT / ImGuiKey_PrintScreen as Windows doesn't emit the key down event.
if (key == ImGuiKey_PrintScreen && !is_key_down)
ImGui_ImplWin32_AddKeyEvent(key, true, vk, scancode);
ImGui_ImplWin32_AddKeyEvent(io, key, true, vk, scancode);

// Submit key event
if (key != ImGuiKey_None)
ImGui_ImplWin32_AddKeyEvent(key, is_key_down, vk, scancode);
ImGui_ImplWin32_AddKeyEvent(io, 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); }
if (IsVkDown(VK_LSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftShift, is_key_down, VK_LSHIFT, scancode); }
if (IsVkDown(VK_RSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightShift, is_key_down, VK_RSHIFT, scancode); }
}
else if (vk == VK_CONTROL)
{
if (IsVkDown(VK_LCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftCtrl, is_key_down, VK_LCONTROL, scancode); }
if (IsVkDown(VK_RCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightCtrl, is_key_down, VK_RCONTROL, scancode); }
if (IsVkDown(VK_LCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftCtrl, is_key_down, VK_LCONTROL, scancode); }
if (IsVkDown(VK_RCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightCtrl, 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); }
if (IsVkDown(VK_LMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftAlt, is_key_down, VK_LMENU, scancode); }
if (IsVkDown(VK_RMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightAlt, is_key_down, VK_RMENU, scancode); }
}
}
return 0;
Expand All @@ -732,7 +738,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA
io.AddFocusEvent(msg == WM_SETFOCUS);
return 0;
case WM_INPUTLANGCHANGE:
ImGui_ImplWin32_UpdateKeyboardCodePage();
ImGui_ImplWin32_UpdateKeyboardCodePage(io);
return 0;
case WM_CHAR:
if (::IsWindowUnicode(hwnd))
Expand All @@ -750,7 +756,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA
return 0;
case WM_SETCURSOR:
// This is required to restore cursor when transitioning from e.g resize borders to client area.
if (LOWORD(lParam) == HTCLIENT && ImGui_ImplWin32_UpdateMouseCursor())
if (LOWORD(lParam) == HTCLIENT && ImGui_ImplWin32_UpdateMouseCursor(io, bd->LastMouseCursor))
return 1;
return 0;
case WM_DEVICECHANGE:
Expand Down
7 changes: 7 additions & 0 deletions imgui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4748,6 +4748,13 @@ ImGuiIO& ImGui::GetIO()
return GImGui->IO;
}

// This variant exists to facilitate backends experimenting with multi-threaded parallel context. (#8069, #6293, #5856)
ImGuiIO& ImGui::GetIOEx(ImGuiContext* ctx)
{
IM_ASSERT(ctx != NULL);
return ctx->IO;
}

ImGuiPlatformIO& ImGui::GetPlatformIO()
{
IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext()?");
Expand Down
2 changes: 1 addition & 1 deletion imgui.h
Original file line number Diff line number Diff line change
Expand Up @@ -2248,7 +2248,7 @@ struct ImGuiIO

// Keyboard/Gamepad Navigation options
bool ConfigNavSwapGamepadButtons; // = false // Swap Activate<>Cancel (A<>B) buttons, matching typical "Nintendo/Japanese style" gamepad layout.
bool ConfigNavMoveSetMousePos; // = false // Directional/tabbing navigation teleports the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is difficult. Will update io.MousePos and set io.WantSetMousePos=true.
bool ConfigNavMoveSetMousePos; // = false // Directional/tabbing navigation teleports the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is difficult. Will update io.MousePos and set io.WantSetMousePos=true.
bool ConfigNavCaptureKeyboard; // = true // Sets io.WantCaptureKeyboard when io.NavActive is set.
bool ConfigNavEscapeClearFocusItem; // = true // Pressing Escape can clear focused item + navigation id/highlight. Set to false if you want to always keep highlight on.
bool ConfigNavEscapeClearFocusWindow;// = false // Pressing Escape can clear focused window as well (super set of io.ConfigNavEscapeClearFocusItem).
Expand Down
1 change: 1 addition & 0 deletions imgui_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -2936,6 +2936,7 @@ namespace ImGui
// If this ever crashes because g.CurrentWindow is NULL, it means that either:
// - ImGui::NewFrame() has never been called, which is illegal.
// - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal.
IMGUI_API ImGuiIO& GetIOEx(ImGuiContext* ctx);
inline ImGuiWindow* GetCurrentWindowRead() { ImGuiContext& g = *GImGui; return g.CurrentWindow; }
inline ImGuiWindow* GetCurrentWindow() { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; }
IMGUI_API ImGuiWindow* FindWindowByID(ImGuiID id);
Expand Down

0 comments on commit 3b8c7d0

Please sign in to comment.