diff --git a/README.md b/README.md index db837a27..d0ab8348 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,13 @@ TODO ## Roadmap -- [ ] Windows: Maximize button docking feature introduced in Windows 11 -- [ ] All: Add cross-platform system menu. Support both light and dark theme. Can be triggered by right-click on the title bar area. +- 2.1 + - [ ] All: Add cross-platform system menu for both Qt Widgets and Qt Quick. Support both light and dark theme. Can be triggered by right-clicking on the title bar area or pressing the system menu shortcut (ALT + SPACE). + - [ ] All: Add QtWebEngine demo applications for both Qt Widgets and Qt Quick. + - [ ] All: Make more settings and options configurable through environment variables and configuration files. +- Future versions + - [ ] Windows: Maximize button docking feature introduced in Windows 11 + - [ ] More feature requests are welcome! ## Feedback diff --git a/include/FramelessHelper/Quick/framelesshelperquick_global.h b/include/FramelessHelper/Quick/framelesshelperquick_global.h index dffe1e24..89457dc5 100644 --- a/include/FramelessHelper/Quick/framelesshelperquick_global.h +++ b/include/FramelessHelper/Quick/framelesshelperquick_global.h @@ -56,12 +56,56 @@ static_cast(static_cast(Value)) #endif +#ifndef FRAMELESSHELPER_FLAGS_CORE_TO_QUICK +# define FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Enum, Value, In, Out) \ + if (In & FRAMELESSHELPER_PREPEND_NAMESPACE(Global)::Enum::Value) { \ + Out |= FRAMELESSHELPER_PREPEND_NAMESPACE(QuickGlobal)::Enum::Value; \ + } +#endif + +#ifndef FRAMELESSHELPER_FLAGS_QUICK_TO_CORE +# define FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Enum, Value, In, Out) \ + if (In & FRAMELESSHELPER_PREPEND_NAMESPACE(QuickGlobal)::Enum::Value) { \ + Out |= FRAMELESSHELPER_PREPEND_NAMESPACE(Global)::Enum::Value; \ + } +#endif + FRAMELESSHELPER_BEGIN_NAMESPACE [[maybe_unused]] static constexpr const char FRAMELESSHELPER_QUICK_URI[] = "org.wangwenx190.FramelessHelper"; struct FRAMELESSHELPER_QUICK_API QuickGlobal { + enum class Option + { + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, ForceHideWindowFrameBorder) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, ForceShowWindowFrameBorder) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DontDrawTopWindowFrameBorder) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, EnableRoundedWindowCorners) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, TransparentWindowBackground) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, MaximizeButtonDocking) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, CreateStandardWindowLayout) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, BeCompatibleWithQtFramelessWindowHint) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DontTouchQtInternals) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DontTouchWindowFrameBorderColor) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DontInstallSystemMenuHook) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DisableSystemMenu) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, NoDoubleClickMaximizeToggle) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DisableResizing) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DisableDragging) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DontTouchCursorShape) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DontMoveWindowToDesktopCenter) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DontTreatFullScreenAsZoomed) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DontTouchHighDpiScalingPolicy) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DontTouchScaleFactorRoundingPolicy) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DontTouchProcessDpiAwarenessLevel) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DontEnsureNonNativeWidgetSiblings) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, SyncNativeControlsThemeWithSystem) + }; + Q_ENUM(Option) + Q_DECLARE_FLAGS(Options, Option) + Q_FLAG(Options) + enum class SystemTheme { FRAMELESSHELPER_QUICK_ENUM_VALUE(SystemTheme, Unknown) @@ -93,7 +137,7 @@ struct FRAMELESSHELPER_QUICK_API QuickGlobal enum class DwmColorizationArea { - FRAMELESSHELPER_QUICK_ENUM_VALUE(DwmColorizationArea, None_) // Avoid name conflicts with X11 headers. + FRAMELESSHELPER_QUICK_ENUM_VALUE(DwmColorizationArea, None_) FRAMELESSHELPER_QUICK_ENUM_VALUE(DwmColorizationArea, StartMenu_TaskBar_ActionCenter) FRAMELESSHELPER_QUICK_ENUM_VALUE(DwmColorizationArea, TitleBar_WindowBorder) FRAMELESSHELPER_QUICK_ENUM_VALUE(DwmColorizationArea, All) @@ -128,7 +172,8 @@ struct FRAMELESSHELPER_QUICK_API QuickGlobal #ifdef QML_UNCREATABLE QML_UNCREATABLE("The FramelessHelper namespace is not creatable, you can only use it to access it's enums.") #endif - Q_DISABLE_COPY_MOVE(QuickGlobal) }; +Q_DECLARE_OPERATORS_FOR_FLAGS(QuickGlobal::Options) + FRAMELESSHELPER_END_NAMESPACE diff --git a/include/FramelessHelper/Quick/framelessquickwindow.h b/include/FramelessHelper/Quick/framelessquickwindow.h index b71f4117..083f7aae 100644 --- a/include/FramelessHelper/Quick/framelessquickwindow.h +++ b/include/FramelessHelper/Quick/framelessquickwindow.h @@ -47,6 +47,7 @@ class FRAMELESSHELPER_QUICK_API FramelessQuickWindow : public QQuickWindow Q_PROPERTY(bool fullScreen READ isFullScreen NOTIFY fullScreenChanged FINAL) Q_PROPERTY(bool fixedSize READ fixedSize WRITE setFixedSize NOTIFY fixedSizeChanged FINAL) Q_PROPERTY(QColor frameBorderColor READ frameBorderColor NOTIFY frameBorderColorChanged FINAL) + Q_PROPERTY(QuickGlobal::Options options READ options WRITE setOptions NOTIFY optionsChanged FINAL) public: explicit FramelessQuickWindow(QWindow *parent = nullptr, const Global::UserSettings &settings = {}); @@ -63,6 +64,9 @@ class FRAMELESSHELPER_QUICK_API FramelessQuickWindow : public QQuickWindow Q_NODISCARD QColor frameBorderColor() const; + Q_NODISCARD QuickGlobal::Options options() const; + void setOptions(const QuickGlobal::Options value); + public Q_SLOTS: void showMinimized2(); void toggleMaximized(); @@ -85,6 +89,7 @@ public Q_SLOTS: void fixedSizeChanged(); void frameBorderColorChanged(); void systemButtonStateChanged(const QuickGlobal::SystemButtonType, const QuickGlobal::ButtonState); + void optionsChanged(); private: QScopedPointer d_ptr; diff --git a/src/quick/framelessquickhelper.cpp b/src/quick/framelessquickhelper.cpp index a4fefb27..1a756da6 100644 --- a/src/quick/framelessquickhelper.cpp +++ b/src/quick/framelessquickhelper.cpp @@ -57,6 +57,7 @@ void FramelessHelper::Quick::registerTypes(QQmlEngine *engine) return; } inited = true; + qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); diff --git a/src/quick/framelessquickwindow.cpp b/src/quick/framelessquickwindow.cpp index 408dd945..8bb02170 100644 --- a/src/quick/framelessquickwindow.cpp +++ b/src/quick/framelessquickwindow.cpp @@ -36,6 +36,64 @@ using namespace Global; static constexpr const char QT_QUICKITEM_CLASS_NAME[] = "QQuickItem"; +[[nodiscard]] static inline QuickGlobal::Options optionsCoreToQuick(const Options value) +{ + QuickGlobal::Options result = {}; + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, ForceHideWindowFrameBorder, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, ForceShowWindowFrameBorder, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, DontDrawTopWindowFrameBorder, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, EnableRoundedWindowCorners, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, TransparentWindowBackground, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, MaximizeButtonDocking, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, CreateStandardWindowLayout, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, BeCompatibleWithQtFramelessWindowHint, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, DontTouchQtInternals, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, DontTouchWindowFrameBorderColor, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, DontInstallSystemMenuHook, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, DisableSystemMenu, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, NoDoubleClickMaximizeToggle, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, DisableResizing, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, DisableDragging, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, DontTouchCursorShape, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, DontMoveWindowToDesktopCenter, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, DontTreatFullScreenAsZoomed, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, DontTouchHighDpiScalingPolicy, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, DontTouchScaleFactorRoundingPolicy, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, DontTouchProcessDpiAwarenessLevel, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, DontEnsureNonNativeWidgetSiblings, value, result) + FRAMELESSHELPER_FLAGS_CORE_TO_QUICK(Option, SyncNativeControlsThemeWithSystem, value, result) + return result; +} + +[[nodiscard]] static inline Options optionsQuickToCore(const QuickGlobal::Options value) +{ + Options result = {}; + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, ForceHideWindowFrameBorder, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, ForceShowWindowFrameBorder, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, DontDrawTopWindowFrameBorder, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, EnableRoundedWindowCorners, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, TransparentWindowBackground, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, MaximizeButtonDocking, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, CreateStandardWindowLayout, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, BeCompatibleWithQtFramelessWindowHint, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, DontTouchQtInternals, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, DontTouchWindowFrameBorderColor, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, DontInstallSystemMenuHook, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, DisableSystemMenu, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, NoDoubleClickMaximizeToggle, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, DisableResizing, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, DisableDragging, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, DontTouchCursorShape, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, DontMoveWindowToDesktopCenter, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, DontTreatFullScreenAsZoomed, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, DontTouchHighDpiScalingPolicy, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, DontTouchScaleFactorRoundingPolicy, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, DontTouchProcessDpiAwarenessLevel, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, DontEnsureNonNativeWidgetSiblings, value, result) + FRAMELESSHELPER_FLAGS_QUICK_TO_CORE(Option, SyncNativeControlsThemeWithSystem, value, result) + return result; +} + FramelessQuickWindowPrivate::FramelessQuickWindowPrivate(FramelessQuickWindow *q, const UserSettings &settings) : QObject(q) { Q_ASSERT(q); @@ -278,6 +336,17 @@ void FramelessQuickWindowPrivate::snapToTopBorder(QQuickItem *item, const QuickG } } +void FramelessQuickWindowPrivate::setOptions(const QuickGlobal::Options value) +{ + Q_Q(FramelessQuickWindow); + if (m_quickOptions == value) { + return; + } + m_quickOptions = value; + m_settings.options = optionsQuickToCore(m_quickOptions); + Q_EMIT q->optionsChanged(); +} + bool FramelessQuickWindowPrivate::eventFilter(QObject *object, QEvent *event) { Q_ASSERT(object); @@ -439,32 +508,26 @@ void FramelessQuickWindowPrivate::initialize() if (m_settings.options & Option::TransparentWindowBackground) { q->setColor(kDefaultTransparentColor); } + m_quickOptions = optionsCoreToQuick(m_settings.options); FramelessWindowsManager * const manager = FramelessWindowsManager::instance(); manager->addWindow(m_settings, m_params); q->installEventFilter(this); QQuickItem * const rootItem = q->contentItem(); const QQuickItemPrivate * const rootItemPrivate = QQuickItemPrivate::get(rootItem); m_topBorderRectangle.reset(new QQuickRectangle(rootItem)); + m_topBorderRectangle->setColor(kDefaultTransparentColor); + m_topBorderRectangle->setHeight(0.0); QQuickPen * const _border = m_topBorderRectangle->border(); _border->setWidth(0.0); _border->setColor(kDefaultTransparentColor); - const bool frameBorderVisible = [this]() -> bool { -#ifdef Q_OS_WINDOWS - return (Utils::isWindowFrameBorderVisible() && !Utils::isWin11OrGreater() - && !(m_settings.options & Option::DontDrawTopWindowFrameBorder)); -#else - Q_UNUSED(this); - return false; -#endif - }(); - if (frameBorderVisible) { + updateTopBorderHeight(); + updateTopBorderColor(); + m_topBorderAnchors.reset(new QQuickAnchors(m_topBorderRectangle.data(), m_topBorderRectangle.data())); + m_topBorderAnchors->setTop(rootItemPrivate->top()); + m_topBorderAnchors->setLeft(rootItemPrivate->left()); + m_topBorderAnchors->setRight(rootItemPrivate->right()); + connect(q, &FramelessQuickWindow::visibilityChanged, this, [this, q](){ updateTopBorderHeight(); - updateTopBorderColor(); - } - connect(q, &FramelessQuickWindow::visibilityChanged, this, [this, q, frameBorderVisible](){ - if (frameBorderVisible) { - updateTopBorderHeight(); - } Q_EMIT q->hiddenChanged(); Q_EMIT q->normalChanged(); Q_EMIT q->minimizedChanged(); @@ -472,16 +535,10 @@ void FramelessQuickWindowPrivate::initialize() Q_EMIT q->fullScreenChanged(); }); connect(q, &FramelessQuickWindow::activeChanged, this, &FramelessQuickWindowPrivate::updateTopBorderColor); - connect(manager, &FramelessWindowsManager::systemThemeChanged, this, [this, q, frameBorderVisible](){ - if (frameBorderVisible) { - updateTopBorderColor(); - } + connect(manager, &FramelessWindowsManager::systemThemeChanged, this, [this, q](){ + updateTopBorderColor(); Q_EMIT q->frameBorderColorChanged(); }); - m_topBorderAnchors.reset(new QQuickAnchors(m_topBorderRectangle.data(), m_topBorderRectangle.data())); - m_topBorderAnchors->setTop(rootItemPrivate->top()); - m_topBorderAnchors->setLeft(rootItemPrivate->left()); - m_topBorderAnchors->setRight(rootItemPrivate->right()); } QRect FramelessQuickWindowPrivate::mapItemGeometryToScene(const QQuickItem * const item) const @@ -608,6 +665,16 @@ void FramelessQuickWindowPrivate::doStartSystemMove2(QMouseEvent *event) startSystemMove2(globalPos); } +bool FramelessQuickWindowPrivate::shouldDrawFrameBorder() const +{ +#ifdef Q_OS_WINDOWS + return (Utils::isWindowFrameBorderVisible() && !Utils::isWin11OrGreater() + && !(m_settings.options & Option::DontDrawTopWindowFrameBorder)); +#else + return false; +#endif +} + void FramelessQuickWindowPrivate::showEventHandler(QShowEvent *event) { Q_ASSERT(event); @@ -711,9 +778,17 @@ void FramelessQuickWindowPrivate::mouseDoubleClickEventHandler(QMouseEvent *even toggleMaximized(); } +QuickGlobal::Options FramelessQuickWindowPrivate::getOptions() const +{ + return m_quickOptions; +} + void FramelessQuickWindowPrivate::updateTopBorderColor() { #ifdef Q_OS_WINDOWS + if (!shouldDrawFrameBorder()) { + return; + } m_topBorderRectangle->setColor(getFrameBorderColor()); #endif } @@ -721,6 +796,9 @@ void FramelessQuickWindowPrivate::updateTopBorderColor() void FramelessQuickWindowPrivate::updateTopBorderHeight() { #ifdef Q_OS_WINDOWS + if (!shouldDrawFrameBorder()) { + return; + } const qreal newHeight = (isNormal() ? 1.0 : 0.0); m_topBorderRectangle->setHeight(newHeight); #endif @@ -781,6 +859,18 @@ QColor FramelessQuickWindow::frameBorderColor() const return d->getFrameBorderColor(); } +QuickGlobal::Options FramelessQuickWindow::options() const +{ + Q_D(const FramelessQuickWindow); + return d->getOptions(); +} + +void FramelessQuickWindow::setOptions(const QuickGlobal::Options value) +{ + Q_D(FramelessQuickWindow); + d->setOptions(value); +} + void FramelessQuickWindow::setTitleBarItem(QQuickItem *item) { Q_ASSERT(item); diff --git a/src/quick/framelessquickwindow_p.h b/src/quick/framelessquickwindow_p.h index ac7af5ed..3ea6af17 100644 --- a/src/quick/framelessquickwindow_p.h +++ b/src/quick/framelessquickwindow_p.h @@ -71,6 +71,8 @@ class FRAMELESSHELPER_QUICK_API FramelessQuickWindowPrivate : public QObject Q_INVOKABLE void mouseReleaseEventHandler(QMouseEvent *event); Q_INVOKABLE void mouseDoubleClickEventHandler(QMouseEvent *event); + Q_INVOKABLE Q_NODISCARD QuickGlobal::Options getOptions() const; + public Q_SLOTS: void showMinimized2(); void toggleMaximized(); @@ -84,6 +86,7 @@ public Q_SLOTS: void setFixedSize(const bool value, const bool force = false); void bringToFront(); void snapToTopBorder(QQuickItem *item, const QuickGlobal::Anchor itemAnchor, const QuickGlobal::Anchor topBorderAnchor); + void setOptions(const QuickGlobal::Options value); protected: Q_NODISCARD bool eventFilter(QObject *object, QEvent *event) override; @@ -95,6 +98,7 @@ public Q_SLOTS: Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const; Q_NODISCARD bool shouldIgnoreMouseEvents(const QPoint &pos) const; void doStartSystemMove2(QMouseEvent *event); + Q_NODISCARD bool shouldDrawFrameBorder() const; private Q_SLOTS: void updateTopBorderColor(); @@ -111,6 +115,7 @@ private Q_SLOTS: bool m_windowExposed = false; QPointer m_titleBarItem = nullptr; QList m_hitTestVisibleItems = {}; + QuickGlobal::Options m_quickOptions = {}; }; FRAMELESSHELPER_END_NAMESPACE