This commit is contained in:
朱子楚\zhuzi 2024-04-09 20:53:52 +08:00
parent 6ebd659e13
commit 6a31e86505
15 changed files with 1711 additions and 815 deletions

View File

@ -8,7 +8,7 @@ if(MSVC)
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF")
endif() endif()
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/.cmake/) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/.cmake/)
include(GetGitRevisionDescription) include(GetGitRevisionDescription)

View File

@ -475,46 +475,51 @@
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="430"/> <location filename="qml/global/ItemsOriginal.qml" line="430"/>
<source>QRCode</source> <source>CodeEditor</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="436"/> <location filename="qml/global/ItemsOriginal.qml" line="436"/>
<source>Tour</source> <source>QRCode</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="442"/> <location filename="qml/global/ItemsOriginal.qml" line="442"/>
<source>Timeline</source> <source>Tour</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="448"/> <location filename="qml/global/ItemsOriginal.qml" line="448"/>
<source>Captcha</source> <source>Timeline</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="454"/> <location filename="qml/global/ItemsOriginal.qml" line="454"/>
<source>Captcha</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/global/ItemsOriginal.qml" line="460"/>
<source>Network</source> <source>Network</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="461"/> <location filename="qml/global/ItemsOriginal.qml" line="467"/>
<source>Remote Loader</source> <source>Remote Loader</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="475"/> <location filename="qml/global/ItemsOriginal.qml" line="481"/>
<source>Hot Loader</source> <source>Hot Loader</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="481"/> <location filename="qml/global/ItemsOriginal.qml" line="487"/>
<source>3D</source> <source>3D</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="487"/> <location filename="qml/global/ItemsOriginal.qml" line="493"/>
<source>Test Crash</source> <source>Test Crash</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -2376,13 +2381,23 @@ Some contents...</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/page/T_TreeView.qml" line="44"/> <location filename="qml/page/T_TreeView.qml" line="230"/>
<source>Total %1 data, %2 data currently displayed</source> <source>Title</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/page/T_TreeView.qml" line="48"/> <location filename="qml/page/T_TreeView.qml" line="238"/>
<source>A total of %1 data items are selected</source> <source>Avatar</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/page/T_TreeView.qml" line="242"/>
<source>Address</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/page/T_TreeView.qml" line="234"/>
<source>Name</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>

View File

@ -475,46 +475,51 @@
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="430"/> <location filename="qml/global/ItemsOriginal.qml" line="430"/>
<source>CodeEditor</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/global/ItemsOriginal.qml" line="436"/>
<source>QRCode</source> <source>QRCode</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="436"/> <location filename="qml/global/ItemsOriginal.qml" line="442"/>
<source>Tour</source> <source>Tour</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="442"/> <location filename="qml/global/ItemsOriginal.qml" line="448"/>
<source>Timeline</source> <source>Timeline</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="448"/> <location filename="qml/global/ItemsOriginal.qml" line="454"/>
<source>Captcha</source> <source>Captcha</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="454"/> <location filename="qml/global/ItemsOriginal.qml" line="460"/>
<source>Network</source> <source>Network</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="461"/> <location filename="qml/global/ItemsOriginal.qml" line="467"/>
<source>Remote Loader</source> <source>Remote Loader</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="475"/> <location filename="qml/global/ItemsOriginal.qml" line="481"/>
<source>Hot Loader</source> <source>Hot Loader</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="481"/> <location filename="qml/global/ItemsOriginal.qml" line="487"/>
<source>3D</source> <source>3D</source>
<translation type="unfinished">3D</translation> <translation type="unfinished">3D</translation>
</message> </message>
<message> <message>
<location filename="qml/global/ItemsOriginal.qml" line="487"/> <location filename="qml/global/ItemsOriginal.qml" line="493"/>
<source>Test Crash</source> <source>Test Crash</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -2459,14 +2464,32 @@ Some contents...</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="qml/page/T_TreeView.qml" line="44"/>
<source>Total %1 data, %2 data currently displayed</source> <source>Total %1 data, %2 data currently displayed</source>
<translation type="unfinished">%1%2</translation> <translation type="obsolete">%1%2</translation>
</message> </message>
<message> <message>
<location filename="qml/page/T_TreeView.qml" line="48"/>
<source>A total of %1 data items are selected</source> <source>A total of %1 data items are selected</source>
<translation type="unfinished">%1</translation> <translation type="obsolete">%1</translation>
</message>
<message>
<location filename="qml/page/T_TreeView.qml" line="230"/>
<source>Title</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/page/T_TreeView.qml" line="242"/>
<source>Address</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/page/T_TreeView.qml" line="238"/>
<source>Avatar</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/page/T_TreeView.qml" line="234"/>
<source>Name</source>
<translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context> <context>

View File

