Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2022-03-18 18:09:20 +08:00
parent ebc20e23b6
commit 4d277a5eac
24 changed files with 686 additions and 203 deletions

View File

@ -115,22 +115,32 @@ bool FramelessHelperQt::eventFilter(QObject *object, QEvent *event)
} }
const auto mouseEvent = static_cast<QMouseEvent *>(event); const auto mouseEvent = static_cast<QMouseEvent *>(event);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const QPointF localPos = mouseEvent->position(); const QPointF scenePos = mouseEvent->scenePosition();
#else #else
const QPointF localPos = mouseEvent->windowPos(); const QPointF scenePos = mouseEvent->windowPos();
#endif #endif
if (type == QEvent::MouseMove) { switch (type) {
const Qt::CursorShape cs = Utils::calculateCursorShape(window, localPos); case QEvent::MouseMove: {
const Qt::CursorShape cs = Utils::calculateCursorShape(window, scenePos);
if (cs == Qt::ArrowCursor) { if (cs == Qt::ArrowCursor) {
window->unsetCursor(); window->unsetCursor();
} else { } else {
window->setCursor(cs); window->setCursor(cs);
} }
} else if (type == QEvent::MouseButtonPress) { } break;
const Qt::Edges edges = Utils::calculateWindowEdges(window, localPos); case QEvent::MouseButtonPress: {
if (edges != Qt::Edges{}) { if (mouseEvent->button() != Qt::LeftButton) {
Utils::startSystemResize(window, edges); return false;
} }
const Qt::Edges edges = Utils::calculateWindowEdges(window, scenePos);
if (edges == Qt::Edges{}) {
return false;
}
Utils::startSystemResize(window, edges);
return true;
}
default:
break;
} }
return false; return false;
} }

View File

@ -222,9 +222,6 @@
<height>16</height> <height>16</height>
</size> </size>
</property> </property>
<property name="checkable">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item> <item>

View File

@ -25,6 +25,9 @@
#include "mainwindow.h" #include "mainwindow.h"
#include "ui_MainWindow.h" #include "ui_MainWindow.h"
#include "ui_TitleBar.h" #include "ui_TitleBar.h"
#include <utils.h>
FRAMELESSHELPER_USE_NAMESPACE
MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) : FramelessMainWindow(parent, flags) MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) : FramelessMainWindow(parent, flags)
{ {
@ -43,6 +46,14 @@ MainWindow::~MainWindow()
} }
} }
void MainWindow::changeEvent(QEvent *event)
{
FramelessMainWindow::changeEvent(event);
if (event->type() == QEvent::WindowStateChange) {
Q_EMIT windowStateChanged();
}
}
void MainWindow::setupUi() void MainWindow::setupUi()
{ {
mainWindow = new Ui::MainWindow; mainWindow = new Ui::MainWindow;
@ -52,6 +63,12 @@ void MainWindow::setupUi()
titleBar = new Ui::TitleBar; titleBar = new Ui::TitleBar;
titleBar->setupUi(titleBarWidget); titleBar->setupUi(titleBarWidget);
const SystemTheme theme = SystemTheme::Light;
const ResourceType resource = ResourceType::Icon;
titleBar->minimizeButton->setIcon(qvariant_cast<QIcon>(Utils::getSystemButtonIconResource(SystemButtonType::Minimize, theme, resource)));
titleBar->maximizeButton->setIcon(qvariant_cast<QIcon>(Utils::getSystemButtonIconResource(SystemButtonType::Maximize, theme, resource)));
titleBar->closeButton->setIcon(qvariant_cast<QIcon>(Utils::getSystemButtonIconResource(SystemButtonType::Close, theme, resource)));
QMenuBar *mb = menuBar(); QMenuBar *mb = menuBar();
titleBar->horizontalLayout->insertWidget(1, mb); titleBar->horizontalLayout->insertWidget(1, mb);
@ -65,19 +82,18 @@ void MainWindow::setupUi()
setHitTestVisible(titleBar->closeButton, true); setHitTestVisible(titleBar->closeButton, true);
connect(titleBar->minimizeButton, &QPushButton::clicked, this, &MainWindow::showMinimized); connect(titleBar->minimizeButton, &QPushButton::clicked, this, &MainWindow::showMinimized);
connect(titleBar->maximizeButton, &QPushButton::clicked, this, [this](){ connect(titleBar->maximizeButton, &QPushButton::clicked, this, &MainWindow::toggleMaximized);
if (isZoomed()) {
showNormal();
} else {
showMaximized();
}
});
connect(titleBar->closeButton, &QPushButton::clicked, this, &MainWindow::close); connect(titleBar->closeButton, &QPushButton::clicked, this, &MainWindow::close);
connect(this, &MainWindow::windowIconChanged, titleBar->iconButton, &QPushButton::setIcon); connect(this, &MainWindow::windowIconChanged, titleBar->iconButton, &QPushButton::setIcon);
connect(this, &MainWindow::windowTitleChanged, titleBar->titleLabel, &QLabel::setText); connect(this, &MainWindow::windowTitleChanged, titleBar->titleLabel, &QLabel::setText);
connect(this, &MainWindow::windowStateChanged, this, [this](){ connect(this, &MainWindow::windowStateChanged, this, [this](){
const bool zoomed = isZoomed(); const bool zoomed = isZoomed();
titleBar->maximizeButton->setChecked(zoomed); const SystemTheme theme = SystemTheme::Light;
const SystemButtonType button = (zoomed ? SystemButtonType::Restore : SystemButtonType::Maximize);
const ResourceType resource = ResourceType::Icon;
titleBar->maximizeButton->setIcon(qvariant_cast<QIcon>(Utils::getSystemButtonIconResource(button, theme, resource)));
titleBar->maximizeButton->setToolTip(zoomed ? tr("Restore") : tr("Maximize")); titleBar->maximizeButton->setToolTip(zoomed ? tr("Restore") : tr("Maximize"));
}); });
setWindowTitle(tr("Hello, World! - Qt MainWindow"));
} }

View File

@ -41,6 +41,9 @@ public:
explicit MainWindow(QWidget *parent = nullptr, Qt::WindowFlags flags = {}); explicit MainWindow(QWidget *parent = nullptr, Qt::WindowFlags flags = {});
~MainWindow() override; ~MainWindow() override;
protected:
void changeEvent(QEvent *event) override;
private: private:
void setupUi(); void setupUi();

View File

