diff --git a/examples/quick/qml/MainWindow.qml b/examples/quick/qml/MainWindow.qml index 583b13a..43ca0c4 100644 --- a/examples/quick/qml/MainWindow.qml +++ b/examples/quick/qml/MainWindow.qml @@ -34,12 +34,6 @@ FramelessWindow { height: 600 title: qsTr("Hello, World! - Qt Quick") color: FramelessUtils.darkModeEnabled ? FramelessUtils.defaultSystemDarkColor : FramelessUtils.defaultSystemLightColor - Component.onCompleted: { - FramelessHelper.setTitleBarItem(window, titleBar); - FramelessHelper.setHitTestVisible(window, minimizeButton, true); - FramelessHelper.setHitTestVisible(window, maximizeButton, true); - FramelessHelper.setHitTestVisible(window, closeButton, true); - } Timer { interval: 500 @@ -62,12 +56,12 @@ FramelessWindow { id: titleBar anchors { top: parent.top - topMargin: window.windowTopBorder.height + topMargin: 1 left: parent.left right: parent.right } active: window.active - maximized: (window.visibility === Window.Maximized) || (window.visibility === Window.FullScreen) + maximized: window.zoomed title: window.title minimizeButton { id: minimizeButton @@ -81,5 +75,11 @@ FramelessWindow { id: closeButton onClicked: window.close() } + Component.onCompleted: { + FramelessHelper.setTitleBarItem(window, titleBar); + FramelessHelper.setHitTestVisible(window, minimizeButton, true); + FramelessHelper.setHitTestVisible(window, maximizeButton, true); + FramelessHelper.setHitTestVisible(window, closeButton, true); + } } } diff --git a/include/FramelessHelper/Core/framelesshelpercore_global.h b/include/FramelessHelper/Core/framelesshelpercore_global.h index 88b6ba7..c9965c3 100644 --- a/include/FramelessHelper/Core/framelesshelpercore_global.h +++ b/include/FramelessHelper/Core/framelesshelpercore_global.h @@ -118,6 +118,8 @@ Q_NAMESPACE_EXPORT(FRAMELESSHELPER_CORE_API) [[maybe_unused]] static const QString kForceHideFrameBorderKeyPath = QStringLiteral("Options/ForceHideFrameBorder"); [[maybe_unused]] static const QString kForceShowFrameBorderKeyPath = QStringLiteral("Options/ForceShowFrameBorder"); +[[maybe_unused]] static constexpr const QSize kInvalidWindowSize = {160, 160}; + enum class Option : int { Default = 0x00000000, // Default placeholder, have no effect. @@ -136,7 +138,8 @@ enum class Option : int NoDoubleClickMaximizeToggle = 0x00001000, // Don't toggle the maximize state when double clicks the titlebar. DisableResizing = 0x00002000, // Disable resizing of the window. DisableDragging = 0x00004000, // Disable dragging through the titlebar of the window. - DontTouchCursorShape = 0x00008000 // Don't change the cursor shape while the mouse is hovering above the window. + DontTouchCursorShape = 0x00008000, // Don't change the cursor shape while the mouse is hovering above the window. + DontMoveWindowToDesktopCenter = 0x00010000 // Don't move the window to the desktop center before shown. }; Q_DECLARE_FLAGS(Options, Option) Q_FLAG_NS(Options) diff --git a/include/FramelessHelper/Core/utils.h b/include/FramelessHelper/Core/utils.h index f5f32ba..ce07863 100644 --- a/include/FramelessHelper/Core/utils.h +++ b/include/FramelessHelper/Core/utils.h @@ -79,6 +79,7 @@ FRAMELESSHELPER_CORE_API void startSystemResize(QWindow *window, const Qt::Edges (const SystemButtonType button, const SystemTheme theme, const ResourceType type); FRAMELESSHELPER_CORE_API void sendMouseReleaseEvent(); [[nodiscard]] FRAMELESSHELPER_CORE_API QWindow *findWindow(const WId winId); +FRAMELESSHELPER_CORE_API void moveWindowToDesktopCenter(QWindow *window, const bool considerTaskBar); #ifdef Q_OS_WINDOWS [[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin8OrGreater(); diff --git a/include/FramelessHelper/Quick/framelessquickwindow.h b/include/FramelessHelper/Quick/framelessquickwindow.h index c0450de..337b133 100644 --- a/include/FramelessHelper/Quick/framelessquickwindow.h +++ b/include/FramelessHelper/Quick/framelessquickwindow.h @@ -29,14 +29,26 @@ FRAMELESSHELPER_BEGIN_NAMESPACE +class FramelessQuickWindowPrivate; + class FRAMELESSHELPER_QUICK_API FramelessQuickWindow : public QQuickWindow { Q_OBJECT + Q_DECLARE_PRIVATE(FramelessQuickWindow) Q_DISABLE_COPY_MOVE(FramelessQuickWindow) + Q_PROPERTY(bool zoomed READ zoomed NOTIFY zoomedChanged FINAL) public: explicit FramelessQuickWindow(QWindow *parent = nullptr); ~FramelessQuickWindow() override; + + Q_NODISCARD bool zoomed() const; + +Q_SIGNALS: + void zoomedChanged(); + +private: + QScopedPointer d_ptr; }; FRAMELESSHELPER_END_NAMESPACE diff --git a/src/core/framelesswindowsmanager.cpp b/src/core/framelesswindowsmanager.cpp index f341623..7cdba27 100644 --- a/src/core/framelesswindowsmanager.cpp +++ b/src/core/framelesswindowsmanager.cpp @@ -58,6 +58,15 @@ FramelessWindowsManagerPrivate *FramelessWindowsManagerPrivate::get(FramelessWin return manager->d_func(); } +const FramelessWindowsManagerPrivate *FramelessWindowsManagerPrivate::get(const FramelessWindowsManager *manager) +{ + Q_ASSERT(manager); + if (!manager) { + return nullptr; + } + return manager->d_func(); +} + bool FramelessWindowsManagerPrivate::usePureQtImplementation() const { #ifdef Q_OS_WINDOWS @@ -188,6 +197,7 @@ void FramelessWindowsManager::addWindow(QWindow *window) } #endif d->mutex.unlock(); + const auto options = qvariant_cast(window->property(kInternalOptionsFlag)); if (pureQt) { FramelessHelperQt::addWindow(window); } @@ -195,11 +205,13 @@ void FramelessWindowsManager::addWindow(QWindow *window) if (!pureQt) { FramelessHelperWin::addWindow(window); } - const auto options = qvariant_cast(window->property(kInternalOptionsFlag)); if (!(options & Option::DontInstallSystemMenuHook)) { Utils::installSystemMenuHook(window); } #endif + if (!(options & Option::DontMoveWindowToDesktopCenter)) { + Utils::moveWindowToDesktopCenter(window, true); + } } void FramelessWindowsManager::removeWindow(QWindow *window) diff --git a/src/core/framelesswindowsmanager_p.h b/src/core/framelesswindowsmanager_p.h index 6171c3f..5cfbc12 100644 --- a/src/core/framelesswindowsmanager_p.h +++ b/src/core/framelesswindowsmanager_p.h @@ -44,6 +44,7 @@ public: ~FramelessWindowsManagerPrivate(); [[nodiscard]] static FramelessWindowsManagerPrivate *get(FramelessWindowsManager *manager); + [[nodiscard]] static const FramelessWindowsManagerPrivate *get(const FramelessWindowsManager *manager); [[nodiscard]] bool usePureQtImplementation() const; diff --git a/src/core/utils.cpp b/src/core/utils.cpp index 46070cd..ccce6de 100644 --- a/src/core/utils.cpp +++ b/src/core/utils.cpp @@ -26,6 +26,7 @@ #include #include #include +#include // The "Q_INIT_RESOURCE()" macro can't be used within a namespace, // so we wrap it into a separate function outside of the namespace and @@ -171,4 +172,30 @@ QWindow *Utils::findWindow(const WId winId) return nullptr; } +void Utils::moveWindowToDesktopCenter(QWindow *window, const bool considerTaskBar) +{ + Q_ASSERT(window); + if (!window) { + return; + } + const QSize windowSize = window->size(); + if (windowSize.isEmpty() || (windowSize == kInvalidWindowSize)) { + return; + } + const QScreen * const screen = [window]() -> const QScreen * { + const QScreen * const s = window->screen(); + return (s ? s : QGuiApplication::primaryScreen()); + }(); + Q_ASSERT(screen); + if (!screen) { + return; + } + const QSize screenSize = (considerTaskBar ? screen->availableSize() : screen->size()); + const QPoint offset = (considerTaskBar ? screen->availableGeometry().topLeft() : QPoint(0, 0)); + const auto newX = static_cast(qRound(qreal(screenSize.width() - windowSize.width()) / 2.0)); + const auto newY = static_cast(qRound(qreal(screenSize.height() - windowSize.height()) / 2.0)); + window->setX(newX + offset.x()); + window->setY(newY + offset.y()); +} + FRAMELESSHELPER_END_NAMESPACE diff --git a/src/quick/CMakeLists.txt b/src/quick/CMakeLists.txt index aa3e404..c7127e7 100644 --- a/src/quick/CMakeLists.txt +++ b/src/quick/CMakeLists.txt @@ -10,6 +10,7 @@ set(SOURCES ${INCLUDE_PREFIX}/framelessquickeventfilter.h ${INCLUDE_PREFIX}/framelesshelper_quick.h ${INCLUDE_PREFIX}/framelessquickwindow.h + framelessquickwindow_p.h framelesshelperquick.qrc framelesshelper_quick.cpp framelessquickhelper.cpp diff --git a/src/quick/framelessquickwindow.cpp b/src/quick/framelessquickwindow.cpp index 09161a8..4752c23 100644 --- a/src/quick/framelessquickwindow.cpp +++ b/src/quick/framelessquickwindow.cpp @@ -23,11 +23,78 @@ */ #include "framelessquickwindow.h" +#include "framelessquickwindow_p.h" +#include +#include +#include +#include +#include FRAMELESSHELPER_BEGIN_NAMESPACE -FramelessQuickWindow::FramelessQuickWindow(QWindow *parent) : QQuickWindow(parent) {} +FramelessQuickWindowPrivate::FramelessQuickWindowPrivate(FramelessQuickWindow *q) : QObject(q) +{ + Q_ASSERT(q); + if (!q) { + return; + } + q_ptr = q; + initialize(); +} + +FramelessQuickWindowPrivate::~FramelessQuickWindowPrivate() = default; + +bool FramelessQuickWindowPrivate::isZoomed() const +{ + Q_Q(const FramelessQuickWindow); + const FramelessQuickWindow::Visibility visibility = q->visibility(); + return ((visibility == FramelessQuickWindow::Maximized) || (visibility == FramelessQuickWindow::FullScreen)); +} + +void FramelessQuickWindowPrivate::initialize() +{ + Q_Q(FramelessQuickWindow); + FramelessWindowsManager * const manager = FramelessWindowsManager::instance(); + manager->addWindow(q); + QQuickItem * const rootItem = q->contentItem(); + const QQuickItemPrivate * const rootItemPrivate = QQuickItemPrivate::get(rootItem); + m_topBorderRectangle.reset(new QQuickRectangle(rootItem)); + updateTopBorderHeight(); + updateTopBorderColor(); + connect(q, &FramelessQuickWindow::visibilityChanged, this, &FramelessQuickWindowPrivate::updateTopBorderHeight); + connect(q, &FramelessQuickWindow::activeChanged, this, &FramelessQuickWindowPrivate::updateTopBorderColor); + connect(manager, &FramelessWindowsManager::systemThemeChanged, this, &FramelessQuickWindowPrivate::updateTopBorderColor); + const auto topBorderAnchors = new QQuickAnchors(m_topBorderRectangle.data(), m_topBorderRectangle.data()); + topBorderAnchors->setTop(rootItemPrivate->top()); + topBorderAnchors->setLeft(rootItemPrivate->left()); + topBorderAnchors->setRight(rootItemPrivate->right()); + connect(q, &FramelessQuickWindow::visibilityChanged, q, &FramelessQuickWindow::zoomedChanged); +} + +void FramelessQuickWindowPrivate::updateTopBorderColor() +{ + Q_Q(FramelessQuickWindow); + m_topBorderRectangle->setColor(Utils::getFrameBorderColor(q->isActive())); +} + +void FramelessQuickWindowPrivate::updateTopBorderHeight() +{ + Q_Q(FramelessQuickWindow); + const qreal newHeight = ((q->visibility() == FramelessQuickWindow::Windowed) ? 1.0 : 0.0); + m_topBorderRectangle->setHeight(newHeight); +} + +FramelessQuickWindow::FramelessQuickWindow(QWindow *parent) : QQuickWindow(parent) +{ + d_ptr.reset(new FramelessQuickWindowPrivate(this)); +} FramelessQuickWindow::~FramelessQuickWindow() = default; +bool FramelessQuickWindow::zoomed() const +{ + Q_D(const FramelessQuickWindow); + return d->isZoomed(); +} + FRAMELESSHELPER_END_NAMESPACE diff --git a/src/quick/framelessquickwindow_p.h b/src/quick/framelessquickwindow_p.h new file mode 100644 index 0000000..1d7aa4a --- /dev/null +++ b/src/quick/framelessquickwindow_p.h @@ -0,0 +1,62 @@ +/* + * 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 + +QT_BEGIN_NAMESPACE +class QQuickRectangle; +QT_END_NAMESPACE + +FRAMELESSHELPER_BEGIN_NAMESPACE + +class FramelessQuickWindow; + +class FRAMELESSHELPER_QUICK_API FramelessQuickWindowPrivate : public QObject +{ + Q_OBJECT + Q_DECLARE_PUBLIC(FramelessQuickWindow) + Q_DISABLE_COPY_MOVE(FramelessQuickWindowPrivate) + +public: + explicit FramelessQuickWindowPrivate(FramelessQuickWindow *q); + ~FramelessQuickWindowPrivate() override; + + Q_NODISCARD bool isZoomed() const; + +private: + void initialize(); + +private Q_SLOTS: + void updateTopBorderColor(); + void updateTopBorderHeight(); + +private: + FramelessQuickWindow *q_ptr = nullptr; + QScopedPointer m_topBorderRectangle; +}; + +FRAMELESSHELPER_END_NAMESPACE diff --git a/src/widgets/framelesswidgetshelper.cpp b/src/widgets/framelesswidgetshelper.cpp index 7ff6408..8a5af31 100644 --- a/src/widgets/framelesswidgetshelper.cpp +++ b/src/widgets/framelesswidgetshelper.cpp @@ -36,6 +36,8 @@ FRAMELESSHELPER_BEGIN_NAMESPACE +static constexpr const char QT_MAINWINDOW_CLASS_NAME[] = "QMainWindow"; + static const QString kSystemButtonStyleSheet = QStringLiteral(R"( QPushButton { border-style: none; @@ -54,11 +56,12 @@ QPushButton:pressed { FramelessWidgetsHelper::FramelessWidgetsHelper(QWidget *q, const Options options) : QObject(q) { Q_ASSERT(q); - if (q) { - this->q = q; - m_options = options; - initialize(); + if (!q) { + return; } + this->q = q; + m_options = options; + initialize(); } FramelessWidgetsHelper::~FramelessWidgetsHelper() = default; @@ -299,7 +302,7 @@ void FramelessWidgetsHelper::initialize() } window->setProperty(kInternalOptionsFlag, QVariant::fromValue(m_options)); if (m_options & Option::UseStandardWindowLayout) { - if (q->inherits("QMainWindow")) { + if (q->inherits(QT_MAINWINDOW_CLASS_NAME)) { m_options &= ~Options(Option::UseStandardWindowLayout); qWarning() << "\"Option::UseStandardWindowLayout\" is not compatible with QMainWindow and it's subclasses." " Enabling this option will mess up with your main window's layout."; @@ -310,7 +313,7 @@ void FramelessWidgetsHelper::initialize() Q_ASSERT(window->flags() & Qt::FramelessWindowHint); } FramelessWindowsManager *manager = FramelessWindowsManager::instance(); - manager->addWindow(q->windowHandle()); + manager->addWindow(window); connect(manager, &FramelessWindowsManager::systemThemeChanged, this, [this](){ if (m_options & Option::UseStandardWindowLayout) { updateSystemTitleBarStyleSheet();