From e4fb9989d0c7b2ea2dfc9fedf10697b70c781714 Mon Sep 17 00:00:00 2001 From: zhuzichu Date: Mon, 7 Aug 2023 18:18:04 +0800 Subject: [PATCH] update --- example/qml/global/ItemsOriginal.qml | 6 + example/qml/page/T_Carousel.qml | 91 ++++++- example/qml/page/T_Tour.qml | 60 +++++ example/qml/window/MainWindow.qml | 1 + src/imports/FluentUI/Controls/FluCarousel.qml | 227 ++++++++++++------ .../FluentUI/Controls/FluNavigationView.qml | 17 +- src/imports/FluentUI/Controls/FluTour.qml | 101 ++++++++ 7 files changed, 420 insertions(+), 83 deletions(-) create mode 100644 example/qml/page/T_Tour.qml create mode 100644 src/imports/FluentUI/Controls/FluTour.qml diff --git a/example/qml/global/ItemsOriginal.qml b/example/qml/global/ItemsOriginal.qml index e6af8a6f..320e8ac5 100644 --- a/example/qml/global/ItemsOriginal.qml +++ b/example/qml/global/ItemsOriginal.qml @@ -331,6 +331,12 @@ FluObject{ FluPaneItemExpander{ title:lang.other icon:FluentIcons.Shop + FluPaneItem{ + title:"Tour" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Tour.qml") + } + } FluPaneItem{ title:"Http" onTap:{ diff --git a/example/qml/page/T_Carousel.qml b/example/qml/page/T_Carousel.qml index 6e9c46fe..75cc647c 100644 --- a/example/qml/page/T_Carousel.qml +++ b/example/qml/page/T_Carousel.qml @@ -9,6 +9,19 @@ FluScrollablePage{ title:"Carousel" + ListModel{ + id:data_model + ListElement{ + url:"qrc:/example/res/image/banner_1.jpg" + } + ListElement{ + url:"qrc:/example/res/image/banner_2.jpg" + } + ListElement{ + url:"qrc:/example/res/image/banner_3.jpg" + } + } + FluArea{ Layout.fillWidth: true height: 370 @@ -24,23 +37,95 @@ FluScrollablePage{ text:"轮播图,支持无限轮播,无限滑动,用ListView实现的组件" } FluCarousel{ - id:carousel + radius:[5,5,5,5] + delegate: Component{ + Image { + anchors.fill: parent + source: model.url + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + } Layout.topMargin: 20 Layout.leftMargin: 5 Component.onCompleted: { - carousel.setData([{url:"qrc:/example/res/image/banner_1.jpg"},{url:"qrc:/example/res/image/banner_2.jpg"},{url:"qrc:/example/res/image/banner_3.jpg"}]) + model = [{url:"qrc:/example/res/image/banner_1.jpg"},{url:"qrc:/example/res/image/banner_2.jpg"},{url:"qrc:/example/res/image/banner_3.jpg"}] } } } } + + FluArea{ + Layout.fillWidth: true + height: 340 + paddings: 10 + Layout.topMargin: 10 + Column{ + spacing: 15 + anchors{ + verticalCenter: parent.verticalCenter + left:parent.left + } + FluCarousel{ + radius:[15,15,15,15] + loopTime:1500 + indicatorGravity: Qt.AlignHCenter | Qt.AlignTop + indicatorMarginTop:15 + delegate: Component{ + Item{ + anchors.fill: parent + Image { + anchors.fill: parent + source: model.url + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + Rectangle{ + height: 40 + width: parent.width + anchors.bottom: parent.bottom + color: "#33000000" + FluText{ + anchors.fill: parent + verticalAlignment: Qt.AlignVCenter + horizontalAlignment: Qt.AlignHCenter + text:model.title + color: FluColors.Grey10 + font.pixelSize: 15 + } + } + } + } + Layout.topMargin: 20 + Layout.leftMargin: 5 + Component.onCompleted: { + var arr = [] + arr.push({url:"qrc:/example/res/image/banner_1.jpg",title:"共同应对全球性问题"}) + arr.push({url:"qrc:/example/res/image/banner_2.jpg",title:"三小只全程没互动"}) + arr.push({url:"qrc:/example/res/image/banner_3.jpg",title:"有效投资扩大 激发增长动能"}) + model = arr + } + } + } + } + CodeExpander{ Layout.fillWidth: true Layout.topMargin: -1 code:'FluCarousel{ + id:carousel width: 400 height: 300 + delegate: Component{ + Image { + anchors.fill: parent + source: model.url + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + } Component.onCompleted: { - setData([{url:"qrc:/example/res/image/banner_1.jpg"},{url:"qrc:/example/res/image/banner_2.jpg"},{url:"qrc:/example/res/image/banner_3.jpg"}]) + carousel.model = [{url:"qrc:/example/res/image/banner_1.jpg"},{url:"qrc:/example/res/image/banner_2.jpg"},{url:"qrc:/example/res/image/banner_3.jpg"}] } }' } diff --git a/example/qml/page/T_Tour.qml b/example/qml/page/T_Tour.qml new file mode 100644 index 00000000..fdba734c --- /dev/null +++ b/example/qml/page/T_Tour.qml @@ -0,0 +1,60 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Tour" + + FluTour{ + id:tour + steps:[ + {title:"Upload File",description: "Put your files here.",target:()=>btn_upload}, + {title:"Save",description: "Save your changes.",target:()=>btn_save}, + {title:"Other Actions",description: "Click to see other actions.",target:()=>btn_more} + ] + + } + + FluArea{ + Layout.fillWidth: true + height: 130 + paddings: 10 + Layout.topMargin: 20 + + FluFilledButton{ + anchors{ + top: parent.top + topMargin: 14 + } + text:"Begin Tour" + onClicked: { + tour.open() + } + } + + Row{ + spacing: 20 + anchors{ + top: parent.top + topMargin: 60 + } + FluButton{ + id:btn_upload + text:"Upload" + } + FluFilledButton{ + id:btn_save + text:"Save" + } + FluIconButton{ + id:btn_more + iconSource: FluentIcons.More + } + } + } + +} diff --git a/example/qml/window/MainWindow.qml b/example/qml/window/MainWindow.qml index 64c23cab..721f255c 100644 --- a/example/qml/window/MainWindow.qml +++ b/example/qml/window/MainWindow.qml @@ -146,6 +146,7 @@ CustomWindow { visible: flipable.flipAngle !== 180 anchors.fill: flipable FluAppBar { + id:app_bar_front anchors { top: parent.top left: parent.left diff --git a/src/imports/FluentUI/Controls/FluCarousel.qml b/src/imports/FluentUI/Controls/FluCarousel.qml index d6ce01ae..8b3ea319 100644 --- a/src/imports/FluentUI/Controls/FluCarousel.qml +++ b/src/imports/FluentUI/Controls/FluCarousel.qml @@ -2,102 +2,170 @@ import QtQuick import QtQuick.Controls import FluentUI -Item { - property bool flagXChanged: true - property int radius : 5 +FluItem { property int loopTime: 2000 + property var model + property Component delegate property bool showIndicator: true + property int indicatorGravity : Qt.AlignBottom | Qt.AlignHCenter + property int indicatorMarginLeft: 0 + property int indicatorMarginRight: 0 + property int indicatorMarginTop: 0 + property int indicatorMarginBottom: 20 + property int indicatorSpacing: 10 + property alias indicatorAnchors: layout_indicator.anchors + property Component indicatorDelegate : com_indicator id:control width: 400 height: 300 ListModel{ id:content_model } - FluRectangle{ - anchors.fill: parent - radius: [control.radius,control.radius,control.radius,control.radius] - FluShadow{ - radius:control.radius + QtObject{ + id:d + property bool flagXChanged: true + function setData(data){ + if(!data){ + return + } + content_model.clear() + content_model.append(data[data.length-1]) + content_model.append(data) + content_model.append(data[0]) + list_view.highlightMoveDuration = 0 + list_view.currentIndex = 1 + list_view.highlightMoveDuration = 250 + timer_run.restart() } - ListView{ - id:list_view - anchors.fill: parent - snapMode: ListView.SnapOneItem - clip: true - boundsBehavior: ListView.StopAtBounds - model:content_model - maximumFlickVelocity: 4 * (list_view.orientation === Qt.Horizontal ? width : height) - preferredHighlightBegin: 0 - preferredHighlightEnd: 0 - highlightMoveDuration: 0 - orientation : ListView.Horizontal - delegate: Item{ - width: ListView.view.width - height: ListView.view.height - property int displayIndex: { - if(index === 0) - return content_model.count-3 - if(index === content_model.count-1) - return 0 - return index-1 - } - Image { - anchors.fill: parent - source: model.url - asynchronous: true - fillMode:Image.PreserveAspectCrop - } + } + ListView{ + id:list_view + anchors.fill: parent + snapMode: ListView.SnapOneItem + clip: true + boundsBehavior: ListView.StopAtBounds + model:content_model + maximumFlickVelocity: 4 * (list_view.orientation === Qt.Horizontal ? width : height) + preferredHighlightBegin: 0 + preferredHighlightEnd: 0 + highlightMoveDuration: 0 + Component.onCompleted: { + d.setData(control.model) + } + Connections{ + target: control + function onModelChanged(){ + d.setData(control.model) } - onMovementEnded:{ - currentIndex = list_view.contentX/list_view.width - if(currentIndex === 0){ - currentIndex = list_view.count-2 - }else if(currentIndex === list_view.count-1){ - currentIndex = 1 - } - flagXChanged = false - timer_run.start() + } + orientation : ListView.Horizontal + delegate: Item{ + id:item_control + width: ListView.view.width + height: ListView.view.height + property int displayIndex: { + if(index === 0) + return content_model.count-3 + if(index === content_model.count-1) + return 0 + return index-1 } - onMovementStarted: { - flagXChanged = true - timer_run.stop() - } - onContentXChanged: { - if(flagXChanged){ - var maxX = Math.min(list_view.width*(currentIndex+1),list_view.count*list_view.width) - var minY = Math.max(0,(list_view.width*(currentIndex-1))) - if(contentX>=maxX){ - contentX = maxX - } - if(contentX<=minY){ - contentX = minY + Loader{ + property int displayIndex : item_control.displayIndex + property var model: list_view.model.get(index) + anchors.fill: parent + sourceComponent: { + if(model){ + return control.delegate } + return undefined + } + } + } + onMovementEnded:{ + currentIndex = list_view.contentX/list_view.width + if(currentIndex === 0){ + currentIndex = list_view.count-2 + }else if(currentIndex === list_view.count-1){ + currentIndex = 1 + } + d.flagXChanged = false + timer_run.restart() + } + onMovementStarted: { + d.flagXChanged = true + timer_run.stop() + } + onContentXChanged: { + if(d.flagXChanged){ + var maxX = Math.min(list_view.width*(currentIndex+1),list_view.count*list_view.width) + var minY = Math.max(0,(list_view.width*(currentIndex-1))) + if(contentX>=maxX){ + contentX = maxX + } + if(contentX<=minY){ + contentX = minY + } + } + } + } + Component{ + id:com_indicator + Rectangle{ + width: 8 + height: 8 + radius: 4 + FluShadow{ + radius: 4 + } + scale: checked ? 1.2 : 1 + color: checked ? FluTheme.primaryColor.dark : Qt.rgba(1,1,1,0.7) + border.width: mouse_item.containsMouse ? 1 : 0 + border.color: FluTheme.primaryColor.dark + MouseArea{ + id:mouse_item + hoverEnabled: true + anchors.fill: parent + onClicked: { + changedIndex(realIndex) } } } } Row{ - spacing: 10 + id:layout_indicator + spacing: control.indicatorSpacing anchors{ - horizontalCenter: parent.horizontalCenter - bottom: parent.bottom - bottomMargin: 20 + horizontalCenter:(indicatorGravity & Qt.AlignHCenter) ? parent.horizontalCenter : undefined + verticalCenter: (indicatorGravity & Qt.AlignVCenter) ? parent.verticalCenter : undefined + bottom: (indicatorGravity & Qt.AlignBottom) ? parent.bottom : undefined + top: (indicatorGravity & Qt.AlignTop) ? parent.top : undefined + left: (indicatorGravity & Qt.AlignLeft) ? parent.left : undefined + right: (indicatorGravity & Qt.AlignRight) ? parent.right : undefined + bottomMargin: control.indicatorMarginBottom + leftMargin: control.indicatorMarginBottom + rightMargin: control.indicatorMarginBottom + topMargin: control.indicatorMarginBottom } visible: showIndicator Repeater{ + id:repeater_indicator model: list_view.count - Rectangle{ - width: 8 - height: 8 - radius: 4 - visible: { - if(index===0 || index===list_view.count-1) - return false - return true + Loader{ + property int displayIndex: { + if(index === 0) + return list_view.count-3 + if(index === list_view.count-1) + return 0 + return index-1 + } + property int realIndex: index + property bool checked: list_view.currentIndex === index + sourceComponent: { + if(index===0 || index===list_view.count-1) + return undefined + return control.indicatorDelegate } - border.width: 1 - border.color: FluColors.Grey100 - color: list_view.currentIndex === index ? FluTheme.primaryColor.dark : Qt.rgba(1,1,1,0.5) } } } @@ -121,12 +189,13 @@ Item { timer_anim.start() } } - function setData(data){ - content_model.clear() - content_model.append(data[data.length-1]) - content_model.append(data) - content_model.append(data[0]) - list_view.currentIndex = 1 + + function changedIndex(index){ + d.flagXChanged = true + timer_run.stop() + list_view.currentIndex = index + d.flagXChanged = false timer_run.restart() } + } diff --git a/src/imports/FluentUI/Controls/FluNavigationView.qml b/src/imports/FluentUI/Controls/FluNavigationView.qml index 8e94c068..3547e877 100644 --- a/src/imports/FluentUI/Controls/FluNavigationView.qml +++ b/src/imports/FluentUI/Controls/FluNavigationView.qml @@ -18,7 +18,7 @@ Item { property int pageMode: FluNavigationViewType.Stack signal logoClicked id:control - QtObject{ + Item{ id:d property bool animDisabled:false property var stackItems: [] @@ -66,6 +66,11 @@ Item { } return data } + function refreshWindow(){ + console.debug(Window.window.width) + Window.window.width = Window.window.width-1 + Window.window.width = Window.window.width+1 + } } Component.onCompleted: { d.displayMode = Qt.binding(function(){ @@ -709,6 +714,11 @@ Item { NumberAnimation{ duration: 167 easing.type: Easing.OutCubic + onRunningChanged: { + if(!running){ + d.refreshWindow() + } + } } } Behavior on x { @@ -716,6 +726,11 @@ Item { NumberAnimation{ duration: 167 easing.type: Easing.OutCubic + onRunningChanged: { + if(!running){ + d.refreshWindow() + } + } } } visible: { diff --git a/src/imports/FluentUI/Controls/FluTour.qml b/src/imports/FluentUI/Controls/FluTour.qml new file mode 100644 index 00000000..5e005333 --- /dev/null +++ b/src/imports/FluentUI/Controls/FluTour.qml @@ -0,0 +1,101 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Window +import FluentUI + +Popup{ + + property var steps : [] + id:control + padding: 0 + anchors.centerIn: Overlay.overlay + width: d.window?.width + height: d.window?.height + + background: Item{} + + contentItem: Item{} + + + property int index: 0 + property var step : steps[index] + property var target : step.target() + + onVisibleChanged: { + if(visible){ + index = 0 + } + } + + + Connections{ + target: d.window + function onWidthChanged(){ + canvas.requestPaint() + } + function onHeightChanged(){ + canvas.requestPaint() + } + } + + onIndexChanged: { + canvas.requestPaint() + } + + Item{ + id:d + property var window: Window.window + property var pos:Qt.point(0,0) + } + + Canvas{ + id:canvas + anchors.fill: parent + onPaint: { + d.pos = target.mapToItem(control,0,0) + var ctx = canvas.getContext("2d"); + ctx.clearRect(0, 0, canvasSize.width, canvasSize.height); + ctx.save() + ctx.fillStyle = "#66000000" + ctx.fillRect(0, 0, canvasSize.width, canvasSize.height) + ctx.globalCompositeOperation = 'destination-out' + ctx.fillStyle = 'black' + console.debug(d.pos.x) + ctx.fillRect(d.pos.x, d.pos.y, target.width, target.height) + ctx.restore() + } + } + + FluRectangle{ + radius: [5,5,5,5] + width: 500 + height: 120 + x: Math.min(Math.max(0,d.pos.x+target.width/2-width/2),d.window?.width-width) + y:d.pos.y+target.height+1 + FluFilledButton{ + property bool isEnd: control.index === steps.length-1 + anchors{ + right: parent.right + bottom: parent.bottom + rightMargin: 10 + bottomMargin: 10 + } + text: isEnd ? "结束导览" :"下一步" + onClicked: { + if(isEnd){ + control.close() + }else{ + control.index = control.index + 1 + } + } + } + } + + + function refresh(){ + canvas.requestPaint() + } + +} + +