Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickmaterialbusyindicator.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
4
6
7#include <QtCore/qmath.h>
8#include <QtCore/qeasingcurve.h>
9#include <QtGui/qpainter.h>
10#include <QtQuick/qsgimagenode.h>
11#include <QtQuick/qquickwindow.h>
12#include <QtQuickControls2Impl/private/qquickanimatednode_p.h>
13
15
16/*
17 Relevant Android code:
18
19 - core/res/res/anim/progress_indeterminate_rotation_material.xml contains
20 the rotation animation data.
21 - core/res/res/anim/progress_indeterminate_material.xml contains the trim
22 animation data.
23 - core/res/res/interpolator/trim_start_interpolator.xml and
24 core/res/res/interpolator/trim_end_interpolator.xml contain the start
25 and end trim path interpolators.
26 - addCommand() in core/java/android/util/PathParser.java has a list of the
27 different path commands available.
28*/
29
30static const int SpanAnimationDuration = 700;
32static const int TargetRotation = 720;
33static const int OneDegree = 16;
34static const qreal MinSweepSpan = 10 * OneDegree;
35static const qreal MaxSweepSpan = 300 * OneDegree;
36
38{
39public:
40 QQuickMaterialBusyIndicatorNode(QQuickMaterialBusyIndicator *item);
41
42 void sync(QQuickItem *item) override;
43
44protected:
46
47private:
48 int m_lastStartAngle = 0;
49 int m_lastEndAngle = 0;
50 qreal m_width = 0;
51 qreal m_height = 0;
52 qreal m_devicePixelRatio = 1;
53 QColor m_color;
54};
55
58{
59 setLoopCount(Infinite);
60 setCurrentTime(item->elapsed());
61 setDuration(RotationAnimationDuration);
62
63 QSGImageNode *textureNode = item->window()->createImageNode();
64 textureNode->setOwnsTexture(true);
65 appendChildNode(textureNode);
66
67 // A texture seems to be required here, but we don't have one yet, as we haven't drawn anything,
68 // so just use a blank image.
69 QImage blankImage(item->width(), item->height(), QImage::Format_ARGB32_Premultiplied);
70 blankImage.fill(Qt::transparent);
71 textureNode->setTexture(item->window()->createTextureFromImage(blankImage));
72}
73
75{
76 const qreal w = m_width;
77 const qreal h = m_height;
78 const qreal size = qMin(w, h);
79 const qreal dx = (w - size) / 2;
80 const qreal dy = (h - size) / 2;
81
82 QImage image(size * m_devicePixelRatio, size * m_devicePixelRatio, QImage::Format_ARGB32_Premultiplied);
83 image.fill(Qt::transparent);
84
85 QPainter painter(&image);
86 painter.setRenderHint(QPainter::Antialiasing);
87
88 QPen pen;
89 QSGImageNode *textureNode = static_cast<QSGImageNode *>(firstChild());
90 pen.setColor(m_color);
91 pen.setWidth(qCeil(size / 12) * m_devicePixelRatio);
92 painter.setPen(pen);
93
94 const qreal percentageComplete = time / qreal(RotationAnimationDuration);
95 const qreal spanPercentageComplete = (time % SpanAnimationDuration) / qreal(SpanAnimationDuration);
96 const int iteration = time / SpanAnimationDuration;
97 int startAngle = 0;
98 int endAngle = 0;
99
100 if (iteration % 2 == 0) {
101 if (m_lastStartAngle > 360 * OneDegree)
102 m_lastStartAngle -= 360 * OneDegree;
103
104 // The start angle is only affected by the rotation animation for the "grow" phase.
105 startAngle = m_lastStartAngle;
106 QEasingCurve angleCurve(QEasingCurve::OutQuad);
107 const qreal percentage = angleCurve.valueForProgress(spanPercentageComplete);
108 endAngle = m_lastStartAngle + MinSweepSpan + percentage * (MaxSweepSpan - MinSweepSpan);
109 m_lastEndAngle = endAngle;
110 } else {
111 // Both the start angle *and* the span are affected by the "shrink" phase.
112 QEasingCurve angleCurve(QEasingCurve::InQuad);
113 const qreal percentage = angleCurve.valueForProgress(spanPercentageComplete);
114 startAngle = m_lastEndAngle - MaxSweepSpan + percentage * (MaxSweepSpan - MinSweepSpan);
115 endAngle = m_lastEndAngle;
116 m_lastStartAngle = startAngle;
117 }
118
119 const int halfPen = pen.width() / 2;
120 const QRectF arcBounds = QRectF(halfPen, halfPen,
121 m_devicePixelRatio * size - pen.width(),
122 m_devicePixelRatio * size - pen.width());
123 // The current angle of the rotation animation.
124 const qreal rotation = OneDegree * percentageComplete * -TargetRotation;
125 startAngle -= rotation;
126 endAngle -= rotation;
127 const int angleSpan = endAngle - startAngle;
128 painter.drawArc(arcBounds, -startAngle, -angleSpan);
129 painter.end();
130
131 textureNode->setRect(QRectF(dx, dy, size, size));
132 textureNode->setTexture(window()->createTextureFromImage(image));
133}
134
136{
137 QQuickMaterialBusyIndicator *indicator = static_cast<QQuickMaterialBusyIndicator *>(item);
138 m_color = indicator->color();
139 m_width = indicator->width();
140 m_height = indicator->height();
141 m_devicePixelRatio = indicator->window()->effectiveDevicePixelRatio();
142}
143
144QQuickMaterialBusyIndicator::QQuickMaterialBusyIndicator(QQuickItem *parent) :
145 QQuickItem(parent)
146{
147 setFlag(ItemHasContents);
148}
149
150QColor QQuickMaterialBusyIndicator::color() const
151{
152 return m_color;
153}
154
155void QQuickMaterialBusyIndicator::setColor(const QColor &color)
156{
157 if (m_color == color)
158 return;
159
160 m_color = color;
161 update();
162}
163
164bool QQuickMaterialBusyIndicator::isRunning() const
165{
166 return m_running;
167}
168
169void QQuickMaterialBusyIndicator::setRunning(bool running)
170{
171 m_running = running;
172
173 if (m_running)
174 setVisible(true);
175 // Don't set visible to false if not running, because we use an opacity animation (in QML) to hide ourselves.
176}
177
178int QQuickMaterialBusyIndicator::elapsed() const
179{
180 return m_elapsed;
181}
182
183void QQuickMaterialBusyIndicator::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
184{
185 QQuickItem::itemChange(change, data);
186 switch (change) {
187 case ItemOpacityHasChanged:
188 // If running is set to false and then true within a short period (QTBUG-85860), our
189 // OpacityAnimator cancels the 1 => 0 animation (which was for running being set to false),
190 // setting opacity to 0 and hence visible to false. This happens _after_ setRunning(true)
191 // was called, because the properties were set synchronously but the animation is
192 // asynchronous. To account for this situation, we only hide ourselves if we're not running.
193 if (qFuzzyIsNull(data.realValue) && !m_running)
194 setVisible(false);
195 break;
196 case ItemVisibleHasChanged:
197 update();
198 break;
199 default:
200 break;
201 }
202}
203
204QSGNode *QQuickMaterialBusyIndicator::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
205{
207 if (isRunning() && width() > 0 && height() > 0) {
208 if (!node) {
210 node->start();
211 }
212 node->sync(this);
213 } else {
214 m_elapsed = node ? node->currentTime() : 0;
215 delete node;
216 node = nullptr;
217 }
218 return node;
219}
220
221QT_END_NAMESPACE
222
223#include "moc_qquickmaterialbusyindicator_p.cpp"
QQuickMaterialBusyIndicatorNode(QQuickMaterialBusyIndicator *item)
static const int OneDegree
static const int RotationAnimationDuration
static QT_BEGIN_NAMESPACE const int SpanAnimationDuration
static const qreal MaxSweepSpan
static const qreal MinSweepSpan
static const int TargetRotation