From a2fbcc9ad4e92f0ea11265c8539c86c0caa1b0c3 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 9 Mar 2018 19:02:52 +0100 Subject: [PATCH] Examples: DPI: Portable DPI related helpers in the _Win32 examples. Using one in examples's main.cpp, the GetDpiScale functions are not wired anywhere for now. (#1542, #1676) --- examples/directx10_example/main.cpp | 2 + examples/directx11_example/main.cpp | 20 +------- examples/imgui_impl_win32.cpp | 78 +++++++++++++++++++++++++++++ examples/imgui_impl_win32.h | 6 +++ imgui.cpp | 4 +- 5 files changed, 89 insertions(+), 21 deletions(-) diff --git a/examples/directx10_example/main.cpp b/examples/directx10_example/main.cpp index 811cda70..fb034cd8 100644 --- a/examples/directx10_example/main.cpp +++ b/examples/directx10_example/main.cpp @@ -95,6 +95,8 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) int main(int, char**) { + ImGui_ImplWin32_EnableDpiAwareness(); + // Create application window WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, _T("ImGui Example"), NULL }; RegisterClassEx(&wc); diff --git a/examples/directx11_example/main.cpp b/examples/directx11_example/main.cpp index d333f61c..3cec017d 100644 --- a/examples/directx11_example/main.cpp +++ b/examples/directx11_example/main.cpp @@ -96,27 +96,9 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) return DefWindowProc(hWnd, msg, wParam, lParam); } -// FIXME-DPI: For now we just want to call SetProcessDpiAwareness(PROCESS_PER_MONITOR_AWARE) without requiring SDK 8.1 or 10 -void SetupDpiAwareness() -{ - typedef enum PROCESS_DPI_AWARENESS - { - PROCESS_DPI_UNAWARE = 0, - PROCESS_SYSTEM_DPI_AWARE = 1, - PROCESS_PER_MONITOR_DPI_AWARE = 2 - } PROCESS_DPI_AWARENESS; - if (HINSTANCE shcore_dll = ::LoadLibraryA("shcore.dll")) - { - typedef HRESULT(WINAPI * PFN_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS); - if (PFN_SetProcessDpiAwareness SetProcessDpiAwarenessFn = (PFN_SetProcessDpiAwareness)::GetProcAddress(shcore_dll, "SetProcessDpiAwareness")) - SetProcessDpiAwarenessFn(PROCESS_PER_MONITOR_DPI_AWARE); - ::FreeLibrary(shcore_dll); - } -} - int main(int, char**) { - SetupDpiAwareness(); + ImGui_ImplWin32_EnableDpiAwareness(); // Create application window WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, _T("ImGui Example"), NULL }; diff --git a/examples/imgui_impl_win32.cpp b/examples/imgui_impl_win32.cpp index c02bfad9..eeea68a8 100644 --- a/examples/imgui_impl_win32.cpp +++ b/examples/imgui_impl_win32.cpp @@ -271,6 +271,84 @@ IMGUI_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wPa return 0; } +// -------------------------------------------------------------------------------------------------------- +// DPI handling +// Those in theory should be simple calls but Windows has multiple ways to handle DPI, and most of them +// require recent Windows versions at runtime or recent Windows SDK at compile-time. Neither we want to depend on. +// So we dynamically select and load those functions to avoid dependencies. This is the scheme successfully +// used by GLFW (from which we borrowed some of the code here) and other applications aiming to be portable. +//--------------------------------------------------------------------------------------------------------- +// FIXME-DPI: For now we just call SetProcessDpiAwareness(PROCESS_PER_MONITOR_AWARE) without requiring SDK 8.1 or 10. +// We may allow/aim calling the most-recent-available version, e.g. Windows 10 Creators Update has SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); +// At this point ImGui_ImplWin32_EnableDpiAwareness() is just a helper called by main.cpp, we don't call it ourselves. +//--------------------------------------------------------------------------------------------------------- + +static BOOL IsWindowsVersionOrGreater(WORD major, WORD minor, WORD sp) +{ + OSVERSIONINFOEXW osvi = { sizeof(osvi), major, minor, 0, 0,{ 0 }, sp }; + DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR; + ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL); + cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL); + cond = VerSetConditionMask(cond, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + return VerifyVersionInfoW(&osvi, mask, cond); +} +#define IsWindows8Point1OrGreater() IsWindowsVersionOrGreater(HIBYTE(0x0602), LOBYTE(0x0602), 0) // _WIN32_WINNT_WINBLUE +#define IsWindows10OrGreater() IsWindowsVersionOrGreater(HIBYTE(0x0A00), LOBYTE(0x0A00), 0) // _WIN32_WINNT_WIN10 + +#ifndef DPI_ENUMS_DECLARED +typedef enum { PROCESS_DPI_UNAWARE = 0, PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS; +typedef enum { MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2, MDT_DEFAULT = MDT_EFFECTIVE_DPI } MONITOR_DPI_TYPE; +#endif +typedef HRESULT(WINAPI * PFN_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS); // Shcore.lib+dll, Windows 8.1 +typedef HRESULT(WINAPI * PFN_GetDpiForMonitor)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); // Shcore.lib+dll, Windows 8.1 + +void ImGui_ImplWin32_EnableDpiAwareness() +{ + if (IsWindows8Point1OrGreater()) + { + static HINSTANCE shcore_dll = ::LoadLibraryA("shcore.dll"); // Reference counted per-process + if (PFN_SetProcessDpiAwareness SetProcessDpiAwarenessFn = (PFN_SetProcessDpiAwareness)::GetProcAddress(shcore_dll, "SetProcessDpiAwareness")) + SetProcessDpiAwarenessFn(PROCESS_PER_MONITOR_DPI_AWARE); + } + else + { + SetProcessDPIAware(); + } +} + +float ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor) +{ + UINT xdpi = 96, ydpi = 96; + if (IsWindows8Point1OrGreater()) + { + static HINSTANCE shcore_dll = ::LoadLibraryA("shcore.dll"); // Reference counted per-process + if (PFN_GetDpiForMonitor GetDpiForMonitorFn = (PFN_GetDpiForMonitor)::GetProcAddress(shcore_dll, "GetDpiForMonitor")) + GetDpiForMonitorFn((HMONITOR)monitor, MDT_EFFECTIVE_DPI, &xdpi, &ydpi); + } + else + { + const HDC dc = ::GetDC(NULL); + xdpi = ::GetDeviceCaps(dc, LOGPIXELSX); + ydpi = ::GetDeviceCaps(dc, LOGPIXELSY); + ::ReleaseDC(NULL, dc); + } + IM_ASSERT(xdpi == ydpi); // Please contact me if you hit this assert! + return xdpi / 96.0f; +} + +float ImGui_ImplWin32_GetDpiScaleForHwnd(void* hwnd) +{ + HMONITOR monitor = ::MonitorFromWindow((HWND)hwnd, MONITOR_DEFAULTTONEAREST); + return ImGui_ImplWin32_GetDpiScaleForMonitor(monitor); +} + +float ImGui_ImplWin32_GetDpiScaleForRect(int x1, int y1, int x2, int y2) +{ + RECT viewport_rect = { (UINT)x1, (UINT)y1, (UINT)x2, (UINT)y2 }; + HMONITOR monitor = ::MonitorFromRect(&viewport_rect, MONITOR_DEFAULTTONEAREST); + return ImGui_ImplWin32_GetDpiScaleForMonitor(monitor); +} + // -------------------------------------------------------------------------------------------------------- // Platform Windows // -------------------------------------------------------------------------------------------------------- diff --git a/examples/imgui_impl_win32.h b/examples/imgui_impl_win32.h index a43f067d..2a04cb09 100644 --- a/examples/imgui_impl_win32.h +++ b/examples/imgui_impl_win32.h @@ -5,6 +5,12 @@ IMGUI_API bool ImGui_ImplWin32_Init(void* hwnd); IMGUI_API void ImGui_ImplWin32_Shutdown(); IMGUI_API void ImGui_ImplWin32_NewFrame(); +// DPI-related helpers (which run and compile without requiring 8.1 or 10, neither Windows version, neither associated SDK) +IMGUI_API void ImGui_ImplWin32_EnableDpiAwareness(); +IMGUI_API float ImGui_ImplWin32_GetDpiScaleForHwnd(void* hwnd); // HWND hwnd +IMGUI_API float ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor); // HMONITOR monitor +IMGUI_API float ImGui_ImplWin32_GetDpiScaleForRect(int x1, int y1, int x2, int y2); + // Handler for Win32 messages, update mouse/keyboard data. // You may or not need this for your implementation, but it can serve as reference for handling inputs. // Intentionally commented out to avoid dragging dependencies on types. You can copy the extern declaration in your code. diff --git a/imgui.cpp b/imgui.cpp index f1e4ab10..fefcfda9 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -13804,13 +13804,13 @@ static void ScaleWindow(ImGuiWindow* window, float scale) // Scale all windows (position, size). Use when e.g. changing DPI. (This is a lossy operation!) void ImGui::ScaleWindowsInViewport(ImGuiViewport* viewport, float scale) { + ImGuiContext& g = *GImGui; if (viewport->Window) { ScaleWindow(viewport->Window, scale); } else { - ImGuiContext& g = *GImGui; for (int i = 0; i != g.Windows.Size; i++) if (g.Windows[i]->Viewport == viewport) ScaleWindow(g.Windows[i], scale); @@ -13992,7 +13992,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) { ImGui::BulletText("Pos: (%.0f,%.0f)", viewport->Pos.x, viewport->Pos.y); ImGui::BulletText("Flags: 0x%04X", viewport->Flags); - ImGui::BulletText("PlatformOsDesktopPos: (%.0f,%.0f)", viewport->PlatformOsDesktopPos.x, viewport->PlatformOsDesktopPos.y); + ImGui::BulletText("PlatformOsDesktopPos: (%.0f,%.0f); DpiScale: %.0f%%", viewport->PlatformOsDesktopPos.x, viewport->PlatformOsDesktopPos.y, viewport->PlatformDpiScale * 100.0f); for (int draw_list_i = 0; draw_list_i < viewport->DrawDataBuilder.Layers[0].Size; draw_list_i++) Funcs::NodeDrawList(NULL, viewport->DrawDataBuilder.Layers[0][draw_list_i], "DrawList"); ImGui::TreePop();