1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 The Qt Company Ltd. |
4 | ** Contact: http://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL3$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see http://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at http://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or later as published by the Free |
28 | ** Software Foundation and appearing in the file LICENSE.GPL included in |
29 | ** the packaging of this file. Please review the following information to |
30 | ** ensure the GNU General Public License version 2.0 requirements will be |
31 | ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
32 | ** |
33 | ** $QT_END_LICENSE$ |
34 | ** |
35 | ****************************************************************************/ |
36 | |
37 | #include "qquickuniversalprogressbar_p.h" |
38 | |
39 | #include <QtCore/qmath.h> |
40 | #include <QtCore/qeasingcurve.h> |
41 | #include <QtQuick/private/qquickitem_p.h> |
42 | #include <QtQuick/private/qsgadaptationlayer_p.h> |
43 | #include <QtQuick/qsgrectanglenode.h> |
44 | #include <QtQuickControls2/private/qquickanimatednode_p.h> |
45 | |
46 | QT_BEGIN_NAMESPACE |
47 | |
48 | static const int PhaseCount = 4; |
49 | static const int EllipseCount = 5; |
50 | static const int Interval = 167; |
51 | static const int TotalDuration = 3917; |
52 | static const int VisibleDuration = 3000; |
53 | static const qreal EllipseDiameter = 4; |
54 | static const qreal EllipseOffset = 4; |
55 | static const qreal ContainerAnimationStartPosition = -34; // absolute |
56 | static const qreal ContainerAnimationEndPosition = 0.435222; // relative |
57 | static const qreal EllipseAnimationWellPosition = 0.333333333333333; // relative |
58 | static const qreal EllipseAnimationEndPosition = 0.666666666666667; // relative |
59 | |
60 | class QQuickUniversalProgressBarNode : public QQuickAnimatedNode |
61 | { |
62 | public: |
63 | QQuickUniversalProgressBarNode(QQuickUniversalProgressBar *item); |
64 | |
65 | void updateCurrentTime(int time) override; |
66 | void sync(QQuickItem *item) override; |
67 | |
68 | private: |
69 | struct Phase { |
70 | Phase() = default; |
71 | Phase(int d, qreal f, qreal t) : duration(d), from(f), to(t) { } |
72 | int duration = 0; |
73 | qreal from = 0; |
74 | qreal to = 0; |
75 | }; |
76 | |
77 | bool m_indeterminate = false; |
78 | Phase m_borderPhases[PhaseCount]; |
79 | Phase m_ellipsePhases[PhaseCount]; |
80 | }; |
81 | |
82 | QQuickUniversalProgressBarNode::QQuickUniversalProgressBarNode(QQuickUniversalProgressBar *item) |
83 | : QQuickAnimatedNode(item) |
84 | { |
85 | setLoopCount(Infinite); |
86 | setDuration(TotalDuration); |
87 | |
88 | m_borderPhases[0] = Phase( 500, -50, 0); |
89 | m_borderPhases[1] = Phase(1500, 0, 0); |
90 | m_borderPhases[2] = Phase(1000, 0, 100); |
91 | m_borderPhases[3] = Phase( 917, 100, 100); |
92 | |
93 | m_ellipsePhases[0] = Phase(1000, 0, EllipseAnimationWellPosition); |
94 | m_ellipsePhases[1] = Phase(1000, EllipseAnimationWellPosition, EllipseAnimationWellPosition); |
95 | m_ellipsePhases[2] = Phase(1000, EllipseAnimationWellPosition, EllipseAnimationEndPosition); |
96 | m_ellipsePhases[3] = Phase(1000, EllipseAnimationWellPosition, EllipseAnimationEndPosition); |
97 | } |
98 | |
99 | void QQuickUniversalProgressBarNode::updateCurrentTime(int time) |
100 | { |
101 | QSGRectangleNode *geometryNode = static_cast<QSGRectangleNode *>(firstChild()); |
102 | Q_ASSERT(!geometryNode || geometryNode->type() == QSGNode::GeometryNodeType); |
103 | if (!geometryNode) |
104 | return; |
105 | |
106 | QSGTransformNode *gridNode = static_cast<QSGTransformNode *>(geometryNode->firstChild()); |
107 | Q_ASSERT(!gridNode || gridNode->type() == QSGNode::TransformNodeType); |
108 | if (!gridNode) |
109 | return; |
110 | |
111 | qreal width = geometryNode->rect().width(); |
112 | { |
113 | qreal from = ContainerAnimationStartPosition; |
114 | qreal to = from + ContainerAnimationEndPosition * width; |
115 | qreal progress = static_cast<qreal>(time) / TotalDuration; |
116 | qreal dx = from + (to - from) * progress; |
117 | |
118 | QMatrix4x4 matrix; |
119 | matrix.translate(x: dx, y: 0); |
120 | gridNode->setMatrix(matrix); |
121 | } |
122 | |
123 | int nodeIndex = 0; |
124 | QSGTransformNode *borderNode = static_cast<QSGTransformNode *>(gridNode->firstChild()); |
125 | while (borderNode) { |
126 | Q_ASSERT(borderNode->type() == QSGNode::TransformNodeType); |
127 | |
128 | QSGTransformNode *ellipseNode = static_cast<QSGTransformNode *>(borderNode->firstChild()); |
129 | Q_ASSERT(ellipseNode->type() == QSGNode::TransformNodeType); |
130 | |
131 | QSGOpacityNode *opacityNode = static_cast<QSGOpacityNode *>(ellipseNode->firstChild()); |
132 | Q_ASSERT(opacityNode->type() == QSGNode::OpacityNodeType); |
133 | |
134 | int begin = nodeIndex * Interval; |
135 | int end = VisibleDuration + nodeIndex * Interval; |
136 | |
137 | bool visible = time >= begin && time <= end; |
138 | opacityNode->setOpacity(visible ? 1.0 : 0.0); |
139 | |
140 | if (visible) { |
141 | { |
142 | int phaseIndex, remain = time, elapsed = 0; |
143 | for (phaseIndex = 0; phaseIndex < PhaseCount - 1; ++phaseIndex) { |
144 | if (remain <= m_borderPhases[phaseIndex].duration + begin) |
145 | break; |
146 | remain -= m_borderPhases[phaseIndex].duration; |
147 | elapsed += m_borderPhases[phaseIndex].duration; |
148 | } |
149 | |
150 | const Phase &phase = m_borderPhases[phaseIndex]; |
151 | |
152 | qreal pos = time - elapsed - begin; |
153 | qreal progress = pos / phase.duration; |
154 | qreal dx = phase.from + (phase.to - phase.from) * progress; |
155 | |
156 | QMatrix4x4 matrix; |
157 | matrix.translate(x: dx, y: 0); |
158 | borderNode->setMatrix(matrix); |
159 | } |
160 | |
161 | { |
162 | QEasingCurve curve(QEasingCurve::BezierSpline); |
163 | curve.addCubicBezierSegment(c1: QPointF(0.4, 0.0), c2: QPointF(0.6, 1.0), endPoint: QPointF(1.0, 1.0)); |
164 | |
165 | int phaseIndex, remain = time, elapsed = 0; |
166 | for (phaseIndex = 0; phaseIndex < PhaseCount - 1; ++phaseIndex) { |
167 | if (remain <= m_ellipsePhases[phaseIndex].duration + begin) |
168 | break; |
169 | remain -= m_ellipsePhases[phaseIndex].duration; |
170 | elapsed += m_ellipsePhases[phaseIndex].duration; |
171 | } |
172 | |
173 | const Phase &phase = m_ellipsePhases[phaseIndex]; |
174 | |
175 | qreal from = phase.from * width; |
176 | qreal to = phase.to * width; |
177 | qreal pos = time - elapsed - begin; |
178 | qreal progress = curve.valueForProgress(progress: pos / phase.duration); |
179 | qreal dx = from + (to - from) * progress; |
180 | |
181 | QMatrix4x4 matrix; |
182 | matrix.translate(x: dx, y: 0); |
183 | ellipseNode->setMatrix(matrix); |
184 | } |
185 | } |
186 | |
187 | borderNode = static_cast<QSGTransformNode *>(borderNode->nextSibling()); |
188 | ++nodeIndex; |
189 | } |
190 | } |
191 | |
192 | void QQuickUniversalProgressBarNode::sync(QQuickItem *item) |
193 | { |
194 | QQuickUniversalProgressBar *bar = static_cast<QQuickUniversalProgressBar *>(item); |
195 | if (m_indeterminate != bar->isIndeterminate()) { |
196 | m_indeterminate = bar->isIndeterminate(); |
197 | if (m_indeterminate) |
198 | start(); |
199 | else |
200 | stop(); |
201 | } |
202 | |
203 | QQuickItemPrivate *d = QQuickItemPrivate::get(item); |
204 | |
205 | QRectF bounds = item->boundingRect(); |
206 | bounds.setHeight(item->implicitHeight()); |
207 | bounds.moveTop(pos: (item->height() - bounds.height()) / 2.0); |
208 | if (!m_indeterminate) |
209 | bounds.setWidth(bar->progress() * bounds.width()); |
210 | |
211 | QSGRectangleNode *geometryNode = static_cast<QSGRectangleNode *>(firstChild()); |
212 | if (!geometryNode) { |
213 | geometryNode = item->window()->createRectangleNode(); |
214 | appendChildNode(node: geometryNode); |
215 | } |
216 | geometryNode->setRect(bounds); |
217 | geometryNode->setColor(m_indeterminate ? Qt::transparent : bar->color()); |
218 | |
219 | if (!m_indeterminate) { |
220 | while (QSGNode *node = geometryNode->firstChild()) |
221 | delete node; |
222 | return; |
223 | } |
224 | |
225 | QSGTransformNode *gridNode = static_cast<QSGTransformNode *>(geometryNode->firstChild()); |
226 | if (!gridNode) { |
227 | gridNode = new QSGTransformNode; |
228 | geometryNode->appendChildNode(node: gridNode); |
229 | } |
230 | Q_ASSERT(gridNode->type() == QSGNode::TransformNodeType); |
231 | |
232 | QSGNode *borderNode = gridNode->firstChild(); |
233 | for (int i = 0; i < EllipseCount; ++i) { |
234 | if (!borderNode) { |
235 | borderNode = new QSGTransformNode; |
236 | gridNode->appendChildNode(node: borderNode); |
237 | |
238 | QSGTransformNode *ellipseNode = new QSGTransformNode; |
239 | borderNode->appendChildNode(node: ellipseNode); |
240 | |
241 | QSGOpacityNode *opacityNode = new QSGOpacityNode; |
242 | ellipseNode->appendChildNode(node: opacityNode); |
243 | |
244 | QSGInternalRectangleNode *rectNode = d->sceneGraphContext()->createInternalRectangleNode(); |
245 | rectNode->setAntialiasing(true); |
246 | rectNode->setRadius(EllipseDiameter / 2); |
247 | opacityNode->appendChildNode(node: rectNode); |
248 | } |
249 | Q_ASSERT(borderNode->type() == QSGNode::TransformNodeType); |
250 | |
251 | QSGNode *ellipseNode = borderNode->firstChild(); |
252 | Q_ASSERT(ellipseNode->type() == QSGNode::TransformNodeType); |
253 | |
254 | QSGNode *opacityNode = ellipseNode->firstChild(); |
255 | Q_ASSERT(opacityNode->type() == QSGNode::OpacityNodeType); |
256 | |
257 | QSGInternalRectangleNode *rectNode = static_cast<QSGInternalRectangleNode *>(opacityNode->firstChild()); |
258 | Q_ASSERT(rectNode->type() == QSGNode::GeometryNodeType); |
259 | |
260 | rectNode->setRect(QRectF((EllipseCount - i - 1) * (EllipseDiameter + EllipseOffset), (item->height() - EllipseDiameter) / 2, EllipseDiameter, EllipseDiameter)); |
261 | rectNode->setColor(bar->color()); |
262 | rectNode->update(); |
263 | |
264 | borderNode = borderNode->nextSibling(); |
265 | } |
266 | } |
267 | |
268 | QQuickUniversalProgressBar::QQuickUniversalProgressBar(QQuickItem *parent) |
269 | : QQuickItem(parent) |
270 | { |
271 | setFlag(flag: ItemHasContents); |
272 | } |
273 | |
274 | QColor QQuickUniversalProgressBar::color() const |
275 | { |
276 | return m_color; |
277 | } |
278 | |
279 | void QQuickUniversalProgressBar::setColor(const QColor &color) |
280 | { |
281 | if (m_color == color) |
282 | return; |
283 | |
284 | m_color = color; |
285 | update(); |
286 | } |
287 | |
288 | qreal QQuickUniversalProgressBar::progress() const |
289 | { |
290 | return m_progress; |
291 | } |
292 | |
293 | void QQuickUniversalProgressBar::setProgress(qreal progress) |
294 | { |
295 | if (progress == m_progress) |
296 | return; |
297 | |
298 | m_progress = progress; |
299 | update(); |
300 | } |
301 | |
302 | bool QQuickUniversalProgressBar::isIndeterminate() const |
303 | { |
304 | return m_indeterminate; |
305 | } |
306 | |
307 | void QQuickUniversalProgressBar::setIndeterminate(bool indeterminate) |
308 | { |
309 | if (indeterminate == m_indeterminate) |
310 | return; |
311 | |
312 | m_indeterminate = indeterminate; |
313 | setClip(m_indeterminate); |
314 | update(); |
315 | } |
316 | |
317 | void QQuickUniversalProgressBar::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) |
318 | { |
319 | QQuickItem::itemChange(change, data); |
320 | if (change == ItemVisibleHasChanged) |
321 | update(); |
322 | } |
323 | |
324 | QSGNode *QQuickUniversalProgressBar::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) |
325 | { |
326 | QQuickUniversalProgressBarNode *node = static_cast<QQuickUniversalProgressBarNode *>(oldNode); |
327 | if (isVisible() && width() > 0 && height() > 0) { |
328 | if (!node) |
329 | node = new QQuickUniversalProgressBarNode(this); |
330 | node->sync(item: this); |
331 | } else { |
332 | delete node; |
333 | node = nullptr; |
334 | } |
335 | return node; |
336 | } |
337 | |
338 | QT_END_NAMESPACE |
339 | |