From 0ec0170dc492c4157835aa6b8731d374c878ecfd Mon Sep 17 00:00:00 2001 From: Mikel Calvo Date: Fri, 17 Apr 2026 05:24:05 +0200 Subject: [PATCH] fix: integrate runtime window resize support and restore maximize button Integrate the remaining client source changes required for real-time window resizing and restore proper maximize/restore behavior in windowed mode. Based on the runtime resize approach shared at: https://metin2.dev/topic/34337-game-window-resize-in-real-time This commit adds resize propagation across the graphics device, application/window handlers, UI window hierarchy, and Python-facing bindings. This fixes: - render/output staying anchored to the top-left after resize - black unused area after expanding the window - cursor/UI hit-testing desync after resize - maximize button doing nothing in windowed resize mode --- src/EterLib/GrpDevice.cpp | 9 ++++++ src/EterLib/GrpDevice.h | 3 ++ src/EterPythonLib/PythonWindow.cpp | 23 ++++++++++++++ src/EterPythonLib/PythonWindow.h | 8 +++++ src/EterPythonLib/PythonWindowManager.cpp | 8 +++++ src/EterPythonLib/PythonWindowManager.h | 3 ++ src/UserInterface/Locale_inc.h | 2 ++ src/UserInterface/PythonApplication.cpp | 14 +++++++++ src/UserInterface/PythonApplication.h | 6 ++++ src/UserInterface/PythonApplicationEvent.cpp | 19 +++++++++++- src/UserInterface/PythonApplicationModule.cpp | 5 ++++ .../PythonApplicationProcedure.cpp | 30 +++++++++++++++++++ src/UserInterface/PythonNetworkStream.cpp | 10 +++++++ src/UserInterface/PythonNetworkStream.h | 3 ++ 14 files changed, 142 insertions(+), 1 deletion(-) diff --git a/src/EterLib/GrpDevice.cpp b/src/EterLib/GrpDevice.cpp index 52c90ec3..8142dea0 100644 --- a/src/EterLib/GrpDevice.cpp +++ b/src/EterLib/GrpDevice.cpp @@ -309,6 +309,15 @@ DWORD GetMaxTextureHeight() return s_MaxTextureHeight; } +#ifdef ENABLE_WINDOW_RESIZE +void CGraphicDevice::SetNewSize(int width, int height) +{ + ms_iWidth = width; + ms_iHeight = height; + + SetViewport(0.0f, 0.0f, (float)width, (float)height, 0, 1); +} +#endif int CGraphicDevice::Create(HWND hWnd, int iHres, int iVres, bool Windowed, int /*iBit*/, int iReflashRate) { int iRet = CREATE_OK; diff --git a/src/EterLib/GrpDevice.h b/src/EterLib/GrpDevice.h index 0b0f739a..0f7c23d1 100644 --- a/src/EterLib/GrpDevice.h +++ b/src/EterLib/GrpDevice.h @@ -38,6 +38,9 @@ class CGraphicDevice : public CGraphicBase void Destroy(); int Create(HWND hWnd, int hres, int vres, bool Windowed = true, int bit = 32, int ReflashRate = 0); +#ifdef ENABLE_WINDOW_RESIZE + void SetNewSize(int width, int height); +#endif EDeviceState GetDeviceState(); bool Reset(); diff --git a/src/EterPythonLib/PythonWindow.cpp b/src/EterPythonLib/PythonWindow.cpp index ea467752..514cf820 100644 --- a/src/EterPythonLib/PythonWindow.cpp +++ b/src/EterPythonLib/PythonWindow.cpp @@ -295,6 +295,29 @@ namespace UI PyCallClassMemberFunc(m_poHandler, "OnRender", BuildEmptyTuple()); } +#ifdef ENABLE_WINDOW_RESIZE + void CWindow::Resize(CWindow* rootWindow) + { + OnResize(); + + m_isUpdatingChildren = TRUE; + + for (auto& Child : m_pChildList) + Child->Resize(rootWindow); + + m_isUpdatingChildren = FALSE; + } + + void CWindow::OnResize() + { + if (!m_poHandler) + return; + + static PyObject* poFuncName_OnUpdate = PyString_InternFromString("OnResize"); + PyCallClassMemberFunc_ByPyString(m_poHandler, poFuncName_OnUpdate, BuildEmptyTuple()); + UpdateRect(); + } +#endif void CWindow::SetName(const char * c_szName) { m_strName = c_szName; diff --git a/src/EterPythonLib/PythonWindow.h b/src/EterPythonLib/PythonWindow.h index 0a545597..02d8f550 100644 --- a/src/EterPythonLib/PythonWindow.h +++ b/src/EterPythonLib/PythonWindow.h @@ -52,6 +52,11 @@ namespace UI void DestroyHandle(); void Update(); void Render(); +#ifdef ENABLE_WINDOW_RESIZE + void Resize(CWindow* rootWindow); +#endif + + void SetName(const char * c_szName); const char * GetName() { return m_strName.c_str(); } @@ -109,6 +114,9 @@ namespace UI virtual void OnRender(); virtual void OnUpdate(); +#ifdef ENABLE_WINDOW_RESIZE + virtual void OnResize(); +#endif virtual void OnChangePosition(){} virtual void OnSetFocus(); diff --git a/src/EterPythonLib/PythonWindowManager.cpp b/src/EterPythonLib/PythonWindowManager.cpp index e26da1a0..b5c1f80a 100644 --- a/src/EterPythonLib/PythonWindowManager.cpp +++ b/src/EterPythonLib/PythonWindowManager.cpp @@ -739,6 +739,14 @@ namespace UI m_pRootWindow->Render(); } +#ifdef ENABLE_WINDOW_RESIZE + void CWindowManager::Resize(int width, int height) + { + SetResolution(width, height); + SetScreenSize(width, height); + m_pRootWindow->Resize(m_pRootWindow); + } +#endif CWindow * CWindowManager::__PickWindow(long x, long y) { if (m_pLockWindow) diff --git a/src/EterPythonLib/PythonWindowManager.h b/src/EterPythonLib/PythonWindowManager.h index 3c386b70..e56162b0 100644 --- a/src/EterPythonLib/PythonWindowManager.h +++ b/src/EterPythonLib/PythonWindowManager.h @@ -112,6 +112,9 @@ namespace UI void Update(); void Render(); +#ifdef ENABLE_WINDOW_RESIZE + void Resize(int width, int height); +#endif void RunMouseMove(long x, long y); void RunMouseLeftButtonDown(long x, long y); diff --git a/src/UserInterface/Locale_inc.h b/src/UserInterface/Locale_inc.h index f171d223..fccbd0ba 100644 --- a/src/UserInterface/Locale_inc.h +++ b/src/UserInterface/Locale_inc.h @@ -4,3 +4,5 @@ #define ENABLE_NEW_EQUIPMENT_SYSTEM #define ENABLE_ATLAS_SCALE //#define ENABLE_DISCORD_RPC + +#define ENABLE_WINDOW_RESIZE diff --git a/src/UserInterface/PythonApplication.cpp b/src/UserInterface/PythonApplication.cpp index 61b8ee42..07552967 100644 --- a/src/UserInterface/PythonApplication.cpp +++ b/src/UserInterface/PythonApplication.cpp @@ -52,6 +52,9 @@ m_IsMovingMainWindow(false) ms_pInstance = this; m_isWindowFullScreenEnable = FALSE; +#ifdef ENABLE_WINDOW_RESIZE + m_isResizing = false; +#endif m_v3CenterPosition = D3DXVECTOR3(0.0f, 0.0f, 0.0f); m_dwStartLocalTime = ELTimer_GetMSec(); @@ -591,6 +594,13 @@ int CPythonApplication::CheckDeviceState() return DEVICE_STATE_OK; } +#ifdef ENABLE_WINDOW_RESIZE +void CPythonApplication::ChangeDeviceSize(int width, int height) +{ + m_grpDevice.SetNewSize(width, height); +} +#endif + bool CPythonApplication::CreateDevice(int width, int height, int Windowed, int bit, int frequency) { int iRet; @@ -776,7 +786,11 @@ bool LoadLocaleData(const char* localePath) unsigned __GetWindowMode(bool windowed) { if (windowed) +#ifdef ENABLE_WINDOW_RESIZE + return WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_TILEDWINDOW; +#else return WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; +#endif return WS_POPUP; } diff --git a/src/UserInterface/PythonApplication.h b/src/UserInterface/PythonApplication.h index d10658fe..66246d86 100644 --- a/src/UserInterface/PythonApplication.h +++ b/src/UserInterface/PythonApplication.h @@ -299,6 +299,9 @@ class CPythonApplication : public CMSApplication, public CInputKeyboard, public void OnIMEKeyDown(int iIndex); int CheckDeviceState(); +#ifdef ENABLE_WINDOW_RESIZE + void ChangeDeviceSize(int width, int height); +#endif BOOL __IsContinuousChangeTypeCursor(int iCursorNum); @@ -432,6 +435,9 @@ class CPythonApplication : public CMSApplication, public CInputKeyboard, public bool m_isMinimizedWnd; bool m_isActivateWnd; BOOL m_isWindowFullScreenEnable; +#ifdef ENABLE_WINDOW_RESIZE + bool m_isResizing; +#endif DWORD m_dwStickyKeysFlag; int m_iForceSightRange; diff --git a/src/UserInterface/PythonApplicationEvent.cpp b/src/UserInterface/PythonApplicationEvent.cpp index 2bbd0e58..a2e4e1fe 100644 --- a/src/UserInterface/PythonApplicationEvent.cpp +++ b/src/UserInterface/PythonApplicationEvent.cpp @@ -25,7 +25,24 @@ void CPythonApplication::OnUIRender() } void CPythonApplication::OnSizeChange(int width, int height) -{ +{ +#ifdef ENABLE_WINDOW_RESIZE + if (width <= 0 || height <= 0) + return; + + if (m_dwWidth != width || m_dwHeight != height) + { + m_dwWidth = width; + m_dwHeight = height; + + SetRect(&m_rect, 0, 0, width, height); + AdjustWindowRectEx(&m_rect, GetWindowStyle(m_hWnd), GetMenu(m_hWnd) != NULL, GetWindowExStyle(m_hWnd)); + + ChangeDeviceSize(width, height); + UI::CWindowManager::Instance().Resize(width, height); + CPythonNetworkStream::Instance().ResizeUI(); + } +#endif } void CPythonApplication::OnMouseMiddleButtonDown(int x, int y) diff --git a/src/UserInterface/PythonApplicationModule.cpp b/src/UserInterface/PythonApplicationModule.cpp index 58ce0e4b..d1df159d 100644 --- a/src/UserInterface/PythonApplicationModule.cpp +++ b/src/UserInterface/PythonApplicationModule.cpp @@ -1460,6 +1460,11 @@ void initapp() PyModule_AddIntConstant(poModule, "CAMERA_TO_POSITIVE", CPythonApplication::CAMERA_TO_POSITIVE); PyModule_AddIntConstant(poModule, "CAMERA_TO_NEGATIVE", CPythonApplication::CAMERA_TO_NEGITIVE); PyModule_AddIntConstant(poModule, "CAMERA_STOP", CPythonApplication::CAMERA_STOP); +#ifdef ENABLE_WINDOW_RESIZE + PyModule_AddIntConstant(poModule, "ENABLE_WINDOW_RESIZE", 1); +#else + PyModule_AddIntConstant(poModule, "ENABLE_WINDOW_RESIZE", 0); +#endif #ifdef ENABLE_COSTUME_SYSTEM PyModule_AddIntConstant(poModule, "ENABLE_COSTUME_SYSTEM", 1); diff --git a/src/UserInterface/PythonApplicationProcedure.cpp b/src/UserInterface/PythonApplicationProcedure.cpp index 9212d358..77e607ef 100644 --- a/src/UserInterface/PythonApplicationProcedure.cpp +++ b/src/UserInterface/PythonApplicationProcedure.cpp @@ -229,10 +229,18 @@ LRESULT CPythonApplication::WindowProcedure(HWND hWnd, UINT uiMsg, WPARAM wParam RECT rcWnd; GetClientRect(&rcWnd); +#ifdef ENABLE_WINDOW_RESIZE + m_isResizing = false; + UINT uWidth = rcWnd.right - rcWnd.left; + UINT uHeight = rcWnd.bottom - rcWnd.top; + m_grpDevice.ResizeBackBuffer(uWidth, uHeight); + OnSizeChange(uWidth, uHeight); +#else UINT uWidth=rcWnd.right-rcWnd.left; UINT uHeight=rcWnd.bottom-rcWnd.left; m_grpDevice.ResizeBackBuffer(uWidth, uHeight); OnSizeChange(short(LOWORD(lParam)), short(HIWORD(lParam))); +#endif } break; case WM_NCLBUTTONDOWN: @@ -240,6 +248,8 @@ LRESULT CPythonApplication::WindowProcedure(HWND hWnd, UINT uiMsg, WPARAM wParam switch (wParam) { case HTMAXBUTTON: + ShowWindow(hWnd, IsZoomed(hWnd) ? SW_RESTORE : SW_MAXIMIZE); + return 0; case HTSYSMENU: return 0; case HTMINBUTTON: @@ -274,6 +284,22 @@ LRESULT CPythonApplication::WindowProcedure(HWND hWnd, UINT uiMsg, WPARAM wParam if (wParam == SC_KEYMENU) return 0; break; +#ifdef ENABLE_WINDOW_RESIZE + case WM_ENTERSIZEMOVE: + m_isResizing = true; + break; + + case WM_GETMINMAXINFO: + { + MINMAXINFO* pMMI = (MINMAXINFO*)lParam; + RECT rcMinClient = { 0, 0, 800, 600 }; + AdjustWindowRectEx(&rcMinClient, GetWindowStyle(m_hWnd), GetMenu(m_hWnd) != NULL, GetWindowExStyle(m_hWnd)); + pMMI->ptMinTrackSize.x = rcMinClient.right - rcMinClient.left; + pMMI->ptMinTrackSize.y = rcMinClient.bottom - rcMinClient.top; + break; + } +#endif + case WM_SYSKEYDOWN: switch (LOWORD(wParam)) { @@ -294,7 +320,11 @@ LRESULT CPythonApplication::WindowProcedure(HWND hWnd, UINT uiMsg, WPARAM wParam break; case WM_SETCURSOR: +#ifdef ENABLE_WINDOW_RESIZE + if (LOWORD(lParam) == HTCLIENT && IsActive()) +#else if (IsActive()) +#endif { if (m_bCursorVisible && CURSOR_MODE_HARDWARE == m_iCursorMode) { diff --git a/src/UserInterface/PythonNetworkStream.cpp b/src/UserInterface/PythonNetworkStream.cpp index 155beef6..64ebcdad 100644 --- a/src/UserInterface/PythonNetworkStream.cpp +++ b/src/UserInterface/PythonNetworkStream.cpp @@ -199,6 +199,16 @@ bool CPythonNetworkStream::IsSelectedEmpire() return false; } +#ifdef ENABLE_WINDOW_RESIZE +void CPythonNetworkStream::ResizeUI() +{ + if ("Game" != m_strPhase) + return; + + PyCallClassMemberFunc(m_apoPhaseWnd[PHASE_WINDOW_GAME], "BINARY_UpdateUI", Py_BuildValue("()")); +} +#endif + UINT CPythonNetworkStream::GetAccountCharacterSlotDatau(UINT iSlot, UINT eType) { if (iSlot >= PLAYER_PER_ACCOUNT4) diff --git a/src/UserInterface/PythonNetworkStream.h b/src/UserInterface/PythonNetworkStream.h index 6c6af6de..0e0a63ff 100644 --- a/src/UserInterface/PythonNetworkStream.h +++ b/src/UserInterface/PythonNetworkStream.h @@ -129,6 +129,9 @@ class CPythonNetworkStream : public CNetworkStream, public CSingleton