From f88b330f8e047b971b5b0fca1a3f96c5f6426abe Mon Sep 17 00:00:00 2001 From: zhuzichu Date: Fri, 22 Sep 2023 17:35:02 +0800 Subject: [PATCH] update --- example/qml-Qt6/global/ItemsOriginal.qml | 2 +- example/qml-Qt6/page/T_TreeView.qml | 16 +++++-- example/qml/global/ItemsOriginal.qml | 2 +- example/qml/page/T_TreeView.qml | 16 +++++-- src/FluRectangle.cpp | 5 +-- src/FluTreeModel.cpp | 40 ++++++++++++++++- src/FluTreeModel.h | 16 +++++++ .../imports/FluentUI/Controls/FluCheckBox.qml | 10 +++-- .../imports/FluentUI/Controls/FluTreeView.qml | 44 +++++++++++++++++-- .../imports/FluentUI/Controls/FluCheckBox.qml | 10 +++-- .../imports/FluentUI/Controls/FluTreeView.qml | 44 +++++++++++++++++-- 11 files changed, 175 insertions(+), 30 deletions(-) diff --git a/example/qml-Qt6/global/ItemsOriginal.qml b/example/qml-Qt6/global/ItemsOriginal.qml index b2d9afa5..20c0b0e4 100644 --- a/example/qml-Qt6/global/ItemsOriginal.qml +++ b/example/qml-Qt6/global/ItemsOriginal.qml @@ -302,7 +302,7 @@ FluObject{ onTap:{ navigationView.push(url) } } FluPaneItem{ - title:"TreeView(Todo)" + title:"TreeView" url:"qrc:/example/qml/page/T_TreeView.qml" onDropped:{ FluApp.navigate("/pageWindow",{title:title,url:url}) } onTap:{ navigationView.push(url) } diff --git a/example/qml-Qt6/page/T_TreeView.qml b/example/qml-Qt6/page/T_TreeView.qml index d49f796b..cc1b1ab0 100644 --- a/example/qml-Qt6/page/T_TreeView.qml +++ b/example/qml-Qt6/page/T_TreeView.qml @@ -12,7 +12,7 @@ FluContentPage { function treeData(){ const dig = (path = '0', level = 4) => { const list = []; - for (let i = 0; i < 9; i += 1) { + for (let i = 0; i < 6; i += 1) { const key = `${path}-${i}`; const treeNode = { title: key, @@ -45,6 +45,10 @@ FluContentPage { text:"共计%1条数据,当前显示的%2条数据".arg(tree_view.count()).arg(tree_view.visibleCount()) } + FluText{ + text:"共计选中%1条数据".arg(tree_view.selectionModel().length) + } + RowLayout{ spacing: 10 FluText{ @@ -74,12 +78,17 @@ FluContentPage { FluToggleSwitch{ id:switch_showline text:"showLine" - checked: true + checked: false } FluToggleSwitch{ id:switch_draggable text:"draggable" - checked: true + checked: false + } + FluToggleSwitch{ + id:switch_checkable + text:"checkable" + checked: false } FluButton{ text:"all expand" @@ -111,6 +120,7 @@ FluContentPage { cellHeight: slider_cell_height.value draggable:switch_draggable.checked showLine: switch_showline.checked + checkable:switch_checkable.checked depthPadding: slider_depth_padding.value Component.onCompleted: { var data = treeData() diff --git a/example/qml/global/ItemsOriginal.qml b/example/qml/global/ItemsOriginal.qml index 884fb44a..b85cb875 100644 --- a/example/qml/global/ItemsOriginal.qml +++ b/example/qml/global/ItemsOriginal.qml @@ -302,7 +302,7 @@ FluObject{ onTap:{ navigationView.push(url) } } FluPaneItem{ - title:"TreeView(Todo)" + title:"TreeView" url:"qrc:/example/qml/page/T_TreeView.qml" onDropped:{ FluApp.navigate("/pageWindow",{title:title,url:url}) } onTap:{ navigationView.push(url) } diff --git a/example/qml/page/T_TreeView.qml b/example/qml/page/T_TreeView.qml index 2caa5f9f..863930d3 100644 --- a/example/qml/page/T_TreeView.qml +++ b/example/qml/page/T_TreeView.qml @@ -13,7 +13,7 @@ FluContentPage { function treeData(){ const dig = (path = '0', level = 4) => { const list = []; - for (let i = 0; i < 9; i += 1) { + for (let i = 0; i < 6; i += 1) { const key = `${path}-${i}`; const treeNode = { title: key, @@ -46,6 +46,10 @@ FluContentPage { text:"共计%1条数据,当前显示的%2条数据".arg(tree_view.count()).arg(tree_view.visibleCount()) } + FluText{ + text:"共计选中%1条数据".arg(tree_view.selectionModel().length) + } + RowLayout{ spacing: 10 FluText{ @@ -75,12 +79,17 @@ FluContentPage { FluToggleSwitch{ id:switch_showline text:"showLine" - checked: true + checked: false } FluToggleSwitch{ id:switch_draggable text:"draggable" - checked: true + checked: false + } + FluToggleSwitch{ + id:switch_checkable + text:"checkable" + checked: false } FluButton{ text:"all expand" @@ -112,6 +121,7 @@ FluContentPage { cellHeight: slider_cell_height.value draggable:switch_draggable.checked showLine: switch_showline.checked + checkable:switch_checkable.checked depthPadding: slider_depth_padding.value Component.onCompleted: { var data = treeData() diff --git a/src/FluRectangle.cpp b/src/FluRectangle.cpp index 9eb07684..d902da9e 100644 --- a/src/FluRectangle.cpp +++ b/src/FluRectangle.cpp @@ -1,8 +1,5 @@ #include "FluRectangle.h" - #include -#include -#include FluRectangle::FluRectangle(QQuickItem* parent) : QQuickPaintedItem(parent){ setFlag(ItemHasContents, true); @@ -24,7 +21,7 @@ void FluRectangle::paint(QPainter* painter){ path.arcTo(QRectF(QPointF(rect.topLeft()), QSize(_radius[0] * 2, _radius[0] * 2)), 90, 90); path.lineTo(rect.bottomLeft() - QPointF(0, _radius[3])); path.arcTo(QRectF(QPointF(rect.bottomLeft() - QPointF(0, _radius[3] * 2)), QSize(_radius[3] * 2, _radius[3] * 2)), 180, 90); - path.lineTo(rect.bottomLeft() + QPointF(_radius[2], 0)); + path.lineTo(rect.bottomRight() - QPointF(_radius[2], 0)); path.arcTo(QRectF(QPointF(rect.bottomRight() - QPointF(_radius[2] * 2, _radius[2] * 2)), QSize(_radius[2] * 2, _radius[2] * 2)), 270, 90); painter->fillPath(path,_color); painter->restore(); diff --git a/src/FluTreeModel.cpp b/src/FluTreeModel.cpp index 05fc9010..5dbbb7e3 100644 --- a/src/FluTreeModel.cpp +++ b/src/FluTreeModel.cpp @@ -76,6 +76,43 @@ QObject* FluTreeModel::getRow(int row){ return _rows.at(row); } +void FluTreeModel::checkRow(int row,bool chekced){ + auto itemData = _rows.at(row); + if(itemData->hasChildren()){ + QList stack = itemData->_children; + std::reverse(stack.begin(), stack.end()); + while (stack.count() > 0) { + auto item = stack.at(stack.count()-1); + stack.pop_back(); + if(!item->hasChildren()){ + item->_checked = chekced; + } + QList children = item->_children; + if(!children.isEmpty()){ + std::reverse(children.begin(), children.end()); + foreach (auto c, children) { + stack.append(c); + } + } + } + }else{ + if(itemData->_checked == chekced){ + return; + } + itemData->_checked = chekced; + } + Q_EMIT layoutChanged(QList(),QAbstractItemModel::VerticalSortHint); + QList data; + foreach (auto item, _dataSource) { + if(!item->hasChildren()){ + if(item->_checked){ + data.append(item); + } + } + } + selectionModel(data); +} + void FluTreeModel::setDataSource(QList> data){ _dataSource.clear(); if(_root){ @@ -84,7 +121,6 @@ void FluTreeModel::setDataSource(QList> data){ } _root = new Node(this); std::reverse(data.begin(), data.end()); - auto startTime = QDateTime::currentMSecsSinceEpoch(); while (data.count() > 0) { auto item = data.at(data.count()-1); data.pop_back(); @@ -114,7 +150,6 @@ void FluTreeModel::setDataSource(QList> data){ } } } - auto endTime = QDateTime::currentMSecsSinceEpoch(); beginResetModel(); _rows = _dataSource; endResetModel(); @@ -204,6 +239,7 @@ void FluTreeModel::dragAnddrop(int dragIndex,int dropIndex,bool isDropTopArea){ } _rows.move(dragIndex,targetIndex); endMoveRows(); + Q_EMIT layoutAboutToBeChanged(); if(dragItem->_parent == dropItem->_parent){ QList* children = &(dragItem->_parent->_children); diff --git a/src/FluTreeModel.h b/src/FluTreeModel.h index a2917d88..be10945f 100644 --- a/src/FluTreeModel.h +++ b/src/FluTreeModel.h @@ -13,6 +13,7 @@ class Node : public QObject{ Q_PROPERTY(QString title READ title CONSTANT) Q_PROPERTY(int depth READ depth CONSTANT) Q_PROPERTY(bool isExpanded READ isExpanded CONSTANT) + Q_PROPERTY(bool checked READ checked CONSTANT) public: explicit Node(QObject *parent = nullptr); Q_INVOKABLE QString key(){return _key;}; @@ -30,6 +31,18 @@ public: } return true; } + Q_INVOKABLE bool checked(){ + if(!hasChildren()){ + return _checked; + } + foreach (auto item, _children) { + if(!item->checked()){ + + return false; + } + } + return true; + }; Q_INVOKABLE bool hideLineFooter(){ if(_parent){ auto childIndex = _parent->_children.indexOf(this); @@ -57,6 +70,7 @@ public: QString _key=""; QString _title=""; int _depth=0; + bool _checked = false; bool _isExpanded=true; QList _children; Node* _parent = nullptr; @@ -66,6 +80,7 @@ class FluTreeModel : public QAbstractItemModel { Q_OBJECT Q_PROPERTY_AUTO(int,dataSourceSize) + Q_PROPERTY_AUTO(QList,selectionModel) QML_NAMED_ELEMENT(FluTreeModel) QML_ADDED_IN_MINOR_VERSION(1) public: @@ -87,6 +102,7 @@ public: Q_INVOKABLE void dragAnddrop(int dragIndex,int dropIndex,bool isDropTopArea); Q_INVOKABLE Node* getNode(int row); Q_INVOKABLE void refreshNode(int row); + Q_INVOKABLE void checkRow(int row,bool chekced); Q_INVOKABLE bool hitHasChildrenExpanded(int row); Q_INVOKABLE void allExpand(); Q_INVOKABLE void allCollapse(); diff --git a/src/Qt5/imports/FluentUI/Controls/FluCheckBox.qml b/src/Qt5/imports/FluentUI/Controls/FluCheckBox.qml index 6ced72a0..8050d0cc 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluCheckBox.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluCheckBox.qml @@ -22,6 +22,7 @@ Button { property alias textColor: btn_text.textColor property bool textRight: true property real textSpacing: 6 + property bool enableAnimation: FluTheme.enableAnimation property var clickListener : function(){ checked = !checked } @@ -39,8 +40,9 @@ Button { visible: control.activeFocus } } - horizontalPadding:2 - verticalPadding: 2 + horizontalPadding:0 + verticalPadding: 0 + padding: 0 Accessible.role: Accessible.Button Accessible.name: control.text Accessible.description: contentDescription @@ -91,7 +93,7 @@ Button { return normalColor } Behavior on color { - enabled: FluTheme.enableAnimation + enabled: control.enableAnimation ColorAnimation{ duration: 83 } @@ -103,7 +105,7 @@ Button { visible: checked iconColor: FluTheme.dark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1) Behavior on visible { - enabled: FluTheme.enableAnimation + enabled: control.enableAnimation NumberAnimation{ duration: 83 } diff --git a/src/Qt5/imports/FluentUI/Controls/FluTreeView.qml b/src/Qt5/imports/FluentUI/Controls/FluTreeView.qml index b5b04611..49ca0a58 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluTreeView.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluTreeView.qml @@ -12,6 +12,7 @@ Item { property bool draggable: false property int cellHeight: 30 property int depthPadding: 30 + property bool checkable: false property color lineColor: FluTheme.dark ? Qt.rgba(111/255,111/255,111/255,1) : Qt.rgba(217/255,217/255,217/255,1) id:control QtObject { @@ -50,7 +51,7 @@ Item { } NumberAnimation { properties: "opacity" - duration: 300 + duration: 88 from: 0 to: 1 } @@ -69,7 +70,7 @@ Item { } NumberAnimation { properties: "opacity" - duration: 300 + duration: 88 from: 0 to: 1 } @@ -197,6 +198,11 @@ Item { var viewPos = table_view.mapToGlobal(0,0) var y = table_view.contentY + pos.y-viewPos.y var index = Math.floor(y/control.cellHeight) + if(index<0 || index>table_view.count-1){ + d.dropIndex = -1 + return + } + console.debug(index) if(tree_model.hitHasChildrenExpanded(index) && y>index*control.cellHeight+control.cellHeight/2){ d.dropIndex = index + 1 d.isDropTopArea = true @@ -321,7 +327,10 @@ Item { if(isCurrent){ return Qt.rgba(1,1,1,0.06) } - if(item_mouse.containsMouse){ + if(item_mouse.containsMouse || item_check_box.hovered){ + return Qt.rgba(1,1,1,0.03) + } + if(item_loader_expand.item && item_loader_expand.item.hovered){ return Qt.rgba(1,1,1,0.03) } return Qt.rgba(0,0,0,0) @@ -329,7 +338,10 @@ Item { if(isCurrent){ return Qt.rgba(0,0,0,0.06) } - if(item_mouse.containsMouse){ + if(item_mouse.containsMouse || item_check_box.hovered){ + return Qt.rgba(0,0,0,0.03) + } + if(item_loader_expand.item && item_loader_expand.item.hovered){ return Qt.rgba(0,0,0,0.03) } return Qt.rgba(0,0,0,0) @@ -341,6 +353,7 @@ Item { height: parent.height anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left + spacing: 0 anchors.leftMargin: 14 + control.depthPadding*itemModel.depth Component{ id:com_icon_btn @@ -357,16 +370,36 @@ Item { } } } + Loader{ + id:item_loader_expand Layout.preferredWidth: 20 Layout.preferredHeight: 20 sourceComponent: itemModel.hasChildren() ? com_icon_btn : undefined Layout.alignment: Qt.AlignVCenter } + + FluCheckBox{ + id:item_check_box + Layout.preferredWidth: 18 + Layout.preferredHeight: 18 + Layout.leftMargin: 5 + horizontalPadding:0 + verticalPadding: 0 + checked: itemModel.checked + enableAnimation:false + visible: control.checkable + padding: 0 + clickListener: function(){ + tree_model.checkRow(rowIndex,!itemModel.checked) + } + Layout.alignment: Qt.AlignVCenter + } Loader{ property var modelData: itemModel property var itemMouse: item_mouse id:item_loader_cell + Layout.leftMargin: 10 Layout.preferredWidth: { if(item){ return item.width @@ -397,6 +430,9 @@ Item { } } } + function selectionModel(){ + return tree_model.selectionModel + } function count(){ return tree_model.dataSourceSize } diff --git a/src/Qt6/imports/FluentUI/Controls/FluCheckBox.qml b/src/Qt6/imports/FluentUI/Controls/FluCheckBox.qml index 13513eba..dcf490ed 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluCheckBox.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluCheckBox.qml @@ -23,6 +23,7 @@ Button { property alias textColor: btn_text.textColor property bool textRight: true property real textSpacing: 6 + property bool enableAnimation: FluTheme.enableAnimation property var clickListener : function(){ checked = !checked } @@ -35,8 +36,9 @@ Button { visible: control.activeFocus } } - horizontalPadding:2 - verticalPadding: 2 + horizontalPadding:0 + verticalPadding: 0 + padding: 0 Accessible.role: Accessible.Button Accessible.name: control.text Accessible.description: contentDescription @@ -87,7 +89,7 @@ Button { return normalColor } Behavior on color { - enabled: FluTheme.enableAnimation + enabled: control.enableAnimation ColorAnimation{ duration: 83 } @@ -99,7 +101,7 @@ Button { visible: checked iconColor: FluTheme.dark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1) Behavior on visible { - enabled: FluTheme.enableAnimation + enabled: control.enableAnimation NumberAnimation{ duration: 83 } diff --git a/src/Qt6/imports/FluentUI/Controls/FluTreeView.qml b/src/Qt6/imports/FluentUI/Controls/FluTreeView.qml index f783c5d1..4f2160a0 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluTreeView.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluTreeView.qml @@ -12,6 +12,7 @@ Item { property bool draggable: false property int cellHeight: 30 property int depthPadding: 30 + property bool checkable: false property color lineColor: FluTheme.dark ? Qt.rgba(111/255,111/255,111/255,1) : Qt.rgba(217/255,217/255,217/255,1) id:control QtObject { @@ -50,7 +51,7 @@ Item { } NumberAnimation { properties: "opacity" - duration: 300 + duration: 88 from: 0 to: 1 } @@ -69,7 +70,7 @@ Item { } NumberAnimation { properties: "opacity" - duration: 300 + duration: 88 from: 0 to: 1 } @@ -197,6 +198,11 @@ Item { var viewPos = table_view.mapToGlobal(0,0) var y = table_view.contentY + pos.y-viewPos.y var index = Math.floor(y/control.cellHeight) + if(index<0 || index>table_view.count-1){ + d.dropIndex = -1 + return + } + console.debug(index) if(tree_model.hitHasChildrenExpanded(index) && y>index*control.cellHeight+control.cellHeight/2){ d.dropIndex = index + 1 d.isDropTopArea = true @@ -321,7 +327,10 @@ Item { if(isCurrent){ return Qt.rgba(1,1,1,0.06) } - if(item_mouse.containsMouse){ + if(item_mouse.containsMouse || item_check_box.hovered){ + return Qt.rgba(1,1,1,0.03) + } + if(item_loader_expand.item && item_loader_expand.item.hovered){ return Qt.rgba(1,1,1,0.03) } return Qt.rgba(0,0,0,0) @@ -329,7 +338,10 @@ Item { if(isCurrent){ return Qt.rgba(0,0,0,0.06) } - if(item_mouse.containsMouse){ + if(item_mouse.containsMouse || item_check_box.hovered){ + return Qt.rgba(0,0,0,0.03) + } + if(item_loader_expand.item && item_loader_expand.item.hovered){ return Qt.rgba(0,0,0,0.03) } return Qt.rgba(0,0,0,0) @@ -341,6 +353,7 @@ Item { height: parent.height anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left + spacing: 0 anchors.leftMargin: 14 + control.depthPadding*itemModel.depth Component{ id:com_icon_btn @@ -357,16 +370,36 @@ Item { } } } + Loader{ + id:item_loader_expand Layout.preferredWidth: 20 Layout.preferredHeight: 20 sourceComponent: itemModel.hasChildren() ? com_icon_btn : undefined Layout.alignment: Qt.AlignVCenter } + + FluCheckBox{ + id:item_check_box + Layout.preferredWidth: 18 + Layout.preferredHeight: 18 + Layout.leftMargin: 5 + horizontalPadding:0 + verticalPadding: 0 + checked: itemModel.checked + enableAnimation:false + visible: control.checkable + padding: 0 + clickListener: function(){ + tree_model.checkRow(rowIndex,!itemModel.checked) + } + Layout.alignment: Qt.AlignVCenter + } Loader{ property var modelData: itemModel property var itemMouse: item_mouse id:item_loader_cell + Layout.leftMargin: 10 Layout.preferredWidth: { if(item){ return item.width @@ -397,6 +430,9 @@ Item { } } } + function selectionModel(){ + return tree_model.selectionModel + } function count(){ return tree_model.dataSourceSize }