Skip to content

Commit b0798d7

Browse files
authored
fix(windows): implement SetMinimumSize/SetMaximumSize via WM_GETMINMAXINFO (#44)
* fix(windows): implement SetMinimumSize/SetMaximumSize via WM_GETMINMAXINFO The SetMinimumSize and SetMaximumSize methods on Windows were empty placeholder implementations that did nothing. This caused setting window size constraints to have no effect in Flutter apps and other consumers. Fix by: - Storing min/max size constraints in Window::Impl - Registering a WM_GETMINMAXINFO handler via WindowMessageDispatcher that reads the constraints from the Window object and sets ptMinTrackSize/ptMaxTrackSize on the MINMAXINFO structure - This works for both self-created windows and Flutter-hosted windows (via Window(void*) constructor) - Triggering SWP_FRAMECHANGED after setting constraints so the window immediately re-evaluates them - Properly cleaning up the handler in the destructor Closes libnativeapi/nativeapi-flutter#9 * Add per-monitor DPI scaling for Windows Use per-monitor DPI scaling across Windows platform code: apply GetScaleFactorForMonitor/Window to convert between physical pixels and logical coordinates for displays, cursor, and window geometry. Updated DisplayManager::GetCursorPosition, Display::GetPosition/GetSize/GetWorkArea/GetScaleFactor, and many Window methods (SetBounds/GetBounds/SetSize/GetSize/SetContentSize/GetContentSize/SetContentBounds/SetPosition/GetPosition and min/max handling) to scale values and round when setting sizes/positions. Added dpi_utils_windows.h declaration and exposed GetScaleFactorForMonitor in dpi_utils_windows.cpp, included dpi header where needed, and added <cmath> for std::lround. All DPI calls fallback to 1.0 when unavailable; added a comment noting Center uses physical pixels.
1 parent 00b6fff commit b0798d7

5 files changed

Lines changed: 172 additions & 47 deletions

File tree

src/platform/windows/display_manager_windows.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "../../display.h"
77
#include "../../display_event.h"
88
#include "../../display_manager.h"
9+
#include "dpi_utils_windows.h"
910

1011
namespace nativeapi {
1112

@@ -68,7 +69,14 @@ Display DisplayManager::GetPrimary() {
6869
Point DisplayManager::GetCursorPosition() {
6970
POINT cursorPos;
7071
if (GetCursorPos(&cursorPos)) {
71-
return {static_cast<double>(cursorPos.x), static_cast<double>(cursorPos.y)};
72+
// Determine which monitor the cursor is on for DPI scaling
73+
HMONITOR hMonitor =
74+
MonitorFromPoint(cursorPos, MONITOR_DEFAULTTONEAREST);
75+
double scale = GetScaleFactorForMonitor(hMonitor);
76+
if (scale <= 0.0)
77+
scale = 1.0;
78+
return {static_cast<double>(cursorPos.x) / scale,
79+
static_cast<double>(cursorPos.y) / scale};
7280
}
7381
return {0.0, 0.0};
7482
}

src/platform/windows/display_windows.cpp

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "../../display.h"
22

33
#include <windows.h>
4+
#include "dpi_utils_windows.h"
45
#include "string_utils_windows.h"
56

67
namespace nativeapi {
@@ -73,38 +74,44 @@ Point Display::GetPosition() const {
7374
return {0.0, 0.0};
7475
MONITORINFOEXW monitorInfo = GetMonitorInfoEx(pimpl_->h_monitor_);
7576
RECT rect = monitorInfo.rcMonitor;
76-
return {static_cast<double>(rect.left), static_cast<double>(rect.top)};
77+
double scale = GetScaleFactorForMonitor(pimpl_->h_monitor_);
78+
if (scale <= 0.0)
79+
scale = 1.0;
80+
return {static_cast<double>(rect.left) / scale,
81+
static_cast<double>(rect.top) / scale};
7782
}
7883

7984
Size Display::GetSize() const {
8085
if (!pimpl_->h_monitor_)
8186
return {0.0, 0.0};
8287
MONITORINFOEXW monitorInfo = GetMonitorInfoEx(pimpl_->h_monitor_);
8388
RECT rect = monitorInfo.rcMonitor;
84-
return {static_cast<double>(rect.right - rect.left), static_cast<double>(rect.bottom - rect.top)};
89+
double scale = GetScaleFactorForMonitor(pimpl_->h_monitor_);
90+
if (scale <= 0.0)
91+
scale = 1.0;
92+
return {static_cast<double>(rect.right - rect.left) / scale,
93+
static_cast<double>(rect.bottom - rect.top) / scale};
8594
}
8695

8796
Rectangle Display::GetWorkArea() const {
8897
if (!pimpl_->h_monitor_)
8998
return {0.0, 0.0, 0.0, 0.0};
9099
MONITORINFOEXW monitorInfo = GetMonitorInfoEx(pimpl_->h_monitor_);
91100
RECT workRect = monitorInfo.rcWork;
92-
return {static_cast<double>(workRect.left), static_cast<double>(workRect.top),
93-
static_cast<double>(workRect.right - workRect.left),
94-
static_cast<double>(workRect.bottom - workRect.top)};
101+
double scale = GetScaleFactorForMonitor(pimpl_->h_monitor_);
102+
if (scale <= 0.0)
103+
scale = 1.0;
104+
return {static_cast<double>(workRect.left) / scale,
105+
static_cast<double>(workRect.top) / scale,
106+
static_cast<double>(workRect.right - workRect.left) / scale,
107+
static_cast<double>(workRect.bottom - workRect.top) / scale};
95108
}
96109

97110
double Display::GetScaleFactor() const {
98111
if (!pimpl_->h_monitor_)
99112
return 1.0;
100-
HDC hdc = GetDC(nullptr);
101-
double scaleFactor = 1.0;
102-
if (hdc) {
103-
int dpiX = GetDeviceCaps(hdc, LOGPIXELSX);
104-
scaleFactor = dpiX / 96.0; // 96 DPI is 100% scale
105-
ReleaseDC(nullptr, hdc);
106-
}
107-
return scaleFactor;
113+
double scale = GetScaleFactorForMonitor(pimpl_->h_monitor_);
114+
return (scale > 0.0) ? scale : 1.0;
108115
}
109116

110117
bool Display::IsPrimary() const {

src/platform/windows/dpi_utils_windows.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace nativeapi {
44

55
// Internal: per-monitor DPI via Shcore when available
6-
static double GetScaleFactorForMonitor(HMONITOR hmonitor) {
6+
double GetScaleFactorForMonitor(HMONITOR hmonitor) {
77
if (!hmonitor)
88
return 1.0;
99
typedef HRESULT(WINAPI * GetDpiForMonitorFunc)(HMONITOR, int, UINT*, UINT*);

src/platform/windows/dpi_utils_windows.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ namespace nativeapi {
66
// Returns the DPI scale factor for the given window (1.0 at 96 DPI)
77
double GetScaleFactorForWindow(HWND hwnd);
88

9+
// Returns the DPI scale factor for the given monitor (1.0 at 96 DPI)
10+
double GetScaleFactorForMonitor(HMONITOR hmonitor);
11+
912
} // namespace nativeapi

src/platform/windows/window_windows.cpp

Lines changed: 139 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
#include <dwmapi.h>
22
#include <windows.h>
3+
#include <cmath>
34
#include <iostream>
45
#include "../../foundation/id_allocator.h"
56
#include "../../window.h"
67
#include "../../window_manager.h"
78
#include "../../window_registry.h"
89
#include "dpi_utils_windows.h"
910
#include "string_utils_windows.h"
11+
#include "window_message_dispatcher.h"
1012

1113
#pragma comment(lib, "dwmapi.lib")
1214

@@ -30,6 +32,9 @@ class Window::Impl {
3032
WindowId window_id_;
3133
TitleBarStyle title_bar_style_;
3234
VisualEffect visual_effect_;
35+
Size min_size_{0, 0};
36+
Size max_size_{0, 0};
37+
int min_max_handler_id_ = 0;
3338
};
3439

3540
// Custom window procedure to handle window messages
@@ -184,8 +189,14 @@ Window::Window(void* native_window) {
184189
}
185190

186191
Window::~Window() {
187-
// Remove window from registry on destruction
188192
if (pimpl_ && pimpl_->window_id_ != IdAllocator::kInvalidId) {
193+
// Unregister WM_GETMINMAXINFO handler if registered
194+
if (pimpl_->min_max_handler_id_ != 0 && pimpl_->hwnd_) {
195+
WindowMessageDispatcher::GetInstance().UnregisterHandler(
196+
pimpl_->min_max_handler_id_);
197+
}
198+
199+
// Remove window from registry on destruction
189200
WindowRegistry::GetInstance().Remove(pimpl_->window_id_);
190201

191202
// Remove the custom property from HWND if window is still valid
@@ -336,8 +347,14 @@ bool Window::IsFullScreen() const {
336347

337348
void Window::SetBounds(Rectangle bounds) {
338349
if (pimpl_->hwnd_) {
339-
SetWindowPos(pimpl_->hwnd_, nullptr, static_cast<int>(bounds.x), static_cast<int>(bounds.y),
340-
static_cast<int>(bounds.width), static_cast<int>(bounds.height), SWP_NOZORDER);
350+
double scale = GetScaleFactorForWindow(pimpl_->hwnd_);
351+
if (scale <= 0.0)
352+
scale = 1.0;
353+
SetWindowPos(pimpl_->hwnd_, nullptr,
354+
static_cast<int>(std::lround(bounds.x * scale)),
355+
static_cast<int>(std::lround(bounds.y * scale)),
356+
static_cast<int>(std::lround(bounds.width * scale)),
357+
static_cast<int>(std::lround(bounds.height * scale)), SWP_NOZORDER);
341358
}
342359
}
343360

@@ -346,10 +363,13 @@ Rectangle Window::GetBounds() const {
346363
if (pimpl_->hwnd_) {
347364
RECT rect;
348365
GetWindowRect(pimpl_->hwnd_, &rect);
349-
bounds.x = rect.left;
350-
bounds.y = rect.top;
351-
bounds.width = rect.right - rect.left;
352-
bounds.height = rect.bottom - rect.top;
366+
double scale = GetScaleFactorForWindow(pimpl_->hwnd_);
367+
if (scale <= 0.0)
368+
scale = 1.0;
369+
bounds.x = static_cast<double>(rect.left) / scale;
370+
bounds.y = static_cast<double>(rect.top) / scale;
371+
bounds.width = static_cast<double>(rect.right - rect.left) / scale;
372+
bounds.height = static_cast<double>(rect.bottom - rect.top) / scale;
353373
}
354374
return bounds;
355375
}
@@ -358,8 +378,13 @@ void Window::SetSize(Size size, bool animate) {
358378
if (pimpl_->hwnd_) {
359379
// Windows doesn't have built-in animation for window resizing
360380
// Animation would require custom implementation
361-
SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, static_cast<int>(size.width),
362-
static_cast<int>(size.height), SWP_NOMOVE | SWP_NOZORDER);
381+
double scale = GetScaleFactorForWindow(pimpl_->hwnd_);
382+
if (scale <= 0.0)
383+
scale = 1.0;
384+
SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0,
385+
static_cast<int>(std::lround(size.width * scale)),
386+
static_cast<int>(std::lround(size.height * scale)),
387+
SWP_NOMOVE | SWP_NOZORDER);
363388
}
364389
}
365390

@@ -368,8 +393,11 @@ Size Window::GetSize() const {
368393
if (pimpl_->hwnd_) {
369394
RECT rect;
370395
GetWindowRect(pimpl_->hwnd_, &rect);
371-
size.width = rect.right - rect.left;
372-
size.height = rect.bottom - rect.top;
396+
double scale = GetScaleFactorForWindow(pimpl_->hwnd_);
397+
if (scale <= 0.0)
398+
scale = 1.0;
399+
size.width = static_cast<double>(rect.right - rect.left) / scale;
400+
size.height = static_cast<double>(rect.bottom - rect.top) / scale;
373401
}
374402
return size;
375403
}
@@ -384,8 +412,13 @@ void Window::SetContentSize(Size size) {
384412
int borderWidth = (windowRect.right - windowRect.left) - clientRect.right;
385413
int borderHeight = (windowRect.bottom - windowRect.top) - clientRect.bottom;
386414

387-
SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, static_cast<int>(size.width) + borderWidth,
388-
static_cast<int>(size.height) + borderHeight, SWP_NOMOVE | SWP_NOZORDER);
415+
double scale = GetScaleFactorForWindow(pimpl_->hwnd_);
416+
if (scale <= 0.0)
417+
scale = 1.0;
418+
SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0,
419+
static_cast<int>(std::lround(size.width * scale)) + borderWidth,
420+
static_cast<int>(std::lround(size.height * scale)) + borderHeight,
421+
SWP_NOMOVE | SWP_NOZORDER);
389422
}
390423
}
391424

@@ -394,8 +427,11 @@ Size Window::GetContentSize() const {
394427
if (pimpl_->hwnd_) {
395428
RECT rect;
396429
GetClientRect(pimpl_->hwnd_, &rect);
397-
size.width = rect.right;
398-
size.height = rect.bottom;
430+
double scale = GetScaleFactorForWindow(pimpl_->hwnd_);
431+
if (scale <= 0.0)
432+
scale = 1.0;
433+
size.width = static_cast<double>(rect.right) / scale;
434+
size.height = static_cast<double>(rect.bottom) / scale;
399435
}
400436
return size;
401437
}
@@ -418,11 +454,15 @@ void Window::SetContentBounds(Rectangle bounds) {
418454
int offsetX = clientTopLeft.x - windowRect.left;
419455
int offsetY = clientTopLeft.y - windowRect.top;
420456

457+
double scale = GetScaleFactorForWindow(pimpl_->hwnd_);
458+
if (scale <= 0.0)
459+
scale = 1.0;
460+
421461
// Calculate window position so that client area is at bounds position
422-
int windowX = static_cast<int>(bounds.x) - offsetX;
423-
int windowY = static_cast<int>(bounds.y) - offsetY;
424-
int windowWidth = static_cast<int>(bounds.width) + borderWidth;
425-
int windowHeight = static_cast<int>(bounds.height) + borderHeight;
462+
int windowX = static_cast<int>(std::lround(bounds.x * scale)) - offsetX;
463+
int windowY = static_cast<int>(std::lround(bounds.y * scale)) - offsetY;
464+
int windowWidth = static_cast<int>(std::lround(bounds.width * scale)) + borderWidth;
465+
int windowHeight = static_cast<int>(std::lround(bounds.height * scale)) + borderHeight;
426466

427467
SetWindowPos(pimpl_->hwnd_, nullptr, windowX, windowY, windowWidth, windowHeight, SWP_NOZORDER);
428468
}
@@ -453,25 +493,83 @@ Rectangle Window::GetContentBounds() const {
453493
return bounds;
454494
}
455495

496+
// Helper function: registers a WM_GETMINMAXINFO handler for the given HWND
497+
// via WindowMessageDispatcher if not already registered. Returns the handler ID.
498+
static int RegisterMinMaxInfoHandler(HWND hwnd, int existing_handler_id) {
499+
if (existing_handler_id != 0) {
500+
return existing_handler_id;
501+
}
502+
if (!hwnd || !IsWindow(hwnd)) {
503+
return 0;
504+
}
505+
auto& dispatcher = WindowMessageDispatcher::GetInstance();
506+
return dispatcher.RegisterHandler(
507+
hwnd,
508+
[](HWND hwnd, UINT msg, WPARAM wparam,
509+
LPARAM lparam) -> std::optional<LRESULT> {
510+
if (msg == WM_GETMINMAXINFO) {
511+
HANDLE prop_handle = GetPropW(hwnd, kWindowIdProperty);
512+
if (prop_handle) {
513+
WindowId window_id = static_cast<WindowId>(
514+
reinterpret_cast<uintptr_t>(prop_handle));
515+
if (window_id != IdAllocator::kInvalidId) {
516+
auto window = WindowRegistry::GetInstance().Get(window_id);
517+
if (window) {
518+
auto minSize = window->GetMinimumSize();
519+
auto maxSize = window->GetMaximumSize();
520+
MINMAXINFO* mmi = reinterpret_cast<MINMAXINFO*>(lparam);
521+
double scale_mm = GetScaleFactorForWindow(hwnd);
522+
if (scale_mm <= 0.0)
523+
scale_mm = 1.0;
524+
if (minSize.width > 0 && minSize.height > 0) {
525+
mmi->ptMinTrackSize.x = static_cast<LONG>(std::lround(minSize.width * scale_mm));
526+
mmi->ptMinTrackSize.y = static_cast<LONG>(std::lround(minSize.height * scale_mm));
527+
}
528+
if (maxSize.width > 0 && maxSize.height > 0) {
529+
mmi->ptMaxTrackSize.x = static_cast<LONG>(std::lround(maxSize.width * scale_mm));
530+
mmi->ptMaxTrackSize.y = static_cast<LONG>(std::lround(maxSize.height * scale_mm));
531+
}
532+
return std::make_optional(0);
533+
}
534+
}
535+
}
536+
}
537+
return std::nullopt;
538+
});
539+
}
540+
456541
void Window::SetMinimumSize(Size size) {
457-
// Windows minimum size would be handled in WM_GETMINMAXINFO message
458-
// This is a placeholder implementation
542+
pimpl_->min_size_ = size;
543+
544+
if (pimpl_->hwnd_) {
545+
pimpl_->min_max_handler_id_ =
546+
RegisterMinMaxInfoHandler(pimpl_->hwnd_, pimpl_->min_max_handler_id_);
547+
548+
// Trigger the window to re-evaluate its size constraints
549+
SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, 0, 0,
550+
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
551+
}
459552
}
460553

461554
Size Window::GetMinimumSize() const {
462-
// Return default minimum size
463-
return {0, 0};
555+
return pimpl_->min_size_;
464556
}
465557

466558
void Window::SetMaximumSize(Size size) {
467-
// Windows maximum size would be handled in WM_GETMINMAXINFO message
468-
// This is a placeholder implementation
559+
pimpl_->max_size_ = size;
560+
561+
if (pimpl_->hwnd_) {
562+
pimpl_->min_max_handler_id_ =
563+
RegisterMinMaxInfoHandler(pimpl_->hwnd_, pimpl_->min_max_handler_id_);
564+
565+
// Trigger the window to re-evaluate its size constraints
566+
SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, 0, 0,
567+
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
568+
}
469569
}
470570

471571
Size Window::GetMaximumSize() const {
472-
// Return default maximum size (screen size)
473-
return {static_cast<double>(GetSystemMetrics(SM_CXSCREEN)),
474-
static_cast<double>(GetSystemMetrics(SM_CYSCREEN))};
572+
return pimpl_->max_size_;
475573
}
476574

477575
void Window::SetResizable(bool is_resizable) {
@@ -603,8 +701,13 @@ bool Window::IsAlwaysOnTop() const {
603701

604702
void Window::SetPosition(Point point) {
605703
if (pimpl_->hwnd_) {
606-
SetWindowPos(pimpl_->hwnd_, nullptr, static_cast<int>(point.x), static_cast<int>(point.y), 0, 0,
607-
SWP_NOSIZE | SWP_NOZORDER);
704+
double scale = GetScaleFactorForWindow(pimpl_->hwnd_);
705+
if (scale <= 0.0)
706+
scale = 1.0;
707+
SetWindowPos(pimpl_->hwnd_, nullptr,
708+
static_cast<int>(std::lround(point.x * scale)),
709+
static_cast<int>(std::lround(point.y * scale)),
710+
0, 0, SWP_NOSIZE | SWP_NOZORDER);
608711
}
609712
}
610713

@@ -613,8 +716,11 @@ Point Window::GetPosition() const {
613716
if (pimpl_->hwnd_) {
614717
RECT rect;
615718
GetWindowRect(pimpl_->hwnd_, &rect);
616-
point.x = rect.left;
617-
point.y = rect.top;
719+
double scale = GetScaleFactorForWindow(pimpl_->hwnd_);
720+
if (scale <= 0.0)
721+
scale = 1.0;
722+
point.x = static_cast<double>(rect.left) / scale;
723+
point.y = static_cast<double>(rect.top) / scale;
618724
}
619725
return point;
620726
}
@@ -635,6 +741,7 @@ void Window::Center() {
635741
GetMonitorInfo(monitor, &mi);
636742

637743
// Calculate the center position on the monitor's work area
744+
// All values here are in physical pixels (GetWindowRect and rcWork), so no DPI scaling needed
638745
int centerX = mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - windowWidth) / 2;
639746
int centerY = mi.rcWork.top + (mi.rcWork.bottom - mi.rcWork.top - windowHeight) / 2;
640747

0 commit comments

Comments
 (0)