@ -426,6 +426,12 @@ FluObject{
FluPaneItemExpander{ FluPaneItemExpander{
title: qsTr("Other") title: qsTr("Other")
icon: FluentIcons.Shop icon: FluentIcons.Shop
FluPaneItem{
title: qsTr("CodeEditor")
menuDelegate: paneItemMenu
url: "qrc:/example/qml/page/T_CodeEditor.qml"
onTap: { navigationView.push(url) }
}
FluPaneItem{ FluPaneItem{
title: qsTr("QRCode") title: qsTr("QRCode")
menuDelegate: paneItemMenu menuDelegate: paneItemMenu

View File

@ -9,14 +9,34 @@ FluContentPage {
title: qsTr("TreeView") title: qsTr("TreeView")
function treeData(){ function treeData(){
const dig = (path = '0', level = 4) => { const names = ["孙悟空", "猪八戒", "沙和尚", "唐僧","白骨夫人","金角大王","熊山君","黄风怪","银角大王"]
function getRandomName(){
var randomIndex = Math.floor(Math.random() * names.length)
return names[randomIndex]
}
const addresses = ["傲来国界花果山水帘洞","傲来国界坎源山脏水洞","大唐国界黑风山黑风洞","大唐国界黄风岭黄风洞","大唐国界骷髅山白骨洞","宝象国界碗子山波月洞","宝象国界平顶山莲花洞","宝象国界压龙山压龙洞","乌鸡国界号山枯松涧火云洞","乌鸡国界衡阳峪黑水河河神府"]
function getRandomAddresses(){
var randomIndex = Math.floor(Math.random() * addresses.length)
return addresses[randomIndex]
}
const avatars = ["qrc:/example/res/svg/avatar_1.svg", "qrc:/example/res/svg/avatar_2.svg", "qrc:/example/res/svg/avatar_3.svg", "qrc:/example/res/svg/avatar_4.svg","qrc:/example/res/svg/avatar_5.svg","qrc:/example/res/svg/avatar_6.svg","qrc:/example/res/svg/avatar_7.svg","qrc:/example/res/svg/avatar_8.svg","qrc:/example/res/svg/avatar_9.svg","qrc:/example/res/svg/avatar_10.svg","qrc:/example/res/svg/avatar_11.svg","qrc:/example/res/svg/avatar_12.svg"]
function getRandomAvatar(){
var randomIndex = Math.floor(Math.random() * avatars.length);
return avatars[randomIndex];
}
const dig = (path = '0', level = 5) => {
const list = []; const list = [];
for (let i = 0; i < 6; i += 1) { for (let i = 0; i < 5; i += 1) {
const key = `${path}-${i}`; const key = `${path}-${i}`;
const treeNode = { const treeNode = {
title: key, title: key,
key, _key: key,
name: getRandomName(),
avatar:tree_view.customItem(com_avatar,{avatar:getRandomAvatar()}),
address: getRandomAddresses()
}; };
if (level > 0) { if (level > 0) {
treeNode.children = dig(key, level - 1); treeNode.children = dig(key, level - 1);
@ -28,26 +48,107 @@ FluContentPage {
return dig(); return dig();
} }
Column{ // Row{
id: layout_column // id: layout_column
spacing: 12 // spacing: 12
width: 300 // width: 340
// anchors{
// top:parent.top
// left: parent.left
// leftMargin: 10
// bottom:parent.bottom
// bottomMargin: 20
// }
// RowLayout{
// spacing: 10
// FluText{
// text: "cellHeight:"
// Layout.alignment: Qt.AlignVCenter
// }
// FluSlider{
// id: slider_cell_height
// value: 30
// from: 30
// to:100
// }
// }
// RowLayout{
// spacing: 10
// FluText{
// text: "depthPadding:"
// Layout.alignment: Qt.AlignVCenter
// }
// FluSlider{
// id: slider_depth_padding
// value: 15
// from: 15
// to:100
// }
// }
// FluToggleSwitch{
// id: switch_showline
// text:"showLine"
// checked: false
// }
// FluToggleSwitch{
// id: switch_checkable
// text:"checkable"
// checked: false
// }
// FluButton{
// text: "all expand"
// onClicked: {
// tree_view.allExpand()
// }
// }
// FluButton{
// text: "all collapse"
// onClicked: {
// tree_view.allCollapse()
// }
// }
// }
Component{
id:com_avatar
Item{
FluClip{
anchors.centerIn: parent
width: height
height: parent.height/3*2
radius: [height/2,height/2,height/2,height/2]
Image{
anchors.fill: parent
source: {
if(options && options.avatar){
return options.avatar
}
return ""
}
sourceSize: Qt.size(80,80)
}
}
}
}
FluFrame{
id:layout_controls
anchors{ anchors{
left: parent.left
right: parent.right
top: parent.top top: parent.top
topMargin: 10
}
height: 80
clip: true
Row{
spacing: 12
anchors{
left: parent.left left: parent.left
leftMargin: 10 leftMargin: 10
bottom:parent.bottom verticalCenter: parent.verticalCenter
bottomMargin: 20
} }
Column{
FluText{ anchors.verticalCenter: parent.verticalCenter
text: qsTr("Total %1 data, %2 data currently displayed").arg(tree_view.count()).arg(tree_view.visibleCount())
}
FluText{
text: qsTr("A total of %1 data items are selected").arg(tree_view.selectionModel().length)
}
RowLayout{ RowLayout{
spacing: 10 spacing: 10
FluText{ FluText{
@ -56,8 +157,8 @@ FluContentPage {
} }
FluSlider{ FluSlider{
id: slider_cell_height id: slider_cell_height
value: 30 value: 38
from: 30 from: 38
to:100 to:100
} }
} }
@ -69,26 +170,29 @@ FluContentPage {
} }
FluSlider{ FluSlider{
id: slider_depth_padding id: slider_depth_padding
value: 30 value: 15
from: 30 from: 15
to:100 to:100
} }
} }
}
Column{
spacing: 8
anchors.verticalCenter: parent.verticalCenter
FluToggleSwitch{ FluToggleSwitch{
id: switch_showline id: switch_showline
text:"showLine" text:"showLine"
checked: false checked: false
} }
FluToggleSwitch{
id: switch_draggable
text:"draggable"
checked: false
}
FluToggleSwitch{ FluToggleSwitch{
id: switch_checkable id: switch_checkable
text:"checkable" text:"checkable"
checked: false checked: false
} }
}
Column{
spacing: 8
anchors.verticalCenter: parent.verticalCenter
FluButton{ FluButton{
text: "all expand" text: "all expand"
onClicked: { onClicked: {
@ -102,25 +206,44 @@ FluContentPage {
} }
} }
} }
}
}
FluFrame{ FluFrame{
anchors{ anchors{
left: layout_column.right left: parent.left
top: parent.top top: layout_controls.bottom
topMargin: 10
bottom: parent.bottom bottom: parent.bottom
right: parent.right right: parent.right
rightMargin: 5
topMargin: 5
bottomMargin: 5
} }
FluShadow{}
FluTreeView{ FluTreeView{
id:tree_view id:tree_view
anchors.fill: parent anchors.fill: parent
cellHeight: slider_cell_height.value cellHeight: slider_cell_height.value
draggable:switch_draggable.checked
showLine: switch_showline.checked showLine: switch_showline.checked
checkable:switch_checkable.checked checkable:switch_checkable.checked
depthPadding: slider_depth_padding.value depthPadding: slider_depth_padding.value
columnSource:[
{
title: qsTr("Title"),
dataIndex: 'title',
width: 300
},{
title: qsTr("Name"),
dataIndex: 'name',
width: 100
},{
title: qsTr("Avatar"),
dataIndex: 'avatar',
width: 100
},{
title: qsTr("Address"),
dataIndex: 'address',
width: 200
},
]
Component.onCompleted: { Component.onCompleted: {
var data = treeData() var data = treeData()
dataSource = data dataSource = data

View File

@ -24,13 +24,15 @@ int FluTreeModel::rowCount(const QModelIndex &parent) const {
}; };
int FluTreeModel::columnCount(const QModelIndex &parent) const { int FluTreeModel::columnCount(const QModelIndex &parent) const {
return 1;; return this->_columnSource.size();
}; };
QVariant FluTreeModel::data(const QModelIndex &index, int role) const { QVariant FluTreeModel::data(const QModelIndex &index, int role) const {
switch (role) { switch (role) {
case Qt::DisplayRole: case TreeModelRoles::RowModel:
return QVariant::fromValue(_rows.at(index.row())); return QVariant::fromValue(_rows.at(index.row()));
case TreeModelRoles::ColumnModel:
return QVariant::fromValue(_columnSource.at(index.column()));
default: default:
break; break;
} }
@ -38,7 +40,10 @@ QVariant FluTreeModel::data(const QModelIndex &index, int role) const {
}; };
QHash<int, QByteArray> FluTreeModel::roleNames() const { QHash<int, QByteArray> FluTreeModel::roleNames() const {
return { {Qt::DisplayRole, "dataModel"} }; return {
{TreeModelRoles::RowModel, "rowModel"},
{TreeModelRoles::ColumnModel, "columnModel"}
};
}; };
void FluTreeModel::setData(QList<FluTreeNode*> data){ void FluTreeModel::setData(QList<FluTreeNode*> data){
@ -76,6 +81,11 @@ QObject* FluTreeModel::getRow(int row){
return _rows.at(row); return _rows.at(row);
} }
void FluTreeModel::setRow(int row,QVariantMap data){
_rows.at(row)->_data = data;
Q_EMIT dataChanged(index(row,0),index(row,columnCount()-1));
}
void FluTreeModel::checkRow(int row,bool checked){ void FluTreeModel::checkRow(int row,bool checked){
auto itemData = _rows.at(row); auto itemData = _rows.at(row);
if(itemData->hasChildren()){ if(itemData->hasChildren()){
@ -125,10 +135,9 @@ void FluTreeModel::setDataSource(QList<QMap<QString,QVariant>> data){
auto item = data.at(data.count()-1); auto item = data.at(data.count()-1);
data.pop_back(); data.pop_back();
FluTreeNode* node = new FluTreeNode(this); FluTreeNode* node = new FluTreeNode(this);
node->_title = item.value("title").toString();
node->_key = item.value("key").toString();
node->_depth = item.value("__depth").toInt(); node->_depth = item.value("__depth").toInt();
node->_parent = item.value("__parent").value<FluTreeNode*>(); node->_parent = item.value("__parent").value<FluTreeNode*>();
node->_data = item;
node->_isExpanded = true; node->_isExpanded = true;
if(node->_parent){ if(node->_parent){
node->_parent->_children.append(node); node->_parent->_children.append(node);
@ -201,110 +210,6 @@ void FluTreeModel::expand(int row){
insertRows(row+1,insertData); insertRows(row+1,insertData);
} }
void FluTreeModel::dragAndDrop(int dragIndex,int dropIndex,bool isDropTopArea){
if(dropIndex>_rows.count() || dropIndex<0){
return;
}
auto dragItem = _rows[dragIndex];
auto dropItem = _rows[dropIndex];
int targetIndex;
if(dropIndex > dragIndex){
if(isDropTopArea){
targetIndex = dropIndex;
}else{
targetIndex = dropIndex+1;
}
}else{
if(isDropTopArea){
targetIndex = dropIndex;
}else{
targetIndex = dropIndex+1;
}
}
if (!beginMoveRows(QModelIndex(), dragIndex, dragIndex, QModelIndex(), targetIndex)) {
return;
}
if(dropIndex > dragIndex){
if(isDropTopArea){
targetIndex = dropIndex-1;
}else{
targetIndex = dropIndex;
}
}else{
if(isDropTopArea){
targetIndex = dropIndex;
}else{
targetIndex = dropIndex+1;
}
}
_rows.move(dragIndex,targetIndex);
endMoveRows();
Q_EMIT layoutAboutToBeChanged();
if(dragItem->_parent == dropItem->_parent){
QList<FluTreeNode*>* children = &(dragItem->_parent->_children);
int srcIndex = children->indexOf(dragItem);
int destIndex = children->indexOf(dropItem);
if(dropIndex > dragIndex){
if(isDropTopArea){
targetIndex = destIndex-1;
}else{
targetIndex = destIndex;
}
}else{
if(isDropTopArea){
targetIndex = destIndex;
}else{
targetIndex = destIndex+1;
}
}
children->move(srcIndex,targetIndex);
}else{
QList<FluTreeNode*>* srcChildren = &(dragItem->_parent->_children);
QList<FluTreeNode*>* destChildren = &(dropItem->_parent->_children);
int srcIndex = srcChildren->indexOf(dragItem);
int destIndex = destChildren->indexOf(dropItem);
dragItem->_depth = dropItem->_depth;
dragItem->_parent = dropItem->_parent;
if(dragItem->hasChildren()){
QList<FluTreeNode*> stack = dragItem->_children;
foreach (auto node, stack) {
node->_depth = dragItem->_depth+1;
}
std::reverse(stack.begin(), stack.end());
while (stack.count() > 0) {
auto item = stack.at(stack.count()-1);
stack.pop_back();
QList<FluTreeNode*> children = item->_children;
if(!children.isEmpty()){
std::reverse(children.begin(), children.end());
foreach (auto c, children) {
c->_depth = item->_depth+1;
stack.append(c);
}
}
}
}
srcChildren->removeAt(srcIndex);
if(dropIndex > dragIndex){
if(isDropTopArea){
targetIndex = destIndex;
}else{
targetIndex = destIndex + 1;
}
}else{
if(isDropTopArea){
targetIndex = destIndex;
}else{
targetIndex = destIndex + 1;
}
}
destChildren->insert(targetIndex,dragItem);
}
changePersistentIndex(index(qMin(dragIndex,dropIndex),0),index(qMax(dragIndex,dropIndex),0));
Q_EMIT dataChanged(index(0,0),index(rowCount()-1,0));
}
bool FluTreeModel::hitHasChildrenExpanded(int row){ bool FluTreeModel::hitHasChildrenExpanded(int row){
auto itemData = _rows.at(row); auto itemData = _rows.at(row);
if(itemData->hasChildren() && itemData->_isExpanded){ if(itemData->hasChildren() && itemData->_isExpanded){

View File

@ -4,6 +4,7 @@
#include <QObject> #include <QObject>
#include <QAbstractTableModel> #include <QAbstractTableModel>
#include <QJsonArray> #include <QJsonArray>
#include <QVariant>
#include <QtQml/qqml.h> #include <QtQml/qqml.h>
#include "stdafx.h" #include "stdafx.h"
@ -12,17 +13,15 @@
*/ */
class FluTreeNode : public QObject{ class FluTreeNode : public QObject{
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString key READ key CONSTANT) Q_PROPERTY(QVariantMap data READ data CONSTANT)
Q_PROPERTY(QString title READ title CONSTANT)
Q_PROPERTY(int depth READ depth CONSTANT) Q_PROPERTY(int depth READ depth CONSTANT)
Q_PROPERTY(bool isExpanded READ isExpanded CONSTANT) Q_PROPERTY(bool isExpanded READ isExpanded CONSTANT)
Q_PROPERTY(bool checked READ checked CONSTANT) Q_PROPERTY(bool checked READ checked CONSTANT)
public: public:
explicit FluTreeNode(QObject *parent = nullptr); explicit FluTreeNode(QObject *parent = nullptr);
Q_INVOKABLE QString key(){return _key;};
Q_INVOKABLE QString title(){return _title;};
Q_INVOKABLE int depth(){return _depth;}; Q_INVOKABLE int depth(){return _depth;};
Q_INVOKABLE bool isExpanded(){return _isExpanded;}; Q_INVOKABLE bool isExpanded(){return _isExpanded;};
Q_INVOKABLE QVariantMap data(){return _data;};
Q_INVOKABLE bool hasChildren(){ return !_children.isEmpty();}; Q_INVOKABLE bool hasChildren(){ return !_children.isEmpty();};
Q_INVOKABLE bool hasNextNodeByIndex(int index){ Q_INVOKABLE bool hasNextNodeByIndex(int index){
FluTreeNode* p = this; FluTreeNode* p = this;
@ -70,11 +69,11 @@ public:
return true; return true;
} }
public: public:
QString _key="";
QString _title=""; QString _title="";
int _depth=0; int _depth=0;
bool _checked = false; bool _checked = false;
bool _isExpanded=true; bool _isExpanded=true;
QVariantMap _data;
QList<FluTreeNode*> _children; QList<FluTreeNode*> _children;
FluTreeNode* _parent = nullptr; FluTreeNode* _parent = nullptr;
}; };
@ -84,9 +83,14 @@ class FluTreeModel : public QAbstractItemModel
Q_OBJECT Q_OBJECT
Q_PROPERTY_AUTO(int,dataSourceSize) Q_PROPERTY_AUTO(int,dataSourceSize)
Q_PROPERTY_AUTO(QList<FluTreeNode*>,selectionModel) Q_PROPERTY_AUTO(QList<FluTreeNode*>,selectionModel)
Q_PROPERTY_AUTO(QList<QVariantMap>,columnSource)
QML_NAMED_ELEMENT(FluTreeModel) QML_NAMED_ELEMENT(FluTreeModel)
QML_ADDED_IN_MINOR_VERSION(1) QML_ADDED_IN_MINOR_VERSION(1)
public: public:
enum TreeModelRoles {
RowModel = 0x0101,
ColumnModel = 0x0102
};
explicit FluTreeModel(QObject *parent = nullptr); explicit FluTreeModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override;
@ -98,11 +102,11 @@ public:
Q_INVOKABLE void removeRows(int row,int count); Q_INVOKABLE void removeRows(int row,int count);
Q_INVOKABLE void insertRows(int row,QList<FluTreeNode*> data); Q_INVOKABLE void insertRows(int row,QList<FluTreeNode*> data);
Q_INVOKABLE QObject* getRow(int row); Q_INVOKABLE QObject* getRow(int row);
Q_INVOKABLE void setRow(int row,QVariantMap data);
Q_INVOKABLE void setData(QList<FluTreeNode*> data); Q_INVOKABLE void setData(QList<FluTreeNode*> data);
Q_INVOKABLE void setDataSource(QList<QMap<QString,QVariant>> data); Q_INVOKABLE void setDataSource(QList<QMap<QString,QVariant>> data);
Q_INVOKABLE void collapse(int row); Q_INVOKABLE void collapse(int row);
Q_INVOKABLE void expand(int row); Q_INVOKABLE void expand(int row);
Q_INVOKABLE void dragAndDrop(int dragIndex,int dropIndex,bool isDropTopArea);
Q_INVOKABLE FluTreeNode* getNode(int row); Q_INVOKABLE FluTreeNode* getNode(int row);
Q_INVOKABLE void refreshNode(int row); Q_INVOKABLE void refreshNode(int row);
Q_INVOKABLE void checkRow(int row,bool checked); Q_INVOKABLE void checkRow(int row,bool checked);

View File

@ -1,5 +1,7 @@
#include "FluWatermark.h" #include "FluWatermark.h"
#include "FluTextStyle.h"
FluWatermark::FluWatermark(QQuickItem* parent) : QQuickPaintedItem(parent){ FluWatermark::FluWatermark(QQuickItem* parent) : QQuickPaintedItem(parent){
gap(QPoint(100,100)); gap(QPoint(100,100));
offset(QPoint(_gap.x()/2,_gap.y()/2)); offset(QPoint(_gap.x()/2,_gap.y()/2));
@ -17,6 +19,7 @@ FluWatermark::FluWatermark(QQuickItem* parent) : QQuickPaintedItem(parent){
void FluWatermark::paint(QPainter* painter){ void FluWatermark::paint(QPainter* painter){
QFont font; QFont font;
font.setFamily(FluTextStyle::getInstance()->family());
font.setPixelSize(_textSize); font.setPixelSize(_textSize);
painter->setFont(font); painter->setFont(font);
painter->setPen(_textColor); painter->setPen(_textColor);

View File

@ -10,8 +10,7 @@ Item {
property real noiseOpacity: 0.02 property real noiseOpacity: 0.02
property alias target: effect_source.sourceItem property alias target: effect_source.sourceItem
property int blurRadius: 32 property int blurRadius: 32
property rect targetRect: Qt.rect(control.x, control.y, control.width, property rect targetRect: Qt.rect(control.x, control.y, control.width,control.height)
control.height)
ShaderEffectSource { ShaderEffectSource {
id: effect_source id: effect_source
anchors.fill: parent anchors.fill: parent

View File

@ -90,7 +90,7 @@ Rectangle {
if(!readOnly){ if(!readOnly){
editTextChaged(text_box.text) editTextChaged(text_box.text)
} }
tableView.closeEditor() control.closeEditor()
} }
} }
} }
@ -119,7 +119,7 @@ Rectangle {
if(!readOnly){ if(!readOnly){
editTextChaged(text_box.text) editTextChaged(text_box.text)
} }
tableView.closeEditor() control.closeEditor()
} }
} }
} }
@ -258,7 +258,7 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onPressed:{ onPressed:{
closeEditor() control.closeEditor()
} }
onCanceled: { onCanceled: {
} }
@ -275,7 +275,7 @@ Rectangle {
onClicked: onClicked:
(event)=>{ (event)=>{
d.current = rowObject d.current = rowObject
closeEditor() control.closeEditor()
event.accepted = true event.accepted = true
} }
} }
@ -381,6 +381,7 @@ Rectangle {
clip: true clip: true
onRowsChanged: { onRowsChanged: {
control.closeEditor() control.closeEditor()
table_view.flick(0,1)
} }
delegate: com_table_delegate delegate: com_table_delegate
FluLoader{ FluLoader{

View File

@ -5,283 +5,186 @@ import QtQuick.Controls 2.15
import Qt.labs.qmlmodels 1.0 import Qt.labs.qmlmodels 1.0
import FluentUI 1.0 import FluentUI 1.0
Item { Rectangle {
property int currentIndex : -1
property var dataSource property var dataSource
property var columnSource : []
property bool showLine: true property bool showLine: true
property bool draggable: false
property int cellHeight: 30 property int cellHeight: 30
property int depthPadding: 30 property int depthPadding: 15
property bool checkable: false 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) property color lineColor: FluTheme.dividerColor
property color borderColor: FluTheme.dark ? Qt.rgba(37/255,37/255,37/255,1) : Qt.rgba(228/255,228/255,228/255,1)
property color selectedBorderColor: FluTheme.primaryColor
property color selectedColor: FluTools.withOpacity(FluTheme.primaryColor,0.3)
readonly property alias current: d.current
id:control id:control
QtObject { color: FluTheme.dark ? Qt.rgba(39/255,39/255,39/255,1) : Qt.rgba(251/255,251/255,253/255,1)
id:d
property int dy
property var current
property int dropIndex: -1
property bool isDropTopArea: false
property int dragIndex: -1
property color hitColor: FluTheme.primaryColor
}
onDataSourceChanged: { onDataSourceChanged: {
tree_model.setDataSource(dataSource) tree_model.setDataSource(dataSource)
} }
onColumnSourceChanged: {
var columns= []
var headerRow = {}
columnSource.forEach(function(item){
var column = Qt.createQmlObject('import Qt.labs.qmlmodels 1.0;TableModelColumn{}',control);
column.display = item.dataIndex
columns.push(column)
headerRow[item.dataIndex] = item.title
})
header_column_model.columns = columns
header_column_model.rows = [headerRow]
}
FluTreeModel{ FluTreeModel{
id:tree_model id:tree_model
columnSource: control.columnSource
} }
ListView{ onDepthPaddingChanged: {
id:table_view table_view.forceLayout()
ScrollBar.horizontal: FluScrollBar{}
ScrollBar.vertical: FluScrollBar{}
boundsBehavior: Flickable.StopAtBounds
model: tree_model
anchors.fill: parent
clip: true
flickableDirection: Flickable.HorizontalAndVerticalFlick
contentWidth: contentItem.childrenRect.width
reuseItems: true
removeDisplaced : Transition{
ParallelAnimation{
NumberAnimation {
properties: "y"
duration: 167
from: d.dy + table_view.height
easing.type: Easing.OutCubic
} }
NumberAnimation { onCellHeightChanged: {
properties: "opacity" table_view.forceLayout()
duration: 83 }
from: 0 onCheckableChanged: {
to: 1 delay_force_layout.restart()
}
Timer{
id:delay_force_layout
interval: 30
onTriggered: {
table_view.forceLayout()
} }
} }
QtObject {
id:d
property var current
property int defaultItemWidth: 100
property int rowHoverIndex: -1
property var editDelegate
property var editPosition
function getEditDelegate(column){
var obj = control.columnSource[column].editDelegate
if(obj){
return obj
} }
move: Transition { if(control.columnSource[column].editMultiline === true){
NumberAnimation { property: "y"; duration: 200 } return com_edit_multiline
} }
add: Transition{ return com_edit
ParallelAnimation{
NumberAnimation {
properties: "y"
duration: 167
from: d.dy - control.cellHeight
easing.type: Easing.OutCubic
}
NumberAnimation {
properties: "opacity"
duration: 83
from: 0
to: 1
}
}
}
delegate: Item {
id:item_control
implicitWidth: item_loader_container.width
implicitHeight: item_loader_container.height
ListView.onReused: {
item_loader_container.item.reused()
}
ListView.onPooled: {
item_loader_container.item.pooled()
}
FluLoader{
property var itemControl: item_control
property var itemModel: dataModel
property int rowIndex: index
property bool isItemLoader: true
id:item_loader_container
sourceComponent: com_item_container
}
}
FluLoader{
id:loader_container
property var itemControl
property var itemModel
property bool isItemLoader: false
} }
} }
Component{ Component{
id:com_item_container id:com_edit
FluTextBox{
id:text_box
text: String(display)
readOnly: true === control.columnSource[column].readOnly
Component.onCompleted: {
forceActiveFocus()
selectAll()
}
onCommit: {
if(!readOnly){
editTextChaged(text_box.text)
}
control.closeEditor()
}
}
}
Component{
id:com_edit_multiline
Item{ Item{
signal reused anchors.fill: parent
signal pooled Flickable{
onReused: { id:item_scroll
clip: true
anchors.fill: parent
ScrollBar.vertical: multiline_text_srcoll_bar
boundsBehavior: Flickable.StopAtBounds
TextArea.flickable: FluMultilineTextBox {
id:text_box
text: String(display)
readOnly: true === control.columnSource[column].readOnly
verticalAlignment: TextInput.AlignVCenter
isCtrlEnterForNewline: true
Component.onCompleted: {
forceActiveFocus()
selectAll()
} }
onPooled: { rightPadding: 34
onCommit: {
if(!readOnly){
editTextChaged(text_box.text)
} }
property bool isCurrent: d.current === itemModel control.closeEditor()
id:item_container
width: {
var w = 46 + item_loader_cell.width + control.depthPadding*itemModel.depth
if(control.width>w){
return control.width
}
return w
}
height: control.cellHeight
implicitWidth: width
implicitHeight: height
function toggle(){
var pos = FluTools.cursorPos()
var viewPos = table_view.mapToGlobal(0,0)
d.dy = table_view.contentY + pos.y-viewPos.y
if(itemModel.isExpanded){
tree_model.collapse(rowIndex)
}else{
tree_model.expand(rowIndex)
} }
} }
Rectangle{ }
width: 3 FluIconButton{
height: 18 iconSource:FluentIcons.ChromeClose
radius: 1.5 iconSize: 10
color: FluTheme.primaryColor width: 20
visible: isCurrent height: 20
padding: 0
verticalPadding: 0
horizontalPadding: 0
visible: {
if(text_box.readOnly)
return false
return text_box.text !== ""
}
anchors{ anchors{
left: parent.left
leftMargin: 6
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: 15
} }
onClicked:{
text_box.text = ""
}
}
FluScrollBar{
id:multiline_text_srcoll_bar
anchors{
right: parent.right
rightMargin: 5
top: parent.top
bottom: parent.bottom
topMargin: 3
bottomMargin: 3
}
}
}
}
Component{
id:com_column
Item{
id:item_container
clip: true
function toggle(){
if(rowModel.isExpanded){
tree_model.collapse(row)
}else{
tree_model.expand(row)
}
delay_force_layout.restart()
} }
MouseArea{ MouseArea{
id:item_mouse id:item_mouse
property point clickPos: Qt.point(0,0) property point clickPos: Qt.point(0,0)
anchors.fill: parent 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)
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 = 0
loader_container.y = cellPosition.y
}
onClicked: { onClicked: {
d.current = itemModel d.current = rowModel
} }
onDoubleClicked: { onDoubleClicked: {
if(itemModel.hasChildren()){ if(rowModel.hasChildren()){
item_container.toggle() 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/control.cellHeight)
if(index<0 || index>table_view.count-1){
d.dropIndex = -1
return
}
if(tree_model.hitHasChildrenExpanded(index) && y>index*control.cellHeight+control.cellHeight/2){
d.dropIndex = index + 1
d.isDropTopArea = true
}else{
d.dropIndex = index
if(y>index*control.cellHeight+control.cellHeight/2){
d.isDropTopArea = false
}else{
d.isDropTopArea = true
}
}
}
onCanceled: {
loader_container.sourceComponent = undefined
loader_container.x = 0
loader_container.y = 0
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
loader_container.x = 0
loader_container.y = 0
}
}
Drag.active: item_mouse.drag.active
Rectangle{
id:item_line_drop_tip
anchors{
left: layout_row.left
leftMargin: 26
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{ FluRectangle{
width: 1 width: 1
color: control.lineColor color: control.lineColor
visible: control.showLine && isItemLoader && itemModel.depth !== 0 && !itemModel.hasChildren() visible: control.showLine && rowModel.depth !== 0 && !rowModel.hasChildren()
height: itemModel.hideLineFooter() ? parent.height/2 : parent.height height: rowModel.hideLineFooter() ? parent.height/2 : parent.height
anchors{ anchors{
top: parent.top top: parent.top
left: item_line_h.left left: item_line_h.left
@ -291,7 +194,7 @@ Item {
id:item_line_h id:item_line_h
height: 1 height: 1
color: control.lineColor color: control.lineColor
visible: control.showLine && isItemLoader && itemModel.depth !== 0 && !itemModel.hasChildren() visible: control.showLine && rowModel.depth !== 0 && !rowModel.hasChildren()
width: depthPadding - 10 width: depthPadding - 10
anchors{ anchors{
right: layout_row.left right: layout_row.left
@ -300,12 +203,12 @@ Item {
} }
} }
Repeater{ Repeater{
model: Math.max(itemModel.depth-1,0) model: Math.max(rowModel.depth-1,0)
delegate: FluRectangle{ delegate: FluRectangle{
required property int index required property int index
width: 1 width: 1
color: control.lineColor color: control.lineColor
visible: control.showLine && isItemLoader && itemModel.depth !== 0 && itemModel.hasNextNodeByIndex(index) visible: control.showLine && rowModel.depth !== 0 && rowModel.hasNextNodeByIndex(index)
anchors{ anchors{
top:parent.top top:parent.top
bottom: parent.bottom bottom: parent.bottom
@ -314,57 +217,34 @@ Item {
} }
} }
} }
Rectangle{
anchors.fill: parent
radius: 4
anchors.leftMargin: 6
anchors.rightMargin: 6
border.color: d.hitColor
border.width: d.dragIndex === rowIndex ? 1 : 0
color: {
if(isCurrent){
return FluTheme.itemCheckColor
}
if(item_mouse.containsMouse || item_check_box.hovered){
return FluTheme.itemHoverColor
}
if(item_loader_expand.item && item_loader_expand.item.hovered){
return FluTheme.itemHoverColor
}
return FluTheme.itemNormalColor
}
}
RowLayout{ RowLayout{
id:layout_row id:layout_row
height: parent.height height: parent.height
anchors.verticalCenter: parent.verticalCenter anchors.fill: parent
anchors.left: parent.left
spacing: 0 spacing: 0
anchors.leftMargin: 14 + control.depthPadding*itemModel.depth anchors.leftMargin: 14 + control.depthPadding*rowModel.depth
Component{ Component{
id:com_icon_btn id:com_icon_btn
FluIconButton{ FluIconButton{
opacity: itemModel.hasChildren() opacity: rowModel.hasChildren()
onClicked: { onClicked: {
item_container.toggle() item_container.toggle()
} }
contentItem:FluIcon{ contentItem:FluIcon{
rotation: itemModel.isExpanded?0:-90 rotation: rowModel.isExpanded?0:-90
iconSource:FluentIcons.ChevronDown iconSource:FluentIcons.ChevronDown
iconSize: 16 iconSize: 16
anchors.centerIn: parent anchors.centerIn: parent
} }
} }
} }
FluLoader{ FluLoader{
id:item_loader_expand id:item_loader_expand
Layout.preferredWidth: 20 Layout.preferredWidth: 20
Layout.preferredHeight: 20 Layout.preferredHeight: 20
sourceComponent: itemModel.hasChildren() ? com_icon_btn : undefined sourceComponent: rowModel.hasChildren() ? com_icon_btn : undefined
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
FluCheckBox{ FluCheckBox{
id:item_check_box id:item_check_box
Layout.preferredWidth: 18 Layout.preferredWidth: 18
@ -372,39 +252,453 @@ Item {
Layout.leftMargin: 5 Layout.leftMargin: 5
horizontalPadding:0 horizontalPadding:0
verticalPadding: 0 verticalPadding: 0
checked: itemModel.checked checked: rowModel.checked
animationEnabled:false animationEnabled:false
visible: control.checkable visible: control.checkable
padding: 0 padding: 0
clickListener: function(){ clickListener: function(){
tree_model.checkRow(rowIndex,!itemModel.checked) tree_model.checkRow(row,!rowModel.checked)
} }
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
Item{
Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: 6
FluText {
id:item_text
text: String(display)
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
MouseArea{
acceptedButtons: Qt.NoButton
id: hover_handler
hoverEnabled: true
anchors.fill: parent
}
FluTooltip{
text: item_text.text
delay: 500
visible: item_text.contentWidth < item_text.implicitWidth && hover_handler.containsMouse
}
}
}
}
}
}
Component{
id:com_other
FluText {
id:item_text
text: String(display)
elide: Text.ElideRight
wrapMode: Text.WrapAnywhere
anchors{
fill: parent
leftMargin: 11
rightMargin: 11
topMargin: 6
bottomMargin: 6
}
verticalAlignment: Text.AlignVCenter
MouseArea{
acceptedButtons: Qt.NoButton
id: hover_handler
hoverEnabled: true
anchors.fill: parent
}
FluTooltip{
text: item_text.text
delay: 500
visible: item_text.contentWidth < item_text.implicitWidth && item_text.contentHeight < item_text.implicitHeight && hover_handler.containsMouse
}
}
}
TableView{
id:table_view
ScrollBar.horizontal: FluScrollBar{}
ScrollBar.vertical: FluScrollBar{}
boundsBehavior: Flickable.StopAtBounds
model: tree_model
anchors{
top: header_horizontal.bottom
left: parent.left
right: parent.right
bottom: parent.bottom
}
clip: true
columnWidthProvider: function(column) {
var columnObject = control.columnSource[column]
var width = columnObject.width
if(width){
return width
}
var minimumWidth = columnObject.minimumWidth
if(minimumWidth){
return minimumWidth
}
return d.defaultItemWidth
}
rowHeightProvider: function(row) {
return control.cellHeight
}
delegate: MouseArea{
property var rowObject : rowModel.data
property alias isRowSelected: item_table_loader.isRowSelected
property var display: rowModel.data[columnModel.dataIndex]
property bool isObject: typeof(item_table.display) == "object"
property bool editVisible: {
if(rowObject && d.editPosition && d.editPosition._key === rowObject._key && d.editPosition.column === column){
return true
}
return false
}
implicitHeight: 30
implicitWidth: 30
id: item_table
hoverEnabled: true
onEntered: {
d.rowHoverIndex = row
}
onWidthChanged: {
if(editVisible){
updateEditPosition()
}
}
onHeightChanged: {
if(editVisible){
updateEditPosition()
}
}
onXChanged: {
if(editVisible){
updateEditPosition()
}
}
onYChanged: {
if(editVisible){
updateEditPosition()
}
}
function updateEditPosition(){
var obj = {}
obj._key = rowObject._key
obj.column = column
obj.row = row
obj.x = item_table.x
obj.y = item_table.y + 1
obj.width = item_table.width
obj.height = item_table.height - 2
d.editPosition = obj
}
onDoubleClicked:{
if(typeof(display) == "object"){
return
}
d.editDelegate = d.getEditDelegate(column)
updateEditPosition()
loader_edit.display = display
}
onClicked:
(event)=>{
d.current = rowModel
event.accepted = true
}
Rectangle{
anchors.fill: parent
color:{
if(item_table.isRowSelected){
return control.selectedColor
}
if(d.rowHoverIndex === row || item_table.isRowSelected){
return FluTheme.dark ? Qt.rgba(1,1,1,0.06) : Qt.rgba(0,0,0,0.06)
}
return (row%2!==0) ? control.color : (FluTheme.dark ? Qt.rgba(1,1,1,0.015) : Qt.rgba(0,0,0,0.015))
}
Item{
anchors.fill: parent
visible: item_table.isRowSelected
Rectangle{
width: 1
height: parent.height
anchors.left: parent.left
color: control.selectedBorderColor
visible: column === 0
}
Rectangle{
width: 1
height: parent.height
anchors.right: parent.right
color: control.selectedBorderColor
visible: column === control.columnSource.length-1
}
Rectangle{
width: parent.width
height: 1
anchors.top: parent.top
color: control.selectedBorderColor
}
Rectangle{
width: parent.width
height: 1
anchors.bottom: parent.bottom
color: control.selectedBorderColor
}
}
}
FluLoader{ FluLoader{
property var dataModel: itemModel anchors.fill: parent
property var itemMouse: item_mouse id:item_table_loader
id:item_loader_cell property var rowModel : model.rowModel
Layout.leftMargin: 10 property var columnModel : model.columnModel
Layout.preferredWidth: { property int row : model.row
if(item){ property int column: model.column
return item.width property var display: item_table.display
property bool isRowSelected: d.current === rowModel
property var options: {
if(isObject){
return display.options
}
return {}
}
sourceComponent: {
if(column === 0)
return com_column
if(item_table.isObject){
return item_table.display.comId
}
return com_other
}
}
}
FluLoader{
id:loader_edit
property var tableView: control
property var display
property int column: {
if(d.editPosition){
return d.editPosition.column
} }
return 0 return 0
} }
Layout.fillHeight: true property int row: {
sourceComponent:com_item_text if(d.editPosition){
return d.editPosition.row
}
return 0
}
signal editTextChaged(string text)
sourceComponent: d.editPosition ? d.editDelegate : undefined
onEditTextChaged:
(text)=>{
const obj = tree_model.getRow(row).data
obj[control.columnSource[column].dataIndex] = text
tree_model.setRow(row,obj)
}
width: {
if(d.editPosition){
return d.editPosition.width
}
return 0
}
height: {
if(d.editPosition){
return d.editPosition.height
}
return 0
}
x:{
if(d.editPosition){
return d.editPosition.x
}
return 0
}
y:{
if(d.editPosition){
return d.editPosition.y
}
return 0
}
z:999
}
}
TableModel{
id:header_column_model
TableModelColumn {}
}
Component{
id:com_column_text
FluText {
id: column_text
text: modelData
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
Component{
id:com_column_header_delegate
Rectangle{
id:column_item_control
readonly property real cellPadding: 8
property bool canceled: false
property int columnIndex: column
property var columnObject : control.columnSource[column]
implicitWidth: {
return (item_column_loader.item && item_column_loader.item.implicitWidth) + (cellPadding * 2)
}
implicitHeight: Math.max(36, (item_column_loader.item&&item_column_loader.item.implicitHeight) + (cellPadding * 2))
color: FluTheme.dark ? Qt.rgba(50/255,50/255,50/255,1) : Qt.rgba(247/255,247/255,247/255,1)
Rectangle{
border.color: control.borderColor
width: parent.width
height: 1
anchors.top: parent.top
color:"#00000000"
}
Rectangle{
border.color: control.borderColor
width: parent.width
height: 1
anchors.bottom: parent.bottom
color:"#00000000"
}
Rectangle{
border.color: control.borderColor
width: 1
height: parent.height
anchors.left: parent.left
color:"#00000000"
}
Rectangle{
border.color: control.borderColor
width: 1
height: parent.height
anchors.right: parent.right
color:"#00000000"
visible: column === table_view.columns - 1
}
MouseArea{
id:column_item_control_mouse
anchors.fill: parent
anchors.rightMargin: 6
hoverEnabled: true
onCanceled: {
column_item_control.canceled = true
}
onContainsMouseChanged: {
if(!containsMouse){
column_item_control.canceled = false
}
}
onClicked:
(event)=>{
}
}
FluLoader{
id:item_column_loader
property var itemModel: model
property var modelData: model.display
property var tableView: table_view
property var options:{
if(typeof(modelData) == "object"){
return modelData.options
}
return {}
}
property int column: column_item_control.columnIndex
width: parent.width
height: parent.height
sourceComponent: {
if(typeof(modelData) == "object"){
return modelData.comId
}
return com_column_text
}
}
MouseArea{
property point clickPos: "0,0"
height: parent.height
width: 6
anchors.right: parent.right
acceptedButtons: Qt.LeftButton
hoverEnabled: true
visible: !(columnObject.width === columnObject.minimumWidth && columnObject.width === columnObject.maximumWidth && columnObject.width)
cursorShape: Qt.SplitHCursor
preventStealing: true
onPressed :
(mouse)=>{
FluTools.setOverrideCursor(Qt.SplitHCursor)
clickPos = Qt.point(mouse.x, mouse.y)
}
onReleased:{
FluTools.restoreOverrideCursor()
}
onCanceled: {
FluTools.restoreOverrideCursor()
}
onPositionChanged:
(mouse)=>{
if(!pressed){
return
}
var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y)
var minimumWidth = columnObject.minimumWidth
var maximumWidth = columnObject.maximumWidth
var w = columnObject.width
if(!w){
w = d.defaultItemWidth
}
if(!minimumWidth){
minimumWidth = d.defaultItemWidth
}
if(!maximumWidth){
maximumWidth = 65535
}
columnObject.width = Math.min(Math.max(minimumWidth, w + delta.x),maximumWidth)
table_view.forceLayout()
} }
} }
} }
} }
TableView {
id: header_horizontal
model: header_column_model
anchors{
left: table_view.left
right: table_view.right
top: parent.top
}
height: Math.max(1, contentHeight)
boundsBehavior: Flickable.StopAtBounds
clip: true
syncDirection: Qt.Horizontal
columnWidthProvider: table_view.columnWidthProvider
syncView: table_view.rows === 0 ? null : table_view
onContentXChanged:{
timer_horizontal_force_layout.restart()
}
Timer{
id:timer_horizontal_force_layout
interval: 50
onTriggered: {
header_horizontal.forceLayout()
}
}
delegate: com_column_header_delegate
}
Component{ Component{
id:com_item_text id:com_item_text
Item{ Item{
width: item_text.width width: item_text.width
FluText { FluText {
id:item_text id:item_text
text: dataModel.title text: model.title
rightPadding: 14 rightPadding: 14
anchors.centerIn: parent anchors.centerIn: parent
color:{ color:{
@ -423,7 +717,7 @@ Item {
return tree_model.dataSourceSize return tree_model.dataSourceSize
} }
function visibleCount(){ function visibleCount(){
return table_view.count return table_view.rows
} }
function collapse(rowIndex){ function collapse(rowIndex){
tree_model.collapse(rowIndex) tree_model.collapse(rowIndex)
@ -437,4 +731,14 @@ Item {
function allCollapse(){ function allCollapse(){
tree_model.allCollapse() tree_model.allCollapse()
} }
function customItem(comId,options={}){
var o = {}
o.comId = comId
o.options = options
return o
}
function closeEditor(){
d.editPosition = undefined
d.editDelegate = undefined
}
} }

View File

@ -21,6 +21,24 @@ Module {
Property { name: "lighter"; type: "QColor" } Property { name: "lighter"; type: "QColor" }
Property { name: "lightest"; type: "QColor" } Property { name: "lightest"; type: "QColor" }
} }
Component {
name: "FluApp"
prototype: "QObject"
exports: ["FluentUI/FluApp 1.0"]
exportMetaObjectRevisions: [0]
Property { name: "useSystemAppBar"; type: "bool" }
Property { name: "windowIcon"; type: "string" }
Property { name: "locale"; type: "QLocale" }
Method {
name: "init"
Parameter { name: "target"; type: "QObject"; isPointer: true }
Parameter { name: "locale"; type: "QLocale" }
}
Method {
name: "init"
Parameter { name: "target"; type: "QObject"; isPointer: true }
}
}
Component { Component {
name: "FluCalendarViewType" name: "FluCalendarViewType"
exports: ["FluentUI/FluCalendarViewType 1.0"] exports: ["FluentUI/FluCalendarViewType 1.0"]
@ -50,6 +68,50 @@ Module {
Parameter { name: "code"; type: "string" } Parameter { name: "code"; type: "string" }
} }
} }
Component {
name: "FluColors"
prototype: "QObject"
exports: ["FluentUI/FluColors 1.0"]
exportMetaObjectRevisions: [0]
Property { name: "Transparent"; type: "QColor" }
Property { name: "Black"; type: "QColor" }
Property { name: "White"; type: "QColor" }
Property { name: "Grey10"; type: "QColor" }
Property { name: "Grey20"; type: "QColor" }
Property { name: "Grey30"; type: "QColor" }
Property { name: "Grey40"; type: "QColor" }
Property { name: "Grey50"; type: "QColor" }
Property { name: "Grey60"; type: "QColor" }
Property { name: "Grey70"; type: "QColor" }
Property { name: "Grey80"; type: "QColor" }
Property { name: "Grey90"; type: "QColor" }
Property { name: "Grey100"; type: "QColor" }
Property { name: "Grey110"; type: "QColor" }
Property { name: "Grey120"; type: "QColor" }
Property { name: "Grey130"; type: "QColor" }
Property { name: "Grey140"; type: "QColor" }
Property { name: "Grey150"; type: "QColor" }
Property { name: "Grey160"; type: "QColor" }
Property { name: "Grey170"; type: "QColor" }
Property { name: "Grey180"; type: "QColor" }
Property { name: "Grey190"; type: "QColor" }
Property { name: "Grey200"; type: "QColor" }
Property { name: "Grey210"; type: "QColor" }
Property { name: "Grey220"; type: "QColor" }
Property { name: "Yellow"; type: "FluAccentColor"; isPointer: true }
Property { name: "Orange"; type: "FluAccentColor"; isPointer: true }
Property { name: "Red"; type: "FluAccentColor"; isPointer: true }
Property { name: "Magenta"; type: "FluAccentColor"; isPointer: true }
Property { name: "Purple"; type: "FluAccentColor"; isPointer: true }
Property { name: "Blue"; type: "FluAccentColor"; isPointer: true }
Property { name: "Teal"; type: "FluAccentColor"; isPointer: true }
Property { name: "Green"; type: "FluAccentColor"; isPointer: true }
Method {
name: "createAccentColor"
type: "FluAccentColor*"
Parameter { name: "primaryColor"; type: "QColor" }
}
}
Component { Component {
name: "FluContentDialogType" name: "FluContentDialogType"
exports: ["FluentUI/FluContentDialogType 1.0"] exports: ["FluentUI/FluContentDialogType 1.0"]
@ -226,6 +288,49 @@ Module {
Parameter { name: "filter"; type: "QJSValue" } Parameter { name: "filter"; type: "QJSValue" }
} }
} }
Component {
name: "FluTextStyle"
prototype: "QObject"
exports: ["FluentUI/FluTextStyle 1.0"]
exportMetaObjectRevisions: [0]
Property { name: "family"; type: "string" }
Property { name: "Caption"; type: "QFont" }
Property { name: "Body"; type: "QFont" }
Property { name: "BodyStrong"; type: "QFont" }
Property { name: "Subtitle"; type: "QFont" }
Property { name: "Title"; type: "QFont" }
Property { name: "TitleLarge"; type: "QFont" }
Property { name: "Display"; type: "QFont" }
}
Component {
name: "FluTheme"
prototype: "QObject"
exports: ["FluentUI/FluTheme 1.0"]
exportMetaObjectRevisions: [0]
Property { name: "dark"; type: "bool"; isReadonly: true }
Property { name: "accentColor"; type: "FluAccentColor"; isPointer: true }
Property { name: "primaryColor"; type: "QColor" }
Property { name: "backgroundColor"; type: "QColor" }
Property { name: "dividerColor"; type: "QColor" }
Property { name: "windowBackgroundColor"; type: "QColor" }
Property { name: "windowActiveBackgroundColor"; type: "QColor" }
Property { name: "fontPrimaryColor"; type: "QColor" }
Property { name: "fontSecondaryColor"; type: "QColor" }
Property { name: "fontTertiaryColor"; type: "QColor" }
Property { name: "itemNormalColor"; type: "QColor" }
Property { name: "itemHoverColor"; type: "QColor" }
Property { name: "itemPressColor"; type: "QColor" }
Property { name: "itemCheckColor"; type: "QColor" }
Property { name: "darkMode"; type: "int" }
Property { name: "nativeText"; type: "bool" }
Property { name: "animationEnabled"; type: "bool" }
Method {
name: "awesomeList"
type: "QJsonArray"
Parameter { name: "keyword"; type: "string" }
}
Method { name: "awesomeList"; type: "QJsonArray" }
}
Component { Component {
name: "FluThemeType" name: "FluThemeType"
exports: ["FluentUI/FluThemeType 1.0"] exports: ["FluentUI/FluThemeType 1.0"]
@ -267,6 +372,115 @@ Module {
} }
} }
} }
Component {
name: "FluTools"
prototype: "QObject"
exports: ["FluentUI/FluTools 1.0"]
exportMetaObjectRevisions: [0]
Method { name: "qtMajor"; type: "int" }
Method { name: "qtMinor"; type: "int" }
Method { name: "isMacos"; type: "bool" }
Method { name: "isLinux"; type: "bool" }
Method { name: "isWin"; type: "bool" }
Method {
name: "clipText"
Parameter { name: "text"; type: "string" }
}
Method { name: "uuid"; type: "string" }
Method {
name: "readFile"
type: "string"
Parameter { name: "fileName"; type: "string" }
}
Method {
name: "setQuitOnLastWindowClosed"
Parameter { name: "val"; type: "bool" }
}
Method {
name: "setOverrideCursor"
Parameter { name: "shape"; type: "Qt::CursorShape" }
}
Method { name: "restoreOverrideCursor" }
Method {
name: "html2PlantText"
type: "string"
Parameter { name: "html"; type: "string" }
}
Method {
name: "toLocalPath"
type: "string"
Parameter { name: "url"; type: "QUrl" }
}
Method {
name: "deleteLater"
Parameter { name: "p"; type: "QObject"; isPointer: true }
}
Method {
name: "getFileNameByUrl"
type: "string"
Parameter { name: "url"; type: "QUrl" }
}
Method { name: "getVirtualGeometry"; type: "QRect" }
Method { name: "getApplicationDirPath"; type: "string" }
Method {
name: "getUrlByFilePath"
type: "QUrl"
Parameter { name: "path"; type: "string" }
}
Method {
name: "withOpacity"
type: "QColor"
Parameter { type: "QColor" }
Parameter { name: "alpha"; type: "double" }
}
Method {
name: "md5"
type: "string"
Parameter { name: "text"; type: "string" }
}
Method {
name: "sha256"
type: "string"
Parameter { name: "text"; type: "string" }
}
Method {
name: "toBase64"
type: "string"
Parameter { name: "text"; type: "string" }
}
Method {
name: "fromBase64"
type: "string"
Parameter { name: "text"; type: "string" }
}
Method {
name: "removeDir"
type: "bool"
Parameter { name: "dirPath"; type: "string" }
}
Method {
name: "removeFile"
type: "bool"
Parameter { name: "filePath"; type: "string" }
}
Method {
name: "showFileInFolder"
Parameter { name: "path"; type: "string" }
}
Method { name: "isSoftware"; type: "bool" }
Method { name: "currentTimestamp"; type: "qlonglong" }
Method { name: "cursorPos"; type: "QPoint" }
Method { name: "windowIcon"; type: "QIcon" }
Method { name: "cursorScreenIndex"; type: "int" }
Method { name: "windowBuildNumber"; type: "int" }
Method { name: "isWindows11OrGreater"; type: "bool" }
Method { name: "isWindows10OrGreater"; type: "bool" }
Method {
name: "desktopAvailableGeometry"
type: "QRect"
Parameter { name: "window"; type: "QQuickWindow"; isPointer: true }
}
}
Component { Component {
name: "FluTreeModel" name: "FluTreeModel"
prototype: "QAbstractItemModel" prototype: "QAbstractItemModel"
@ -274,6 +488,7 @@ Module {
exportMetaObjectRevisions: [0] exportMetaObjectRevisions: [0]
Property { name: "dataSourceSize"; type: "int" } Property { name: "dataSourceSize"; type: "int" }
Property { name: "selectionModel"; type: "QList<FluTreeNode*>" } Property { name: "selectionModel"; type: "QList<FluTreeNode*>" }
Property { name: "columnSource"; type: "QList<QVariantMap>" }
Method { Method {
name: "removeRows" name: "removeRows"
Parameter { name: "row"; type: "int" } Parameter { name: "row"; type: "int" }
@ -305,12 +520,6 @@ Module {
name: "expand" name: "expand"
Parameter { name: "row"; type: "int" } Parameter { name: "row"; type: "int" }
} }
Method {
name: "dragAndDrop"
Parameter { name: "dragIndex"; type: "int" }
Parameter { name: "dropIndex"; type: "int" }
Parameter { name: "isDropTopArea"; type: "bool" }
}
Method { Method {
name: "getNode" name: "getNode"
type: "FluTreeNode*" type: "FluTreeNode*"
@ -2242,7 +2451,7 @@ Module {
} }
Property { Property {
name: "layoutMacosButtons" name: "layoutMacosButtons"
type: "FluLoader_QMLTYPE_13" type: "FluLoader_QMLTYPE_12"
isReadonly: true isReadonly: true
isPointer: true isPointer: true
} }
@ -2932,15 +3141,15 @@ Module {
defaultProperty: "data" defaultProperty: "data"
Property { name: "logo"; type: "QUrl" } Property { name: "logo"; type: "QUrl" }
Property { name: "title"; type: "string" } Property { name: "title"; type: "string" }
Property { name: "items"; type: "FluObject_QMLTYPE_137"; isPointer: true } Property { name: "items"; type: "FluObject_QMLTYPE_172"; isPointer: true }
Property { name: "footerItems"; type: "FluObject_QMLTYPE_137"; isPointer: true } Property { name: "footerItems"; type: "FluObject_QMLTYPE_172"; isPointer: true }
Property { name: "displayMode"; type: "int" } Property { name: "displayMode"; type: "int" }
Property { name: "autoSuggestBox"; type: "QQmlComponent"; isPointer: true } Property { name: "autoSuggestBox"; type: "QQmlComponent"; isPointer: true }
Property { name: "actionItem"; type: "QQmlComponent"; isPointer: true } Property { name: "actionItem"; type: "QQmlComponent"; isPointer: true }
Property { name: "topPadding"; type: "int" } Property { name: "topPadding"; type: "int" }
Property { name: "pageMode"; type: "int" } Property { name: "pageMode"; type: "int" }
Property { name: "navItemRightMenu"; type: "FluMenu_QMLTYPE_38"; isPointer: true } Property { name: "navItemRightMenu"; type: "FluMenu_QMLTYPE_42"; isPointer: true }
Property { name: "navItemExpanderRightMenu"; type: "FluMenu_QMLTYPE_38"; isPointer: true } Property { name: "navItemExpanderRightMenu"; type: "FluMenu_QMLTYPE_42"; isPointer: true }
Property { name: "navCompactWidth"; type: "int" } Property { name: "navCompactWidth"; type: "int" }
Property { name: "navTopMargin"; type: "int" } Property { name: "navTopMargin"; type: "int" }
Property { name: "cellHeight"; type: "int" } Property { name: "cellHeight"; type: "int" }

View File

@ -10,8 +10,7 @@ Item {
property real noiseOpacity: 0.02 property real noiseOpacity: 0.02
property alias target: effect_source.sourceItem property alias target: effect_source.sourceItem
property int blurRadius: 32 property int blurRadius: 32
property rect targetRect: Qt.rect(control.x, control.y, control.width, property rect targetRect: Qt.rect(control.x, control.y, control.width,control.height)
control.height)
ShaderEffectSource { ShaderEffectSource {
id: effect_source id: effect_source
anchors.fill: parent anchors.fill: parent

View File

@ -91,7 +91,7 @@ Rectangle {
if(!readOnly){ if(!readOnly){
editTextChaged(text_box.text) editTextChaged(text_box.text)
} }
tableView.closeEditor() control.closeEditor()
} }
} }
} }
@ -120,7 +120,7 @@ Rectangle {
if(!readOnly){ if(!readOnly){
editTextChaged(text_box.text) editTextChaged(text_box.text)
} }
tableView.closeEditor() control.closeEditor()
} }
} }
} }
@ -259,7 +259,7 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onPressed:{ onPressed:{
closeEditor() control.closeEditor()
} }
onCanceled: { onCanceled: {
} }
@ -276,7 +276,7 @@ Rectangle {
onClicked: onClicked:
(event)=>{ (event)=>{
d.current = rowObject d.current = rowObject
closeEditor() control.closeEditor()
event.accepted = true event.accepted = true
} }
} }
@ -382,6 +382,7 @@ Rectangle {
clip: true clip: true
onRowsChanged: { onRowsChanged: {
control.closeEditor() control.closeEditor()
table_view.flick(0,1)
} }
delegate: com_table_delegate delegate: com_table_delegate
FluLoader{ FluLoader{

View File

@ -5,283 +5,186 @@ import QtQuick.Controls
import Qt.labs.qmlmodels import Qt.labs.qmlmodels
import FluentUI import FluentUI
Item { Rectangle {
property int currentIndex : -1
property var dataSource property var dataSource
property var columnSource : []
property bool showLine: true property bool showLine: true
property bool draggable: false
property int cellHeight: 30 property int cellHeight: 30
property int depthPadding: 30 property int depthPadding: 15
property bool checkable: false 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) property color lineColor: FluTheme.dividerColor
property color borderColor: FluTheme.dark ? Qt.rgba(37/255,37/255,37/255,1) : Qt.rgba(228/255,228/255,228/255,1)
property color selectedBorderColor: FluTheme.primaryColor
property color selectedColor: FluTools.withOpacity(FluTheme.primaryColor,0.3)
readonly property alias current: d.current
id:control id:control
QtObject { color: FluTheme.dark ? Qt.rgba(39/255,39/255,39/255,1) : Qt.rgba(251/255,251/255,253/255,1)
id:d
property int dy
property var current
property int dropIndex: -1
property bool isDropTopArea: false
property int dragIndex: -1
property color hitColor: FluTheme.primaryColor
}
onDataSourceChanged: { onDataSourceChanged: {
tree_model.setDataSource(dataSource) tree_model.setDataSource(dataSource)
} }
onColumnSourceChanged: {
var columns= []
var headerRow = {}
columnSource.forEach(function(item){
var column = Qt.createQmlObject('import Qt.labs.qmlmodels 1.0;TableModelColumn{}',control);
column.display = item.dataIndex
columns.push(column)
headerRow[item.dataIndex] = item.title
})
header_column_model.columns = columns
header_column_model.rows = [headerRow]
}
FluTreeModel{ FluTreeModel{
id:tree_model id:tree_model
columnSource: control.columnSource
} }
ListView{ onDepthPaddingChanged: {
id:table_view table_view.forceLayout()
ScrollBar.horizontal: FluScrollBar{}
ScrollBar.vertical: FluScrollBar{}
boundsBehavior: Flickable.StopAtBounds
model: tree_model
anchors.fill: parent
clip: true
flickableDirection: Flickable.HorizontalAndVerticalFlick
contentWidth: contentItem.childrenRect.width
reuseItems: true
removeDisplaced : Transition{
ParallelAnimation{
NumberAnimation {
properties: "y"
duration: 167
from: d.dy + table_view.height
easing.type: Easing.OutCubic
} }
NumberAnimation { onCellHeightChanged: {
properties: "opacity" table_view.forceLayout()
duration: 83 }
from: 0 onCheckableChanged: {
to: 1 delay_force_layout.restart()
}
Timer{
id:delay_force_layout
interval: 30
onTriggered: {
table_view.forceLayout()
} }
} }
QtObject {
id:d
property var current
property int defaultItemWidth: 100
property int rowHoverIndex: -1
property var editDelegate
property var editPosition
function getEditDelegate(column){
var obj = control.columnSource[column].editDelegate
if(obj){
return obj
} }
move: Transition { if(control.columnSource[column].editMultiline === true){
NumberAnimation { property: "y"; duration: 200 } return com_edit_multiline
} }
add: Transition{ return com_edit
ParallelAnimation{
NumberAnimation {
properties: "y"
duration: 167
from: d.dy - control.cellHeight
easing.type: Easing.OutCubic
}
NumberAnimation {
properties: "opacity"
duration: 83
from: 0
to: 1
}
}
}
delegate: Item {
id:item_control
implicitWidth: item_loader_container.width
implicitHeight: item_loader_container.height
ListView.onReused: {
item_loader_container.item.reused()
}
ListView.onPooled: {
item_loader_container.item.pooled()
}
FluLoader{
property var itemControl: item_control
property var itemModel: dataModel
property int rowIndex: index
property bool isItemLoader: true
id:item_loader_container
sourceComponent: com_item_container
}
}
FluLoader{
id:loader_container
property var itemControl
property var itemModel
property bool isItemLoader: false
} }
} }
Component{ Component{
id:com_item_container id:com_edit
FluTextBox{
id:text_box
text: String(display)
readOnly: true === control.columnSource[column].readOnly
Component.onCompleted: {
forceActiveFocus()
selectAll()
}
onCommit: {
if(!readOnly){
editTextChaged(text_box.text)
}
control.closeEditor()
}
}
}
Component{
id:com_edit_multiline
Item{ Item{
signal reused anchors.fill: parent
signal pooled Flickable{
onReused: { id:item_scroll
clip: true
anchors.fill: parent
ScrollBar.vertical: multiline_text_srcoll_bar
boundsBehavior: Flickable.StopAtBounds
TextArea.flickable: FluMultilineTextBox {
id:text_box
text: String(display)
readOnly: true === control.columnSource[column].readOnly
verticalAlignment: TextInput.AlignVCenter
isCtrlEnterForNewline: true
Component.onCompleted: {
forceActiveFocus()
selectAll()
} }
onPooled: { rightPadding: 34
onCommit: {
if(!readOnly){
editTextChaged(text_box.text)
} }
property bool isCurrent: d.current === itemModel control.closeEditor()
id:item_container
width: {
var w = 46 + item_loader_cell.width + control.depthPadding*itemModel.depth
if(control.width>w){
return control.width
}
return w
}
height: control.cellHeight
implicitWidth: width
implicitHeight: height
function toggle(){
var pos = FluTools.cursorPos()
var viewPos = table_view.mapToGlobal(0,0)
d.dy = table_view.contentY + pos.y-viewPos.y
if(itemModel.isExpanded){
tree_model.collapse(rowIndex)
}else{
tree_model.expand(rowIndex)
} }
} }
Rectangle{ }
width: 3 FluIconButton{
height: 18 iconSource:FluentIcons.ChromeClose
radius: 1.5 iconSize: 10
color: FluTheme.primaryColor width: 20
visible: isCurrent height: 20
padding: 0
verticalPadding: 0
horizontalPadding: 0
visible: {
if(text_box.readOnly)
return false
return text_box.text !== ""
}
anchors{ anchors{
left: parent.left
leftMargin: 6
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: 15
} }
onClicked:{
text_box.text = ""
}
}
FluScrollBar{
id:multiline_text_srcoll_bar
anchors{
right: parent.right
rightMargin: 5
top: parent.top
bottom: parent.bottom
topMargin: 3
bottomMargin: 3
}
}
}
}
Component{
id:com_column
Item{
id:item_container
clip: true
function toggle(){
if(rowModel.isExpanded){
tree_model.collapse(row)
}else{
tree_model.expand(row)
}
delay_force_layout.restart()
} }
MouseArea{ MouseArea{
id:item_mouse id:item_mouse
property point clickPos: Qt.point(0,0) property point clickPos: Qt.point(0,0)
anchors.fill: parent 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)
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 = 0
loader_container.y = cellPosition.y
}
onClicked: { onClicked: {
d.current = itemModel d.current = rowModel
} }
onDoubleClicked: { onDoubleClicked: {
if(itemModel.hasChildren()){ if(rowModel.hasChildren()){
item_container.toggle() 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/control.cellHeight)
if(index<0 || index>table_view.count-1){
d.dropIndex = -1
return
}
if(tree_model.hitHasChildrenExpanded(index) && y>index*control.cellHeight+control.cellHeight/2){
d.dropIndex = index + 1
d.isDropTopArea = true
}else{
d.dropIndex = index
if(y>index*control.cellHeight+control.cellHeight/2){
d.isDropTopArea = false
}else{
d.isDropTopArea = true
}
}
}
onCanceled: {
loader_container.sourceComponent = undefined
loader_container.x = 0
loader_container.y = 0
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
loader_container.x = 0
loader_container.y = 0
}
}
Drag.active: item_mouse.drag.active
Rectangle{
id:item_line_drop_tip
anchors{
left: layout_row.left
leftMargin: 26
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{ FluRectangle{
width: 1 width: 1
color: control.lineColor color: control.lineColor
visible: control.showLine && isItemLoader && itemModel.depth !== 0 && !itemModel.hasChildren() visible: control.showLine && rowModel.depth !== 0 && !rowModel.hasChildren()
height: itemModel.hideLineFooter() ? parent.height/2 : parent.height height: rowModel.hideLineFooter() ? parent.height/2 : parent.height
anchors{ anchors{
top: parent.top top: parent.top
left: item_line_h.left left: item_line_h.left
@ -291,7 +194,7 @@ Item {
id:item_line_h id:item_line_h
height: 1 height: 1
color: control.lineColor color: control.lineColor
visible: control.showLine && isItemLoader && itemModel.depth !== 0 && !itemModel.hasChildren() visible: control.showLine && rowModel.depth !== 0 && !rowModel.hasChildren()
width: depthPadding - 10 width: depthPadding - 10
anchors{ anchors{
right: layout_row.left right: layout_row.left
@ -300,12 +203,12 @@ Item {
} }
} }
Repeater{ Repeater{
model: Math.max(itemModel.depth-1,0) model: Math.max(rowModel.depth-1,0)
delegate: FluRectangle{ delegate: FluRectangle{
required property int index required property int index
width: 1 width: 1
color: control.lineColor color: control.lineColor
visible: control.showLine && isItemLoader && itemModel.depth !== 0 && itemModel.hasNextNodeByIndex(index) visible: control.showLine && rowModel.depth !== 0 && rowModel.hasNextNodeByIndex(index)
anchors{ anchors{
top:parent.top top:parent.top
bottom: parent.bottom bottom: parent.bottom
@ -314,57 +217,34 @@ Item {
} }
} }
} }
Rectangle{
anchors.fill: parent
radius: 4
anchors.leftMargin: 6
anchors.rightMargin: 6
border.color: d.hitColor
border.width: d.dragIndex === rowIndex ? 1 : 0
color: {
if(isCurrent){
return FluTheme.itemCheckColor
}
if(item_mouse.containsMouse || item_check_box.hovered){
return FluTheme.itemHoverColor
}
if(item_loader_expand.item && item_loader_expand.item.hovered){
return FluTheme.itemHoverColor
}
return FluTheme.itemNormalColor
}
}
RowLayout{ RowLayout{
id:layout_row id:layout_row
height: parent.height height: parent.height
anchors.verticalCenter: parent.verticalCenter anchors.fill: parent
anchors.left: parent.left
spacing: 0 spacing: 0
anchors.leftMargin: 14 + control.depthPadding*itemModel.depth anchors.leftMargin: 14 + control.depthPadding*rowModel.depth
Component{ Component{
id:com_icon_btn id:com_icon_btn
FluIconButton{ FluIconButton{
opacity: itemModel.hasChildren() opacity: rowModel.hasChildren()
onClicked: { onClicked: {
item_container.toggle() item_container.toggle()
} }
contentItem:FluIcon{ contentItem:FluIcon{
rotation: itemModel.isExpanded?0:-90 rotation: rowModel.isExpanded?0:-90
iconSource:FluentIcons.ChevronDown iconSource:FluentIcons.ChevronDown
iconSize: 16 iconSize: 16
anchors.centerIn: parent anchors.centerIn: parent
} }
} }
} }
FluLoader{ FluLoader{
id:item_loader_expand id:item_loader_expand
Layout.preferredWidth: 20 Layout.preferredWidth: 20
Layout.preferredHeight: 20 Layout.preferredHeight: 20
sourceComponent: itemModel.hasChildren() ? com_icon_btn : undefined sourceComponent: rowModel.hasChildren() ? com_icon_btn : undefined
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
FluCheckBox{ FluCheckBox{
id:item_check_box id:item_check_box
Layout.preferredWidth: 18 Layout.preferredWidth: 18
@ -372,39 +252,453 @@ Item {
Layout.leftMargin: 5 Layout.leftMargin: 5
horizontalPadding:0 horizontalPadding:0
verticalPadding: 0 verticalPadding: 0
checked: itemModel.checked checked: rowModel.checked
animationEnabled:false animationEnabled:false
visible: control.checkable visible: control.checkable
padding: 0 padding: 0
clickListener: function(){ clickListener: function(){
tree_model.checkRow(rowIndex,!itemModel.checked) tree_model.checkRow(row,!rowModel.checked)
} }
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
Item{
Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: 6
FluText {
id:item_text
text: String(display)
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
MouseArea{
acceptedButtons: Qt.NoButton
id: hover_handler
hoverEnabled: true
anchors.fill: parent
}
FluTooltip{
text: item_text.text
delay: 500
visible: item_text.contentWidth < item_text.implicitWidth && hover_handler.containsMouse
}
}
}
}
}
}
Component{
id:com_other
FluText {
id:item_text
text: String(display)
elide: Text.ElideRight
wrapMode: Text.WrapAnywhere
anchors{
fill: parent
leftMargin: 11
rightMargin: 11
topMargin: 6
bottomMargin: 6
}
verticalAlignment: Text.AlignVCenter
MouseArea{
acceptedButtons: Qt.NoButton
id: hover_handler
hoverEnabled: true
anchors.fill: parent
}
FluTooltip{
text: item_text.text
delay: 500
visible: item_text.contentWidth < item_text.implicitWidth && item_text.contentHeight < item_text.implicitHeight && hover_handler.containsMouse
}
}
}
TableView{
id:table_view
ScrollBar.horizontal: FluScrollBar{}
ScrollBar.vertical: FluScrollBar{}
boundsBehavior: Flickable.StopAtBounds
model: tree_model
anchors{
top: header_horizontal.bottom
left: parent.left
right: parent.right
bottom: parent.bottom
}
clip: true
columnWidthProvider: function(column) {
var columnObject = control.columnSource[column]
var width = columnObject.width
if(width){
return width
}
var minimumWidth = columnObject.minimumWidth
if(minimumWidth){
return minimumWidth
}
return d.defaultItemWidth
}
rowHeightProvider: function(row) {
return control.cellHeight
}
delegate: MouseArea{
property var rowObject : rowModel.data
property alias isRowSelected: item_table_loader.isRowSelected
property var display: rowModel.data[columnModel.dataIndex]
property bool isObject: typeof(item_table.display) == "object"
property bool editVisible: {
if(rowObject && d.editPosition && d.editPosition._key === rowObject._key && d.editPosition.column === column){
return true
}
return false
}
implicitHeight: 30
implicitWidth: 30
id: item_table
hoverEnabled: true
onEntered: {
d.rowHoverIndex = row
}
onWidthChanged: {
if(editVisible){
updateEditPosition()
}
}
onHeightChanged: {
if(editVisible){
updateEditPosition()
}
}
onXChanged: {
if(editVisible){
updateEditPosition()
}
}
onYChanged: {
if(editVisible){
updateEditPosition()
}
}
function updateEditPosition(){
var obj = {}
obj._key = rowObject._key
obj.column = column
obj.row = row
obj.x = item_table.x
obj.y = item_table.y + 1
obj.width = item_table.width
obj.height = item_table.height - 2
d.editPosition = obj
}
onDoubleClicked:{
if(typeof(display) == "object"){
return
}
d.editDelegate = d.getEditDelegate(column)
updateEditPosition()
loader_edit.display = display
}
onClicked:
(event)=>{
d.current = rowModel
event.accepted = true
}
Rectangle{
anchors.fill: parent
color:{
if(item_table.isRowSelected){
return control.selectedColor
}
if(d.rowHoverIndex === row || item_table.isRowSelected){
return FluTheme.dark ? Qt.rgba(1,1,1,0.06) : Qt.rgba(0,0,0,0.06)
}
return (row%2!==0) ? control.color : (FluTheme.dark ? Qt.rgba(1,1,1,0.015) : Qt.rgba(0,0,0,0.015))
}
Item{
anchors.fill: parent
visible: item_table.isRowSelected
Rectangle{
width: 1
height: parent.height
anchors.left: parent.left
color: control.selectedBorderColor
visible: column === 0
}
Rectangle{
width: 1
height: parent.height
anchors.right: parent.right
color: control.selectedBorderColor
visible: column === control.columnSource.length-1
}
Rectangle{
width: parent.width
height: 1
anchors.top: parent.top
color: control.selectedBorderColor
}
Rectangle{
width: parent.width
height: 1
anchors.bottom: parent.bottom
color: control.selectedBorderColor
}
}
}
FluLoader{ FluLoader{
property var dataModel: itemModel anchors.fill: parent
property var itemMouse: item_mouse id:item_table_loader
id:item_loader_cell property var rowModel : model.rowModel
Layout.leftMargin: 10 property var columnModel : model.columnModel
Layout.preferredWidth: { property int row : model.row
if(item){ property int column: model.column
return item.width property var display: item_table.display
property bool isRowSelected: d.current === rowModel
property var options: {
if(isObject){
return display.options
}
return {}
}
sourceComponent: {
if(column === 0)
return com_column
if(item_table.isObject){
return item_table.display.comId
}
return com_other
}
}
}
FluLoader{
id:loader_edit
property var tableView: control
property var display
property int column: {
if(d.editPosition){
return d.editPosition.column
} }
return 0 return 0
} }
Layout.fillHeight: true property int row: {
sourceComponent:com_item_text if(d.editPosition){
return d.editPosition.row
}
return 0
}
signal editTextChaged(string text)
sourceComponent: d.editPosition ? d.editDelegate : undefined
onEditTextChaged:
(text)=>{
const obj = tree_model.getRow(row).data
obj[control.columnSource[column].dataIndex] = text
tree_model.setRow(row,obj)
}
width: {
if(d.editPosition){
return d.editPosition.width
}
return 0
}
height: {
if(d.editPosition){
return d.editPosition.height
}
return 0
}
x:{
if(d.editPosition){
return d.editPosition.x
}
return 0
}
y:{
if(d.editPosition){
return d.editPosition.y
}
return 0
}
z:999
}
}
TableModel{
id:header_column_model
TableModelColumn {}
}
Component{
id:com_column_text
FluText {
id: column_text
text: modelData
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
Component{
id:com_column_header_delegate
Rectangle{
id:column_item_control
readonly property real cellPadding: 8
property bool canceled: false
property int columnIndex: column
property var columnObject : control.columnSource[column]
implicitWidth: {
return (item_column_loader.item && item_column_loader.item.implicitWidth) + (cellPadding * 2)
}
implicitHeight: Math.max(36, (item_column_loader.item&&item_column_loader.item.implicitHeight) + (cellPadding * 2))
color: FluTheme.dark ? Qt.rgba(50/255,50/255,50/255,1) : Qt.rgba(247/255,247/255,247/255,1)
Rectangle{
border.color: control.borderColor
width: parent.width
height: 1
anchors.top: parent.top
color:"#00000000"
}
Rectangle{
border.color: control.borderColor
width: parent.width
height: 1
anchors.bottom: parent.bottom
color:"#00000000"
}
Rectangle{
border.color: control.borderColor
width: 1
height: parent.height
anchors.left: parent.left
color:"#00000000"
}
Rectangle{
border.color: control.borderColor
width: 1
height: parent.height
anchors.right: parent.right
color:"#00000000"
visible: column === table_view.columns - 1
}
MouseArea{
id:column_item_control_mouse
anchors.fill: parent
anchors.rightMargin: 6
hoverEnabled: true
onCanceled: {
column_item_control.canceled = true
}
onContainsMouseChanged: {
if(!containsMouse){
column_item_control.canceled = false
}
}
onClicked:
(event)=>{
}
}
FluLoader{
id:item_column_loader
property var itemModel: model
property var modelData: model.display
property var tableView: table_view
property var options:{
if(typeof(modelData) == "object"){
return modelData.options
}
return {}
}
property int column: column_item_control.columnIndex
width: parent.width
height: parent.height
sourceComponent: {
if(typeof(modelData) == "object"){
return modelData.comId
}
return com_column_text
}
}
MouseArea{
property point clickPos: "0,0"
height: parent.height
width: 6
anchors.right: parent.right
acceptedButtons: Qt.LeftButton
hoverEnabled: true
visible: !(columnObject.width === columnObject.minimumWidth && columnObject.width === columnObject.maximumWidth && columnObject.width)
cursorShape: Qt.SplitHCursor
preventStealing: true
onPressed :
(mouse)=>{
FluTools.setOverrideCursor(Qt.SplitHCursor)
clickPos = Qt.point(mouse.x, mouse.y)
}
onReleased:{
FluTools.restoreOverrideCursor()
}
onCanceled: {
FluTools.restoreOverrideCursor()
}
onPositionChanged:
(mouse)=>{
if(!pressed){
return
}
var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y)
var minimumWidth = columnObject.minimumWidth
var maximumWidth = columnObject.maximumWidth
var w = columnObject.width
if(!w){
w = d.defaultItemWidth
}
if(!minimumWidth){
minimumWidth = d.defaultItemWidth
}
if(!maximumWidth){
maximumWidth = 65535
}
columnObject.width = Math.min(Math.max(minimumWidth, w + delta.x),maximumWidth)
table_view.forceLayout()
} }
} }
} }
} }
TableView {
id: header_horizontal
model: header_column_model
anchors{
left: table_view.left
right: table_view.right
top: parent.top
}
height: Math.max(1, contentHeight)
boundsBehavior: Flickable.StopAtBounds
clip: true
syncDirection: Qt.Horizontal
columnWidthProvider: table_view.columnWidthProvider
syncView: table_view.rows === 0 ? null : table_view
onContentXChanged:{
timer_horizontal_force_layout.restart()
}
Timer{
id:timer_horizontal_force_layout
interval: 50
onTriggered: {
header_horizontal.forceLayout()
}
}
delegate: com_column_header_delegate
}
Component{ Component{
id:com_item_text id:com_item_text
Item{ Item{
width: item_text.width width: item_text.width
FluText { FluText {
id:item_text id:item_text
text: dataModel.title text: model.title
rightPadding: 14 rightPadding: 14
anchors.centerIn: parent anchors.centerIn: parent
color:{ color:{
@ -423,7 +717,7 @@ Item {
return tree_model.dataSourceSize return tree_model.dataSourceSize
} }
function visibleCount(){ function visibleCount(){
return table_view.count return table_view.rows
} }
function collapse(rowIndex){ function collapse(rowIndex){
tree_model.collapse(rowIndex) tree_model.collapse(rowIndex)
@ -437,4 +731,14 @@ Item {
function allCollapse(){ function allCollapse(){
tree_model.allCollapse() tree_model.allCollapse()
} }
function customItem(comId,options={}){
var o = {}
o.comId = comId
o.options = options
return o
}
function closeEditor(){
d.editPosition = undefined
d.editDelegate = undefined
}
} }