2023-03-07 00:05:27 +08:00
|
|
|
|
import QtQuick 2.15
|
|
|
|
|
import QtQuick.Window 2.15
|
|
|
|
|
import QtQuick.Layouts 1.15
|
2023-03-07 18:43:03 +08:00
|
|
|
|
import QtQuick.Controls 2.15
|
2023-03-07 00:05:27 +08:00
|
|
|
|
import FluentUI 1.0
|
|
|
|
|
import QtGraphicalEffects 1.15
|
|
|
|
|
|
2023-03-10 18:08:32 +08:00
|
|
|
|
Item {
|
2023-03-07 00:05:27 +08:00
|
|
|
|
id:root
|
|
|
|
|
|
2023-03-07 23:27:32 +08:00
|
|
|
|
enum TreeViewSelectionMode {
|
|
|
|
|
None,
|
|
|
|
|
Single,
|
|
|
|
|
Multiple
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
property int selectionMode: FluTreeView.None
|
|
|
|
|
|
|
|
|
|
property var currentElement
|
|
|
|
|
property var currentParentElement
|
|
|
|
|
|
|
|
|
|
property var rootModel: tree_model.get(0).items
|
|
|
|
|
|
|
|
|
|
signal itemClicked(var item)
|
|
|
|
|
|
|
|
|
|
ListModel{
|
|
|
|
|
id:tree_model
|
|
|
|
|
ListElement{
|
|
|
|
|
text: "根节点"
|
2023-03-07 18:43:03 +08:00
|
|
|
|
expanded:true
|
2023-03-08 13:41:43 +08:00
|
|
|
|
items:[]
|
2023-03-08 11:46:40 +08:00
|
|
|
|
key:"123456"
|
|
|
|
|
multipSelected:false
|
2023-03-08 13:41:43 +08:00
|
|
|
|
multipIndex:0
|
|
|
|
|
multipParentKey:""
|
2023-03-07 18:43:03 +08:00
|
|
|
|
}
|
2023-03-07 00:05:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-03-07 23:27:32 +08:00
|
|
|
|
Component{
|
|
|
|
|
id: delegate_root
|
|
|
|
|
Column{
|
|
|
|
|
width: calculateWidth()
|
|
|
|
|
property var itemModel: model
|
|
|
|
|
Repeater{
|
|
|
|
|
id: repeater_first_level
|
|
|
|
|
model: items
|
|
|
|
|
delegate: delegate_items
|
|
|
|
|
}
|
|
|
|
|
function calculateWidth(){
|
|
|
|
|
var w = 0;
|
|
|
|
|
for(var i = 0; i < repeater_first_level.count; i++) {
|
|
|
|
|
var child = repeater_first_level.itemAt(i)
|
|
|
|
|
if(w < child.width_hint){
|
|
|
|
|
w = child.width_hint;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return w;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-07 18:43:03 +08:00
|
|
|
|
|
|
|
|
|
Component{
|
2023-03-07 23:27:32 +08:00
|
|
|
|
id:delegate_items
|
|
|
|
|
|
|
|
|
|
Column{
|
|
|
|
|
id:item_layout
|
2023-03-07 18:43:03 +08:00
|
|
|
|
|
2023-03-07 23:27:32 +08:00
|
|
|
|
property real level: (mapToItem(list_root,0,0).x+list_root.contentX)/0.001
|
|
|
|
|
property var text: model.text??"Item"
|
2023-03-08 11:46:40 +08:00
|
|
|
|
property bool hasChild : (model.items !== undefined) && (model.items.count !== 0)
|
2023-03-07 23:27:32 +08:00
|
|
|
|
property var items: model.items??[]
|
|
|
|
|
property var expanded: model.expanded??true
|
|
|
|
|
property int width_hint: calculateWidth()
|
|
|
|
|
property bool singleSelected: currentElement === model
|
2023-03-07 18:43:03 +08:00
|
|
|
|
property var itemModel: model
|
2023-03-07 23:27:32 +08:00
|
|
|
|
|
|
|
|
|
function calculateWidth(){
|
|
|
|
|
var w = Math.max(list_root.width, item_layout_row.implicitWidth + 10);
|
|
|
|
|
if(expanded){
|
|
|
|
|
for(var i = 0; i < repeater_items.count; i++) {
|
|
|
|
|
var child = repeater_items.itemAt(i)
|
|
|
|
|
if(w < child.width_hint){
|
|
|
|
|
w = child.width_hint;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return w;
|
|
|
|
|
}
|
2023-03-07 18:43:03 +08:00
|
|
|
|
Item{
|
2023-03-07 23:27:32 +08:00
|
|
|
|
id:item_layout_rect
|
|
|
|
|
width: list_root.contentWidth
|
|
|
|
|
height: item_layout_row.implicitHeight
|
|
|
|
|
|
2023-03-07 00:05:27 +08:00
|
|
|
|
|
2023-03-07 18:43:03 +08:00
|
|
|
|
Rectangle{
|
2023-03-07 23:27:32 +08:00
|
|
|
|
anchors.fill: parent
|
2023-03-07 18:43:03 +08:00
|
|
|
|
anchors.margins: 2
|
|
|
|
|
color:{
|
|
|
|
|
if(FluTheme.isDark){
|
2023-03-07 23:27:32 +08:00
|
|
|
|
if(item_layout.singleSelected && selectionMode === FluTreeView.Single){
|
|
|
|
|
return Qt.rgba(62/255,62/255,62/255,1)
|
|
|
|
|
}
|
2023-03-10 18:08:32 +08:00
|
|
|
|
return (item_layout_mouse.containsMouse || item_layout_expanded.hovered || item_layout_checkbox.hovered)?Qt.rgba(62/255,62/255,62/255,1):Qt.rgba(0,0,0,0)
|
2023-03-07 18:43:03 +08:00
|
|
|
|
}else{
|
2023-03-07 23:27:32 +08:00
|
|
|
|
if(item_layout.singleSelected && selectionMode === FluTreeView.Single){
|
2023-03-10 18:08:32 +08:00
|
|
|
|
return Qt.rgba(0,0,0,0.06)
|
2023-03-07 23:27:32 +08:00
|
|
|
|
}
|
2023-03-10 18:08:32 +08:00
|
|
|
|
return (item_layout_mouse.containsMouse || item_layout_expanded.hovered || item_layout_checkbox.hovered)?Qt.rgba(0,0,0,0.03):Qt.rgba(0,0,0,0)
|
2023-03-07 18:43:03 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-07 00:05:27 +08:00
|
|
|
|
|
2023-03-07 23:27:32 +08:00
|
|
|
|
Rectangle{
|
|
|
|
|
width: 3
|
|
|
|
|
color:FluTheme.primaryColor.dark
|
|
|
|
|
visible: item_layout.singleSelected && (selectionMode === FluTreeView.Single)
|
|
|
|
|
radius: 3
|
|
|
|
|
height: 20
|
|
|
|
|
anchors{
|
|
|
|
|
left: parent.left
|
|
|
|
|
verticalCenter: parent.verticalCenter
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-07 18:43:03 +08:00
|
|
|
|
MouseArea{
|
|
|
|
|
id:item_layout_mouse
|
|
|
|
|
anchors.fill: parent
|
|
|
|
|
hoverEnabled: true
|
|
|
|
|
onClicked: {
|
2023-03-07 23:27:32 +08:00
|
|
|
|
item_layout_rect.onClickItem()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function onClickItem(){
|
|
|
|
|
if(selectionMode === FluTreeView.None){
|
|
|
|
|
itemClicked(model)
|
|
|
|
|
}
|
|
|
|
|
if(selectionMode === FluTreeView.Single){
|
|
|
|
|
currentElement = model
|
|
|
|
|
if(item_layout.parent.parent.parent.itemModel){
|
|
|
|
|
currentParentElement = item_layout.parent.parent.parent.itemModel
|
|
|
|
|
}else{
|
|
|
|
|
if(item_layout.parent.itemModel){
|
|
|
|
|
currentParentElement = item_layout.parent.itemModel
|
|
|
|
|
}
|
2023-03-07 18:43:03 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-07 23:27:32 +08:00
|
|
|
|
if(selectionMode === FluTreeView.Multiple){
|
|
|
|
|
|
|
|
|
|
}
|
2023-03-07 18:43:03 +08:00
|
|
|
|
}
|
2023-03-07 00:05:27 +08:00
|
|
|
|
|
2023-03-07 18:43:03 +08:00
|
|
|
|
RowLayout{
|
2023-03-07 23:27:32 +08:00
|
|
|
|
id:item_layout_row
|
|
|
|
|
anchors.verticalCenter: item_layout_rect.verticalCenter
|
|
|
|
|
|
2023-03-07 18:43:03 +08:00
|
|
|
|
Item{
|
|
|
|
|
width: 15*level
|
|
|
|
|
Layout.alignment: Qt.AlignVCenter
|
|
|
|
|
}
|
2023-03-07 23:27:32 +08:00
|
|
|
|
|
|
|
|
|
FluCheckBox{
|
2023-03-08 11:46:40 +08:00
|
|
|
|
id:item_layout_checkbox
|
2023-03-07 23:27:32 +08:00
|
|
|
|
text:""
|
2023-03-08 11:46:40 +08:00
|
|
|
|
checked: itemModel.multipSelected
|
2023-03-07 23:27:32 +08:00
|
|
|
|
visible: selectionMode === FluTreeView.Multiple
|
2023-03-08 13:41:43 +08:00
|
|
|
|
Layout.leftMargin: 5
|
|
|
|
|
|
|
|
|
|
function refreshCheckBox(){
|
|
|
|
|
const stack = [tree_model.get(0)];
|
|
|
|
|
const result = [];
|
|
|
|
|
while (stack.length > 0) {
|
|
|
|
|
const curr = stack.pop();
|
|
|
|
|
result.unshift(curr);
|
|
|
|
|
if (curr.items) {
|
|
|
|
|
for(var i=0 ; i<curr.items.count ; i++){
|
|
|
|
|
curr.items.setProperty(i,"multipIndex",i)
|
|
|
|
|
curr.items.setProperty(i,"multipParentKey",curr.key)
|
|
|
|
|
stack.push(curr.items.get(i));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for(var j=0 ; j<result.length-1 ; j++){
|
|
|
|
|
var item = result[j]
|
|
|
|
|
let obj = result.find(function(o) {
|
|
|
|
|
return o.key === item.multipParentKey;
|
|
|
|
|
});
|
|
|
|
|
if((item.items !== undefined) && (item.items.count !== 0)){
|
|
|
|
|
var items = item.items
|
|
|
|
|
for(var k=0 ; k<items.count ; k++){
|
|
|
|
|
if(items.get(k).multipSelected === false){
|
|
|
|
|
obj.items.setProperty(item.multipIndex,"multipSelected",false)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
obj.items.setProperty(item.multipIndex,"multipSelected",true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
checkClicked:function(){
|
|
|
|
|
if(hasChild){
|
|
|
|
|
const stack = [itemModel];
|
2023-03-08 11:46:40 +08:00
|
|
|
|
while (stack.length > 0) {
|
|
|
|
|
const curr = stack.pop();
|
|
|
|
|
if (curr.items) {
|
|
|
|
|
for(var i=0 ; i<curr.items.count ; i++){
|
2023-03-08 13:41:43 +08:00
|
|
|
|
curr.items.setProperty(i,"multipSelected",!itemModel.multipSelected)
|
2023-03-08 11:46:40 +08:00
|
|
|
|
stack.push(curr.items.get(i));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-08 13:41:43 +08:00
|
|
|
|
refreshCheckBox()
|
|
|
|
|
}else{
|
|
|
|
|
itemModel.multipSelected = !itemModel.multipSelected
|
|
|
|
|
refreshCheckBox()
|
2023-03-07 23:27:32 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-07 18:43:03 +08:00
|
|
|
|
FluIconButton{
|
|
|
|
|
id:item_layout_expanded
|
|
|
|
|
color:"#00000000"
|
2023-03-11 00:29:06 +08:00
|
|
|
|
icon:item_layout.expanded?FluentIcons.ChevronDown:FluentIcons.ChevronRight
|
2023-03-08 11:46:40 +08:00
|
|
|
|
opacity: item_layout.hasChild
|
2023-03-11 00:29:06 +08:00
|
|
|
|
iconSize: 15
|
2023-03-07 18:43:03 +08:00
|
|
|
|
onClicked: {
|
2023-03-08 11:46:40 +08:00
|
|
|
|
if(!item_layout.hasChild){
|
2023-03-07 23:27:32 +08:00
|
|
|
|
item_layout_rect.onClickItem()
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-03-07 18:43:03 +08:00
|
|
|
|
model.expanded = !model.expanded
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-07 23:27:32 +08:00
|
|
|
|
|
|
|
|
|
FluText {
|
|
|
|
|
text: item_layout.text
|
2023-03-07 18:43:03 +08:00
|
|
|
|
Layout.alignment: Qt.AlignVCenter
|
2023-03-09 23:11:59 +08:00
|
|
|
|
topPadding: 7
|
|
|
|
|
bottomPadding: 7
|
2023-03-07 18:43:03 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-07 23:27:32 +08:00
|
|
|
|
|
2023-03-07 18:43:03 +08:00
|
|
|
|
Item{
|
2023-03-07 23:27:32 +08:00
|
|
|
|
id:item_sub
|
|
|
|
|
visible: {
|
2023-03-08 11:46:40 +08:00
|
|
|
|
if(!hasChild){
|
2023-03-07 23:27:32 +08:00
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return item_layout.expanded??false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
width: item_sub_layout.implicitWidth
|
|
|
|
|
height: item_sub_layout.implicitHeight
|
|
|
|
|
x:0.001
|
|
|
|
|
Column{
|
|
|
|
|
id: item_sub_layout
|
|
|
|
|
Repeater{
|
|
|
|
|
id:repeater_items
|
|
|
|
|
model: item_layout.items
|
|
|
|
|
delegate: delegate_items
|
|
|
|
|
}
|
2023-03-07 18:43:03 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-07 00:05:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-03-07 23:27:32 +08:00
|
|
|
|
ListView {
|
|
|
|
|
id: list_root
|
2023-03-07 18:43:03 +08:00
|
|
|
|
anchors.fill: parent
|
2023-03-07 23:27:32 +08:00
|
|
|
|
delegate: delegate_root
|
|
|
|
|
boundsBehavior: ListView.StopAtBounds
|
|
|
|
|
contentWidth: contentItem.childrenRect.width
|
|
|
|
|
model: tree_model
|
|
|
|
|
flickableDirection: Flickable.HorizontalAndVerticalFlick
|
2023-03-07 18:43:03 +08:00
|
|
|
|
clip: true
|
2023-03-07 23:27:32 +08:00
|
|
|
|
ScrollBar.vertical: ScrollBar { }
|
|
|
|
|
ScrollBar.horizontal: ScrollBar { }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateData(items){
|
|
|
|
|
rootModel.clear()
|
|
|
|
|
rootModel.append(items)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function signleData(){
|
|
|
|
|
return currentElement
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function multipData(){
|
2023-03-08 13:41:43 +08:00
|
|
|
|
const stack = [tree_model.get(0)];
|
|
|
|
|
const result = [];
|
|
|
|
|
while (stack.length > 0) {
|
|
|
|
|
const curr = stack.pop();
|
|
|
|
|
if(curr.multipSelected){
|
|
|
|
|
result.push(curr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(var i=0 ; i<curr.items.count ; i++){
|
|
|
|
|
stack.push(curr.items.get(i));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result
|
2023-03-07 23:27:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createItem(text="Title",expanded=true,items=[]){
|
2023-03-08 13:41:43 +08:00
|
|
|
|
return {text:text,expanded:expanded,items:items,key:uniqueRandom(),multipSelected:false,multipIndex:0,multipParentKey:""};
|
2023-03-07 18:43:03 +08:00
|
|
|
|
}
|
2023-03-07 00:05:27 +08:00
|
|
|
|
|
2023-03-08 11:46:40 +08:00
|
|
|
|
function uniqueRandom() {
|
|
|
|
|
var timestamp = Date.now();
|
|
|
|
|
var random = Math.floor(Math.random() * 1000000);
|
|
|
|
|
return timestamp.toString() + random.toString();
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-07 00:05:27 +08:00
|
|
|
|
}
|