Compare commits

...

21 Commits
1.5.8 ... 1.5.9

Author SHA1 Message Date
7a1776407f update 2023-09-23 15:55:29 +08:00
f88b330f8e update 2023-09-22 17:35:02 +08:00
67ef7f13aa update 2023-09-22 09:48:54 +08:00
23ec52ce6a update 2023-09-22 09:31:47 +08:00
5b7fdab1d9 update 2023-09-22 09:21:17 +08:00
4c1a96c03e update 2023-09-22 01:00:32 +08:00
ab4090ea9b update 2023-09-22 00:40:09 +08:00
8fb2ef723e update 2023-09-22 00:31:25 +08:00
77d9b4bde9 update 2023-09-22 00:11:58 +08:00
a96191b2af update 2023-09-21 18:29:09 +08:00
28e1799ca4 update 2023-09-20 18:38:15 +08:00
8337e278ff update 2023-09-19 23:41:56 +08:00
7ad8c969da update 2023-09-19 18:31:29 +08:00
66ae37a023 update 2023-09-19 00:31:49 +08:00
b27a88d261 update 2023-09-18 18:10:27 +08:00
257f3a7b3d update 2023-09-18 00:12:39 +08:00
4710379324 update 2023-09-17 21:34:02 +08:00
8fc74fe43b update 2023-09-17 20:36:33 +08:00
be194e7624 update 2023-09-15 19:11:55 +08:00
e6d9de34ea update 2023-09-15 01:28:03 +08:00
c47fa5ebc7 update 2023-09-14 18:50:36 +08:00
98 changed files with 2811 additions and 1545 deletions

View File

@ -18,9 +18,10 @@ on:
jobs:
build:
name: Build
runs-on: windows-2022
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-2022]
include:
- qt_arch: win64_mingw
qt_ver: 6.5.0
@ -34,6 +35,11 @@ jobs:
uses: actions/checkout@v3
with:
submodules: recursive
- name: Setup ninja
uses: seanmiddleditch/gha-setup-ninja@master
with:
version: 1.10.2
- name: Install Qt
uses: jurplel/install-qt-action@v3
@ -62,6 +68,7 @@ jobs:
run: |
mkdir build
cd build
ninja --version
cmake -DCMAKE_MESSAGE_LOG_LEVEL=STATUS -DCMAKE_PREFIX_PATH=D:\a\FluentUI\Qt\6.5.0\mingw_64 -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Release -GNinja ..
cmake --build . --target all --config Release --parallel

View File

@ -49,6 +49,7 @@ jobs:
shell: cmd
run: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.msvc_arch }}
ninja --version
mkdir build
cd build
cmake -DCMAKE_MESSAGE_LOG_LEVEL=STATUS -DCMAKE_PREFIX_PATH=D:\a\FluentUI\Qt\6.5.0\msvc2019_64 -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=Release -GNinja ..

View File