@ -26,7 +26,7 @@
#include <QtQml/qqmlapplicationengine.h> #include <QtQml/qqmlapplicationengine.h>
#include <QtQuick/qquickwindow.h> #include <QtQuick/qquickwindow.h>
#include <QtQuickControls2/qquickstyle.h> #include <QtQuickControls2/qquickstyle.h>
#include <framelessquickhelper.h> #include <framelesshelper_quick.h>
FRAMELESSHELPER_USE_NAMESPACE FRAMELESSHELPER_USE_NAMESPACE
@ -60,7 +60,7 @@ int main(int argc, char *argv[])
QQuickStyle::setStyle(QStringLiteral("Default")); QQuickStyle::setStyle(QStringLiteral("Default"));
#endif #endif
FramelessQuickHelper::registerTypes(&engine); FramelessHelper::Quick::registerTypes(&engine);
const QUrl homepageUrl(QStringLiteral("qrc:///qml/MainWindow.qml")); const QUrl homepageUrl(QStringLiteral("qrc:///qml/MainWindow.qml"));
const QMetaObject::Connection connection = QObject::connect( const QMetaObject::Connection connection = QObject::connect(

View File

@ -67,33 +67,12 @@ Window {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
MouseArea {
anchors.fill: parent
anchors.rightMargin: 30 * 1.5 * 3
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
onClicked: {
if (mouse.button === Qt.RightButton) {
FramelessUtils.showSystemMenu(window, Qt.point(mouse.x, mouse.y));
}
}
onDoubleClicked: {
if (mouse.button === Qt.LeftButton) {
maximizeButton.clicked();
}
}
onPositionChanged: {
if (containsPress && (window.visibility !== Window.FullScreen)) {
FramelessUtils.startSystemMove2(window);
}
}
}
Row { Row {
anchors.top: parent.top anchors.top: parent.top
anchors.right: parent.right anchors.right: parent.right
MinimizeButton { MinimizeButton {
id: minimizeButton
onClicked: FramelessUtils.showMinimized2(window) onClicked: FramelessUtils.showMinimized2(window)
} }
@ -110,6 +89,7 @@ Window {
} }
CloseButton { CloseButton {
id: closeButton
onClicked: window.close() onClicked: window.close()
} }
} }
@ -136,5 +116,11 @@ Window {
color: window.active ? FramelessUtils.frameBorderActiveColor : FramelessUtils.frameBorderInactiveColor color: window.active ? FramelessUtils.frameBorderActiveColor : FramelessUtils.frameBorderInactiveColor
} }
Component.onCompleted: FramelessHelper.addWindow(window) Component.onCompleted: {
FramelessHelper.addWindow(window);
FramelessHelper.setTitleBarItem(window, titleBar);
FramelessHelper.setHitTestVisible(window, minimizeButton, true);
FramelessHelper.setHitTestVisible(window, maximizeButton, true);
FramelessHelper.setHitTestVisible(window, closeButton, true);
}
} }

View File

@ -30,11 +30,11 @@
FRAMELESSHELPER_USE_NAMESPACE FRAMELESSHELPER_USE_NAMESPACE
Widget::Widget(QWidget *parent) : FramelessWidget(parent) Widget::Widget(QWidget *parent) : FramelessWidget(parent, WindowLayout::Standard)
{ {
setupUi(); setupUi();
connect(this, &Widget::systemThemeChanged, this, &Widget::updateStyleSheet);
startTimer(500); startTimer(500);
connect(this, &Widget::systemThemeChanged, this, &Widget::updateStyleSheet);
} }
Widget::~Widget() = default; Widget::~Widget() = default;

View File

@ -8,6 +8,10 @@ set(SOURCES
framelessquickutils.cpp framelessquickutils.cpp
framelesshelperimageprovider.h framelesshelperimageprovider.h
framelesshelperimageprovider.cpp framelesshelperimageprovider.cpp
framelessquickeventfilter.h
framelessquickeventfilter.cpp
framelesshelper_quick.h
framelesshelper_quick.cpp
) )
if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC) if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC)

View File

