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)
#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
# define GET_X_LPARAM(lp) (static_cast<int>(static_cast<short>(LOWORD(lp))))
#endif
@ -130,6 +154,42 @@
# define GET_Y_LPARAM(lp) (static_cast<int>(static_cast<short>(HIWORD(lp))))
#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
# define IsMinimized(hwnd) (::IsIconic(hwnd) != FALSE)
#endif

View File

@ -288,6 +288,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
};
#endif // (QT_VERSION < QT_VERSION_CHECK(6, 5, 1))
#if 0
const auto releaseButtons = [&data](const std::optional<SystemButtonType> exclude) -> void {
static constexpr const auto defaultButtonState = ButtonState::Normal;
const SystemButtonType button = exclude.value_or(SystemButtonType::Unknown);
@ -322,6 +323,88 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
releaseButtons(button);
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) {
#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
// totally OK but since we want to preserve as much original frame as possible, we
// can't use that solution.
const LRESULT ret = ::DefWindowProcW(hWnd, WM_NCCALCSIZE, wParam, lParam);
if (ret != 0) {
*result = ret;
const LRESULT hitTestResult = ::DefWindowProcW(hWnd, WM_NCCALCSIZE, wParam, lParam);
if (hitTestResult != FALSE) {
*result = hitTestResult;
return true;
}
// 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
// whether it exists on Windows XP to Windows Vista or not.
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;
}
case WM_NCHITTEST: {
@ -719,9 +802,9 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
if (frameBorderVisible) {
// This will handle the left, right and bottom parts of the frame
// because we didn't change them.
const LRESULT originalRet = ::DefWindowProcW(hWnd, WM_NCHITTEST, 0, lParam);
if (originalRet != HTCLIENT) {
*result = ((isFixedSize || dontOverrideCursor) ? HTBORDER : originalRet);
const LRESULT originalHitTestResult = ::DefWindowProcW(hWnd, WM_NCHITTEST, 0, lParam);
if (originalHitTestResult != HTCLIENT) {
*result = ((isFixedSize || dontOverrideCursor) ? HTBORDER : originalHitTestResult);
return true;
}
if (full) {
@ -822,47 +905,33 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
return true;
}
}
case WM_NCMOUSEMOVE: {
// When we get this message, it's because the mouse moved when it was
// over somewhere we said was the non-client area.
//
// We'll use this to communicate state to the title bar control, so that
// it can update its visuals.
// - If we're over a button, hover it.
// - If we're over _anything else_, stop hovering the buttons.
bool insideChromeButton = false;
switch (wParam) {
case HTSYSMENU: {
insideChromeButton = true;
hoverButton(SystemButtonType::WindowIcon);
} break;
case HTHELP: {
insideChromeButton = true;
hoverButton(SystemButtonType::Help);
} break;
case HTREDUCE: {
insideChromeButton = true;
hoverButton(SystemButtonType::Minimize);
} break;
case HTZOOM: {
insideChromeButton = true;
hoverButton(SystemButtonType::Maximize);
} break;
case HTCLOSE: {
insideChromeButton = true;
hoverButton(SystemButtonType::Close);
} break;
default:
// We are not hovering any of the chrome buttons, so dismiss the hover state of all buttons.
releaseButtons(std::nullopt);
break;
}
// If we haven't previously asked for mouse tracking, request mouse
// tracking. We need to do this so we can get the WM_NCMOUSELEAVE
// message when the mouse leave the title bar. Otherwise, we won't always
// get that message (especially if the user moves the mouse _real
// fast_).
if (insideChromeButton && !data.trackingMouse) {
case WM_NCMOUSEMOVE:
case WM_NCLBUTTONDOWN:
case WM_NCLBUTTONUP:
case WM_NCLBUTTONDBLCLK:
case WM_NCRBUTTONDOWN:
case WM_NCRBUTTONUP:
case WM_NCRBUTTONDBLCLK:
case WM_NCMBUTTONDOWN:
case WM_NCMBUTTONUP:
case WM_NCMBUTTONDBLCLK:
case WM_NCXBUTTONDOWN:
case WM_NCXBUTTONUP:
case WM_NCXBUTTONDBLCLK:
#if 0
case WM_NCPOINTERUPDATE:
case WM_NCPOINTERDOWN:
case WM_NCPOINTERUP:
#endif
case WM_NCMOUSEHOVER:
case WM_NCMOUSELEAVE: {
if (uMsg == WM_NCMOUSELEAVE) {
muData.trackingMouse = false;
emulateClientAreaMessage();
//*result = FALSE;
//return true;
} else {
if ((uMsg == WM_NCMOUSEMOVE) && !data.trackingMouse) {
TRACKMOUSEEVENT tme;
SecureZeroMemory(&tme, sizeof(tme));
tme.cbSize = sizeof(tme);
@ -875,92 +944,28 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
tme.dwHoverTime = HOVER_DEFAULT; // We don't _really_ care about this.
if (::TrackMouseEvent(&tme) == FALSE) {
WARNING << Utils::getSystemErrorMessage(kTrackMouseEvent);
break;
}
} else {
muData.trackingMouse = true;
}
} 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;
}
const bool isXButtonMessage = ((uMsg >= WM_NCXBUTTONDOWN) && (uMsg <= WM_NCXBUTTONDBLCLK));
const auto hitTestResult = (isXButtonMessage ? GET_NCHITTEST_WPARAM(wParam) : wParam);
switch (hitTestResult) {
case HTSYSMENU:
case HTHELP:
case HTREDUCE:
case HTZOOM:
case HTCLOSE: {
insideChromeButton = true;
pressButton(SystemButtonType::Close);
} break;
emulateClientAreaMessage();
//if ((uMsg != WM_NCMOUSEMOVE) && (uMsg != WM_NCMOUSEHOVER)) {
*result = (isXButtonMessage ? TRUE : FALSE);
return true;
//}
}
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
case WM_WINDOWPOSCHANGING: {
@ -1073,7 +1078,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
// our request).
if ((filteredWParam == SC_SCREENSAVE) || (filteredWParam == SC_MONITORPOWER)) {
if (Utils::isFullScreen(windowId)) {
*result = 0;
*result = FALSE;
return true;
}
}
@ -1089,7 +1094,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
// These undocumented messages are sent to draw themed window
// borders. Block them to prevent drawing borders over the client
// area.
*result = 0;
*result = FALSE;
return true;
}
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
// it's blocked when DWM composition is enabled, the frame
// shadow won't be drawn.
*result = 0;
*result = FALSE;
return true;
} else {
break;
@ -1141,14 +1146,14 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
break;
}
std::ignore = Utils::triggerFrameChange(windowId);
const LRESULT ret = ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
const LRESULT originalResult = ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
::SetLastError(ERROR_SUCCESS);
if (::SetWindowLongPtrW(hWnd, GWL_STYLE, static_cast<LONG_PTR>(oldStyle)) == 0) {
WARNING << Utils::getSystemErrorMessage(kSetWindowLongPtrW);
break;
}
std::ignore = Utils::triggerFrameChange(windowId);
*result = ret;
*result = originalResult;
return true;
}
default: