Warning, /frameworks/kirigami/src/controls/private/PullDownIndicator.qml is written in an unsupported language. File is not indexed.

0001 /*
0002  *  SPDX-FileCopyrightText: 2023 Connor Carney <hello@connorcarney.com>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 import QtQuick
0008 import QtQuick.Controls as QQC2
0009 import org.kde.kirigami as Kirigami
0010 import QtQuick.Shapes as QQShapes
0011 
0012 /**
0013  * @brief A pull-down to refresh indicator that can be added to any Flickable or ScrollablePage.
0014  */
0015 Item {
0016     id: root
0017 
0018     //BEGIN properties
0019     /**
0020      * @brief The flickable that this indicator is attached to.
0021      *
0022      * If this is not set, the indicator will search for a Flickable in its parent chain.
0023      */
0024     property Flickable flickable: {
0025         let candidate = parent
0026         while (candidate) {
0027             if (candidate instanceof Flickable) {
0028                 return candidate
0029             } else if (candidate instanceof Kirigami.ScrollablePage) {
0030                 return candidate.flickable
0031             }
0032             candidate = candidate.parent
0033         }
0034         return null;
0035     }
0036 
0037     /**
0038      * @brief Whether to show the busy indicator at the top of the flickable
0039      *
0040      * This should be set to true whenever a refresh is in progress. It should typically
0041      * be set to true whe triggered() is emitted, and set to false when the refresh is
0042      * complete. This is not done automatically because the refresh may be triggered
0043      * from outside the indicator.
0044      */
0045     property bool active: false
0046 
0047     /**
0048      * @brief How far the flickable has been pulled down, between 0 (not at all) and 1 (where a refresh is triggered).
0049      */
0050     readonly property real progress: !refreshing ? Math.min(-Math.min(flickable?.verticalOvershoot ?? 0, 0) / indicatorContainer.height, 1) : 0
0051 
0052     /**
0053      * @brief Time to wait after the flickable has been pulled down before triggering a refresh
0054      *
0055      * This gives the user a chance to back out of the refresh if they release the flickable
0056      * before the refreshDelay has elapsed.
0057      */
0058     property int refreshDelay: 500
0059 
0060     /**
0061      * @brief emitted when the flickable is pulled down far enough to trigger a refresh
0062      */
0063     signal triggered()
0064     //END properties
0065 
0066     Item {
0067         id: indicatorContainer
0068         parent: root.flickable
0069         anchors {
0070             bottom: parent?.contentItem?.top
0071             bottomMargin: root.flickable.topMargin
0072         }
0073 
0074         width: flickable?.width
0075         height: Kirigami.Units.gridUnit * 4
0076         QQC2.BusyIndicator {
0077             id: busyIndicator
0078             z: 1
0079             anchors.centerIn: parent
0080             running: root.active
0081             visible: root.active
0082             // Android busywidget QQC seems to be broken at custom sizes
0083         }
0084         QQShapes.Shape {
0085             id: spinnerProgress
0086             anchors {
0087                 fill: busyIndicator
0088                 margins: Kirigami.Units.smallSpacing
0089             }
0090             visible: !root.active && root.progress > 0
0091             QQShapes.ShapePath {
0092                 strokeWidth: Kirigami.Units.smallSpacing
0093                 strokeColor: Kirigami.Theme.highlightColor
0094                 fillColor: "transparent"
0095                 PathAngleArc {
0096                     centerX: spinnerProgress.width / 2
0097                     centerY: spinnerProgress.height / 2
0098                     radiusX: spinnerProgress.width / 2 - Kirigami.Units.smallSpacing / 2
0099                     radiusY: spinnerProgress.height / 2 - Kirigami.Units.smallSpacing / 2
0100                     startAngle: 0
0101                     sweepAngle: 360 * root.progress
0102                 }
0103             }
0104         }
0105     }
0106 
0107     onProgressChanged: {
0108         if (!root.active && root.progress >= 1) {
0109             refreshTriggerTimer.running = true;
0110         } else {
0111             refreshTriggerTimer.running = false;
0112         }
0113     }
0114 
0115 
0116     states: [
0117         State {
0118             name: "active"
0119             when: root.active
0120             PropertyChanges {
0121                 target: indicatorContainer
0122                 anchors.bottomMargin: root.flickable.topMargin - indicatorContainer.height
0123             }
0124             PropertyChanges {
0125                 target: root.flickable
0126                 explicit: true
0127 
0128                 // this is not a loop because of explicit:true above
0129                 // It adds the height of the indicator to the topMargin of the flickable
0130                 // when we enter the active state; the change is automatically reversed
0131                 // when returning to the base state.
0132                 topMargin: indicatorContainer.height + root.flickable.topMargin
0133             }
0134         }
0135     ]
0136 
0137     transitions: [
0138         Transition {
0139             from: ""
0140             to: "active"
0141             enabled: root.flickable.verticalOvershoot >= 0
0142             reversible: true
0143             NumberAnimation {
0144                 target: root.flickable
0145                 properties: "topMargin"
0146                 easing.type: Easing.InOutQuad
0147                 duration: Kirigami.Units.longDuration
0148             }
0149         }
0150     ]
0151 
0152     Timer {
0153         id: refreshTriggerTimer
0154         interval: root.refreshDelay
0155         onTriggered: {
0156             if (!root.active && root.progress >= 1) {
0157                 root.triggered()
0158             }
0159         }
0160     }
0161 
0162 }