@ -0,0 +1,54 @@
/*
* MIT License
*
* Copyright (C) 2022 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 "framelesshelper_quick.h"
#include <QtQml/qqmlengine.h>
#include "framelesshelperimageprovider.h"
#include "framelessquickhelper.h"
#include "framelessquickutils.h"
FRAMELESSHELPER_BEGIN_NAMESPACE
static constexpr const char FRAMELESSHELPER_QUICK_URI[] = "org.wangwenx190.FramelessHelper";
void FramelessHelper::Quick::registerTypes(QQmlEngine *engine)
{
Q_ASSERT(engine);
if (!engine) {
return;
}
engine->addImageProvider(QStringLiteral("framelesshelper"), new FramelessHelperImageProvider);
qmlRegisterSingletonType<FramelessQuickHelper>(FRAMELESSHELPER_QUICK_URI, 1, 0, "FramelessHelper", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject * {
Q_UNUSED(engine);
Q_UNUSED(scriptEngine);
return new FramelessQuickHelper;
});
qmlRegisterSingletonType<FramelessQuickUtils>(FRAMELESSHELPER_QUICK_URI, 1, 0, "FramelessUtils", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject * {
Q_UNUSED(engine);
Q_UNUSED(scriptEngine);
return new FramelessQuickUtils;
});
}
FRAMELESSHELPER_END_NAMESPACE

View File

@ -0,0 +1,42 @@
/*
* MIT License
*
* Copyright (C) 2022 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 "framelesshelperquick_global.h"
QT_BEGIN_NAMESPACE
class QQmlEngine;
QT_END_NAMESPACE
FRAMELESSHELPER_BEGIN_NAMESPACE
namespace FramelessHelper::Quick
{
FRAMELESSHELPER_QUICK_API void registerTypes(QQmlEngine *engine);
}
FRAMELESSHELPER_END_NAMESPACE

View File

@ -94,7 +94,7 @@ QPixmap FramelessHelperImageProvider::requestPixmap(const QString &id, QSize *si
if (!pixmapVar.isValid()) { if (!pixmapVar.isValid()) {
return {}; return {};
} }
if (static_cast<QMetaType::Type>(pixmapVar.typeId()) != QMetaType::QPixmap) { if (static_cast<QMetaType::Type>(pixmapVar.userType()) != QMetaType::QPixmap) {
return {}; return {};
} }
if (size) { if (size) {

View File

@ -31,14 +31,16 @@ FRAMELESSHELPER_BEGIN_NAMESPACE
class FRAMELESSHELPER_QUICK_API FramelessHelperImageProvider : public QQuickImageProvider class FRAMELESSHELPER_QUICK_API FramelessHelperImageProvider : public QQuickImageProvider
{ {
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
Q_OBJECT Q_OBJECT
#endif
Q_DISABLE_COPY_MOVE(FramelessHelperImageProvider) Q_DISABLE_COPY_MOVE(FramelessHelperImageProvider)
public: public:
explicit FramelessHelperImageProvider(); explicit FramelessHelperImageProvider();
~FramelessHelperImageProvider() override; ~FramelessHelperImageProvider() override;
Q_NODISCARD QPixmap requestPixmap(const QString &id, QSize *size, const QSize& requestedSize) override; Q_NODISCARD QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
}; };
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -0,0 +1,252 @@
/*
* MIT License
*
* Copyright (C) 2022 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 "framelessquickeventfilter.h"
#include <QtCore/qmutex.h>
#include <QtCore/qhash.h>
#include <QtCore/qlist.h>
#include <QtQuick/qquickwindow.h>
#include <QtQuick/qquickitem.h>
#include <utils.h>
FRAMELESSHELPER_BEGIN_NAMESPACE
struct EventFilterDataInternal
{
QQuickWindow *window = nullptr;
FramelessQuickEventFilter *eventFilter = nullptr;
QQuickItem *titleBarItem = nullptr;
QList<QQuickItem *> hitTestVisibleItems = {};
};
struct EventFilterData
{
QMutex mutex = {};
QHash<QQuickWindow *, EventFilterDataInternal> data = {};
explicit EventFilterData() = default;
~EventFilterData() = default;
private:
Q_DISABLE_COPY_MOVE(EventFilterData)
};
Q_GLOBAL_STATIC(EventFilterData, g_data)
[[nodiscard]] static inline bool isInTitleBarDraggableArea(QQuickWindow *window, const QPointF &pos)
{
Q_ASSERT(window);
if (!window) {
return false;
}
g_data()->mutex.lock();
if (!g_data()->data.contains(window)) {
g_data()->mutex.unlock();
return false;
}
const EventFilterDataInternal data = g_data()->data.value(window);
g_data()->mutex.unlock();
if (!data.titleBarItem) {
return false;
}
const auto mapGeometryToScene = [](const QQuickItem * const item) -> QRectF {
Q_ASSERT(item);
if (!item) {
return {};
}
return QRectF(item->mapToScene(QPointF(0.0, 0.0)), item->size());
};
QRegion region = mapGeometryToScene(data.titleBarItem).toRect();
if (!data.hitTestVisibleItems.isEmpty()) {
for (auto &&item : qAsConst(data.hitTestVisibleItems)) {
Q_ASSERT(item);
if (item) {
region -= mapGeometryToScene(item).toRect();
}
}
}
return region.contains(pos.toPoint());
}
FramelessQuickEventFilter::FramelessQuickEventFilter(QObject *parent) : QObject(parent) {}
FramelessQuickEventFilter::~FramelessQuickEventFilter() = default;
void FramelessQuickEventFilter::addWindow(QQuickWindow *window)
{
Q_ASSERT(window);
if (!window) {
return;
}
g_data()->mutex.lock();
if (g_data()->data.contains(window)) {
g_data()->mutex.unlock();
return;
}
auto data = EventFilterDataInternal{};
data.window = window;
data.eventFilter = new FramelessQuickEventFilter(window);
g_data()->data.insert(window, data);
g_data()->mutex.unlock();
window->installEventFilter(data.eventFilter);
}
void FramelessQuickEventFilter::removeWindow(QQuickWindow *window)
{
Q_ASSERT(window);
if (!window) {
return;
}
g_data()->mutex.lock();
if (!g_data()->data.contains(window)) {
g_data()->mutex.unlock();
return;
}
const EventFilterDataInternal data = g_data()->data.value(window);
g_data()->data.remove(window);
g_data()->mutex.unlock();
window->removeEventFilter(data.eventFilter);
delete data.eventFilter;
}
void FramelessQuickEventFilter::setTitleBarItem(QQuickWindow *window, QQuickItem *item)
{
Q_ASSERT(window);
Q_ASSERT(item);
if (!window || !item) {
return;
}
QMutexLocker locker(&g_data()->mutex);
if (!g_data()->data.contains(window)) {
return;
}
g_data()->data[window].titleBarItem = item;
}
void FramelessQuickEventFilter::setHitTestVisible(QQuickWindow *window, QQuickItem *item, const bool visible)
{
Q_ASSERT(window);
Q_ASSERT(item);
if (!window || !item) {
return;
}
QMutexLocker locker(&g_data()->mutex);
if (!g_data()->data.contains(window)) {
return;
}
auto &items = g_data()->data[window].hitTestVisibleItems;
const bool exists = items.contains(item);
if (visible && !exists) {
items.append(item);
}
if (!visible && exists) {
items.removeAll(item);
}
}
bool FramelessQuickEventFilter::eventFilter(QObject *object, QEvent *event)
{
Q_ASSERT(object);
Q_ASSERT(event);
if (!object || !event) {
return false;
}
if (!object->isWindowType()) {
return false;
}
const auto window = qobject_cast<QQuickWindow *>(object);
if (!window) {
return false;
}
g_data()->mutex.lock();
if (!g_data()->data.contains(window)) {
g_data()->mutex.unlock();
return false;
}
g_data()->mutex.unlock();
const QEvent::Type eventType = event->type();
if ((eventType != QEvent::MouseButtonPress)
&& (eventType != QEvent::MouseButtonRelease) && (eventType != QEvent::MouseButtonDblClick)) {
return false;
}
const auto mouseEvent = static_cast<QMouseEvent *>(event);
const Qt::MouseButton button = mouseEvent->button();
if ((button != Qt::LeftButton) && (button != Qt::RightButton)) {
return false;
}
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const QPointF scenePos = mouseEvent->scenePosition();
#else
const QPointF scenePos = mouseEvent->windowPos();
#endif
const bool titleBar = isInTitleBarDraggableArea(window, scenePos);
switch (eventType) {
case QEvent::MouseButtonPress: {
if (button != Qt::LeftButton) {
return false;
}
if (!titleBar) {
return false;
}
Utils::startSystemMove(window);
return true;
}
case QEvent::MouseButtonRelease: {
if (button != Qt::RightButton) {
return false;
}
if (!titleBar) {
return false;
}
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const QPointF globalPos = mouseEvent->globalPosition();
#else
const QPointF globalPos = mouseEvent->globalPos();
#endif
const QPointF nativePos = QPointF(globalPos * window->effectiveDevicePixelRatio());
Utils::showSystemMenu(window->winId(), nativePos);
return true;
}
case QEvent::MouseButtonDblClick: {
if (button != Qt::LeftButton) {
return false;
}
if (!titleBar) {
return false;
}
const QQuickWindow::Visibility visibility = window->visibility();
if ((visibility == QQuickWindow::Maximized) || (visibility == QQuickWindow::FullScreen)) {
window->showNormal();
} else {
window->showMaximized();
}
return true;
}
default:
break;
}
return false;
}
FRAMELESSHELPER_END_NAMESPACE

View File

@ -0,0 +1,55 @@
/*
* MIT License
*
* Copyright (C) 2022 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 "framelesshelperquick_global.h"
#include <QtCore/qobject.h>
QT_BEGIN_NAMESPACE
class QQuickWindow;
class QQuickItem;
QT_END_NAMESPACE
FRAMELESSHELPER_BEGIN_NAMESPACE
class FRAMELESSHELPER_QUICK_API FramelessQuickEventFilter : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(FramelessQuickEventFilter)
public:
explicit FramelessQuickEventFilter(QObject *parent = nullptr);
~FramelessQuickEventFilter() override;
Q_INVOKABLE static void addWindow(QQuickWindow *window);
Q_INVOKABLE static void removeWindow(QQuickWindow *window);
Q_INVOKABLE static void setTitleBarItem(QQuickWindow *window, QQuickItem *item);
Q_INVOKABLE static void setHitTestVisible(QQuickWindow *window, QQuickItem *item, const bool visible);
protected:
Q_NODISCARD bool eventFilter(QObject *object, QEvent *event) override;
};
FRAMELESSHELPER_END_NAMESPACE

View File

@ -23,56 +23,54 @@
*/ */
#include "framelessquickhelper.h" #include "framelessquickhelper.h"
#include <QtQml/qqmlengine.h> #include <QtQuick/qquickwindow.h>
#include "framelesshelperimageprovider.h"
#include "framelessquickutils.h"
#include <framelesswindowsmanager.h> #include <framelesswindowsmanager.h>
#include "framelessquickeventfilter.h"
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
static constexpr const char FRAMELESSHELPER_QUICK_URI[] = "org.wangwenx190.FramelessHelper";
FramelessQuickHelper::FramelessQuickHelper(QObject *parent) : QObject(parent) {} FramelessQuickHelper::FramelessQuickHelper(QObject *parent) : QObject(parent) {}
FramelessQuickHelper::~FramelessQuickHelper() = default; FramelessQuickHelper::~FramelessQuickHelper() = default;
void FramelessQuickHelper::registerTypes(QQmlEngine *engine) void FramelessQuickHelper::addWindow(QQuickWindow *window)
{
Q_ASSERT(engine);
if (!engine) {
return;
}
engine->addImageProvider(QStringLiteral("framelesshelper"), new FramelessHelperImageProvider);
qmlRegisterSingletonType<FramelessQuickHelper>(FRAMELESSHELPER_QUICK_URI, 1, 0, "FramelessHelper", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject * {
Q_UNUSED(engine);
Q_UNUSED(scriptEngine);
const auto framelessHelper = new FramelessQuickHelper;
return framelessHelper;
});
qmlRegisterSingletonType<FramelessQuickUtils>(FRAMELESSHELPER_QUICK_URI, 1, 0, "FramelessUtils", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject * {
Q_UNUSED(engine);
Q_UNUSED(scriptEngine);
const auto framelessUtils = new FramelessQuickUtils;
return framelessUtils;
});
}
void FramelessQuickHelper::addWindow(QWindow *window)
{ {
Q_ASSERT(window); Q_ASSERT(window);
if (!window) { if (!window) {
return; return;
} }
FramelessWindowsManager::instance()->addWindow(window); FramelessWindowsManager::instance()->addWindow(window);
FramelessQuickEventFilter::addWindow(window);
} }
void FramelessQuickHelper::removeWindow(QWindow *window) void FramelessQuickHelper::removeWindow(QQuickWindow *window)
{ {
Q_ASSERT(window); Q_ASSERT(window);
if (!window) { if (!window) {
return; return;
} }
FramelessQuickEventFilter::removeWindow(window);
FramelessWindowsManager::instance()->removeWindow(window); FramelessWindowsManager::instance()->removeWindow(window);
} }
void FramelessQuickHelper::setTitleBarItem(QQuickWindow *window, QQuickItem *item)
{
Q_ASSERT(window);
Q_ASSERT(item);
if (!window || !item) {
return;
}
FramelessQuickEventFilter::setTitleBarItem(window, item);
}
void FramelessQuickHelper::setHitTestVisible(QQuickWindow *window, QQuickItem *item, const bool visible)
{
Q_ASSERT(window);
Q_ASSERT(item);
if (!window || !item) {
return;
}
FramelessQuickEventFilter::setHitTestVisible(window, item, visible);
}
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -28,8 +28,8 @@
#include <QtCore/qobject.h> #include <QtCore/qobject.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QWindow; class QQuickWindow;
class QQmlEngine; class QQuickItem;
QT_END_NAMESPACE QT_END_NAMESPACE
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
@ -49,10 +49,10 @@ public:
explicit FramelessQuickHelper(QObject *parent = nullptr); explicit FramelessQuickHelper(QObject *parent = nullptr);
~FramelessQuickHelper() override; ~FramelessQuickHelper() override;
Q_INVOKABLE static void registerTypes(QQmlEngine *engine); Q_INVOKABLE static void addWindow(QQuickWindow *window);
Q_INVOKABLE static void removeWindow(QQuickWindow *window);
Q_INVOKABLE static void addWindow(QWindow *window); Q_INVOKABLE static void setTitleBarItem(QQuickWindow *window, QQuickItem *item);
Q_INVOKABLE static void removeWindow(QWindow *window); Q_INVOKABLE static void setHitTestVisible(QQuickWindow *window, QQuickItem *item, const bool visible);
}; };
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -23,6 +23,7 @@
*/ */
#include "framelessquickutils.h" #include "framelessquickutils.h"
#include <QtQuick/qquickwindow.h>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 1)) #if (QT_VERSION >= QT_VERSION_CHECK(6, 2, 1))
# include <QtGui/qpa/qplatformtheme.h> # include <QtGui/qpa/qplatformtheme.h>
# include <QtGui/private/qguiapplication_p.h> # include <QtGui/private/qguiapplication_p.h>
@ -119,7 +120,7 @@ bool FramelessQuickUtils::titleBarColorVisible()
#endif #endif
} }
void FramelessQuickUtils::showMinimized2(QWindow *window) void FramelessQuickUtils::showMinimized2(QQuickWindow *window)
{ {
Q_ASSERT(window); Q_ASSERT(window);
if (!window) { if (!window) {
@ -136,7 +137,7 @@ void FramelessQuickUtils::showMinimized2(QWindow *window)
#endif #endif
} }
void FramelessQuickUtils::showSystemMenu(QWindow *window, const QPointF &pos) void FramelessQuickUtils::showSystemMenu(QQuickWindow *window, const QPointF &pos)
{ {
Q_ASSERT(window); Q_ASSERT(window);
if (!window) { if (!window) {
@ -144,15 +145,16 @@ void FramelessQuickUtils::showSystemMenu(QWindow *window, const QPointF &pos)
} }
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
# if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) # if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const QPointF globalPos = window->mapToGlobal(pos) * window->devicePixelRatio(); const QPointF globalPos = window->mapToGlobal(pos);
# else # else
const QPointF globalPos = QPointF(window->mapToGlobal(pos.toPoint())) * window->devicePixelRatio(); const QPointF globalPos = window->mapToGlobal(pos.toPoint());
# endif # endif
Utils::showSystemMenu(window->winId(), globalPos); const QPointF nativePos = QPointF(globalPos * window->effectiveDevicePixelRatio());
Utils::showSystemMenu(window->winId(), nativePos);
#endif #endif
} }
void FramelessQuickUtils::startSystemMove2(QWindow *window) void FramelessQuickUtils::startSystemMove2(QQuickWindow *window)
{ {
Q_ASSERT(window); Q_ASSERT(window);
if (!window) { if (!window) {
@ -165,7 +167,7 @@ void FramelessQuickUtils::startSystemMove2(QWindow *window)
#endif #endif
} }
void FramelessQuickUtils::startSystemResize2(QWindow *window, const Qt::Edges edges) void FramelessQuickUtils::startSystemResize2(QQuickWindow *window, const Qt::Edges edges)
{ {
Q_ASSERT(window); Q_ASSERT(window);
if (!window) { if (!window) {

View File

@ -29,7 +29,7 @@
#include <QtGui/qcolor.h> #include <QtGui/qcolor.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QWindow; class QQuickWindow;
QT_END_NAMESPACE QT_END_NAMESPACE
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
@ -66,10 +66,10 @@ public:
Q_NODISCARD static QColor systemAccentColor(); Q_NODISCARD static QColor systemAccentColor();
Q_NODISCARD static bool titleBarColorVisible(); Q_NODISCARD static bool titleBarColorVisible();
Q_INVOKABLE static void showMinimized2(QWindow *window); Q_INVOKABLE static void showMinimized2(QQuickWindow *window);
Q_INVOKABLE static void showSystemMenu(QWindow *window, const QPointF &pos); Q_INVOKABLE static void showSystemMenu(QQuickWindow *window, const QPointF &pos);
Q_INVOKABLE static void startSystemMove2(QWindow *window); Q_INVOKABLE static void startSystemMove2(QQuickWindow *window);
Q_INVOKABLE static void startSystemResize2(QWindow *window, const Qt::Edges edges); Q_INVOKABLE static void startSystemResize2(QQuickWindow *window, const Qt::Edges edges);
Q_SIGNALS: Q_SIGNALS:
void frameBorderActiveColorChanged(); void frameBorderActiveColorChanged();

View File

@ -29,8 +29,7 @@ FRAMELESSHELPER_BEGIN_NAMESPACE
FramelessMainWindow::FramelessMainWindow(QWidget *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) FramelessMainWindow::FramelessMainWindow(QWidget *parent, Qt::WindowFlags flags) : QMainWindow(parent, flags)
{ {
m_helper.reset(new FramelessWidgetsHelper(this, this)); m_helper.reset(new FramelessWidgetsHelper(this, WindowLayout::Custom));
m_helper->initialize();
} }
FramelessMainWindow::~FramelessMainWindow() = default; FramelessMainWindow::~FramelessMainWindow() = default;
@ -60,6 +59,11 @@ void FramelessMainWindow::setHitTestVisible(QWidget *widget, const bool visible)
m_helper->setHitTestVisible(widget, visible); m_helper->setHitTestVisible(widget, visible);
} }
void FramelessMainWindow::toggleMaximized()
{
m_helper->toggleMaximized();
}
void FramelessMainWindow::showEvent(QShowEvent *event) void FramelessMainWindow::showEvent(QShowEvent *event)
{ {
QMainWindow::showEvent(event); QMainWindow::showEvent(event);
@ -84,6 +88,12 @@ void FramelessMainWindow::mousePressEvent(QMouseEvent *event)
m_helper->mousePressEventHandler(event); m_helper->mousePressEventHandler(event);
} }
void FramelessMainWindow::mouseReleaseEvent(QMouseEvent *event)
{
QMainWindow::mouseReleaseEvent(event);
m_helper->mouseReleaseEventHandler(event);
}
void FramelessMainWindow::mouseDoubleClickEvent(QMouseEvent *event) void FramelessMainWindow::mouseDoubleClickEvent(QMouseEvent *event)
{ {
QMainWindow::mouseDoubleClickEvent(event); QMainWindow::mouseDoubleClickEvent(event);

View File

@ -38,23 +38,25 @@ class FRAMELESSHELPER_WIDGETS_API FramelessMainWindow : public QMainWindow
Q_PROPERTY(QWidget* titleBarWidget READ titleBarWidget WRITE setTitleBarWidget NOTIFY titleBarWidgetChanged FINAL) Q_PROPERTY(QWidget* titleBarWidget READ titleBarWidget WRITE setTitleBarWidget NOTIFY titleBarWidgetChanged FINAL)
public: public:
explicit FramelessMainWindow(QWidget *parent = nullptr, Qt::WindowFlags flags = {}, explicit FramelessMainWindow(QWidget *parent = nullptr, Qt::WindowFlags flags = {});
const WindowLayout wl = WindowLayout::Standard);
~FramelessMainWindow() override; ~FramelessMainWindow() override;
Q_NODISCARD bool isNormal() const; Q_NODISCARD Q_INVOKABLE bool isNormal() const;
Q_NODISCARD bool isZoomed() const; Q_NODISCARD Q_INVOKABLE bool isZoomed() const;
void setTitleBarWidget(QWidget *widget); void setTitleBarWidget(QWidget *widget);
Q_NODISCARD QWidget *titleBarWidget() const; Q_NODISCARD QWidget *titleBarWidget() const;
Q_INVOKABLE void setHitTestVisible(QWidget *widget, const bool visible); Q_INVOKABLE void setHitTestVisible(QWidget *widget, const bool visible);
Q_INVOKABLE void toggleMaximized();
protected: protected:
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;
void changeEvent(QEvent *event) override; void changeEvent(QEvent *event) override;
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override;
Q_SIGNALS: Q_SIGNALS:

View File

@ -27,10 +27,9 @@
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
FramelessWidget::FramelessWidget(QWidget *parent) : QWidget(parent) FramelessWidget::FramelessWidget(QWidget *parent, const WindowLayout wl) : QWidget(parent)
{ {
m_helper.reset(new FramelessWidgetsHelper(this, this)); m_helper.reset(new FramelessWidgetsHelper(this, wl));
m_helper->initialize();
} }
FramelessWidget::~FramelessWidget() = default; FramelessWidget::~FramelessWidget() = default;
@ -70,6 +69,11 @@ void FramelessWidget::setHitTestVisible(QWidget *widget, const bool visible)
m_helper->setHitTestVisible(widget, visible); m_helper->setHitTestVisible(widget, visible);
} }
void FramelessWidget::toggleMaximized()
{
m_helper->toggleMaximized();
}
void FramelessWidget::showEvent(QShowEvent *event) void FramelessWidget::showEvent(QShowEvent *event)
{ {
QWidget::showEvent(event); QWidget::showEvent(event);
@ -94,6 +98,12 @@ void FramelessWidget::mousePressEvent(QMouseEvent *event)
m_helper->mousePressEventHandler(event); m_helper->mousePressEventHandler(event);
} }
void FramelessWidget::mouseReleaseEvent(QMouseEvent *event)
{
QWidget::mouseReleaseEvent(event);
m_helper->mouseReleaseEventHandler(event);
}
void FramelessWidget::mouseDoubleClickEvent(QMouseEvent *event) void FramelessWidget::mouseDoubleClickEvent(QMouseEvent *event)
{ {
QWidget::mouseDoubleClickEvent(event); QWidget::mouseDoubleClickEvent(event);

View File

@ -42,8 +42,8 @@ public:
explicit FramelessWidget(QWidget *parent = nullptr, const WindowLayout wl = WindowLayout::Standard); explicit FramelessWidget(QWidget *parent = nullptr, const WindowLayout wl = WindowLayout::Standard);
~FramelessWidget() override; ~FramelessWidget() override;
Q_NODISCARD bool isNormal() const; Q_NODISCARD Q_INVOKABLE bool isNormal() const;
Q_NODISCARD bool isZoomed() const; Q_NODISCARD Q_INVOKABLE bool isZoomed() const;
void setTitleBarWidget(QWidget *widget); void setTitleBarWidget(QWidget *widget);
Q_NODISCARD QWidget *titleBarWidget() const; Q_NODISCARD QWidget *titleBarWidget() const;
@ -53,11 +53,14 @@ public:
Q_INVOKABLE void setHitTestVisible(QWidget *widget, const bool visible); Q_INVOKABLE void setHitTestVisible(QWidget *widget, const bool visible);
Q_INVOKABLE void toggleMaximized();
protected: protected:
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;
void changeEvent(QEvent *event) override; void changeEvent(QEvent *event) override;
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override;
Q_SIGNALS: Q_SIGNALS:

View File

@ -34,8 +34,6 @@
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
static constexpr const char QT_MAINWINDOW_CLASS_NAME[] = "QMainWindow";
static const QString kSystemButtonStyleSheet = QStringLiteral(R"( static const QString kSystemButtonStyleSheet = QStringLiteral(R"(
QPushButton { QPushButton {
border-style: none; border-style: none;
@ -82,18 +80,19 @@ void FramelessWidgetsHelper::setTitleBarWidget(QWidget *widget)
if (m_userTitleBarWidget == widget) { if (m_userTitleBarWidget == widget) {
return; return;
} }
if (isStandardLayout()) {
if (m_systemTitleBarWidget && m_systemTitleBarWidget->isVisible()) { if (m_systemTitleBarWidget && m_systemTitleBarWidget->isVisible()) {
m_mainLayout->removeWidget(m_systemTitleBarWidget);
m_systemTitleBarWidget->hide(); m_systemTitleBarWidget->hide();
} }
if (isMainWindow()) {
m_userTitleBarWidget = widget;
} else {
if (m_userTitleBarWidget) { if (m_userTitleBarWidget) {
m_mainLayout->removeWidget(m_userTitleBarWidget); m_mainLayout->removeWidget(m_userTitleBarWidget);
m_userTitleBarWidget = nullptr; m_userTitleBarWidget = nullptr;
} }
m_userTitleBarWidget = widget; m_userTitleBarWidget = widget;
m_mainLayout->insertWidget(0, m_userTitleBarWidget); m_mainLayout->insertWidget(0, m_userTitleBarWidget);
} else {
m_userTitleBarWidget = widget;
} }
QMetaObject::invokeMethod(q, "titleBarWidgetChanged"); QMetaObject::invokeMethod(q, "titleBarWidgetChanged");
} }
@ -109,7 +108,7 @@ void FramelessWidgetsHelper::setContentWidget(QWidget *widget)
if (!widget) { if (!widget) {
return; return;
} }
if (isMainWindow()) { if (isCustomLayout()) {
return; return;
} }
if (m_userContentWidget == widget) { if (m_userContentWidget == widget) {
@ -126,9 +125,6 @@ void FramelessWidgetsHelper::setContentWidget(QWidget *widget)
QWidget *FramelessWidgetsHelper::contentWidget() const QWidget *FramelessWidgetsHelper::contentWidget() const
{ {
if (isMainWindow()) {
return nullptr;
}
return m_userContentWidget; return m_userContentWidget;
} }
@ -149,33 +145,46 @@ void FramelessWidgetsHelper::setHitTestVisible(QWidget *widget, const bool visib
void FramelessWidgetsHelper::showEventHandler(QShowEvent *event) void FramelessWidgetsHelper::showEventHandler(QShowEvent *event)
{ {
Q_UNUSED(event); Q_ASSERT(event);
if (!event) {
return;
}
setupFramelessHelperOnce(); setupFramelessHelperOnce();
} }
void FramelessWidgetsHelper::changeEventHandler(QEvent *event) void FramelessWidgetsHelper::changeEventHandler(QEvent *event)
{ {
bool shouldUpdate = false; Q_ASSERT(event);
if (event->type() == QEvent::WindowStateChange) { if (!event) {
return;
}
const QEvent::Type type = event->type();
if ((type != QEvent::WindowStateChange) && (type != QEvent::ActivationChange)) {
return;
}
if (type == QEvent::WindowStateChange) {
if (isStandardLayout()) {
if (isZoomed()) { if (isZoomed()) {
m_systemMaximizeButton->setToolTip(tr("Restore")); m_systemMaximizeButton->setToolTip(tr("Restore"));
} else { } else {
m_systemMaximizeButton->setToolTip(tr("Maximize")); m_systemMaximizeButton->setToolTip(tr("Maximize"));
} }
updateContentsMargins();
updateSystemButtonsIcon(); updateSystemButtonsIcon();
shouldUpdate = true;
} else if (event->type() == QEvent::ActivationChange) {
shouldUpdate = true;
} }
if (shouldUpdate) { updateContentsMargins();
}
if (isStandardLayout()) {
updateSystemTitleBarStyleSheet(); updateSystemTitleBarStyleSheet();
} }
q->update();
} }
void FramelessWidgetsHelper::paintEventHandler(QPaintEvent *event) void FramelessWidgetsHelper::paintEventHandler(QPaintEvent *event)
{ {
Q_UNUSED(event); Q_ASSERT(event);
if (!event) {
return;
}
if (!shouldDrawFrameBorder()) { if (!shouldDrawFrameBorder()) {
return; return;
} }
@ -191,39 +200,63 @@ void FramelessWidgetsHelper::paintEventHandler(QPaintEvent *event)
void FramelessWidgetsHelper::mousePressEventHandler(QMouseEvent *event) void FramelessWidgetsHelper::mousePressEventHandler(QMouseEvent *event)
{ {
const Qt::MouseButton button = event->button(); Q_ASSERT(event);
if ((button != Qt::LeftButton) && (button != Qt::RightButton)) { if (!event) {
return;
}
if (event->button() != Qt::LeftButton) {
return;
}
if (!isInTitleBarDraggableArea(event->pos())) {
return; return;
} }
if (isInTitleBarDraggableArea(event->pos())) {
if (button == Qt::LeftButton) {
Utils::startSystemMove(q->windowHandle()); Utils::startSystemMove(q->windowHandle());
} else { }
void FramelessWidgetsHelper::mouseReleaseEventHandler(QMouseEvent *event)
{
Q_ASSERT(event);
if (!event) {
return;
}
if (event->button() != Qt::RightButton) {
return;
}
if (!isInTitleBarDraggableArea(event->pos())) {
return;
}
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
# if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) # if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const QPointF globalPos = event->globalPosition(); const QPointF globalPos = event->globalPosition();
# else # else
const QPointF globalPos = event->globalPos(); const QPointF globalPos = event->globalPos();
# endif # endif
const QPointF pos = globalPos * q->devicePixelRatioF(); const QPointF nativePos = QPointF(globalPos * q->devicePixelRatioF());
Utils::showSystemMenu(q->winId(), pos); Utils::showSystemMenu(q->winId(), nativePos);
#endif #endif
}
}
} }
void FramelessWidgetsHelper::mouseDoubleClickEventHandler(QMouseEvent *event) void FramelessWidgetsHelper::mouseDoubleClickEventHandler(QMouseEvent *event)
{ {
Q_ASSERT(event);
if (!event) {
return;
}
if (event->button() != Qt::LeftButton) { if (event->button() != Qt::LeftButton) {
return; return;
} }
if (isInTitleBarDraggableArea(event->pos())) { if (!isInTitleBarDraggableArea(event->pos())) {
m_systemMaximizeButton->click(); return;
} }
toggleMaximized();
} }
void FramelessWidgetsHelper::initialize() void FramelessWidgetsHelper::initialize()
{ {
if (m_initialized) {
return;
}
m_initialized = true;
q->setAttribute(Qt::WA_DontCreateNativeAncestors); q->setAttribute(Qt::WA_DontCreateNativeAncestors);
q->createWinId(); q->createWinId();
setupInitialUi(); setupInitialUi();
@ -231,15 +264,18 @@ void FramelessWidgetsHelper::initialize()
void FramelessWidgetsHelper::setupFramelessHelperOnce() void FramelessWidgetsHelper::setupFramelessHelperOnce()
{ {
if (m_framelessHelperInited) { if (m_framelessHelperSetup) {
return; return;
} }
m_framelessHelperInited = true; m_framelessHelperSetup = true;
FramelessWindowsManager *manager = FramelessWindowsManager::instance(); FramelessWindowsManager *manager = FramelessWindowsManager::instance();
manager->addWindow(q->windowHandle()); manager->addWindow(q->windowHandle());
connect(manager, &FramelessWindowsManager::systemThemeChanged, this, [this](){ connect(manager, &FramelessWindowsManager::systemThemeChanged, this, [this](){
if (isStandardLayout()) {
updateSystemTitleBarStyleSheet(); updateSystemTitleBarStyleSheet();
updateSystemButtonsIcon(); updateSystemButtonsIcon();
q->update();
}
QMetaObject::invokeMethod(q, "systemThemeChanged"); QMetaObject::invokeMethod(q, "systemThemeChanged");
}); });
connect(manager, &FramelessWindowsManager::systemMenuRequested, this, [this](const QPointF &pos){ connect(manager, &FramelessWindowsManager::systemMenuRequested, this, [this](const QPointF &pos){
@ -249,7 +285,7 @@ void FramelessWidgetsHelper::setupFramelessHelperOnce()
void FramelessWidgetsHelper::createSystemTitleBar() void FramelessWidgetsHelper::createSystemTitleBar()
{ {
if (isCustomWindow()) { if (isCustomLayout()) {
return; return;
} }
m_systemTitleBarWidget = new QWidget(q); m_systemTitleBarWidget = new QWidget(q);
@ -271,14 +307,7 @@ void FramelessWidgetsHelper::createSystemTitleBar()
m_systemMaximizeButton->setFixedSize(kDefaultSystemButtonSize); m_systemMaximizeButton->setFixedSize(kDefaultSystemButtonSize);
m_systemMaximizeButton->setIconSize(kDefaultSystemButtonIconSize); m_systemMaximizeButton->setIconSize(kDefaultSystemButtonIconSize);
m_systemMaximizeButton->setToolTip(tr("Maximize")); m_systemMaximizeButton->setToolTip(tr("Maximize"));
connect(m_systemMaximizeButton, &QPushButton::clicked, this, [this](){ connect(m_systemMaximizeButton, &QPushButton::clicked, this, &FramelessWidgetsHelper::toggleMaximized);
if (isZoomed()) {
q->showNormal();
} else {
q->showMaximized();
}
updateSystemButtonsIcon();
});
m_systemCloseButton = new QPushButton(m_systemTitleBarWidget); m_systemCloseButton = new QPushButton(m_systemTitleBarWidget);
m_systemCloseButton->setFixedSize(kDefaultSystemButtonSize); m_systemCloseButton->setFixedSize(kDefaultSystemButtonSize);
m_systemCloseButton->setIconSize(kDefaultSystemButtonIconSize); m_systemCloseButton->setIconSize(kDefaultSystemButtonIconSize);
@ -299,7 +328,7 @@ void FramelessWidgetsHelper::createSystemTitleBar()
void FramelessWidgetsHelper::createUserContentContainer() void FramelessWidgetsHelper::createUserContentContainer()
{ {
if (isCustomWindow() || isMainWindow()) { if (isCustomLayout()) {
return; return;
} }
m_userContentContainerWidget = new QWidget(q); m_userContentContainerWidget = new QWidget(q);
@ -312,11 +341,8 @@ void FramelessWidgetsHelper::createUserContentContainer()
void FramelessWidgetsHelper::setupInitialUi() void FramelessWidgetsHelper::setupInitialUi()
{ {
if (isStandardWindow()) { if (isStandardLayout()) {
createSystemTitleBar(); createSystemTitleBar();
if (isMainWindow()) {
// ### TODO
} else {
createUserContentContainer(); createUserContentContainer();
m_mainLayout = new QVBoxLayout(q); m_mainLayout = new QVBoxLayout(q);
m_mainLayout->setContentsMargins(0, 0, 0, 0); m_mainLayout->setContentsMargins(0, 0, 0, 0);
@ -324,8 +350,8 @@ void FramelessWidgetsHelper::setupInitialUi()
m_mainLayout->addWidget(m_systemTitleBarWidget); m_mainLayout->addWidget(m_systemTitleBarWidget);
m_mainLayout->addWidget(m_userContentContainerWidget); m_mainLayout->addWidget(m_userContentContainerWidget);
q->setLayout(m_mainLayout); q->setLayout(m_mainLayout);
}
updateSystemTitleBarStyleSheet(); updateSystemTitleBarStyleSheet();
q->update();
} }
updateContentsMargins(); updateContentsMargins();
} }
@ -333,23 +359,30 @@ void FramelessWidgetsHelper::setupInitialUi()
bool FramelessWidgetsHelper::isInTitleBarDraggableArea(const QPoint &pos) const bool FramelessWidgetsHelper::isInTitleBarDraggableArea(const QPoint &pos) const
{ {
const QRegion draggableRegion = [this]() -> QRegion { const QRegion draggableRegion = [this]() -> QRegion {
const auto mapGeometryToScene = [this](const QWidget * const widget) -> QRect {
Q_ASSERT(widget);
if (!widget) {
return {};
}
return QRect(widget->mapTo(q, QPoint(0, 0)), widget->size());
};
if (m_userTitleBarWidget) { if (m_userTitleBarWidget) {
QRegion region = {QRect(QPoint(0, 0), m_userTitleBarWidget->size())}; QRegion region = mapGeometryToScene(m_userTitleBarWidget);
if (!m_hitTestVisibleWidgets.isEmpty()) { if (!m_hitTestVisibleWidgets.isEmpty()) {
for (auto &&widget : qAsConst(m_hitTestVisibleWidgets)) { for (auto &&widget : qAsConst(m_hitTestVisibleWidgets)) {
Q_ASSERT(widget); Q_ASSERT(widget);
if (widget) { if (widget) {
region -= widget->geometry(); region -= mapGeometryToScene(widget);
} }
} }
} }
return region; return region;
} }
if (isStandardWindow()) { if (isStandardLayout()) {
QRegion region = {QRect(QPoint(0, 0), m_systemTitleBarWidget->size())}; QRegion region = mapGeometryToScene(m_systemTitleBarWidget);
region -= m_systemMinimizeButton->geometry(); region -= mapGeometryToScene(m_systemMinimizeButton);
region -= m_systemMaximizeButton->geometry(); region -= mapGeometryToScene(m_systemMaximizeButton);
region -= m_systemCloseButton->geometry(); region -= mapGeometryToScene(m_systemCloseButton);
return region; return region;
} }
return {}; return {};
@ -366,20 +399,12 @@ bool FramelessWidgetsHelper::shouldDrawFrameBorder() const
#endif #endif
} }
bool FramelessWidgetsHelper::isMainWindow() const bool FramelessWidgetsHelper::isStandardLayout() const
{
if (!q) {
return false;
}
return q->inherits(QT_MAINWINDOW_CLASS_NAME);
}
bool FramelessWidgetsHelper::isStandardWindow() const
{ {
return (m_windowLayout == WindowLayout::Standard); return (m_windowLayout == WindowLayout::Standard);
} }
bool FramelessWidgetsHelper::isCustomWindow() bool FramelessWidgetsHelper::isCustomLayout() const
{ {
return (m_windowLayout == WindowLayout::Custom); return (m_windowLayout == WindowLayout::Custom);
} }
@ -393,7 +418,7 @@ void FramelessWidgetsHelper::updateContentsMargins()
void FramelessWidgetsHelper::updateSystemTitleBarStyleSheet() void FramelessWidgetsHelper::updateSystemTitleBarStyleSheet()
{ {
if (isCustomWindow()) { if (isCustomLayout()) {
return; return;
} }
const bool active = q->isActiveWindow(); const bool active = q->isActiveWindow();
@ -424,22 +449,31 @@ void FramelessWidgetsHelper::updateSystemTitleBarStyleSheet()
m_systemMaximizeButton->setStyleSheet(kSystemButtonStyleSheet); m_systemMaximizeButton->setStyleSheet(kSystemButtonStyleSheet);
m_systemCloseButton->setStyleSheet(kSystemButtonStyleSheet); m_systemCloseButton->setStyleSheet(kSystemButtonStyleSheet);
m_systemTitleBarWidget->setStyleSheet(QStringLiteral("background-color: %1;").arg(systemTitleBarWidgetBackgroundColor.name())); m_systemTitleBarWidget->setStyleSheet(QStringLiteral("background-color: %1;").arg(systemTitleBarWidgetBackgroundColor.name()));
q->update();
} }
void FramelessWidgetsHelper::updateSystemButtonsIcon() void FramelessWidgetsHelper::updateSystemButtonsIcon()
{ {
if (isCustomWindow()) { if (isCustomLayout()) {
return; return;
} }
const SystemTheme theme = ((Utils::shouldAppsUseDarkMode() || Utils::isTitleBarColorized()) ? SystemTheme::Dark : SystemTheme::Light); const SystemTheme theme = ((Utils::shouldAppsUseDarkMode() || Utils::isTitleBarColorized()) ? SystemTheme::Dark : SystemTheme::Light);
m_systemMinimizeButton->setIcon(qvariant_cast<QIcon>(Utils::getSystemButtonIconResource(SystemButtonType::Minimize, theme, ResourceType::Icon))); const ResourceType resource = ResourceType::Icon;
m_systemMinimizeButton->setIcon(qvariant_cast<QIcon>(Utils::getSystemButtonIconResource(SystemButtonType::Minimize, theme, resource)));
if (isZoomed()) { if (isZoomed()) {
m_systemMaximizeButton->setIcon(qvariant_cast<QIcon>(Utils::getSystemButtonIconResource(SystemButtonType::Restore, theme, ResourceType::Icon))); m_systemMaximizeButton->setIcon(qvariant_cast<QIcon>(Utils::getSystemButtonIconResource(SystemButtonType::Restore, theme, resource)));
} else { } else {
m_systemMaximizeButton->setIcon(qvariant_cast<QIcon>(Utils::getSystemButtonIconResource(SystemButtonType::Maximize, theme, ResourceType::Icon))); m_systemMaximizeButton->setIcon(qvariant_cast<QIcon>(Utils::getSystemButtonIconResource(SystemButtonType::Maximize, theme, resource)));
}
m_systemCloseButton->setIcon(qvariant_cast<QIcon>(Utils::getSystemButtonIconResource(SystemButtonType::Close, theme, resource)));
}
void FramelessWidgetsHelper::toggleMaximized()
{
if (isZoomed()) {
q->showNormal();
} else {
q->showMaximized();
} }
m_systemCloseButton->setIcon(qvariant_cast<QIcon>(Utils::getSystemButtonIconResource(SystemButtonType::Close, theme, ResourceType::Icon)));
} }
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -59,11 +59,14 @@ public:
Q_INVOKABLE void setHitTestVisible(QWidget *widget, const bool visible); Q_INVOKABLE void setHitTestVisible(QWidget *widget, const bool visible);
void showEventHandler(QShowEvent *event); Q_INVOKABLE void toggleMaximized();
void changeEventHandler(QEvent *event);
void paintEventHandler(QPaintEvent *event); Q_INVOKABLE void showEventHandler(QShowEvent *event);
void mousePressEventHandler(QMouseEvent *event); Q_INVOKABLE void changeEventHandler(QEvent *event);
void mouseDoubleClickEventHandler(QMouseEvent *event); Q_INVOKABLE void paintEventHandler(QPaintEvent *event);
Q_INVOKABLE void mousePressEventHandler(QMouseEvent *event);
Q_INVOKABLE void mouseReleaseEventHandler(QMouseEvent *event);
Q_INVOKABLE void mouseDoubleClickEventHandler(QMouseEvent *event);
private: private:
void initialize(); void initialize();
@ -73,9 +76,8 @@ private:
void setupInitialUi(); void setupInitialUi();
Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const; Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const;
Q_NODISCARD bool shouldDrawFrameBorder() const; Q_NODISCARD bool shouldDrawFrameBorder() const;
Q_NODISCARD bool isMainWindow() const; Q_NODISCARD bool isStandardLayout() const;
Q_NODISCARD bool isStandardWindow() const; Q_NODISCARD bool isCustomLayout() const;
Q_NODISCARD bool isCustomWindow();
private Q_SLOTS: private Q_SLOTS:
void updateContentsMargins(); void updateContentsMargins();
@ -84,7 +86,8 @@ private Q_SLOTS:
private: private:
QWidget *q = nullptr; QWidget *q = nullptr;
bool m_framelessHelperInited = false; bool m_initialized = false;
bool m_framelessHelperSetup = false;
WindowLayout m_windowLayout = WindowLayout::Standard; WindowLayout m_windowLayout = WindowLayout::Standard;
QWidget *m_systemTitleBarWidget = nullptr; QWidget *m_systemTitleBarWidget = nullptr;
QLabel *m_systemWindowTitleLabel = nullptr; QLabel *m_systemWindowTitleLabel = nullptr;