diff --git a/example/qml-Qt6/page/T_TreeView.qml b/example/qml-Qt6/page/T_TreeView.qml index 414f3c39..2432e528 100644 --- a/example/qml-Qt6/page/T_TreeView.qml +++ b/example/qml-Qt6/page/T_TreeView.qml @@ -86,22 +86,32 @@ FluScrollablePage { } FluSlider{ id:slider_width - value: 200 + value: 280 from: 160 to:400 } } - FluToggleSwitch{ id:switch_showline text:"showLine" checked: true } - FluToggleSwitch{ id:switch_draggable text:"draggable" - checked: false + checked: true + } + FluButton{ + text:"all expand" + onClicked: { + tree_view.allExpand() + } + } + FluButton{ + text:"all collapse" + onClicked: { + tree_view.allCollapse() + } } } } @@ -120,3 +130,4 @@ FluScrollablePage { ' } } + diff --git a/src/FluTools.cpp b/src/FluTools.cpp index 094f72d9..fee8e81d 100644 --- a/src/FluTools.cpp +++ b/src/FluTools.cpp @@ -166,3 +166,7 @@ void FluTools::showFileInFolder(QString path){ bool FluTools::isSoftware(){ return QQuickWindow::sceneGraphBackend() == "software"; } + +QPoint FluTools::cursorPos(){ + return QCursor::pos(); +} diff --git a/src/FluTools.h b/src/FluTools.h index a1101feb..25fe0ff4 100644 --- a/src/FluTools.h +++ b/src/FluTools.h @@ -47,6 +47,7 @@ public: Q_INVOKABLE bool removeFile(QString filePath); Q_INVOKABLE void showFileInFolder(QString path); Q_INVOKABLE bool isSoftware(); + Q_INVOKABLE QPoint cursorPos(); }; #endif // FLUTOOLS_H diff --git a/src/FluTreeModel.cpp b/src/FluTreeModel.cpp index c56636fd..c94330e4 100644 --- a/src/FluTreeModel.cpp +++ b/src/FluTreeModel.cpp @@ -154,8 +154,8 @@ void FluTreeModel::expand(int row){ insertRows(row+1,insertData); } -void FluTreeModel::dragAnddrop(int dragIndex,int dropIndex){ - if(dragIndex == dropIndex+1 || dropIndex>_rows.count() || dropIndex<0){ +void FluTreeModel::dragAnddrop(int dragIndex,int dropIndex,bool isDropTopArea){ + if(dropIndex>_rows.count() || dropIndex<0){ return; } auto dragItem = _rows[dragIndex]; @@ -167,8 +167,12 @@ void FluTreeModel::dragAnddrop(int dragIndex,int dropIndex){ QList* children = &(dragItem->_parent->_children); int srcIndex = children->indexOf(dragItem); int destIndex = children->indexOf(dropItem); - children->move(srcIndex,destIndex>srcIndex? destIndex : destIndex +1); - _rows.move(dragIndex,dropIndex>dragIndex? dropIndex : dropIndex+1); + int offset = 1; + if(isDropTopArea){ + offset = offset - 1; + } + children->move(srcIndex,destIndex>srcIndex? destIndex-1 + offset : destIndex + offset); + _rows.move(dragIndex,dropIndex>dragIndex? dropIndex-1 + offset : dropIndex + offset); }else{ QList* srcChildren = &(dragItem->_parent->_children); QList* destChildren = &(dropItem->_parent->_children); @@ -197,11 +201,23 @@ void FluTreeModel::dragAnddrop(int dragIndex,int dropIndex){ } srcChildren->removeAt(srcIndex); destChildren->insert(destIndex+1,dragItem); - _rows.move(dragIndex,dropIndex>dragIndex? dropIndex : dropIndex+1); + int offset = 1; + if(isDropTopArea){ + offset = offset - 1; + } + _rows.move(dragIndex,dropIndex>dragIndex? dropIndex-1+offset : dropIndex+offset); } endMoveRows(); } +bool FluTreeModel::hitHasChildrenExpanded(int row){ + auto itemData = _rows.at(row); + if(itemData->hasChildren() && itemData->_isExpanded){ + return true; + } + return false; +} + void FluTreeModel::refreshNode(int row){ Q_EMIT dataChanged(index(row,0),index(row,0)); }; @@ -209,3 +225,48 @@ void FluTreeModel::refreshNode(int row){ Node* FluTreeModel::getNode(int row){ return _rows.at(row); } + +void FluTreeModel::allExpand(){ + beginResetModel(); + QList data; + QList stack = _root->_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->_isExpanded = true; + } + data.append(item); + QList children = item->_children; + if(!children.isEmpty()){ + std::reverse(children.begin(), children.end()); + foreach (auto c, children) { + stack.append(c); + } + } + } + _rows = data; + endResetModel(); +} +void FluTreeModel::allCollapse(){ + beginResetModel(); + QList stack = _root->_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->_isExpanded = false; + } + QList children = item->_children; + if(!children.isEmpty()){ + std::reverse(children.begin(), children.end()); + foreach (auto c, children) { + stack.append(c); + } + } + } + _rows = _root->_children; + endResetModel(); +} diff --git a/src/FluTreeModel.h b/src/FluTreeModel.h index 288d9222..6d3140d6 100644 --- a/src/FluTreeModel.h +++ b/src/FluTreeModel.h @@ -20,16 +20,6 @@ public: Q_INVOKABLE int depth(){return _depth;}; Q_INVOKABLE bool isExpanded(){return _isExpanded;}; Q_INVOKABLE bool hasChildren(){ return !_children.isEmpty();}; - Q_INVOKABLE bool isHeaderNode(){ - if(hasChildren() && _isExpanded){ - return true; - } - return false; - } - Q_INVOKABLE bool isFooterNode(){ - return this->_parent->_children.indexOf(this) == this->_parent->_children.count()-1; - } - Q_INVOKABLE bool hasNextNodeByIndex(int index){ Node* p = this; for(int i=0;i<(_depth - index -1);i++){ @@ -91,9 +81,12 @@ public: Q_INVOKABLE void setDataSource(QList> data); Q_INVOKABLE void collapse(int row); Q_INVOKABLE void expand(int row); - Q_INVOKABLE void dragAnddrop(int dragIndex,int dropIndex); + Q_INVOKABLE void dragAnddrop(int dragIndex,int dropIndex,bool isDropTopArea); Q_INVOKABLE Node* getNode(int row); Q_INVOKABLE void refreshNode(int row); + Q_INVOKABLE bool hitHasChildrenExpanded(int row); + Q_INVOKABLE void allExpand(); + Q_INVOKABLE void allCollapse(); private: QList _rows; QList _dataSource; diff --git a/src/Qt5/imports/FluentUI/Controls/FluTreeView.qml b/src/Qt5/imports/FluentUI/Controls/FluTreeView.qml index d380b4e6..c91f16a4 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluTreeView.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluTreeView.qml @@ -16,6 +16,7 @@ Item { id:d property var current property int dropIndex: -1 + property bool isDropTopArea: false property int dragIndex: -1 property color hitColor: FluTheme.dark ? FluTheme.primaryColor.lighter : FluTheme.primaryColor.dark @@ -40,10 +41,8 @@ Item { signal pooled onReused: { - console.debug("----->onReused") } onPooled: { - console.debug("----->onPooled") } property bool isCurrent: d.current === itemModel @@ -92,18 +91,18 @@ Item { loader_container.sourceComponent = com_item_container } } - onPressed: (mouse)=>{ - clickPos = Qt.point(mouse.x,mouse.y) - console.debug(clickPos) - loader_container.itemControl = itemControl - loader_container.itemModel = itemModel - var cellPosition = item_container.mapToItem(table_view, 0, 0) - loader_container.width = item_container.width - loader_container.height = item_container.height - loader_container.x = table_view.contentX + cellPosition.x - loader_container.y = table_view.contentY + cellPosition.y - - } + onPressed: + (mouse)=>{ + clickPos = Qt.point(mouse.x,mouse.y) + console.debug(clickPos) + loader_container.itemControl = itemControl + loader_container.itemModel = itemModel + var cellPosition = item_container.mapToItem(table_view, 0, 0) + loader_container.width = item_container.width + loader_container.height = item_container.height + loader_container.x = table_view.contentX + cellPosition.x + loader_container.y = table_view.contentY + cellPosition.y + } onClicked: { d.current = itemModel } @@ -126,13 +125,20 @@ Item { d.dropIndex = -1 return } - var y = loader_container.y - var index = Math.round(y/30) - if(index !== d.dragIndex){ - d.dropIndex = index - tree_model.refreshNode(rowIndex) + var pos = FluTools.cursorPos() + var viewPos = table_view.mapToGlobal(0,0) + var y = table_view.contentY + pos.y-viewPos.y + var index = Math.floor(y/30) + if(tree_model.hitHasChildrenExpanded(index) && y>index*30+15){ + d.dropIndex = index + 1 + d.isDropTopArea = true }else{ - d.dropIndex = -1 + d.dropIndex = index + if(y>index*30+15){ + d.isDropTopArea = false + }else{ + d.isDropTopArea = true + } } } onCanceled: { @@ -143,7 +149,7 @@ Item { onReleased: { loader_container.sourceComponent = undefined if(d.dropIndex !== -1){ - tree_model.dragAnddrop(d.dragIndex,d.dropIndex) + tree_model.dragAnddrop(d.dragIndex,d.dropIndex,d.isDropTopArea) } d.dropIndex = -1 d.dragIndex = -1 @@ -151,6 +157,7 @@ Item { } Drag.active: item_mouse.drag.active Rectangle{ + id:item_line_drop_tip anchors{ left: parent.left leftMargin: { @@ -163,7 +170,23 @@ Item { right: parent.right rightMargin: 10 bottom: parent.bottom + bottomMargin: -1.5 + top: undefined } + states: [ + State { + when:d.isDropTopArea + AnchorChanges { + target: item_line_drop_tip + anchors.top: item_container.top + anchors.bottom: undefined + } + PropertyChanges { + target: item_line_drop_tip + anchors.topMargin: -1.5 + } + } + ] height: 3 radius: 1.5 color: d.hitColor @@ -324,7 +347,6 @@ Item { } } } - Loader{ id:loader_container property var itemControl diff --git a/src/Qt6/imports/FluentUI/Controls/FluTreeView.qml b/src/Qt6/imports/FluentUI/Controls/FluTreeView.qml index d7f250d8..02f7c345 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluTreeView.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluTreeView.qml @@ -16,6 +16,7 @@ Item { id:d property var current property int dropIndex: -1 + property bool isDropTopArea: false property int dragIndex: -1 property color hitColor: FluTheme.dark ? FluTheme.primaryColor.lighter : FluTheme.primaryColor.dark @@ -40,10 +41,8 @@ Item { signal pooled onReused: { - console.debug("----->onReused") } onPooled: { - console.debug("----->onPooled") } property bool isCurrent: d.current === itemModel @@ -65,6 +64,185 @@ Item { tree_model.expand(rowIndex) } } + Rectangle{ + width: 3 + height: 18 + radius: 1.5 + color: FluTheme.primaryColor.dark + visible: isCurrent + anchors{ + left: parent.left + leftMargin: 6 + verticalCenter: parent.verticalCenter + } + } + MouseArea{ + id:item_mouse + property point clickPos: Qt.point(0,0) + anchors.fill: parent + drag.target:control.draggable ? loader_container : undefined + hoverEnabled: true + drag.onActiveChanged: { + if(drag.active){ + if(itemModel.isExpanded && itemModel.hasChildren()){ + tree_model.collapse(rowIndex) + } + d.dragIndex = rowIndex + loader_container.sourceComponent = com_item_container + } + } + onPressed: + (mouse)=>{ + clickPos = Qt.point(mouse.x,mouse.y) + console.debug(clickPos) + loader_container.itemControl = itemControl + loader_container.itemModel = itemModel + var cellPosition = item_container.mapToItem(table_view, 0, 0) + loader_container.width = item_container.width + loader_container.height = item_container.height + loader_container.x = table_view.contentX + cellPosition.x + loader_container.y = table_view.contentY + cellPosition.y + } + onClicked: { + d.current = itemModel + } + onDoubleClicked: { + if(itemModel.hasChildren()){ + item_container.toggle() + } + } + onPositionChanged: + (mouse)=> { + if(!drag.active){ + return + } + var cellPosition = item_container.mapToItem(table_view, 0, 0) + if(mouse.y+cellPosition.y<0 || mouse.y+cellPosition.y>table_view.height){ + d.dropIndex = -1 + return + } + if((mouse.x-table_view.contentX)>table_view.width || (mouse.x-table_view.contentX)<0){ + d.dropIndex = -1 + return + } + var pos = FluTools.cursorPos() + var viewPos = table_view.mapToGlobal(0,0) + var y = table_view.contentY + pos.y-viewPos.y + var index = Math.floor(y/30) + if(tree_model.hitHasChildrenExpanded(index) && y>index*30+15){ + d.dropIndex = index + 1 + d.isDropTopArea = true + }else{ + d.dropIndex = index + if(y>index*30+15){ + d.isDropTopArea = false + }else{ + d.isDropTopArea = true + } + } + } + onCanceled: { + loader_container.sourceComponent = undefined + d.dropIndex = -1 + d.dragIndex = -1 + } + onReleased: { + loader_container.sourceComponent = undefined + if(d.dropIndex !== -1){ + tree_model.dragAnddrop(d.dragIndex,d.dropIndex,d.isDropTopArea) + } + d.dropIndex = -1 + d.dragIndex = -1 + } + } + Drag.active: item_mouse.drag.active + Rectangle{ + id:item_line_drop_tip + anchors{ + left: parent.left + leftMargin: { + var count = itemModel.depth+1 + if(itemModel.hasChildren()){ + return 30*count - 8 + } + return 30*count + 18 + } + right: parent.right + rightMargin: 10 + bottom: parent.bottom + bottomMargin: -1.5 + top: undefined + } + states: [ + State { + when:d.isDropTopArea + AnchorChanges { + target: item_line_drop_tip + anchors.top: item_container.top + anchors.bottom: undefined + } + PropertyChanges { + target: item_line_drop_tip + anchors.topMargin: -1.5 + } + } + ] + height: 3 + radius: 1.5 + color: d.hitColor + visible: d.dropIndex === rowIndex + Rectangle{ + width: 10 + height: 10 + radius: 5 + border.width: 3 + border.color: d.hitColor + color: FluTheme.dark ? FluColors.Black : FluColors.White + anchors{ + top: parent.top + left: parent.left + topMargin: -3 + leftMargin: -5 + } + } + } + FluRectangle{ + width: 1 + color: control.lineColor + visible: control.showLine && isItemLoader && itemModel.depth !== 0 && !itemModel.hasChildren() + height: itemModel.hideLineFooter() ? parent.height/2 : parent.height + anchors{ + top: parent.top + right: layout_row.left + rightMargin: -9 + } + } + FluRectangle{ + height: 1 + color: control.lineColor + visible: control.showLine && isItemLoader && itemModel.depth !== 0 && !itemModel.hasChildren() + width: 18 + anchors{ + right: layout_row.left + rightMargin: -27 + verticalCenter: parent.verticalCenter + } + } + Repeater{ + model: Math.max(itemModel.depth-1,0) + delegate: FluRectangle{ + required property int index + width: 1 + color: control.lineColor + visible: control.showLine && isItemLoader && itemModel.depth !== 0 && itemModel.hasNextNodeByIndex(index) + anchors{ + top:parent.top + bottom: parent.bottom + left: parent.left + leftMargin: 30*(index+2) - 8 + } + } + } Rectangle{ anchors.fill: parent radius: 4 @@ -92,156 +270,6 @@ Item { } } } - Rectangle{ - width: 3 - height: 18 - radius: 1.5 - color: FluTheme.primaryColor.dark - visible: isCurrent - anchors{ - left: parent.left - leftMargin: 6 - verticalCenter: parent.verticalCenter - } - } - MouseArea{ - id:item_mouse - anchors.fill: parent - drag.target:control.draggable ? loader_container : undefined - hoverEnabled: true - drag.onActiveChanged: { - if(drag.active){ - if(itemModel.isExpanded && itemModel.hasChildren()){ - tree_model.collapse(rowIndex) - } - d.dragIndex = rowIndex - loader_container.sourceComponent = com_item_container - } - } - onPressed: { - loader_container.itemControl = itemControl - loader_container.itemModel = itemModel - var cellPosition = item_container.mapToItem(table_view, 0, 0) - loader_container.width = item_container.width - loader_container.height = item_container.height - loader_container.x = table_view.contentX + cellPosition.x - loader_container.y = table_view.contentY + cellPosition.y - - } - onClicked: { - d.current = itemModel - } - onDoubleClicked: { - if(itemModel.hasChildren()){ - item_container.toggle() - } - } - onPositionChanged: - (mouse)=> { - if(!drag.active){ - return - } - var cellPosition = item_container.mapToItem(table_view, 0, 0) - if(mouse.y+cellPosition.y<0 || mouse.y+cellPosition.y>table_view.height){ - d.dropIndex = -1 - return - } - if((mouse.x-table_view.contentX)>table_view.width || (mouse.x-table_view.contentX)<0){ - d.dropIndex = -1 - return - } - var y = loader_container.y - var index = Math.round(y/30) - if(index !== d.dragIndex){ - d.dropIndex = index - }else{ - d.dropIndex = -1 - } - } - onCanceled: { - loader_container.sourceComponent = undefined - d.dropIndex = -1 - d.dragIndex = -1 - } - onReleased: { - loader_container.sourceComponent = undefined - if(d.dropIndex !== -1){ - tree_model.dragAnddrop(d.dragIndex,d.dropIndex) - } - d.dropIndex = -1 - d.dragIndex = -1 - } - } - Drag.active: item_mouse.drag.active - Rectangle{ - anchors{ - left: parent.left - leftMargin: { - if(itemModel.hasChildren()){ - return 30*(itemModel.depth+1) - 8 - } - return 30*(itemModel.depth+1) + 18 - } - right: parent.right - rightMargin: 10 - bottom: parent.bottom - } - height: 3 - radius: 1.5 - color: d.hitColor - visible: d.dropIndex === rowIndex - Rectangle{ - width: 10 - height: 10 - radius: 5 - border.width: 3 - border.color: d.hitColor - color: FluTheme.dark ? FluColors.Black : FluColors.White - anchors{ - top: parent.top - left: parent.left - topMargin: -3 - leftMargin: -5 - } - } - } - FluRectangle{ - width: 1 - color: control.lineColor - visible: itemModel.depth !== 0 && control.showLine && !itemModel.hasChildren() - height: itemModel.hideLineFooter() ? parent.height/2 : parent.height - anchors{ - top: parent.top - right: layout_row.left - rightMargin: -9 - } - } - FluRectangle{ - height: 1 - color: control.lineColor - visible: itemModel.depth !== 0 && control.showLine && !itemModel.hasChildren() - width: 18 - anchors{ - right: layout_row.left - rightMargin: -27 - verticalCenter: parent.verticalCenter - } - } - Repeater{ - model: Math.max(itemModel.depth-1,0) - delegate: FluRectangle{ - required property int index - width: 1 - color: control.lineColor - visible: itemModel.depth !== 0 && control.showLine - anchors{ - top:parent.top - bottom: parent.bottom - left: parent.left - leftMargin: 30*(index+2) - 8 - } - } - } RowLayout{ id:layout_row anchors.verticalCenter: parent.verticalCenter @@ -313,16 +341,17 @@ Item { property var itemControl: item_control property var itemModel: modelData property int rowIndex: row + property bool isItemLoader: true id:item_loader_container sourceComponent: com_item_container } } } - Loader{ id:loader_container property var itemControl property var itemModel + property bool isItemLoader: false } } function count(){ @@ -331,4 +360,17 @@ Item { function visibleCount(){ return table_view.rows } + function collapse(rowIndex){ + tree_model.collapse(rowIndex) + } + function expand(rowIndex){ + tree_model.expand(rowIndex) + } + function allExpand(){ + tree_model.allExpand() + } + function allCollapse(){ + tree_model.allCollapse() + } + }