Windows QPA: improve Qt::FramlessWindowHint windows
Currently, Qt::FramelessWindowHint windows lose functionalities that we might want them to keep. This patch attempts to address these by giving the frameless windows the following features: - Border-resizable: it is now possible to resize them by hovering over the edges of the (invisible) border. - Aero snap: While this patch does not enable dragging by clicking on non client areas, shortcuts like Win key + arrow keys let the user snap the windows as expected. - Animations/Shadows: minimizing/maximizing windows now has the typical animations, and the windows also have shadows. Change-Id: Icd7b671a6ac3dc300ba78378897b5dcae2432f76 Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io> Reviewed-by: Oliver Wolff <oliver.wolff@qt.io>
This commit is contained in:
parent
0b155e9ea2
commit
1a6ab689d5
@ -1100,7 +1100,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
case QtWindows::CalculateSize:
|
case QtWindows::CalculateSize:
|
||||||
return QWindowsGeometryHint::handleCalculateSize(d->m_creationContext->customMargins, msg, result);
|
return QWindowsGeometryHint::handleCalculateSize(d->m_creationContext->window, d->m_creationContext->customMargins, msg, result);
|
||||||
case QtWindows::GeometryChangingEvent:
|
case QtWindows::GeometryChangingEvent:
|
||||||
return QWindowsWindow::handleGeometryChangingMessage(&msg, d->m_creationContext->window,
|
return QWindowsWindow::handleGeometryChangingMessage(&msg, d->m_creationContext->window,
|
||||||
d->m_creationContext->margins + d->m_creationContext->customMargins);
|
d->m_creationContext->margins + d->m_creationContext->customMargins);
|
||||||
@ -1165,7 +1165,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
|
|||||||
platformWindow->getSizeHints(reinterpret_cast<MINMAXINFO *>(lParam));
|
platformWindow->getSizeHints(reinterpret_cast<MINMAXINFO *>(lParam));
|
||||||
return true;// maybe available on some SDKs revisit WM_NCCALCSIZE
|
return true;// maybe available on some SDKs revisit WM_NCCALCSIZE
|
||||||
case QtWindows::CalculateSize:
|
case QtWindows::CalculateSize:
|
||||||
return QWindowsGeometryHint::handleCalculateSize(platformWindow->customMargins(), msg, result);
|
return QWindowsGeometryHint::handleCalculateSize(platformWindow->window(), platformWindow->customMargins(), msg, result);
|
||||||
case QtWindows::NonClientHitTest:
|
case QtWindows::NonClientHitTest:
|
||||||
return platformWindow->handleNonClientHitTest(QPoint(msg.pt.x, msg.pt.y), result);
|
return platformWindow->handleNonClientHitTest(QPoint(msg.pt.x, msg.pt.y), result);
|
||||||
case QtWindows::GeometryChangingEvent:
|
case QtWindows::GeometryChangingEvent:
|
||||||
|
@ -772,12 +772,7 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag
|
|||||||
if (popup || (type == Qt::ToolTip) || (type == Qt::SplashScreen)) {
|
if (popup || (type == Qt::ToolTip) || (type == Qt::SplashScreen)) {
|
||||||
style = WS_POPUP;
|
style = WS_POPUP;
|
||||||
} else if (topLevel) {
|
} else if (topLevel) {
|
||||||
if (flags & Qt::FramelessWindowHint)
|
style = WS_OVERLAPPED;
|
||||||
style = WS_POPUP; // no border
|
|
||||||
else if (flags & Qt::WindowTitleHint)
|
|
||||||
style = WS_OVERLAPPED;
|
|
||||||
else
|
|
||||||
style = 0;
|
|
||||||
} else {
|
} else {
|
||||||
style = WS_CHILD;
|
style = WS_CHILD;
|
||||||
}
|
}
|
||||||
@ -786,16 +781,15 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag
|
|||||||
|
|
||||||
if (topLevel) {
|
if (topLevel) {
|
||||||
if ((type == Qt::Window || dialog || tool)) {
|
if ((type == Qt::Window || dialog || tool)) {
|
||||||
if (!(flags & Qt::FramelessWindowHint)) {
|
if (!(flags & Qt::FramelessWindowHint))
|
||||||
style |= WS_POPUP;
|
style |= WS_POPUP;
|
||||||
if (flags & Qt::MSWindowsFixedSizeDialogHint) {
|
if (flags & Qt::MSWindowsFixedSizeDialogHint) {
|
||||||
style |= WS_DLGFRAME;
|
style |= WS_DLGFRAME;
|
||||||
} else {
|
} else {
|
||||||
style |= WS_THICKFRAME;
|
style |= WS_THICKFRAME;
|
||||||
}
|
|
||||||
if (flags & Qt::WindowTitleHint)
|
|
||||||
style |= WS_CAPTION; // Contains WS_DLGFRAME
|
|
||||||
}
|
}
|
||||||
|
if (flags & Qt::WindowTitleHint)
|
||||||
|
style |= WS_CAPTION; // Contains WS_DLGFRAME
|
||||||
if (flags & Qt::WindowSystemMenuHint)
|
if (flags & Qt::WindowSystemMenuHint)
|
||||||
style |= WS_SYSMENU;
|
style |= WS_SYSMENU;
|
||||||
else if (dialog && (flags & Qt::WindowCloseButtonHint) && !(flags & Qt::FramelessWindowHint)) {
|
else if (dialog && (flags & Qt::WindowCloseButtonHint) && !(flags & Qt::FramelessWindowHint)) {
|
||||||
@ -815,8 +809,15 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag
|
|||||||
if ((flags & Qt::WindowContextHelpButtonHint) && !showMinimizeButton
|
if ((flags & Qt::WindowContextHelpButtonHint) && !showMinimizeButton
|
||||||
&& !showMaximizeButton)
|
&& !showMaximizeButton)
|
||||||
exStyle |= WS_EX_CONTEXTHELP;
|
exStyle |= WS_EX_CONTEXTHELP;
|
||||||
|
if (flags & Qt::FramelessWindowHint) {
|
||||||
|
style |= WS_CAPTION; // Needed for aero
|
||||||
|
style |= WS_SYSMENU; // Needed for taskbar shortcuts
|
||||||
|
style |= WS_THICKFRAME;
|
||||||
|
style |= WS_MINIMIZEBOX; // Needed for minimize animations
|
||||||
|
style |= WS_MAXIMIZEBOX; // Needed for maximize animations
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
exStyle |= WS_EX_TOOLWINDOW;
|
exStyle |= WS_EX_TOOLWINDOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make mouse events fall through this window
|
// make mouse events fall through this window
|
||||||
@ -975,9 +976,13 @@ void WindowCreationData::initialize(const QWindow *w, HWND hwnd, bool frameChang
|
|||||||
if (flags & (Qt::CustomizeWindowHint|Qt::WindowTitleHint)) {
|
if (flags & (Qt::CustomizeWindowHint|Qt::WindowTitleHint)) {
|
||||||
HMENU systemMenu = GetSystemMenu(hwnd, FALSE);
|
HMENU systemMenu = GetSystemMenu(hwnd, FALSE);
|
||||||
if (flags & Qt::WindowCloseButtonHint)
|
if (flags & Qt::WindowCloseButtonHint)
|
||||||
EnableMenuItem(systemMenu, SC_CLOSE, MF_BYCOMMAND|MF_ENABLED);
|
EnableMenuItem(systemMenu, SC_CLOSE, MF_BYCOMMAND | MF_ENABLED);
|
||||||
else
|
else
|
||||||
EnableMenuItem(systemMenu, SC_CLOSE, MF_BYCOMMAND|MF_GRAYED);
|
EnableMenuItem(systemMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
|
||||||
|
}
|
||||||
|
if (flags & Qt::FramelessWindowHint) { // Gives us the rounded corners looks and the frame shadow
|
||||||
|
MARGINS margins = { -1, -1, -1, -1 };
|
||||||
|
DwmExtendFrameIntoClientArea(hwnd, &margins);
|
||||||
}
|
}
|
||||||
updateGLWindowSettings(w, hwnd, flags, opacityLevel);
|
updateGLWindowSettings(w, hwnd, flags, opacityLevel);
|
||||||
} else { // child.
|
} else { // child.
|
||||||
@ -1106,8 +1111,29 @@ QMargins QWindowsGeometryHint::frame(const QWindow *w, const QRect &geometry,
|
|||||||
return QWindowsGeometryHint::frame(w, style, exStyle, dpi);
|
return QWindowsGeometryHint::frame(w, style, exStyle, dpi);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QWindowsGeometryHint::handleCalculateSize(const QMargins &customMargins, const MSG &msg, LRESULT *result)
|
bool QWindowsGeometryHint::handleCalculateSize(const QWindow *window, const QMargins &customMargins, const MSG &msg, LRESULT *result)
|
||||||
{
|
{
|
||||||
|
// Return 0 to remove the window's border
|
||||||
|
QWindowsWindow *w = QWindowsWindow::windowsWindowOf(window);
|
||||||
|
bool frameless = w ? w->isFrameless() : window->flags() & Qt::FramelessWindowHint;
|
||||||
|
if (msg.wParam && frameless) {
|
||||||
|
//Query for fullscreen
|
||||||
|
QUERY_USER_NOTIFICATION_STATE quns;
|
||||||
|
SHQueryUserNotificationState(&quns);
|
||||||
|
|
||||||
|
// Prevent content from being cutoff by border for maximized, but not fullscreened windows.
|
||||||
|
if (IsZoomed(msg.hwnd) && quns == QUNS_ACCEPTS_NOTIFICATIONS) {
|
||||||
|
auto *ncp = reinterpret_cast<NCCALCSIZE_PARAMS *>(msg.lParam);
|
||||||
|
RECT *clientArea = &ncp->rgrc[0];
|
||||||
|
const int border = getResizeBorderThickness(QWindowsWindow::windowsWindowOf(window)->savedDpi());
|
||||||
|
clientArea->top += border;
|
||||||
|
clientArea->bottom -= border;
|
||||||
|
clientArea->left += border;
|
||||||
|
clientArea->right -= border;
|
||||||
|
}
|
||||||
|
*result = msg.wParam ? WVR_REDRAW : 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// NCCALCSIZE_PARAMS structure if wParam==TRUE
|
// NCCALCSIZE_PARAMS structure if wParam==TRUE
|
||||||
if (!msg.wParam || customMargins.isNull())
|
if (!msg.wParam || customMargins.isNull())
|
||||||
return false;
|
return false;
|
||||||
@ -2177,13 +2203,11 @@ void QWindowsWindow::handleMoved()
|
|||||||
|
|
||||||
void QWindowsWindow::handleResized(int wParam, LPARAM lParam)
|
void QWindowsWindow::handleResized(int wParam, LPARAM lParam)
|
||||||
{
|
{
|
||||||
/* Prevents borderless windows from covering the taskbar when maximized. */
|
/* Prevents windows with no frame from covering the taskbar when maximized. */
|
||||||
if ((m_data.flags.testFlag(Qt::FramelessWindowHint)
|
if (m_data.flags.testFlag(Qt::CustomizeWindowHint) && !m_data.flags.testFlag(Qt::WindowTitleHint)
|
||||||
|| (m_data.flags.testFlag(Qt::CustomizeWindowHint) && !m_data.flags.testFlag(Qt::WindowTitleHint)))
|
|
||||||
&& IsZoomed(m_data.hwnd)) {
|
&& IsZoomed(m_data.hwnd)) {
|
||||||
const int resizedWidth = LOWORD(lParam);
|
const int resizedWidth = LOWORD(lParam);
|
||||||
const int resizedHeight = HIWORD(lParam);
|
const int resizedHeight = HIWORD(lParam);
|
||||||
|
|
||||||
const HMONITOR monitor = MonitorFromWindow(m_data.hwnd, MONITOR_DEFAULTTOPRIMARY);
|
const HMONITOR monitor = MonitorFromWindow(m_data.hwnd, MONITOR_DEFAULTTOPRIMARY);
|
||||||
MONITORINFO monitorInfo = {};
|
MONITORINFO monitorInfo = {};
|
||||||
monitorInfo.cbSize = sizeof(MONITORINFO);
|
monitorInfo.cbSize = sizeof(MONITORINFO);
|
||||||
@ -2194,13 +2218,11 @@ void QWindowsWindow::handleResized(int wParam, LPARAM lParam)
|
|||||||
int correctWidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left;
|
int correctWidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left;
|
||||||
int correctHeight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top;
|
int correctHeight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top;
|
||||||
|
|
||||||
if (!m_data.flags.testFlag(Qt::FramelessWindowHint)) {
|
const int borderWidth = invisibleMargins(m_data.hwnd).left();
|
||||||
const int borderWidth = invisibleMargins(m_data.hwnd).left();
|
correctLeft -= borderWidth;
|
||||||
correctLeft -= borderWidth;
|
correctTop -= borderWidth;
|
||||||
correctTop -= borderWidth;
|
correctWidth += borderWidth * 2;
|
||||||
correctWidth += borderWidth * 2;
|
correctHeight += borderWidth * 2;
|
||||||
correctHeight += borderWidth * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resizedWidth != correctWidth || resizedHeight != correctHeight) {
|
if (resizedWidth != correctWidth || resizedHeight != correctHeight) {
|
||||||
qCDebug(lcQpaWindow) << __FUNCTION__ << "correcting: " << resizedWidth << "x"
|
qCDebug(lcQpaWindow) << __FUNCTION__ << "correcting: " << resizedWidth << "x"
|
||||||
@ -3139,8 +3161,35 @@ void QWindowsWindow::getSizeHints(MINMAXINFO *mmi) const
|
|||||||
|
|
||||||
bool QWindowsWindow::handleNonClientHitTest(const QPoint &globalPos, LRESULT *result) const
|
bool QWindowsWindow::handleNonClientHitTest(const QPoint &globalPos, LRESULT *result) const
|
||||||
{
|
{
|
||||||
// QTBUG-32663, suppress resize cursor for fixed size windows.
|
|
||||||
const QWindow *w = window();
|
const QWindow *w = window();
|
||||||
|
const QPoint localPos = w->mapFromGlobal(QHighDpi::fromNativePixels(globalPos, w));
|
||||||
|
|
||||||
|
if (m_data.flags & Qt::FramelessWindowHint) {
|
||||||
|
const int border = (IsZoomed(m_data.hwnd) || isFullScreen_sys()) ? 0 : getResizeBorderThickness(savedDpi());
|
||||||
|
if (border == 0) {
|
||||||
|
*result = HTCLIENT;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool left = (globalPos.x() >= geometry().left()) && (globalPos.x() < geometry().left() + border);
|
||||||
|
const bool right = (globalPos.x() > geometry().right() - border) && (globalPos.x() <= geometry().right());
|
||||||
|
const bool top = (globalPos.y() >= geometry().top()) && (globalPos.y() < geometry().top() + border);
|
||||||
|
const bool bottom = (globalPos.y() > geometry().bottom() - border) && (globalPos.y() <= geometry().bottom());
|
||||||
|
|
||||||
|
if (left || right || top || bottom) {
|
||||||
|
if (left)
|
||||||
|
*result = top ? HTTOPLEFT : (bottom ? HTBOTTOMLEFT : HTLEFT);
|
||||||
|
else if (right)
|
||||||
|
*result = top ? HTTOPRIGHT : (bottom ? HTBOTTOMRIGHT : HTRIGHT);
|
||||||
|
else
|
||||||
|
*result = top ? HTTOP : HTBOTTOM;
|
||||||
|
} else {
|
||||||
|
*result = HTCLIENT;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// QTBUG-32663, suppress resize cursor for fixed size windows.
|
||||||
if (!w->isTopLevel() // Task 105852, minimized windows need to respond to user input.
|
if (!w->isTopLevel() // Task 105852, minimized windows need to respond to user input.
|
||||||
|| (m_windowState != Qt::WindowNoState)
|
|| (m_windowState != Qt::WindowNoState)
|
||||||
|| !isActive()
|
|| !isActive()
|
||||||
@ -3155,7 +3204,6 @@ bool QWindowsWindow::handleNonClientHitTest(const QPoint &globalPos, LRESULT *re
|
|||||||
const bool fixedHeight = minimumSize.height() == maximumSize.height();
|
const bool fixedHeight = minimumSize.height() == maximumSize.height();
|
||||||
if (!fixedWidth && !fixedHeight)
|
if (!fixedWidth && !fixedHeight)
|
||||||
return false;
|
return false;
|
||||||
const QPoint localPos = w->mapFromGlobal(QHighDpi::fromNativePixels(globalPos, w));
|
|
||||||
const QSize size = w->size();
|
const QSize size = w->size();
|
||||||
if (fixedHeight) {
|
if (fixedHeight) {
|
||||||
if (localPos.y() >= size.height()) {
|
if (localPos.y() >= size.height()) {
|
||||||
|
@ -33,7 +33,7 @@ struct QWindowsGeometryHint
|
|||||||
static QMargins frame(const QWindow *w, HWND hwnd);
|
static QMargins frame(const QWindow *w, HWND hwnd);
|
||||||
static QMargins frame(const QWindow *w, const QRect &geometry,
|
static QMargins frame(const QWindow *w, const QRect &geometry,
|
||||||
DWORD style, DWORD exStyle);
|
DWORD style, DWORD exStyle);
|
||||||
static bool handleCalculateSize(const QMargins &customMargins, const MSG &msg, LRESULT *result);
|
static bool handleCalculateSize(const QWindow *window, const QMargins &customMargins, const MSG &msg, LRESULT *result);
|
||||||
static void applyToMinMaxInfo(const QWindow *w, const QScreen *screen,
|
static void applyToMinMaxInfo(const QWindow *w, const QScreen *screen,
|
||||||
const QMargins &margins, MINMAXINFO *mmi);
|
const QMargins &margins, MINMAXINFO *mmi);
|
||||||
static void applyToMinMaxInfo(const QWindow *w, const QMargins &margins,
|
static void applyToMinMaxInfo(const QWindow *w, const QMargins &margins,
|
||||||
|
@ -646,7 +646,7 @@ void tst_QWidget::getSetCheck()
|
|||||||
#if defined (Q_OS_WIN)
|
#if defined (Q_OS_WIN)
|
||||||
obj1.setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
|
obj1.setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
|
||||||
const HWND handle = reinterpret_cast<HWND>(obj1.winId()); // explicitly create window handle
|
const HWND handle = reinterpret_cast<HWND>(obj1.winId()); // explicitly create window handle
|
||||||
QVERIFY(GetWindowLongPtr(handle, GWL_STYLE) & LONG_PTR(WS_POPUP));
|
QVERIFY((GetWindowLongPtr(handle, GWL_STYLE) & LONG_PTR(WS_OVERLAPPED)) == 0);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -9838,7 +9838,7 @@ public:
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void resizeMe() {
|
void resizeMe() {
|
||||||
resize(100, 100);
|
resize(150, 150);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -9850,7 +9850,7 @@ void tst_QWidget::moveInResizeEvent()
|
|||||||
testWidget.show();
|
testWidget.show();
|
||||||
QVERIFY(QTest::qWaitForWindowExposed(&testWidget));
|
QVERIFY(QTest::qWaitForWindowExposed(&testWidget));
|
||||||
|
|
||||||
QRect expectedGeometry(100,100, 100, 100);
|
QRect expectedGeometry(100,100, 150, 150);
|
||||||
QTRY_COMPARE(testWidget.geometry(), expectedGeometry);
|
QTRY_COMPARE(testWidget.geometry(), expectedGeometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user