From 2ca6a90ae20588d88fd80c804c93079b46e003d2 Mon Sep 17 00:00:00 2001 From: Yuhang Zhao <2546789017@qq.com> Date: Tue, 12 May 2020 20:53:53 +0800 Subject: [PATCH] Add a Qt Quick helper class. It's much easier to use it in Qt Quick now. Fixes: #11 Signed-off-by: Yuhang Zhao <2546789017@qq.com> --- framelesshelper.cpp | 5 +- framelesshelper_unix.pro | 6 +- framelesshelper_windows.pro | 6 +- framelessquickhelper.cpp | 442 ++++++++++++++++++++++++++++++++++++ framelessquickhelper.h | 90 ++++++++ main_windows.cpp | 23 +- resources/qml/main.qml | 16 ++ winnativeeventfilter.cpp | 5 +- 8 files changed, 567 insertions(+), 26 deletions(-) create mode 100644 framelessquickhelper.cpp create mode 100644 framelessquickhelper.h diff --git a/framelesshelper.cpp b/framelesshelper.cpp index dacc227..6785d76 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -332,8 +332,9 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) { #ifdef QT_QUICK_LIB const auto quickItem = qobject_cast(obj); if (quickItem) { - if (QRect(quickItem->x(), quickItem->y(), - quickItem->width(), quickItem->height()) + const auto pos = quickItem->mapToScene({0, 0}); + if (QRect(pos.x(), pos.y(), quickItem->width(), + quickItem->height()) .contains(x, y)) { return true; } diff --git a/framelesshelper_unix.pro b/framelesshelper_unix.pro index 1dd7d84..9d4f3b3 100644 --- a/framelesshelper_unix.pro +++ b/framelesshelper_unix.pro @@ -3,7 +3,11 @@ debug: TARGET = $$join(TARGET,,,_debug) TEMPLATE = app QT += gui-private qtHaveModule(widgets): QT += widgets -qtHaveModule(quick): QT += quick +qtHaveModule(quick) { + QT += quick + HEADERS += framelessquickhelper.h + SOURCES += framelessquickhelper.cpp +} CONFIG += c++17 strict_c++ warn_on utf8_source DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII VERSION = 1.0.0 diff --git a/framelesshelper_windows.pro b/framelesshelper_windows.pro index 6e0670b..5b39005 100644 --- a/framelesshelper_windows.pro +++ b/framelesshelper_windows.pro @@ -3,7 +3,11 @@ debug: TARGET = $$join(TARGET,,,d) TEMPLATE = app QT += gui-private qtHaveModule(widgets): QT += widgets -qtHaveModule(quick): QT += quick +qtHaveModule(quick) { + QT += quick + HEADERS += framelessquickhelper.h + SOURCES += framelessquickhelper.cpp +} CONFIG += c++17 strict_c++ utf8_source warn_on windeployqt DEFINES += WIN32_LEAN_AND_MEAN QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII CONFIG -= embed_manifest_exe diff --git a/framelessquickhelper.cpp b/framelessquickhelper.cpp new file mode 100644 index 0000000..e4711f2 --- /dev/null +++ b/framelessquickhelper.cpp @@ -0,0 +1,442 @@ +/* + * MIT License + * + * Copyright (C) 2020 by wangwenx190 (Yuhang Zhao) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "framelessquickhelper.h" + +#ifdef Q_OS_WINDOWS +#include "winnativeeventfilter.h" +#else +#include "framelesshelper.h" +#endif +#include + +#ifdef Q_OS_WINDOWS +namespace { + +const int m_defaultBorderWidth = 8, m_defaultBorderHeight = 8, + m_defaultTitleBarHeight = 30; + +} +#endif + +FramelessQuickHelper::FramelessQuickHelper(QQuickItem *parent) + : QQuickItem(parent) {} + +int FramelessQuickHelper::borderWidth() const { +#ifdef Q_OS_WINDOWS + const auto win = window(); + if (win) { + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + return WinNativeEventFilter::getSystemMetric( + hWnd, WinNativeEventFilter::SystemMetric::BorderWidth, false); + } + } + return m_defaultBorderWidth; +#else + return m_framelessHelper.getBorderWidth(); +#endif +} + +void FramelessQuickHelper::setBorderWidth(const int val) { +#ifdef Q_OS_WINDOWS + const auto win = window(); + if (win) { + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->borderWidth = val; + Q_EMIT borderWidthChanged(val); + } + } + } +#else + m_framelessHelper.setBorderWidth(val); + Q_EMIT borderWidthChanged(val); +#endif +} + +int FramelessQuickHelper::borderHeight() const { +#ifdef Q_OS_WINDOWS + const auto win = window(); + if (win) { + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + return WinNativeEventFilter::getSystemMetric( + hWnd, WinNativeEventFilter::SystemMetric::BorderHeight, false); + } + } + return m_defaultBorderHeight; +#else + return m_framelessHelper.getBorderHeight(); +#endif +} + +void FramelessQuickHelper::setBorderHeight(const int val) { +#ifdef Q_OS_WINDOWS + const auto win = window(); + if (win) { + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->borderHeight = val; + Q_EMIT borderHeightChanged(val); + } + } + } +#else + m_framelessHelper.setBorderHeight(val); + Q_EMIT borderHeightChanged(val); +#endif +} + +int FramelessQuickHelper::titleBarHeight() const { +#ifdef Q_OS_WINDOWS + const auto win = window(); + if (win) { + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + return WinNativeEventFilter::getSystemMetric( + hWnd, WinNativeEventFilter::SystemMetric::TitleBarHeight, + false); + } + } + return m_defaultTitleBarHeight; +#else + return m_framelessHelper.getTitleBarHeight(); +#endif +} + +void FramelessQuickHelper::setTitleBarHeight(const int val) { +#ifdef Q_OS_WINDOWS + const auto win = window(); + if (win) { + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->titleBarHeight = val; + Q_EMIT titleBarHeightChanged(val); + } + } + } +#else + m_framelessHelper.setTitleBarHeight(val); + Q_EMIT titleBarHeightChanged(val); +#endif +} + +bool FramelessQuickHelper::resizable() const { +#ifdef Q_OS_WINDOWS + const auto win = window(); + if (win) { + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + return !data->fixedSize; + } + } + } + return true; +#else + // ### TODO: impl + return true; +#endif +} + +void FramelessQuickHelper::setResizable(const bool val) { +#ifdef Q_OS_WINDOWS + const auto win = window(); + if (win) { + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->fixedSize = !val; + Q_EMIT resizableChanged(val); + } + } + } +#else + // ### TODO: impl + Q_UNUSED(val) +#endif +} + +void FramelessQuickHelper::removeWindowFrame(const bool center) { + const auto win = window(); + if (win) { +#ifdef Q_OS_WINDOWS + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + WinNativeEventFilter::addFramelessWindow(hWnd, nullptr, center); + } +#else + m_framelessHelper.removeWindowFrame(win); + if (center) { + FramelessHelper::moveWindowToDesktopCenter(win); + } +#endif + } +} + +void FramelessQuickHelper::setIgnoreAreas(const QVector &val) { + const auto win = window(); + if (win) { +#ifdef Q_OS_WINDOWS + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->ignoreAreas = val; + } + } +#else + m_framelessHelper.setIgnoreAreas(win, val); +#endif + } +} + +void FramelessQuickHelper::clearIgnoreAreas() { + const auto win = window(); + if (win) { +#ifdef Q_OS_WINDOWS + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->ignoreAreas.clear(); + } + } +#else + m_framelessHelper.setIgnoreAreas(win, {}); +#endif + } +} + +void FramelessQuickHelper::addIgnoreArea(const QRect &val) { + const auto win = window(); + if (win) { +#ifdef Q_OS_WINDOWS + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->ignoreAreas.append(val); + } + } +#else + QVector areas = m_framelessHelper.getIgnoreAreas(win); + areas.append(val); + m_framelessHelper.setIgnoreAreas(win, areas); +#endif + } +} + +void FramelessQuickHelper::setDraggableAreas(const QVector &val) { + const auto win = window(); + if (win) { +#ifdef Q_OS_WINDOWS + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->draggableAreas = val; + } + } +#else + m_framelessHelper.setDraggableAreas(win, val); +#endif + } +} + +void FramelessQuickHelper::clearDraggableAreas() { + const auto win = window(); + if (win) { +#ifdef Q_OS_WINDOWS + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->draggableAreas.clear(); + } + } +#else + m_framelessHelper.setDraggableAreas(win, {}); +#endif + } +} + +void FramelessQuickHelper::addDraggableArea(const QRect &val) { + const auto win = window(); + if (win) { +#ifdef Q_OS_WINDOWS + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->draggableAreas.append(val); + } + } +#else + QVector areas = m_framelessHelper.getDraggableAreas(win); + areas.append(val); + m_framelessHelper.setDraggableAreas(win, areas); +#endif + } +} + +void FramelessQuickHelper::setIgnoreObjects(const QVector &val) { + const auto win = window(); + if (win) { +#ifdef Q_OS_WINDOWS + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->ignoreObjects.clear(); + if (!val.isEmpty()) { + for (auto &&obj : qAsConst(val)) { + data->ignoreObjects.append(obj); + } + } + } + } +#else + QVector> objs; + if (!val.isEmpty()) { + for (auto &&obj : qAsConst(val)) { + objs.append(obj); + } + m_framelessHelper.setIgnoreObjects(win, objs); + } +#endif + } +} + +void FramelessQuickHelper::clearIgnoreObjects() { + const auto win = window(); + if (win) { +#ifdef Q_OS_WINDOWS + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->ignoreObjects.clear(); + } + } +#else + m_framelessHelper.setIgnoreObjects(win, {}); +#endif + } +} + +void FramelessQuickHelper::addIgnoreObject(QQuickItem *val) { + const auto win = window(); + if (win && val) { +#ifdef Q_OS_WINDOWS + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->ignoreObjects.append(val); + } + } +#else + QVector> objs = + m_framelessHelper.getIgnoreObjects(win); + objs.append(val); + m_framelessHelper.setIgnoreObjects(win, objs); +#endif + } +} + +void FramelessQuickHelper::setDraggableObjects( + const QVector &val) { + const auto win = window(); + if (win) { +#ifdef Q_OS_WINDOWS + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->draggableObjects.clear(); + if (!val.isEmpty()) { + for (auto &&obj : qAsConst(val)) { + data->draggableObjects.append(obj); + } + } + } + } +#else + QVector> objs; + if (!val.isEmpty()) { + for (auto &&obj : qAsConst(val)) { + objs.append(obj); + } + m_framelessHelper.setDraggableObjects(win, objs); + } +#endif + } +} + +void FramelessQuickHelper::clearDraggableObjects() { + const auto win = window(); + if (win) { +#ifdef Q_OS_WINDOWS + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->draggableObjects.clear(); + } + } +#else + m_framelessHelper.setDraggableObjects(win, {}); +#endif + } +} + +void FramelessQuickHelper::addDraggableObject(QQuickItem *val) { + const auto win = window(); + if (win && val) { +#ifdef Q_OS_WINDOWS + const auto hWnd = reinterpret_cast(win->winId()); + if (hWnd) { + const auto data = WinNativeEventFilter::windowData(hWnd); + if (data) { + data->draggableObjects.append(val); + } + } +#else + QVector> objs = + m_framelessHelper.getDraggableObjects(win); + objs.append(val); + m_framelessHelper.setDraggableObjects(win, objs); +#endif + } +} diff --git a/framelessquickhelper.h b/framelessquickhelper.h new file mode 100644 index 0000000..d83e083 --- /dev/null +++ b/framelessquickhelper.h @@ -0,0 +1,90 @@ +/* + * MIT License + * + * Copyright (C) 2020 by wangwenx190 (Yuhang Zhao) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include + +#ifndef Q_OS_WINDOWS +#include "framelesshelper.h" +#endif + +class FramelessQuickHelper : public QQuickItem { + Q_OBJECT + Q_DISABLE_COPY_MOVE(FramelessQuickHelper) + Q_PROPERTY(int borderWidth READ borderWidth WRITE setBorderWidth NOTIFY + borderWidthChanged) + Q_PROPERTY(int borderHeight READ borderHeight WRITE setBorderHeight NOTIFY + borderHeightChanged) + Q_PROPERTY(int titleBarHeight READ titleBarHeight WRITE setTitleBarHeight + NOTIFY titleBarHeightChanged) + Q_PROPERTY(bool resizable READ resizable WRITE setResizable NOTIFY + resizableChanged) + +public: + explicit FramelessQuickHelper(QQuickItem *parent = nullptr); + ~FramelessQuickHelper() override = default; + + int borderWidth() const; + void setBorderWidth(const int val); + + int borderHeight() const; + void setBorderHeight(const int val); + + int titleBarHeight() const; + void setTitleBarHeight(const int val); + + bool resizable() const; + void setResizable(const bool val); + +public Q_SLOTS: + void removeWindowFrame(const bool center = true); + + void setIgnoreAreas(const QVector &val); + void clearIgnoreAreas(); + void addIgnoreArea(const QRect &val); + + void setDraggableAreas(const QVector &val); + void clearDraggableAreas(); + void addDraggableArea(const QRect &val); + + void setIgnoreObjects(const QVector &val); + void clearIgnoreObjects(); + void addIgnoreObject(QQuickItem *val); + + void setDraggableObjects(const QVector &val); + void clearDraggableObjects(); + void addDraggableObject(QQuickItem *val); + +Q_SIGNALS: + void borderWidthChanged(int); + void borderHeightChanged(int); + void titleBarHeightChanged(int); + void resizableChanged(bool); + +private: +#ifndef Q_OS_WINDOWS + FramelessHelper m_framelessHelper; +#endif +}; diff --git a/main_windows.cpp b/main_windows.cpp index 3117b1e..1e485cd 100644 --- a/main_windows.cpp +++ b/main_windows.cpp @@ -4,8 +4,8 @@ #include #include #ifdef QT_QUICK_LIB +#include "framelessquickhelper.h" #include -#include #endif #include #include @@ -97,6 +97,8 @@ int main(int argc, char *argv[]) { #ifdef QT_QUICK_LIB // Qt Quick example: QQmlApplicationEngine engine; + qmlRegisterType("wangwenx190.Utils", 1, 0, + "FramelessHelper"); const QUrl url(QString::fromUtf8("qrc:///qml/main.qml")); QObject::connect( &engine, &QQmlApplicationEngine::objectCreated, &application, @@ -107,25 +109,6 @@ int main(int argc, char *argv[]) { }, Qt::QueuedConnection); engine.load(url); - const auto window = qobject_cast(engine.rootObjects().at(0)); - Q_ASSERT(window); - const auto hWnd_qml = reinterpret_cast(window->winId()); - WinNativeEventFilter::addFramelessWindow(hWnd_qml, nullptr, true); - QObject::connect( - window, &QWindow::widthChanged, [window, hWnd_qml](const int arg) { - if (arg >= window->minimumWidth()) { - const auto data = WinNativeEventFilter::windowData(hWnd_qml); - if (data) { - const int tbh_qml = WinNativeEventFilter::getSystemMetric( - hWnd_qml, - WinNativeEventFilter::SystemMetric::TitleBarHeight, - false); - data->draggableAreas = { - {0, 0, (window->width() - (m_defaultButtonWidth * 3)), - tbh_qml}}; - } - } - }); #endif return QApplication::exec(); diff --git a/resources/qml/main.qml b/resources/qml/main.qml index 662380f..8a5cf6d 100644 --- a/resources/qml/main.qml +++ b/resources/qml/main.qml @@ -1,5 +1,6 @@ import QtQuick 2.15 import QtQuick.Window 2.15 +import wangwenx190.Utils 1.0 Window { id: root @@ -8,6 +9,11 @@ Window { height: 600 title: qsTr("Hello, World!") + FramelessHelper { + id: framelessHelper + Component.onCompleted: framelessHelper.removeWindowFrame() + } + Rectangle { id: titleBar height: 30 @@ -32,10 +38,15 @@ Window { anchors.right: parent.right MinimizeButton { + id: minimizeButton onClicked: root.showMinimized() + Component.onCompleted: framelessHelper.addIgnoreObject( + minimizeButton) } MaximizeButton { + id: maximizeButton + // QWindow::Visibility::Maximized maximized: root.visibility === 4 onClicked: { if (maximized) { @@ -44,10 +55,15 @@ Window { root.showMaximized() } } + Component.onCompleted: framelessHelper.addIgnoreObject( + maximizeButton) } CloseButton { + id: closeButton onClicked: root.close() + Component.onCompleted: framelessHelper.addIgnoreObject( + closeButton) } } } diff --git a/winnativeeventfilter.cpp b/winnativeeventfilter.cpp index 23a4b6e..705bc90 100644 --- a/winnativeeventfilter.cpp +++ b/winnativeeventfilter.cpp @@ -1324,8 +1324,9 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, const auto quickItem = qobject_cast(object); if (quickItem) { - if (QRect(qRound(quickItem->x() * dpr), - qRound(quickItem->y() * dpr), + const auto pos = quickItem->mapToScene({0, 0}); + if (QRect(qRound(pos.x() * dpr), + qRound(pos.y() * dpr), qRound(quickItem->width() * dpr), qRound(quickItem->height() * dpr)) .contains(x, y)) {