diff --git a/src/platform/windows/display_manager_windows.cpp b/src/platform/windows/display_manager_windows.cpp index 3e3a889..4613c2a 100644 --- a/src/platform/windows/display_manager_windows.cpp +++ b/src/platform/windows/display_manager_windows.cpp @@ -6,6 +6,7 @@ #include "../../display.h" #include "../../display_event.h" #include "../../display_manager.h" +#include "dpi_utils_windows.h" namespace nativeapi { @@ -68,7 +69,14 @@ Display DisplayManager::GetPrimary() { Point DisplayManager::GetCursorPosition() { POINT cursorPos; if (GetCursorPos(&cursorPos)) { - return {static_cast(cursorPos.x), static_cast(cursorPos.y)}; + // Determine which monitor the cursor is on for DPI scaling + HMONITOR hMonitor = + MonitorFromPoint(cursorPos, MONITOR_DEFAULTTONEAREST); + double scale = GetScaleFactorForMonitor(hMonitor); + if (scale <= 0.0) + scale = 1.0; + return {static_cast(cursorPos.x) / scale, + static_cast(cursorPos.y) / scale}; } return {0.0, 0.0}; } diff --git a/src/platform/windows/display_windows.cpp b/src/platform/windows/display_windows.cpp index e09b57e..461408a 100644 --- a/src/platform/windows/display_windows.cpp +++ b/src/platform/windows/display_windows.cpp @@ -1,6 +1,7 @@ #include "../../display.h" #include +#include "dpi_utils_windows.h" #include "string_utils_windows.h" namespace nativeapi { @@ -73,7 +74,11 @@ Point Display::GetPosition() const { return {0.0, 0.0}; MONITORINFOEXW monitorInfo = GetMonitorInfoEx(pimpl_->h_monitor_); RECT rect = monitorInfo.rcMonitor; - return {static_cast(rect.left), static_cast(rect.top)}; + double scale = GetScaleFactorForMonitor(pimpl_->h_monitor_); + if (scale <= 0.0) + scale = 1.0; + return {static_cast(rect.left) / scale, + static_cast(rect.top) / scale}; } Size Display::GetSize() const { @@ -81,7 +86,11 @@ Size Display::GetSize() const { return {0.0, 0.0}; MONITORINFOEXW monitorInfo = GetMonitorInfoEx(pimpl_->h_monitor_); RECT rect = monitorInfo.rcMonitor; - return {static_cast(rect.right - rect.left), static_cast(rect.bottom - rect.top)}; + double scale = GetScaleFactorForMonitor(pimpl_->h_monitor_); + if (scale <= 0.0) + scale = 1.0; + return {static_cast(rect.right - rect.left) / scale, + static_cast(rect.bottom - rect.top) / scale}; } Rectangle Display::GetWorkArea() const { @@ -89,22 +98,20 @@ Rectangle Display::GetWorkArea() const { return {0.0, 0.0, 0.0, 0.0}; MONITORINFOEXW monitorInfo = GetMonitorInfoEx(pimpl_->h_monitor_); RECT workRect = monitorInfo.rcWork; - return {static_cast(workRect.left), static_cast(workRect.top), - static_cast(workRect.right - workRect.left), - static_cast(workRect.bottom - workRect.top)}; + double scale = GetScaleFactorForMonitor(pimpl_->h_monitor_); + if (scale <= 0.0) + scale = 1.0; + return {static_cast(workRect.left) / scale, + static_cast(workRect.top) / scale, + static_cast(workRect.right - workRect.left) / scale, + static_cast(workRect.bottom - workRect.top) / scale}; } double Display::GetScaleFactor() const { if (!pimpl_->h_monitor_) return 1.0; - HDC hdc = GetDC(nullptr); - double scaleFactor = 1.0; - if (hdc) { - int dpiX = GetDeviceCaps(hdc, LOGPIXELSX); - scaleFactor = dpiX / 96.0; // 96 DPI is 100% scale - ReleaseDC(nullptr, hdc); - } - return scaleFactor; + double scale = GetScaleFactorForMonitor(pimpl_->h_monitor_); + return (scale > 0.0) ? scale : 1.0; } bool Display::IsPrimary() const { diff --git a/src/platform/windows/dpi_utils_windows.cpp b/src/platform/windows/dpi_utils_windows.cpp index 061f61b..9e9afcd 100644 --- a/src/platform/windows/dpi_utils_windows.cpp +++ b/src/platform/windows/dpi_utils_windows.cpp @@ -3,7 +3,7 @@ namespace nativeapi { // Internal: per-monitor DPI via Shcore when available -static double GetScaleFactorForMonitor(HMONITOR hmonitor) { +double GetScaleFactorForMonitor(HMONITOR hmonitor) { if (!hmonitor) return 1.0; typedef HRESULT(WINAPI * GetDpiForMonitorFunc)(HMONITOR, int, UINT*, UINT*); diff --git a/src/platform/windows/dpi_utils_windows.h b/src/platform/windows/dpi_utils_windows.h index ddeca77..1ebd702 100644 --- a/src/platform/windows/dpi_utils_windows.h +++ b/src/platform/windows/dpi_utils_windows.h @@ -6,4 +6,7 @@ namespace nativeapi { // Returns the DPI scale factor for the given window (1.0 at 96 DPI) double GetScaleFactorForWindow(HWND hwnd); +// Returns the DPI scale factor for the given monitor (1.0 at 96 DPI) +double GetScaleFactorForMonitor(HMONITOR hmonitor); + } // namespace nativeapi diff --git a/src/platform/windows/window_windows.cpp b/src/platform/windows/window_windows.cpp index 998a5b9..1df6c62 100644 --- a/src/platform/windows/window_windows.cpp +++ b/src/platform/windows/window_windows.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include "../../foundation/id_allocator.h" #include "../../window.h" @@ -7,6 +8,7 @@ #include "../../window_registry.h" #include "dpi_utils_windows.h" #include "string_utils_windows.h" +#include "window_message_dispatcher.h" #pragma comment(lib, "dwmapi.lib") @@ -30,6 +32,9 @@ class Window::Impl { WindowId window_id_; TitleBarStyle title_bar_style_; VisualEffect visual_effect_; + Size min_size_{0, 0}; + Size max_size_{0, 0}; + int min_max_handler_id_ = 0; }; // Custom window procedure to handle window messages @@ -184,8 +189,14 @@ Window::Window(void* native_window) { } Window::~Window() { - // Remove window from registry on destruction if (pimpl_ && pimpl_->window_id_ != IdAllocator::kInvalidId) { + // Unregister WM_GETMINMAXINFO handler if registered + if (pimpl_->min_max_handler_id_ != 0 && pimpl_->hwnd_) { + WindowMessageDispatcher::GetInstance().UnregisterHandler( + pimpl_->min_max_handler_id_); + } + + // Remove window from registry on destruction WindowRegistry::GetInstance().Remove(pimpl_->window_id_); // Remove the custom property from HWND if window is still valid @@ -336,8 +347,14 @@ bool Window::IsFullScreen() const { void Window::SetBounds(Rectangle bounds) { if (pimpl_->hwnd_) { - SetWindowPos(pimpl_->hwnd_, nullptr, static_cast(bounds.x), static_cast(bounds.y), - static_cast(bounds.width), static_cast(bounds.height), SWP_NOZORDER); + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + SetWindowPos(pimpl_->hwnd_, nullptr, + static_cast(std::lround(bounds.x * scale)), + static_cast(std::lround(bounds.y * scale)), + static_cast(std::lround(bounds.width * scale)), + static_cast(std::lround(bounds.height * scale)), SWP_NOZORDER); } } @@ -346,10 +363,13 @@ Rectangle Window::GetBounds() const { if (pimpl_->hwnd_) { RECT rect; GetWindowRect(pimpl_->hwnd_, &rect); - bounds.x = rect.left; - bounds.y = rect.top; - bounds.width = rect.right - rect.left; - bounds.height = rect.bottom - rect.top; + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + bounds.x = static_cast(rect.left) / scale; + bounds.y = static_cast(rect.top) / scale; + bounds.width = static_cast(rect.right - rect.left) / scale; + bounds.height = static_cast(rect.bottom - rect.top) / scale; } return bounds; } @@ -358,8 +378,13 @@ void Window::SetSize(Size size, bool animate) { if (pimpl_->hwnd_) { // Windows doesn't have built-in animation for window resizing // Animation would require custom implementation - SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, static_cast(size.width), - static_cast(size.height), SWP_NOMOVE | SWP_NOZORDER); + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, + static_cast(std::lround(size.width * scale)), + static_cast(std::lround(size.height * scale)), + SWP_NOMOVE | SWP_NOZORDER); } } @@ -368,8 +393,11 @@ Size Window::GetSize() const { if (pimpl_->hwnd_) { RECT rect; GetWindowRect(pimpl_->hwnd_, &rect); - size.width = rect.right - rect.left; - size.height = rect.bottom - rect.top; + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + size.width = static_cast(rect.right - rect.left) / scale; + size.height = static_cast(rect.bottom - rect.top) / scale; } return size; } @@ -384,8 +412,13 @@ void Window::SetContentSize(Size size) { int borderWidth = (windowRect.right - windowRect.left) - clientRect.right; int borderHeight = (windowRect.bottom - windowRect.top) - clientRect.bottom; - SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, static_cast(size.width) + borderWidth, - static_cast(size.height) + borderHeight, SWP_NOMOVE | SWP_NOZORDER); + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, + static_cast(std::lround(size.width * scale)) + borderWidth, + static_cast(std::lround(size.height * scale)) + borderHeight, + SWP_NOMOVE | SWP_NOZORDER); } } @@ -394,8 +427,11 @@ Size Window::GetContentSize() const { if (pimpl_->hwnd_) { RECT rect; GetClientRect(pimpl_->hwnd_, &rect); - size.width = rect.right; - size.height = rect.bottom; + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + size.width = static_cast(rect.right) / scale; + size.height = static_cast(rect.bottom) / scale; } return size; } @@ -418,11 +454,15 @@ void Window::SetContentBounds(Rectangle bounds) { int offsetX = clientTopLeft.x - windowRect.left; int offsetY = clientTopLeft.y - windowRect.top; + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + // Calculate window position so that client area is at bounds position - int windowX = static_cast(bounds.x) - offsetX; - int windowY = static_cast(bounds.y) - offsetY; - int windowWidth = static_cast(bounds.width) + borderWidth; - int windowHeight = static_cast(bounds.height) + borderHeight; + int windowX = static_cast(std::lround(bounds.x * scale)) - offsetX; + int windowY = static_cast(std::lround(bounds.y * scale)) - offsetY; + int windowWidth = static_cast(std::lround(bounds.width * scale)) + borderWidth; + int windowHeight = static_cast(std::lround(bounds.height * scale)) + borderHeight; SetWindowPos(pimpl_->hwnd_, nullptr, windowX, windowY, windowWidth, windowHeight, SWP_NOZORDER); } @@ -453,25 +493,83 @@ Rectangle Window::GetContentBounds() const { return bounds; } +// Helper function: registers a WM_GETMINMAXINFO handler for the given HWND +// via WindowMessageDispatcher if not already registered. Returns the handler ID. +static int RegisterMinMaxInfoHandler(HWND hwnd, int existing_handler_id) { + if (existing_handler_id != 0) { + return existing_handler_id; + } + if (!hwnd || !IsWindow(hwnd)) { + return 0; + } + auto& dispatcher = WindowMessageDispatcher::GetInstance(); + return dispatcher.RegisterHandler( + hwnd, + [](HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) -> std::optional { + if (msg == WM_GETMINMAXINFO) { + HANDLE prop_handle = GetPropW(hwnd, kWindowIdProperty); + if (prop_handle) { + WindowId window_id = static_cast( + reinterpret_cast(prop_handle)); + if (window_id != IdAllocator::kInvalidId) { + auto window = WindowRegistry::GetInstance().Get(window_id); + if (window) { + auto minSize = window->GetMinimumSize(); + auto maxSize = window->GetMaximumSize(); + MINMAXINFO* mmi = reinterpret_cast(lparam); + double scale_mm = GetScaleFactorForWindow(hwnd); + if (scale_mm <= 0.0) + scale_mm = 1.0; + if (minSize.width > 0 && minSize.height > 0) { + mmi->ptMinTrackSize.x = static_cast(std::lround(minSize.width * scale_mm)); + mmi->ptMinTrackSize.y = static_cast(std::lround(minSize.height * scale_mm)); + } + if (maxSize.width > 0 && maxSize.height > 0) { + mmi->ptMaxTrackSize.x = static_cast(std::lround(maxSize.width * scale_mm)); + mmi->ptMaxTrackSize.y = static_cast(std::lround(maxSize.height * scale_mm)); + } + return std::make_optional(0); + } + } + } + } + return std::nullopt; + }); +} + void Window::SetMinimumSize(Size size) { - // Windows minimum size would be handled in WM_GETMINMAXINFO message - // This is a placeholder implementation + pimpl_->min_size_ = size; + + if (pimpl_->hwnd_) { + pimpl_->min_max_handler_id_ = + RegisterMinMaxInfoHandler(pimpl_->hwnd_, pimpl_->min_max_handler_id_); + + // Trigger the window to re-evaluate its size constraints + SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); + } } Size Window::GetMinimumSize() const { - // Return default minimum size - return {0, 0}; + return pimpl_->min_size_; } void Window::SetMaximumSize(Size size) { - // Windows maximum size would be handled in WM_GETMINMAXINFO message - // This is a placeholder implementation + pimpl_->max_size_ = size; + + if (pimpl_->hwnd_) { + pimpl_->min_max_handler_id_ = + RegisterMinMaxInfoHandler(pimpl_->hwnd_, pimpl_->min_max_handler_id_); + + // Trigger the window to re-evaluate its size constraints + SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); + } } Size Window::GetMaximumSize() const { - // Return default maximum size (screen size) - return {static_cast(GetSystemMetrics(SM_CXSCREEN)), - static_cast(GetSystemMetrics(SM_CYSCREEN))}; + return pimpl_->max_size_; } void Window::SetResizable(bool is_resizable) { @@ -603,8 +701,13 @@ bool Window::IsAlwaysOnTop() const { void Window::SetPosition(Point point) { if (pimpl_->hwnd_) { - SetWindowPos(pimpl_->hwnd_, nullptr, static_cast(point.x), static_cast(point.y), 0, 0, - SWP_NOSIZE | SWP_NOZORDER); + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + SetWindowPos(pimpl_->hwnd_, nullptr, + static_cast(std::lround(point.x * scale)), + static_cast(std::lround(point.y * scale)), + 0, 0, SWP_NOSIZE | SWP_NOZORDER); } } @@ -613,8 +716,11 @@ Point Window::GetPosition() const { if (pimpl_->hwnd_) { RECT rect; GetWindowRect(pimpl_->hwnd_, &rect); - point.x = rect.left; - point.y = rect.top; + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + point.x = static_cast(rect.left) / scale; + point.y = static_cast(rect.top) / scale; } return point; } @@ -635,6 +741,7 @@ void Window::Center() { GetMonitorInfo(monitor, &mi); // Calculate the center position on the monitor's work area + // All values here are in physical pixels (GetWindowRect and rcWork), so no DPI scaling needed int centerX = mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - windowWidth) / 2; int centerY = mi.rcWork.top + (mi.rcWork.bottom - mi.rcWork.top - windowHeight) / 2;