1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickuniversalprogressbar_p.h"
5
6#include <QtCore/qmath.h>
7#include <QtCore/qeasingcurve.h>
8#include <QtQuick/private/qquickitem_p.h>
9#include <QtQuick/private/qsgadaptationlayer_p.h>
10#include <QtQuick/qsgrectanglenode.h>
11#include <QtQuickControls2Impl/private/qquickanimatednode_p.h>
12
13QT_BEGIN_NAMESPACE
14
15static const int QupbPhaseCount = 4;
16static const int EllipseCount = 5;
17static const int QupbInterval = 167;
18static const int QupbTotalDuration = 3917;
19static const int VisibleDuration = 3000;
20static const qreal EllipseDiameter = 4;
21static const qreal EllipseOffset = 4;
22static const qreal ContainerAnimationStartPosition = -34; // absolute
23static const qreal ContainerAnimationEndPosition = 0.435222; // relative
24static const qreal EllipseAnimationWellPosition = 0.333333333333333; // relative
25static const qreal EllipseAnimationEndPosition = 0.666666666666667; // relative
26
27class QQuickUniversalProgressBarNode : public QQuickAnimatedNode
28{
29public:
30 QQuickUniversalProgressBarNode(QQuickUniversalProgressBar *item);
31
32 void updateCurrentTime(int time) override;
33 void sync(QQuickItem *item) override;
34
35private:
36 struct Phase {
37 Phase() = default;
38 Phase(int d, qreal f, qreal t) : duration(d), from(f), to(t) { }
39 int duration = 0;
40 qreal from = 0;
41 qreal to = 0;
42 };
43
44 bool m_indeterminate = false;
45 Phase m_borderPhases[QupbPhaseCount];
46 Phase m_ellipsePhases[QupbPhaseCount];
47};
48
49QQuickUniversalProgressBarNode::QQuickUniversalProgressBarNode(QQuickUniversalProgressBar *item)
50 : QQuickAnimatedNode(item)
51{
52 setLoopCount(Infinite);
53 setDuration(QupbTotalDuration);
54
55 m_borderPhases[0] = Phase( 500, -50, 0);
56 m_borderPhases[1] = Phase(1500, 0, 0);
57 m_borderPhases[2] = Phase(1000, 0, 100);
58 m_borderPhases[3] = Phase( 917, 100, 100);
59
60 m_ellipsePhases[0] = Phase(1000, 0, EllipseAnimationWellPosition);
61 m_ellipsePhases[1] = Phase(1000, EllipseAnimationWellPosition, EllipseAnimationWellPosition);
62 m_ellipsePhases[2] = Phase(1000, EllipseAnimationWellPosition, EllipseAnimationEndPosition);
63 m_ellipsePhases[3] = Phase(1000, EllipseAnimationWellPosition, EllipseAnimationEndPosition);
64}
65
66void QQuickUniversalProgressBarNode::updateCurrentTime(int time)
67{
68 QSGRectangleNode *geometryNode = static_cast<QSGRectangleNode *>(firstChild());
69 Q_ASSERT(!geometryNode || geometryNode->type() == QSGNode::GeometryNodeType);
70 if (!geometryNode)
71 return;
72
73 QSGTransformNode *gridNode = static_cast<QSGTransformNode *>(geometryNode->firstChild());
74 Q_ASSERT(!gridNode || gridNode->type() == QSGNode::TransformNodeType);
75 if (!gridNode)
76 return;
77
78 qreal width = geometryNode->rect().width();
79 {
80 qreal from = ContainerAnimationStartPosition;
81 qreal to = from + ContainerAnimationEndPosition * width;
82 qreal progress = static_cast<qreal>(time) / QupbTotalDuration;
83 qreal dx = from + (to - from) * progress;
84
85 QMatrix4x4 matrix;
86 matrix.translate(x: dx, y: 0);
87 gridNode->setMatrix(matrix);
88 }
89
90 int nodeIndex = 0;
91 QSGTransformNode *borderNode = static_cast<QSGTransformNode *>(gridNode->firstChild());
92 while (borderNode) {
93 Q_ASSERT(borderNode->type() == QSGNode::TransformNodeType);
94
95 QSGTransformNode *ellipseNode = static_cast<QSGTransformNode *>(borderNode->firstChild());
96 Q_ASSERT(ellipseNode->type() == QSGNode::TransformNodeType);
97
98 QSGOpacityNode *opacityNode = static_cast<QSGOpacityNode *>(ellipseNode->firstChild());
99 Q_ASSERT(opacityNode->type() == QSGNode::OpacityNodeType);
100
101 int begin = nodeIndex * QupbInterval;
102 int end = VisibleDuration + nodeIndex * QupbInterval;
103
104 bool visible = time >= begin && time <= end;
105 opacityNode->setOpacity(visible ? 1.0 : 0.0);
106
107 if (visible) {
108 {
109 int phaseIndex, remain = time, elapsed = 0;
110 for (phaseIndex = 0; phaseIndex < QupbPhaseCount - 1; ++phaseIndex) {
111 if (remain <= m_borderPhases[phaseIndex].duration + begin)
112 break;
113 remain -= m_borderPhases[phaseIndex].duration;
114 elapsed += m_borderPhases[phaseIndex].duration;
115 }
116
117 const Phase &phase = m_borderPhases[phaseIndex];
118
119 qreal pos = time - elapsed - begin;
120 qreal progress = pos / phase.duration;
121 qreal dx = phase.from + (phase.to - phase.from) * progress;
122
123 QMatrix4x4 matrix;
124 matrix.translate(x: dx, y: 0);
125 borderNode->setMatrix(matrix);
126 }
127
128 {
129 QEasingCurve curve(QEasingCurve::BezierSpline);
130 curve.addCubicBezierSegment(c1: QPointF(0.4, 0.0), c2: QPointF(0.6, 1.0), endPoint: QPointF(1.0, 1.0));
131
132 int phaseIndex, remain = time, elapsed = 0;
133 for (phaseIndex = 0; phaseIndex < QupbPhaseCount - 1; ++phaseIndex) {
134 if (remain <= m_ellipsePhases[phaseIndex].duration + begin)
135 break;
136 remain -= m_ellipsePhases[phaseIndex].duration;
137 elapsed += m_ellipsePhases[phaseIndex].duration;
138 }
139
140 const Phase &phase = m_ellipsePhases[phaseIndex];
141
142 qreal from = phase.from * width;
143 qreal to = phase.to * width;
144 qreal pos = time - elapsed - begin;
145 qreal progress = curve.valueForProgress(progress: pos / phase.duration);
146 qreal dx = from + (to - from) * progress;
147
148 QMatrix4x4 matrix;
149 matrix.translate(x: dx, y: 0);
150 ellipseNode->setMatrix(matrix);
151 }
152 }
153
154 borderNode = static_cast<QSGTransformNode *>(borderNode->nextSibling());
155 ++nodeIndex;
156 }
157}
158
159void QQuickUniversalProgressBarNode::sync(QQuickItem *item)
160{
161 QQuickUniversalProgressBar *bar = static_cast<QQuickUniversalProgressBar *>(item);
162 if (m_indeterminate != bar->isIndeterminate()) {
163 m_indeterminate = bar->isIndeterminate();
164 if (m_indeterminate)
165 start();
166 else
167 stop();
168 }
169
170 QQuickItemPrivate *d = QQuickItemPrivate::get(item);
171
172 QRectF bounds = item->boundingRect();
173 bounds.setHeight(item->implicitHeight());
174 bounds.moveTop(pos: (item->height() - bounds.height()) / 2.0);
175 if (!m_indeterminate)
176 bounds.setWidth(bar->progress() * bounds.width());
177
178 QSGRectangleNode *geometryNode = static_cast<QSGRectangleNode *>(firstChild());
179 if (!geometryNode) {
180 geometryNode = item->window()->createRectangleNode();
181 appendChildNode(node: geometryNode);
182 }
183 geometryNode->setRect(bounds);
184 geometryNode->setColor(m_indeterminate ? Qt::transparent : bar->color());
185
186 if (!m_indeterminate) {
187 while (QSGNode *node = geometryNode->firstChild())
188 delete node;
189 return;
190 }
191
192 QSGTransformNode *gridNode = static_cast<QSGTransformNode *>(geometryNode->firstChild());
193 if (!gridNode) {
194 gridNode = new QSGTransformNode;
195 geometryNode->appendChildNode(node: gridNode);
196 }
197 Q_ASSERT(gridNode->type() == QSGNode::TransformNodeType);
198
199 QSGNode *borderNode = gridNode->firstChild();
200 for (int i = 0; i < EllipseCount; ++i) {
201 if (!borderNode) {
202 borderNode = new QSGTransformNode;
203 gridNode->appendChildNode(node: borderNode);
204
205 QSGTransformNode *ellipseNode = new QSGTransformNode;
206 borderNode->appendChildNode(node: ellipseNode);
207
208 QSGOpacityNode *opacityNode = new QSGOpacityNode;
209 ellipseNode->appendChildNode(node: opacityNode);
210
211 QSGInternalRectangleNode *rectNode = d->sceneGraphContext()->createInternalRectangleNode();
212 rectNode->setAntialiasing(true);
213 rectNode->setRadius(EllipseDiameter / 2);
214 opacityNode->appendChildNode(node: rectNode);
215 }
216 Q_ASSERT(borderNode->type() == QSGNode::TransformNodeType);
217
218 QSGNode *ellipseNode = borderNode->firstChild();
219 Q_ASSERT(ellipseNode->type() == QSGNode::TransformNodeType);
220
221 QSGNode *opacityNode = ellipseNode->firstChild();
222 Q_ASSERT(opacityNode->type() == QSGNode::OpacityNodeType);
223
224 QSGInternalRectangleNode *rectNode = static_cast<QSGInternalRectangleNode *>(opacityNode->firstChild());
225 Q_ASSERT(rectNode->type() == QSGNode::GeometryNodeType);
226
227 rectNode->setRect(QRectF((EllipseCount - i - 1) * (EllipseDiameter + EllipseOffset), (item->height() - EllipseDiameter) / 2, EllipseDiameter, EllipseDiameter));
228 rectNode->setColor(bar->color());
229 rectNode->update();
230
231 borderNode = borderNode->nextSibling();
232 }
233}
234
235QQuickUniversalProgressBar::QQuickUniversalProgressBar(QQuickItem *parent)
236 : QQuickItem(parent)
237{
238 setFlag(flag: ItemHasContents);
239}
240
241QColor QQuickUniversalProgressBar::color() const
242{
243 return m_color;
244}
245
246void QQuickUniversalProgressBar::setColor(const QColor &color)
247{
248 if (m_color == color)
249 return;
250
251 m_color = color;
252 update();
253}
254
255qreal QQuickUniversalProgressBar::progress() const
256{
257 return m_progress;
258}
259
260void QQuickUniversalProgressBar::setProgress(qreal progress)
261{
262 if (progress == m_progress)
263 return;
264
265 m_progress = progress;
266 update();
267}
268
269bool QQuickUniversalProgressBar::isIndeterminate() const
270{
271 return m_indeterminate;
272}
273
274void QQuickUniversalProgressBar::setIndeterminate(bool indeterminate)
275{
276 if (indeterminate == m_indeterminate)
277 return;
278
279 m_indeterminate = indeterminate;
280 setClip(m_indeterminate);
281 update();
282}
283
284void QQuickUniversalProgressBar::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
285{
286 QQuickItem::itemChange(change, data);
287 if (change == ItemVisibleHasChanged)
288 update();
289}
290
291QSGNode *QQuickUniversalProgressBar::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
292{
293 QQuickUniversalProgressBarNode *node = static_cast<QQuickUniversalProgressBarNode *>(oldNode);
294 if (isVisible() && width() > 0 && height() > 0) {
295 if (!node)
296 node = new QQuickUniversalProgressBarNode(this);
297 node->sync(item: this);
298 } else {
299 delete node;
300 node = nullptr;
301 }
302 return node;
303}
304
305QT_END_NAMESPACE
306
307#include "moc_qquickuniversalprogressbar_p.cpp"
308

source code of qtdeclarative/src/quickcontrols/universal/impl/qquickuniversalprogressbar.cpp