win: snap layout refactor

This commit is contained in:
Yuhang Zhao 2023-08-21 15:07:07 +08:00
parent 469c686ade
commit 644266923b
2 changed files with 213 additions and 148 deletions

View File

@ -122,6 +122,30 @@
# define ABM_GETAUTOHIDEBAREX (0x0000000b) # define ABM_GETAUTOHIDEBAREX (0x0000000b)
#endif #endif
#ifndef MAKEWORD
# define MAKEWORD(a, b) ((WORD)(((BYTE)(((DWORD_PTR)(a)) & 0xff)) | ((WORD)((BYTE)(((DWORD_PTR)(b)) & 0xff))) << 8))
#endif
#ifndef MAKELONG
# define MAKELONG(a, b) ((LONG)(((WORD)(((DWORD_PTR)(a)) & 0xffff)) | ((DWORD)((WORD)(((DWORD_PTR)(b)) & 0xffff))) << 16))
#endif
#ifndef LOWORD
# define LOWORD(l) ((WORD)(((DWORD_PTR)(l)) & 0xffff))
#endif
#ifndef HIWORD
# define HIWORD(l) ((WORD)((((DWORD_PTR)(l)) >> 16) & 0xffff))
#endif
#ifndef LOBYTE
# define LOBYTE(w) ((BYTE)(((DWORD_PTR)(w)) & 0xff))
#endif
#ifndef HIBYTE
# define HIBYTE(w) ((BYTE)((((DWORD_PTR)(w)) >> 8) & 0xff))
#endif
#ifndef GET_X_LPARAM #ifndef GET_X_LPARAM
# define GET_X_LPARAM(lp) (static_cast<int>(static_cast<short>(LOWORD(lp)))) # define GET_X_LPARAM(lp) (static_cast<int>(static_cast<short>(LOWORD(lp))))
#endif #endif
@ -130,6 +154,42 @@
# define GET_Y_LPARAM(lp) (static_cast<int>(static_cast<short>(HIWORD(lp)))) # define GET_Y_LPARAM(lp) (static_cast<int>(static_cast<short>(HIWORD(lp))))
#endif #endif
#ifndef GET_KEYSTATE_WPARAM
# define GET_KEYSTATE_WPARAM(wParam) (LOWORD(wParam))
#endif
#ifndef GET_NCHITTEST_WPARAM
# define GET_NCHITTEST_WPARAM(wParam) (static_cast<short>(LOWORD(wParam)))
#endif
#ifndef GET_XBUTTON_WPARAM
# define GET_XBUTTON_WPARAM(wParam) (HIWORD(wParam))
#endif
#ifndef POINTSTOPOINT
# define POINTSTOPOINT(pt, pts) \
{ \
(pt).x = static_cast<LONG>(static_cast<SHORT>(LOWORD(*(LONG*)&pts))); \
(pt).y = static_cast<LONG>(static_cast<SHORT>(HIWORD(*(LONG*)&pts))); \
}
#endif
#ifndef POINTTOPOINTS
# define POINTTOPOINTS(pt) (MAKELONG(static_cast<short>((pt).x), static_cast<short>((pt).y)))
#endif
#ifndef MAKEWPARAM
# define MAKEWPARAM(l, h) (static_cast<WPARAM>(static_cast<DWORD>(MAKELONG(l, h))))
#endif
#ifndef MAKELPARAM
# define MAKELPARAM(l, h) (static_cast<LPARAM>(static_cast<DWORD>(MAKELONG(l, h))))
#endif
#ifndef MAKELRESULT
# define MAKELRESULT(l, h) (static_cast<LRESULT>(static_cast<DWORD>(MAKELONG(l, h))))
#endif
#ifndef IsMinimized #ifndef IsMinimized
# define IsMinimized(hwnd) (::IsIconic(hwnd) != FALSE) # define IsMinimized(hwnd) (::IsIconic(hwnd) != FALSE)
#endif #endif

View File

