diff --git a/os/windows/openttd.manifest b/os/windows/openttd.manifest
index ee1c7ea224..cb536a819d 100644
--- a/os/windows/openttd.manifest
+++ b/os/windows/openttd.manifest
@@ -10,6 +10,7 @@
True/PM
+ PerMonitorV2,PerMonitor
diff --git a/src/gfx.cpp b/src/gfx.cpp
index cb9fb0651b..d96b2bdac4 100644
--- a/src/gfx.cpp
+++ b/src/gfx.cpp
@@ -17,10 +17,12 @@
#include "settings_type.h"
#include "network/network.h"
#include "network/network_func.h"
+#include "window_gui.h"
#include "window_func.h"
#include "newgrf_debug.h"
#include "thread.h"
#include "core/backup_type.hpp"
+#include "viewport_func.h"
#include "table/palettes.h"
#include "table/string_colours.h"
@@ -2042,6 +2044,38 @@ void UpdateGUIZoom()
}
}
+/**
+ * Resolve GUI zoom level and adjust GUI to new zoom, if auto-suggestion is requested.
+ * @returns true when the zoom level has changed, caller must call ReInitAllWindows(true)
+ * after resizing the application's window/buffer.
+ */
+bool AdjustGUIZoom()
+{
+ auto old_zoom = _gui_zoom;
+ UpdateGUIZoom();
+ if (old_zoom == _gui_zoom) return false;
+ GfxClearSpriteCache();
+ VideoDriver::GetInstance()->ClearSystemSprites();
+ ClearFontCache();
+ GfxClearSpriteCache();
+ UpdateAllVirtCoords();
+
+ /* Adjust all window sizes to match the new zoom level, so that they don't appear
+ to move around when the application is moved to a screen with different DPI. */
+ auto zoom_shift = old_zoom - _gui_zoom;
+ for (Window *w : Window::Iterate()) {
+ w->left = AdjustByZoom(w->left, zoom_shift);
+ w->top = AdjustByZoom(w->top, zoom_shift);
+ w->width = AdjustByZoom(w->width, zoom_shift);
+ w->height = AdjustByZoom(w->height, zoom_shift);
+ if (w->viewport != nullptr) {
+ w->viewport->zoom = Clamp(ZoomLevel(w->viewport->zoom - zoom_shift), _settings_client.gui.zoom_min, _settings_client.gui.zoom_max);
+ }
+ }
+
+ return true;
+}
+
void ChangeGameSpeed(bool enable_fast_forward)
{
if (enable_fast_forward) {
diff --git a/src/gfx_func.h b/src/gfx_func.h
index b811c089fe..4ebb85aed2 100644
--- a/src/gfx_func.h
+++ b/src/gfx_func.h
@@ -80,6 +80,7 @@ void DrawMouseCursor();
void ScreenSizeChanged();
void GameSizeChanged();
void UpdateGUIZoom();
+bool AdjustGUIZoom();
void UndrawMouseCursor();
/** Size of the buffer used for drawing strings. */
diff --git a/src/video/cocoa/cocoa_wnd.mm b/src/video/cocoa/cocoa_wnd.mm
index af1bdcab99..36d010177d 100644
--- a/src/video/cocoa/cocoa_wnd.mm
+++ b/src/video/cocoa/cocoa_wnd.mm
@@ -1269,8 +1269,12 @@ void CocoaDialog(const char *title, const char *message, const char *buttonLabel
/** Screen the window is on changed. */
- (void)windowDidChangeBackingProperties:(NSNotification *)notification
{
+ bool did_adjust = AdjustGUIZoom();
+
/* Reallocate screen buffer if necessary. */
driver->AllocateBackingStore();
+
+ if (did_adjust) ReInitAllWindows(true);
}
/** Presentation options to use for full screen mode. */
diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp
index 44368b2249..49c967bfa4 100644
--- a/src/video/win32_v.cpp
+++ b/src/video/win32_v.cpp
@@ -38,6 +38,10 @@
#define PM_QS_INPUT 0x20000
#endif
+#ifndef WM_DPICHANGED
+#define WM_DPICHANGED 0x02E0
+#endif
+
bool _window_maximize;
static Dimension _bck_resolution;
DWORD _imm_props;
@@ -670,6 +674,24 @@ LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
return TRUE;
}
+ case WM_DPICHANGED: {
+ auto did_adjust = AdjustGUIZoom();
+
+ /* Resize the window to match the new DPI setting. */
+ RECT *prcNewWindow = (RECT *)lParam;
+ SetWindowPos(hwnd,
+ NULL,
+ prcNewWindow->left,
+ prcNewWindow->top,
+ prcNewWindow->right - prcNewWindow->left,
+ prcNewWindow->bottom - prcNewWindow->top,
+ SWP_NOZORDER | SWP_NOACTIVATE);
+
+ if (did_adjust) ReInitAllWindows(true);
+
+ return 0;
+ }
+
/* needed for wheel */
#if !defined(WM_MOUSEWHEEL)
# define WM_MOUSEWHEEL 0x020A
diff --git a/src/window.cpp b/src/window.cpp
index 96cc5b3b97..603bca151f 100644
--- a/src/window.cpp
+++ b/src/window.cpp
@@ -3343,6 +3343,13 @@ void HideVitalWindows()
CloseWindowById(WC_STATUS_BAR, 0);
}
+void ReInitWindow(Window *w, bool zoom_changed)
+{
+ if (w == nullptr) return;
+ if (zoom_changed) w->nested_root->AdjustPaddingForZoom();
+ w->ReInit();
+}
+
/** Re-initialize all windows. */
void ReInitAllWindows(bool zoom_changed)
{
@@ -3352,9 +3359,13 @@ void ReInitAllWindows(bool zoom_changed)
extern void InitDepotWindowBlockSizes();
InitDepotWindowBlockSizes();
+ /* When _gui_zoom has changed, we need to resize toolbar and statusbar first,
+ * so EnsureVisibleCaption uses the updated size information. */
+ ReInitWindow(FindWindowById(WC_MAIN_TOOLBAR, 0), zoom_changed);
+ ReInitWindow(FindWindowById(WC_STATUS_BAR, 0), zoom_changed);
for (Window *w : Window::Iterate()) {
- if (zoom_changed) w->nested_root->AdjustPaddingForZoom();
- w->ReInit();
+ if (w->window_class == WC_MAIN_TOOLBAR || w->window_class == WC_STATUS_BAR) continue;
+ ReInitWindow(w, zoom_changed);
}
void NetworkReInitChatBoxSize();
diff --git a/src/zoom_func.h b/src/zoom_func.h
index 485284a06c..0a5113186f 100644
--- a/src/zoom_func.h
+++ b/src/zoom_func.h
@@ -36,6 +36,17 @@ static inline int UnScaleByZoom(int value, ZoomLevel zoom)
return (value + (1 << zoom) - 1) >> zoom;
}
+/**
+ * Adjust by zoom level; zoom < 0 shifts right, zoom >= 0 shifts left
+ * @param value value to shift
+ * @param zoom zoom level to shift to
+ * @return shifted value
+ */
+static inline int AdjustByZoom(int value, int zoom)
+{
+ return zoom < 0 ? UnScaleByZoom(value, ZoomLevel(-zoom)) : ScaleByZoom(value, ZoomLevel(zoom));
+}
+
/**
* Scale by zoom level, usually shift left (when zoom > ZOOM_LVL_NORMAL)
* @param value value to shift