diff --git a/CMakeLists.txt b/CMakeLists.txt index 64a1f39..53a144a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ cmake_minimum_required(VERSION 3.20) project(FramelessHelper VERSION 2.0.0.0 DESCRIPTION "Window customization framework for Qt Widgets and Qt Quick." - HOMEPAGE_URL "https://github.com/wangwenx190/framelesshelper" + HOMEPAGE_URL "https://github.com/wangwenx190/framelesshelper/" LANGUAGES CXX ) diff --git a/README.md b/README.md index 1096b84..b8058c0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,13 @@ -# FramelessHelper 2.0 +# FramelessHelper 2.x + +## Highlights compared to 2.0 + +- Windows: Added support for the snap layout feature introduced in Windows 11. +- Quick: Restored some 1.x interfaces which may be convenient for Qt Quick users. +- Examples: Added QtWebEngine based demo projects for both Qt Widgets and Qt Quick. +- Common: Added cross-platform customizable system menu for both Qt Widgets and Qt Quick. Also supports both light and dark theme. +- Common: Removed bundled Qt internal classes that are licensed under Commercial/GPL/LGPL. This library is now pure MIT licensed. +- Common: Bug fix and internal refactoring, improved stability on all supported platforms. ## Highlights compared to 1.x @@ -44,8 +53,16 @@ - Future versions - [ ] Linux: Support runtime theme switching. - [ ] Linux: Move window resize area outside of the client area. + - [ ] macOS: Move window resize area outside of the client area. - [ ] More feature requests are welcome! +## Requiredments + +- Compiler: a modern compiler which supports C++17 at least. +- Qt version: using the latest stable version of Qt is highly recommended, the minimum supported version is Qt 5.6. +- Qt modules: QtCore and QtGui for the core module, QtWidgets for the widgets module, QtQml QtQuick QtQuickControls2 QtQuickTemplates2 for the quick module. +- CMake & ninja: the newer, the better. + ## Build ```bash @@ -70,7 +87,7 @@ Please refer to the demo applications to see more detailed usages: [examples](./ ### Windows -- If DWM composition is disabled in some very rare cases (only possible on Windows 7), the top-left corner and top-right corner will appear in round shape. The round corners can be restored to square again if you re-enable DWM composition. +- If DWM composition is disabled in some very rare cases (only possible on Windows 7), the top-left corner and top-right corner will appear in round shape. The round corners can be restored to square if you re-enable DWM composition. - There's an OpenGL driver bug which will cause some frameless windows have a strange black bar right on top of your homemade title bar, and it also makes the controls in your windows shifted to the bottom-right corner for some pixels. It's a bug of your graphics card driver, specifically, your OpenGL driver, not FramelessHelper. There are some solutions provided by our users but some of them may not work in all conditions, you can pick one from them: - Upgrade your graphics card driver to the latest version. - Change your system theme to "Basic". @@ -83,11 +100,13 @@ Please refer to the demo applications to see more detailed usages: [examples](./ ### Linux - FramelessHelper will force your application to use the _XCB_ platform plugin when running on Wayland. -- Currently lacks runtime theme switching support. +- Currently lacks runtime theme switching support due to Qt is missing the ability to detect theme change event on Linux. +- The resize area is inside of the window. ### macOS - The frameless windows will appear in square corners instead of round corners. +- The resize area is inside of the window. ## License diff --git a/examples/mainwindow/mainwindow.cpp b/examples/mainwindow/mainwindow.cpp index 83e69dc..8ef4041 100644 --- a/examples/mainwindow/mainwindow.cpp +++ b/examples/mainwindow/mainwindow.cpp @@ -72,10 +72,9 @@ void MainWindow::setupUi() setTitleBarWidget(titleBarWidget); setHitTestVisible(mb); // IMPORTANT! - setHitTestVisible(titleBar->iconButton); - setHitTestVisible(titleBar->minimizeButton); - setHitTestVisible(titleBar->maximizeButton); - setHitTestVisible(titleBar->closeButton); + setSystemButton(titleBar->minimizeButton, SystemButtonType::Minimize); + setSystemButton(titleBar->maximizeButton, SystemButtonType::Maximize); + setSystemButton(titleBar->closeButton, SystemButtonType::Close); connect(titleBar->minimizeButton, &QPushButton::clicked, this, &MainWindow::showMinimized); connect(titleBar->maximizeButton, &QPushButton::clicked, this, &MainWindow::toggleMaximized); diff --git a/examples/openglwidget/mainwindow.cpp b/examples/openglwidget/mainwindow.cpp index b32d863..da921fb 100644 --- a/examples/openglwidget/mainwindow.cpp +++ b/examples/openglwidget/mainwindow.cpp @@ -84,4 +84,8 @@ void MainWindow::setupUi() setTitleBarWidget(m_titleBarWidget); resize(800, 600); setWindowTitle(tr("QOpenGLWidget demo")); + + setSystemButton(m_minBtn, SystemButtonType::Minimize); + setSystemButton(m_maxBtn, SystemButtonType::Maximize); + setSystemButton(m_closeBtn, SystemButtonType::Close); } diff --git a/examples/quick/MainWindow.qml b/examples/quick/MainWindow.qml index c025dbb..403fc35 100644 --- a/examples/quick/MainWindow.qml +++ b/examples/quick/MainWindow.qml @@ -76,14 +76,14 @@ FramelessWindow { Component.onCompleted: { // Make our homemade title bar snap to the window top frame border. window.snapToTopBorder(titleBar, FramelessHelper.Top, FramelessHelper.Bottom); - // Make our homemade title bar draggable, and on Windows, open the system menu + // Make our homemade title bar draggable, and open the system menu // when the user right clicks on the title bar area. window.titleBarItem = titleBar; - // Make our homemade system buttons visible to hit test. - // The call to "setHitTestVisible()" is necessary, don't forget to do it. - window.setHitTestVisible(minimizeButton); - window.setHitTestVisible(maximizeButton); - window.setHitTestVisible(closeButton); + // Make our own items visible to the hit test and on Windows, enable + // the snap layout feature (available since Windows 11). + window.setSystemButton(minimizeButton, FramelessHelper.Minimize); + window.setSystemButton(maximizeButton, FramelessHelper.Maximize); + window.setSystemButton(closeButton, FramelessHelper.Close); } } } diff --git a/include/FramelessHelper/Core/framelesshelpercore_global.h b/include/FramelessHelper/Core/framelesshelpercore_global.h index 9e60b9d..2040dbd 100644 --- a/include/FramelessHelper/Core/framelesshelpercore_global.h +++ b/include/FramelessHelper/Core/framelesshelpercore_global.h @@ -197,9 +197,9 @@ enum class Option ForceHideWindowFrameBorder = 0x00000001, // Windows only, force hide the window frame border even on Windows 10 and onwards. ForceShowWindowFrameBorder = 0x00000002, // Windows only, force show the window frame border even on Windows 7 (~ 8.1). DontDrawTopWindowFrameBorder = 0x00000004, // Windows only, don't draw the top window frame border even if the window frame border is visible. - EnableRoundedWindowCorners = 0x00000008, // Not implemented yet. + DontForceSquareWindowCorners = 0x00000008, // Windows only, don't force the window corners to be square if the default corner style is not square. TransparentWindowBackground = 0x00000010, // Make the window's background become transparent. - MaximizeButtonDocking = 0x00000020, // Not implemented yet. + DisableWindowsSnapLayout = 0x00000020, // Windows only, don't enable the snap layout feature (available since Windows 11) unconditionally. CreateStandardWindowLayout = 0x00000040, // Using this option will cause FramelessHelper create a homemade titlebar and a window layout to contain it. If your window has a layout already, the newly created layout will mess up your own layout. BeCompatibleWithQtFramelessWindowHint = 0x00000080, // Windows only, make the code compatible with Qt::FramelessWindowHint. Don't use this option unless you really need that flag. DontTouchQtInternals = 0x00000100, // Windows only, don't modify Qt's internal data. diff --git a/include/FramelessHelper/Core/utils.h b/include/FramelessHelper/Core/utils.h index 8b8a30f..ac9fed0 100644 --- a/include/FramelessHelper/Core/utils.h +++ b/include/FramelessHelper/Core/utils.h @@ -95,7 +95,9 @@ FRAMELESSHELPER_CORE_API void installSystemMenuHook( const WId windowId, const Global::Options options, const QPoint &offset, - const Global::IsWindowFixedSizeCallback &isWindowFixedSize); + const Global::IsWindowFixedSizeCallback &isWindowFixedSize, + const Global::IsInsideTitleBarDraggableAreaCallback &isInTitleBarArea, + const Global::GetWindowDevicePixelRatioCallback &getDevicePixelRatio); FRAMELESSHELPER_CORE_API void uninstallSystemMenuHook(const WId windowId); FRAMELESSHELPER_CORE_API void tryToBeCompatibleWithQtFramelessWindowHint( const WId windowId, @@ -106,6 +108,7 @@ FRAMELESSHELPER_CORE_API void setAeroSnappingEnabled(const WId windowId, const b FRAMELESSHELPER_CORE_API void tryToEnableHighestDpiAwarenessLevel(); FRAMELESSHELPER_CORE_API void updateGlobalWin32ControlsTheme(const WId windowId, const bool dark); [[nodiscard]] FRAMELESSHELPER_CORE_API bool shouldAppsUseDarkMode_windows(); +FRAMELESSHELPER_CORE_API void forceSquareCornersForWindow(const WId windowId, const bool force); #endif // Q_OS_WINDOWS #ifdef Q_OS_LINUX diff --git a/include/FramelessHelper/Quick/framelesshelperquick_global.h b/include/FramelessHelper/Quick/framelesshelperquick_global.h index e274aa3..72f7ce0 100644 --- a/include/FramelessHelper/Quick/framelesshelperquick_global.h +++ b/include/FramelessHelper/Quick/framelesshelperquick_global.h @@ -81,9 +81,9 @@ struct FRAMELESSHELPER_QUICK_API QuickGlobal 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, DontForceSquareWindowCorners) FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, TransparentWindowBackground) - FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, MaximizeButtonDocking) + FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DisableWindowsSnapLayout) FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, CreateStandardWindowLayout) FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, BeCompatibleWithQtFramelessWindowHint) FRAMELESSHELPER_QUICK_ENUM_VALUE(Option, DontTouchQtInternals) diff --git a/include/FramelessHelper/Quick/framelessquickutils.h b/include/FramelessHelper/Quick/framelessquickutils.h index 80416c6..0867603 100644 --- a/include/FramelessHelper/Quick/framelessquickutils.h +++ b/include/FramelessHelper/Quick/framelessquickutils.h @@ -63,8 +63,6 @@ public: explicit FramelessQuickUtils(QObject *parent = nullptr); ~FramelessQuickUtils() override; - Q_NODISCARD static FramelessQuickUtils *instance(); - Q_NODISCARD static qreal titleBarHeight(); Q_NODISCARD static bool frameBorderVisible(); Q_NODISCARD static qreal frameBorderThickness(); @@ -81,19 +79,6 @@ public: Q_NODISCARD Q_INVOKABLE static QColor getSystemButtonBackgroundColor( const QuickGlobal::SystemButtonType button, const QuickGlobal::ButtonState state); -#if 0 -public Q_SLOTS: - static void removeWindowFrame(QQuickWindow *window); - static void setTitleBarItem(QQuickWindow *window, QQuickItem *item); - static void setHitTestVisible(QQuickWindow *window, QQuickItem *item); - static void setWindowFixedSize(QQuickWindow *window, const bool value); - static void moveWindowToDesktopCenter(QQuickWindow *window); - static void startSystemMove2(QQuickWindow *window, const QPoint &pos); - static void startSystemResize2(QQuickWindow *window, const Qt::Edges edges, const QPoint &pos); - static void bringWindowToFront(QQuickWindow *window); - static void showSystemMenu(QQuickWindow *window, const QPoint &pos); -#endif - Q_SIGNALS: void darkModeEnabledChanged(); void systemAccentColorChanged(); diff --git a/include/FramelessHelper/Quick/framelessquickwindow.h b/include/FramelessHelper/Quick/framelessquickwindow.h index b3d2ed6..dd99ea3 100644 --- a/include/FramelessHelper/Quick/framelessquickwindow.h +++ b/include/FramelessHelper/Quick/framelessquickwindow.h @@ -82,6 +82,7 @@ public Q_SLOTS: void moveToDesktopCenter(); void bringToFront(); void snapToTopBorder(QQuickItem *item, const QuickGlobal::Anchor itemAnchor, const QuickGlobal::Anchor topBorderAnchor); + void setSystemButton(QQuickItem *item, const QuickGlobal::SystemButtonType buttonType); Q_SIGNALS: void hiddenChanged(); diff --git a/include/FramelessHelper/Widgets/framelessmainwindow.h b/include/FramelessHelper/Widgets/framelessmainwindow.h index 2ccef08..b8fabd9 100644 --- a/include/FramelessHelper/Widgets/framelessmainwindow.h +++ b/include/FramelessHelper/Widgets/framelessmainwindow.h @@ -63,6 +63,7 @@ public Q_SLOTS: void showSystemMenu(const QPoint &pos); void startSystemMove2(const QPoint &pos); void startSystemResize2(const Qt::Edges edges, const QPoint &pos); + void setSystemButton(QWidget *widget, const Global::SystemButtonType buttonType); Q_SIGNALS: void hiddenChanged(); diff --git a/include/FramelessHelper/Widgets/framelesswidget.h b/include/FramelessHelper/Widgets/framelesswidget.h index 6be876a..4408696 100644 --- a/include/FramelessHelper/Widgets/framelesswidget.h +++ b/include/FramelessHelper/Widgets/framelesswidget.h @@ -67,6 +67,7 @@ public Q_SLOTS: void showSystemMenu(const QPoint &pos); void startSystemMove2(const QPoint &pos); void startSystemResize2(const Qt::Edges edges, const QPoint &pos); + void setSystemButton(QWidget *widget, const Global::SystemButtonType buttonType); Q_SIGNALS: void hiddenChanged(); diff --git a/include/FramelessHelper/Widgets/standardsystembutton.h b/include/FramelessHelper/Widgets/standardsystembutton.h index a40cd89..22d8093 100644 --- a/include/FramelessHelper/Widgets/standardsystembutton.h +++ b/include/FramelessHelper/Widgets/standardsystembutton.h @@ -48,22 +48,18 @@ public: ~StandardSystemButton() override; Q_NODISCARD QSize sizeHint() const override; - - void setIcon(const QIcon &icon); - Q_NODISCARD Global::SystemButtonType buttonType(); - void setButtonType(const Global::SystemButtonType value); - Q_NODISCARD bool isHovered() const; - void setHovered(const bool value); - Q_NODISCARD bool isPressed() const; - void setPressed(const bool value); - Q_NODISCARD QColor hoverColor() const; - void setHoverColor(const QColor &value); - Q_NODISCARD QColor pressColor() const; + +public Q_SLOTS: + void setIcon(const QIcon &icon); + void setButtonType(const Global::SystemButtonType value); + void setHovered(const bool value); + void setPressed(const bool value); + void setHoverColor(const QColor &value); void setPressColor(const QColor &value); protected: diff --git a/src/core/framelesshelper_win.cpp b/src/core/framelesshelper_win.cpp index 6c73a67..d6bf320 100644 --- a/src/core/framelesshelper_win.cpp +++ b/src/core/framelesshelper_win.cpp @@ -42,6 +42,8 @@ struct Win32HelperData { UserSettings settings = {}; SystemParameters params = {}; + bool trackingMouse = false; + WId dragBarWindowId = 0; }; struct Win32Helper @@ -49,12 +51,14 @@ struct Win32Helper QMutex mutex; QScopedPointer nativeEventFilter; QHash data = {}; + QHash dragBarToParentWindowMapping = {}; }; Q_GLOBAL_STATIC(Win32Helper, g_win32Helper) FRAMELESSHELPER_BYTEARRAY_CONSTANT2(Win32MessageTypeName, "windows_generic_MSG") static const QString qThemeSettingChangeEventName = QString::fromWCharArray(kThemeSettingChangeEventName); +static constexpr const wchar_t kDragBarWindowClassName[] = L"FRAMELESSHELPER@DRAG_BAR_WINDOW_CLASS"; FRAMELESSHELPER_STRING_CONSTANT(MonitorFromWindow) FRAMELESSHELPER_STRING_CONSTANT(GetMonitorInfoW) FRAMELESSHELPER_STRING_CONSTANT(ScreenToClient) @@ -63,14 +67,335 @@ FRAMELESSHELPER_STRING_CONSTANT(GetClientRect) #ifdef Q_PROCESSOR_X86_64 FRAMELESSHELPER_STRING_CONSTANT(GetWindowLongPtrW) FRAMELESSHELPER_STRING_CONSTANT(SetWindowLongPtrW) -#else +#else // Q_PROCESSOR_X86_64 // WinUser.h defines G/SetClassLongPtr as G/SetClassLong due to the // "Ptr" suffixed APIs are not available on 32-bit platforms, so we // have to add the following workaround. Undefine the macros and then // redefine them is also an option but the following solution is more simple. FRAMELESSHELPER_STRING_CONSTANT2(GetWindowLongPtrW, "GetWindowLongW") FRAMELESSHELPER_STRING_CONSTANT2(SetWindowLongPtrW, "SetWindowLongW") -#endif +#endif // Q_PROCESSOR_X86_64 +FRAMELESSHELPER_STRING_CONSTANT(RegisterClassExW) +FRAMELESSHELPER_STRING_CONSTANT(GetModuleHandleW) +FRAMELESSHELPER_STRING_CONSTANT(CreateWindowExW) +FRAMELESSHELPER_STRING_CONSTANT(SetLayeredWindowAttributes) +FRAMELESSHELPER_STRING_CONSTANT(SetWindowPos) +FRAMELESSHELPER_STRING_CONSTANT(TrackMouseEvent) + +[[nodiscard]] static inline LRESULT CALLBACK DragBarWindowProc + (const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM lParam) +{ + Q_ASSERT(hWnd); + if (!hWnd) { + return 0; + } + const auto windowId = reinterpret_cast(hWnd); + g_win32Helper()->mutex.lock(); + if (!g_win32Helper()->dragBarToParentWindowMapping.contains(windowId)) { + g_win32Helper()->mutex.unlock(); + return DefWindowProcW(hWnd, uMsg, wParam, lParam); + } + const WId parentWindowId = g_win32Helper()->dragBarToParentWindowMapping.value(windowId); + if (!g_win32Helper()->data.contains(parentWindowId)) { + g_win32Helper()->mutex.unlock(); + return DefWindowProcW(hWnd, uMsg, wParam, lParam); + } + const Win32HelperData data = g_win32Helper()->data.value(parentWindowId); + g_win32Helper()->mutex.unlock(); + const auto parentWindowHandle = reinterpret_cast(parentWindowId); + const auto releaseButtons = [&data]() -> void { + static constexpr const auto defaultButtonState = ButtonState::Unspecified; + data.params.setSystemButtonState(SystemButtonType::WindowIcon, defaultButtonState); + data.params.setSystemButtonState(SystemButtonType::Help, defaultButtonState); + data.params.setSystemButtonState(SystemButtonType::Minimize, defaultButtonState); + data.params.setSystemButtonState(SystemButtonType::Maximize, defaultButtonState); + data.params.setSystemButtonState(SystemButtonType::Restore, defaultButtonState); + data.params.setSystemButtonState(SystemButtonType::Close, defaultButtonState); + }; + const auto hoverButton = [&releaseButtons, &data](const SystemButtonType button) -> void { + releaseButtons(); + data.params.setSystemButtonState(button, ButtonState::Hovered); + }; + const auto pressButton = [&releaseButtons, &data](const SystemButtonType button) -> void { + releaseButtons(); + data.params.setSystemButtonState(button, ButtonState::Pressed); + }; + const auto clickButton = [&releaseButtons, &data](const SystemButtonType button) -> void { + releaseButtons(); + data.params.setSystemButtonState(button, ButtonState::Clicked); + }; + switch (uMsg) { + case WM_NCHITTEST: { + // Try to determine what part of the window is being hovered here. This + // is absolutely critical to making sure the snap layout works! + const POINT nativeGlobalPos = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; + POINT nativeLocalPos = nativeGlobalPos; + if (ScreenToClient(hWnd, &nativeLocalPos) == FALSE) { + qWarning() << Utils::getSystemErrorMessage(kScreenToClient); + break; + } + const qreal devicePixelRatio = data.params.getWindowDevicePixelRatio(); + const QPoint qtScenePos = QPointF(QPointF(qreal(nativeLocalPos.x), qreal(nativeLocalPos.y)) / devicePixelRatio).toPoint(); + SystemButtonType buttonType = SystemButtonType::Unknown; + if (data.params.isInsideSystemButtons(qtScenePos, &buttonType)) { + switch (buttonType) { + case SystemButtonType::Unknown: + break; + case SystemButtonType::WindowIcon: + return HTSYSMENU; + case SystemButtonType::Help: + return HTHELP; + case SystemButtonType::Minimize: + return HTREDUCE; + case SystemButtonType::Maximize: + case SystemButtonType::Restore: + return HTZOOM; + case SystemButtonType::Close: + return HTCLOSE; + } + } + // The parent window has quite some logic in the hit test handler, we + // should forward this message to the parent window and return what it + // returns to make sure our homemade titlebar is still functional. + return SendMessageW(parentWindowHandle, WM_NCHITTEST, 0, lParam); + } + case WM_NCMOUSEMOVE: { + // When we get this message, it's because the mouse moved when it was + // over somewhere we said was the non-client area. + // + // We'll use this to communicate state to the title bar control, so that + // it can update its visuals. + // - If we're over a button, hover it. + // - If we're over _anything else_, stop hovering the buttons. + switch (wParam) { + case HTTOP: + case HTCAPTION: { + releaseButtons(); + // Pass caption-related nonclient messages to the parent window. + // Make sure to do this for the HTTOP, which is the top resize + // border, so we can resize the window on the top. + return SendMessageW(parentWindowHandle, uMsg, wParam, lParam); + } + case HTSYSMENU: + hoverButton(SystemButtonType::WindowIcon); + break; + case HTHELP: + hoverButton(SystemButtonType::Help); + break; + case HTREDUCE: + hoverButton(SystemButtonType::Minimize); + break; + case HTZOOM: + hoverButton(SystemButtonType::Maximize); + break; + case HTCLOSE: + hoverButton(SystemButtonType::Close); + break; + default: + releaseButtons(); + break; + } + // If we haven't previously asked for mouse tracking, request mouse + // tracking. We need to do this so we can get the WM_NCMOUSELEAVE + // message when the mouse leave the titlebar. Otherwise, we won't always + // get that message (especially if the user moves the mouse _real + // fast_). + if (!data.trackingMouse && ((wParam == HTSYSMENU) || (wParam == HTHELP) + || (wParam == HTREDUCE) || (wParam == HTZOOM) || (wParam == HTCLOSE))) { + TRACKMOUSEEVENT tme; + SecureZeroMemory(&tme, sizeof(tme)); + tme.cbSize = sizeof(tme); + // TME_NONCLIENT is absolutely critical here. In my experimentation, + // we'd get WM_MOUSELEAVE messages after just a HOVER_DEFAULT + // timeout even though we're not requesting TME_HOVER, which kinda + // ruined the whole point of this. + tme.dwFlags = (TME_LEAVE | TME_NONCLIENT); + tme.hwndTrack = hWnd; + tme.dwHoverTime = HOVER_DEFAULT; // We don't _really_ care about this. + if (TrackMouseEvent(&tme) == FALSE) { + qWarning() << Utils::getSystemErrorMessage(kTrackMouseEvent); + break; + } + QMutexLocker locker(&g_win32Helper()->mutex); + g_win32Helper()->data[parentWindowId].trackingMouse = true; + } + } break; + case WM_NCMOUSELEAVE: + case WM_MOUSELEAVE: { + // When the mouse leaves the drag rect, make sure to dismiss any hover. + releaseButtons(); + QMutexLocker locker(&g_win32Helper()->mutex); + g_win32Helper()->data[parentWindowId].trackingMouse = false; + } break; + // NB: *Shouldn't be forwarding these* when they're not over the caption + // because they can inadvertently take action using the system's default + // metrics instead of our own. + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: { + // Manual handling for mouse clicks in the drag bar. If it's in a + // caption button, then tell the titlebar to "press" the button, which + // should change its visual state. + // + // If it's not in a caption button, then just forward the message along + // to the root HWND. Make sure to do this for the HTTOP, which is the + // top resize border. + switch (wParam) { + case HTTOP: + case HTCAPTION: + // Pass caption-related nonclient messages to the parent window. + return SendMessageW(parentWindowHandle, uMsg, wParam, lParam); + // The buttons won't work as you'd expect; we need to handle those + // ourselves. + case HTSYSMENU: + pressButton(SystemButtonType::WindowIcon); + break; + case HTHELP: + pressButton(SystemButtonType::Help); + break; + case HTREDUCE: + pressButton(SystemButtonType::Minimize); + break; + case HTZOOM: + pressButton(SystemButtonType::Maximize); + break; + case HTCLOSE: + pressButton(SystemButtonType::Close); + break; + default: + break; + } + return 0; + } + case WM_NCLBUTTONUP: { + // Manual handling for mouse RELEASES in the drag bar. If it's in a + // caption button, then manually handle what we'd expect for that button. + // + // If it's not in a caption button, then just forward the message along + // to the root HWND. + switch (wParam) { + case HTTOP: + case HTCAPTION: + // Pass caption-related nonclient messages to the parent window. + return SendMessageW(parentWindowHandle, uMsg, wParam, lParam); + // The buttons won't work as you'd expect; we need to handle those ourselves. + case HTSYSMENU: + clickButton(SystemButtonType::WindowIcon); + break; + case HTHELP: + clickButton(SystemButtonType::Help); + break; + case HTREDUCE: + clickButton(SystemButtonType::Minimize); + break; + case HTZOOM: + clickButton(SystemButtonType::Maximize); + break; + case HTCLOSE: + clickButton(SystemButtonType::Close); + break; + default: + break; + } + return 0; + } + // Make sure to pass along right-clicks in this region to our parent window + // - we don't need to handle these. + case WM_NCRBUTTONDOWN: + case WM_NCRBUTTONDBLCLK: + case WM_NCRBUTTONUP: + return SendMessageW(parentWindowHandle, uMsg, wParam, lParam); + default: + break; + } + // Forward all the mouse events we don't handled here to the parent window, + // this is a necessary step to make sure the child widgets/quick items can still + // receive mouse events from our homemade titlebar. + if (((uMsg >= WM_MOUSEFIRST) && (uMsg <= WM_MOUSELAST)) || + ((uMsg >= WM_NCMOUSEMOVE) && (uMsg <= WM_NCXBUTTONDBLCLK))) { + SendMessageW(parentWindowHandle, uMsg, wParam, lParam); + } + return DefWindowProcW(hWnd, uMsg, wParam, lParam); +} + +[[nodiscard]] static inline bool resizeDragBarWindow(const WId parentWindowId, const WId dragBarWindowId) +{ + Q_ASSERT(parentWindowId); + Q_ASSERT(dragBarWindowId); + if (!parentWindowId || !dragBarWindowId) { + return false; + } + const auto parentWindowHandle = reinterpret_cast(parentWindowId); + RECT parentWindowClientRect = {}; + if (GetClientRect(parentWindowHandle, &parentWindowClientRect) == FALSE) { + qWarning() << Utils::getSystemErrorMessage(kGetClientRect); + return false; + } + const int titleBarHeight = Utils::getTitleBarHeight(parentWindowId, true); + const auto dragBarWindowHandle = reinterpret_cast(dragBarWindowId); + if (SetWindowPos(dragBarWindowHandle, HWND_TOP, 0, 0, parentWindowClientRect.right, + titleBarHeight, (SWP_NOACTIVATE | SWP_SHOWWINDOW)) == FALSE) { + qWarning() << Utils::getSystemErrorMessage(kSetWindowPos); + return false; + } + return true; +} + +[[nodiscard]] static inline bool createDragBarWindow(const WId parentWindowId) +{ + Q_ASSERT(parentWindowId); + if (!parentWindowId) { + return false; + } + if (!Utils::isWindowsVersionOrGreater(WindowsVersion::_8)) { + qWarning() << "Our drag bar window needs the WS_EX_LAYERED style, however, " + "it's not supported for child windows until Windows 8."; + return false; + } + const auto parentWindowHandle = reinterpret_cast(parentWindowId); + const auto instance = static_cast(GetModuleHandleW(nullptr)); + Q_ASSERT(instance); + if (!instance) { + qWarning() << Utils::getSystemErrorMessage(kGetModuleHandleW); + return false; + } + static const ATOM dragBarWindowClass = [instance]() -> ATOM { + WNDCLASSEXW wcex; + SecureZeroMemory(&wcex, sizeof(wcex)); + wcex.cbSize = sizeof(wcex); + wcex.style = (CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS); + wcex.lpszClassName = kDragBarWindowClassName; + wcex.hbrBackground = static_cast(GetStockObject(BLACK_BRUSH)); + wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW); + wcex.lpfnWndProc = DragBarWindowProc; + wcex.hInstance = instance; + return RegisterClassExW(&wcex); + }(); + Q_ASSERT(dragBarWindowClass); + if (!dragBarWindowClass) { + qWarning() << Utils::getSystemErrorMessage(kRegisterClassExW); + return false; + } + const HWND dragBarWindowHandle = CreateWindowExW((WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP), + kDragBarWindowClassName, nullptr, WS_CHILD, 0, 0, 0, 0, parentWindowHandle, nullptr, instance, nullptr); + Q_ASSERT(dragBarWindowHandle); + if (!dragBarWindowHandle) { + qWarning() << Utils::getSystemErrorMessage(kCreateWindowExW); + return false; + } + if (SetLayeredWindowAttributes(dragBarWindowHandle, 0, 255, LWA_ALPHA) == FALSE) { + qWarning() << Utils::getSystemErrorMessage(kSetLayeredWindowAttributes); + return false; + } + const auto dragBarWindowId = reinterpret_cast(dragBarWindowHandle); + if (!resizeDragBarWindow(parentWindowId, dragBarWindowId)) { + qWarning() << "Failed to re-position the drag bar window."; + return false; + } + QMutexLocker locker(&g_win32Helper()->mutex); + g_win32Helper()->data[parentWindowId].dragBarWindowId = dragBarWindowId; + g_win32Helper()->dragBarToParentWindowMapping.insert(dragBarWindowId, parentWindowId); + return true; +} FramelessHelperWin::FramelessHelperWin() : QAbstractNativeEventFilter() {} @@ -108,14 +433,26 @@ void FramelessHelperWin::addWindow(const UserSettings &settings, const SystemPar } Utils::updateInternalWindowFrameMargins(params.getWindowHandle(), true); Utils::updateWindowFrameMargins(windowId, false); - if (Utils::isWindowsVersionOrGreater(WindowsVersion::_10_1607)) { - const bool dark = Utils::shouldAppsUseDarkMode(); - if (!(settings.options & Option::DontTouchWindowFrameBorderColor)) { - Utils::updateWindowFrameBorderColor(windowId, dark); + if (Utils::isWindowsVersionOrGreater(WindowsVersion::_8)) { + if (!(settings.options & Option::DisableWindowsSnapLayout)) { + if (!createDragBarWindow(windowId)) { + qWarning() << "Failed to create the drag bar window."; + } } - if (Utils::isWindowsVersionOrGreater(WindowsVersion::_10_1809)) { - if (settings.options & Option::SyncNativeControlsThemeWithSystem) { - Utils::updateGlobalWin32ControlsTheme(windowId, dark); + if (Utils::isWindowsVersionOrGreater(WindowsVersion::_10_1607)) { + const bool dark = Utils::shouldAppsUseDarkMode(); + if (!(settings.options & Option::DontTouchWindowFrameBorderColor)) { + Utils::updateWindowFrameBorderColor(windowId, dark); + } + if (Utils::isWindowsVersionOrGreater(WindowsVersion::_10_1809)) { + if (settings.options & Option::SyncNativeControlsThemeWithSystem) { + Utils::updateGlobalWin32ControlsTheme(windowId, dark); + } + if (Utils::isWindowsVersionOrGreater(WindowsVersion::_11_21H2)) { + if (!(settings.options & Option::DontForceSquareWindowCorners)) { + Utils::forceSquareCornersForWindow(windowId, true); + } + } } } } @@ -669,6 +1006,18 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me break; } } + if (Utils::isWindowsVersionOrGreater(WindowsVersion::_8) && data.dragBarWindowId) { + switch (uMsg) { + case WM_SIZE: + case WM_DISPLAYCHANGE: { + if (!resizeDragBarWindow(windowId, data.dragBarWindowId)) { + qWarning() << "Failed to re-position the drag bar window."; + } + } break; + default: + break; + } + } bool systemThemeChanged = ((uMsg == WM_THEMECHANGED) || (uMsg == WM_SYSCOLORCHANGE) || (uMsg == WM_DWMCOLORIZATIONCOLORCHANGED)); if (Utils::isWindowsVersionOrGreater(WindowsVersion::_10_1607)) { diff --git a/src/core/framelesswindowsmanager.cpp b/src/core/framelesswindowsmanager.cpp index d8367fa..c75b42c 100644 --- a/src/core/framelesswindowsmanager.cpp +++ b/src/core/framelesswindowsmanager.cpp @@ -158,7 +158,8 @@ void FramelessWindowsManagerPrivate::addWindow(const UserSettings &settings, con FramelessHelperWin::addWindow(settings, params); } if (!(settings.options & Option::DontInstallSystemMenuHook)) { - Utils::installSystemMenuHook(windowId, settings.options, settings.systemMenuOffset, params.isWindowFixedSize); + Utils::installSystemMenuHook(windowId, settings.options, settings.systemMenuOffset, + params.isWindowFixedSize, params.isInsideTitleBarDraggableArea, params.getWindowDevicePixelRatio); } #endif } diff --git a/src/core/utils_win.cpp b/src/core/utils_win.cpp index 113501a..bc5d6e0 100644 --- a/src/core/utils_win.cpp +++ b/src/core/utils_win.cpp @@ -53,6 +53,8 @@ struct Win32UtilsHelperData Options options = {}; QPoint offset = {}; IsWindowFixedSizeCallback isWindowFixedSize = nullptr; + IsInsideTitleBarDraggableAreaCallback isInTitleBarArea = nullptr; + GetWindowDevicePixelRatioCallback getDevicePixelRatio = nullptr; }; struct Win32UtilsHelper @@ -97,7 +99,7 @@ FRAMELESSHELPER_STRING_CONSTANT(SystemParametersInfoW) FRAMELESSHELPER_STRING_CONSTANT(SetClassLongPtrW) FRAMELESSHELPER_STRING_CONSTANT(GetWindowLongPtrW) FRAMELESSHELPER_STRING_CONSTANT(SetWindowLongPtrW) -#else +#else // Q_PROCESSOR_X86_64 // WinUser.h defines G/SetClassLongPtr as G/SetClassLong due to the // "Ptr" suffixed APIs are not available on 32-bit platforms, so we // have to add the following workaround. Undefine the macros and then @@ -106,7 +108,7 @@ FRAMELESSHELPER_STRING_CONSTANT(SystemParametersInfoW) FRAMELESSHELPER_STRING_CONSTANT2(SetClassLongPtrW, "SetClassLongW") FRAMELESSHELPER_STRING_CONSTANT2(GetWindowLongPtrW, "GetWindowLongW") FRAMELESSHELPER_STRING_CONSTANT2(SetWindowLongPtrW, "SetWindowLongW") -#endif +#endif // Q_PROCESSOR_X86_64 FRAMELESSHELPER_STRING_CONSTANT(ReleaseCapture) FRAMELESSHELPER_STRING_CONSTANT(SetWindowTheme) FRAMELESSHELPER_STRING_CONSTANT(SetProcessDpiAwarenessContext) @@ -124,6 +126,7 @@ FRAMELESSHELPER_STRING_CONSTANT(EnableMenuItem) FRAMELESSHELPER_STRING_CONSTANT(SetMenuDefaultItem) FRAMELESSHELPER_STRING_CONSTANT(HiliteMenuItem) FRAMELESSHELPER_STRING_CONSTANT(TrackPopupMenu) +FRAMELESSHELPER_STRING_CONSTANT(ClientToScreen) [[nodiscard]] static inline bool doCompareWindowsVersion(const DWORD dwMajor, const DWORD dwMinor, const DWORD dwBuild) @@ -238,10 +241,10 @@ FRAMELESSHELPER_STRING_CONSTANT(TrackPopupMenu) } const Win32UtilsHelperData data = g_utilsHelper()->data.value(windowId); g_utilsHelper()->mutex.unlock(); - const auto getGlobalPosFromMouse = [lParam]() -> QPoint { + const auto getNativePosFromMouse = [lParam]() -> QPoint { return {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; }; - const auto getGlobalPosFromKeyboard = [hWnd, windowId, &data]() -> QPoint { + const auto getNativeGlobalPosFromKeyboard = [hWnd, windowId, &data]() -> QPoint { RECT windowPos = {}; if (GetWindowRect(hWnd, &windowPos) == FALSE) { qWarning() << Utils::getSystemErrorMessage(kGetWindowRect); @@ -273,30 +276,51 @@ FRAMELESSHELPER_STRING_CONSTANT(TrackPopupMenu) }; bool shouldShowSystemMenu = false; bool broughtByKeyboard = false; - QPoint globalPos = {}; - if (uMsg == WM_NCRBUTTONUP) { + QPoint nativeGlobalPos = {}; + switch (uMsg) { + case WM_RBUTTONUP: { + const QPoint nativeLocalPos = getNativePosFromMouse(); + const qreal dpr = data.getDevicePixelRatio(); + const QPoint qtScenePos = QPointF(QPointF(nativeLocalPos) / dpr).toPoint(); + if (data.isInTitleBarArea(qtScenePos)) { + POINT pos = {nativeLocalPos.x(), nativeLocalPos.y()}; + if (ClientToScreen(hWnd, &pos) == FALSE) { + qWarning() << Utils::getSystemErrorMessage(kClientToScreen); + break; + } + shouldShowSystemMenu = true; + nativeGlobalPos = {pos.x, pos.y}; + } + } break; + case WM_NCRBUTTONUP: { if (wParam == HTCAPTION) { shouldShowSystemMenu = true; - globalPos = getGlobalPosFromMouse(); + nativeGlobalPos = getNativePosFromMouse(); } - } else if (uMsg == WM_SYSCOMMAND) { + } break; + case WM_SYSCOMMAND: { const WPARAM filteredWParam = (wParam & 0xFFF0); if ((filteredWParam == SC_KEYMENU) && (lParam == VK_SPACE)) { shouldShowSystemMenu = true; broughtByKeyboard = true; - globalPos = getGlobalPosFromKeyboard(); + nativeGlobalPos = getNativeGlobalPosFromKeyboard(); } - } else if ((uMsg == WM_KEYDOWN) || (uMsg == WM_SYSKEYDOWN)) { + } break; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: { const bool altPressed = ((wParam == VK_MENU) || (GetKeyState(VK_MENU) < 0)); const bool spacePressed = ((wParam == VK_SPACE) || (GetKeyState(VK_SPACE) < 0)); if (altPressed && spacePressed) { shouldShowSystemMenu = true; broughtByKeyboard = true; - globalPos = getGlobalPosFromKeyboard(); + nativeGlobalPos = getNativeGlobalPosFromKeyboard(); } + } break; + default: + break; } if (shouldShowSystemMenu) { - Utils::showSystemMenu(windowId, globalPos, data.offset, + Utils::showSystemMenu(windowId, nativeGlobalPos, data.offset, broughtByKeyboard, data.options, data.isWindowFixedSize); // QPA's internal code will handle system menu events separately, and its // behavior is not what we would want to see because it doesn't know our @@ -1040,7 +1064,9 @@ bool Utils::isFrameBorderColorized() } void Utils::installSystemMenuHook(const WId windowId, const Options options, const QPoint &offset, - const IsWindowFixedSizeCallback &isWindowFixedSize) + const IsWindowFixedSizeCallback &isWindowFixedSize, + const IsInsideTitleBarDraggableAreaCallback &isInTitleBarArea, + const GetWindowDevicePixelRatioCallback &getDevicePixelRatio) { Q_ASSERT(windowId); Q_ASSERT(isWindowFixedSize); @@ -1070,6 +1096,8 @@ void Utils::installSystemMenuHook(const WId windowId, const Options options, con data.options = options; data.offset = offset; data.isWindowFixedSize = isWindowFixedSize; + data.isInTitleBarArea = isInTitleBarArea; + data.getDevicePixelRatio = getDevicePixelRatio; g_utilsHelper()->data.insert(windowId, data); } @@ -1295,4 +1323,28 @@ bool Utils::shouldAppsUseDarkMode_windows() return resultFromRegistry(); } +void Utils::forceSquareCornersForWindow(const WId windowId, const bool force) +{ + Q_ASSERT(windowId); + if (!windowId) { + return; + } + // We cannot change the window corner style until Windows 11. + if (!isWindowsVersionOrGreater(WindowsVersion::_11_21H2)) { + return; + } + static const auto pDwmSetWindowAttribute = + reinterpret_cast( + QSystemLibrary::resolve(kdwmapi, "DwmSetWindowAttribute")); + if (!pDwmSetWindowAttribute) { + return; + } + const auto hwnd = reinterpret_cast(windowId); + const DWM_WINDOW_CORNER_PREFERENCE wcp = (force ? DWMWCP_DONOTROUND : DWMWCP_ROUND); + const HRESULT hr = pDwmSetWindowAttribute(hwnd, _DWMWA_WINDOW_CORNER_PREFERENCE, &wcp, sizeof(wcp)); + if (FAILED(hr)) { + qWarning() << __getSystemErrorMessage(kDwmSetWindowAttribute, hr); + } +} + FRAMELESSHELPER_END_NAMESPACE diff --git a/src/quick/framelessquickutils.cpp b/src/quick/framelessquickutils.cpp index 3fb8f10..8d08b0a 100644 --- a/src/quick/framelessquickutils.cpp +++ b/src/quick/framelessquickutils.cpp @@ -30,8 +30,6 @@ FRAMELESSHELPER_BEGIN_NAMESPACE using namespace Global; -Q_GLOBAL_STATIC(FramelessQuickUtils, g_quickUtils) - FramelessQuickUtils::FramelessQuickUtils(QObject *parent) : QObject(parent) { connect(FramelessWindowsManager::instance(), &FramelessWindowsManager::systemThemeChanged, this, [this](){ @@ -43,11 +41,6 @@ FramelessQuickUtils::FramelessQuickUtils(QObject *parent) : QObject(parent) FramelessQuickUtils::~FramelessQuickUtils() = default; -FramelessQuickUtils *FramelessQuickUtils::instance() -{ - return g_quickUtils(); -} - qreal FramelessQuickUtils::titleBarHeight() { return kDefaultTitleBarHeight; @@ -131,54 +124,4 @@ QColor FramelessQuickUtils::getSystemButtonBackgroundColor(const QuickGlobal::Sy FRAMELESSHELPER_ENUM_QUICK_TO_CORE(ButtonState, state)); } -#if 0 -void FramelessQuickUtils::removeWindowFrame(QQuickWindow *window) -{ - Q_ASSERT(window); - if (!window) { - return; - } -} - -void FramelessQuickUtils::setTitleBarItem(QQuickWindow *window, QQuickItem *item) -{ - -} - -void FramelessQuickUtils::setHitTestVisible(QQuickWindow *window, QQuickItem *item) -{ - -} - -void FramelessQuickUtils::setWindowFixedSize(QQuickWindow *window, const bool value) -{ - -} - -void FramelessQuickUtils::moveWindowToDesktopCenter(QQuickWindow *window) -{ - -} - -void FramelessQuickUtils::startSystemMove2(QQuickWindow *window, const QPoint &pos) -{ - -} - -void FramelessQuickUtils::startSystemResize2(QQuickWindow *window, const Qt::Edges edges, const QPoint &pos) -{ - -} - -void FramelessQuickUtils::bringWindowToFront(QQuickWindow *window) -{ - -} - -void FramelessQuickUtils::showSystemMenu(QQuickWindow *window, const QPoint &pos) -{ - -} -#endif - FRAMELESSHELPER_END_NAMESPACE diff --git a/src/quick/framelessquickwindow.cpp b/src/quick/framelessquickwindow.cpp index 619ac4c..f633ee8 100644 --- a/src/quick/framelessquickwindow.cpp +++ b/src/quick/framelessquickwindow.cpp @@ -44,9 +44,9 @@ static constexpr const char QTQUICK_BUTTON_CLASS_NAME[] = "QQuickAbstractButton" 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, DontForceSquareWindowCorners, 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, DisableWindowsSnapLayout, 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) @@ -73,9 +73,9 @@ static constexpr const char QTQUICK_BUTTON_CLASS_NAME[] = "QQuickAbstractButton" 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, DontForceSquareWindowCorners, 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, DisableWindowsSnapLayout, 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) @@ -352,6 +352,36 @@ void FramelessQuickWindowPrivate::setOptions(const QuickGlobal::Options value) Q_EMIT q->optionsChanged(); } +void FramelessQuickWindowPrivate::setSystemButton(QQuickItem *item, const QuickGlobal::SystemButtonType buttonType) +{ + Q_ASSERT(item); + Q_ASSERT(buttonType != QuickGlobal::SystemButtonType::Unknown); + if (!item || (buttonType == QuickGlobal::SystemButtonType::Unknown)) { + return; + } + switch (buttonType) { + case QuickGlobal::SystemButtonType::Unknown: + Q_ASSERT(false); + break; + case QuickGlobal::SystemButtonType::WindowIcon: + m_settings.windowIconButton = item; + break; + case QuickGlobal::SystemButtonType::Help: + m_settings.contextHelpButton = item; + break; + case QuickGlobal::SystemButtonType::Minimize: + m_settings.minimizeButton = item; + break; + case QuickGlobal::SystemButtonType::Maximize: + case QuickGlobal::SystemButtonType::Restore: + m_settings.maximizeButton = item; + break; + case QuickGlobal::SystemButtonType::Close: + m_settings.closeButton = item; + break; + } +} + bool FramelessQuickWindowPrivate::eventFilter(QObject *object, QEvent *event) { Q_ASSERT(object); @@ -884,6 +914,17 @@ void FramelessQuickWindow::snapToTopBorder(QQuickItem *item, const QuickGlobal:: d->snapToTopBorder(item, itemAnchor, topBorderAnchor); } +void FramelessQuickWindow::setSystemButton(QQuickItem *item, const QuickGlobal::SystemButtonType buttonType) +{ + Q_ASSERT(item); + Q_ASSERT(buttonType != QuickGlobal::SystemButtonType::Unknown); + if (!item || (buttonType == QuickGlobal::SystemButtonType::Unknown)) { + return; + } + Q_D(FramelessQuickWindow); + d->setSystemButton(item, buttonType); +} + void FramelessQuickWindow::showMinimized2() { Q_D(FramelessQuickWindow); diff --git a/src/quick/framelessquickwindow_p.h b/src/quick/framelessquickwindow_p.h index 2c20df6..208f3b2 100644 --- a/src/quick/framelessquickwindow_p.h +++ b/src/quick/framelessquickwindow_p.h @@ -85,6 +85,7 @@ public Q_SLOTS: void bringToFront(); void snapToTopBorder(QQuickItem *item, const QuickGlobal::Anchor itemAnchor, const QuickGlobal::Anchor topBorderAnchor); void setOptions(const QuickGlobal::Options value); + void setSystemButton(QQuickItem *item, const QuickGlobal::SystemButtonType buttonType); protected: Q_NODISCARD bool eventFilter(QObject *object, QEvent *event) override; diff --git a/src/widgets/framelessmainwindow.cpp b/src/widgets/framelessmainwindow.cpp index 95c7380..91a4b3f 100644 --- a/src/widgets/framelessmainwindow.cpp +++ b/src/widgets/framelessmainwindow.cpp @@ -106,4 +106,9 @@ void FramelessMainWindow::startSystemResize2(const Qt::Edges edges, const QPoint d_ptr->startSystemResize2(edges, pos); } +void FramelessMainWindow::setSystemButton(QWidget *widget, const SystemButtonType buttonType) +{ + d_ptr->setSystemButton(widget, buttonType); +} + FRAMELESSHELPER_END_NAMESPACE diff --git a/src/widgets/framelesswidget.cpp b/src/widgets/framelesswidget.cpp index 6210b7d..dfb39c4 100644 --- a/src/widgets/framelesswidget.cpp +++ b/src/widgets/framelesswidget.cpp @@ -116,4 +116,9 @@ void FramelessWidget::startSystemResize2(const Qt::Edges edges, const QPoint &po d_ptr->startSystemResize2(edges, pos); } +void FramelessWidget::setSystemButton(QWidget *widget, const SystemButtonType buttonType) +{ + d_ptr->setSystemButton(widget, buttonType); +} + FRAMELESSHELPER_END_NAMESPACE diff --git a/src/widgets/framelesswidgetshelper.cpp b/src/widgets/framelesswidgetshelper.cpp index ca6149f..dd12a82 100644 --- a/src/widgets/framelesswidgetshelper.cpp +++ b/src/widgets/framelesswidgetshelper.cpp @@ -728,6 +728,36 @@ void FramelessWidgetsHelper::startSystemResize2(const Qt::Edges edges, const QPo Utils::startSystemResize(q->windowHandle(), edges, pos); } +void FramelessWidgetsHelper::setSystemButton(QWidget *widget, const SystemButtonType buttonType) +{ + Q_ASSERT(widget); + Q_ASSERT(buttonType != SystemButtonType::Unknown); + if (!widget || (buttonType == SystemButtonType::Unknown)) { + return; + } + switch (buttonType) { + case SystemButtonType::Unknown: + Q_ASSERT(false); + break; + case SystemButtonType::WindowIcon: + m_settings.windowIconButton = widget; + break; + case SystemButtonType::Help: + m_settings.contextHelpButton = widget; + break; + case SystemButtonType::Minimize: + m_settings.minimizeButton = widget; + break; + case SystemButtonType::Maximize: + case SystemButtonType::Restore: + m_settings.maximizeButton = widget; + break; + case SystemButtonType::Close: + m_settings.closeButton = widget; + break; + } +} + bool FramelessWidgetsHelper::eventFilter(QObject *object, QEvent *event) { Q_ASSERT(object); diff --git a/src/widgets/framelesswidgetshelper_p.h b/src/widgets/framelesswidgetshelper_p.h index d863f22..ddb6ead 100644 --- a/src/widgets/framelesswidgetshelper_p.h +++ b/src/widgets/framelesswidgetshelper_p.h @@ -76,6 +76,7 @@ public Q_SLOTS: void showSystemMenu(const QPoint &pos); void startSystemMove2(const QPoint &pos); void startSystemResize2(const Qt::Edges edges, const QPoint &pos); + void setSystemButton(QWidget *widget, const Global::SystemButtonType buttonType); protected: Q_NODISCARD bool eventFilter(QObject *object, QEvent *event) override;