314 lines
8 KiB
QML
314 lines
8 KiB
QML
|
import QtQuick 2.9
|
||
|
import QtQuick.Controls 2.4
|
||
|
import QtGraphicalEffects 1.0
|
||
|
|
||
|
Item {
|
||
|
id: control
|
||
|
|
||
|
state: "idle"
|
||
|
|
||
|
property var target // targeted ListView
|
||
|
|
||
|
property bool autoConfigureTarget: true // should the target be automaticaly be configured?
|
||
|
|
||
|
property int postRefreshDelay: 1000 // delay after reload funcion has finished
|
||
|
property int preRefreshDelay: 1000 // delay before reload funcion is called
|
||
|
|
||
|
property int refreshPosition: height * 1.2 // position of the item when refreshing
|
||
|
property int dragOutPosition: height * 2 // position of the item when starting to refresh
|
||
|
|
||
|
property color backgroundColor: "white" // color for the pre-defined background
|
||
|
property color pullIndicatorColor: "black" // color for the pre-defined pull indicator
|
||
|
//property color busyIndicatorColor: "pink" // color for the pre-defined busy indicator
|
||
|
|
||
|
readonly property double dragProgress: Math.min( userPosition / dragOutPosition, 1)
|
||
|
|
||
|
property Component background: Item {
|
||
|
RectangularGlow {
|
||
|
anchors.fill: backgroundRe
|
||
|
|
||
|
scale: 0.8 * backgroundRe.scale
|
||
|
cornerRadius: backgroundRe.radius
|
||
|
color: "black"
|
||
|
|
||
|
glowRadius: 0.001
|
||
|
spread: 0.2
|
||
|
}
|
||
|
|
||
|
Rectangle {
|
||
|
id: backgroundRe
|
||
|
|
||
|
anchors.fill: parent
|
||
|
|
||
|
radius: width * 0.5
|
||
|
color: control.backgroundColor
|
||
|
}
|
||
|
}
|
||
|
property Component busyIndicator: BusyIndicator { running: true }
|
||
|
property Component pullIndicator: Canvas {
|
||
|
|
||
|
property double drawProgress: control.dragProgress
|
||
|
|
||
|
onDrawProgressChanged: {
|
||
|
requestPaint()
|
||
|
}
|
||
|
|
||
|
onPaint: {
|
||
|
var ctx = getContext("2d");
|
||
|
|
||
|
var topMargin = height * 0.1
|
||
|
var bottomMargin = topMargin
|
||
|
var rightMargin = 0
|
||
|
var leftMargin = 0
|
||
|
|
||
|
var arrowHeight = height - topMargin - bottomMargin
|
||
|
|
||
|
var peakHeight = arrowHeight * 0.35
|
||
|
var peakWidth = peakHeight
|
||
|
|
||
|
var lineWidth = 2
|
||
|
|
||
|
var progress = drawProgress
|
||
|
|
||
|
// modify all values to math the progress
|
||
|
|
||
|
arrowHeight = arrowHeight * progress
|
||
|
if(progress > 0.3){
|
||
|
peakHeight = peakHeight * (progress - 0.3) * 1/0.7
|
||
|
peakWidth = peakWidth * (progress - 0.3) * 1/0.7
|
||
|
}
|
||
|
else {
|
||
|
peakHeight = 0
|
||
|
peakWidth = 0
|
||
|
}
|
||
|
|
||
|
// clear canvas
|
||
|
ctx.reset()
|
||
|
|
||
|
ctx.lineWidth = lineWidth;
|
||
|
ctx.strokeStyle = control.pullIndicatorColor;
|
||
|
|
||
|
// middle line
|
||
|
ctx.moveTo(width/2, topMargin);
|
||
|
ctx.lineTo(width/2, arrowHeight + topMargin);
|
||
|
|
||
|
// right line
|
||
|
ctx.moveTo(width/2 - lineWidth * 0.3, arrowHeight + topMargin);
|
||
|
ctx.lineTo(width/2 + peakWidth,arrowHeight + topMargin - peakHeight);
|
||
|
// left line
|
||
|
ctx.moveTo(width/2 + lineWidth * 0.3, arrowHeight + topMargin);
|
||
|
ctx.lineTo(width/2 - peakWidth,arrowHeight + topMargin - peakHeight);
|
||
|
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
signal refreshRequested
|
||
|
|
||
|
// internal properties
|
||
|
property int minimumPosition: 0
|
||
|
property int maximumPosition: 0
|
||
|
property int userPosition: 0
|
||
|
property int position: Math.max( minimumPosition, Math.min(maximumPosition, userPosition))
|
||
|
|
||
|
height: 50
|
||
|
width: height
|
||
|
|
||
|
Component.onCompleted: {
|
||
|
if(control.autoConfigureTarget){
|
||
|
target.boundsBehavior = Flickable.DragOverBounds
|
||
|
target.boundsMovement = Flickable.StopAtBounds
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function refresh() {
|
||
|
control.refreshRequested()
|
||
|
postRefreshTimer.start()
|
||
|
}
|
||
|
|
||
|
anchors {
|
||
|
top: control.target.top
|
||
|
horizontalCenter: control.target.horizontalCenter
|
||
|
topMargin: control.position - height * 1.1
|
||
|
}
|
||
|
|
||
|
onUserPositionChanged: {
|
||
|
if(control.state === "idle" && userPosition >= control.dragOutPosition){
|
||
|
control.state = "ready"
|
||
|
}
|
||
|
else if(control.state === "ready" && userPosition < control.dragOutPosition){
|
||
|
control.state = "refreshing"
|
||
|
preRefreshTimer.start()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Loader {
|
||
|
id: backgroundLd
|
||
|
|
||
|
anchors.fill: parent
|
||
|
|
||
|
sourceComponent: control.background
|
||
|
}
|
||
|
|
||
|
Loader {
|
||
|
id: pullIndicatorLd
|
||
|
|
||
|
anchors.centerIn: parent
|
||
|
|
||
|
height: parent.height * 0.6
|
||
|
width: height
|
||
|
|
||
|
rotation: 180
|
||
|
|
||
|
sourceComponent: control.pullIndicator
|
||
|
}
|
||
|
|
||
|
Loader {
|
||
|
id: busyIndicatorLd
|
||
|
|
||
|
anchors.centerIn: parent
|
||
|
|
||
|
height: parent.height * 0.7
|
||
|
width: height
|
||
|
|
||
|
opacity: 0
|
||
|
|
||
|
sourceComponent: control.busyIndicator
|
||
|
}
|
||
|
|
||
|
Behavior on minimumPosition {
|
||
|
enabled: control.state !== "hidden" && control.state !== "idle"
|
||
|
NumberAnimation {
|
||
|
duration: 200
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Timer {
|
||
|
id: preRefreshTimer
|
||
|
interval: control.preRefreshDelay <= 0 ? 1:control.preRefreshDelay
|
||
|
running: false
|
||
|
repeat: false
|
||
|
onTriggered: {
|
||
|
control.refresh()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Timer {
|
||
|
id: postRefreshTimer
|
||
|
interval: control.postRefreshDelay <= 0 ? 1:control.postRefreshDelay
|
||
|
running: false
|
||
|
repeat: false
|
||
|
onTriggered: {
|
||
|
control.state = "hidden"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
states: [
|
||
|
State {
|
||
|
name: "idle"
|
||
|
|
||
|
PropertyChanges {
|
||
|
target: control
|
||
|
minimumPosition: userPosition > maximumPosition ? maximumPosition:userPosition
|
||
|
userPosition: Math.abs( target.verticalOvershoot )
|
||
|
maximumPosition: control.dragOutPosition
|
||
|
}
|
||
|
|
||
|
PropertyChanges {
|
||
|
target: pullIndicatorLd
|
||
|
rotation: 0
|
||
|
}
|
||
|
},
|
||
|
State {
|
||
|
name: "ready"
|
||
|
PropertyChanges {
|
||
|
target: control
|
||
|
maximumPosition: control.dragOutPosition
|
||
|
userPosition: Math.abs( target.verticalOvershoot )
|
||
|
minimumPosition: control.maximumPosition - 1
|
||
|
}
|
||
|
|
||
|
PropertyChanges {
|
||
|
target: pullIndicatorLd
|
||
|
rotation: 180
|
||
|
}
|
||
|
},
|
||
|
State {
|
||
|
name: "refreshing"
|
||
|
PropertyChanges {
|
||
|
target: control
|
||
|
minimumPosition: control.refreshPosition
|
||
|
userPosition: 0
|
||
|
maximumPosition: control.refreshPosition
|
||
|
|
||
|
}
|
||
|
|
||
|
PropertyChanges {
|
||
|
target: pullIndicatorLd
|
||
|
opacity: 0
|
||
|
}
|
||
|
|
||
|
PropertyChanges {
|
||
|
target: busyIndicatorLd
|
||
|
opacity: 1
|
||
|
}
|
||
|
},
|
||
|
State {
|
||
|
name: "hidden"
|
||
|
PropertyChanges {
|
||
|
target: control
|
||
|
minimumPosition: control.refreshPosition
|
||
|
userPosition: 0
|
||
|
maximumPosition: control.refreshPosition
|
||
|
scale: 0
|
||
|
}
|
||
|
|
||
|
PropertyChanges {
|
||
|
target: pullIndicatorLd
|
||
|
opacity: 0
|
||
|
}
|
||
|
|
||
|
PropertyChanges {
|
||
|
target: busyIndicatorLd
|
||
|
opacity: 1
|
||
|
}
|
||
|
}
|
||
|
]
|
||
|
|
||
|
transitions: [
|
||
|
Transition {
|
||
|
NumberAnimation {
|
||
|
duration: 200
|
||
|
properties: "rotation, opacity"
|
||
|
}
|
||
|
},
|
||
|
|
||
|
Transition {
|
||
|
from: "refreshing"
|
||
|
to: "hidden"
|
||
|
|
||
|
PauseAnimation {
|
||
|
duration: 200
|
||
|
}
|
||
|
|
||
|
NumberAnimation {
|
||
|
duration: 200
|
||
|
properties: "scale"
|
||
|
}
|
||
|
|
||
|
onRunningChanged: {
|
||
|
if(control.state === "hidden" && !running){
|
||
|
control.state = "idle"
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
Transition {
|
||
|
from: "hidden"
|
||
|
to: "idle"
|
||
|
}
|
||
|
|
||
|
]
|
||
|
|
||
|
}
|