@ -288,6 +288,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
}; };
#endif // (QT_VERSION < QT_VERSION_CHECK(6, 5, 1)) #endif // (QT_VERSION < QT_VERSION_CHECK(6, 5, 1))
#if 0
const auto releaseButtons = [&data](const std::optional<SystemButtonType> exclude) -> void { const auto releaseButtons = [&data](const std::optional<SystemButtonType> exclude) -> void {
static constexpr const auto defaultButtonState = ButtonState::Normal; static constexpr const auto defaultButtonState = ButtonState::Normal;
const SystemButtonType button = exclude.value_or(SystemButtonType::Unknown); const SystemButtonType button = exclude.value_or(SystemButtonType::Unknown);
@ -322,6 +323,88 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
releaseButtons(button); releaseButtons(button);
data.params.setSystemButtonState(button, ButtonState::Released); data.params.setSystemButtonState(button, ButtonState::Released);
}; };
#else
const auto emulateClientAreaMessage = [hWnd, uMsg, wParam, lParam]() -> void {
const auto wparam = [uMsg, wParam]() -> WPARAM {
if (uMsg == WM_NCMOUSELEAVE) {
return 0;
}
const quint64 keyState = Utils::getMouseButtonsAndModifiers(false);
if ((uMsg >= WM_NCXBUTTONDOWN) && (uMsg <= WM_NCXBUTTONDBLCLK)) {
const auto xButtonMask = GET_XBUTTON_WPARAM(wParam);
return MAKEWPARAM(keyState, xButtonMask);
}
return keyState;
}();
const auto lparam = [uMsg, lParam, hWnd]() -> LPARAM {
if (uMsg == WM_NCMOUSELEAVE) {
return 0;
}
const auto screenPos = POINT{ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
POINT clientPos = screenPos;
if (::ScreenToClient(hWnd, &clientPos) == FALSE) {
WARNING << Utils::getSystemErrorMessage(kScreenToClient);
return 0;
}
return MAKELPARAM(clientPos.x, clientPos.y);
}();
switch (uMsg) {
case WM_NCMOUSEMOVE:
::SendMessageW(hWnd, WM_MOUSEMOVE, wparam, lparam);
break;
case WM_NCLBUTTONDOWN:
::SendMessageW(hWnd, WM_LBUTTONDOWN, wparam, lparam);
break;
case WM_NCLBUTTONUP:
::SendMessageW(hWnd, WM_LBUTTONUP, wparam, lparam);
break;
case WM_NCLBUTTONDBLCLK:
::SendMessageW(hWnd, WM_LBUTTONDBLCLK, wparam, lparam);
break;
case WM_NCRBUTTONDOWN:
::SendMessageW(hWnd, WM_RBUTTONDOWN, wparam, lparam);
break;
case WM_NCRBUTTONUP:
::SendMessageW(hWnd, WM_RBUTTONUP, wparam, lparam);
break;
case WM_NCRBUTTONDBLCLK:
::SendMessageW(hWnd, WM_RBUTTONDBLCLK, wparam, lparam);
break;
case WM_NCMBUTTONDOWN:
::SendMessageW(hWnd, WM_MBUTTONDOWN, wparam, lparam);
break;
case WM_NCMBUTTONUP:
::SendMessageW(hWnd, WM_MBUTTONUP, wparam, lparam);
break;
case WM_NCMBUTTONDBLCLK:
::SendMessageW(hWnd, WM_MBUTTONDBLCLK, wparam, lparam);
break;
case WM_NCXBUTTONDOWN:
::SendMessageW(hWnd, WM_XBUTTONDOWN, wparam, lparam);
break;
case WM_NCXBUTTONUP:
::SendMessageW(hWnd, WM_XBUTTONUP, wparam, lparam);
break;
case WM_NCXBUTTONDBLCLK:
::SendMessageW(hWnd, WM_XBUTTONDBLCLK, wparam, lparam);
break;
#if 0
case WM_NCPOINTERUPDATE:
case WM_NCPOINTERDOWN:
case WM_NCPOINTERUP:
break;
#endif
case WM_NCMOUSEHOVER:
::SendMessageW(hWnd, WM_MOUSEHOVER, wparam, lparam);
break;
case WM_NCMOUSELEAVE:
::SendMessageW(hWnd, WM_MOUSELEAVE, wparam, lparam);
break;
default:
Q_UNREACHABLE();
}
};
#endif
switch (uMsg) { switch (uMsg) {
#if (QT_VERSION < QT_VERSION_CHECK(5, 9, 0)) // Qt has done this for us since 5.9.0 #if (QT_VERSION < QT_VERSION_CHECK(5, 9, 0)) // Qt has done this for us since 5.9.0
@ -434,9 +517,9 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
// and that's also how most applications customize their title bars on Windows. It's // and that's also how most applications customize their title bars on Windows. It's
// totally OK but since we want to preserve as much original frame as possible, we // totally OK but since we want to preserve as much original frame as possible, we
// can't use that solution. // can't use that solution.
const LRESULT ret = ::DefWindowProcW(hWnd, WM_NCCALCSIZE, wParam, lParam); const LRESULT hitTestResult = ::DefWindowProcW(hWnd, WM_NCCALCSIZE, wParam, lParam);
if (ret != 0) { if (hitTestResult != FALSE) {
*result = ret; *result = hitTestResult;
return true; return true;
} }
// Re-apply the original top from before the size of the default frame was applied, // Re-apply the original top from before the size of the default frame was applied,
@ -574,7 +657,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
// from Windows 7 to Windows 10. Not tested on Windows 11 yet. Don't know // from Windows 7 to Windows 10. Not tested on Windows 11 yet. Don't know
// whether it exists on Windows XP to Windows Vista or not. // whether it exists on Windows XP to Windows Vista or not.
static const bool needD3DWorkaround = (qEnvironmentVariableIntValue("FRAMELESSHELPER_USE_D3D_WORKAROUND") != 0); static const bool needD3DWorkaround = (qEnvironmentVariableIntValue("FRAMELESSHELPER_USE_D3D_WORKAROUND") != 0);
*result = (((static_cast<BOOL>(wParam) == FALSE) || needD3DWorkaround) ? 0 : WVR_REDRAW); *result = (((static_cast<BOOL>(wParam) == FALSE) || needD3DWorkaround) ? FALSE : WVR_REDRAW);
return true; return true;
} }
case WM_NCHITTEST: { case WM_NCHITTEST: {
@ -719,9 +802,9 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
if (frameBorderVisible) { if (frameBorderVisible) {
// This will handle the left, right and bottom parts of the frame // This will handle the left, right and bottom parts of the frame
// because we didn't change them. // because we didn't change them.
const LRESULT originalRet = ::DefWindowProcW(hWnd, WM_NCHITTEST, 0, lParam); const LRESULT originalHitTestResult = ::DefWindowProcW(hWnd, WM_NCHITTEST, 0, lParam);
if (originalRet != HTCLIENT) { if (originalHitTestResult != HTCLIENT) {
*result = ((isFixedSize || dontOverrideCursor) ? HTBORDER : originalRet); *result = ((isFixedSize || dontOverrideCursor) ? HTBORDER : originalHitTestResult);
return true; return true;
} }
if (full) { if (full) {
@ -822,146 +905,68 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
return true; return true;
} }
} }
case WM_NCMOUSEMOVE: { case WM_NCMOUSEMOVE:
// When we get this message, it's because the mouse moved when it was case WM_NCLBUTTONDOWN:
// over somewhere we said was the non-client area. case WM_NCLBUTTONUP:
// case WM_NCLBUTTONDBLCLK:
// We'll use this to communicate state to the title bar control, so that case WM_NCRBUTTONDOWN:
// it can update its visuals. case WM_NCRBUTTONUP:
// - If we're over a button, hover it. case WM_NCRBUTTONDBLCLK:
// - If we're over _anything else_, stop hovering the buttons. case WM_NCMBUTTONDOWN:
bool insideChromeButton = false; case WM_NCMBUTTONUP:
switch (wParam) { case WM_NCMBUTTONDBLCLK:
case HTSYSMENU: { case WM_NCXBUTTONDOWN:
insideChromeButton = true; case WM_NCXBUTTONUP:
hoverButton(SystemButtonType::WindowIcon); case WM_NCXBUTTONDBLCLK:
} break; #if 0
case HTHELP: { case WM_NCPOINTERUPDATE:
insideChromeButton = true; case WM_NCPOINTERDOWN:
hoverButton(SystemButtonType::Help); case WM_NCPOINTERUP:
} break; #endif
case HTREDUCE: { case WM_NCMOUSEHOVER:
insideChromeButton = true; case WM_NCMOUSELEAVE: {
hoverButton(SystemButtonType::Minimize); if (uMsg == WM_NCMOUSELEAVE) {
} break; muData.trackingMouse = false;
case HTZOOM: { emulateClientAreaMessage();
insideChromeButton = true; //*result = FALSE;
hoverButton(SystemButtonType::Maximize); //return true;
} break; } else {
case HTCLOSE: { if ((uMsg == WM_NCMOUSEMOVE) && !data.trackingMouse) {
insideChromeButton = true; TRACKMOUSEEVENT tme;
hoverButton(SystemButtonType::Close); SecureZeroMemory(&tme, sizeof(tme));
} break; tme.cbSize = sizeof(tme);
default: // TME_NONCLIENT is absolutely critical here. In my experimentation,
// We are not hovering any of the chrome buttons, so dismiss the hover state of all buttons. // we'd get WM_MOUSELEAVE messages after just a HOVER_DEFAULT
releaseButtons(std::nullopt); // timeout even though we're not requesting TME_HOVER, which kinda
break; // ruined the whole point of this.
} tme.dwFlags = (TME_LEAVE | TME_NONCLIENT);
// If we haven't previously asked for mouse tracking, request mouse tme.hwndTrack = hWnd;
// tracking. We need to do this so we can get the WM_NCMOUSELEAVE tme.dwHoverTime = HOVER_DEFAULT; // We don't _really_ care about this.
// message when the mouse leave the title bar. Otherwise, we won't always if (::TrackMouseEvent(&tme) == FALSE) {
// get that message (especially if the user moves the mouse _real WARNING << Utils::getSystemErrorMessage(kTrackMouseEvent);
// fast_). } else {
if (insideChromeButton && !data.trackingMouse) { muData.trackingMouse = true;
TRACKMOUSEEVENT tme; }
SecureZeroMemory(&tme, sizeof(tme)); }
tme.cbSize = sizeof(tme); const bool isXButtonMessage = ((uMsg >= WM_NCXBUTTONDOWN) && (uMsg <= WM_NCXBUTTONDBLCLK));
// TME_NONCLIENT is absolutely critical here. In my experimentation, const auto hitTestResult = (isXButtonMessage ? GET_NCHITTEST_WPARAM(wParam) : wParam);
// we'd get WM_MOUSELEAVE messages after just a HOVER_DEFAULT switch (hitTestResult) {
// timeout even though we're not requesting TME_HOVER, which kinda case HTSYSMENU:
// ruined the whole point of this. case HTHELP:
tme.dwFlags = (TME_LEAVE | TME_NONCLIENT); case HTREDUCE:
tme.hwndTrack = hWnd; case HTZOOM:
tme.dwHoverTime = HOVER_DEFAULT; // We don't _really_ care about this. case HTCLOSE: {
if (::TrackMouseEvent(&tme) == FALSE) { emulateClientAreaMessage();
WARNING << Utils::getSystemErrorMessage(kTrackMouseEvent); //if ((uMsg != WM_NCMOUSEMOVE) && (uMsg != WM_NCMOUSEHOVER)) {
*result = (isXButtonMessage ? TRUE : FALSE);
return true;
//}
}
default:
break; break;
} }
muData.trackingMouse = true;
} }
} break; } break;
case WM_NCMOUSELEAVE:
case WM_MOUSELEAVE: {
// When the mouse leaves our interested area, make sure to dismiss any hover.
releaseButtons(std::nullopt);
muData.trackingMouse = false;
} break;
case WM_NCLBUTTONDOWN:
case WM_NCLBUTTONDBLCLK: {
// Manually handling mouse presses for our own chrome buttons. If it's in a caption
// button, then tell Qt to "press" that button, which should change its visual state.
bool insideChromeButton = false;
// The buttons won't work as you'd expect, we need to handle those ourselves.
switch (wParam) {
case HTSYSMENU: {
insideChromeButton = true;
pressButton(SystemButtonType::WindowIcon);
} break;
case HTHELP: {
insideChromeButton = true;
pressButton(SystemButtonType::Help);
} break;
case HTREDUCE: {
insideChromeButton = true;
pressButton(SystemButtonType::Minimize);
} break;
case HTZOOM: {
insideChromeButton = true;
pressButton(SystemButtonType::Maximize);
} break;
case HTCLOSE: {
insideChromeButton = true;
pressButton(SystemButtonType::Close);
} break;
default:
releaseButtons(std::nullopt);
break;
}
if (insideChromeButton) {
// We have handled this message already, so let Windows discard this message.
*result = 0;
return true;
}
// Not inside of any chrome buttons, just let Windows handle this message.
} break;
case WM_NCLBUTTONUP: {
// Manually handling mouse releases for our own chrome buttons. If it's in a caption
// button, then tell Qt to release that button, which should change its visual state
// and also generate a click event.
bool insideChromeButton = false;
// The buttons won't work as you'd expect, we need to handle those ourselves.
switch (wParam) {
case HTSYSMENU: {
insideChromeButton = true;
clickButton(SystemButtonType::WindowIcon);
} break;
case HTHELP: {
insideChromeButton = true;
clickButton(SystemButtonType::Help);
} break;
case HTREDUCE: {
insideChromeButton = true;
clickButton(SystemButtonType::Minimize);
} break;
case HTZOOM: {
insideChromeButton = true;
clickButton(SystemButtonType::Maximize);
} break;
case HTCLOSE: {
insideChromeButton = true;
clickButton(SystemButtonType::Close);
} break;
default:
releaseButtons(std::nullopt);
break;
}
if (insideChromeButton) {
// We have handled this message already, so tell Windows to discard this message.
*result = 0;
return true;
}
// Not inside of any chrome buttons, just let Windows handle this message.
} break;
#if (QT_VERSION < QT_VERSION_CHECK(6, 2, 2)) // I contributed this small technique to upstream Qt since 6.2.2 #if (QT_VERSION < QT_VERSION_CHECK(6, 2, 2)) // I contributed this small technique to upstream Qt since 6.2.2
case WM_WINDOWPOSCHANGING: { case WM_WINDOWPOSCHANGING: {
// Tell Windows to discard the entire contents of the client area, as re-using // Tell Windows to discard the entire contents of the client area, as re-using
@ -1073,7 +1078,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
// our request). // our request).
if ((filteredWParam == SC_SCREENSAVE) || (filteredWParam == SC_MONITORPOWER)) { if ((filteredWParam == SC_SCREENSAVE) || (filteredWParam == SC_MONITORPOWER)) {
if (Utils::isFullScreen(windowId)) { if (Utils::isFullScreen(windowId)) {
*result = 0; *result = FALSE;
return true; return true;
} }
} }
@ -1089,7 +1094,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
// These undocumented messages are sent to draw themed window // These undocumented messages are sent to draw themed window
// borders. Block them to prevent drawing borders over the client // borders. Block them to prevent drawing borders over the client
// area. // area.
*result = 0; *result = FALSE;
return true; return true;
} }
case WM_NCPAINT: { case WM_NCPAINT: {
@ -1099,7 +1104,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
// Only block WM_NCPAINT when DWM composition is disabled. If // Only block WM_NCPAINT when DWM composition is disabled. If
// it's blocked when DWM composition is enabled, the frame // it's blocked when DWM composition is enabled, the frame
// shadow won't be drawn. // shadow won't be drawn.
*result = 0; *result = FALSE;
return true; return true;
} else { } else {
break; break;
@ -1141,14 +1146,14 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
break; break;
} }
std::ignore = Utils::triggerFrameChange(windowId); std::ignore = Utils::triggerFrameChange(windowId);
const LRESULT ret = ::DefWindowProcW(hWnd, uMsg, wParam, lParam); const LRESULT originalResult = ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
::SetLastError(ERROR_SUCCESS); ::SetLastError(ERROR_SUCCESS);
if (::SetWindowLongPtrW(hWnd, GWL_STYLE, static_cast<LONG_PTR>(oldStyle)) == 0) { if (::SetWindowLongPtrW(hWnd, GWL_STYLE, static_cast<LONG_PTR>(oldStyle)) == 0) {
WARNING << Utils::getSystemErrorMessage(kSetWindowLongPtrW); WARNING << Utils::getSystemErrorMessage(kSetWindowLongPtrW);
break; break;
} }
std::ignore = Utils::triggerFrameChange(windowId); std::ignore = Utils::triggerFrameChange(windowId);
*result = ret; *result = originalResult;
return true; return true;
} }
default: default: