This commit is contained in:
zhuzichu 2023-09-15 19:11:55 +08:00
parent e6d9de34ea
commit be194e7624
16 changed files with 637 additions and 155 deletions

View File

@ -12,7 +12,7 @@ FluScrollablePage{
FluArea{ FluArea{
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 20 Layout.topMargin: 20
height: 240 height: 270
paddings: 10 paddings: 10
ColumnLayout{ ColumnLayout{
spacing: 14 spacing: 14
@ -23,7 +23,7 @@ FluScrollablePage{
FluButton{ FluButton{
text:"Info" text:"Info"
onClicked: { onClicked: {
showInfo("这是一个Info样式的InfoBar") showInfo("这是一个Info样式的InfoBar",0,"123")
} }
} }
FluButton{ FluButton{
@ -44,6 +44,12 @@ FluScrollablePage{
showSuccess("这是一个Success样式的InfoBar这是一个Success样式的InfoBar") showSuccess("这是一个Success样式的InfoBar这是一个Success样式的InfoBar")
} }
} }
FluButton{
text:"手动关闭的InfoBar"
onClicked: {
showInfo("这是一个Info样式的InfoBar",0,"支持手动关闭")
}
}
FluButton{ FluButton{
text:"Loading" text:"Loading"
onClicked: { onClicked: {

View File

@ -12,7 +12,7 @@ FluScrollablePage {
function treeData(){ function treeData(){
const dig = (path = '0', level = 4) => { const dig = (path = '0', level = 4) => {
const list = []; const list = [];
for (let i = 0; i < 6; i += 1) { for (let i = 0; i < 10; i += 1) {
const key = `${path}-${i}`; const key = `${path}-${i}`;
const treeNode = { const treeNode = {
title: key, title: key,
@ -32,33 +32,71 @@ FluScrollablePage {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 10 Layout.topMargin: 10
paddings: 10 paddings: 10
height: 60 height: 80
FluText{ Column{
text:"共计:%1条数据".arg(tree_view.count())
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: 10
FluText{
text:"高性能树控件新的TreeView用TableView实现"
}
FluText{
text:"共计:%1条数据当前显示的%2条数据".arg(tree_view.count()).arg(tree_view.visibleCount())
}
} }
} }
FluArea{ FluArea{
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 10 Layout.topMargin: 10
paddings: 10 paddings: 10
height: 400 height: 400
Item{
anchors.fill: tree_view
FluShadow{}
}
FluTreeView{ FluTreeView{
id:tree_view id:tree_view
width:240 width:slider_width.value
anchors{ anchors{
top:parent.top top:parent.top
left:parent.left left:parent.left
bottom:parent.bottom bottom:parent.bottom
} }
showLine: switch_showline.checked
Component.onCompleted: { Component.onCompleted: {
var data = treeData() var data = treeData()
dataSource = data dataSource = data
} }
} }
} Column{
anchors{
top:parent.top
topMargin: 10
bottomMargin: 10
rightMargin: 10
bottom:parent.bottom
right: parent.right
}
RowLayout{
spacing: 10
FluText{
text:"width:"
Layout.alignment: Qt.AlignVCenter
}
FluSlider{
id:slider_width
value: 200
from: 160
to:320
}
}
FluToggleSwitch{
id:switch_showline
text:"showLine"
checked: true
}
}
}
CodeExpander{ CodeExpander{
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: -1 Layout.topMargin: -1

View File

@ -13,7 +13,7 @@ FluScrollablePage{
FluArea{ FluArea{
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 20 Layout.topMargin: 20
height: 240 height: 270
paddings: 10 paddings: 10
ColumnLayout{ ColumnLayout{
spacing: 14 spacing: 14
@ -24,7 +24,7 @@ FluScrollablePage{
FluButton{ FluButton{
text:"Info" text:"Info"
onClicked: { onClicked: {
showInfo("这是一个Info样式的InfoBar") showInfo("这是一个Info样式的InfoBar",0,"123")
} }
} }
FluButton{ FluButton{
@ -45,6 +45,12 @@ FluScrollablePage{
showSuccess("这是一个Success样式的InfoBar这是一个Success样式的InfoBar") showSuccess("这是一个Success样式的InfoBar这是一个Success样式的InfoBar")
} }
} }
FluButton{
text:"手动关闭的InfoBar"
onClicked: {
showInfo("这是一个Info样式的InfoBar",0,"支持手动关闭")
}
}
FluButton{ FluButton{
text:"Loading" text:"Loading"
onClicked: { onClicked: {

View File

@ -13,7 +13,7 @@ FluScrollablePage {
function treeData(){ function treeData(){
const dig = (path = '0', level = 4) => { const dig = (path = '0', level = 4) => {
const list = []; const list = [];
for (let i = 0; i < 6; i += 1) { for (let i = 0; i < 10; i += 1) {
const key = `${path}-${i}`; const key = `${path}-${i}`;
const treeNode = { const treeNode = {
title: key, title: key,
@ -33,33 +33,71 @@ FluScrollablePage {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 10 Layout.topMargin: 10
paddings: 10 paddings: 10
height: 60 height: 80
FluText{ Column{
text:"共计:%1条数据".arg(tree_view.count())
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: 10
FluText{
text:"高性能树控件新的TreeView用TableView实现"
}
FluText{
text:"共计:%1条数据当前显示的%2条数据".arg(tree_view.count()).arg(tree_view.visibleCount())
}
} }
} }
FluArea{ FluArea{
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 10 Layout.topMargin: 10
paddings: 10 paddings: 10
height: 400 height: 400
Item{
anchors.fill: tree_view
FluShadow{}
}
FluTreeView{ FluTreeView{
id:tree_view id:tree_view
width:240 width:slider_width.value
anchors{ anchors{
top:parent.top top:parent.top
left:parent.left left:parent.left
bottom:parent.bottom bottom:parent.bottom
} }
showLine: switch_showline.checked
Component.onCompleted: { Component.onCompleted: {
var data = treeData() var data = treeData()
dataSource = data dataSource = data
} }
} }
} Column{
anchors{
top:parent.top
topMargin: 10
bottomMargin: 10
rightMargin: 10
bottom:parent.bottom
right: parent.right
}
RowLayout{
spacing: 10
FluText{
text:"width:"
Layout.alignment: Qt.AlignVCenter
}
FluSlider{
id:slider_width
value: 200
from: 160
to:320
}
}
FluToggleSwitch{
id:switch_showline
text:"showLine"
checked: true
}
}
}
CodeExpander{ CodeExpander{
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: -1 Layout.topMargin: -1

61
src/FluTreeModel.cpp Normal file
View File

@ -0,0 +1,61 @@
#include "FluTreeModel.h"
#include <QMetaEnum>
FluTreeModel::FluTreeModel(QObject *parent)
: QAbstractTableModel{parent}
{
}
int FluTreeModel::rowCount(const QModelIndex &parent) const {
return _rows.count();
};
int FluTreeModel::columnCount(const QModelIndex &parent) const {
return 1;;
};
QVariant FluTreeModel::data(const QModelIndex &index, int role) const {
switch (role) {
case Qt::DisplayRole:
return QVariant::fromValue(_rows.at(index.row()));
default:
break;
}
return QVariant();
};
QHash<int, QByteArray> FluTreeModel::roleNames() const {
return { {Qt::DisplayRole, "display"} };
};
void FluTreeModel::setData(QList<QObject*> data){
beginResetModel();
_rows = data;
endResetModel();
}
void FluTreeModel::removeRows(int row,int count){
if (row < 0 || row + count > _rows.size())
return;
beginRemoveRows(QModelIndex(),row, row + count - 1);
for (int i = 0; i < count; ++i) {
_rows.removeAt(row);
}
endRemoveRows();
}
void FluTreeModel::insertRows(int row,QList<QObject*> data){
if (row < 0 || row > _rows.size())
return;
beginInsertRows(QModelIndex(), row, row + data.size() - 1);
for (const auto& item : data) {
_rows.insert(row++, item);
}
endInsertRows();
}
QObject* FluTreeModel::getRow(int row){
return _rows.at(row);
}

27
src/FluTreeModel.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef FLUTREEMODEL_H
#define FLUTREEMODEL_H
#include <QObject>
#include <QAbstractTableModel>
#include <QtQml/qqml.h>
class FluTreeModel : public QAbstractTableModel
{
Q_OBJECT
QML_NAMED_ELEMENT(FluTreeModel)
QML_ADDED_IN_MINOR_VERSION(1)
public:
explicit FluTreeModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void removeRows(int row,int count);
Q_INVOKABLE void insertRows(int row,QList<QObject*> data);
Q_INVOKABLE QObject* getRow(int row);
Q_INVOKABLE void setData(QList<QObject*> data);
private:
QList<QObject*> _rows;
};
#endif // FLUTREEMODEL_H

View File

@ -13,6 +13,7 @@
#include "FluWatermark.h" #include "FluWatermark.h"
#include "FluCaptcha.h" #include "FluCaptcha.h"
#include "FluEventBus.h" #include "FluEventBus.h"
#include "FluTreeModel.h"
#include "FluViewModel.h" #include "FluViewModel.h"
#include "Screenshot.h" #include "Screenshot.h"
#include "QRCode.h" #include "QRCode.h"
@ -53,6 +54,7 @@ void FluentUI::registerTypes(const char *uri){
qmlRegisterType<HttpRequest>(uri,major,minor,"HttpRequest"); qmlRegisterType<HttpRequest>(uri,major,minor,"HttpRequest");
qmlRegisterType<FluEvent>(uri,major,minor,"FluEvent"); qmlRegisterType<FluEvent>(uri,major,minor,"FluEvent");
qmlRegisterType<FluViewModel>(uri,major,minor,"FluViewModel"); qmlRegisterType<FluViewModel>(uri,major,minor,"FluViewModel");
qmlRegisterType<FluTreeModel>(uri,major,minor,"FluTreeModel");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/ColorPicker/ColorPicker.qml"),uri,major,minor,"ColorPicker"); 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"); qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/ColorPicker/Content/Checkerboard.qml"),uri,major,minor,"Checkerboard");

View File

@ -83,7 +83,7 @@ FluObject {
} }
Timer { Timer {
id:delayTimer id:delayTimer
interval: duration; running: true; repeat: true interval: duration; running: duration > 0; repeat: duration > 0
onTriggered: content.close(); onTriggered: content.close();
} }
Loader{ Loader{
@ -184,10 +184,47 @@ FluObject {
} }
} }
FluText{ Column{
text:_super.text spacing: 5
wrapMode: Text.WrapAnywhere FluText{
width: Math.min(implicitWidth,mcontrol.maxWidth) text:_super.text
wrapMode: Text.WrapAnywhere
width: Math.min(implicitWidth,mcontrol.maxWidth)
}
FluText{
text: _super.moremsg
visible: _super.moremsg
wrapMode : Text.WordWrap
textColor: FluColors.Grey120
}
}
FluIconButton{
iconSource: FluentIcons.ChromeClose
iconSize: 10
y:5
x:parent.width-35
visible: _super.duration<=0
iconColor: {
if(FluTheme.dark){
switch(_super.type){
case mcontrol.const_success: return Qt.rgba(108/255,203/255,95/255,1);
case mcontrol.const_warning: return Qt.rgba(252/255,225/255,0/255,1);
case mcontrol.const_info: return FluTheme.primaryColor.lighter;
case mcontrol.const_error: return Qt.rgba(255/255,153/255,164/255,1);
}
return "#FFFFFF"
}else{
switch(_super.type){
case mcontrol.const_success: return "#0f7b0f";
case mcontrol.const_warning: return "#9d5d00";
case mcontrol.const_info: return "#0066b4";
case mcontrol.const_error: return "#c42b1c";
}
return "#FFFFFF"
}
}
onClicked: _super.close()
} }
} }
} }

View File

@ -7,6 +7,7 @@ QtObject {
property int depth: 0 property int depth: 0
property bool isExpanded: true property bool isExpanded: true
property var __parent property var __parent
property int __childIndex: 0
property bool __expanded:{ property bool __expanded:{
var p = __parent; var p = __parent;
while (p) { while (p) {

View File

@ -6,14 +6,14 @@ import Qt.labs.qmlmodels 1.0
import FluentUI 1.0 import FluentUI 1.0
Item { Item {
property int currentIndex : -1
property var dataSource property var dataSource
property bool showLine: true
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 id:control
QtObject { QtObject {
id:d id:d
signal refreshLayout() property var rowData: []
onRefreshLayout: {
table_view.forceLayout()
}
function handleTree(treeData) { function handleTree(treeData) {
var comItem = Qt.createComponent("FluTreeItem.qml"); var comItem = Qt.createComponent("FluTreeItem.qml");
if (comItem.status !== Component.Ready) { if (comItem.status !== Component.Ready) {
@ -22,15 +22,16 @@ Item {
var stack = [] var stack = []
var rawData = [] var rawData = []
for (var item of treeData) { for (var item of treeData) {
stack.push({node:item,depth:0,isExpanded:true,__parent:undefined}) stack.push({node:item,depth:0,isExpanded:true,__parent:undefined,__childIndex:0})
} }
stack = stack.reverse() stack = stack.reverse()
var index =0 var index =0
while (stack.length > 0) { while (stack.length > 0) {
const { node, depth,isExpanded,__parent} = stack.pop(); const { node, depth,isExpanded,__parent,__childIndex} = stack.pop();
node.depth = depth; node.depth = depth;
node.isExpanded = isExpanded; node.isExpanded = isExpanded;
node.__parent = __parent; node.__parent = __parent;
node.__childIndex = __childIndex;
var objItem = comItem.createObject(table_view); var objItem = comItem.createObject(table_view);
objItem.title = node.title objItem.title = node.title
objItem.key = node.key objItem.key = node.key
@ -38,13 +39,16 @@ Item {
objItem.isExpanded = node.isExpanded objItem.isExpanded = node.isExpanded
objItem.__parent = node.__parent objItem.__parent = node.__parent
objItem.children = node.children objItem.children = node.children
objItem.__childIndex = node.__childIndex
objItem.index = index objItem.index = index
index = index + 1; index = index + 1;
rawData.push({display:objItem}) rawData.push(objItem)
if (node.children && node.children.length > 0) { if (node.children && node.children.length > 0) {
const children = node.children.reverse(); const children = node.children.reverse();
var childIndex = children.length-1
for (const child of children) { for (const child of children) {
stack.push({ node: child, depth: depth + 1,isExpanded:true,__parent:objItem}); stack.push({ node: child, depth: depth + 1,isExpanded:true,__parent:objItem,__childIndex:childIndex});
childIndex=childIndex-1;
} }
} }
} }
@ -52,52 +56,139 @@ Item {
} }
} }
onDataSourceChanged: { onDataSourceChanged: {
table_model.clear() d.rowData = d.handleTree(dataSource)
var data = d.handleTree(dataSource) tree_model.setData(d.rowData)
table_model.rows = data
table_view.forceLayout()
console.debug("共计:%1条数据".arg(table_model.rowCount))
} }
TableModel { FluTreeModel{
id:table_model id:tree_model
TableModelColumn { display: "display" } }
Timer{
id:timer_refresh
interval: 10
onTriggered: {
table_view.forceLayout()
}
} }
TableView{ TableView{
id:table_view id:table_view
ScrollBar.horizontal: FluScrollBar{} ScrollBar.horizontal: FluScrollBar{}
ScrollBar.vertical: FluScrollBar{} ScrollBar.vertical: FluScrollBar{}
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
model: table_model model: tree_model
clip: true clip: true
anchors.fill: parent anchors.fill: parent
rowHeightProvider: function(row) { onContentYChanged:{
if(table_model.getRow(row).display.__expanded){ timer_refresh.restart()
return 38
}
return 0
} }
reuseItems: false
delegate: Item { delegate: Item {
property bool hasChildren: {
if(display.children){
return true
}
return false
}
property var itemData: display
property bool vlineVisible: display.depth !== 0 && control.showLine
property bool hlineVisible: display.depth !== 0 && control.showLine && !hasChildren
property bool isLastIndex : {
if(display.__parent && display.__parent.children){
return display.__childIndex === display.__parent.children.length-1
}
return false
}
property bool isCurrent: control.currentIndex === row
implicitWidth: 46 + item_layout_text.width + 30*display.depth implicitWidth: 46 + item_layout_text.width + 30*display.depth
implicitHeight: 30
Rectangle{
width: 1
color: control.lineColor
visible: hlineVisible
height: isLastIndex ? parent.height/2 : parent.height
anchors{
top: parent.top
right: layout_row.left
}
}
Rectangle{
height: 1
color: control.lineColor
visible: hlineVisible
width: 18
anchors{
right: layout_row.left
rightMargin: -18
verticalCenter: parent.verticalCenter
}
}
Repeater{
model: Math.max(display.depth-1,0)
delegate: Rectangle{
required property int index
width: 1
color: control.lineColor
visible: vlineVisible
anchors{
top:parent.top
bottom: parent.bottom
left: parent.left
leftMargin: 30*(index+2) - 8
}
}
}
RowLayout{ RowLayout{
id:layout_row
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 14 + 30*display.depth anchors.leftMargin: 14 + 30*display.depth
FluIcon{ FluIconButton{
rotation: display.isExpanded?0:-90 Layout.preferredWidth: 20
iconSource:FluentIcons.ChevronDown Layout.preferredHeight: 20
iconSize: 15 enabled: opacity
Layout.alignment: Qt.AlignVCenter opacity: hasChildren
opacity: { contentItem: FluIcon{
if(display.children){ rotation: itemData.isExpanded?0:-90
return true iconSource:FluentIcons.ChevronDown
iconSize: 16
Behavior on rotation{
NumberAnimation{
duration: FluTheme.enableAnimation ? 167 : 0
easing.type: Easing.OutCubic
}
} }
return false
} }
MouseArea{ onClicked: {
anchors.fill: parent var isExpanded = !itemData.isExpanded
onClicked: { itemData.isExpanded = isExpanded
display.isExpanded = !display.isExpanded var i,obj
d.refreshLayout() if(isExpanded){
for( i=0;i<d.rowData.length;i++){
obj = d.rowData[i]
if(obj === itemData){
var data = []
for(var j=i+1;j<d.rowData.length;j++){
obj = d.rowData[j]
if(obj.depth === itemData.depth){
break
}
if(obj.__expanded){
data.push(obj)
}
}
tree_model.insertRows(row+1,data)
break
}
}
}else{
var removeCount = 0
for( i=row+1;i<tree_model.rowCount();i++){
obj = tree_model.getRow(i)
if(obj.depth === itemData.depth){
break
}
removeCount = removeCount + 1;
}
tree_model.removeRows(row+1,removeCount)
} }
} }
} }
@ -107,17 +198,32 @@ Item {
Layout.preferredWidth: item_text.implicitWidth+14 Layout.preferredWidth: item_text.implicitWidth+14
Layout.preferredHeight:item_text.implicitHeight+14 Layout.preferredHeight:item_text.implicitHeight+14
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
HoverHandler{ Rectangle{
id:item_hover_text width: 3
height: 18
radius: 1.5
color: FluTheme.primaryColor.dark
visible: isCurrent
anchors{
verticalCenter: parent.verticalCenter
}
}
MouseArea{
id:item_text_mousearea
anchors.fill: parent
hoverEnabled: true
onClicked: {
control.currentIndex = row
}
} }
color: { color: {
if(FluTheme.dark){ if(FluTheme.dark){
if(item_hover_text.hovered){ if(item_text_mousearea.containsMouse || isCurrent){
return Qt.rgba(1,1,1,0.03) return Qt.rgba(1,1,1,0.03)
} }
return Qt.rgba(0,0,0,0) return Qt.rgba(0,0,0,0)
}else{ }else{
if(item_hover_text.hovered){ if(item_text_mousearea.containsMouse || isCurrent){
return Qt.rgba(0,0,0,0.03) return Qt.rgba(0,0,0,0.03)
} }
return Qt.rgba(0,0,0,0) return Qt.rgba(0,0,0,0)
@ -127,11 +233,11 @@ Item {
id:item_text id:item_text
text: display.title text: display.title
anchors.centerIn: parent anchors.centerIn: parent
} color:{
MouseArea{ if(item_text_mousearea.pressed){
anchors.fill: parent return FluTheme.dark ? FluColors.Grey80 : FluColors.Grey120
onClicked: { }
d.refreshLayout() return FluTheme.dark ? FluColors.White : FluColors.Grey220
} }
} }
} }
@ -139,6 +245,9 @@ Item {
} }
} }
function count(){ function count(){
return table_model.rowCount return d.rowData.length
}
function visibleCount(){
return table_view.rows
} }
} }

View File

@ -107,7 +107,9 @@ Window {
MouseArea{ MouseArea{
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
popup_loading.visible = false if (cancel){
popup_loading.visible = false
}
} }
} }
ColumnLayout{ ColumnLayout{

View File

@ -83,7 +83,7 @@ FluObject {
} }
Timer { Timer {
id:delayTimer id:delayTimer
interval: duration; running: true; repeat: true interval: duration; running: duration > 0; repeat: duration > 0
onTriggered: content.close(); onTriggered: content.close();
} }
Loader{ Loader{
@ -184,10 +184,47 @@ FluObject {
} }
} }
FluText{ Column{
text:_super.text spacing: 5
wrapMode: Text.WrapAnywhere FluText{
width: Math.min(implicitWidth,mcontrol.maxWidth) text:_super.text
wrapMode: Text.WrapAnywhere
width: Math.min(implicitWidth,mcontrol.maxWidth)
}
FluText{
text: _super.moremsg
visible: _super.moremsg
wrapMode : Text.WordWrap
textColor: FluColors.Grey120
}
}
FluIconButton{
iconSource: FluentIcons.ChromeClose
iconSize: 10
y:5
x:parent.width-35
visible: _super.duration<=0
iconColor: {
if(FluTheme.dark){
switch(_super.type){
case mcontrol.const_success: return Qt.rgba(108/255,203/255,95/255,1);
case mcontrol.const_warning: return Qt.rgba(252/255,225/255,0/255,1);
case mcontrol.const_info: return FluTheme.primaryColor.lighter;
case mcontrol.const_error: return Qt.rgba(255/255,153/255,164/255,1);
}
return "#FFFFFF"
}else{
switch(_super.type){
case mcontrol.const_success: return "#0f7b0f";
case mcontrol.const_warning: return "#9d5d00";
case mcontrol.const_info: return "#0066b4";
case mcontrol.const_error: return "#c42b1c";
}
return "#FFFFFF"
}
}
onClicked: _super.close()
} }
} }
} }

View File

@ -8,6 +8,7 @@ Item {
property int launchMode: FluPageType.SingleTop property int launchMode: FluPageType.SingleTop
property bool animDisabled: false property bool animDisabled: false
property string url : "" property string url : ""
signal animationEnd()
id: control id: control
opacity: visible opacity: visible
visible: false visible: false
@ -30,5 +31,13 @@ Item {
} }
Component.onCompleted: { Component.onCompleted: {
visible = true visible = true
timer.restart()
}
Timer{
id:timer
interval: !animDisabled && FluTheme.enableAnimation ? 200 : 0
onTriggered: {
control.animationEnd()
}
} }
} }

View File

@ -7,6 +7,7 @@ QtObject {
property int depth: 0 property int depth: 0
property bool isExpanded: true property bool isExpanded: true
property var __parent property var __parent
property int __childIndex: 0
property bool __expanded:{ property bool __expanded:{
var p = __parent; var p = __parent;
while (p) { while (p) {

View File

@ -6,14 +6,14 @@ import Qt.labs.qmlmodels
import FluentUI import FluentUI
Item { Item {
property int currentIndex : -1
property var dataSource property var dataSource
property bool showLine: true
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 id:control
QtObject { QtObject {
id:d id:d
signal refreshLayout() property var rowData: []
onRefreshLayout: {
table_view.forceLayout()
}
function handleTree(treeData) { function handleTree(treeData) {
var comItem = Qt.createComponent("FluTreeItem.qml"); var comItem = Qt.createComponent("FluTreeItem.qml");
if (comItem.status !== Component.Ready) { if (comItem.status !== Component.Ready) {
@ -22,15 +22,16 @@ Item {
var stack = [] var stack = []
var rawData = [] var rawData = []
for (var item of treeData) { for (var item of treeData) {
stack.push({node:item,depth:0,isExpanded:true,__parent:undefined}) stack.push({node:item,depth:0,isExpanded:true,__parent:undefined,__childIndex:0})
} }
stack = stack.reverse() stack = stack.reverse()
var index =0 var index =0
while (stack.length > 0) { while (stack.length > 0) {
const { node, depth,isExpanded,__parent} = stack.pop(); const { node, depth,isExpanded,__parent,__childIndex} = stack.pop();
node.depth = depth; node.depth = depth;
node.isExpanded = isExpanded; node.isExpanded = isExpanded;
node.__parent = __parent; node.__parent = __parent;
node.__childIndex = __childIndex;
var objItem = comItem.createObject(table_view); var objItem = comItem.createObject(table_view);
objItem.title = node.title objItem.title = node.title
objItem.key = node.key objItem.key = node.key
@ -38,13 +39,16 @@ Item {
objItem.isExpanded = node.isExpanded objItem.isExpanded = node.isExpanded
objItem.__parent = node.__parent objItem.__parent = node.__parent
objItem.children = node.children objItem.children = node.children
objItem.__childIndex = node.__childIndex
objItem.index = index objItem.index = index
index = index + 1; index = index + 1;
rawData.push({display:objItem}) rawData.push(objItem)
if (node.children && node.children.length > 0) { if (node.children && node.children.length > 0) {
const children = node.children.reverse(); const children = node.children.reverse();
var childIndex = children.length-1
for (const child of children) { for (const child of children) {
stack.push({ node: child, depth: depth + 1,isExpanded:true,__parent:objItem}); stack.push({ node: child, depth: depth + 1,isExpanded:true,__parent:objItem,__childIndex:childIndex});
childIndex=childIndex-1;
} }
} }
} }
@ -52,89 +56,188 @@ Item {
} }
} }
onDataSourceChanged: { onDataSourceChanged: {
table_model.clear() d.rowData = d.handleTree(dataSource)
var data = d.handleTree(dataSource) tree_model.setData(d.rowData)
table_model.rows = data
table_view.forceLayout()
console.debug("共计:%1条数据".arg(table_model.rowCount))
} }
TableModel { FluTreeModel{
id:table_model id:tree_model
TableModelColumn { display: "display" }
} }
ListView{ Timer{
id:timer_refresh
interval: 10
onTriggered: {
table_view.forceLayout()
}
}
TableView{
id:table_view
ScrollBar.horizontal: FluScrollBar{}
ScrollBar.vertical: FluScrollBar{}
boundsBehavior: Flickable.StopAtBounds
model: tree_model
clip: true
anchors.fill: parent anchors.fill: parent
TableView{ onContentYChanged:{
id:table_view timer_refresh.restart()
ScrollBar.horizontal: FluScrollBar{} }
ScrollBar.vertical: FluScrollBar{} reuseItems: false
boundsBehavior: Flickable.StopAtBounds delegate: Item {
model: table_model property bool hasChildren: {
clip: true if(display.children){
anchors.fill: parent return true
rowHeightProvider: function(row) {
if(table_model.getRow(row).display.__expanded){
return 38
} }
return 0 return false
} }
delegate: Item { property var itemData: display
implicitWidth: 46 + item_layout_text.width + 30*display.depth property bool vlineVisible: display.depth !== 0 && control.showLine
RowLayout{ property bool hlineVisible: display.depth !== 0 && control.showLine && !hasChildren
anchors.verticalCenter: parent.verticalCenter property bool isLastIndex : {
anchors.left: parent.left if(display.__parent && display.__parent.children){
anchors.leftMargin: 14 + 30*display.depth return display.__childIndex === display.__parent.children.length-1
FluIcon{ }
rotation: display.isExpanded?0:-90 return false
}
property bool isCurrent: control.currentIndex === row
implicitWidth: 46 + item_layout_text.width + 30*display.depth
implicitHeight: 30
Rectangle{
width: 1
color: control.lineColor
visible: hlineVisible
height: isLastIndex ? parent.height/2 : parent.height
anchors{
top: parent.top
right: layout_row.left
}
}
Rectangle{
height: 1
color: control.lineColor
visible: hlineVisible
width: 18
anchors{
right: layout_row.left
rightMargin: -18
verticalCenter: parent.verticalCenter
}
}
Repeater{
model: Math.max(display.depth-1,0)
delegate: Rectangle{
required property int index
width: 1
color: control.lineColor
visible: vlineVisible
anchors{
top:parent.top
bottom: parent.bottom
left: parent.left
leftMargin: 30*(index+2) - 8
}
}
}
RowLayout{
id:layout_row
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 14 + 30*display.depth
FluIconButton{
Layout.preferredWidth: 20
Layout.preferredHeight: 20
enabled: opacity
opacity: hasChildren
contentItem: FluIcon{
rotation: itemData.isExpanded?0:-90
iconSource:FluentIcons.ChevronDown iconSource:FluentIcons.ChevronDown
iconSize: 15 iconSize: 16
Layout.alignment: Qt.AlignVCenter Behavior on rotation{
opacity: { NumberAnimation{
if(display.children){ duration: FluTheme.enableAnimation ? 167 : 0
return true easing.type: Easing.OutCubic
}
return false
}
MouseArea{
anchors.fill: parent
onClicked: {
display.isExpanded = !display.isExpanded
d.refreshLayout()
} }
} }
} }
onClicked: {
var isExpanded = !itemData.isExpanded
itemData.isExpanded = isExpanded
var i,obj
if(isExpanded){
for( i=0;i<d.rowData.length;i++){
obj = d.rowData[i]
if(obj === itemData){
var data = []
for(var j=i+1;j<d.rowData.length;j++){
obj = d.rowData[j]
if(obj.depth === itemData.depth){
break
}
if(obj.__expanded){
data.push(obj)
}
}
tree_model.insertRows(row+1,data)
break
}
}
}else{
var removeCount = 0
for( i=row+1;i<tree_model.rowCount();i++){
obj = tree_model.getRow(i)
if(obj.depth === itemData.depth){
break
}
removeCount = removeCount + 1;
}
tree_model.removeRows(row+1,removeCount)
}
}
}
Rectangle{
id:item_layout_text
radius: 4
Layout.preferredWidth: item_text.implicitWidth+14
Layout.preferredHeight:item_text.implicitHeight+14
Layout.alignment: Qt.AlignVCenter
Rectangle{ Rectangle{
id:item_layout_text width: 3
radius: 4 height: 18
Layout.preferredWidth: item_text.implicitWidth+14 radius: 1.5
Layout.preferredHeight:item_text.implicitHeight+14 color: FluTheme.primaryColor.dark
Layout.alignment: Qt.AlignVCenter visible: isCurrent
HoverHandler{ anchors{
id:item_hover_text verticalCenter: parent.verticalCenter
} }
color: { }
if(FluTheme.dark){ MouseArea{
if(item_hover_text.hovered){ id:item_text_mousearea
return Qt.rgba(1,1,1,0.03) anchors.fill: parent
} hoverEnabled: true
return Qt.rgba(0,0,0,0) onClicked: {
}else{ control.currentIndex = row
if(item_hover_text.hovered){ }
return Qt.rgba(0,0,0,0.03) }
} color: {
return Qt.rgba(0,0,0,0) if(FluTheme.dark){
if(item_text_mousearea.containsMouse || isCurrent){
return Qt.rgba(1,1,1,0.03)
} }
} return Qt.rgba(0,0,0,0)
FluText { }else{
id:item_text if(item_text_mousearea.containsMouse || isCurrent){
text: display.title return Qt.rgba(0,0,0,0.03)
anchors.centerIn: parent
}
MouseArea{
anchors.fill: parent
onClicked: {
d.refreshLayout()
} }
return Qt.rgba(0,0,0,0)
}
}
FluText {
id:item_text
text: display.title
anchors.centerIn: parent
color:{
if(item_text_mousearea.pressed){
return FluTheme.dark ? FluColors.Grey80 : FluColors.Grey120
}
return FluTheme.dark ? FluColors.White : FluColors.Grey220
} }
} }
} }
@ -142,6 +245,9 @@ Item {
} }
} }
function count(){ function count(){
return table_model.rowCount return d.rowData.length
}
function visibleCount(){
return table_view.rows
} }
} }

View File

@ -106,7 +106,9 @@ Window {
MouseArea{ MouseArea{
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
popup_loading.visible = false if (cancel){
popup_loading.visible = false
}
} }
} }
ColumnLayout{ ColumnLayout{