diff --git a/example/example.qrc b/example/example.qrc index a941b5bc..0dd5438b 100644 --- a/example/example.qrc +++ b/example/example.qrc @@ -126,7 +126,6 @@ qml/component/CustomWindow.qml qml/global/ItemsFooter.qml qml/global/ItemsOriginal.qml - qml/global/MainEvent.qml qml/global/qmldir qml/page/T_Acrylic.qml qml/page/T_Awesome.qml @@ -186,5 +185,6 @@ res/image/image_1.jpg qml/window/PageWindow.qml qml/page/T_StaggeredView.qml + qml/viewmodel/SettingsViewModel.qml diff --git a/example/qml-Qt6/global/MainEvent.qml b/example/qml-Qt6/global/MainEvent.qml deleted file mode 100644 index 7119ffc6..00000000 --- a/example/qml-Qt6/global/MainEvent.qml +++ /dev/null @@ -1,9 +0,0 @@ -pragma Singleton - -import QtQuick -import QtQuick.Controls -import FluentUI - -QtObject { - property int displayMode : FluNavigationViewType.Auto -} diff --git a/example/qml-Qt6/global/qmldir b/example/qml-Qt6/global/qmldir index a1122801..eb6bfeaf 100644 --- a/example/qml-Qt6/global/qmldir +++ b/example/qml-Qt6/global/qmldir @@ -1,3 +1,2 @@ singleton ItemsOriginal 1.0 ItemsOriginal.qml singleton ItemsFooter 1.0 ItemsFooter.qml -singleton MainEvent 1.0 MainEvent.qml diff --git a/example/qml-Qt6/page/T_Settings.qml b/example/qml-Qt6/page/T_Settings.qml index f170fad2..fb789cd9 100644 --- a/example/qml-Qt6/page/T_Settings.qml +++ b/example/qml-Qt6/page/T_Settings.qml @@ -5,11 +5,15 @@ import QtQuick.Controls import FluentUI import "qrc:///example/qml/global" import "qrc:///example/qml/component" +import "qrc:///example/qml/viewmodel" FluScrollablePage{ title:"Settings" + SettingsViewModel{ + id:viewmodel_settings + } FluEvent{ id:event_checkupdate_finish @@ -103,10 +107,10 @@ FluScrollablePage{ Repeater{ model: [{title:"Open",mode:FluNavigationViewType.Open},{title:"Compact",mode:FluNavigationViewType.Compact},{title:"Minimal",mode:FluNavigationViewType.Minimal},{title:"Auto",mode:FluNavigationViewType.Auto}] delegate: FluRadioButton{ - checked : MainEvent.displayMode===modelData.mode + checked : viewmodel_settings.displayMode===modelData.mode text:modelData.title clickListener:function(){ - MainEvent.displayMode = modelData.mode + viewmodel_settings.displayMode = modelData.mode } } } diff --git a/example/qml-Qt6/viewmodel/SettingsViewModel.qml b/example/qml-Qt6/viewmodel/SettingsViewModel.qml new file mode 100644 index 00000000..04524e54 --- /dev/null +++ b/example/qml-Qt6/viewmodel/SettingsViewModel.qml @@ -0,0 +1,14 @@ +import QtQuick +import FluentUI +import "qrc:///example/qml/component" + +FluViewModel{ + + objectName: "SettingsViewModel" + property int displayMode + + onInitData: { + displayMode = FluNavigationViewType.Auto + } + +} diff --git a/example/qml-Qt6/window/MainWindow.qml b/example/qml-Qt6/window/MainWindow.qml index ca6904e2..2159be8c 100644 --- a/example/qml-Qt6/window/MainWindow.qml +++ b/example/qml-Qt6/window/MainWindow.qml @@ -7,6 +7,7 @@ import FluentUI import example import "qrc:///example/qml/component" import "qrc:///example/qml/global" +import "qrc:///example/qml/viewmodel" CustomWindow { @@ -20,6 +21,10 @@ CustomWindow { appBarVisible: false launchMode: FluWindowType.SingleTask + SettingsViewModel{ + id:viewmodel_settings + } + closeFunc:function(event){ dialog_close.open() event.accepted = false @@ -183,7 +188,7 @@ CustomWindow { items: ItemsOriginal footerItems:ItemsFooter topPadding:FluTools.isMacos() ? 20 : 0 - displayMode:MainEvent.displayMode + displayMode:viewmodel_settings.displayMode logo: "qrc:/example/res/image/favicon.ico" title:"FluentUI" onLogoClicked:{ diff --git a/example/qml/global/MainEvent.qml b/example/qml/global/MainEvent.qml deleted file mode 100644 index c31e3a4a..00000000 --- a/example/qml/global/MainEvent.qml +++ /dev/null @@ -1,9 +0,0 @@ -pragma Singleton - -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import FluentUI 1.0 - -QtObject { - property int displayMode : FluNavigationViewType.Auto -} diff --git a/example/qml/global/qmldir b/example/qml/global/qmldir index a1122801..eb6bfeaf 100644 --- a/example/qml/global/qmldir +++ b/example/qml/global/qmldir @@ -1,3 +1,2 @@ singleton ItemsOriginal 1.0 ItemsOriginal.qml singleton ItemsFooter 1.0 ItemsFooter.qml -singleton MainEvent 1.0 MainEvent.qml diff --git a/example/qml/page/T_Pivot.qml b/example/qml/page/T_Pivot.qml index ef7ea1da..8c673067 100644 --- a/example/qml/page/T_Pivot.qml +++ b/example/qml/page/T_Pivot.qml @@ -16,9 +16,12 @@ FluScrollablePage{ height: 400 paddings: 10 + + FluPivot{ anchors.fill: parent currentIndex: 2 + FluPivotItem{ title:"All" contentItem:FluText{ diff --git a/example/qml/page/T_Settings.qml b/example/qml/page/T_Settings.qml index 5b9f789b..43057e48 100644 --- a/example/qml/page/T_Settings.qml +++ b/example/qml/page/T_Settings.qml @@ -5,12 +5,18 @@ import QtQuick.Controls 2.15 import FluentUI 1.0 import "qrc:///example/qml/global" import "qrc:///example/qml/component" +import "qrc:///example/qml/viewmodel" import "../component" +import "../viewmodel" +import "../global" FluScrollablePage{ title:"Settings" + SettingsViewModel{ + id:viewmodel_settings + } FluEvent{ id:event_checkupdate_finish @@ -104,10 +110,10 @@ FluScrollablePage{ Repeater{ model: [{title:"Open",mode:FluNavigationViewType.Open},{title:"Compact",mode:FluNavigationViewType.Compact},{title:"Minimal",mode:FluNavigationViewType.Minimal},{title:"Auto",mode:FluNavigationViewType.Auto}] delegate: FluRadioButton{ - checked : MainEvent.displayMode===modelData.mode + checked : viewmodel_settings.displayMode===modelData.mode text:modelData.title clickListener:function(){ - MainEvent.displayMode = modelData.mode + viewmodel_settings.displayMode = modelData.mode } } } @@ -150,4 +156,3 @@ FluScrollablePage{ } } - diff --git a/example/qml/viewmodel/SettingsViewModel.qml b/example/qml/viewmodel/SettingsViewModel.qml new file mode 100644 index 00000000..75fc5ae7 --- /dev/null +++ b/example/qml/viewmodel/SettingsViewModel.qml @@ -0,0 +1,14 @@ +import QtQuick 2.15 +import FluentUI 1.0 +import "qrc:///example/qml/component" + +FluViewModel{ + + objectName: "SettingsViewModel" + property int displayMode + + onInitData: { + displayMode = FluNavigationViewType.Auto + } + +} diff --git a/example/qml/window/MainWindow.qml b/example/qml/window/MainWindow.qml index 1b51f11c..e60dc23b 100644 --- a/example/qml/window/MainWindow.qml +++ b/example/qml/window/MainWindow.qml @@ -6,8 +6,11 @@ import Qt.labs.platform 1.1 import FluentUI 1.0 import example 1.0 import "qrc:///example/qml/component" -import "../component" import "qrc:///example/qml/global" +import "qrc:///example/qml/viewmodel" +import "../component" +import "../viewmodel" +import "../global" CustomWindow { @@ -21,6 +24,10 @@ CustomWindow { appBarVisible: false launchMode: FluWindowType.SingleTask + SettingsViewModel{ + id:viewmodel_settings + } + closeFunc:function(event){ dialog_close.open() event.accepted = false @@ -184,7 +191,7 @@ CustomWindow { items: ItemsOriginal footerItems:ItemsFooter topPadding:FluTools.isMacos() ? 20 : 0 - displayMode:MainEvent.displayMode + displayMode:viewmodel_settings.displayMode logo: "qrc:/example/res/image/favicon.ico" title:"FluentUI" onLogoClicked:{ diff --git a/src/Def.h b/src/Def.h index f11a3ea1..3784b679 100644 --- a/src/Def.h +++ b/src/Def.h @@ -4,6 +4,16 @@ #include #include +namespace FluViewModelType { +Q_NAMESPACE +enum Scope { + Window = 0x0000, + Application = 0x0001 +}; +Q_ENUM_NS(Scope) +QML_NAMED_ELEMENT(FluViewModelType) +} + namespace FluHttpType { Q_NAMESPACE enum CacheMode { diff --git a/src/FluViewModel.cpp b/src/FluViewModel.cpp new file mode 100644 index 00000000..9d57d172 --- /dev/null +++ b/src/FluViewModel.cpp @@ -0,0 +1,139 @@ +#include "FluViewModel.h" + +#include +#include "Def.h" + +ViewModelManager* ViewModelManager::m_instance = nullptr; + +ViewModelManager *ViewModelManager::getInstance() +{ + if(ViewModelManager::m_instance == nullptr){ + ViewModelManager::m_instance = new ViewModelManager; + } + return ViewModelManager::m_instance; +} + +Model::Model(QObject *parent) + : QObject{parent} +{ + +} + +Model::~Model() +{ + qDebug()<<"Model delete~"; +} + +ViewModelManager::ViewModelManager(QObject *parent) + : QObject{parent} +{ + +} + +void ViewModelManager::insertViewModel(FluViewModel* value){ + m_viewmodel.append(value); +} + +void ViewModelManager::deleteViewModel(FluViewModel* value){ + m_viewmodel.removeOne(value); +} + +QObject* ViewModelManager::getModel(const QString& key){ + return m_data.value(key); +} + +void ViewModelManager::insert(const QString& key,QObject* value){ + m_data.insert(key,value); +} + +bool ViewModelManager::exist(const QString& key){ + return m_data.contains(key); +} + +void ViewModelManager::refreshViewModel(FluViewModel* viewModel,QString key,QVariant value){ + foreach (auto item, m_viewmodel) { + if(item->getKey() == viewModel->getKey()){ + item->setProperty(key.toStdString().c_str(),value); + } + } +} + +PropertyObserver::PropertyObserver(QString name,QObject* model,QObject *parent) + : QObject{parent} +{ + _name = name; + _model = model; + _property = QQmlProperty(parent,_name); + _property.connectNotifySignal(this,SLOT(_propertyChange())); +} + +PropertyObserver::~PropertyObserver(){ + qDebug()<<"PropertyObserver delete~"; +} + +void PropertyObserver::_propertyChange(){ + auto value = _property.read(); + _model->setProperty(_name.toStdString().c_str(),value); + ViewModelManager::getInstance()->refreshViewModel((FluViewModel*)parent(),_name,value); +} + +FluViewModel::FluViewModel(QObject *parent) + : QObject{parent} +{ + ViewModelManager::getInstance()->insertViewModel(this); + scope(FluViewModelType::Scope::Window); +} + +FluViewModel::~FluViewModel(){ + ViewModelManager::getInstance()->deleteViewModel(this); +} + +void FluViewModel::classBegin() +{ +} + +void FluViewModel::componentComplete() +{ + auto o = parent(); + while (nullptr != o) { + _window = o; + o = o->parent(); + } + const QMetaObject* obj = metaObject(); + if(_scope == FluViewModelType::Scope::Window){ + _key = property("objectName_").toString()+QString::number(reinterpret_cast(_window), 16); + }else{ + _key = property("objectName").toString(); + } + QObject * model; + if(!ViewModelManager::getInstance()->exist(_key)){ + if(_scope == FluViewModelType::Scope::Window){ + model = new Model(_window); + }else{ + model = new Model(); + } + Q_EMIT initData(); + for (int i = 0; i < obj->propertyCount(); ++i) { + const QMetaProperty property = obj->property(i); + QString propertyName = property.name(); + auto value = property.read(this); + model->setProperty(propertyName.toStdString().c_str(),value); + new PropertyObserver(propertyName,model,this); + } + ViewModelManager::getInstance()->insert(_key,model); + }else{ + model = ViewModelManager::getInstance()->getModel(_key); + for (int i = 0; i < obj->propertyCount(); ++i) { + const QMetaProperty property = obj->property(i); + QString propertyName = property.name(); + new PropertyObserver(propertyName,model,this); + } + } + foreach (auto key, model->dynamicPropertyNames()) { + setProperty(key,model->property(key)); + } +} + +QString FluViewModel::getKey(){ + return _key; +} diff --git a/src/FluViewModel.h b/src/FluViewModel.h new file mode 100644 index 00000000..2e6b02ea --- /dev/null +++ b/src/FluViewModel.h @@ -0,0 +1,68 @@ +#ifndef FLUVIEWMODEL_H +#define FLUVIEWMODEL_H + +#include +#include +#include +#include +#include "stdafx.h" + +class Model : public QObject{ + Q_OBJECT +public: + explicit Model(QObject *parent = nullptr); + ~Model(); +}; + +class FluViewModel : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY_AUTO(int,scope); + Q_PROPERTY_AUTO(QObject*,target); + QML_NAMED_ELEMENT(FluViewModel) +public: + explicit FluViewModel(QObject *parent = nullptr); + ~FluViewModel(); + void classBegin() override; + void componentComplete() override; + Q_SIGNAL void initData(); + QString getKey(); +private: + QObject* _window = nullptr; + QString _key; +}; + +class PropertyObserver: public QObject{ + Q_OBJECT +public: + explicit PropertyObserver(QString name,QObject* model,QObject *parent = nullptr); + ~PropertyObserver(); +private: + Q_SLOT void _propertyChange(); +private: + QString _name; + QQmlProperty _property; + QObject* _model; +}; + + +class ViewModelManager:public QObject{ + Q_OBJECT +private: + explicit ViewModelManager(QObject *parent = nullptr); +public: + static ViewModelManager *getInstance(); + bool exist(const QString& key); + void insert(const QString& key,QObject* value); + QObject* getModel(const QString& key); + void insertViewModel(FluViewModel* value); + void deleteViewModel(FluViewModel* value); + void refreshViewModel(FluViewModel* viewModel,QString key,QVariant value); +private: + static ViewModelManager* m_instance; + QMap m_data; + QList m_viewmodel; +}; + +#endif // FLUVIEWMODEL_H diff --git a/src/FluentUI.cpp b/src/FluentUI.cpp index 99a6189d..fd5235df 100644 --- a/src/FluentUI.cpp +++ b/src/FluentUI.cpp @@ -13,6 +13,7 @@ #include "FluWatermark.h" #include "FluCaptcha.h" #include "FluEventBus.h" +#include "FluViewModel.h" #include "Screenshot.h" #include "QRCode.h" @@ -51,6 +52,7 @@ void FluentUI::registerTypes(const char *uri){ qmlRegisterType(uri,major,minor,"HttpCallable"); qmlRegisterType(uri,major,minor,"HttpRequest"); qmlRegisterType(uri,major,minor,"FluEvent"); + qmlRegisterType(uri,major,minor,"FluViewModel"); qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/ColorPicker/ColorPicker.qml"),uri,major,minor,"ColorPicker"); qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/ColorPicker/Content/Checkerboard.qml"),uri,major,minor,"Checkerboard"); diff --git a/src/Qt5/imports/FluentUI/Controls/FluNavigationView.qml b/src/Qt5/imports/FluentUI/Controls/FluNavigationView.qml index d7061bf4..0acaab6c 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluNavigationView.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluNavigationView.qml @@ -124,7 +124,6 @@ Item { Component{ id:com_panel_item_header Item{ - clip: true height: { if(model.parent){ return model.parent.isExpand ? 30 : 0 @@ -154,7 +153,6 @@ Item { Item{ height: 38 width: layout_list.width - clip: true FluControl{ id:item_control anchors{ @@ -378,7 +376,6 @@ Item { duration: 83 } } - clip: true height: { if(model.parent){ return model.parent.isExpand ? 38 : 0 @@ -409,7 +406,7 @@ Item { Drag.hotSpot.x: item_control.width / 2 Drag.hotSpot.y: item_control.height / 2 Drag.dragType: Drag.Automatic - onClicked: { + onClicked:{ if(type === 0){ if(model.onTapListener){ model.onTapListener() diff --git a/src/Qt5/imports/FluentUI/Controls/FluPivot.qml b/src/Qt5/imports/FluentUI/Controls/FluPivot.qml index 8ddbc7dc..52b87c57 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluPivot.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluPivot.qml @@ -2,30 +2,32 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import FluentUI 1.0 -Item { +Page { default property alias content: d.children property alias currentIndex: nav_list.currentIndex - property color normalColor: FluTheme.dark ? FluColors.Grey120 : FluColors.Grey120 - property color hoverColor: FluTheme.dark ? FluColors.Grey10 : FluColors.Black + property color textNormalColor: FluTheme.dark ? FluColors.Grey120 : FluColors.Grey120 + property color textHoverColor: FluTheme.dark ? FluColors.Grey10 : FluColors.Black + property int textSize: 28 + property bool textBold: true + property int textSpacing: 10 + property int headerSpacing: 20 + property int headerHeight: 40 id:control width: 400 height: 300 implicitHeight: height implicitWidth: width - MouseArea{ - anchors.fill: parent - preventStealing: true - } FluObject{ id:d + property int tabY: control.headerHeight/2+control.textSize/2 + 3 } - ListView{ + background:Item{} + header:ListView{ id:nav_list - height: 40 - width: control.width + implicitHeight: control.headerHeight + implicitWidth: control.width model:d.children - clip: true - spacing: 20 + spacing: control.headerSpacing interactive: false orientation: ListView.Horizontal highlightMoveDuration: FluTheme.enableAnimation ? 167 : 0 @@ -36,7 +38,7 @@ Item { radius: 1.5 color: FluTheme.primaryColor.dark width: nav_list.currentItem ? nav_list.currentItem.width : 0 - y:37 + y:d.tabY Behavior on width { enabled: FluTheme.enableAnimation NumberAnimation{ @@ -50,18 +52,25 @@ Item { id:item_button width: item_title.width height: nav_list.height + focusPolicy:Qt.TabFocus background:Item{ + FluFocusRectangle{ + anchors.margins: -4 + visible: item_button.activeFocus + radius:4 + } } contentItem: Item{ FluText { id:item_title - font: FluTextStyle.Title text: modelData.title anchors.centerIn: parent + font.pixelSize: control.textSize + font.bold: control.textBold color: { if(item_button.hovered) - return hoverColor - return normalColor + return textHoverColor + return textNormalColor } } } @@ -72,13 +81,7 @@ Item { } Item{ id:container - anchors{ - top: nav_list.bottom - topMargin: 10 - left: parent.left - right: parent.right - bottom: parent.bottom - } + anchors.fill: parent Repeater{ model:d.children Loader{ diff --git a/src/Qt6/imports/FluentUI/Controls/FluNavigationView.qml b/src/Qt6/imports/FluentUI/Controls/FluNavigationView.qml index a855d42c..9bf45b19 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluNavigationView.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluNavigationView.qml @@ -125,7 +125,6 @@ Item { Component{ id:com_panel_item_header Item{ - clip: true height: { if(model.parent){ return model.parent.isExpand ? 30 : 0 @@ -155,7 +154,6 @@ Item { Item{ height: 38 width: layout_list.width - clip: true FluControl{ id:item_control anchors{ @@ -379,7 +377,6 @@ Item { duration: 83 } } - clip: true height: { if(model.parent){ return model.parent.isExpand ? 38 : 0 diff --git a/src/Qt6/imports/FluentUI/Controls/FluPivot.qml b/src/Qt6/imports/FluentUI/Controls/FluPivot.qml index c5671aa7..cde8782f 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluPivot.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluPivot.qml @@ -2,30 +2,32 @@ import QtQuick import QtQuick.Controls import FluentUI -Item { +Page { default property alias content: d.children property alias currentIndex: nav_list.currentIndex - property color normalColor: FluTheme.dark ? FluColors.Grey120 : FluColors.Grey120 - property color hoverColor: FluTheme.dark ? FluColors.Grey10 : FluColors.Black + property color textNormalColor: FluTheme.dark ? FluColors.Grey120 : FluColors.Grey120 + property color textHoverColor: FluTheme.dark ? FluColors.Grey10 : FluColors.Black + property int textSize: 28 + property bool textBold: true + property int textSpacing: 10 + property int headerSpacing: 20 + property int headerHeight: 40 id:control width: 400 height: 300 implicitHeight: height implicitWidth: width - MouseArea{ - anchors.fill: parent - preventStealing: true - } FluObject{ id:d + property int tabY: control.headerHeight/2+control.textSize/2 + 3 } - ListView{ + background:Item{} + header:ListView{ id:nav_list - height: 40 - width: control.width + implicitHeight: control.headerHeight + implicitWidth: control.width model:d.children - clip: true - spacing: 20 + spacing: control.headerSpacing interactive: false orientation: ListView.Horizontal highlightMoveDuration: FluTheme.enableAnimation ? 167 : 0 @@ -36,7 +38,7 @@ Item { radius: 1.5 color: FluTheme.primaryColor.dark width: nav_list.currentItem ? nav_list.currentItem.width : 0 - y:37 + y:d.tabY Behavior on width { enabled: FluTheme.enableAnimation NumberAnimation{ @@ -50,18 +52,25 @@ Item { id:item_button width: item_title.width height: nav_list.height + focusPolicy:Qt.TabFocus background:Item{ + FluFocusRectangle{ + anchors.margins: -4 + visible: item_button.activeFocus + radius:4 + } } contentItem: Item{ FluText { id:item_title - font: FluTextStyle.Title text: modelData.title anchors.centerIn: parent + font.pixelSize: control.textSize + font.bold: control.textBold color: { if(item_button.hovered) - return hoverColor - return normalColor + return textHoverColor + return textNormalColor } } } @@ -72,13 +81,7 @@ Item { } Item{ id:container - anchors{ - top: nav_list.bottom - topMargin: 10 - left: parent.left - right: parent.right - bottom: parent.bottom - } + anchors.fill: parent Repeater{ model:d.children Loader{