diff --git a/example/qml-Qt6/window/MainWindow.qml b/example/qml-Qt6/window/MainWindow.qml index fc352300..0b30f0c9 100644 --- a/example/qml-Qt6/window/MainWindow.qml +++ b/example/qml-Qt6/window/MainWindow.qml @@ -174,6 +174,9 @@ FluWindow { loader.reload() } } + Component.onCompleted: { + appBar.setHitTestVisible(layout_back_buttons) + } } FluRemoteLoader{ id:loader @@ -230,6 +233,9 @@ FluWindow { ItemsOriginal.paneItemMenu = nav_item_right_menu ItemsFooter.navigationView = nav_view ItemsFooter.paneItemMenu = nav_item_right_menu + appBar.setHitTestVisible(nav_view.buttonMenu) + appBar.setHitTestVisible(nav_view.buttonBack) + appBar.setHitTestVisible(nav_view.imageLogo) setCurrentIndex(0) } } @@ -310,9 +316,9 @@ FluWindow { steps:{ var data = [] if(!window.useSystemAppBar){ - data.push({title:"夜间模式",description: "这里可以切换夜间模式.",target:()=>appBar.darkButton()}) + data.push({title:"夜间模式",description: "这里可以切换夜间模式.",target:()=>appBar.buttonDark}) } - data.push({title:"隐藏彩蛋",description: "多点几下试试!!",target:()=>nav_view.logoButton()}) + data.push({title:"隐藏彩蛋",description: "多点几下试试!!",target:()=>nav_view.imageLogo}) return data } } @@ -385,5 +391,4 @@ FluWindow { FluNetwork.get("https://api.github.com/repos/zhuzichu520/FluentUI/releases/latest") .go(callable) } - } diff --git a/example/qml/window/MainWindow.qml b/example/qml/window/MainWindow.qml index e6af1faf..350d5ed3 100644 --- a/example/qml/window/MainWindow.qml +++ b/example/qml/window/MainWindow.qml @@ -174,6 +174,9 @@ FluWindow { loader.reload() } } + Component.onCompleted: { + appBar.setHitTestVisible(layout_back_buttons) + } } FluRemoteLoader{ id:loader @@ -230,6 +233,9 @@ FluWindow { ItemsOriginal.paneItemMenu = nav_item_right_menu ItemsFooter.navigationView = nav_view ItemsFooter.paneItemMenu = nav_item_right_menu + appBar.setHitTestVisible(nav_view.buttonMenu) + appBar.setHitTestVisible(nav_view.buttonBack) + appBar.setHitTestVisible(nav_view.imageLogo) setCurrentIndex(0) } } @@ -310,9 +316,9 @@ FluWindow { steps:{ var data = [] if(!window.useSystemAppBar){ - data.push({title:"夜间模式",description: "这里可以切换夜间模式.",target:()=>appBar.darkButton()}) + data.push({title:"夜间模式",description: "这里可以切换夜间模式.",target:()=>appBar.buttonDark}) } - data.push({title:"隐藏彩蛋",description: "多点几下试试!!",target:()=>nav_view.logoButton()}) + data.push({title:"隐藏彩蛋",description: "多点几下试试!!",target:()=>nav_view.imageLogo}) return data } } @@ -385,5 +391,4 @@ FluWindow { FluNetwork.get("https://api.github.com/repos/zhuzichu520/FluentUI/releases/latest") .go(callable) } - } diff --git a/src/FluFramelessHelper.cpp b/src/FluFramelessHelper.cpp index 3d9ba182..2fffcd18 100644 --- a/src/FluFramelessHelper.cpp +++ b/src/FluFramelessHelper.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "FluTools.h" #ifdef Q_OS_WIN @@ -83,7 +84,7 @@ bool FramelessEventFilter::nativeEventFilter(const QByteArray &eventType, void * int offsetSize = 0; bool isMaximum = IsZoomed(hwnd); offsetXY = QPoint(abs(clientRect->left - originalLeft),abs(clientRect->top - originalTop)); - if(isMaximum || _helper->window->visibility() == QWindow::FullScreen){ + if(isMaximum || _helper->fullScreen()){ _helper->setOriginalPos(QPoint(originalLeft,originalTop)); offsetSize = 0; }else{ @@ -110,6 +111,52 @@ bool FramelessEventFilter::nativeEventFilter(const QByteArray &eventType, void * } return true; } + *result = 0; + short x = LOWORD(msg->lParam); + short y = HIWORD(msg->lParam); + int margins = _helper->getMargins(); + QPointer win = _helper->window; + QPoint pos = win->mapFromGlobal(QPoint(x, y)); + bool left = pos.x() < margins; + bool right = pos.x() > win->width() - margins; + bool top = pos.y() < margins; + bool bottom = pos.y() > win->height() - margins; + *result = 0; + if (_helper->resizeable() && !_helper->fullScreen() && !_helper->maximized()) { + if (left && top) { + *result = HTTOPLEFT; + } else if (left && bottom) { + *result = HTBOTTOMLEFT; + } else if (right && top) { + *result = HTTOPRIGHT; + } else if (right && bottom) { + *result = HTBOTTOMRIGHT; + } else if (left) { + *result = HTLEFT; + } else if (right) { + *result = HTRIGHT; + } else if (top) { + *result = HTTOP; + } else if (bottom) { + *result = HTBOTTOM; + } + } + if (0 != *result) { + return true; + } + QVariant appBar = _helper->getAppBar(); + if(!appBar.isNull()){ + auto item = appBar.value(); + if(item->contains(pos)){ + QPoint appBarTopLeft = item->mapToItem(_helper->window->contentItem(),QPoint(0, 0)).toPoint(); + QRect rcAppBar = QRect(appBarTopLeft, QSize(item->width(), item->height())); + if (rcAppBar.contains(pos) && _helper->hoverAppBar()) + { + *result = HTCAPTION; + return true; + } + } + } *result = HTCLIENT; return true; }else if(uMsg == WM_NCLBUTTONDBLCLK || uMsg == WM_NCLBUTTONDOWN){ @@ -140,6 +187,10 @@ bool FramelessEventFilter::nativeEventFilter(const QByteArray &eventType, void * minmaxInfo->ptMaxSize.y = geometry.height()*pixelRatio + offsetXY.y()*2; #endif return false; + }else if(uMsg == WM_NCRBUTTONDOWN){ + if (wParam == HTCAPTION) { + _helper->showSystemMenu(); + } } return false; #endif @@ -181,7 +232,6 @@ void FluFramelessHelper::_updateCursor(int edges){ bool FluFramelessHelper::eventFilter(QObject *obj, QEvent *ev){ if (!window.isNull() && window->flags()) { - static int margin = 8; switch (ev->type()) { case QEvent::MouseButtonPress: if(_edges!=0){ @@ -196,7 +246,7 @@ bool FluFramelessHelper::eventFilter(QObject *obj, QEvent *ev){ _edges = 0; break; case QEvent::MouseMove: { - if(_maximized() || _fullScreen()){ + if(maximized() || fullScreen()){ break; } if(!resizeable()){ @@ -209,7 +259,7 @@ 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() >= _margins && p.x() <= (window->width() - _margins) && p.y() >= _margins && p.y() <= (window->height() - _margins)){ if(_edges != 0){ _edges = 0; _updateCursor(_edges); @@ -217,16 +267,16 @@ bool FluFramelessHelper::eventFilter(QObject *obj, QEvent *ev){ break; } _edges = 0; - if ( p.x() < margin ) { + if ( p.x() < _margins ) { _edges |= Qt::LeftEdge; } - if ( p.x() > (window->width() - margin) ) { + if ( p.x() > (window->width() - _margins) ) { _edges |= Qt::RightEdge; } - if ( p.y() < margin ) { + if ( p.y() < _margins ) { _edges |= Qt::TopEdge; } - if ( p.y() > (window->height() - margin) ) { + if ( p.y() > (window->height() - _margins) ) { _edges |= Qt::BottomEdge; } _updateCursor(_edges); @@ -256,7 +306,11 @@ void FluFramelessHelper::componentComplete(){ _realHeight = QQmlProperty(window,"_realHeight"); _realWidth = QQmlProperty(window,"_realWidth"); _appBarHeight = QQmlProperty(window,"_appBarHeight"); + _appBar = window->property("appBar"); #ifdef Q_OS_WIN + if(!_appBar.isNull()){ + _appBar.value()->setProperty("systemMoveEnable",false); + } window->setFlags((window->flags()) | Qt::CustomizeWindowHint | Qt::WindowMinimizeButtonHint); _nativeEvent =new FramelessEventFilter(this); qApp->installNativeEventFilter(_nativeEvent); @@ -283,6 +337,7 @@ void FluFramelessHelper::componentComplete(){ }); #else window->setFlags((window->flags() & (~Qt::WindowMinMaxButtonsHint) & (~Qt::Dialog)) | Qt::FramelessWindowHint | Qt::Window); + window->installEventFilter(this); #endif int w = _realWidth.read().toInt(); int h = _realHeight.read().toInt()+_appBarHeight.read().toInt(); @@ -295,7 +350,6 @@ void FluFramelessHelper::componentComplete(){ _onStayTopChange(); _stayTop.connectNotifySignal(this,SLOT(_onStayTopChange())); _screen.connectNotifySignal(this,SLOT(_onScreenChanged())); - window->installEventFilter(this); Q_EMIT loadCompleted(); } } @@ -315,16 +369,18 @@ void FluFramelessHelper::showSystemMenu(){ DWORD style = GetWindowLongPtr(hwnd,GWL_STYLE); SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_SYSMENU); const HMENU hMenu = ::GetSystemMenu(hwnd, FALSE); - DeleteMenu(hMenu, SC_MOVE, MF_BYCOMMAND); - DeleteMenu(hMenu, SC_SIZE, MF_BYCOMMAND); - if(_maximized() || _fullScreen()){ + if(maximized() || fullScreen()){ + EnableMenuItem(hMenu,SC_MOVE,MFS_DISABLED); EnableMenuItem(hMenu,SC_RESTORE,MFS_ENABLED); }else{ + EnableMenuItem(hMenu,SC_MOVE,MFS_ENABLED); EnableMenuItem(hMenu,SC_RESTORE,MFS_DISABLED); } - if(resizeable() && !_maximized() && !_fullScreen()){ + if(resizeable() && !maximized() && !fullScreen()){ + EnableMenuItem(hMenu,SC_SIZE,MFS_ENABLED); EnableMenuItem(hMenu,SC_MAXIMIZE,MFS_ENABLED); }else{ + EnableMenuItem(hMenu,SC_SIZE,MFS_DISABLED); EnableMenuItem(hMenu,SC_MAXIMIZE,MFS_DISABLED); } const int result = TrackPopupMenu(hMenu, (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)), point.x()*window->devicePixelRatio(), point.y()*window->devicePixelRatio(), 0, hwnd, nullptr); @@ -361,25 +417,38 @@ FluFramelessHelper::~FluFramelessHelper(){ } bool FluFramelessHelper::hoverMaxBtn(){ - QVariant appBar = window->property("appBar"); - if(appBar.isNull()){ + if(_appBar.isNull()){ return false; } QVariant var; - QMetaObject::invokeMethod(appBar.value(), "maximizeButtonHover",Q_RETURN_ARG(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; +bool FluFramelessHelper::hoverAppBar(){ + if(_appBar.isNull()){ + return false; } QVariant var; - QMetaObject::invokeMethod(appBar.value(), "maximizeButton",Q_RETURN_ARG(QVariant, var)); + QMetaObject::invokeMethod(_appBar.value(), "_appBarHover",Q_RETURN_ARG(QVariant, var)); + if(var.isNull()){ + return false; + } + return var.toBool(); +} + +QVariant FluFramelessHelper::getAppBar(){ + return _appBar; +} + +QObject* FluFramelessHelper::maximizeButton(){ + if(_appBar.isNull()){ + return nullptr; + } + QVariant var = _appBar.value()->property("buttonMaximize"); if(var.isNull()){ return nullptr; } @@ -394,10 +463,14 @@ bool FluFramelessHelper::resizeable(){ return !_fixSize.read().toBool(); } -bool FluFramelessHelper::_maximized(){ +bool FluFramelessHelper::maximized(){ return window->visibility() == QWindow::Maximized; } -bool FluFramelessHelper::_fullScreen(){ +bool FluFramelessHelper::fullScreen(){ return window->visibility() == QWindow::FullScreen; } + +int FluFramelessHelper::getMargins(){ + return _margins; +} diff --git a/src/FluFramelessHelper.h b/src/FluFramelessHelper.h index 6873081c..898c6c20 100644 --- a/src/FluFramelessHelper.h +++ b/src/FluFramelessHelper.h @@ -38,7 +38,12 @@ public: void classBegin() override; void componentComplete() override; bool hoverMaxBtn(); + bool hoverAppBar(); bool resizeable(); + int getMargins(); + bool maximized(); + bool fullScreen(); + QVariant getAppBar(); QObject* maximizeButton(); void setOriginalPos(QVariant pos); Q_INVOKABLE void showSystemMenu(); @@ -47,8 +52,6 @@ protected: bool eventFilter(QObject *obj, QEvent *event) override; private: void _updateCursor(int edges); - bool _maximized(); - bool _fullScreen(); Q_SLOT void _onStayTopChange(); Q_SLOT void _onScreenChanged(); public: @@ -62,7 +65,9 @@ private: QQmlProperty _realHeight; QQmlProperty _realWidth; QQmlProperty _appBarHeight; + QVariant _appBar; int _edges = 0; + int _margins = 8; }; #endif // FLUFRAMELESSHELPER_H diff --git a/src/Qt5/imports/FluentUI/Controls/FluAppBar.qml b/src/Qt5/imports/FluentUI/Controls/FluAppBar.qml index 1c4b7535..5332f367 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluAppBar.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluAppBar.qml @@ -33,6 +33,7 @@ Rectangle{ property int iconSize: 20 property bool isMac: FluTools.isMacos() property color borerlessColor : FluTheme.primaryColor + property bool systemMoveEnable: true property var maxClickListener : function(){ if(FluTools.isMacos()){ if (d.win.visibility === Window.FullScreen) @@ -70,6 +71,11 @@ Rectangle{ d.win.showSystemMenu() } } + property alias buttonStayTop: btn_stay_top + property alias buttonMinimize: btn_minimize + property alias buttonMaximize: btn_maximize + property alias buttonClose: btn_close + property alias buttonDark: btn_dark id:control color: Qt.rgba(0,0,0,0) height: visible ? 30 : 0 @@ -77,6 +83,7 @@ Rectangle{ z: 65535 Item{ id:d + property var hitTestList: [] property bool hoverMaxBtn: false property var win: Window.window property bool stayTop: { @@ -89,21 +96,24 @@ Rectangle{ property bool resizable: win && !(win.height === win.maximumHeight && win.height === win.minimumHeight && win.width === win.maximumWidth && win.width === win.minimumWidth) } MouseArea{ + id:mouse_app_bar anchors.fill: parent onPositionChanged: (mouse)=>{ - d.win.startSystemMove() + if(systemMoveEnable){ + d.win.startSystemMove() + } } onDoubleClicked: (mouse)=>{ - if(d.resizable && Qt.LeftButton){ + if(systemMoveEnable && d.resizable && Qt.LeftButton){ btn_maximize.clicked() } } acceptedButtons: Qt.LeftButton|Qt.RightButton onClicked: (mouse)=>{ - if (mouse.button === Qt.RightButton){ + if (systemMoveEnable && mouse.button === Qt.RightButton){ control.systemMenuListener() } } @@ -174,9 +184,13 @@ Rectangle{ } RowLayout{ + id:layout_row anchors.right: parent.right height: control.height spacing: 0 + Component.onCompleted: { + setHitTestVisible(layout_row) + } FluToggleSwitch{ id:btn_dark Layout.alignment: Qt.AlignVCenter @@ -267,23 +281,8 @@ Rectangle{ onClicked: closeClickListener() } } - function stayTopButton(){ - return btn_stay_top - } - function minimizeButton(){ - return btn_minimize - } - function maximizeButton(){ - return btn_maximize - } - function closeButton(){ - return btn_close - } - function darkButton(){ - return btn_dark - } - function maximizeButtonHover(){ - var hover = false; + 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) @@ -295,4 +294,21 @@ Rectangle{ d.hoverMaxBtn = hover return hover; } + function _appBarHover(){ + for(var i =0 ;i< d.hitTestList.length; i++){ + var item = d.hitTestList[i] + var pos = item.mapToGlobal(0,0) + if(item.visible){ + var rect = Qt.rect(pos.x,pos.y,item.width,item.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)){ + return false + } + } + } + return true + } + function setHitTestVisible(id){ + d.hitTestList.push(id) + } } diff --git a/src/Qt5/imports/FluentUI/Controls/FluNavigationView.qml b/src/Qt5/imports/FluentUI/Controls/FluNavigationView.qml index 3254b530..092ac6af 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluNavigationView.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluNavigationView.qml @@ -21,6 +21,9 @@ Item { property int cellHeight: 38 property int cellWidth: 300 property bool hideNavAppBar: false + property alias buttonMenu: btn_menu + property alias buttonBack: btn_back + property alias imageLogo: image_logo signal logoClicked id:control Item{ @@ -755,7 +758,7 @@ Item { } } FluIconButton{ - id:btn_nav + id:btn_menu iconSource: FluentIcons.GlobalNavButton iconSize: 15 Layout.preferredWidth: d.isMinimal ? 30 : 0 @@ -787,7 +790,7 @@ Item { Layout.preferredWidth: 20 source: control.logo Layout.leftMargin: { - if(btn_nav.visible){ + if(btn_menu.visible){ return 12 } return 5 @@ -1337,13 +1340,4 @@ Item { } } } - function backButton(){ - return btn_back - } - function navButton(){ - return btn_nav - } - function logoButton(){ - return image_logo - } } diff --git a/src/Qt6/imports/FluentUI/Controls/FluAppBar.qml b/src/Qt6/imports/FluentUI/Controls/FluAppBar.qml index b4d54735..0e6f5963 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluAppBar.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluAppBar.qml @@ -33,6 +33,7 @@ Rectangle{ property int iconSize: 20 property bool isMac: FluTools.isMacos() property color borerlessColor : FluTheme.primaryColor + property bool systemMoveEnable: true property var maxClickListener : function(){ if(FluTools.isMacos()){ if (d.win.visibility === Window.FullScreen) @@ -70,6 +71,11 @@ Rectangle{ d.win.showSystemMenu() } } + property alias buttonStayTop: btn_stay_top + property alias buttonMinimize: btn_minimize + property alias buttonMaximize: btn_maximize + property alias buttonClose: btn_close + property alias buttonDark: btn_dark id:control color: Qt.rgba(0,0,0,0) height: visible ? 30 : 0 @@ -77,6 +83,7 @@ Rectangle{ z: 65535 Item{ id:d + property var hitTestList: [] property bool hoverMaxBtn: false property var win: Window.window property bool stayTop: { @@ -89,21 +96,24 @@ Rectangle{ property bool resizable: win && !(win.height === win.maximumHeight && win.height === win.minimumHeight && win.width === win.maximumWidth && win.width === win.minimumWidth) } MouseArea{ + id:mouse_app_bar anchors.fill: parent onPositionChanged: (mouse)=>{ - d.win.startSystemMove() + if(systemMoveEnable){ + d.win.startSystemMove() + } } onDoubleClicked: (mouse)=>{ - if(d.resizable && Qt.LeftButton){ + if(systemMoveEnable && d.resizable && Qt.LeftButton){ btn_maximize.clicked() } } acceptedButtons: Qt.LeftButton|Qt.RightButton onClicked: (mouse)=>{ - if (mouse.button === Qt.RightButton){ + if (systemMoveEnable && mouse.button === Qt.RightButton){ control.systemMenuListener() } } @@ -174,9 +184,13 @@ Rectangle{ } RowLayout{ + id:layout_row anchors.right: parent.right height: control.height spacing: 0 + Component.onCompleted: { + setHitTestVisible(layout_row) + } FluToggleSwitch{ id:btn_dark Layout.alignment: Qt.AlignVCenter @@ -267,23 +281,8 @@ Rectangle{ onClicked: closeClickListener() } } - function stayTopButton(){ - return btn_stay_top - } - function minimizeButton(){ - return btn_minimize - } - function maximizeButton(){ - return btn_maximize - } - function closeButton(){ - return btn_close - } - function darkButton(){ - return btn_dark - } - function maximizeButtonHover(){ - var hover = false; + 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) @@ -295,4 +294,21 @@ Rectangle{ d.hoverMaxBtn = hover return hover; } + function _appBarHover(){ + for(var i =0 ;i< d.hitTestList.length; i++){ + var item = d.hitTestList[i] + var pos = item.mapToGlobal(0,0) + if(item.visible){ + var rect = Qt.rect(pos.x,pos.y,item.width,item.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)){ + return false + } + } + } + return true + } + function setHitTestVisible(id){ + d.hitTestList.push(id) + } } diff --git a/src/Qt6/imports/FluentUI/Controls/FluNavigationView.qml b/src/Qt6/imports/FluentUI/Controls/FluNavigationView.qml index fcdf77ad..a44027a7 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluNavigationView.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluNavigationView.qml @@ -22,6 +22,9 @@ Item { property int cellHeight: 38 property int cellWidth: 300 property bool hideNavAppBar: false + property alias buttonMenu: btn_menu + property alias buttonBack: btn_back + property alias imageLogo: image_logo signal logoClicked id:control Item{ @@ -756,7 +759,7 @@ Item { } } FluIconButton{ - id:btn_nav + id:btn_menu iconSource: FluentIcons.GlobalNavButton iconSize: 15 Layout.preferredWidth: d.isMinimal ? 30 : 0 @@ -788,7 +791,7 @@ Item { Layout.preferredWidth: 20 source: control.logo Layout.leftMargin: { - if(btn_nav.visible){ + if(btn_menu.visible){ return 12 } return 5 @@ -1338,13 +1341,4 @@ Item { } } } - function backButton(){ - return btn_back - } - function navButton(){ - return btn_nav - } - function logoButton(){ - return image_logo - } }