双光标问题
向游戏窗口注入 ImGui 窗口后,默认情况下就会出现双光标。
解决
更新光标的地方在imgui_impl_win32.cpp
文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| static bool ImGui_ImplWin32_UpdateMouseCursor() { ImGuiIO& io = ImGui::GetIO(); if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) return false;
ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor) { ::SetCursor(NULL); } else { LPTSTR win32_cursor = IDC_ARROW; switch (imgui_cursor) { case ImGuiMouseCursor_Arrow: win32_cursor = IDC_ARROW; break; case ImGuiMouseCursor_TextInput: win32_cursor = IDC_IBEAM; break; case ImGuiMouseCursor_ResizeAll: win32_cursor = IDC_SIZEALL; break; case ImGuiMouseCursor_ResizeEW: win32_cursor = IDC_SIZEWE; break; case ImGuiMouseCursor_ResizeNS: win32_cursor = IDC_SIZENS; break; case ImGuiMouseCursor_ResizeNESW: win32_cursor = IDC_SIZENESW; break; case ImGuiMouseCursor_ResizeNWSE: win32_cursor = IDC_SIZENWSE; break; case ImGuiMouseCursor_Hand: win32_cursor = IDC_HAND; break; case ImGuiMouseCursor_NotAllowed: win32_cursor = IDC_NO; break; } ::SetCursor(::LoadCursor(NULL, win32_cursor)); } return true; }
|
从代码能看出,如果不希望出现光标,也就是执行::SetCursor(::LoadCursor(NULL, win32_cursor))
这句,那么有两个地方可以修改
- 为
ConfigFlags
加上ImGuiConfigFlags_NoMouseCursorChange
参数,函数将完全失效。
io.MouseDrawCursor
这个值为true
时(默认值为false)。
方式一
在创建 ImGui 时为ConfigFlags
加上ImGuiConfigFlags_NoMouseCursorChange
参数,使ImGui_ImplWin32_UpdateMouseCursor
函数彻底失效
1 2 3
| ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
|
接着在窗口消息处理过程中,自行决定是否渲染光标,所以需要处理WM_SETCURSOR
消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| LRESULT WINAPI MyWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (ImGui_ImplWin32_WndProcHandler(hWnd, uMsg, wParam, lParam)) { return ERROR_SUCCESS; }
if (uMsg == WM_SETCURSOR) { ::SetCursor(io.WantCaptureMouse ? ::LoadCursorW(nullptr, IDC_ARROW) : nullptr); return ERROR_SUCCESS; }
return ::CallWindowProcW(_oldWndProc, hWnd, uMsg, wParam, lParam); }
|
但有瑕疵
当光标在输入框,窗口边框等情况下,它任然会显示箭头,所以还需要对每种情况进行处理,这使得工作变得复杂。
查看源码发现,ImGui 会在ImGui_ImplWin32_WndProcHandler
中调用ImGui_ImplWin32_UpdateMouseCursor
1 2 3 4 5 6 7 8 9
| IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { ... case WM_SETCURSOR: if (LOWORD(lParam) == HTCLIENT && ImGui_ImplWin32_UpdateMouseCursor()) return 1; return 0; ... }
|
所以也可以换个思路,去掉ImGuiConfigFlags_NoMouseCursorChange
设置,并在消息中拦截处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| LRESULT WINAPI MyWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ImGuiIO& io = ImGui::GetIO(); if (LOWORD(lParam) == HTCLIENT && uMsg == WM_SETCURSOR) { if (!io.WantCaptureMouse) { ::SetCursor(nullptr); return ERROR_SUCCESS; }
if (!_show) { return ERROR_SUCCESS; } }
if (ImGui_ImplWin32_WndProcHandler(hWnd, uMsg, wParam, lParam)) { return ERROR_SUCCESS; }
return ::CallWindowProcW(_oldWndProc, hWnd, uMsg, wParam, lParam); }
|
还没完,通常外挂窗口可以通过快捷键隐藏,当通过快捷键隐藏窗口时也要处理光标
1 2 3 4 5 6 7
| if (uMsg == WM_KEYUP) { if (wParam == VK_F4) { _show = !_show; ::SetCursor(_show ? ::LoadCursorW(nullptr, IDC_ARROW) : nullptr); return ERROR_SUCCESS; } }
|
方式二
从ImGui_ImplWin32_UpdateMouseCursor
的逻辑看,当io.MouseDrawCursor
为true
时,ImGui 就不会使用系统光标,而是自行绘制光标
1 2 3
| ImGui::CreateContext(); io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; io.MouseDrawCursor = true;
|
此时就需要在每一帧创建后指定光标类型
1 2 3
| ImGui::NewFrame(); if (!io.WantCaptureMouse) ImGui::SetMouseCursor(ImGuiMouseCursor_None);
|
工作一切正常,很完美,实现起来比方式一更简单。
参考
SDL2 hide cursor
Panorama cursor fix (imgui)
ImGUI mouse cursor problem