@ -8,6 +8,13 @@ Window {
id: app
flags: Qt.SplashScreen
Connections{
target: FluTheme
function onDarkModeChanged(){
SettingsHelper.saveDarkMode(FluTheme.darkMode)
}
}
FluHttpInterceptor{
id:interceptor
function onIntercept(request){
@ -26,9 +33,8 @@ Window {
Component.onCompleted: {
FluApp.init(app)
FluTheme.darkMode = FluThemeType.System
FluTheme.darkMode = SettingsHelper.getDarkMode()
FluTheme.enableAnimation = true
FluTheme.nativeText = true
FluApp.routes = {
"/":"qrc:/example/qml/window/MainWindow.qml",
"/about":"qrc:/example/qml/window/AboutWindow.qml",

View File

@ -80,7 +80,6 @@ FluExpander{
"FluIcon",
"FluIconButton",
"FluInfoBar",
"FluItem",
"FluMediaPlayer",
"FluMenu",
"FluMenuItem",

View File

@ -477,6 +477,9 @@ FluObject{
}
function getSearchData(){
if(!navigationView){
return
}
var arr = []
var items = navigationView.getItems();
for(var i=0;i<items.length;i++){

View File

@ -47,7 +47,7 @@ FluScrollablePage{
height: 1200/4+20
paddings: 10
Layout.topMargin: 10
FluRectangle{
FluClip{
width: 1920/4
height: 1200/4
radius:[8,8,8,8]

View File

@ -44,7 +44,6 @@ FluScrollablePage{
}
FluCarousel{
anchors.fill: parent
radius:[8,8,8,8]
delegate: Component{
Image {
anchors.fill: parent
@ -82,7 +81,6 @@ FluScrollablePage{
}
FluCarousel{
anchors.fill: parent
radius:[8,8,8,8]
loopTime:1500
indicatorGravity: Qt.AlignHCenter | Qt.AlignTop
indicatorMarginTop:15

View File

@ -46,7 +46,6 @@ FluScrollablePage{
}
FluComboBox {
editable: true
font:FluTextStyle.BodyStrong
model: ListModel {
id: model_2
ListElement { text: "Banana" }

View File

@ -27,6 +27,7 @@ FluScrollablePage{
id: bg
fillMode:Image.PreserveAspectCrop
anchors.fill: parent
asynchronous: true
verticalAlignment: Qt.AlignTop
sourceSize: Qt.size(960,640)
source: "qrc:/example/res/image/bg_home_header.png"
@ -49,23 +50,9 @@ FluScrollablePage{
}
}
ListView{
id: list
anchors{
left: parent.left
right: parent.right
bottom: parent.bottom
}
orientation: ListView.Horizontal
height: 240
model: model_header
header: Item{height: 10;width: 10}
footer: Item{height: 10;width: 10}
ScrollBar.horizontal: FluScrollBar{
id: scrollbar_header
}
clip: false
delegate:Item{
Component{
id:com_grallery
Item{
id: control
width: 220
height: 240
@ -73,7 +60,7 @@ FluScrollablePage{
radius:5
anchors.fill: item_content
}
FluItem{
FluClip{
id:item_content
radius: [5,5,5,5]
width: 200
@ -90,19 +77,14 @@ FluScrollablePage{
Rectangle{
anchors.fill: parent
radius: 5
color:{
if(FluTheme.dark){
if(item_mouse.containsMouse){
return Qt.rgba(1,1,1,0.03)
}
return Qt.rgba(0,0,0,0.0)
}else{
if(item_mouse.containsMouse){
return Qt.rgba(0,0,0,0.03)
}
return Qt.rgba(0,0,0,0.0)
}
}
color:FluTheme.dark ? Qt.rgba(1,1,1,0.03) : Qt.rgba(0,0,0,0.03)
visible: item_mouse.containsMouse
}
Rectangle{
anchors.fill: parent
radius: 5
color:Qt.rgba(0,0,0,0.0)
visible: !item_mouse.containsMouse
}
ColumnLayout{
Image {
@ -154,11 +136,31 @@ FluScrollablePage{
}
}
}
ListView{
id: list
anchors{
left: parent.left
right: parent.right
bottom: parent.bottom
}
orientation: ListView.Horizontal
height: 240
model: model_header
header: Item{height: 10;width: 10}
footer: Item{height: 10;width: 10}
ScrollBar.horizontal: FluScrollBar{
id: scrollbar_header
}
clip: false
delegate: com_grallery
}
}
Component{
id:com_item
Item{
property string desc: modelData.desc
width: 320
height: 120
FluArea{
@ -194,7 +196,6 @@ FluScrollablePage{
verticalCenter: parent.verticalCenter
}
}
FluText{
id:item_title
text:modelData.title
@ -205,10 +206,9 @@ FluScrollablePage{
top: item_icon.top
}
}
FluText{
id:item_desc
text:modelData.desc
text:desc
color:FluColors.Grey120
wrapMode: Text.WrapAnywhere
elide: Text.ElideRight

View File

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

View File

@ -3,7 +3,6 @@ import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
import FluentUI
import Qt5Compat.GraphicalEffects
import "qrc:///example/qml/component"
FluScrollablePage{

View File

@ -68,7 +68,7 @@ FluScrollablePage{
}
RowLayout{
spacing: 14
FluRectangle{
FluClip{
width: 50
height: 50
radius:[25,0,25,25]
@ -79,7 +79,7 @@ FluScrollablePage{
sourceSize: Qt.size(width,height)
}
}
FluRectangle{
FluClip{
width: 50
height: 50
radius:[10,10,10,10]
@ -90,7 +90,7 @@ FluScrollablePage{
source: "qrc:/example/res/svg/avatar_2.svg"
}
}
FluRectangle{
FluClip{
width: 50
height: 50
radius:[25,25,25,25]
@ -101,7 +101,7 @@ FluScrollablePage{
source: "qrc:/example/res/svg/avatar_3.svg"
}
}
FluRectangle{
FluClip{
width: 50
height: 50
radius:[0,25,25,25]
@ -113,7 +113,7 @@ FluScrollablePage{
}
}
}
FluRectangle{
FluClip{
width: 1920/5
height: 1200/5
radius:[8,8,8,8]

View File

@ -40,7 +40,7 @@ FluScrollablePage{
spacing: 20
anchors.verticalCenter: parent.verticalCenter
FluText{
text:"当前版本 v%1".arg(appInfo.version)
text:"当前版本 v%1".arg(AppInfo.version)
font: FluTextStyle.Body
anchors.verticalCenter: parent.verticalCenter
}
@ -56,6 +56,38 @@ FluScrollablePage{
}
}
FluArea{
Layout.fillWidth: true
Layout.topMargin: 20
height: 50
paddings: 10
FluCheckBox{
text:"Software Render"
checked: SettingsHelper.getReander() === "software"
anchors.verticalCenter: parent.verticalCenter
onClicked: {
if(SettingsHelper.getReander() === "software"){
SettingsHelper.saveRender("")
}else{
SettingsHelper.saveRender("software")
}
dialog_render.open()
}
}
}
FluContentDialog{
id:dialog_render
title:"友情提示"
message:"此操作需要重启才能生效,是否重新启动?"
buttonFlags: FluContentDialogType.NegativeButton | FluContentDialogType.PositiveButton
negativeText: "取消"
positiveText:"确定"
onPositiveClicked:{
window.deleteWindow()
AppInfo.restart()
}
}
FluArea{
Layout.fillWidth: true
@ -141,10 +173,10 @@ FluScrollablePage{
Repeater{
model: ["Zh","En"]
delegate: FluRadioButton{
checked: appInfo.lang.objectName === modelData
checked: AppInfo.lang.objectName === modelData
text:modelData
clickListener:function(){
appInfo.changeLang(modelData)
AppInfo.changeLang(modelData)
}
}
}

View File

@ -5,152 +5,127 @@ import QtQuick.Controls
import FluentUI
import "qrc:///example/qml/component"
FluScrollablePage {
FluContentPage {
title:"TreeView"
function randomName() {
var names = ["张三", "李四", "王五", "赵六", "钱七", "孙八", "周九", "吴十"]
return names[Math.floor(Math.random() * names.length)]
function treeData(){
const dig = (path = '0', level = 4) => {
const list = [];
for (let i = 0; i < 6; i += 1) {
const key = `${path}-${i}`;
const treeNode = {
title: key,
key,
};
if (level > 0) {
treeNode.children = dig(key, level - 1);
}
list.push(treeNode);
}
return list;
};
return dig();
}
function randomCompany() {
var companies = ["阿里巴巴", "腾讯", "百度", "京东", "华为", "小米", "字节跳动", "美团", "滴滴"]
return companies[Math.floor(Math.random() * companies.length)]
}
function randomDepartment() {
var departments = ["技术部", "销售部", "市场部", "人事部", "财务部", "客服部", "产品部", "设计部", "运营部"]
return departments[Math.floor(Math.random() * departments.length)]
}
function createEmployee() {
var name = randomName()
return tree_view.createItem(name, false)
}
function createSubtree(numEmployees) {
var employees = []
for (var i = 0; i < numEmployees; i++) {
employees.push(createEmployee())
Column{
id:layout_column
spacing: 12
width: 300
anchors{
topMargin: 20
top:parent.top
left: parent.left
leftMargin: 10
bottom:parent.bottom
bottomMargin: 20
}
return tree_view.createItem(randomDepartment(), true, employees)
}
function createOrg(numLevels, numSubtrees, numEmployees) {
if (numLevels === 0) {
return []
FluText{
text:"共计%1条数据当前显示的%2条数据".arg(tree_view.count()).arg(tree_view.visibleCount())
}
FluText{
text:"共计选中%1条数据".arg(tree_view.selectionModel().length)
}
var subtrees = []
for (var i = 0; i < numSubtrees; i++) {
subtrees.push(createSubtree(numEmployees))
}
return [tree_view.createItem(randomCompany(), true, subtrees)].concat(createOrg(numLevels - 1, numSubtrees, numEmployees))
}
FluArea{
id:layout_actions
Layout.fillWidth: true
Layout.topMargin: 20
height: 50
paddings: 10
RowLayout{
spacing: 14
FluDropDownButton{
id:btn_selection_model
Layout.preferredWidth: 140
text:"None"
FluMenuItem{
text:"None"
onClicked: {
btn_selection_model.text = text
tree_view.selectionMode = FluTabViewType.Equal
}
}
FluMenuItem{
text:"Single"
onClicked: {
btn_selection_model.text = text
tree_view.selectionMode = FluTabViewType.SizeToContent
}
}
FluMenuItem{
text:"Muiltple"
onClicked: {
btn_selection_model.text = text
tree_view.selectionMode = FluTabViewType.Compact
}
}
spacing: 10
FluText{
text:"cellHeight:"
Layout.alignment: Qt.AlignVCenter
}
FluFilledButton{
text:"获取选中的数据"
onClicked: {
if(tree_view.selectionMode === FluTreeViewType.None){
showError("当前非选择模式,没有选中的数据")
}
if(tree_view.selectionMode === FluTreeViewType.Single){
if(!tree_view.signleData()){
showError("没有选中数据")
return
}
showSuccess(tree_view.signleData().text)
}
if(tree_view.selectionMode === FluTreeViewType.Multiple){
if(tree_view.multipData().length===0){
showError("没有选中数据")
return
}
var info = []
tree_view.multipData().map((value)=>info.push(value.text))
showSuccess(info.join(","))
}
}
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: 30
from: 30
to:100
}
}
FluToggleSwitch{
id:switch_showline
text:"showLine"
checked: false
}
FluToggleSwitch{
id:switch_draggable
text:"draggable"
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()
}
}
}
FluArea{
Layout.fillWidth: true
Layout.topMargin: 10
paddings: 10
height: 400
anchors{
left: layout_column.right
top: parent.top
bottom: parent.bottom
right: parent.right
rightMargin: 5
topMargin: 5
bottomMargin: 5
}
FluShadow{}
FluTreeView{
id:tree_view
width:240
anchors{
top:parent.top
left:parent.left
bottom:parent.bottom
}
onItemClicked:
(model)=>{
showSuccess(model.text)
}
anchors.fill: parent
cellHeight: slider_cell_height.value
draggable:switch_draggable.checked
showLine: switch_showline.checked
checkable:switch_checkable.checked
depthPadding: slider_depth_padding.value
Component.onCompleted: {
var org = createOrg(3, 3, 3)
createItem()
updateData(org)
var data = treeData()
dataSource = data
}
}
}
CodeExpander{
Layout.fillWidth: true
Layout.topMargin: -1
code:'FluTreeView{
id:tree_view
width:240
height:600
Component.onCompleted: {
var datas = []
datas.push(createItem("Node1",false))
datas.push(createItem("Node2",false))
datas.push(createItem("Node2",true,[createItem("Node2-1",false),createItem("Node2-2",false)]))
updateData(datas)
}
}
'
}
}

View File

@ -35,7 +35,7 @@ CustomWindow {
}
}
FluText{
text:"v%1".arg(appInfo.version)
text:"v%1".arg(AppInfo.version)
font: FluTextStyle.Body
Layout.alignment: Qt.AlignBottom
}

View File

@ -183,7 +183,7 @@ CustomWindow {
z:999
//Stack模式每次切换都会将页面压入栈中随着栈的页面增多消耗的内存也越多内存消耗多就会卡顿这时候就需要按返回将页面pop掉释放内存。该模式可以配合FluPage中的launchMode属性设置页面的启动模式
// pageMode: FluNavigationViewType.Stack
//NoStack模式每次切换都会销毁之前的页面然后创建一个新的页面只需消耗少量内存推荐
//NoStack模式每次切换都会销毁之前的页面然后创建一个新的页面只需消耗少量内存可以配合FluViewModel保存页面数据(推荐)
pageMode: FluNavigationViewType.NoStack
items: ItemsOriginal
footerItems:ItemsFooter
@ -318,7 +318,7 @@ CustomWindow {
property string body
id:dialog_update
title:"升级提示"
message:"FluentUI目前最新版本 "+ newVerson +" -- 当前应用版本 "+appInfo.version+" \n现在是否去下载新版本\n\n更新内容\n"+body
message:"FluentUI目前最新版本 "+ newVerson +" -- 当前应用版本 "+AppInfo.version+" \n现在是否去下载新版本\n\n更新内容\n"+body
buttonFlags: FluContentDialogType.NegativeButton | FluContentDialogType.PositiveButton
negativeText: "取消"
positiveText:"确定"
@ -340,9 +340,9 @@ CustomWindow {
onSuccess:
(result)=>{
var data = JSON.parse(result)
console.debug("current version "+appInfo.version)
console.debug("current version "+AppInfo.version)
console.debug("new version "+data.tag_name)
if(data.tag_name !== appInfo.version){
if(data.tag_name !== AppInfo.version){
dialog_update.newVerson = data.tag_name
dialog_update.body = data.body
dialog_update.open()

View File

@ -8,6 +8,13 @@ Window {
id: app
flags: Qt.SplashScreen
Connections{
target: FluTheme
function onDarkModeChanged(){
SettingsHelper.saveDarkMode(FluTheme.darkMode)
}
}
FluHttpInterceptor{
id:interceptor
function onIntercept(request){
@ -26,9 +33,8 @@ Window {
Component.onCompleted: {
FluApp.init(app)
FluTheme.darkMode = FluThemeType.System
FluTheme.darkMode = SettingsHelper.getDarkMode()
FluTheme.enableAnimation = true
FluTheme.nativeText = true
FluApp.routes = {
"/":"qrc:/example/qml/window/MainWindow.qml",
"/about":"qrc:/example/qml/window/AboutWindow.qml",

View File

@ -80,7 +80,6 @@ FluExpander{
"FluIcon",
"FluIconButton",
"FluInfoBar",
"FluItem",
"FluMediaPlayer",
"FluMenu",
"FluMenuItem",

View File

@ -477,6 +477,9 @@ FluObject{
}
function getSearchData(){
if(!navigationView){
return
}
var arr = []
var items = navigationView.getItems();
for(var i=0;i<items.length;i++){

View File

@ -48,7 +48,7 @@ FluScrollablePage{
height: 1200/4+20
paddings: 10
Layout.topMargin: 10
FluRectangle{
FluClip{
width: 1920/4
height: 1200/4
radius:[8,8,8,8]

View File

@ -45,7 +45,6 @@ FluScrollablePage{
}
FluCarousel{
anchors.fill: parent
radius:[8,8,8,8]
delegate: Component{
Image {
anchors.fill: parent
@ -83,7 +82,6 @@ FluScrollablePage{
}
FluCarousel{
anchors.fill: parent
radius:[8,8,8,8]
loopTime:1500
indicatorGravity: Qt.AlignHCenter | Qt.AlignTop
indicatorMarginTop:15

View File

@ -47,7 +47,6 @@ FluScrollablePage{
}
FluComboBox {
editable: true
font:FluTextStyle.BodyStrong
model: ListModel {
id: model_2
ListElement { text: "Banana" }

View File

@ -2,6 +2,7 @@ import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtGraphicalEffects 1.0
import "qrc:///example/qml/global"
import FluentUI 1.0
@ -50,23 +51,9 @@ FluScrollablePage{
}
}
ListView{
id: list
anchors{
left: parent.left
right: parent.right
bottom: parent.bottom
}
orientation: ListView.Horizontal
height: 240
model: model_header
header: Item{height: 10;width: 10}
footer: Item{height: 10;width: 10}
ScrollBar.horizontal: FluScrollBar{
id: scrollbar_header
}
clip: false
delegate:Item{
Component{
id:com_grallery
Item{
id: control
width: 220
height: 240
@ -74,7 +61,7 @@ FluScrollablePage{
radius:5
anchors.fill: item_content
}
FluItem{
FluClip{
id:item_content
radius: [5,5,5,5]
width: 200
@ -91,19 +78,14 @@ FluScrollablePage{
Rectangle{
anchors.fill: parent
radius: 5
color:{
if(FluTheme.dark){
if(item_mouse.containsMouse){
return Qt.rgba(1,1,1,0.03)
}
return Qt.rgba(0,0,0,0.0)
}else{
if(item_mouse.containsMouse){
return Qt.rgba(0,0,0,0.03)
}
return Qt.rgba(0,0,0,0.0)
}
}
color:FluTheme.dark ? Qt.rgba(1,1,1,0.03) : Qt.rgba(0,0,0,0.03)
visible: item_mouse.containsMouse
}
Rectangle{
anchors.fill: parent
radius: 5
color:Qt.rgba(0,0,0,0.0)
visible: !item_mouse.containsMouse
}
ColumnLayout{
Image {
@ -155,11 +137,31 @@ FluScrollablePage{
}
}
}
ListView{
id: list
anchors{
left: parent.left
right: parent.right
bottom: parent.bottom
}
orientation: ListView.Horizontal
height: 240
model: model_header
header: Item{height: 10;width: 10}
footer: Item{height: 10;width: 10}
ScrollBar.horizontal: FluScrollBar{
id: scrollbar_header
}
clip: false
delegate: com_grallery
}
}
Component{
id:com_item
Item{
property string desc: modelData.desc
width: 320
height: 120
FluArea{
@ -195,7 +197,6 @@ FluScrollablePage{
verticalCenter: parent.verticalCenter
}
}
FluText{
id:item_title
text:modelData.title
@ -206,10 +207,9 @@ FluScrollablePage{
top: item_icon.top
}
}
FluText{
id:item_desc
text:modelData.desc
text:desc
color:FluColors.Grey120
wrapMode: Text.WrapAnywhere
elide: Text.ElideRight

View File

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

View File

@ -3,7 +3,6 @@ import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Window 2.15
import FluentUI 1.0
import QtGraphicalEffects 1.15
import "qrc:///example/qml/component"
import "../component"

View File

@ -69,7 +69,7 @@ FluScrollablePage{
}
RowLayout{
spacing: 14
FluRectangle{
FluClip{
width: 50
height: 50
radius:[25,0,25,25]
@ -80,7 +80,7 @@ FluScrollablePage{
sourceSize: Qt.size(width,height)
}
}
FluRectangle{
FluClip{
width: 50
height: 50
radius:[10,10,10,10]
@ -91,7 +91,7 @@ FluScrollablePage{
source: "qrc:/example/res/svg/avatar_2.svg"
}
}
FluRectangle{
FluClip{
width: 50
height: 50
radius:[25,25,25,25]
@ -102,7 +102,7 @@ FluScrollablePage{
source: "qrc:/example/res/svg/avatar_3.svg"
}
}
FluRectangle{
FluClip{
width: 50
height: 50
radius:[0,25,25,25]
@ -114,7 +114,7 @@ FluScrollablePage{
}
}
}
FluRectangle{
FluClip{
width: 1920/5
height: 1200/5
radius:[8,8,8,8]

View File

@ -43,7 +43,7 @@ FluScrollablePage{
spacing: 20
anchors.verticalCenter: parent.verticalCenter
FluText{
text:"当前版本 v%1".arg(appInfo.version)
text:"当前版本 v%1".arg(AppInfo.version)
font: FluTextStyle.Body
anchors.verticalCenter: parent.verticalCenter
}
@ -59,6 +59,38 @@ FluScrollablePage{
}
}
FluArea{
Layout.fillWidth: true
Layout.topMargin: 20
height: 50
paddings: 10
FluCheckBox{
text:"Software Render"
checked: SettingsHelper.getReander() === "software"
anchors.verticalCenter: parent.verticalCenter
onClicked: {
if(SettingsHelper.getReander() === "software"){
SettingsHelper.saveRender("")
}else{
SettingsHelper.saveRender("software")
}
dialog_render.open()
}
}
}
FluContentDialog{
id:dialog_render
title:"友情提示"
message:"此操作需要重启才能生效,是否重新启动?"
buttonFlags: FluContentDialogType.NegativeButton | FluContentDialogType.PositiveButton
negativeText: "取消"
positiveText:"确定"
onPositiveClicked:{
window.deleteWindow()
AppInfo.restart()
}
}
FluArea{
Layout.fillWidth: true
@ -144,10 +176,10 @@ FluScrollablePage{
Repeater{
model: ["Zh","En"]
delegate: FluRadioButton{
checked: appInfo.lang.objectName === modelData
checked: AppInfo.lang.objectName === modelData
text:modelData
clickListener:function(){
appInfo.changeLang(modelData)
AppInfo.changeLang(modelData)
}
}
}

View File

@ -6,152 +6,127 @@ import FluentUI 1.0
import "qrc:///example/qml/component"
import "../component"
FluScrollablePage {
FluContentPage {
title:"TreeView"
function randomName() {
var names = ["张三", "李四", "王五", "赵六", "钱七", "孙八", "周九", "吴十"]
return names[Math.floor(Math.random() * names.length)]
function treeData(){
const dig = (path = '0', level = 4) => {
const list = [];
for (let i = 0; i < 6; i += 1) {
const key = `${path}-${i}`;
const treeNode = {
title: key,
key,
};
if (level > 0) {
treeNode.children = dig(key, level - 1);
}
list.push(treeNode);
}
return list;
};
return dig();
}
function randomCompany() {
var companies = ["阿里巴巴", "腾讯", "百度", "京东", "华为", "小米", "字节跳动", "美团", "滴滴"]
return companies[Math.floor(Math.random() * companies.length)]
}
function randomDepartment() {
var departments = ["技术部", "销售部", "市场部", "人事部", "财务部", "客服部", "产品部", "设计部", "运营部"]
return departments[Math.floor(Math.random() * departments.length)]
}
function createEmployee() {
var name = randomName()
return tree_view.createItem(name, false)
}
function createSubtree(numEmployees) {
var employees = []
for (var i = 0; i < numEmployees; i++) {
employees.push(createEmployee())
Column{
id:layout_column
spacing: 12
width: 300
anchors{
topMargin: 20
top:parent.top
left: parent.left
leftMargin: 10
bottom:parent.bottom
bottomMargin: 20
}
return tree_view.createItem(randomDepartment(), true, employees)
}
function createOrg(numLevels, numSubtrees, numEmployees) {
if (numLevels === 0) {
return []
FluText{
text:"共计%1条数据当前显示的%2条数据".arg(tree_view.count()).arg(tree_view.visibleCount())
}
FluText{
text:"共计选中%1条数据".arg(tree_view.selectionModel().length)
}
var subtrees = []
for (var i = 0; i < numSubtrees; i++) {
subtrees.push(createSubtree(numEmployees))
}
return [tree_view.createItem(randomCompany(), true, subtrees)].concat(createOrg(numLevels - 1, numSubtrees, numEmployees))
}
FluArea{
id:layout_actions
Layout.fillWidth: true
Layout.topMargin: 20
height: 50
paddings: 10
RowLayout{
spacing: 14
FluDropDownButton{
id:btn_selection_model
Layout.preferredWidth: 140
text:"None"
FluMenuItem{
text:"None"
onClicked: {
btn_selection_model.text = text
tree_view.selectionMode = FluTabViewType.Equal
}
}
FluMenuItem{
text:"Single"
onClicked: {
btn_selection_model.text = text
tree_view.selectionMode = FluTabViewType.SizeToContent
}
}
FluMenuItem{
text:"Muiltple"
onClicked: {
btn_selection_model.text = text
tree_view.selectionMode = FluTabViewType.Compact
}
}
spacing: 10
FluText{
text:"cellHeight:"
Layout.alignment: Qt.AlignVCenter
}
FluFilledButton{
text:"获取选中的数据"
onClicked: {
if(tree_view.selectionMode === FluTreeViewType.None){
showError("当前非选择模式,没有选中的数据")
}
if(tree_view.selectionMode === FluTreeViewType.Single){
if(!tree_view.signleData()){
showError("没有选中数据")
return
}
showSuccess(tree_view.signleData().text)
}
if(tree_view.selectionMode === FluTreeViewType.Multiple){
if(tree_view.multipData().length===0){
showError("没有选中数据")
return
}
var info = []
tree_view.multipData().map((value)=>info.push(value.text))
showSuccess(info.join(","))
}
}
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: 30
from: 30
to:100
}
}
FluToggleSwitch{
id:switch_showline
text:"showLine"
checked: false
}
FluToggleSwitch{
id:switch_draggable
text:"draggable"
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()
}
}
}
FluArea{
Layout.fillWidth: true
Layout.topMargin: 10
paddings: 10
height: 400
anchors{
left: layout_column.right
top: parent.top
bottom: parent.bottom
right: parent.right
rightMargin: 5
topMargin: 5
bottomMargin: 5
}
FluShadow{}
FluTreeView{
id:tree_view
width:240
anchors{
top:parent.top
left:parent.left
bottom:parent.bottom
}
onItemClicked:
(model)=>{
showSuccess(model.text)
}
anchors.fill: parent
cellHeight: slider_cell_height.value
draggable:switch_draggable.checked
showLine: switch_showline.checked
checkable:switch_checkable.checked
depthPadding: slider_depth_padding.value
Component.onCompleted: {
var org = createOrg(3, 3, 3)
createItem()
updateData(org)
var data = treeData()
dataSource = data
}
}
}
CodeExpander{
Layout.fillWidth: true
Layout.topMargin: -1
code:'FluTreeView{
id:tree_view
width:240
height:600
Component.onCompleted: {
var datas = []
datas.push(createItem("Node1",false))
datas.push(createItem("Node2",false))
datas.push(createItem("Node2",true,[createItem("Node2-1",false),createItem("Node2-2",false)]))
updateData(datas)
}
}
'
}
}

View File

@ -36,7 +36,7 @@ CustomWindow {
}
}
FluText{
text:"v%1".arg(appInfo.version)
text:"v%1".arg(AppInfo.version)
font: FluTextStyle.Body
Layout.alignment: Qt.AlignBottom
}

View File

@ -186,7 +186,7 @@ CustomWindow {
z:999
//Stack模式每次切换都会将页面压入栈中随着栈的页面增多消耗的内存也越多内存消耗多就会卡顿这时候就需要按返回将页面pop掉释放内存。该模式可以配合FluPage中的launchMode属性设置页面的启动模式
// pageMode: FluNavigationViewType.Stack
//NoStack模式每次切换都会销毁之前的页面然后创建一个新的页面只需消耗少量内存推荐
//NoStack模式每次切换都会销毁之前的页面然后创建一个新的页面只需消耗少量内存可以配合FluViewModel保存页面数据(推荐)
pageMode: FluNavigationViewType.NoStack
items: ItemsOriginal
footerItems:ItemsFooter
@ -321,7 +321,7 @@ CustomWindow {
property string body
id:dialog_update
title:"升级提示"
message:"FluentUI目前最新版本 "+ newVerson +" -- 当前应用版本 "+appInfo.version+" \n现在是否去下载新版本\n\n更新内容\n"+body
message:"FluentUI目前最新版本 "+ newVerson +" -- 当前应用版本 "+AppInfo.version+" \n现在是否去下载新版本\n\n更新内容\n"+body
buttonFlags: FluContentDialogType.NegativeButton | FluContentDialogType.PositiveButton
negativeText: "取消"
positiveText:"确定"
@ -343,9 +343,9 @@ CustomWindow {
onSuccess:
(result)=>{
var data = JSON.parse(result)
console.debug("current version "+appInfo.version)
console.debug("current version "+AppInfo.version)
console.debug("new version "+data.tag_name)
if(data.tag_name !== appInfo.version){
if(data.tag_name !== AppInfo.version){
dialog_update.newVerson = data.tag_name
dialog_update.body = data.body
dialog_update.open()

View File

@ -2,6 +2,7 @@
#include <QQmlContext>
#include <QDebug>
#include <QGuiApplication>
#include "lang/En.h"
#include "lang/Zh.h"
#include "Version.h"
@ -20,7 +21,6 @@ void AppInfo::init(QQmlApplicationEngine *engine){
QObject::connect(this,&AppInfo::langChanged,this,[=]{
context->setContextProperty("lang",this->lang());
});
context->setContextProperty("appInfo",this);
}
void AppInfo::changeLang(const QString& locale){
@ -35,3 +35,7 @@ void AppInfo::changeLang(const QString& locale){
lang(new En());
}
}
void AppInfo::restart(){
qApp->exit(931);
}

View File

@ -5,16 +5,20 @@
#include <QQmlApplicationEngine>
#include "lang/Lang.h"
#include "stdafx.h"
#include "singleton.h"
class AppInfo : public QObject
{
Q_OBJECT
Q_PROPERTY_AUTO(QString,version)
Q_PROPERTY_AUTO(Lang*,lang)
public:
private:
explicit AppInfo(QObject *parent = nullptr);
public:
SINGLETONG(AppInfo)
void init(QQmlApplicationEngine *engine);
Q_INVOKABLE void changeLang(const QString& locale);
Q_INVOKABLE void restart();
};
#endif // APPINFO_H

View File

@ -0,0 +1,40 @@
#include "SettingsHelper.h"
#include <QDataStream>
SettingsHelper::SettingsHelper(QObject *parent) : QObject(parent)
{
}
SettingsHelper::~SettingsHelper() = default;
void SettingsHelper::save(const QString& key,QVariant val)
{
QByteArray data = {};
QDataStream stream(&data, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_6);
stream << val;
m_settings->setValue(key, data);
}
QVariant SettingsHelper::get(const QString& key){
const QByteArray data = m_settings->value(key).toByteArray();
if (data.isEmpty()) {
return {};
}
QDataStream stream(data);
stream.setVersion(QDataStream::Qt_5_6);
QVariant val;
stream >> val;
return val;
}
void SettingsHelper::init(char *argv[]){
auto applicationPath = QString::fromStdString(argv[0]);
const QFileInfo fileInfo(applicationPath);
const QString iniFileName = fileInfo.completeBaseName() + ".ini";
const QString iniFilePath = fileInfo.dir().path() + "/" + iniFileName;
qDebug()<<iniFilePath;
m_settings.reset(new QSettings(iniFilePath, QSettings::IniFormat));
}

View File

@ -0,0 +1,33 @@
#ifndef SETTINGSHELPER_H
#define SETTINGSHELPER_H
#include <QtCore/qobject.h>
#include <QtQml/qqml.h>
#include <QSettings>
#include <QScopedPointer>
#include <QFileInfo>
#include <QCoreApplication>
#include <QDir>
#include "src/singleton.h"
class SettingsHelper : public QObject
{
Q_OBJECT
private:
explicit SettingsHelper(QObject* parent = nullptr);
public:
SINGLETONG(SettingsHelper)
~SettingsHelper() override;
void init(char *argv[]);
Q_INVOKABLE void saveRender(const QString& render){save("render",render);}
Q_INVOKABLE QString getReander(){return get("render").toString();}
Q_INVOKABLE void saveDarkMode(int darkModel){save("darkMode",darkModel);}
Q_INVOKABLE int getDarkMode(){return get("darkMode").toInt(0);}
private:
void save(const QString& key,QVariant val);
QVariant get(const QString& key);
private:
QScopedPointer<QSettings> m_settings;
};
#endif // SETTINGSHELPER_H

View File

@ -13,6 +13,8 @@
#include "src/component/CircularReveal.h"
#include "src/component/FileWatcher.h"
#include "src/component/FpsItem.h"
#include "src/helper/SettingsHelper.h"
#ifdef FLUENTUI_BUILD_STATIC_LIB
#if (QT_VERSION > QT_VERSION_CHECK(6, 2, 0))
Q_IMPORT_QML_PLUGIN(FluentUIPlugin)
@ -22,8 +24,9 @@ Q_IMPORT_QML_PLUGIN(FluentUIPlugin)
FRAMELESSHELPER_USE_NAMESPACE
int main(int argc, char *argv[])
int main(int argc, char *argv[])
{
SettingsHelper::getInstance()->init(argv);
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
@ -36,6 +39,13 @@ FRAMELESSHELPER_USE_NAMESPACE
QGuiApplication::setOrganizationName("ZhuZiChu");
QGuiApplication::setOrganizationDomain("https://zhuzichu520.github.io");
QGuiApplication::setApplicationName("FluentUI");
if(SettingsHelper::getInstance()->getReander()=="software"){
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QQuickWindow::setGraphicsApi(QSGRendererInterface::Software);
#elif (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software);
#endif
}
QGuiApplication app(argc, argv);
FramelessConfig::instance()->set(Global::Option::DisableLazyInitializationForMicaMaterial);
FramelessConfig::instance()->set(Global::Option::CenterWindowBeforeShow);
@ -48,8 +58,10 @@ FRAMELESSHELPER_USE_NAMESPACE
#ifdef Q_OS_MACOS
FramelessConfig::instance()->set(Global::Option::ForceNonNativeBackgroundBlur,false);
#endif
AppInfo* appInfo = new AppInfo();
QQmlApplicationEngine engine;
AppInfo::getInstance()->init(&engine);
engine.rootContext()->setContextProperty("AppInfo",AppInfo::getInstance());
engine.rootContext()->setContextProperty("SettingsHelper",SettingsHelper::getInstance());
FramelessHelper::Quick::registerTypes(&engine);
#ifdef FLUENTUI_BUILD_STATIC_LIB
FluentUI::getInstance()->registerTypes(&engine);
@ -58,7 +70,6 @@ FRAMELESSHELPER_USE_NAMESPACE
qmlRegisterType<CircularReveal>("example", 1, 0, "CircularReveal");
qmlRegisterType<FileWatcher>("example", 1, 0, "FileWatcher");
qmlRegisterType<FpsItem>("example", 1, 0, "FpsItem");
appInfo->init(&engine);
const QUrl url(QStringLiteral("qrc:/example/qml/App.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
@ -66,5 +77,9 @@ FRAMELESSHELPER_USE_NAMESPACE
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
const int exec = QGuiApplication::exec();
if (exec == 931) {
QProcess::startDetached(qApp->applicationFilePath(), QStringList());
}
return exec;
}

47
example/src/singleton.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef SINGLETON_H
#define SINGLETON_H
#include <QMutex>
#include <QScopedPointer>
#include <memory>
#include <mutex>
template <typename T>
class Singleton {
public:
static T* getInstance();
Singleton(const Singleton& other) = delete;
Singleton<T>& operator=(const Singleton& other) = delete;
private:
static std::mutex mutex;
static T* instance;
};
template <typename T>
std::mutex Singleton<T>::mutex;
template <typename T>
T* Singleton<T>::instance;
template <typename T>
T* Singleton<T>::getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> locker(mutex);
if (instance == nullptr) {
instance = new T();
}
}
return instance;
}
#define SINGLETONG(Class) \
private: \
friend class Singleton<Class>; \
friend struct QScopedPointerDeleter<Class>; \
\
public: \
static Class* getInstance() { \
return Singleton<Class>::getInstance(); \
}
#endif // SINGLETON_H

View File

@ -119,7 +119,8 @@ void FluHttp::post(HttpRequest* r,HttpCallable* c){
QString result = QString::fromUtf8(reply->readAll());
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QString errorString = reply->errorString();
bool isSuccess = reply->error() == QNetworkReply::NoError;
QNetworkReply::NetworkError error = reply->error();
bool isSuccess = error == QNetworkReply::NoError;
reply->deleteLater();
reply = nullptr;
if (isSuccess) {
@ -134,6 +135,9 @@ void FluHttp::post(HttpRequest* r,HttpCallable* c){
onError(callable,status,errorString,result);
}
}
if(error == QNetworkReply::OperationCanceledError){
break;
}
}
onFinish(callable,request);
});
@ -174,7 +178,8 @@ void FluHttp::postString(HttpRequest* r,HttpCallable* c){
QString result = QString::fromUtf8(reply->readAll());
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QString errorString = reply->errorString();
bool isSuccess = reply->error() == QNetworkReply::NoError;
QNetworkReply::NetworkError error = reply->error();
bool isSuccess = error == QNetworkReply::NoError;
reply->deleteLater();
reply = nullptr;
if (isSuccess) {
@ -189,6 +194,9 @@ void FluHttp::postString(HttpRequest* r,HttpCallable* c){
onError(callable,status,errorString,result);
}
}
if(error == QNetworkReply::OperationCanceledError){
break;
}
}
onFinish(callable,request);
});
@ -228,7 +236,8 @@ void FluHttp::postJson(HttpRequest* r,HttpCallable* c){
QString result = QString::fromUtf8(reply->readAll());
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QString errorString = reply->errorString();
bool isSuccess = reply->error() == QNetworkReply::NoError;
QNetworkReply::NetworkError error = reply->error();
bool isSuccess = error == QNetworkReply::NoError;
reply->deleteLater();
reply = nullptr;
if (isSuccess) {
@ -243,6 +252,9 @@ void FluHttp::postJson(HttpRequest* r,HttpCallable* c){
onError(callable,status,errorString,result);
}
}
if(error == QNetworkReply::OperationCanceledError){
break;
}
}
onFinish(callable,request);
});
@ -273,14 +285,15 @@ void FluHttp::get(HttpRequest* r,HttpCallable* c){
QNetworkRequest req(url);
addHeaders(&req,data["headers"].toMap());
QEventLoop loop;
QNetworkReply* reply = manager.get(req);
auto reply = QPointer(manager.get(req));
_cacheReply.append(reply);
connect(&manager,&QNetworkAccessManager::finished,&manager,[&loop](QNetworkReply *reply){loop.quit();});
connect(qApp,&QGuiApplication::aboutToQuit,&manager, [&loop](){loop.quit();});
loop.exec();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QString errorString = reply->errorString();
bool isSuccess = reply->error() == QNetworkReply::NoError;
QNetworkReply::NetworkError error = reply->error();
bool isSuccess = error == QNetworkReply::NoError;
QString result = QString::fromUtf8(reply->readAll());
if (isSuccess) {
handleCache(httpId,result);
@ -296,6 +309,9 @@ void FluHttp::get(HttpRequest* r,HttpCallable* c){
}
reply->deleteLater();
reply = nullptr;
if(error == QNetworkReply::OperationCanceledError){
break;
}
}
onFinish(callable,request);
});

28
src/FluRectangle.cpp Normal file
View File

@ -0,0 +1,28 @@
#include "FluRectangle.h"
#include <QPainterPath>
FluRectangle::FluRectangle(QQuickItem* parent) : QQuickPaintedItem(parent){
setFlag(ItemHasContents, true);
color(QColor(255,255,255,255));
radius({0,0,0,0});
connect(this,&FluRectangle::colorChanged,this,[=]{update();});
connect(this,&FluRectangle::radiusChanged,this,[=]{update();});
}
void FluRectangle::paint(QPainter* painter){
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
QPainterPath path;
QRectF rect = boundingRect();
path.moveTo(rect.bottomRight() - QPointF(0, _radius[2]));
path.lineTo(rect.topRight() + QPointF(0, _radius[1]));
path.arcTo(QRectF(QPointF(rect.topRight() - QPointF(_radius[1] * 2, 0)), QSize(_radius[1] * 2, _radius[1] * 2)), 0, 90);
path.lineTo(rect.topLeft() + QPointF(_radius[0], 0));
path.arcTo(QRectF(QPointF(rect.topLeft()), QSize(_radius[0] * 2, _radius[0] * 2)), 90, 90);
path.lineTo(rect.bottomLeft() - QPointF(0, _radius[3]));
path.arcTo(QRectF(QPointF(rect.bottomLeft() - QPointF(0, _radius[3] * 2)), QSize(_radius[3] * 2, _radius[3] * 2)), 180, 90);
path.lineTo(rect.bottomRight() - QPointF(_radius[2], 0));
path.arcTo(QRectF(QPointF(rect.bottomRight() - QPointF(_radius[2] * 2, _radius[2] * 2)), QSize(_radius[2] * 2, _radius[2] * 2)), 270, 90);
painter->fillPath(path,_color);
painter->restore();
}

20
src/FluRectangle.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef FLURECTANGLE_H
#define FLURECTANGLE_H
#include <QQuickItem>
#include <QQuickPaintedItem>
#include <QPainter>
#include "stdafx.h"
class FluRectangle : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY_AUTO(QColor,color)
Q_PROPERTY_AUTO(QList<int>,radius)
QML_NAMED_ELEMENT(FluRectangle)
public:
explicit FluRectangle(QQuickItem *parent = nullptr);
void paint(QPainter* painter) override;
};
#endif // FLURECTANGLE_H

View File

@ -9,10 +9,14 @@
#include <QFileInfo>
#include <QProcess>
#include <QDir>
#include <QOpenGLContext>
#include <QCryptographicHash>
#include <QTextDocument>
#include <QQuickWindow>
#include <QDateTime>
FluTools::FluTools(QObject *parent):QObject{parent}{
}
void FluTools::clipText(const QString& text){
@ -159,3 +163,15 @@ void FluTools::showFileInFolder(QString path){
QProcess::execute("/usr/bin/osascript", {"-e", "tell application \"Finder\" to activate"});
#endif
}
bool FluTools::isSoftware(){
return QQuickWindow::sceneGraphBackend() == "software";
}
QPoint FluTools::cursorPos(){
return QCursor::pos();
}
qint64 FluTools::currentTimestamp(){
return QDateTime::currentMSecsSinceEpoch();
}

View File

@ -46,6 +46,9 @@ public:
Q_INVOKABLE bool removeDir(QString dirPath);
Q_INVOKABLE bool removeFile(QString filePath);
Q_INVOKABLE void showFileInFolder(QString path);
Q_INVOKABLE bool isSoftware();
Q_INVOKABLE qint64 currentTimestamp();
Q_INVOKABLE QPoint cursorPos();
};
#endif // FLUTOOLS_H

368
src/FluTreeModel.cpp Normal file
View File

@ -0,0 +1,368 @@
#include "FluTreeModel.h"
#include <QMetaEnum>
Node::Node(QObject *parent): QObject{parent}{
}
FluTreeModel::FluTreeModel(QObject *parent): QAbstractItemModel{parent}{
dataSourceSize(0);
}
QModelIndex FluTreeModel::parent(const QModelIndex &child) const{
return QModelIndex();
}
QModelIndex FluTreeModel::index(int row, int column,const QModelIndex &parent) const{
if (!hasIndex(row, column, parent) || parent.isValid())
return QModelIndex();
return createIndex(row, column, _rows.at(row));
}
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, "modelData"} };
};
void FluTreeModel::setData(QList<Node*> data){
beginResetModel();
_rows = data;
endResetModel();
}
void FluTreeModel::removeRows(int row,int count){
if (row < 0 || row + count > _rows.size() || count==0)
return;
beginRemoveRows(QModelIndex(),row, row + count - 1);
QList<Node*> firstPart = _rows.mid(0,row);
QList<Node*> secondPart = _rows.mid(row + count);
_rows.clear();
_rows.append(firstPart);
_rows.append(secondPart);
endRemoveRows();
}
void FluTreeModel::insertRows(int row,QList<Node*> data){
if (row < 0 || row > _rows.size() || data.size() == 0)
return;;
beginInsertRows(QModelIndex(), row, row + data.size() - 1);
QList<Node*> firstPart = _rows.mid(0, row);
QList<Node*> secondPart = _rows.mid(row);
_rows.clear();
_rows.append(firstPart);
_rows.append(data);
_rows.append(secondPart);
endInsertRows();
}
QObject* FluTreeModel::getRow(int row){
return _rows.at(row);
}
void FluTreeModel::checkRow(int row,bool chekced){
auto itemData = _rows.at(row);
if(itemData->hasChildren()){
QList<Node*> stack = itemData->_children;
std::reverse(stack.begin(), stack.end());
while (stack.count() > 0) {
auto item = stack.at(stack.count()-1);
stack.pop_back();
if(!item->hasChildren()){
item->_checked = chekced;
}
QList<Node*> children = item->_children;
if(!children.isEmpty()){
std::reverse(children.begin(), children.end());
foreach (auto c, children) {
stack.append(c);
}
}
}
}else{
if(itemData->_checked == chekced){
return;
}
itemData->_checked = chekced;
}
Q_EMIT layoutChanged(QList<QPersistentModelIndex>(),QAbstractItemModel::VerticalSortHint);
QList<Node*> data;
foreach (auto item, _dataSource) {
if(!item->hasChildren()){
if(item->_checked){
data.append(item);
}
}
}
selectionModel(data);
}
void FluTreeModel::setDataSource(QList<QMap<QString,QVariant>> data){
_dataSource.clear();
if(_root){
delete _root;
_root = nullptr;
}
_root = new Node(this);
std::reverse(data.begin(), data.end());
while (data.count() > 0) {
auto item = data.at(data.count()-1);
data.pop_back();
Node* node = new Node(this);
node->_title = item.value("title").toString();
node->_key = item.value("key").toString();
node->_depth = item.value("__depth").toInt();
node->_parent = item.value("__parent").value<Node*>();
node->_isExpanded = true;
if(node->_parent){
node->_parent->_children.append(node);
}else{
node->_parent = _root;
_root->_children.append(node);
}
_dataSource.append(node);
if (item.contains("children")) {
QList<QVariant> children = item.value("children").toList();
if(!children.isEmpty()){
std::reverse(children.begin(), children.end());
for (int i = 0; i < children.count(); ++i) {
auto child = children.at(i).toMap();
child.insert("__depth",item.value("__depth").toInt(0)+1);
child.insert("__parent",QVariant::fromValue(node));
data.append(child);
}
}
}
}
beginResetModel();
_rows = _dataSource;
endResetModel();
dataSourceSize(_dataSource.size());
}
void FluTreeModel::collapse(int row){
if(!_rows.at(row)->_isExpanded){
return;
}
_rows.at(row)->_isExpanded = false;
Q_EMIT dataChanged(index(row,0),index(row,0));
auto modelData = _rows.at(row);
int removeCount = 0;
for(int i=row+1;i<_rows.count();i++){
auto obj = _rows[i];
if(obj->_depth<=modelData->_depth){
break;
}
removeCount = removeCount + 1;
}
removeRows(row+1,removeCount);
}
void FluTreeModel::expand(int row){
if(_rows.at(row)->_isExpanded){
return;
}
_rows.at(row)->_isExpanded = true;
Q_EMIT dataChanged(index(row,0),index(row,0));
auto modelData = _rows.at(row);
QList<Node*> insertData;
QList<Node*> stack = modelData->_children;
std::reverse(stack.begin(), stack.end());
while (stack.count() > 0) {
auto item = stack.at(stack.count()-1);
stack.pop_back();
if(item->isShown()){
insertData.append(item);
}
QList<Node*> children = item->_children;
if(!children.isEmpty()){
std::reverse(children.begin(), children.end());
foreach (auto c, children) {
stack.append(c);
}
}
}
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<Node*>* 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<Node*>* srcChildren = &(dragItem->_parent->_children);
QList<Node*>* 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<Node*> 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<Node*> 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 layoutChanged(QList<QPersistentModelIndex>(),QAbstractItemModel::VerticalSortHint);
}
bool FluTreeModel::hitHasChildrenExpanded(int row){
auto itemData = _rows.at(row);
if(itemData->hasChildren() && itemData->_isExpanded){
return true;
}
return false;
}
void FluTreeModel::refreshNode(int row){
Q_EMIT dataChanged(index(row,0),index(row,0));
};
Node* FluTreeModel::getNode(int row){
return _rows.at(row);
}
void FluTreeModel::allExpand(){
beginResetModel();
QList<Node*> data;
QList<Node*> stack = _root->_children;
std::reverse(stack.begin(), stack.end());
while (stack.count() > 0) {
auto item = stack.at(stack.count()-1);
stack.pop_back();
if(item->hasChildren()){
item->_isExpanded = true;
}
data.append(item);
QList<Node*> children = item->_children;
if(!children.isEmpty()){
std::reverse(children.begin(), children.end());
foreach (auto c, children) {
stack.append(c);
}
}
}
_rows = data;
endResetModel();
}
void FluTreeModel::allCollapse(){
beginResetModel();
QList<Node*> stack = _root->_children;
std::reverse(stack.begin(), stack.end());
while (stack.count() > 0) {
auto item = stack.at(stack.count()-1);
stack.pop_back();
if(item->hasChildren()){
item->_isExpanded = false;
}
QList<Node*> children = item->_children;
if(!children.isEmpty()){
std::reverse(children.begin(), children.end());
foreach (auto c, children) {
stack.append(c);
}
}
}
_rows = _root->_children;
endResetModel();
}

115
src/FluTreeModel.h Normal file
View File

@ -0,0 +1,115 @@
#ifndef FLUTREEMODEL_H
#define FLUTREEMODEL_H
#include <QObject>
#include <QAbstractTableModel>
#include <QJsonArray>
#include <QtQml/qqml.h>
#include "stdafx.h"
class Node : public QObject{
Q_OBJECT
Q_PROPERTY(QString key READ key CONSTANT)
Q_PROPERTY(QString title READ title CONSTANT)
Q_PROPERTY(int depth READ depth CONSTANT)
Q_PROPERTY(bool isExpanded READ isExpanded CONSTANT)
Q_PROPERTY(bool checked READ checked CONSTANT)
public:
explicit Node(QObject *parent = nullptr);
Q_INVOKABLE QString key(){return _key;};
Q_INVOKABLE QString title(){return _title;};
Q_INVOKABLE int depth(){return _depth;};
Q_INVOKABLE bool isExpanded(){return _isExpanded;};
Q_INVOKABLE bool hasChildren(){ return !_children.isEmpty();};
Q_INVOKABLE bool hasNextNodeByIndex(int index){
Node* p = this;
for(int i=0;i<(_depth - index -1);i++){
p = p->_parent;
}
if(p->_parent->_children.indexOf(p) == p->_parent->_children.count()-1){
return false;
}
return true;
}
Q_INVOKABLE bool checked(){
if(!hasChildren()){
return _checked;
}
foreach (auto item, _children) {
if(!item->checked()){
return false;
}
}
return true;
};
Q_INVOKABLE bool hideLineFooter(){
if(_parent){
auto childIndex = _parent->_children.indexOf(this);
if(childIndex==_parent->_children.count()-1){
return true;
}
if(_parent->_children.at(childIndex+1)->hasChildren()){
return true;
}
return false;
}
return false;
};
bool isShown(){
auto p = _parent;
while (p) {
if(!p->_isExpanded){
return false;
}
p = p->_parent;
}
return true;
}
public:
QString _key="";
QString _title="";
int _depth=0;
bool _checked = false;
bool _isExpanded=true;
QList<Node*> _children;
Node* _parent = nullptr;
};
class FluTreeModel : public QAbstractItemModel
{
Q_OBJECT
Q_PROPERTY_AUTO(int,dataSourceSize)
Q_PROPERTY_AUTO(QList<Node*>,selectionModel)
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;
QModelIndex parent(const QModelIndex &child) const override;
QModelIndex index(int row, int column,const QModelIndex &parent = QModelIndex()) const override;
Q_INVOKABLE void removeRows(int row,int count);
Q_INVOKABLE void insertRows(int row,QList<Node*> data);
Q_INVOKABLE QObject* getRow(int row);
Q_INVOKABLE void setData(QList<Node*> data);
Q_INVOKABLE void setDataSource(QList<QMap<QString,QVariant>> data);
Q_INVOKABLE void collapse(int row);
Q_INVOKABLE void expand(int row);
Q_INVOKABLE void dragAnddrop(int dragIndex,int dropIndex,bool isDropTopArea);
Q_INVOKABLE Node* getNode(int row);
Q_INVOKABLE void refreshNode(int row);
Q_INVOKABLE void checkRow(int row,bool chekced);
Q_INVOKABLE bool hitHasChildrenExpanded(int row);
Q_INVOKABLE void allExpand();
Q_INVOKABLE void allCollapse();
private:
QList<Node*> _rows;
QList<Node*> _dataSource;
Node* _root = nullptr;
};
#endif // FLUTREEMODEL_H

View File

@ -57,8 +57,9 @@ void PropertyObserver::_propertyChange(){
}
FluViewModel::FluViewModel(QObject *parent):QObject{parent}{
ViewModelManager::getInstance()->insertViewModel(this);
scope(FluViewModelType::Scope::Window);
target(nullptr);
ViewModelManager::getInstance()->insertViewModel(this);
}
FluViewModel::~FluViewModel(){

View File

@ -31,7 +31,7 @@ public:
QString getKey();
private:
QObject* _window = nullptr;
QString _key;
QString _key = "";
};
class PropertyObserver: public QObject{
@ -42,9 +42,9 @@ public:
private:
Q_SLOT void _propertyChange();
private:
QString _name;
QString _name = "";
QQmlProperty _property;
QObject* _model;
QObject* _model = nullptr;
};

View File

@ -13,8 +13,10 @@
#include "FluWatermark.h"
#include "FluCaptcha.h"
#include "FluEventBus.h"
#include "FluTreeModel.h"
#include "FluViewModel.h"
#include "Screenshot.h"
#include "FluRectangle.h"
#include "QRCode.h"
int major = 1;
@ -53,6 +55,8 @@ void FluentUI::registerTypes(const char *uri){
qmlRegisterType<HttpRequest>(uri,major,minor,"HttpRequest");
qmlRegisterType<FluEvent>(uri,major,minor,"FluEvent");
qmlRegisterType<FluViewModel>(uri,major,minor,"FluViewModel");
qmlRegisterType<FluTreeModel>(uri,major,minor,"FluTreeModel");
qmlRegisterType<FluRectangle>(uri,major,minor,"FluRectangle");
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");
@ -91,7 +95,6 @@ void FluentUI::registerTypes(const char *uri){
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluIconButton.qml"),uri,major,minor,"FluIconButton");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluImage.qml"),uri,major,minor,"FluImage");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluInfoBar.qml"),uri,major,minor,"FluInfoBar");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluItem.qml"),uri,major,minor,"FluItem");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluItemDelegate.qml"),uri,major,minor,"FluItemDelegate");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluMenu.qml"),uri,major,minor,"FluMenu");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluMenuBar.qml"),uri,major,minor,"FluMenuBar");
@ -118,7 +121,6 @@ void FluentUI::registerTypes(const char *uri){
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluRadioButton.qml"),uri,major,minor,"FluRadioButton");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluRadioButtons.qml"),uri,major,minor,"FluRadioButtons");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluRatingControl.qml"),uri,major,minor,"FluRatingControl");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluRectangle.qml"),uri,major,minor,"FluRectangle");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluRemoteLoader.qml"),uri,major,minor,"FluRemoteLoader");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluScreenshot.qml"),uri,major,minor,"FluScreenshot");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluScrollBar.qml"),uri,major,minor,"FluScrollBar");
@ -148,6 +150,7 @@ void FluentUI::registerTypes(const char *uri){
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluStaggeredView.qml"),uri,major,minor,"FluStaggeredView");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluProgressButton.qml"),uri,major,minor,"FluProgressButton");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluLoadingButton.qml"),uri,major,minor,"FluLoadingButton");
qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluClip.qml"),uri,major,minor,"FluClip");
qmlRegisterUncreatableMetaObject(Fluent_Awesome::staticMetaObject, uri,major,minor,"FluentIcons", "Access to enums & flags only");
qmlRegisterUncreatableMetaObject(FluHttpType::staticMetaObject, uri,major,minor,"FluHttpType", "Access to enums & flags only");

View File

@ -2,7 +2,7 @@ import QtQuick 2.15
import QtGraphicalEffects 1.15
import FluentUI 1.0
FluItem {
Item {
id: control
property color tintColor: Qt.rgba(1,1,1,1)
property real tintOpacity: 0.65

View File

@ -13,11 +13,14 @@ Rectangle{
property string closeText : "关闭"
property color textColor: FluTheme.dark ? "#FFFFFF" : "#000000"
property color minimizeNormalColor: Qt.rgba(0,0,0,0)
property color minimizeHoverColor: FluTheme.dark ? Qt.rgba(1,1,1,0.1) : Qt.rgba(0,0,0,0.06)
property color minimizeHoverColor: FluTheme.dark ? Qt.rgba(1,1,1,0.03) : Qt.rgba(0,0,0,0.03)
property color minimizePressColor: FluTheme.dark ? Qt.rgba(1,1,1,0.06) : Qt.rgba(0,0,0,0.06)
property color maximizeNormalColor: Qt.rgba(0,0,0,0)
property color maximizeHoverColor: FluTheme.dark ? Qt.rgba(1,1,1,0.1) : Qt.rgba(0,0,0,0.06)
property color maximizeHoverColor: FluTheme.dark ? Qt.rgba(1,1,1,0.03) : Qt.rgba(0,0,0,0.03)
property color maximizePressColor: FluTheme.dark ? Qt.rgba(1,1,1,0.06) : Qt.rgba(0,0,0,0.06)
property color closeNormalColor: Qt.rgba(0,0,0,0)
property color closeHoverColor: Qt.rgba(251/255,115/255,115/255,1)
property color closePressColor: Qt.rgba(251/255,115/255,115/255,0.8)
property bool showDark: false
property bool showClose: true
property bool showMinimize: true
@ -114,7 +117,12 @@ Rectangle{
radius: 0
visible: !isMac && showMinimize
iconColor: control.textColor
color: hovered ? minimizeHoverColor : minimizeNormalColor
color: {
if(pressed){
return minimizePressColor
}
return hovered ? minimizeHoverColor : minimizeNormalColor
}
onClicked: minClickListener()
}
FluIconButton{
@ -122,7 +130,12 @@ Rectangle{
Layout.preferredWidth: 40
Layout.preferredHeight: 30
iconSource : d.isRestore ? FluentIcons.ChromeRestore : FluentIcons.ChromeMaximize
color: hovered ? maximizeHoverColor : maximizeNormalColor
color: {
if(pressed){
return maximizePressColor
}
return hovered ? maximizeHoverColor : maximizeNormalColor
}
Layout.alignment: Qt.AlignVCenter
visible: d.resizable && !isMac && showMaximize
radius: 0
@ -142,7 +155,12 @@ Rectangle{
radius: 0
iconSize: 10
iconColor: hovered ? Qt.rgba(1,1,1,1) : control.textColor
color:hovered ? closeHoverColor : closeNormalColor
color:{
if(pressed){
return closePressColor
}
return hovered ? closeHoverColor : closeNormalColor
}
onClicked: closeClickListener()
}
}

View File

@ -2,7 +2,7 @@ import QtQuick 2.15
import QtQuick.Controls 2.15
import FluentUI 1.0
FluItem {
Item {
property bool autoPlay: true
property int loopTime: 2000
property var model

View File

@ -22,6 +22,7 @@ Button {
property alias textColor: btn_text.textColor
property bool textRight: true
property real textSpacing: 6
property bool enableAnimation: FluTheme.enableAnimation
property var clickListener : function(){
checked = !checked
}
@ -39,8 +40,9 @@ Button {
visible: control.activeFocus
}
}
horizontalPadding:2
verticalPadding: 2
horizontalPadding:0
verticalPadding: 0
padding: 0
Accessible.role: Accessible.Button
Accessible.name: control.text
Accessible.description: contentDescription
@ -91,7 +93,7 @@ Button {
return normalColor
}
Behavior on color {
enabled: FluTheme.enableAnimation
enabled: control.enableAnimation
ColorAnimation{
duration: 83
}
@ -103,7 +105,7 @@ Button {
visible: checked
iconColor: FluTheme.dark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1)
Behavior on visible {
enabled: FluTheme.enableAnimation
enabled: control.enableAnimation
NumberAnimation{
duration: 83
}

View File

@ -0,0 +1,17 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtGraphicalEffects 1.0
import FluentUI 1.0
FluRectangle {
id:control
color: "#00000000"
layer.enabled: !FluTools.isSoftware()
layer.effect: OpacityMask{
maskSource: FluRectangle{
radius: control.radius
width: control.width
height: control.height
}
}
}

View File

@ -4,7 +4,7 @@ import QtQuick.Window 2.15
import FluentUI 1.0
import QtQuick.Templates 2.15 as T
ComboBox {
T.ComboBox {
id: control
signal commit(string text)
property bool disabled: false
@ -62,7 +62,7 @@ ComboBox {
bottomInset:1
rightInset:1
background: FluTextBoxBackground{
border.width: 0
borderWidth: 0
inputItem: contentItem
}
Component.onCompleted: {
@ -72,6 +72,7 @@ ComboBox {
Keys.onReturnPressed:(event)=> handleCommit(event)
function handleCommit(event){
control.commit(control.editText)
accepted()
}
}

View File

@ -5,14 +5,13 @@ import FluentUI 1.0
Rectangle {
property real spacing
property alias separatorHeight:separator.height
id:root
id:control
color:Qt.rgba(0,0,0,0)
height: spacing*2+separator.height
Rectangle{
FluRectangle{
id:separator
color: FluTheme.dark ? Qt.rgba(80/255,80/255,80/255,1) : Qt.rgba(210/255,210/255,210/255,1)
width:parent.width
anchors.centerIn: parent
color: FluTheme.dark ? Qt.rgba(80/255,80/255,80/255,1) : Qt.rgba(210/255,210/255,210/255,1)
width:parent.width
anchors.centerIn: parent
}
}

View File

@ -70,14 +70,15 @@ Item {
left: layout_header.left
}
width: parent.width
clip: true
visible: contentHeight+container.y !== 0
height: contentHeight+container.y
clip: true
Rectangle{
id:container
width: parent.width
height: parent.height
radius: 4
clip: true
color: FluTheme.dark ? Qt.rgba(39/255,39/255,39/255,1) : Qt.rgba(251/255,251/255,253/255,1)
border.color: FluTheme.dark ? Qt.rgba(45/255,45/255,45/255,1) : Qt.rgba(226/255,229/255,234/255,1)
y: -contentHeight

View File

@ -83,7 +83,7 @@ FluObject {
}
Timer {
id:delayTimer
interval: duration; running: true; repeat: true
interval: duration; running: duration > 0; repeat: duration > 0
onTriggered: content.close();
}
Loader{
@ -184,10 +184,47 @@ FluObject {
}
}
FluText{
text:_super.text
wrapMode: Text.WrapAnywhere
width: Math.min(implicitWidth,mcontrol.maxWidth)
Column{
spacing: 5
FluText{
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

@ -1,57 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtGraphicalEffects 1.15
Item{
property var radius:[0,0,0,0]
default property alias contentItem: container.data
id:control
Item{
id:container
width: control.width
height: control.height
opacity: 0
}
onWidthChanged: {
canvas.requestPaint()
}
onHeightChanged: {
canvas.requestPaint()
}
onRadiusChanged: {
canvas.requestPaint()
}
Canvas {
id: canvas
anchors.fill: parent
visible: false
onPaint: {
var ctx = getContext("2d");
var x = 0;
var y = 0;
var w = control.width;
var h = control.height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.beginPath();
ctx.moveTo(x + radius[0], y);
ctx.lineTo(x + w - radius[1], y);
ctx.arcTo(x + w, y, x + w, y + radius[1], radius[1]);
ctx.lineTo(x + w, y + h - radius[2]);
ctx.arcTo(x + w, y + h, x + w - radius[2], y + h, radius[2]);
ctx.lineTo(x + radius[3], y + h);
ctx.arcTo(x, y + h, x, y + h - radius[3], radius[3]);
ctx.lineTo(x, y + radius[0]);
ctx.arcTo(x, y, x + radius[0], y, radius[0]);
ctx.closePath();
ctx.fillStyle = control.color;
ctx.fill();
ctx.restore();
}
}
OpacityMask {
anchors.fill: container
source: container
maskSource: canvas
}
}

View File

@ -4,28 +4,28 @@ import FluentUI 1.0
T.ItemDelegate {
id: control
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding,
implicitIndicatorHeight + topPadding + bottomPadding)
padding: 12
spacing: 8
icon.width: 24
icon.height: 24
padding: 0
verticalPadding: 8
horizontalPadding: 10
icon.color: control.palette.text
contentItem: FluText {
contentItem:FluText {
text: control.text
font: control.font
color:{
if(control.down){
return FluTheme.dark ? FluColors.Grey80 : FluColors.Grey120
}
return FluTheme.dark ? FluColors.White : FluColors.Grey220
}
}
background: Rectangle {
implicitWidth: 100
implicitHeight: 40
implicitHeight: 30
color:{
if(FluTheme.dark){
return Qt.rgba(1,1,1,0.05)

View File

@ -112,8 +112,16 @@ Item {
id:com_panel_item_separatorr
FluDivider{
width: layout_list.width
spacing: model.spacing
spacing: {
if(model){
return model.spacing
}
return 1
}
separatorHeight: {
if(!model){
return 1
}
if(model.parent){
return model.parent.isExpand ? model.size : 0
}
@ -200,6 +208,9 @@ Item {
verticalCenterOffset: -8
}
visible: {
if(!model){
return false
}
if(!model.isExpand){
for(var i=0;i<model.children.length;i++){
var item = model.children[i]
@ -220,6 +231,12 @@ Item {
radius: 1.5
color: FluTheme.primaryColor.dark
visible: {
if(!model){
return false
}
if(!model.children){
return false
}
for(var i=0;i<model.children.length;i++){
var item = model.children[i]
if(item._idx === nav_list.currentIndex && !model.isExpand){
@ -234,7 +251,7 @@ Item {
}
FluIcon{
id:item_icon_expand
rotation: model.isExpand?0:180
rotation: model&&model.isExpand?0:180
iconSource:FluentIcons.ChevronUp
iconSize: 15
anchors{
@ -279,7 +296,7 @@ Item {
id:com_icon
FluIcon{
iconSource: {
if(model.icon){
if(model&&model.icon){
return model.icon
}
return 0
@ -299,7 +316,7 @@ Item {
Loader{
anchors.centerIn: parent
sourceComponent: {
if(model.cusIcon){
if(model&&model.cusIcon){
return model.cusIcon
}
return com_icon
@ -308,7 +325,12 @@ Item {
}
FluText{
id:item_title
text:model.title
text:{
if(model){
return model.title
}
return ""
}
visible: {
if(d.isCompactAndNotPanel){
return false
@ -341,7 +363,7 @@ Item {
if(d.isCompact){
return undefined
}
return model.showEdit ? model.editDelegate : undefined
return model&&model.showEdit ? model.editDelegate : undefined
}
onStatusChanged: {
if(status === Loader.Ready){
@ -377,13 +399,13 @@ Item {
}
}
height: {
if(model.parent){
if(model&&model.parent){
return model.parent.isExpand ? 38 : 0
}
return 38
}
visible: {
if(model.parent){
if(model&&model.parent){
return model.parent.isExpand ? true : false
}
return true
@ -497,7 +519,7 @@ Item {
id:com_icon
FluIcon{
iconSource: {
if(model.icon){
if(model&&model.icon){
return model.icon
}
return 0
@ -517,7 +539,7 @@ Item {
Loader{
anchors.centerIn: parent
sourceComponent: {
if(model.cusIcon){
if(model&&model.cusIcon){
return model.cusIcon
}
return com_icon
@ -526,7 +548,12 @@ Item {
}
FluText{
id:item_title
text:model.title
text:{
if(model){
return model.title
}
return ""
}
visible: {
if(d.isCompactAndNotPanel){
return false
@ -559,6 +586,9 @@ Item {
if(d.isCompact){
return undefined
}
if(!model){
return undefined
}
return model.showEdit ? model.editDelegate : undefined
}
onStatusChanged: {
@ -591,7 +621,7 @@ Item {
verticalCenterOffset: isDot ? -8 : 0
}
sourceComponent: {
if(model.infoBadge){
if(model&&model.infoBadge){
return model.infoBadge
}
return undefined
@ -771,7 +801,9 @@ Item {
anchors.fill: loader_content
onDropped:
(drag)=>{
drag.source.modelData.dropped(drag)
if(drag.source.modelData){
drag.source.modelData.dropped(drag)
}
}
}
Loader{
@ -938,6 +970,8 @@ Item {
property var _idx: index
property int type: 0
sourceComponent: {
if(model === null || !model)
return undefined
if(modelData instanceof FluPaneItem){
return com_panel_item
}
@ -953,6 +987,7 @@ Item {
if(modelData instanceof FluPaneItemEmpty){
return com_panel_item_empty
}
return undefined
}
}
}

View File

@ -19,7 +19,7 @@ ProgressBar{
color: control.backgroundColor
radius: d._radius
}
contentItem: FluItem {
contentItem: FluClip {
clip: true
radius: [d._radius,d._radius,d._radius,d._radius]
Rectangle {

View File

@ -40,7 +40,7 @@ Button {
id: control
enabled: !disabled
horizontalPadding:12
background: FluItem{
background: FluClip{
implicitWidth: 28
implicitHeight: 28
radius: [4,4,4,4]

View File

@ -31,27 +31,18 @@ T.RangeSlider {
radius: 12
}
Rectangle{
width: 24
height: 24
radius: 12
width: radius*2
height: radius*2
radius:{
if(control.first.pressed){
return 5
}
return control.first.hovered ? 7 : 6
}
color:FluTheme.dark ? FluTheme.primaryColor.lighter :FluTheme.primaryColor.dark
anchors.centerIn: parent
scale: {
if(control.first.pressed){
return 4/10
}
return control.first.hovered ? 6/10 : 5/10
}
Behavior on scale {
enabled: FluTheme.enableAnimation
NumberAnimation{
duration: 167
easing.type: Easing.OutCubic
}
}
}
}
second.handle: Rectangle {
x: control.leftPadding + (control.horizontal ? control.second.visualPosition * (control.availableWidth - width) : (control.availableWidth - width) / 2)
y: control.topPadding + (control.horizontal ? (control.availableHeight - height) / 2 : control.second.visualPosition * (control.availableHeight - height))
@ -63,27 +54,18 @@ T.RangeSlider {
radius: 12
}
Rectangle{
width: 24
height: 24
radius: 12
width: radius*2
height: radius*2
radius:{
if(control.second.pressed){
return 5
}
return control.second.hovered ? 7 : 6
}
color:FluTheme.dark ? FluTheme.primaryColor.lighter :FluTheme.primaryColor.dark
anchors.centerIn: parent
scale: {
if(control.second.pressed){
return 4/10
}
return control.second.hovered ? 6/10 : 5/10
}
Behavior on scale {
enabled: FluTheme.enableAnimation
NumberAnimation{
duration: 167
easing.type: Easing.OutCubic
}
}
}
}
background: Item {
x: control.leftPadding + (control.horizontal ? 0 : (control.availableWidth - width) / 2)
y: control.topPadding + (control.horizontal ? (control.availableHeight - height) / 2 : 0)

View File

@ -1,71 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtGraphicalEffects 1.15
import FluentUI 1.0
Item{
property var radius:[0,0,0,0]
property color color : FluTheme.dark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1)
property bool shadow: true
default property alias contentItem: container.data
id:control
onWidthChanged: {
canvas.requestPaint()
}
onHeightChanged: {
canvas.requestPaint()
}
onRadiusChanged: {
canvas.requestPaint()
}
FluShadow{
anchors.fill: container
radius: control.radius[0]
visible: {
if(control.radius[0] === control.radius[1] && control.radius[0] === control.radius[2] && control.radius[0] === control.radius[3] && control.shadow){
return true
}
return false
}
}
Rectangle{
id:container
width: control.width
height: control.height
opacity: 0
color:control.color
}
Canvas {
id: canvas
anchors.fill: parent
visible: false
onPaint: {
var ctx = getContext("2d")
var x = 0
var y = 0
var w = control.width
var h = control.height
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.save()
ctx.beginPath();
ctx.moveTo(x + radius[0], y)
ctx.lineTo(x + w - radius[1], y)
ctx.arcTo(x + w, y, x + w, y + radius[1], radius[1])
ctx.lineTo(x + w, y + h - radius[2])
ctx.arcTo(x + w, y + h, x + w - radius[2], y + h, radius[2])
ctx.lineTo(x + radius[3], y + h)
ctx.arcTo(x, y + h, x, y + h - radius[3], radius[3])
ctx.lineTo(x, y + radius[0])
ctx.arcTo(x, y, x + radius[0], y, radius[0])
ctx.closePath()
ctx.fillStyle = "#000000"
ctx.fill()
ctx.restore()
}
}
OpacityMask {
anchors.fill: container
source: container
maskSource: canvas
}
}

View File

@ -23,24 +23,16 @@ T.Slider {
radius: 12
}
Rectangle{
width: 24
height: 24
radius: 12
width: radius*2
height: radius*2
radius:{
if(control.pressed){
return 5
}
return control.hovered ? 7 : 6
}
color:FluTheme.dark ? FluTheme.primaryColor.lighter :FluTheme.primaryColor.dark
anchors.centerIn: parent
scale: {
if(control.pressed){
return 4/10
}
return control.hovered ? 6/10 : 5/10
}
Behavior on scale {
enabled: FluTheme.enableAnimation
NumberAnimation{
duration: 167
easing.type: Easing.OutCubic
}
}
}
}
background: Item {

View File

@ -68,7 +68,7 @@ T.SpinBox {
}
}
up.indicator: FluItem {
up.indicator: FluRectangle {
x: control.mirrored ? 0 : control.width - width
height: control.height
implicitWidth: 32
@ -103,7 +103,7 @@ T.SpinBox {
}
down.indicator: FluItem {
down.indicator: FluRectangle {
x: control.mirrored ? parent.width - width : 0
height: control.height
implicitWidth: 32

View File

@ -76,7 +76,7 @@ Item {
id:item_layout
width: item_container.width
height: item_container.height
FluItem{
Item{
id:item_container
property real timestamp: new Date().getTime()
height: tab_nav.height
@ -92,7 +92,6 @@ Item {
}
return Math.max(Math.min(d.maxEqualWidth,tab_nav.width/tab_nav.count),41 + item_btn_close.width)
}
radius: [6,6,0,0]
Behavior on x { enabled: d.dragBehavior; NumberAnimation { duration: 200 } }
Behavior on y { enabled: d.dragBehavior; NumberAnimation { duration: 200 } }
MouseArea{
@ -186,8 +185,9 @@ Item {
}
}
}
Rectangle{
FluRectangle{
anchors.fill: parent
radius: [6,6,0,0]
color: {
if(FluTheme.dark){
if(item_mouse_hove.containsMouse || item_btn_close.hovered){

View File

@ -141,13 +141,15 @@ Rectangle {
FluText {
id:item_text
text: itemData
anchors.fill: parent
leftPadding: 11
rightPadding: 11
topPadding: 6
bottomPadding: 6
elide: Text.ElideRight
wrapMode: Text.WrapAnywhere
anchors{
fill: parent
leftMargin: 11
rightMargin: 11
topMargin: 6
bottomMargin: 6
}
verticalAlignment: Text.AlignVCenter
HoverHandler{
id: hover_handler

View File

@ -1,38 +1,34 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtGraphicalEffects 1.15
import FluentUI 1.0
Rectangle{
FluClip{
property Item inputItem
id:content
radius: 4
layer.enabled: true
color: {
if(inputItem.disabled){
return FluTheme.dark ? Qt.rgba(59/255,59/255,59/255,1) : Qt.rgba(252/255,252/255,252/255,1)
property int borderWidth: 1
id:control
radius: [4,4,4,4]
Rectangle{
radius: 4
anchors.fill: parent
color: {
if(inputItem.disabled){
return FluTheme.dark ? Qt.rgba(59/255,59/255,59/255,1) : Qt.rgba(252/255,252/255,252/255,1)
}
if(inputItem.activeFocus){
return FluTheme.dark ? Qt.rgba(36/255,36/255,36/255,1) : Qt.rgba(1,1,1,1)
}
if(inputItem.hovered){
return FluTheme.dark ? Qt.rgba(68/255,68/255,68/255,1) : Qt.rgba(251/255,251/255,251/255,1)
}
return FluTheme.dark ? Qt.rgba(62/255,62/255,62/255,1) : Qt.rgba(1,1,1,1)
}
if(inputItem.activeFocus){
return FluTheme.dark ? Qt.rgba(36/255,36/255,36/255,1) : Qt.rgba(1,1,1,1)
border.width: control.borderWidth
border.color: {
if(inputItem.disabled){
return FluTheme.dark ? Qt.rgba(73/255,73/255,73/255,1) : Qt.rgba(237/255,237/255,237/255,1)
}
return FluTheme.dark ? Qt.rgba(76/255,76/255,76/255,1) : Qt.rgba(240/255,240/255,240/255,1)
}
if(inputItem.hovered){
return FluTheme.dark ? Qt.rgba(68/255,68/255,68/255,1) : Qt.rgba(251/255,251/255,251/255,1)
}
return FluTheme.dark ? Qt.rgba(62/255,62/255,62/255,1) : Qt.rgba(1,1,1,1)
}
layer.effect:OpacityMask {
maskSource: Rectangle {
width: content.width
height: content.height
radius: 4
}
}
border.width: 1
border.color: {
if(inputItem.disabled){
return FluTheme.dark ? Qt.rgba(73/255,73/255,73/255,1) : Qt.rgba(237/255,237/255,237/255,1)
}
return FluTheme.dark ? Qt.rgba(76/255,76/255,76/255,1) : Qt.rgba(240/255,240/255,240/255,1)
}
Rectangle{
width: parent.width

View File

@ -75,12 +75,12 @@ Button {
}
return borderNormalColor
}
Rectangle {
FluIcon {
width: parent.height
x:checked ? control_backgound.width-width : 0
height: width
radius: width/2
scale: hovered&enabled ? 7/10 : 6/10
iconSource: FluentIcons.FullCircleMask
iconSize: 20
color: {
if(!enabled){
return dotDisableColor
@ -91,14 +91,9 @@ Button {
return dotNormalColor
}
Behavior on x {
enabled: FluTheme.enableAnimation
NumberAnimation {
easing.type: Easing.OutCubic
}
}
Behavior on scale {
enabled: FluTheme.enableAnimation
NumberAnimation {
duration: 167
easing.type: Easing.OutCubic
}
}

View File

@ -2,291 +2,453 @@ import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import Qt.labs.qmlmodels 1.0
import FluentUI 1.0
Item {
property int selectionMode: FluTreeViewType.None
property var currentElement
property var currentParentElement
property var rootModel: tree_model.get(0).items
signal itemClicked(var item)
id:root
ListModel{
property int currentIndex : -1
property var dataSource
property bool showLine: true
property bool draggable: false
property int cellHeight: 30
property int depthPadding: 30
property bool checkable: false
property color lineColor: FluTheme.dark ? Qt.rgba(111/255,111/255,111/255,1) : Qt.rgba(217/255,217/255,217/255,1)
id:control
QtObject {
id:d
property int dy
property var current
property int dropIndex: -1
property bool isDropTopArea: false
property int dragIndex: -1
property color hitColor: FluTheme.dark ? FluTheme.primaryColor.lighter : FluTheme.primaryColor.dark
}
onDataSourceChanged: {
tree_model.setDataSource(dataSource)
}
FluTreeModel{
id:tree_model
ListElement{
text: "根节点"
expanded:true
items:[]
key:"123456"
multipSelected:false
multipIndex:0
multipParentKey:""
}
}
Component{
id: delegate_root
Column{
width: calculateWidth()
property var itemModel: model
Repeater{
id: repeater_first_level
model: items
delegate: delegate_items
}
function calculateWidth(){
var w = 0;
for(var i = 0; i < repeater_first_level.count; i++) {
var child = repeater_first_level.itemAt(i)
if(w < child.width_hint){
w = child.width_hint;
}
}
return w;
}
}
}
Component{
id:delegate_items
Column{
id:item_layout
property real level: (mapToItem(list_root,0,0).x+list_root.contentX)/0.001
property var text: model.text??"Item"
property bool hasChild : (model.items !== undefined) && (model.items.count !== 0)
property var items: model.items??[]
property var expanded: model.expanded??true
property int width_hint: calculateWidth()
property bool singleSelected: currentElement === model
property var itemModel: model
function calculateWidth(){
var w = Math.max(list_root.width, item_layout_row.implicitWidth + 10);
if(expanded){
for(var i = 0; i < repeater_items.count; i++) {
var child = repeater_items.itemAt(i)
if(w < child.width_hint){
w = child.width_hint;
}
}
}
return w;
}
Item{
id:item_layout_rect
width: list_root.contentWidth
height: item_layout_row.implicitHeight
Rectangle{
anchors.fill: parent
anchors.margins: 2
color:{
if(FluTheme.dark){
if(item_layout.singleSelected && selectionMode === FluTreeViewType.Single){
return Qt.rgba(62/255,62/255,62/255,1)
}
return (item_layout_mouse.containsMouse || item_layout_expanded.hovered || item_layout_checkbox.hovered)?Qt.rgba(62/255,62/255,62/255,1):Qt.rgba(0,0,0,0)
}else{
if(item_layout.singleSelected && selectionMode === FluTreeViewType.Single){
return Qt.rgba(0,0,0,0.06)
}
return (item_layout_mouse.containsMouse || item_layout_expanded.hovered || item_layout_checkbox.hovered)?Qt.rgba(0,0,0,0.03):Qt.rgba(0,0,0,0)
}
}
Rectangle{
width: 3
color:FluTheme.primaryColor.dark
visible: item_layout.singleSelected && (selectionMode === FluTreeViewType.Single)
radius: 3
height: 20
anchors{
left: parent.left
verticalCenter: parent.verticalCenter
}
}
MouseArea{
id:item_layout_mouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
item_layout_rect.onClickItem()
}
}
}
function onClickItem(){
if(selectionMode === FluTreeViewType.None){
itemClicked(model)
}
if(selectionMode === FluTreeViewType.Single){
currentElement = model
if(item_layout.parent.parent.parent.itemModel){
currentParentElement = item_layout.parent.parent.parent.itemModel
}else{
if(item_layout.parent.itemModel){
currentParentElement = item_layout.parent.itemModel
}
}
itemClicked(model)
}
if(selectionMode === FluTreeViewType.Multiple){
}
}
RowLayout{
id:item_layout_row
anchors.verticalCenter: item_layout_rect.verticalCenter
Item{
width: 15*level
Layout.alignment: Qt.AlignVCenter
}
FluCheckBox{
id:item_layout_checkbox
text:""
checked: itemModel.multipSelected
visible: selectionMode === FluTreeViewType.Multiple
Layout.leftMargin: 5
function refreshCheckBox(){
const stack = [tree_model.get(0)];
const result = [];
while (stack.length > 0) {
const curr = stack.pop();
result.unshift(curr);
if (curr.items) {
for(var i=0 ; i<curr.items.count ; i++){
curr.items.setProperty(i,"multipIndex",i)
curr.items.setProperty(i,"multipParentKey",curr.key)
stack.push(curr.items.get(i));
}
}
}
for(var j=0 ; j<result.length-1 ; j++){
var item = result[j]
let obj = result.find(function(o) {
return o.key === item.multipParentKey;
});
if((item.items !== undefined) && (item.items.count !== 0)){
var items = item.items
for(var k=0 ; k<items.count ; k++){
if(items.get(k).multipSelected === false){
obj.items.setProperty(item.multipIndex,"multipSelected",false)
break
}
obj.items.setProperty(item.multipIndex,"multipSelected",true)
}
}
}
}
clickListener:function(){
if(hasChild){
const stack = [itemModel];
while (stack.length > 0) {
const curr = stack.pop();
if (curr.items) {
for(var i=0 ; i<curr.items.count ; i++){
curr.items.setProperty(i,"multipSelected",!itemModel.multipSelected)
stack.push(curr.items.get(i));
}
}
}
refreshCheckBox()
}else{
itemModel.multipSelected = !itemModel.multipSelected
refreshCheckBox()
}
}
}
FluIconButton{
id:item_layout_expanded
color:"#00000000"
opacity: item_layout.hasChild
onClicked: {
if(!item_layout.hasChild){
item_layout_rect.onClickItem()
return
}
model.expanded = !model.expanded
}
contentItem: FluIcon{
rotation: item_layout.expanded?0:-90
iconSource:FluentIcons.ChevronDown
iconSize: 15
Behavior on rotation {
enabled: FluTheme.enableAnimation
NumberAnimation{
duration: 167
easing.type: Easing.OutCubic
}
}
}
}
FluText {
text: item_layout.text
Layout.alignment: Qt.AlignVCenter
topPadding: 7
bottomPadding: 7
}
}
}
Item{
id:item_sub
visible: {
if(!hasChild){
return false
}
return item_layout.expanded??false
}
width: item_sub_layout.implicitWidth
height: item_sub_layout.implicitHeight
x:0.001
Column{
id: item_sub_layout
Repeater{
id:repeater_items
model: item_layout.items
delegate: delegate_items
}
}
}
}
}
ListView {
id: list_root
anchors.fill: parent
delegate: delegate_root
contentWidth: contentItem.childrenRect.width
ListView{
id:table_view
ScrollBar.horizontal: FluScrollBar{}
ScrollBar.vertical: FluScrollBar{}
boundsBehavior: Flickable.StopAtBounds
model: tree_model
flickableDirection: Flickable.HorizontalAndVerticalFlick
anchors.fill: parent
clip: true
boundsBehavior: ListView.StopAtBounds
ScrollBar.vertical: FluScrollBar {}
ScrollBar.horizontal: FluScrollBar { }
}
function updateData(items){
rootModel.clear()
rootModel.append(items)
}
function signleData(){
return currentElement
}
function multipData(){
const stack = [tree_model.get(0)];
const result = [];
while (stack.length > 0) {
const curr = stack.pop();
if(curr.multipSelected){
result.push(curr)
}
for(var i=0 ; i<curr.items.count ; i++){
stack.push(curr.items.get(i));
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 {
properties: "opacity"
duration: 88
from: 0
to: 1
}
}
}
return result
}
function createItem(text="",expanded=true,items=[],data={}){
return {text:text,expanded:expanded,items:items,key:uniqueRandom(),multipSelected:false,multipIndex:0,multipParentKey:"",data:data};
}
function uniqueRandom() {
var timestamp = Date.now();
var random = Math.floor(Math.random() * 1000000);
return timestamp.toString() + random.toString();
move: Transition {
NumberAnimation { property: "y"; duration: 200 }
}
add: Transition{
ParallelAnimation{
NumberAnimation {
properties: "y"
duration: 167
from: d.dy
easing.type: Easing.OutCubic
}
NumberAnimation {
properties: "opacity"
duration: 88
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()
}
Loader{
property var itemControl: item_control
property var itemModel: modelData
property int rowIndex: index
property bool isItemLoader: true
id:item_loader_container
sourceComponent: com_item_container
}
}
Loader{
id:loader_container
property var itemControl
property var itemModel
property bool isItemLoader: false
}
}
Component{
id:com_item_container
Item{
signal reused
signal pooled
onReused: {
}
onPooled: {
}
property bool isCurrent: d.current === itemModel
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
height: 18
radius: 1.5
color: FluTheme.primaryColor.dark
visible: isCurrent
anchors{
left: parent.left
leftMargin: 6
verticalCenter: parent.verticalCenter
}
}
MouseArea{
id:item_mouse
property point clickPos: Qt.point(0,0)
anchors.fill: parent
drag.target:control.draggable ? loader_container : undefined
hoverEnabled: true
drag.onActiveChanged: {
if(drag.active){
if(itemModel.isExpanded && itemModel.hasChildren()){
tree_model.collapse(rowIndex)
}
d.dragIndex = rowIndex
loader_container.sourceComponent = com_item_container
}
}
onPressed:
(mouse)=>{
clickPos = Qt.point(mouse.x,mouse.y)
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: {
d.current = itemModel
}
onDoubleClicked: {
if(itemModel.hasChildren()){
item_container.toggle()
}
}
onPositionChanged:
(mouse)=> {
if(!drag.active){
return
}
var cellPosition = item_container.mapToItem(table_view, 0, 0)
if(mouse.y+cellPosition.y<0 || mouse.y+cellPosition.y>table_view.height){
d.dropIndex = -1
return
}
if((mouse.x-table_view.contentX)>table_view.width || (mouse.x-table_view.contentX)<0){
d.dropIndex = -1
return
}
var pos = FluTools.cursorPos()
var viewPos = table_view.mapToGlobal(0,0)
var y = table_view.contentY + pos.y-viewPos.y
var index = Math.floor(y/control.cellHeight)
if(index<0 || index>table_view.count-1){
d.dropIndex = -1
return
}
console.debug(index)
if(tree_model.hitHasChildrenExpanded(index) && y>index*control.cellHeight+control.cellHeight/2){
d.dropIndex = index + 1
d.isDropTopArea = true
}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{
width: 1
color: control.lineColor
visible: control.showLine && isItemLoader && itemModel.depth !== 0 && !itemModel.hasChildren()
height: itemModel.hideLineFooter() ? parent.height/2 : parent.height
anchors{
top: parent.top
left: item_line_h.left
}
}
FluRectangle{
id:item_line_h
height: 1
color: control.lineColor
visible: control.showLine && isItemLoader && itemModel.depth !== 0 && !itemModel.hasChildren()
width: depthPadding - 10
anchors{
right: layout_row.left
rightMargin: -24
verticalCenter: parent.verticalCenter
}
}
Repeater{
model: Math.max(itemModel.depth-1,0)
delegate: FluRectangle{
required property int index
width: 1
color: control.lineColor
visible: control.showLine && isItemLoader && itemModel.depth !== 0 && itemModel.hasNextNodeByIndex(index)
anchors{
top:parent.top
bottom: parent.bottom
left: parent.left
leftMargin: control.depthPadding*(index+1) + 24
}
}
}
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(FluTheme.dark){
if(isCurrent){
return Qt.rgba(1,1,1,0.06)
}
if(item_mouse.containsMouse || item_check_box.hovered){
return Qt.rgba(1,1,1,0.03)
}
if(item_loader_expand.item && item_loader_expand.item.hovered){
return Qt.rgba(1,1,1,0.03)
}
return Qt.rgba(0,0,0,0)
}else{
if(isCurrent){
return Qt.rgba(0,0,0,0.06)
}
if(item_mouse.containsMouse || item_check_box.hovered){
return Qt.rgba(0,0,0,0.03)
}
if(item_loader_expand.item && item_loader_expand.item.hovered){
return Qt.rgba(0,0,0,0.03)
}
return Qt.rgba(0,0,0,0)
}
}
}
RowLayout{
id:layout_row
height: parent.height
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
spacing: 0
anchors.leftMargin: 14 + control.depthPadding*itemModel.depth
Component{
id:com_icon_btn
FluIconButton{
opacity: itemModel.hasChildren()
onClicked: {
item_container.toggle()
}
contentItem:FluIcon{
rotation: itemModel.isExpanded?0:-90
iconSource:FluentIcons.ChevronDown
iconSize: 16
anchors.centerIn: parent
}
}
}
Loader{
id:item_loader_expand
Layout.preferredWidth: 20
Layout.preferredHeight: 20
sourceComponent: itemModel.hasChildren() ? com_icon_btn : undefined
Layout.alignment: Qt.AlignVCenter
}
FluCheckBox{
id:item_check_box
Layout.preferredWidth: 18
Layout.preferredHeight: 18
Layout.leftMargin: 5
horizontalPadding:0
verticalPadding: 0
checked: itemModel.checked
enableAnimation:false
visible: control.checkable
padding: 0
clickListener: function(){
tree_model.checkRow(rowIndex,!itemModel.checked)
}
Layout.alignment: Qt.AlignVCenter
}
Loader{
property var modelData: itemModel
property var itemMouse: item_mouse
id:item_loader_cell
Layout.leftMargin: 10
Layout.preferredWidth: {
if(item){
return item.width
}
return 0
}
Layout.fillHeight: true
sourceComponent:com_item_text
}
}
}
}
Component{
id:com_item_text
Item{
width: item_text.width
FluText {
id:item_text
text: modelData.title
rightPadding: 14
anchors.centerIn: parent
color:{
if(itemMouse.pressed){
return FluTheme.dark ? FluColors.Grey80 : FluColors.Grey120
}
return FluTheme.dark ? FluColors.White : FluColors.Grey220
}
}
}
}
function selectionModel(){
return tree_model.selectionModel
}
function count(){
return tree_model.dataSourceSize
}
function visibleCount(){
return table_view.count
}
function collapse(rowIndex){
tree_model.collapse(rowIndex)
}
function expand(rowIndex){
tree_model.expand(rowIndex)
}
function allExpand(){
tree_model.allExpand()
}
function allCollapse(){
tree_model.allCollapse()
}
}

View File

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

View File

@ -29,6 +29,7 @@ Module {
exports: ["FluentUI/FluCaptcha 1.0"]
exportMetaObjectRevisions: [0]
Property { name: "font"; type: "QFont" }
Property { name: "ignoreCase"; type: "bool" }
Method { name: "refresh" }
Method {
name: "verify"
@ -185,6 +186,15 @@ Module {
}
}
}
Component {
name: "FluRectangle"
defaultProperty: "data"
prototype: "QQuickPaintedItem"
exports: ["FluentUI/FluRectangle 1.0"]
exportMetaObjectRevisions: [0]
Property { name: "color"; type: "QColor" }
Property { name: "radius"; type: "QList<int>" }
}
Component {
name: "FluScreenshotType"
exports: ["FluentUI/FluScreenshotType 1.0"]
@ -276,6 +286,49 @@ Module {
}
}
}
Component {
name: "FluTreeModel"
prototype: "QAbstractTableModel"
exports: ["FluentUI/FluTreeModel 1.0"]
exportMetaObjectRevisions: [0]
Property { name: "dataSourceSize"; type: "int" }
Method {
name: "removeRows"
Parameter { name: "row"; type: "int" }
Parameter { name: "count"; type: "int" }
}
Method {
name: "insertRows"
Parameter { name: "row"; type: "int" }
Parameter { name: "data"; type: "QList<Node*>" }
}
Method {
name: "getRow"
type: "QObject*"
Parameter { name: "row"; type: "int" }
}
Method {
name: "setData"
Parameter { name: "data"; type: "QList<Node*>" }
}
Method {
name: "setDataSource"
Parameter { name: "data"; type: "QList<QMap<QString,QVariant> >" }
}
Method {
name: "collapse"
Parameter { name: "row"; type: "int" }
}
Method {
name: "expand"
Parameter { name: "row"; type: "int" }
}
Method {
name: "dragAnddrop"
Parameter { name: "dragIndex"; type: "int" }
Parameter { name: "dropIndex"; type: "int" }
}
}
Component {
name: "FluTreeViewType"
exports: ["FluentUI/FluTreeViewType 1.0"]
@ -1797,6 +1850,280 @@ Module {
Property { name: "downloadSavePath"; type: "string" }
Method { name: "httpId"; type: "string" }
}
Component {
name: "QAbstractItemModel"
prototype: "QObject"
Enum {
name: "LayoutChangeHint"
values: {
"NoLayoutChangeHint": 0,
"VerticalSortHint": 1,
"HorizontalSortHint": 2
}
}
Enum {
name: "CheckIndexOption"
values: {
"NoOption": 0,
"IndexIsValid": 1,
"DoNotUseParent": 2,
"ParentIsInvalid": 4
}
}
Signal {
name: "dataChanged"
Parameter { name: "topLeft"; type: "QModelIndex" }
Parameter { name: "bottomRight"; type: "QModelIndex" }
Parameter { name: "roles"; type: "QVector<int>" }
}
Signal {
name: "dataChanged"
Parameter { name: "topLeft"; type: "QModelIndex" }
Parameter { name: "bottomRight"; type: "QModelIndex" }
}
Signal {
name: "headerDataChanged"
Parameter { name: "orientation"; type: "Qt::Orientation" }
Parameter { name: "first"; type: "int" }
Parameter { name: "last"; type: "int" }
}
Signal {
name: "layoutChanged"
Parameter { name: "parents"; type: "QList<QPersistentModelIndex>" }
Parameter { name: "hint"; type: "QAbstractItemModel::LayoutChangeHint" }
}
Signal {
name: "layoutChanged"
Parameter { name: "parents"; type: "QList<QPersistentModelIndex>" }
}
Signal { name: "layoutChanged" }
Signal {
name: "layoutAboutToBeChanged"
Parameter { name: "parents"; type: "QList<QPersistentModelIndex>" }
Parameter { name: "hint"; type: "QAbstractItemModel::LayoutChangeHint" }
}
Signal {
name: "layoutAboutToBeChanged"
Parameter { name: "parents"; type: "QList<QPersistentModelIndex>" }
}
Signal { name: "layoutAboutToBeChanged" }
Signal {
name: "rowsAboutToBeInserted"
Parameter { name: "parent"; type: "QModelIndex" }
Parameter { name: "first"; type: "int" }
Parameter { name: "last"; type: "int" }
}
Signal {
name: "rowsInserted"
Parameter { name: "parent"; type: "QModelIndex" }
Parameter { name: "first"; type: "int" }
Parameter { name: "last"; type: "int" }
}
Signal {
name: "rowsAboutToBeRemoved"
Parameter { name: "parent"; type: "QModelIndex" }
Parameter { name: "first"; type: "int" }
Parameter { name: "last"; type: "int" }
}
Signal {
name: "rowsRemoved"
Parameter { name: "parent"; type: "QModelIndex" }
Parameter { name: "first"; type: "int" }
Parameter { name: "last"; type: "int" }
}
Signal {
name: "columnsAboutToBeInserted"
Parameter { name: "parent"; type: "QModelIndex" }
Parameter { name: "first"; type: "int" }
Parameter { name: "last"; type: "int" }
}
Signal {
name: "columnsInserted"
Parameter { name: "parent"; type: "QModelIndex" }
Parameter { name: "first"; type: "int" }
Parameter { name: "last"; type: "int" }
}
Signal {
name: "columnsAboutToBeRemoved"
Parameter { name: "parent"; type: "QModelIndex" }
Parameter { name: "first"; type: "int" }
Parameter { name: "last"; type: "int" }
}
Signal {
name: "columnsRemoved"
Parameter { name: "parent"; type: "QModelIndex" }
Parameter { name: "first"; type: "int" }
Parameter { name: "last"; type: "int" }
}
Signal { name: "modelAboutToBeReset" }
Signal { name: "modelReset" }
Signal {
name: "rowsAboutToBeMoved"
Parameter { name: "sourceParent"; type: "QModelIndex" }
Parameter { name: "sourceStart"; type: "int" }
Parameter { name: "sourceEnd"; type: "int" }
Parameter { name: "destinationParent"; type: "QModelIndex" }
Parameter { name: "destinationRow"; type: "int" }
}
Signal {
name: "rowsMoved"
Parameter { name: "parent"; type: "QModelIndex" }
Parameter { name: "start"; type: "int" }
Parameter { name: "end"; type: "int" }
Parameter { name: "destination"; type: "QModelIndex" }
Parameter { name: "row"; type: "int" }
}
Signal {
name: "columnsAboutToBeMoved"
Parameter { name: "sourceParent"; type: "QModelIndex" }
Parameter { name: "sourceStart"; type: "int" }
Parameter { name: "sourceEnd"; type: "int" }
Parameter { name: "destinationParent"; type: "QModelIndex" }
Parameter { name: "destinationColumn"; type: "int" }
}
Signal {
name: "columnsMoved"
Parameter { name: "parent"; type: "QModelIndex" }
Parameter { name: "start"; type: "int" }
Parameter { name: "end"; type: "int" }
Parameter { name: "destination"; type: "QModelIndex" }
Parameter { name: "column"; type: "int" }
}
Method { name: "submit"; type: "bool" }
Method { name: "revert" }
Method {
name: "hasIndex"
type: "bool"
Parameter { name: "row"; type: "int" }
Parameter { name: "column"; type: "int" }
Parameter { name: "parent"; type: "QModelIndex" }
}
Method {
name: "hasIndex"
type: "bool"
Parameter { name: "row"; type: "int" }
Parameter { name: "column"; type: "int" }
}
Method {
name: "index"
type: "QModelIndex"
Parameter { name: "row"; type: "int" }
Parameter { name: "column"; type: "int" }
Parameter { name: "parent"; type: "QModelIndex" }
}
Method {
name: "index"
type: "QModelIndex"
Parameter { name: "row"; type: "int" }
Parameter { name: "column"; type: "int" }
}
Method {
name: "parent"
type: "QModelIndex"
Parameter { name: "child"; type: "QModelIndex" }
}
Method {
name: "sibling"
type: "QModelIndex"
Parameter { name: "row"; type: "int" }
Parameter { name: "column"; type: "int" }
Parameter { name: "idx"; type: "QModelIndex" }
}
Method {
name: "rowCount"
type: "int"
Parameter { name: "parent"; type: "QModelIndex" }
}
Method { name: "rowCount"; type: "int" }
Method {
name: "columnCount"
type: "int"
Parameter { name: "parent"; type: "QModelIndex" }
}
Method { name: "columnCount"; type: "int" }
Method {
name: "hasChildren"
type: "bool"
Parameter { name: "parent"; type: "QModelIndex" }
}
Method { name: "hasChildren"; type: "bool" }
Method {
name: "data"
type: "QVariant"
Parameter { name: "index"; type: "QModelIndex" }
Parameter { name: "role"; type: "int" }
}
Method {
name: "data"
type: "QVariant"
Parameter { name: "index"; type: "QModelIndex" }
}
Method {
name: "setData"
type: "bool"
Parameter { name: "index"; type: "QModelIndex" }
Parameter { name: "value"; type: "QVariant" }
Parameter { name: "role"; type: "int" }
}
Method {
name: "setData"
type: "bool"
Parameter { name: "index"; type: "QModelIndex" }
Parameter { name: "value"; type: "QVariant" }
}
Method {
name: "headerData"
type: "QVariant"
Parameter { name: "section"; type: "int" }
Parameter { name: "orientation"; type: "Qt::Orientation" }
Parameter { name: "role"; type: "int" }
}
Method {
name: "headerData"
type: "QVariant"
Parameter { name: "section"; type: "int" }
Parameter { name: "orientation"; type: "Qt::Orientation" }
}
Method {
name: "fetchMore"
Parameter { name: "parent"; type: "QModelIndex" }
}
Method {
name: "canFetchMore"
type: "bool"
Parameter { name: "parent"; type: "QModelIndex" }
}
Method {
name: "flags"
type: "Qt::ItemFlags"
Parameter { name: "index"; type: "QModelIndex" }
}
Method {
name: "match"
type: "QModelIndexList"
Parameter { name: "start"; type: "QModelIndex" }
Parameter { name: "role"; type: "int" }
Parameter { name: "value"; type: "QVariant" }
Parameter { name: "hits"; type: "int" }
Parameter { name: "flags"; type: "Qt::MatchFlags" }
}
Method {
name: "match"
type: "QModelIndexList"
Parameter { name: "start"; type: "QModelIndex" }
Parameter { name: "role"; type: "int" }
Parameter { name: "value"; type: "QVariant" }
Parameter { name: "hits"; type: "int" }
}
Method {
name: "match"
type: "QModelIndexList"
Parameter { name: "start"; type: "QModelIndex" }
Parameter { name: "role"; type: "int" }
Parameter { name: "value"; type: "QVariant" }
}
}
Component { name: "QAbstractTableModel"; prototype: "QAbstractItemModel" }
Component {
name: "QRCode"
defaultProperty: "data"
@ -1934,7 +2261,7 @@ Module {
exports: ["FluentUI/FluAcrylic 1.0"]
exportMetaObjectRevisions: [0]
isComposite: true
defaultProperty: "contentItem"
defaultProperty: "data"
Property { name: "tintColor"; type: "QColor" }
Property { name: "tintOpacity"; type: "double" }
Property { name: "luminosity"; type: "double" }
@ -1942,8 +2269,6 @@ Module {
Property { name: "blurRadius"; type: "int" }
Property { name: "targetRect"; type: "QRectF" }
Property { name: "target"; type: "QQuickItem"; isPointer: true }
Property { name: "radius"; type: "QVariant" }
Property { name: "contentItem"; type: "QObject"; isList: true; isReadonly: true }
}
Component {
prototype: "QQuickRectangle"
@ -2159,7 +2484,7 @@ Module {
exports: ["FluentUI/FluCarousel 1.0"]
exportMetaObjectRevisions: [0]
isComposite: true
defaultProperty: "contentItem"
defaultProperty: "data"
Property { name: "autoPlay"; type: "bool" }
Property { name: "loopTime"; type: "int" }
Property { name: "model"; type: "QVariant" }
@ -2178,8 +2503,6 @@ Module {
type: "QVariant"
Parameter { name: "index"; type: "QVariant" }
}
Property { name: "radius"; type: "QVariant" }
Property { name: "contentItem"; type: "QObject"; isList: true; isReadonly: true }
}
Component {
prototype: "QQuickCanvasItem"
@ -2229,6 +2552,14 @@ Module {
Property { name: "clickListener"; type: "QVariant" }
Property { name: "textColor"; type: "QColor" }
}
Component {
prototype: "FluRectangle"
name: "FluentUI/FluClip 1.0"
exports: ["FluentUI/FluClip 1.0"]
exportMetaObjectRevisions: [0]
isComposite: true
defaultProperty: "data"
}
Component {
prototype: "QQuickButton"
name: "FluentUI/FluColorPicker 1.0"
@ -2530,16 +2861,6 @@ Module {
}
Property { name: "children"; type: "QObject"; isList: true; isReadonly: true }
}
Component {
prototype: "QQuickItem"
name: "FluentUI/FluItem 1.0"
exports: ["FluentUI/FluItem 1.0"]
exportMetaObjectRevisions: [0]
isComposite: true
defaultProperty: "contentItem"
Property { name: "radius"; type: "QVariant" }
Property { name: "contentItem"; type: "QObject"; isList: true; isReadonly: true }
}
Component {
prototype: "QQuickItemDelegate"
name: "FluentUI/FluItemDelegate 1.0"
@ -2638,16 +2959,16 @@ Module {
defaultProperty: "data"
Property { name: "logo"; type: "QUrl" }
Property { name: "title"; type: "string" }
Property { name: "items"; type: "FluObject_QMLTYPE_156"; isPointer: true }
Property { name: "footerItems"; type: "FluObject_QMLTYPE_156"; isPointer: true }
Property { name: "items"; type: "FluObject_QMLTYPE_148"; isPointer: true }
Property { name: "footerItems"; type: "FluObject_QMLTYPE_148"; isPointer: true }
Property { name: "displayMode"; type: "int" }
Property { name: "autoSuggestBox"; type: "QQmlComponent"; isPointer: true }
Property { name: "actionItem"; type: "QQmlComponent"; isPointer: true }
Property { name: "topPadding"; type: "int" }
Property { name: "navWidth"; type: "int" }
Property { name: "pageMode"; type: "int" }
Property { name: "navItemRightMenu"; type: "FluMenu_QMLTYPE_48"; isPointer: true }
Property { name: "navItemExpanderRightMenu"; type: "FluMenu_QMLTYPE_48"; isPointer: true }
Property { name: "navItemRightMenu"; type: "FluMenu_QMLTYPE_52"; isPointer: true }
Property { name: "navItemExpanderRightMenu"; type: "FluMenu_QMLTYPE_52"; isPointer: true }
Signal { name: "logoClicked" }
Method { name: "collapseAll"; type: "QVariant" }
Method {
@ -2961,18 +3282,6 @@ Module {
Property { name: "size"; type: "int" }
Property { name: "value"; type: "int" }
}
Component {
prototype: "QQuickItem"
name: "FluentUI/FluRectangle 1.0"
exports: ["FluentUI/FluRectangle 1.0"]
exportMetaObjectRevisions: [0]
isComposite: true
defaultProperty: "contentItem"
Property { name: "radius"; type: "QVariant" }
Property { name: "color"; type: "QColor" }
Property { name: "shadow"; type: "bool" }
Property { name: "contentItem"; type: "QObject"; isList: true; isReadonly: true }
}
Component {
prototype: "QQuickItem"
name: "FluentUI/FluRemoteLoader 1.0"
@ -3221,13 +3530,14 @@ Module {
}
}
Component {
prototype: "QQuickRectangle"
prototype: "FluRectangle"
name: "FluentUI/FluTextBoxBackground 1.0"
exports: ["FluentUI/FluTextBoxBackground 1.0"]
exportMetaObjectRevisions: [0]
isComposite: true
defaultProperty: "data"
Property { name: "inputItem"; type: "QQuickItem"; isPointer: true }
Property { name: "borderWidth"; type: "int" }
}
Component {
prototype: "QQuickMenu"
@ -3362,30 +3672,13 @@ Module {
exportMetaObjectRevisions: [0]
isComposite: true
defaultProperty: "data"
Property { name: "selectionMode"; type: "int" }
Property { name: "currentElement"; type: "QVariant" }
Property { name: "currentParentElement"; type: "QVariant" }
Property { name: "rootModel"; type: "QVariant" }
Signal {
name: "itemClicked"
Parameter { name: "item"; type: "QVariant" }
}
Method {
name: "updateData"
type: "QVariant"
Parameter { name: "items"; type: "QVariant" }
}
Method { name: "signleData"; type: "QVariant" }
Method { name: "multipData"; type: "QVariant" }
Method {
name: "createItem"
type: "QVariant"
Parameter { name: "text"; type: "QVariant" }
Parameter { name: "expanded"; type: "QVariant" }
Parameter { name: "items"; type: "QVariant" }
Parameter { name: "data"; type: "QVariant" }
}
Method { name: "uniqueRandom"; type: "QVariant" }
Property { name: "currentIndex"; type: "int" }
Property { name: "dataSource"; type: "QVariant" }
Property { name: "showLine"; type: "bool" }
Property { name: "draggable"; type: "bool" }
Property { name: "lineColor"; type: "QColor" }
Method { name: "count"; type: "QVariant" }
Method { name: "visibleCount"; type: "QVariant" }
}
Component {
prototype: "QQuickWindowQmlImpl"

View File

@ -39,7 +39,6 @@ FluIcon 1.0 Controls/FluIcon.qml
FluIconButton 1.0 Controls/FluIconButton.qml
FluImage 1.0 Controls/FluImage.qml
FluInfoBar 1.0 Controls/FluInfoBar.qml
FluItem 1.0 Controls/FluItem.qml
FluItemDelegate 1.0 Controls/FluItemDelegate.qml
FluMenu 1.0 Controls/FluMenu.qml
FluMenuBar 1.0 Controls/FluMenuBar.qml
@ -66,7 +65,6 @@ FluQRCode 1.0 Controls/FluQRCode.qml
FluRadioButton 1.0 Controls/FluRadioButton.qml
FluRadioButtons 1.0 Controls/FluRadioButtons.qml
FluRatingControl 1.0 Controls/FluRatingControl.qml
FluRectangle 1.0 Controls/FluRectangle.qml
FluRemoteLoader 1.0 Controls/FluRemoteLoader.qml
FluScreenshot 1.0 Controls/FluScreenshot.qml
FluScrollBar 1.0 Controls/FluScrollBar.qml
@ -96,4 +94,5 @@ FluRangeSlider 1.0 Controls/FluRangeSlider.qml
FluStaggeredView 1.0 Controls/FluStaggeredView.qml
FluProgressButton 1.0 Controls/FluProgressButton.qml
FluLoadingButton 1.0 Controls/FluLoadingButton.qml
FluClip 1.0 Controls/FluClip.qml
plugin fluentuiplugin

View File

@ -33,7 +33,6 @@
<file>FluentUI/Controls/FluIconButton.qml</file>
<file>FluentUI/Controls/FluImage.qml</file>
<file>FluentUI/Controls/FluInfoBar.qml</file>
<file>FluentUI/Controls/FluItem.qml</file>
<file>FluentUI/Controls/FluItemDelegate.qml</file>
<file>FluentUI/Controls/FluMenu.qml</file>
<file>FluentUI/Controls/FluMenuBar.qml</file>
@ -62,7 +61,6 @@
<file>FluentUI/Controls/FluRadioButtons.qml</file>
<file>FluentUI/Controls/FluRangeSlider.qml</file>
<file>FluentUI/Controls/FluRatingControl.qml</file>
<file>FluentUI/Controls/FluRectangle.qml</file>
<file>FluentUI/Controls/FluRemoteLoader.qml</file>
<file>FluentUI/Controls/FluScreenshot.qml</file>
<file>FluentUI/Controls/FluScrollablePage.qml</file>
@ -96,5 +94,6 @@
<file>FluentUI/Controls/ColorPicker/Content/PanelBorder.qml</file>
<file>FluentUI/Controls/ColorPicker/Content/SBPicker.qml</file>
<file>FluentUI/Controls/FluLoadingButton.qml</file>
<file>FluentUI/Controls/FluClip.qml</file>
</qresource>
</RCC>

View File

@ -2,7 +2,7 @@ import QtQuick
import Qt5Compat.GraphicalEffects
import FluentUI
FluItem {
Item {
id: control
property color tintColor: Qt.rgba(1,1,1,1)
property real tintOpacity: 0.65

View File

@ -2,7 +2,7 @@ import QtQuick
import QtQuick.Controls
import FluentUI
FluItem {
Item {
property bool autoPlay: true
property int loopTime: 2000
property var model

View File

@ -23,6 +23,7 @@ Button {
property alias textColor: btn_text.textColor
property bool textRight: true
property real textSpacing: 6
property bool enableAnimation: FluTheme.enableAnimation
property var clickListener : function(){
checked = !checked
}
@ -35,8 +36,9 @@ Button {
visible: control.activeFocus
}
}
horizontalPadding:2
verticalPadding: 2
horizontalPadding:0
verticalPadding: 0
padding: 0
Accessible.role: Accessible.Button
Accessible.name: control.text
Accessible.description: contentDescription
@ -87,7 +89,7 @@ Button {
return normalColor
}
Behavior on color {
enabled: FluTheme.enableAnimation
enabled: control.enableAnimation
ColorAnimation{
duration: 83
}
@ -99,7 +101,7 @@ Button {
visible: checked
iconColor: FluTheme.dark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1)
Behavior on visible {
enabled: FluTheme.enableAnimation
enabled: control.enableAnimation
NumberAnimation{
duration: 83
}

View File

@ -0,0 +1,19 @@
import QtQuick
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import FluentUI
FluRectangle {
id:control
color: "#00000000"
layer.enabled: !FluTools.isSoftware()
layer.effect: OpacityMask{
maskSource: ShaderEffectSource{
sourceItem: FluRectangle{
radius: control.radius
width: control.width
height: control.height
}
}
}
}

View File

@ -4,7 +4,7 @@ import QtQuick.Controls.Basic
import FluentUI
import QtQuick.Templates as T
ComboBox {
T.ComboBox {
id: control
signal commit(string text)
property bool disabled: false
@ -62,7 +62,7 @@ ComboBox {
bottomInset:1
rightInset:1
background: FluTextBoxBackground{
border.width: 0
borderWidth: 0
inputItem: contentItem
}
Component.onCompleted: {
@ -72,6 +72,7 @@ ComboBox {
Keys.onReturnPressed:(event)=> handleCommit(event)
function handleCommit(event){
control.commit(control.editText)
accepted()
}
}

View File

@ -5,14 +5,13 @@ import FluentUI
Rectangle {
property real spacing
property alias separatorHeight:separator.height
id:root
id:control
color:Qt.rgba(0,0,0,0)
height: spacing*2+separator.height
Rectangle{
FluRectangle{
id:separator
color: FluTheme.dark ? Qt.rgba(80/255,80/255,80/255,1) : Qt.rgba(210/255,210/255,210/255,1)
width:parent.width
anchors.centerIn: parent
color: FluTheme.dark ? Qt.rgba(80/255,80/255,80/255,1) : Qt.rgba(210/255,210/255,210/255,1)
width:parent.width
anchors.centerIn: parent
}
}

View File

@ -78,6 +78,7 @@ Item {
width: parent.width
height: parent.height
radius: 4
clip: true
color: FluTheme.dark ? Qt.rgba(39/255,39/255,39/255,1) : Qt.rgba(251/255,251/255,253/255,1)
border.color: FluTheme.dark ? Qt.rgba(45/255,45/255,45/255,1) : Qt.rgba(226/255,229/255,234/255,1)
y: -contentHeight

View File

@ -83,7 +83,7 @@ FluObject {
}
Timer {
id:delayTimer
interval: duration; running: true; repeat: true
interval: duration; running: duration > 0; repeat: duration > 0
onTriggered: content.close();
}
Loader{
@ -184,10 +184,47 @@ FluObject {
}
}
FluText{
text:_super.text
wrapMode: Text.WrapAnywhere
width: Math.min(implicitWidth,mcontrol.maxWidth)
Column{
spacing: 5
FluText{
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

@ -1,57 +0,0 @@
import QtQuick
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
Item{
property var radius:[0,0,0,0]
default property alias contentItem: container.data
id:control
Item{
id:container
width: control.width
height: control.height
opacity: 0
}
onWidthChanged: {
canvas.requestPaint()
}
onHeightChanged: {
canvas.requestPaint()
}
onRadiusChanged: {
canvas.requestPaint()
}
Canvas {
id: canvas
anchors.fill: parent
visible: false
onPaint: {
var ctx = getContext("2d");
var x = 0;
var y = 0;
var w = control.width;
var h = control.height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.beginPath();
ctx.moveTo(x + radius[0], y);
ctx.lineTo(x + w - radius[1], y);
ctx.arcTo(x + w, y, x + w, y + radius[1], radius[1]);
ctx.lineTo(x + w, y + h - radius[2]);
ctx.arcTo(x + w, y + h, x + w - radius[2], y + h, radius[2]);
ctx.lineTo(x + radius[3], y + h);
ctx.arcTo(x, y + h, x, y + h - radius[3], radius[3]);
ctx.lineTo(x, y + radius[0]);
ctx.arcTo(x, y, x + radius[0], y, radius[0]);
ctx.closePath();
ctx.fillStyle = control.color;
ctx.fill();
ctx.restore();
}
}
OpacityMask {
anchors.fill: container
source: container
maskSource: canvas
}
}

View File

@ -5,28 +5,28 @@ import FluentUI
T.ItemDelegate {
id: control
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding,
implicitIndicatorHeight + topPadding + bottomPadding)
padding: 12
spacing: 8
icon.width: 24
icon.height: 24
padding: 0
verticalPadding: 8
horizontalPadding: 10
icon.color: control.palette.text
contentItem: FluText {
contentItem:FluText {
text: control.text
font: control.font
color:{
if(control.down){
return FluTheme.dark ? FluColors.Grey80 : FluColors.Grey120
}
return FluTheme.dark ? FluColors.White : FluColors.Grey220
}
}
background: Rectangle {
implicitWidth: 100
implicitHeight: 40
implicitHeight: 30
color:{
if(FluTheme.dark){
return Qt.rgba(1,1,1,0.05)

View File

@ -113,8 +113,16 @@ Item {
id:com_panel_item_separatorr
FluDivider{
width: layout_list.width
spacing: model.spacing
spacing: {
if(model){
return model.spacing
}
return 1
}
separatorHeight: {
if(!model){
return 1
}
if(model.parent){
return model.parent.isExpand ? model.size : 0
}
@ -201,6 +209,9 @@ Item {
verticalCenterOffset: -8
}
visible: {
if(!model){
return false
}
if(!model.isExpand){
for(var i=0;i<model.children.length;i++){
var item = model.children[i]
@ -221,6 +232,12 @@ Item {
radius: 1.5
color: FluTheme.primaryColor.dark
visible: {
if(!model){
return false
}
if(!model.children){
return false
}
for(var i=0;i<model.children.length;i++){
var item = model.children[i]
if(item._idx === nav_list.currentIndex && !model.isExpand){
@ -235,7 +252,7 @@ Item {
}
FluIcon{
id:item_icon_expand
rotation: model.isExpand?0:180
rotation: model&&model.isExpand?0:180
iconSource:FluentIcons.ChevronUp
iconSize: 15
anchors{
@ -280,7 +297,7 @@ Item {
id:com_icon
FluIcon{
iconSource: {
if(model.icon){
if(model&&model.icon){
return model.icon
}
return 0
@ -300,7 +317,7 @@ Item {
Loader{
anchors.centerIn: parent
sourceComponent: {
if(model.cusIcon){
if(model&&model.cusIcon){
return model.cusIcon
}
return com_icon
@ -309,7 +326,12 @@ Item {
}
FluText{
id:item_title
text:model.title
text:{
if(model){
return model.title
}
return ""
}
visible: {
if(d.isCompactAndNotPanel){
return false
@ -342,7 +364,7 @@ Item {
if(d.isCompact){
return undefined
}
return model.showEdit ? model.editDelegate : undefined
return model&&model.showEdit ? model.editDelegate : undefined
}
onStatusChanged: {
if(status === Loader.Ready){
@ -378,13 +400,13 @@ Item {
}
}
height: {
if(model.parent){
if(model&&model.parent){
return model.parent.isExpand ? 38 : 0
}
return 38
}
visible: {
if(model.parent){
if(model&&model.parent){
return model.parent.isExpand ? true : false
}
return true
@ -498,7 +520,7 @@ Item {
id:com_icon
FluIcon{
iconSource: {
if(model.icon){
if(model&&model.icon){
return model.icon
}
return 0
@ -518,7 +540,7 @@ Item {
Loader{
anchors.centerIn: parent
sourceComponent: {
if(model.cusIcon){
if(model&&model.cusIcon){
return model.cusIcon
}
return com_icon
@ -527,7 +549,12 @@ Item {
}
FluText{
id:item_title
text:model.title
text:{
if(model){
return model.title
}
return ""
}
visible: {
if(d.isCompactAndNotPanel){
return false
@ -560,6 +587,9 @@ Item {
if(d.isCompact){
return undefined
}
if(!model){
return undefined
}
return model.showEdit ? model.editDelegate : undefined
}
onStatusChanged: {
@ -592,7 +622,7 @@ Item {
verticalCenterOffset: isDot ? -8 : 0
}
sourceComponent: {
if(model.infoBadge){
if(model&&model.infoBadge){
return model.infoBadge
}
return undefined
@ -772,7 +802,9 @@ Item {
anchors.fill: loader_content
onDropped:
(drag)=>{
drag.source.modelData.dropped(drag)
if(drag.source.modelData){
drag.source.modelData.dropped(drag)
}
}
}
Loader{
@ -939,6 +971,8 @@ Item {
property var _idx: index
property int type: 0
sourceComponent: {
if(model === null || !model)
return undefined
if(modelData instanceof FluPaneItem){
return com_panel_item
}
@ -954,6 +988,7 @@ Item {
if(modelData instanceof FluPaneItemEmpty){
return com_panel_item_empty
}
return undefined
}
}
}

View File

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

View File

@ -19,7 +19,7 @@ ProgressBar{
color: control.backgroundColor
radius: d._radius
}
contentItem: FluItem {
contentItem: FluClip {
clip: true
radius: [d._radius,d._radius,d._radius,d._radius]
Rectangle {

View File

@ -41,7 +41,7 @@ Button {
id: control
enabled: !disabled
horizontalPadding:12
background: FluItem{
background: FluClip{
implicitWidth: 28
implicitHeight: 28
radius: [4,4,4,4]

View File

@ -32,27 +32,18 @@ T.RangeSlider {
radius: 12
}
Rectangle{
width: 24
height: 24
radius: 12
width: radius*2
height: radius*2
radius:{
if(control.first.pressed){
return 5
}
return control.first.hovered ? 7 : 6
}
color:FluTheme.dark ? FluTheme.primaryColor.lighter :FluTheme.primaryColor.dark
anchors.centerIn: parent
scale: {
if(control.first.pressed){
return 4/10
}
return control.first.hovered ? 6/10 : 5/10
}
Behavior on scale {
enabled: FluTheme.enableAnimation
NumberAnimation{
duration: 167
easing.type: Easing.OutCubic
}
}
}
}
second.handle: Rectangle {
x: control.leftPadding + (control.horizontal ? control.second.visualPosition * (control.availableWidth - width) : (control.availableWidth - width) / 2)
y: control.topPadding + (control.horizontal ? (control.availableHeight - height) / 2 : control.second.visualPosition * (control.availableHeight - height))
@ -64,27 +55,18 @@ T.RangeSlider {
radius: 12
}
Rectangle{
width: 24
height: 24
radius: 12
width: radius*2
height: radius*2
radius:{
if(control.second.pressed){
return 5
}
return control.second.hovered ? 7 : 6
}
color:FluTheme.dark ? FluTheme.primaryColor.lighter :FluTheme.primaryColor.dark
anchors.centerIn: parent
scale: {
if(control.second.pressed){
return 4/10
}
return control.second.hovered ? 6/10 : 5/10
}
Behavior on scale {
enabled: FluTheme.enableAnimation
NumberAnimation{
duration: 167
easing.type: Easing.OutCubic
}
}
}
}
background: Item {
x: control.leftPadding + (control.horizontal ? 0 : (control.availableWidth - width) / 2)
y: control.topPadding + (control.horizontal ? (control.availableHeight - height) / 2 : 0)

View File

@ -1,71 +0,0 @@
import QtQuick
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import FluentUI
Item{
property var radius:[0,0,0,0]
property color color : FluTheme.dark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1)
property bool shadow: true
default property alias contentItem: container.data
id:control
onWidthChanged: {
canvas.requestPaint()
}
onHeightChanged: {
canvas.requestPaint()
}
onRadiusChanged: {
canvas.requestPaint()
}
FluShadow{
anchors.fill: container
radius: control.radius[0]
visible: {
if(control.radius[0] === control.radius[1] && control.radius[0] === control.radius[2] && control.radius[0] === control.radius[3] && control.shadow){
return true
}
return false
}
}
Rectangle{
id:container
width: control.width
height: control.height
opacity: 0
color:control.color
}
Canvas {
id: canvas
anchors.fill: parent
visible: false
onPaint: {
var ctx = getContext("2d")
var x = 0
var y = 0
var w = control.width
var h = control.height
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.save()
ctx.beginPath();
ctx.moveTo(x + radius[0], y)
ctx.lineTo(x + w - radius[1], y)
ctx.arcTo(x + w, y, x + w, y + radius[1], radius[1])
ctx.lineTo(x + w, y + h - radius[2])
ctx.arcTo(x + w, y + h, x + w - radius[2], y + h, radius[2])
ctx.lineTo(x + radius[3], y + h)
ctx.arcTo(x, y + h, x, y + h - radius[3], radius[3])
ctx.lineTo(x, y + radius[0])
ctx.arcTo(x, y, x + radius[0], y, radius[0])
ctx.closePath()
ctx.fillStyle = "#000000"
ctx.fill()
ctx.restore()
}
}
OpacityMask {
anchors.fill: container
source: container
maskSource: canvas
}
}

View File

@ -21,6 +21,7 @@ T.ScrollBar {
property int minLine : 2
property int maxLine : 6
}
z: horizontal? 10 : 20
verticalPadding : vertical ? 15 : 3
horizontalPadding : horizontal ? 15 : 3
background: Rectangle{

View File

@ -24,24 +24,16 @@ T.Slider {
radius: 12
}
Rectangle{
width: 24
height: 24
radius: 12
width: radius*2
height: radius*2
radius:{
if(control.pressed){
return 5
}
return control.hovered ? 7 : 6
}
color:FluTheme.dark ? FluTheme.primaryColor.lighter :FluTheme.primaryColor.dark
anchors.centerIn: parent
scale: {
if(control.pressed){
return 4/10
}
return control.hovered ? 6/10 : 5/10
}
Behavior on scale {
enabled: FluTheme.enableAnimation
NumberAnimation{
duration: 167
easing.type: Easing.OutCubic
}
}
}
}
background: Item {

View File

@ -69,7 +69,7 @@ T.SpinBox {
}
}
up.indicator: FluItem {
up.indicator: FluRectangle {
x: control.mirrored ? 0 : control.width - width
height: control.height
implicitWidth: 32
@ -104,7 +104,7 @@ T.SpinBox {
}
down.indicator: FluItem {
down.indicator: FluRectangle {
x: control.mirrored ? parent.width - width : 0
height: control.height
implicitWidth: 32

View File

@ -76,7 +76,7 @@ Item {
id:item_layout
width: item_container.width
height: item_container.height
FluItem{
Item{
id:item_container
property real timestamp: new Date().getTime()
height: tab_nav.height
@ -92,7 +92,6 @@ Item {
}
return Math.max(Math.min(d.maxEqualWidth,tab_nav.width/tab_nav.count),41 + item_btn_close.width)
}
radius: [6,6,0,0]
Behavior on x { enabled: d.dragBehavior; NumberAnimation { duration: 200 } }
Behavior on y { enabled: d.dragBehavior; NumberAnimation { duration: 200 } }
MouseArea{
@ -186,8 +185,9 @@ Item {
}
}
}
Rectangle{
FluRectangle{
anchors.fill: parent
radius: [6,6,0,0]
color: {
if(FluTheme.dark){
if(item_mouse_hove.containsMouse || item_btn_close.hovered){

View File

@ -143,13 +143,15 @@ Rectangle {
FluText {
id:item_text
text: itemData
anchors.fill: parent
elide: Text.ElideRight
wrapMode: Text.WrapAnywhere
leftPadding: 11
rightPadding: 11
topPadding: 6
bottomPadding: 6
anchors{
fill: parent
leftMargin: 11
rightMargin: 11
topMargin: 6
bottomMargin: 6
}
verticalAlignment: Text.AlignVCenter
HoverHandler{
id: hover_handler

View File

@ -1,38 +1,34 @@
import QtQuick
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import FluentUI
Rectangle{
FluClip{
property Item inputItem
id:content
radius: 4
layer.enabled: true
color: {
if(inputItem.disabled){
return FluTheme.dark ? Qt.rgba(59/255,59/255,59/255,1) : Qt.rgba(252/255,252/255,252/255,1)
property int borderWidth: 1
id:control
radius: [4,4,4,4]
Rectangle{
radius: 4
anchors.fill: parent
color: {
if(inputItem.disabled){
return FluTheme.dark ? Qt.rgba(59/255,59/255,59/255,1) : Qt.rgba(252/255,252/255,252/255,1)
}
if(inputItem.activeFocus){
return FluTheme.dark ? Qt.rgba(36/255,36/255,36/255,1) : Qt.rgba(1,1,1,1)
}
if(inputItem.hovered){
return FluTheme.dark ? Qt.rgba(68/255,68/255,68/255,1) : Qt.rgba(251/255,251/255,251/255,1)
}
return FluTheme.dark ? Qt.rgba(62/255,62/255,62/255,1) : Qt.rgba(1,1,1,1)
}
if(inputItem.activeFocus){
return FluTheme.dark ? Qt.rgba(36/255,36/255,36/255,1) : Qt.rgba(1,1,1,1)
border.width: control.borderWidth
border.color: {
if(inputItem.disabled){
return FluTheme.dark ? Qt.rgba(73/255,73/255,73/255,1) : Qt.rgba(237/255,237/255,237/255,1)
}
return FluTheme.dark ? Qt.rgba(76/255,76/255,76/255,1) : Qt.rgba(240/255,240/255,240/255,1)
}
if(inputItem.hovered){
return FluTheme.dark ? Qt.rgba(68/255,68/255,68/255,1) : Qt.rgba(251/255,251/255,251/255,1)
}
return FluTheme.dark ? Qt.rgba(62/255,62/255,62/255,1) : Qt.rgba(1,1,1,1)
}
layer.effect:OpacityMask {
maskSource: Rectangle {
width: content.width
height: content.height
radius: 4
}
}
border.width: 1
border.color: {
if(inputItem.disabled){
return FluTheme.dark ? Qt.rgba(73/255,73/255,73/255,1) : Qt.rgba(237/255,237/255,237/255,1)
}
return FluTheme.dark ? Qt.rgba(76/255,76/255,76/255,1) : Qt.rgba(240/255,240/255,240/255,1)
}
Rectangle{
width: parent.width

View File

@ -76,12 +76,12 @@ Button {
}
return borderNormalColor
}
Rectangle {
FluIcon {
width: parent.height
x:checked ? control_backgound.width-width : 0
height: width
radius: width/2
scale: hovered&enabled ? 7/10 : 6/10
iconSource: FluentIcons.FullCircleMask
iconSize: 20
color: {
if(!enabled){
return dotDisableColor
@ -92,14 +92,9 @@ Button {
return dotNormalColor
}
Behavior on x {
enabled: FluTheme.enableAnimation
NumberAnimation {
easing.type: Easing.OutCubic
}
}
Behavior on scale {
enabled: FluTheme.enableAnimation
NumberAnimation {
duration: 167
easing.type: Easing.OutCubic
}
}

View File

@ -2,291 +2,453 @@ import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import QtQuick.Controls
import Qt.labs.qmlmodels
import FluentUI
Item {
property int selectionMode: FluTreeViewType.None
property var currentElement
property var currentParentElement
property var rootModel: tree_model.get(0).items
signal itemClicked(var item)
id:root
ListModel{
property int currentIndex : -1
property var dataSource
property bool showLine: true
property bool draggable: false
property int cellHeight: 30
property int depthPadding: 30
property bool checkable: false
property color lineColor: FluTheme.dark ? Qt.rgba(111/255,111/255,111/255,1) : Qt.rgba(217/255,217/255,217/255,1)
id:control
QtObject {
id:d
property int dy
property var current
property int dropIndex: -1
property bool isDropTopArea: false
property int dragIndex: -1
property color hitColor: FluTheme.dark ? FluTheme.primaryColor.lighter : FluTheme.primaryColor.dark
}
onDataSourceChanged: {
tree_model.setDataSource(dataSource)
}
FluTreeModel{
id:tree_model
ListElement{
text: "根节点"
expanded:true
items:[]
key:"123456"
multipSelected:false
multipIndex:0
multipParentKey:""
}
}
Component{
id: delegate_root
Column{
width: calculateWidth()
property var itemModel: model
Repeater{
id: repeater_first_level
model: items
delegate: delegate_items
}
function calculateWidth(){
var w = 0;
for(var i = 0; i < repeater_first_level.count; i++) {
var child = repeater_first_level.itemAt(i)
if(w < child.width_hint){
w = child.width_hint;
}
}
return w;
}
}
}
Component{
id:delegate_items
Column{
id:item_layout
property real level: (mapToItem(list_root,0,0).x+list_root.contentX)/0.001
property var text: model.text??"Item"
property bool hasChild : (model.items !== undefined) && (model.items.count !== 0)
property var items: model.items??[]
property var expanded: model.expanded??true
property int width_hint: calculateWidth()
property bool singleSelected: currentElement === model
property var itemModel: model
function calculateWidth(){
var w = Math.max(list_root.width, item_layout_row.implicitWidth + 10);
if(expanded){
for(var i = 0; i < repeater_items.count; i++) {
var child = repeater_items.itemAt(i)
if(w < child.width_hint){
w = child.width_hint;
}
}
}
return w;
}
Item{
id:item_layout_rect
width: list_root.contentWidth
height: item_layout_row.implicitHeight
Rectangle{
anchors.fill: parent
anchors.margins: 2
color:{
if(FluTheme.dark){
if(item_layout.singleSelected && selectionMode === FluTreeViewType.Single){
return Qt.rgba(62/255,62/255,62/255,1)
}
return (item_layout_mouse.containsMouse || item_layout_expanded.hovered || item_layout_checkbox.hovered)?Qt.rgba(62/255,62/255,62/255,1):Qt.rgba(0,0,0,0)
}else{
if(item_layout.singleSelected && selectionMode === FluTreeViewType.Single){
return Qt.rgba(0,0,0,0.06)
}
return (item_layout_mouse.containsMouse || item_layout_expanded.hovered || item_layout_checkbox.hovered)?Qt.rgba(0,0,0,0.03):Qt.rgba(0,0,0,0)
}
}
Rectangle{
width: 3
color:FluTheme.primaryColor.dark
visible: item_layout.singleSelected && (selectionMode === FluTreeViewType.Single)
radius: 3
height: 20
anchors{
left: parent.left
verticalCenter: parent.verticalCenter
}
}
MouseArea{
id:item_layout_mouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
item_layout_rect.onClickItem()
}
}
}
function onClickItem(){
if(selectionMode === FluTreeViewType.None){
itemClicked(model)
}
if(selectionMode === FluTreeViewType.Single){
currentElement = model
if(item_layout.parent.parent.parent.itemModel){
currentParentElement = item_layout.parent.parent.parent.itemModel
}else{
if(item_layout.parent.itemModel){
currentParentElement = item_layout.parent.itemModel
}
}
itemClicked(model)
}
if(selectionMode === FluTreeViewType.Multiple){
}
}
RowLayout{
id:item_layout_row
anchors.verticalCenter: item_layout_rect.verticalCenter
Item{
width: 15*level
Layout.alignment: Qt.AlignVCenter
}
FluCheckBox{
id:item_layout_checkbox
text:""
checked: itemModel.multipSelected
visible: selectionMode === FluTreeViewType.Multiple
Layout.leftMargin: 5
function refreshCheckBox(){
const stack = [tree_model.get(0)];
const result = [];
while (stack.length > 0) {
const curr = stack.pop();
result.unshift(curr);
if (curr.items) {
for(var i=0 ; i<curr.items.count ; i++){
curr.items.setProperty(i,"multipIndex",i)
curr.items.setProperty(i,"multipParentKey",curr.key)
stack.push(curr.items.get(i));
}
}
}
for(var j=0 ; j<result.length-1 ; j++){
var item = result[j]
let obj = result.find(function(o) {
return o.key === item.multipParentKey;
});
if((item.items !== undefined) && (item.items.count !== 0)){
var items = item.items
for(var k=0 ; k<items.count ; k++){
if(items.get(k).multipSelected === false){
obj.items.setProperty(item.multipIndex,"multipSelected",false)
break
}
obj.items.setProperty(item.multipIndex,"multipSelected",true)
}
}
}
}
clickListener:function(){
if(hasChild){
const stack = [itemModel];
while (stack.length > 0) {
const curr = stack.pop();
if (curr.items) {
for(var i=0 ; i<curr.items.count ; i++){
curr.items.setProperty(i,"multipSelected",!itemModel.multipSelected)
stack.push(curr.items.get(i));
}
}
}
refreshCheckBox()
}else{
itemModel.multipSelected = !itemModel.multipSelected
refreshCheckBox()
}
}
}
FluIconButton{
id:item_layout_expanded
color:"#00000000"
opacity: item_layout.hasChild
onClicked: {
if(!item_layout.hasChild){
item_layout_rect.onClickItem()
return
}
model.expanded = !model.expanded
}
contentItem: FluIcon{
rotation: item_layout.expanded?0:-90
iconSource:FluentIcons.ChevronDown
iconSize: 15
Behavior on rotation {
enabled: FluTheme.enableAnimation
NumberAnimation{
duration: 167
easing.type: Easing.OutCubic
}
}
}
}
FluText {
text: item_layout.text
Layout.alignment: Qt.AlignVCenter
topPadding: 7
bottomPadding: 7
}
}
}
Item{
id:item_sub
visible: {
if(!hasChild){
return false
}
return item_layout.expanded??false
}
width: item_sub_layout.implicitWidth
height: item_sub_layout.implicitHeight
x:0.001
Column{
id: item_sub_layout
Repeater{
id:repeater_items
model: item_layout.items
delegate: delegate_items
}
}
}
}
}
ListView {
id: list_root
anchors.fill: parent
delegate: delegate_root
contentWidth: contentItem.childrenRect.width
ListView{
id:table_view
ScrollBar.horizontal: FluScrollBar{}
ScrollBar.vertical: FluScrollBar{}
boundsBehavior: Flickable.StopAtBounds
model: tree_model
flickableDirection: Flickable.HorizontalAndVerticalFlick
anchors.fill: parent
clip: true
boundsBehavior: ListView.StopAtBounds
ScrollBar.vertical: FluScrollBar {}
ScrollBar.horizontal: FluScrollBar { }
}
function updateData(items){
rootModel.clear()
rootModel.append(items)
}
function signleData(){
return currentElement
}
function multipData(){
const stack = [tree_model.get(0)];
const result = [];
while (stack.length > 0) {
const curr = stack.pop();
if(curr.multipSelected){
result.push(curr)
}
for(var i=0 ; i<curr.items.count ; i++){
stack.push(curr.items.get(i));
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 {
properties: "opacity"
duration: 88
from: 0
to: 1
}
}
}
return result
}
function createItem(text="",expanded=true,items=[],data={}){
return {text:text,expanded:expanded,items:items,key:uniqueRandom(),multipSelected:false,multipIndex:0,multipParentKey:"",data:data};
}
function uniqueRandom() {
var timestamp = Date.now();
var random = Math.floor(Math.random() * 1000000);
return timestamp.toString() + random.toString();
move: Transition {
NumberAnimation { property: "y"; duration: 200 }
}
add: Transition{
ParallelAnimation{
NumberAnimation {
properties: "y"
duration: 167
from: d.dy
easing.type: Easing.OutCubic
}
NumberAnimation {
properties: "opacity"
duration: 88
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()
}
Loader{
property var itemControl: item_control
property var itemModel: modelData
property int rowIndex: index
property bool isItemLoader: true
id:item_loader_container
sourceComponent: com_item_container
}
}
Loader{
id:loader_container
property var itemControl
property var itemModel
property bool isItemLoader: false
}
}
Component{
id:com_item_container
Item{
signal reused
signal pooled
onReused: {
}
onPooled: {
}
property bool isCurrent: d.current === itemModel
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
height: 18
radius: 1.5
color: FluTheme.primaryColor.dark
visible: isCurrent
anchors{
left: parent.left
leftMargin: 6
verticalCenter: parent.verticalCenter
}
}
MouseArea{
id:item_mouse
property point clickPos: Qt.point(0,0)
anchors.fill: parent
drag.target:control.draggable ? loader_container : undefined
hoverEnabled: true
drag.onActiveChanged: {
if(drag.active){
if(itemModel.isExpanded && itemModel.hasChildren()){
tree_model.collapse(rowIndex)
}
d.dragIndex = rowIndex
loader_container.sourceComponent = com_item_container
}
}
onPressed:
(mouse)=>{
clickPos = Qt.point(mouse.x,mouse.y)
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: {
d.current = itemModel
}
onDoubleClicked: {
if(itemModel.hasChildren()){
item_container.toggle()
}
}
onPositionChanged:
(mouse)=> {
if(!drag.active){
return
}
var cellPosition = item_container.mapToItem(table_view, 0, 0)
if(mouse.y+cellPosition.y<0 || mouse.y+cellPosition.y>table_view.height){
d.dropIndex = -1
return
}
if((mouse.x-table_view.contentX)>table_view.width || (mouse.x-table_view.contentX)<0){
d.dropIndex = -1
return
}
var pos = FluTools.cursorPos()
var viewPos = table_view.mapToGlobal(0,0)
var y = table_view.contentY + pos.y-viewPos.y
var index = Math.floor(y/control.cellHeight)
if(index<0 || index>table_view.count-1){
d.dropIndex = -1
return
}
console.debug(index)
if(tree_model.hitHasChildrenExpanded(index) && y>index*control.cellHeight+control.cellHeight/2){
d.dropIndex = index + 1
d.isDropTopArea = true
}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{
width: 1
color: control.lineColor
visible: control.showLine && isItemLoader && itemModel.depth !== 0 && !itemModel.hasChildren()
height: itemModel.hideLineFooter() ? parent.height/2 : parent.height
anchors{
top: parent.top
left: item_line_h.left
}
}
FluRectangle{
id:item_line_h
height: 1
color: control.lineColor
visible: control.showLine && isItemLoader && itemModel.depth !== 0 && !itemModel.hasChildren()
width: depthPadding - 10
anchors{
right: layout_row.left
rightMargin: -24
verticalCenter: parent.verticalCenter
}
}
Repeater{
model: Math.max(itemModel.depth-1,0)
delegate: FluRectangle{
required property int index
width: 1
color: control.lineColor
visible: control.showLine && isItemLoader && itemModel.depth !== 0 && itemModel.hasNextNodeByIndex(index)
anchors{
top:parent.top
bottom: parent.bottom
left: parent.left
leftMargin: control.depthPadding*(index+1) + 24
}
}
}
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(FluTheme.dark){
if(isCurrent){
return Qt.rgba(1,1,1,0.06)
}
if(item_mouse.containsMouse || item_check_box.hovered){
return Qt.rgba(1,1,1,0.03)
}
if(item_loader_expand.item && item_loader_expand.item.hovered){
return Qt.rgba(1,1,1,0.03)
}
return Qt.rgba(0,0,0,0)
}else{
if(isCurrent){
return Qt.rgba(0,0,0,0.06)
}
if(item_mouse.containsMouse || item_check_box.hovered){
return Qt.rgba(0,0,0,0.03)
}
if(item_loader_expand.item && item_loader_expand.item.hovered){
return Qt.rgba(0,0,0,0.03)
}
return Qt.rgba(0,0,0,0)
}
}
}
RowLayout{
id:layout_row
height: parent.height
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
spacing: 0
anchors.leftMargin: 14 + control.depthPadding*itemModel.depth
Component{
id:com_icon_btn
FluIconButton{
opacity: itemModel.hasChildren()
onClicked: {
item_container.toggle()
}
contentItem:FluIcon{
rotation: itemModel.isExpanded?0:-90
iconSource:FluentIcons.ChevronDown
iconSize: 16
anchors.centerIn: parent
}
}
}
Loader{
id:item_loader_expand
Layout.preferredWidth: 20
Layout.preferredHeight: 20
sourceComponent: itemModel.hasChildren() ? com_icon_btn : undefined
Layout.alignment: Qt.AlignVCenter
}
FluCheckBox{
id:item_check_box
Layout.preferredWidth: 18
Layout.preferredHeight: 18
Layout.leftMargin: 5
horizontalPadding:0
verticalPadding: 0
checked: itemModel.checked
enableAnimation:false
visible: control.checkable
padding: 0
clickListener: function(){
tree_model.checkRow(rowIndex,!itemModel.checked)
}
Layout.alignment: Qt.AlignVCenter
}
Loader{
property var modelData: itemModel
property var itemMouse: item_mouse
id:item_loader_cell
Layout.leftMargin: 10
Layout.preferredWidth: {
if(item){
return item.width
}
return 0
}
Layout.fillHeight: true
sourceComponent:com_item_text
}
}
}
}
Component{
id:com_item_text
Item{
width: item_text.width
FluText {
id:item_text
text: modelData.title
rightPadding: 14
anchors.centerIn: parent
color:{
if(itemMouse.pressed){
return FluTheme.dark ? FluColors.Grey80 : FluColors.Grey120
}
return FluTheme.dark ? FluColors.White : FluColors.Grey220
}
}
}
}
function selectionModel(){
return tree_model.selectionModel
}
function count(){
return tree_model.dataSourceSize
}
function visibleCount(){
return table_view.count
}
function collapse(rowIndex){
tree_model.collapse(rowIndex)
}
function expand(rowIndex){
tree_model.expand(rowIndex)
}
function allExpand(){
tree_model.allExpand()
}
function allCollapse(){
tree_model.allCollapse()
}
}

View File

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