diff --git a/examples/quick/qml/main.qml b/examples/quick/qml/main.qml index db6e523..5483340 100644 --- a/examples/quick/qml/main.qml +++ b/examples/quick/qml/main.qml @@ -1,3 +1,5 @@ + + /* * MIT License * @@ -21,7 +23,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - import QtQuick 2.0 import QtQuick.Window 2.0 import QtQuick.Controls 2.0 @@ -35,7 +36,10 @@ Window { title: qsTr("Hello, World!") color: "#f0f0f0" - property real _flh_margin: ((window.visibility === Window.Maximized) || (window.visibility === Window.FullScreen)) ? 0.0 : (1.0 / Screen.devicePixelRatio) + property real _flh_margin: ((window.visibility === Window.Maximized) + || (window.visibility + === Window.FullScreen)) ? 0.0 : (1.0 / Screen.devicePixelRatio) + property var _win_prev_state: null FramelessHelper { id: framelessHelper @@ -51,7 +55,7 @@ Window { Rectangle { id: titleBar - height: framelessHelper.titleBarHeight + framelessHelper.resizeBorderHeight + height: framelessHelper.titleBarHeight color: "transparent" anchors { top: parent.top @@ -79,12 +83,14 @@ Window { MinimizeButton { id: minimizeButton onClicked: window.showMinimized() - Component.onCompleted: framelessHelper.setHitTestVisibleInChrome(minimizeButton, true) + Component.onCompleted: framelessHelper.setHitTestVisibleInChrome( + minimizeButton, true) } MaximizeButton { id: maximizeButton - maximized: ((window.visibility === Window.Maximized) || (window.visibility === Window.FullScreen)) + maximized: ((window.visibility === Window.Maximized) + || (window.visibility === Window.FullScreen)) onClicked: { if (maximized) { window.showNormal() @@ -92,13 +98,15 @@ Window { window.showMaximized() } } - Component.onCompleted: framelessHelper.setHitTestVisibleInChrome(maximizeButton, true) + Component.onCompleted: framelessHelper.setHitTestVisibleInChrome( + maximizeButton, true) } CloseButton { id: closeButton onClicked: window.close() - Component.onCompleted: framelessHelper.setHitTestVisibleInChrome(closeButton, true) + Component.onCompleted: framelessHelper.setHitTestVisibleInChrome( + closeButton, true) } } } @@ -112,6 +120,28 @@ Window { } } + Button { + anchors { + horizontalCenter: parent.horizontalCenter + top: label.bottom + topMargin: 15 + } + property bool _full: window.visibility === Window.FullScreen + text: _full ? qsTr("Exit FullScreen") : qsTr("Enter FullScreen") + onClicked: { + if (_full) { + if (_win_prev_state == Window.Maximized) { + window.showMaximized() + } else if (_win_prev_state == Window.Windowed) { + window.showNormal() + } + } else { + _win_prev_state = window.visibility + window.showFullScreen() + } + } + } + Rectangle { id: windowFrame anchors.fill: parent diff --git a/examples/widget/widget.cpp b/examples/widget/widget.cpp index f36df71..c9809eb 100644 --- a/examples/widget/widget.cpp +++ b/examples/widget/widget.cpp @@ -116,10 +116,8 @@ void Widget::setupUi() setWindowTitle(tr("Hello, World!")); resize(800, 600); const QWindow *win = windowHandle(); - const int resizeBorderThickness = Utilities::getSystemMetric(win, SystemMetric::ResizeBorderThickness, false); const int titleBarHeight = Utilities::getSystemMetric(win, SystemMetric::TitleBarHeight, false); - const int systemButtonHeight = titleBarHeight + resizeBorderThickness; - const QSize systemButtonSize = {qRound(static_cast(systemButtonHeight) * 1.5), systemButtonHeight}; + const QSize systemButtonSize = {qRound(static_cast(titleBarHeight) * 1.5), titleBarHeight}; m_minimizeButton = new QPushButton(this); m_minimizeButton->setObjectName(QStringLiteral("MinimizeButton")); m_minimizeButton->setFixedSize(systemButtonSize); diff --git a/framelesshelper.cpp b/framelesshelper.cpp index 3b1ae86..dca059b 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -71,7 +71,8 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) } const QEvent::Type type = event->type(); // We are only interested in mouse events. - if ((type != QEvent::MouseButtonDblClick) && (type != QEvent::MouseButtonPress) && (type != QEvent::MouseMove)) { + if ((type != QEvent::MouseButtonDblClick) && (type != QEvent::MouseButtonPress) + && (type != QEvent::MouseMove)) { return false; } const auto window = qobject_cast(object); @@ -125,7 +126,7 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) } if (window->windowState() == Qt::WindowNoState) { isInTitlebarArea = (localMousePosition.y() > resizeBorderThickness) - && (localMousePosition.y() <= (titleBarHeight + resizeBorderThickness)) + && (localMousePosition.y() <= titleBarHeight) && (localMousePosition.x() > resizeBorderThickness) && (localMousePosition.x() < (windowWidth - resizeBorderThickness)) && !hitTestVisible; diff --git a/framelesshelper_global.h b/framelesshelper_global.h index 754ec13..59a4fde 100644 --- a/framelesshelper_global.h +++ b/framelesshelper_global.h @@ -90,6 +90,7 @@ namespace Constants [[maybe_unused]] constexpr char kFramelessModeFlag[] = "_FRAMELESSHELPER_FRAMELESS_MODE"; [[maybe_unused]] constexpr char kResizeBorderThicknessFlag[] = "_FRAMELESSHELPER_RESIZE_BORDER_THICKNESS"; +[[maybe_unused]] constexpr char kCaptionHeightFlag[] = "_FRAMELESSHELPER_CAPTION_HEIGHT"; [[maybe_unused]] constexpr char kTitleBarHeightFlag[] = "_FRAMELESSHELPER_TITLE_BAR_HEIGHT"; [[maybe_unused]] constexpr char kHitTestVisibleInChromeFlag[] = "_FRAMELESSHELPER_HIT_TEST_VISIBLE_IN_CHROME"; [[maybe_unused]] constexpr char kUseNativeTitleBarFlag[] = "_FRAMELESSHELPER_USE_NATIVE_TITLE_BAR"; diff --git a/framelesshelper_win32.cpp b/framelesshelper_win32.cpp index 07f1510..02ca8ac 100644 --- a/framelesshelper_win32.cpp +++ b/framelesshelper_win32.cpp @@ -589,21 +589,21 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me qWarning() << "Failed to retrieve the client rect of the current window."; break; } - const LONG ww = clientRect.right; - const int rbt = Utilities::getSystemMetric(window, SystemMetric::ResizeBorderThickness, true); - const int tbh = Utilities::getSystemMetric(window, SystemMetric::TitleBarHeight, true); + const LONG windowWidth = clientRect.right; + const int resizeBorderThickness = Utilities::getSystemMetric(window, SystemMetric::ResizeBorderThickness, true); + const int titleBarHeight = Utilities::getSystemMetric(window, SystemMetric::TitleBarHeight, true); bool isTitleBar = false; if (IsMaximized(msg->hwnd) || (window->windowState() == Qt::WindowFullScreen)) { - isTitleBar = (localMouse.y() >= 0) && (localMouse.y() <= tbh) - && (localMouse.x() >= 0) && (localMouse.x() <= ww) + isTitleBar = (localMouse.y() >= 0) && (localMouse.y() <= titleBarHeight) + && (localMouse.x() >= 0) && (localMouse.x() <= windowWidth) && !Utilities::isHitTestVisibleInChrome(window); } if (window->windowState() == Qt::WindowNoState) { - isTitleBar = (localMouse.y() > rbt) && (localMouse.y() <= (rbt + tbh)) - && (localMouse.x() > rbt) && (localMouse.x() < (ww - rbt)) + isTitleBar = (localMouse.y() > resizeBorderThickness) && (localMouse.y() <= titleBarHeight) + && (localMouse.x() > resizeBorderThickness) && (localMouse.x() < (windowWidth - resizeBorderThickness)) && !Utilities::isHitTestVisibleInChrome(window); } - const bool isTop = localMouse.y() <= rbt; + const bool isTop = localMouse.y() <= resizeBorderThickness; if (shouldHaveWindowFrame()) { // This will handle the left, right and bottom parts of the frame // because we didn't change them. @@ -628,19 +628,19 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me *result = HTCLIENT; return true; } else { - const LRESULT hitTestResult = [clientRect, msg, isTitleBar, &localMouse, rbt, ww, isTop, window]{ + const LRESULT hitTestResult = [clientRect, msg, isTitleBar, &localMouse, resizeBorderThickness, windowWidth, isTop, window]{ if (IsMaximized(msg->hwnd)) { if (isTitleBar) { return HTCAPTION; } return HTCLIENT; } - const LONG wh = clientRect.bottom; - const bool isBottom = (localMouse.y() >= (wh - rbt)); + const LONG windowHeight = clientRect.bottom; + const bool isBottom = (localMouse.y() >= (windowHeight - resizeBorderThickness)); // Make the border a little wider to let the user easy to resize on corners. const qreal factor = (isTop || isBottom) ? 2.0 : 1.0; - const bool isLeft = (localMouse.x() <= qRound(static_cast(rbt) * factor)); - const bool isRight = (localMouse.x() >= (ww - qRound(static_cast(rbt) * factor))); + const bool isLeft = (localMouse.x() <= qRound(static_cast(resizeBorderThickness) * factor)); + const bool isRight = (localMouse.x() >= (windowWidth - qRound(static_cast(resizeBorderThickness) * factor))); const bool fixedSize = Utilities::isWindowFixedSize(window); const auto getBorderValue = [fixedSize](int value) -> int { return fixedSize ? HTCLIENT : value; @@ -689,15 +689,32 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me const auto oldStyle = GetWindowLongPtrW(msg->hwnd, GWL_STYLE); // Prevent Windows from drawing the default title bar by temporarily // toggling the WS_VISIBLE style. - SetWindowLongPtrW(msg->hwnd, GWL_STYLE, oldStyle & ~WS_VISIBLE); + if (SetWindowLongPtrW(msg->hwnd, GWL_STYLE, oldStyle & ~WS_VISIBLE) == 0) { + qWarning() << "SetWindowLongPtrW() failed."; + break; + } const auto winId = reinterpret_cast(msg->hwnd); Utilities::triggerFrameChange(winId); const LRESULT ret = DefWindowProcW(msg->hwnd, msg->message, msg->wParam, msg->lParam); - SetWindowLongPtrW(msg->hwnd, GWL_STYLE, oldStyle); + if (SetWindowLongPtrW(msg->hwnd, GWL_STYLE, oldStyle) == 0) { + qWarning() << "SetWindowLongPtrW() failed."; + break; + } Utilities::triggerFrameChange(winId); *result = ret; return true; } +#if 0 + case WM_SIZE: { + const bool normal = (msg->wParam == SIZE_RESTORED); + const bool max = (msg->wParam == SIZE_MAXIMIZED); + const bool full = (window->windowState() == Qt::WindowFullScreen); + if (normal || max || full) { + Utilities::updateFrameMargins(reinterpret_cast(msg->hwnd), (max || full)); + Utilities::updateQtFrameMargins(const_cast(window), true); + } + } break; +#endif default: break; } diff --git a/framelesswindowsmanager.cpp b/framelesswindowsmanager.cpp index 2e714da..9a3b356 100644 --- a/framelesswindowsmanager.cpp +++ b/framelesswindowsmanager.cpp @@ -54,6 +54,15 @@ void FramelessWindowsManager::addWindow(QWindow *window) framelessHelperUnix()->removeWindowFrame(window); #else FramelessHelperWin::addFramelessWindow(window); + QObject::connect(window, &QWindow::windowStateChanged, [window](Qt::WindowState state){ + const bool normal = (state == Qt::WindowNoState); + const bool max = (state == Qt::WindowMaximized); + const bool full = (state == Qt::WindowFullScreen); + if (normal || max || full) { + Utilities::updateFrameMargins(window->winId(), (max || full)); + Utilities::updateQtFrameMargins(window, true); + } + }); // Work-around a Win32 multi-monitor bug. QObject::connect(window, &QWindow::screenChanged, [window](QScreen *screen){ Q_UNUSED(screen); diff --git a/utilities.h b/utilities.h index fc4915d..dab5995 100644 --- a/utilities.h +++ b/utilities.h @@ -32,6 +32,7 @@ FRAMELESSHELPER_BEGIN_NAMESPACE enum class SystemMetric : int { ResizeBorderThickness = 0, + CaptionHeight, TitleBarHeight }; diff --git a/utilities_win32.cpp b/utilities_win32.cpp index 6d4afc5..7c461de 100644 --- a/utilities_win32.cpp +++ b/utilities_win32.cpp @@ -50,11 +50,10 @@ Q_DECLARE_METATYPE(QMargins) FRAMELESSHELPER_BEGIN_NAMESPACE static constexpr char kDwmRegistryKey[] = R"(Software\Microsoft\Windows\DWM)"; -static constexpr char kDwmCompositionRegistryKey[] = "Composition"; -static constexpr int kDefaultResizeBorderThickness = 8; static constexpr int kDefaultResizeBorderThicknessClassic = 4; -static constexpr int kDefaultTitleBarHeight = 23; +static constexpr int kDefaultResizeBorderThicknessAero = 8; +static constexpr int kDefaultCaptionHeight = 23; bool Utilities::isDwmCompositionAvailable() { @@ -68,7 +67,7 @@ bool Utilities::isDwmCompositionAvailable() } const QSettings registry(QString::fromUtf8(kDwmRegistryKey), QSettings::NativeFormat); bool ok = false; - const int value = registry.value(QString::fromUtf8(kDwmCompositionRegistryKey), 0).toInt(&ok); + const int value = registry.value(QStringLiteral("Composition"), 0).toInt(&ok); return (ok && (value != 0)); } @@ -78,51 +77,67 @@ int Utilities::getSystemMetric(const QWindow *window, const SystemMetric metric, if (!window) { return 0; } + const qreal devicePixelRatio = window->devicePixelRatio(); + const qreal scaleFactor = (dpiScale ? devicePixelRatio : 1.0); switch (metric) { case SystemMetric::ResizeBorderThickness: { - const int rbt = window->property(Constants::kResizeBorderThicknessFlag).toInt(); - if ((rbt > 0) && !forceSystemValue) { - return qRound(static_cast(rbt) * (dpiScale ? window->devicePixelRatio() : 1.0)); + const int resizeBorderThickness = window->property(Constants::kResizeBorderThicknessFlag).toInt(); + if ((resizeBorderThickness > 0) && !forceSystemValue) { + return qRound(static_cast(resizeBorderThickness) * scaleFactor); } else { const int result = GetSystemMetrics(SM_CXSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); if (result > 0) { if (dpiScale) { return result; } else { - return qRound(static_cast(result) / window->devicePixelRatio()); + return qRound(static_cast(result) / devicePixelRatio); } } else { // The padded border will disappear if DWM composition is disabled. - const int defaultRBT = (isDwmCompositionAvailable() ? kDefaultResizeBorderThickness : kDefaultResizeBorderThicknessClassic); + const int defaultResizeBorderThickness = (isDwmCompositionAvailable() ? kDefaultResizeBorderThicknessAero : kDefaultResizeBorderThicknessClassic); if (dpiScale) { - return qRound(static_cast(defaultRBT) * window->devicePixelRatio()); + return qRound(static_cast(defaultResizeBorderThickness) * devicePixelRatio); } else { - return defaultRBT; + return defaultResizeBorderThickness; } } } } - case SystemMetric::TitleBarHeight: { - const int tbh = window->property(Constants::kTitleBarHeightFlag).toInt(); - if ((tbh > 0) && !forceSystemValue) { - return qRound(static_cast(tbh) * (dpiScale ? window->devicePixelRatio() : 1.0)); + case SystemMetric::CaptionHeight: { + const int captionHeight = window->property(Constants::kCaptionHeightFlag).toInt(); + if ((captionHeight > 0) && !forceSystemValue) { + return qRound(static_cast(captionHeight) * scaleFactor); } else { const int result = GetSystemMetrics(SM_CYCAPTION); if (result > 0) { if (dpiScale) { return result; } else { - return qRound(static_cast(result) / window->devicePixelRatio()); + return qRound(static_cast(result) / devicePixelRatio); } } else { if (dpiScale) { - return qRound(static_cast(kDefaultTitleBarHeight) * window->devicePixelRatio()); + return qRound(static_cast(kDefaultCaptionHeight) * devicePixelRatio); } else { - return kDefaultTitleBarHeight; + return kDefaultCaptionHeight; } } } } + case SystemMetric::TitleBarHeight: { + const int titleBarHeight = window->property(Constants::kTitleBarHeightFlag).toInt(); + if ((titleBarHeight > 0) && !forceSystemValue) { + return qRound(static_cast(titleBarHeight) * scaleFactor); + } else { + const int captionHeight = getSystemMetric(window,SystemMetric::CaptionHeight, + dpiScale, forceSystemValue); + const int resizeBorderThickness = getSystemMetric(window, SystemMetric::ResizeBorderThickness, + dpiScale, forceSystemValue); + return (((window->windowState() == Qt::WindowMaximized) + || (window->windowState() == Qt::WindowFullScreen)) + ? captionHeight : (captionHeight + resizeBorderThickness)); + } + } } return 0; } @@ -134,14 +149,15 @@ void Utilities::triggerFrameChange(const WId winId) return; } const auto hwnd = reinterpret_cast(winId); - if (SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER) == FALSE) { + constexpr UINT flags = (SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); + if (SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, flags) == FALSE) { qWarning() << "SetWindowPos() failed."; } } void Utilities::updateFrameMargins(const WId winId, const bool reset) { - // DwmExtendFrameIntoClientArea() will always fail if DWM Composition is disabled. + // DwmExtendFrameIntoClientArea() will always fail if DWM composition is disabled. // No need to try in this case. if (!isDwmCompositionAvailable()) { return; @@ -163,9 +179,12 @@ void Utilities::updateQtFrameMargins(QWindow *window, const bool enable) if (!window) { return; } - const int rbt = enable ? Utilities::getSystemMetric(window, SystemMetric::ResizeBorderThickness, true, true) : 0; - const int tbh = enable ? Utilities::getSystemMetric(window, SystemMetric::TitleBarHeight, true, true) : 0; - const QMargins margins = {-rbt, -(rbt + tbh), -rbt, -rbt}; // left, top, right, bottom + const bool shouldApplyCustomFrameMargins = (enable + && (window->windowState() != Qt::WindowMaximized) + && (window->windowState() != Qt::WindowFullScreen)); + const int resizeBorderThickness = shouldApplyCustomFrameMargins ? Utilities::getSystemMetric(window, SystemMetric::ResizeBorderThickness, true, true) : 0; + const int titleBarHeight = shouldApplyCustomFrameMargins ? Utilities::getSystemMetric(window, SystemMetric::TitleBarHeight, true, true) : 0; + const QMargins margins = {-resizeBorderThickness, -titleBarHeight, -resizeBorderThickness, -resizeBorderThickness}; // left, top, right, bottom const QVariant marginsVar = QVariant::fromValue(margins); window->setProperty("_q_windowsCustomMargins", marginsVar); #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) @@ -185,28 +204,31 @@ void Utilities::updateQtFrameMargins(QWindow *window, const bool enable) bool Utilities::isWin8OrGreater() { #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) - return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8; + static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8); #else - return QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS8; + static const bool result = (QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS8); #endif + return result; } bool Utilities::isWin8Point1OrGreater() { #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) - return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8_1; + static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8_1); #else - return QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS8_1; + static const bool result = (QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS8_1); #endif + return result; } bool Utilities::isWin10OrGreater() { #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) - return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10; + static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10); #else - return QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS10; + static const bool result = (QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS10); #endif + return result; } FRAMELESSHELPER_END_NAMESPACE