Kirigami2

PullDownIndicator.qml
1/*
2 * SPDX-FileCopyrightText: 2023 Connor Carney <hello@connorcarney.com>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7import QtQuick
8import QtQuick.Controls as QQC2
9import org.kde.kirigami as Kirigami
10import QtQuick.Shapes as QQShapes
11
12/**
13 * @brief A pull-down to refresh indicator that can be added to any Flickable or ScrollablePage.
14 */
15Item {
16 id: root
17
18 //BEGIN properties
19 /**
20 * @brief The flickable that this indicator is attached to.
21 *
22 * If this is not set, the indicator will search for a Flickable in its parent chain.
23 */
24 property Flickable flickable: {
25 let candidate = parent
26 while (candidate) {
27 if (candidate instanceof Flickable) {
28 return candidate
29 } else if (candidate instanceof Kirigami.ScrollablePage) {
30 return candidate.flickable
31 }
32 candidate = candidate.parent
33 }
34 return null;
35 }
36
37 /**
38 * @brief Whether to show the busy indicator at the top of the flickable
39 *
40 * This should be set to true whenever a refresh is in progress. It should typically
41 * be set to true whe triggered() is emitted, and set to false when the refresh is
42 * complete. This is not done automatically because the refresh may be triggered
43 * from outside the indicator.
44 */
45 property bool active: false
47 /**
48 * @brief How far the flickable has been pulled down, between 0 (not at all) and 1 (where a refresh is triggered).
49 */
50 readonly property real progress: !refreshing ? Math.min(-Math.min(flickable?.verticalOvershoot ?? 0, 0) / indicatorContainer.height, 1) : 0
51
52 /**
53 * @brief Time to wait after the flickable has been pulled down before triggering a refresh
54 *
55 * This gives the user a chance to back out of the refresh if they release the flickable
56 * before the refreshDelay has elapsed.
57 */
58 property int refreshDelay: 500
59
60 /**
61 * @brief emitted when the flickable is pulled down far enough to trigger a refresh
62 */
63 signal triggered()
64 //END properties
65
66 Item {
67 id: indicatorContainer
68 parent: root.flickable
69 anchors {
70 bottom: parent?.contentItem?.top
71 bottomMargin: root.flickable.topMargin
72 }
73
74 width: flickable?.width
75 height: Kirigami.Units.gridUnit * 4
76 QQC2.BusyIndicator {
77 id: busyIndicator
78 z: 1
79 anchors.centerIn: parent
80 running: root.active
81 visible: root.active
82 // Android busywidget QQC seems to be broken at custom sizes
83 }
84 QQShapes.Shape {
85 id: spinnerProgress
86 anchors {
87 fill: busyIndicator
88 margins: Kirigami.Units.smallSpacing
89 }
90 visible: !root.active && root.progress > 0
91 QQShapes.ShapePath {
92 strokeWidth: Kirigami.Units.smallSpacing
93 strokeColor: Kirigami.Theme.highlightColor
94 fillColor: "transparent"
95 PathAngleArc {
96 centerX: spinnerProgress.width / 2
97 centerY: spinnerProgress.height / 2
98 radiusX: spinnerProgress.width / 2 - Kirigami.Units.smallSpacing / 2
99 radiusY: spinnerProgress.height / 2 - Kirigami.Units.smallSpacing / 2
100 startAngle: 0
101 sweepAngle: 360 * root.progress
102 }
103 }
104 }
105 }
106
107 onProgressChanged: {
108 if (!root.active && root.progress >= 1) {
109 refreshTriggerTimer.running = true;
110 } else {
111 refreshTriggerTimer.running = false;
112 }
113 }
114
115
116 states: [
117 State {
118 name: "active"
119 when: root.active
120 PropertyChanges {
121 target: indicatorContainer
122 anchors.bottomMargin: root.flickable.topMargin - indicatorContainer.height
123 }
124 PropertyChanges {
125 target: root.flickable
126 explicit: true
127
128 // this is not a loop because of explicit:true above
129 // It adds the height of the indicator to the topMargin of the flickable
130 // when we enter the active state; the change is automatically reversed
131 // when returning to the base state.
132 topMargin: indicatorContainer.height + root.flickable.topMargin
133 }
134 }
135 ]
136
137 transitions: [
138 Transition {
139 from: ""
140 to: "active"
141 enabled: root.flickable.verticalOvershoot >= 0
142 reversible: true
143 NumberAnimation {
144 target: root.flickable
145 properties: "topMargin"
146 easing.type: Easing.InOutQuad
147 duration: Kirigami.Units.longDuration
148 }
149 }
150 ]
151
152 Timer {
153 id: refreshTriggerTimer
154 interval: root.refreshDelay
155 onTriggered: {
156 if (!root.active && root.progress >= 1) {
157 root.triggered()
158 }
159 }
160 }
161
162}
void triggered()
emitted when the flickable is pulled down far enough to trigger a refresh
Flickable flickable
The flickable that this indicator is attached to.
int refreshDelay
Time to wait after the flickable has been pulled down before triggering a refresh.
real progress
How far the flickable has been pulled down, between 0 (not at all) and 1 (where a refresh is triggere...
bool active
Whether to show the busy indicator at the top of the flickable.
QString name(StandardAction id)
KGuiItem properties()
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 18 2025 12:03:26 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.