From fa6b5cfc45d64d8f46ce87090e12c49714d672a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E5=AD=90=E6=A5=9A=5Czhuzi?= Date: Fri, 22 Dec 2023 01:30:25 +0800 Subject: [PATCH] update --- src/FluFramelessHelper.cpp | 162 ++++++++++++++---- src/FluFramelessHelper.h | 17 +- .../imports/FluentUI/Controls/FluAppBar.qml | 44 ++++- .../imports/FluentUI/Controls/FluWindow.qml | 10 +- .../imports/FluentUI/Controls/FluAppBar.qml | 44 ++++- .../imports/FluentUI/Controls/FluWindow.qml | 10 +- 6 files changed, 227 insertions(+), 60 deletions(-) diff --git a/src/FluFramelessHelper.cpp b/src/FluFramelessHelper.cpp index 5e04d7f2..0a48fd9a 100644 --- a/src/FluFramelessHelper.cpp +++ b/src/FluFramelessHelper.cpp @@ -13,6 +13,7 @@ static inline QByteArray qtNativeEventType() static const auto result = "windows_generic_MSG"; return result; } + static inline bool isCompositionEnabled(){ typedef HRESULT (WINAPI* DwmIsCompositionEnabledPtr)(BOOL *pfEnabled); HMODULE module = LoadLibraryW(L"dwmapi.dll"); @@ -52,14 +53,14 @@ static inline void showShadow(HWND hwnd){ #endif -FramelessEventFilter::FramelessEventFilter(QQuickWindow* window){ - _window = window; - _current = window->winId(); +FramelessEventFilter::FramelessEventFilter(FluFramelessHelper* helper){ + _helper = helper; + _current = _helper->window->winId(); } bool FramelessEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, QT_NATIVE_EVENT_RESULT_TYPE *result){ #ifdef Q_OS_WIN - if ((eventType != qtNativeEventType()) || !message || !result || !_window) { + if ((eventType != qtNativeEventType()) || !message || _helper.isNull() || _helper->window.isNull()) { return false; } const auto msg = static_cast(message); @@ -102,6 +103,25 @@ bool FramelessEventFilter::nativeEventFilter(const QByteArray &eventType, void * return true; } return false; + }else if(uMsg == WM_NCHITTEST){ + if(_helper->hoverMaxBtn() && _helper->resizeable()){ + *result = HTMAXBUTTON; + return true; + } + return false; + }else if(uMsg == WM_NCLBUTTONDBLCLK || uMsg == WM_NCLBUTTONDOWN){ + if(_helper->hoverMaxBtn() && _helper->resizeable()){ + QMouseEvent event = QMouseEvent(QEvent::MouseButtonPress, QPoint(), QPoint(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QGuiApplication::sendEvent(_helper->maximizeButton(),&event); + return true; + } + return false; + }else if(uMsg == WM_NCLBUTTONUP || uMsg == WM_NCRBUTTONUP){ + if(_helper->hoverMaxBtn() && _helper->resizeable()){ + QMouseEvent event = QMouseEvent(QEvent::MouseButtonRelease, QPoint(), QPoint(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QGuiApplication::sendEvent(_helper->maximizeButton(),&event); + } + return false; } return false; #endif @@ -111,56 +131,60 @@ bool FramelessEventFilter::nativeEventFilter(const QByteArray &eventType, void * FluFramelessHelper::FluFramelessHelper(QObject *parent) : QObject{parent} { + } void FluFramelessHelper::classBegin(){ } -void FluFramelessHelper::updateCursor(int edges){ +void FluFramelessHelper::_updateCursor(int edges){ switch (edges) { case 0: - _window->setCursor(Qt::ArrowCursor); + window->setCursor(Qt::ArrowCursor); break; case Qt::LeftEdge: case Qt::RightEdge: - _window->setCursor(Qt::SizeHorCursor); + window->setCursor(Qt::SizeHorCursor); break; case Qt::TopEdge: case Qt::BottomEdge: - _window->setCursor(Qt::SizeVerCursor); + window->setCursor(Qt::SizeVerCursor); break; case Qt::LeftEdge | Qt::TopEdge: case Qt::RightEdge | Qt::BottomEdge: - _window->setCursor(Qt::SizeFDiagCursor); + window->setCursor(Qt::SizeFDiagCursor); break; case Qt::RightEdge | Qt::TopEdge: case Qt::LeftEdge | Qt::BottomEdge: - _window->setCursor(Qt::SizeBDiagCursor); + window->setCursor(Qt::SizeBDiagCursor); break; } } bool FluFramelessHelper::eventFilter(QObject *obj, QEvent *ev){ - if (!_window.isNull() && _window->flags() & Qt::FramelessWindowHint) { + if (!window.isNull() && window->flags() & Qt::FramelessWindowHint) { static int edges = 0; const int margin = 8; switch (ev->type()) { case QEvent::MouseButtonPress: if(edges!=0){ - updateCursor(edges); - _window->startSystemResize(Qt::Edges(edges)); + QMouseEvent *event = static_cast(ev); + if(event->button() == Qt::LeftButton){ + _updateCursor(edges); + window->startSystemResize(Qt::Edges(edges)); + } } break; case QEvent::MouseButtonRelease: edges = 0; - updateCursor(edges); + _updateCursor(edges); break; case QEvent::MouseMove: { - if(_window->visibility() == QWindow::Maximized || _window->visibility() == QWindow::FullScreen){ + if(_maximized() || _fullScreen()){ break; } - if(_window->width() == _window->maximumWidth() && _window->width() == _window->minimumWidth() && _window->height() == _window->maximumHeight() && _window->height() == _window->minimumHeight()){ + if(!resizeable()){ break; } QMouseEvent *event = static_cast(ev); @@ -170,10 +194,10 @@ bool FluFramelessHelper::eventFilter(QObject *obj, QEvent *ev){ #else event->position().toPoint(); #endif - if(p.x() >= margin && p.x() <= (_window->width() - margin) && p.y() >= margin && p.y() <= (_window->height() - margin)){ + if(p.x() >= margin && p.x() <= (window->width() - margin) && p.y() >= margin && p.y() <= (window->height() - margin)){ if(edges != 0){ edges = 0; - updateCursor(edges); + _updateCursor(edges); } break; } @@ -181,16 +205,16 @@ bool FluFramelessHelper::eventFilter(QObject *obj, QEvent *ev){ if ( p.x() < margin ) { edges |= Qt::LeftEdge; } - if ( p.x() > (_window->width() - margin) ) { + if ( p.x() > (window->width() - margin) ) { edges |= Qt::RightEdge; } if ( p.y() < margin ) { edges |= Qt::TopEdge; } - if ( p.y() > (_window->height() - margin) ) { + if ( p.y() > (window->height() - margin) ) { edges |= Qt::BottomEdge; } - updateCursor(edges); + _updateCursor(edges); break; } default: @@ -203,57 +227,123 @@ bool FluFramelessHelper::eventFilter(QObject *obj, QEvent *ev){ void FluFramelessHelper::componentComplete(){ auto o = parent(); while (nullptr != o) { - _window = (QQuickWindow*)o; + window = (QQuickWindow*)o; o = o->parent(); } - if(!_window.isNull()){ - _window->setFlags(Qt::FramelessWindowHint|Qt::Window|Qt::WindowTitleHint|Qt::WindowMinMaxButtonsHint|Qt::WindowCloseButtonHint); + if(!window.isNull()){ + window->setFlags(Qt::FramelessWindowHint|Qt::Window|Qt::WindowTitleHint|Qt::WindowMinMaxButtonsHint|Qt::WindowCloseButtonHint); #ifdef Q_OS_WIN - _nativeEvent =new FramelessEventFilter(_window); + _nativeEvent =new FramelessEventFilter(this); qApp->installNativeEventFilter(_nativeEvent); - HWND hwnd = reinterpret_cast(_window->winId()); + HWND hwnd = reinterpret_cast(window->winId()); SetWindowPos(hwnd,0,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOOWNERZORDER); showShadow(hwnd); #endif - _stayTop = QQmlProperty(_window,"stayTop"); + _stayTop = QQmlProperty(window,"stayTop"); _onStayTopChange(); _stayTop.connectNotifySignal(this,SLOT(_onStayTopChange())); - _screen = QQmlProperty(_window,"screen"); + _screen = QQmlProperty(window,"screen"); _screen.connectNotifySignal(this,SLOT(_onScreenChanged())); - _window->installEventFilter(this); + window->installEventFilter(this); } } void FluFramelessHelper::_onScreenChanged(){ #ifdef Q_OS_WIN - HWND hwnd = reinterpret_cast(_window->winId()); + HWND hwnd = reinterpret_cast(window->winId()); SetWindowPos(hwnd,0,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOOWNERZORDER); RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW); #endif } +void FluFramelessHelper::showSystemMenu(){ +#ifdef Q_OS_WIN + QPoint point = QCursor::pos(); + HWND hwnd = reinterpret_cast(window->winId()); + DWORD style = GetWindowLongPtr(hwnd,GWL_STYLE); + SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX); + const HMENU hMenu = ::GetSystemMenu(hwnd, FALSE); + DeleteMenu(hMenu, SC_MOVE, MF_BYCOMMAND); + DeleteMenu(hMenu, SC_SIZE, MF_BYCOMMAND); + if(_maximized() || _fullScreen()){ + EnableMenuItem(hMenu,SC_RESTORE,MFS_ENABLED); + }else{ + EnableMenuItem(hMenu,SC_RESTORE,MFS_DISABLED); + } + if(resizeable() && !_maximized() && !_fullScreen()){ + EnableMenuItem(hMenu,SC_MAXIMIZE,MFS_ENABLED); + }else{ + EnableMenuItem(hMenu,SC_MAXIMIZE,MFS_DISABLED); + } + const int result = TrackPopupMenu(hMenu, (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)), point.x(), point.y(), 0, hwnd, nullptr); + if (result != FALSE) { + PostMessageW(hwnd, WM_SYSCOMMAND, result, 0); + } + SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_THICKFRAME | WS_CAPTION &~ WS_SYSMENU); +#endif +} + void FluFramelessHelper::_onStayTopChange(){ bool isStayTop = _stayTop.read().toBool(); #ifdef Q_OS_WIN - HWND hwnd = reinterpret_cast(_window->winId()); + HWND hwnd = reinterpret_cast(window->winId()); DWORD style = GetWindowLongPtr(hwnd,GWL_STYLE); - SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_THICKFRAME | WS_CAPTION &~ WS_SYSMENU); + SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_THICKFRAME | WS_CAPTION | WS_MAXIMIZEBOX &~ WS_SYSMENU); if(isStayTop){ SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); }else{ SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } #else - _window->setFlag(Qt::WindowStaysOnTopHint,isStayTop); + window->setFlag(Qt::WindowStaysOnTopHint,isStayTop); #endif } FluFramelessHelper::~FluFramelessHelper(){ - if (!_window.isNull()) { - _window->setFlags(Qt::Window); + if (!window.isNull()) { + window->setFlags(Qt::Window); #ifdef Q_OS_WIN qApp->removeNativeEventFilter(_nativeEvent); + delete _nativeEvent; #endif - _window->removeEventFilter(this); + window->removeEventFilter(this); } } + +bool FluFramelessHelper::hoverMaxBtn(){ + QVariant appBar = window->property("appBar"); + if(appBar.isNull()){ + return false; + } + QVariant var; + QMetaObject::invokeMethod(appBar.value(), "maximizeButtonHover",Q_RETURN_ARG(QVariant, var)); + if(var.isNull()){ + return false; + } + return var.toBool(); +} + +QObject* FluFramelessHelper::maximizeButton(){ + QVariant appBar = window->property("appBar"); + if(appBar.isNull()){ + return nullptr; + } + QVariant var; + QMetaObject::invokeMethod(appBar.value(), "maximizeButton",Q_RETURN_ARG(QVariant, var)); + if(var.isNull()){ + return nullptr; + } + return var.value(); +} + +bool FluFramelessHelper::resizeable(){ + return !(window->width() == window->maximumWidth() && window->width() == window->minimumWidth() && window->height() == window->maximumHeight() && window->height() == window->minimumHeight()); +} + +bool FluFramelessHelper::_maximized(){ + return window->visibility() == QWindow::Maximized; +} + +bool FluFramelessHelper::_fullScreen(){ + return window->visibility() == QWindow::FullScreen; +} diff --git a/src/FluFramelessHelper.h b/src/FluFramelessHelper.h index 1dfa2219..09334533 100644 --- a/src/FluFramelessHelper.h +++ b/src/FluFramelessHelper.h @@ -15,13 +15,15 @@ using QT_NATIVE_EVENT_RESULT_TYPE = long; using QT_ENTER_EVENT_TYPE = QEvent; #endif +class FluFramelessHelper; + class FramelessEventFilter : public QAbstractNativeEventFilter { public: - FramelessEventFilter(QQuickWindow* window); + FramelessEventFilter(FluFramelessHelper* helper); bool nativeEventFilter(const QByteArray &eventType, void *message, QT_NATIVE_EVENT_RESULT_TYPE *result) override; public: - QQuickWindow* _window = nullptr; + QPointer _helper = nullptr; qint64 _current = 0; }; @@ -34,14 +36,21 @@ public: ~FluFramelessHelper(); void classBegin() override; void componentComplete() override; + bool hoverMaxBtn(); + bool resizeable(); + QObject* maximizeButton(); + Q_INVOKABLE void showSystemMenu(); protected: bool eventFilter(QObject *obj, QEvent *event) override; private: - void updateCursor(int edges); + void _updateCursor(int edges); + bool _maximized(); + bool _fullScreen(); Q_SLOT void _onStayTopChange(); Q_SLOT void _onScreenChanged(); +public: + QPointer window = nullptr; private: - QPointer _window = nullptr; FramelessEventFilter* _nativeEvent = nullptr; QQmlProperty _stayTop; QQmlProperty _screen; diff --git a/src/Qt5/imports/FluentUI/Controls/FluAppBar.qml b/src/Qt5/imports/FluentUI/Controls/FluAppBar.qml index a2966354..5b399d35 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluAppBar.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluAppBar.qml @@ -64,6 +64,11 @@ Rectangle{ FluTheme.darkMode = FluThemeType.Dark } } + property var systemMenuListener: function(){ + if(d.win instanceof FluWindow){ + d.win.showSystemMenu() + } + } id:control color: Qt.rgba(0,0,0,0) height: visible ? 30 : 0 @@ -71,6 +76,7 @@ Rectangle{ z: 65535 Item{ id:d + property bool hoverMaxBtn: false property var win: Window.window property bool stayTop: { if(d.win instanceof FluWindow){ @@ -83,14 +89,23 @@ Rectangle{ } MouseArea{ anchors.fill: parent - onPositionChanged: { - d.win.startSystemMove() - } - onDoubleClicked: { - if(d.resizable){ - btn_maximize.clicked() + onPositionChanged: + (mouse)=>{ + d.win.startSystemMove() + } + onDoubleClicked: + (mouse)=>{ + if(d.resizable && Qt.LeftButton){ + btn_maximize.clicked() + } + } + acceptedButtons: Qt.LeftButton|Qt.RightButton + onClicked: + (mouse)=>{ + if (mouse.button === Qt.RightButton){ + control.systemMenuListener() + } } - } } Row{ anchors{ @@ -218,7 +233,7 @@ Rectangle{ if(pressed){ return maximizePressColor } - return hovered ? maximizeHoverColor : maximizeNormalColor + return d.hoverMaxBtn ? maximizeHoverColor : maximizeNormalColor } Layout.alignment: Qt.AlignVCenter visible: d.resizable && !isMac && showMaximize @@ -263,4 +278,17 @@ Rectangle{ function darkButton(){ return btn_dark } + function maximizeButtonHover(){ + var hover = false; + var pos = btn_maximize.mapToGlobal(0,0) + if(btn_maximize.visible){ + var rect = Qt.rect(pos.x,pos.y,btn_maximize.width,btn_maximize.height) + pos = FluTools.cursorPos() + if(pos.x>rect.x && pos.x<(rect.x+rect.width) && pos.y>rect.y && pos.y<(rect.y+rect.height)){ + hover = true; + } + } + d.hoverMaxBtn = hover + return hover; + } } diff --git a/src/Qt5/imports/FluentUI/Controls/FluWindow.qml b/src/Qt5/imports/FluentUI/Controls/FluWindow.qml index cf5a15da..4793f8da 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluWindow.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluWindow.qml @@ -50,6 +50,7 @@ Window { event.accepted = false } } + signal showSystemMenu signal initArgument(var argument) signal firstVisible() id:window @@ -58,7 +59,7 @@ Window { moveWindowToDesktopCenter() useSystemAppBar = FluApp.useSystemAppBar if(!useSystemAppBar){ - loader_frameless.sourceComponent = com_frameless + loader_frameless_helper.sourceComponent = com_frameless } lifecycle.onCompleted(window) initArgument(argument) @@ -71,6 +72,11 @@ Window { Component.onDestruction: { lifecycle.onDestruction() } + onShowSystemMenu: { + if(loader_frameless_helper.item){ + loader_frameless_helper.item.showSystemMenu() + } + } onVisibleChanged: { if(visible && d.isFirstVisible){ window.firstVisible() @@ -165,7 +171,7 @@ Window { } } FluLoader{ - id:loader_frameless + id:loader_frameless_helper } Item{ id:layout_container diff --git a/src/Qt6/imports/FluentUI/Controls/FluAppBar.qml b/src/Qt6/imports/FluentUI/Controls/FluAppBar.qml index 37b85452..bc7ae1f9 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluAppBar.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluAppBar.qml @@ -64,6 +64,11 @@ Rectangle{ FluTheme.darkMode = FluThemeType.Dark } } + property var systemMenuListener: function(){ + if(d.win instanceof FluWindow){ + d.win.showSystemMenu() + } + } id:control color: Qt.rgba(0,0,0,0) height: visible ? 30 : 0 @@ -71,6 +76,7 @@ Rectangle{ z: 65535 Item{ id:d + property bool hoverMaxBtn: false property var win: Window.window property bool stayTop: { if(d.win instanceof FluWindow){ @@ -83,14 +89,23 @@ Rectangle{ } MouseArea{ anchors.fill: parent - onPositionChanged: { - d.win.startSystemMove() - } - onDoubleClicked: { - if(d.resizable){ - btn_maximize.clicked() + onPositionChanged: + (mouse)=>{ + d.win.startSystemMove() + } + onDoubleClicked: + (mouse)=>{ + if(d.resizable && Qt.LeftButton){ + btn_maximize.clicked() + } + } + acceptedButtons: Qt.LeftButton|Qt.RightButton + onClicked: + (mouse)=>{ + if (mouse.button === Qt.RightButton){ + control.systemMenuListener() + } } - } } Row{ anchors{ @@ -218,7 +233,7 @@ Rectangle{ if(pressed){ return maximizePressColor } - return hovered ? maximizeHoverColor : maximizeNormalColor + return d.hoverMaxBtn ? maximizeHoverColor : maximizeNormalColor } Layout.alignment: Qt.AlignVCenter visible: d.resizable && !isMac && showMaximize @@ -263,4 +278,17 @@ Rectangle{ function darkButton(){ return btn_dark } + function maximizeButtonHover(){ + var hover = false; + var pos = btn_maximize.mapToGlobal(0,0) + if(btn_maximize.visible){ + var rect = Qt.rect(pos.x,pos.y,btn_maximize.width,btn_maximize.height) + pos = FluTools.cursorPos() + if(pos.x>rect.x && pos.x<(rect.x+rect.width) && pos.y>rect.y && pos.y<(rect.y+rect.height)){ + hover = true; + } + } + d.hoverMaxBtn = hover + return hover; + } } diff --git a/src/Qt6/imports/FluentUI/Controls/FluWindow.qml b/src/Qt6/imports/FluentUI/Controls/FluWindow.qml index c28b7955..54f406b7 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluWindow.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluWindow.qml @@ -49,6 +49,7 @@ Window { event.accepted = false } } + signal showSystemMenu signal initArgument(var argument) signal firstVisible() id:window @@ -57,7 +58,7 @@ Window { moveWindowToDesktopCenter() useSystemAppBar = FluApp.useSystemAppBar if(!useSystemAppBar){ - loader_frameless.sourceComponent = com_frameless + loader_frameless_helper.sourceComponent = com_frameless } lifecycle.onCompleted(window) initArgument(argument) @@ -70,6 +71,11 @@ Window { Component.onDestruction: { lifecycle.onDestruction() } + onShowSystemMenu: { + if(loader_frameless_helper.item){ + loader_frameless_helper.item.showSystemMenu() + } + } onVisibleChanged: { if(visible && d.isFirstVisible){ window.firstVisible() @@ -164,7 +170,7 @@ Window { } } FluLoader{ - id:loader_frameless + id:loader_frameless_helper } Item{ id:layout_container