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 "qquickbasicbusyindicator_p.h"
5
6#include <QtQuick/private/qquickitem_p.h>
7#include <QtQuick/private/qsgadaptationlayer_p.h>
8#include <QtQuickControls2Impl/private/qquickanimatednode_p.h>
9
10QT_BEGIN_NAMESPACE
11
12static const int CircleCount = 10;
13static const int QbbiTotalDuration = 100 * CircleCount * 2;
14static const QRgb TransparentColor = 0x00000000;
15
16static QPointF moveCircle(const QPointF &pos, qreal rotation, qreal distance)
17{
18 return pos - QTransform().rotate(a: rotation).map(p: QPointF(0, distance));
19}
20
21class QQuickBasicBusyIndicatorNode : public QQuickAnimatedNode
22{
23public:
24 QQuickBasicBusyIndicatorNode(QQuickBasicBusyIndicator *item);
25
26 void updateCurrentTime(int time) override;
27 void sync(QQuickItem *item) override;
28
29private:
30 QColor m_pen;
31 QColor m_fill;
32};
33
34QQuickBasicBusyIndicatorNode::QQuickBasicBusyIndicatorNode(QQuickBasicBusyIndicator *item)
35 : QQuickAnimatedNode(item)
36{
37 setLoopCount(Infinite);
38 setDuration(QbbiTotalDuration);
39 setCurrentTime(item->elapsed());
40
41 for (int i = 0; i < CircleCount; ++i) {
42 QSGTransformNode *transformNode = new QSGTransformNode;
43 appendChildNode(node: transformNode);
44
45 QQuickItemPrivate *d = QQuickItemPrivate::get(item);
46 QSGInternalRectangleNode *rectNode = d->sceneGraphContext()->createInternalRectangleNode();
47 rectNode->setAntialiasing(true);
48 transformNode->appendChildNode(node: rectNode);
49 }
50}
51
52void QQuickBasicBusyIndicatorNode::updateCurrentTime(int time)
53{
54 const qreal percentageComplete = time / qreal(QbbiTotalDuration);
55 const qreal firstPhaseProgress = percentageComplete <= 0.5 ? percentageComplete * 2 : 0;
56 const qreal secondPhaseProgress = percentageComplete > 0.5 ? (percentageComplete - 0.5) * 2 : 0;
57
58 QSGTransformNode *transformNode = static_cast<QSGTransformNode*>(firstChild());
59 Q_ASSERT(transformNode->type() == QSGNode::TransformNodeType);
60 for (int i = 0; i < CircleCount; ++i) {
61 QSGInternalRectangleNode *rectNode = static_cast<QSGInternalRectangleNode*>(transformNode->firstChild());
62 Q_ASSERT(rectNode->type() == QSGNode::GeometryNodeType);
63
64 const bool fill = (firstPhaseProgress > qreal(i) / CircleCount) || (secondPhaseProgress > 0 && secondPhaseProgress < qreal(i) / CircleCount);
65 rectNode->setColor(fill ? m_fill : QColor::fromRgba(rgba: TransparentColor));
66 rectNode->setPenColor(m_pen);
67 rectNode->setPenWidth(1);
68 rectNode->update();
69
70 transformNode = static_cast<QSGTransformNode*>(transformNode->nextSibling());
71 }
72}
73
74void QQuickBasicBusyIndicatorNode::sync(QQuickItem *item)
75{
76 const qreal w = item->width();
77 const qreal h = item->height();
78 const qreal sz = qMin(a: w, b: h);
79 const qreal dx = (w - sz) / 2;
80 const qreal dy = (h - sz) / 2;
81 const int circleRadius = sz / 12;
82
83 m_pen = static_cast<QQuickBasicBusyIndicator *>(item)->pen();
84 m_fill = static_cast<QQuickBasicBusyIndicator *>(item)->fill();
85
86 QSGTransformNode *transformNode = static_cast<QSGTransformNode *>(firstChild());
87 for (int i = 0; i < CircleCount; ++i) {
88 Q_ASSERT(transformNode->type() == QSGNode::TransformNodeType);
89
90 QSGInternalRectangleNode *rectNode = static_cast<QSGInternalRectangleNode *>(transformNode->firstChild());
91 Q_ASSERT(rectNode->type() == QSGNode::GeometryNodeType);
92
93 QPointF pos = QPointF(sz / 2 - circleRadius, sz / 2 - circleRadius);
94 pos = moveCircle(pos, rotation: 360.0 / CircleCount * i, distance: sz / 2 - circleRadius);
95
96 QMatrix4x4 m;
97 m.translate(x: dx + pos.x(), y: dy + pos.y());
98 transformNode->setMatrix(m);
99
100 rectNode->setRect(QRectF(QPointF(), QSizeF(circleRadius * 2, circleRadius * 2)));
101 rectNode->setRadius(circleRadius);
102
103 transformNode = static_cast<QSGTransformNode *>(transformNode->nextSibling());
104 }
105}
106
107QQuickBasicBusyIndicator::QQuickBasicBusyIndicator(QQuickItem *parent) :
108 QQuickItem(parent)
109{
110 setFlag(flag: ItemHasContents);
111}
112
113QColor QQuickBasicBusyIndicator::pen() const
114{
115 return m_pen;
116}
117
118void QQuickBasicBusyIndicator::setPen(const QColor &pen)
119{
120 if (pen == m_pen)
121 return;
122
123 m_pen = pen;
124 update();
125}
126
127QColor QQuickBasicBusyIndicator::fill() const
128{
129 return m_fill;
130}
131
132void QQuickBasicBusyIndicator::setFill(const QColor &fill)
133{
134 if (fill == m_fill)
135 return;
136
137 m_fill = fill;
138 update();
139}
140
141bool QQuickBasicBusyIndicator::isRunning() const
142{
143 return isVisible();
144}
145
146void QQuickBasicBusyIndicator::setRunning(bool running)
147{
148 if (running)
149 setVisible(true);
150}
151
152int QQuickBasicBusyIndicator::elapsed() const
153{
154 return m_elapsed;
155}
156
157void QQuickBasicBusyIndicator::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
158{
159 QQuickItem::itemChange(change, data);
160 switch (change) {
161 case ItemOpacityHasChanged:
162 if (qFuzzyIsNull(d: data.realValue))
163 setVisible(false);
164 break;
165 case ItemVisibleHasChanged:
166 update();
167 break;
168 default:
169 break;
170 }
171}
172
173QSGNode *QQuickBasicBusyIndicator::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
174{
175 QQuickBasicBusyIndicatorNode *node = static_cast<QQuickBasicBusyIndicatorNode *>(oldNode);
176 if (isRunning() && width() > 0 && height() > 0) {
177 if (!node) {
178 node = new QQuickBasicBusyIndicatorNode(this);
179 node->start();
180 }
181 node->sync(item: this);
182 } else {
183 m_elapsed = node ? node->currentTime() : 0;
184 delete node;
185 node = nullptr;
186 }
187 return node;
188}
189
190QT_END_NAMESPACE
191
192#include "moc_qquickbasicbusyindicator_p.cpp"
193

source code of qtdeclarative/src/quickcontrols/basic/impl/qquickbasicbusyindicator.cpp