| 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 |  | 
| 10 | QT_BEGIN_NAMESPACE | 
| 11 |  | 
| 12 | static const int CircleCount = 10; | 
| 13 | static const int QbbiTotalDuration = 100 * CircleCount * 2; | 
| 14 | static const QRgb TransparentColor = 0x00000000; | 
| 15 |  | 
| 16 | static QPointF moveCircle(const QPointF &pos, qreal rotation, qreal distance) | 
| 17 | { | 
| 18 |     return pos - QTransform().rotate(a: rotation).map(p: QPointF(0, distance)); | 
| 19 | } | 
| 20 |  | 
| 21 | class QQuickBasicBusyIndicatorNode : public QQuickAnimatedNode | 
| 22 | { | 
| 23 | public: | 
| 24 |     QQuickBasicBusyIndicatorNode(QQuickBasicBusyIndicator *item); | 
| 25 |  | 
| 26 |     void updateCurrentTime(int time) override; | 
| 27 |     void sync(QQuickItem *item) override; | 
| 28 |  | 
| 29 | private: | 
| 30 |     QColor m_pen; | 
| 31 |     QColor m_fill; | 
| 32 | }; | 
| 33 |  | 
| 34 | QQuickBasicBusyIndicatorNode::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 |  | 
| 52 | void 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 |  | 
| 74 | void 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 |  | 
| 107 | QQuickBasicBusyIndicator::QQuickBasicBusyIndicator(QQuickItem *parent) : | 
| 108 |     QQuickItem(parent) | 
| 109 | { | 
| 110 |     setFlag(flag: ItemHasContents); | 
| 111 | } | 
| 112 |  | 
| 113 | QColor QQuickBasicBusyIndicator::pen() const | 
| 114 | { | 
| 115 |     return m_pen; | 
| 116 | } | 
| 117 |  | 
| 118 | void QQuickBasicBusyIndicator::setPen(const QColor &pen) | 
| 119 | { | 
| 120 |     if (pen == m_pen) | 
| 121 |         return; | 
| 122 |  | 
| 123 |     m_pen = pen; | 
| 124 |     update(); | 
| 125 | } | 
| 126 |  | 
| 127 | QColor QQuickBasicBusyIndicator::fill() const | 
| 128 | { | 
| 129 |     return m_fill; | 
| 130 | } | 
| 131 |  | 
| 132 | void QQuickBasicBusyIndicator::setFill(const QColor &fill) | 
| 133 | { | 
| 134 |     if (fill == m_fill) | 
| 135 |         return; | 
| 136 |  | 
| 137 |     m_fill = fill; | 
| 138 |     update(); | 
| 139 | } | 
| 140 |  | 
| 141 | bool QQuickBasicBusyIndicator::isRunning() const | 
| 142 | { | 
| 143 |     return isVisible(); | 
| 144 | } | 
| 145 |  | 
| 146 | void QQuickBasicBusyIndicator::setRunning(bool running) | 
| 147 | { | 
| 148 |     m_running = running; | 
| 149 |  | 
| 150 |     if (m_running) | 
| 151 |         setVisible(true); | 
| 152 |     // Don't set visible to false if not running, because we use an opacity animation (in QML) to hide ourselves. | 
| 153 | } | 
| 154 |  | 
| 155 | int QQuickBasicBusyIndicator::elapsed() const | 
| 156 | { | 
| 157 |     return m_elapsed; | 
| 158 | } | 
| 159 |  | 
| 160 | void QQuickBasicBusyIndicator::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) | 
| 161 | { | 
| 162 |     QQuickItem::itemChange(change, data); | 
| 163 |     switch (change) { | 
| 164 |     case ItemOpacityHasChanged: | 
| 165 |         // If running is set to false and then true within a short period (QTBUG-85860), our | 
| 166 |         // OpacityAnimator cancels the 1 => 0 animation (which was for running being set to false), | 
| 167 |         // setting opacity to 0 and hence visible to false. This happens _after_ setRunning(true) | 
| 168 |         // was called, because the properties were set synchronously but the animation is | 
| 169 |         // asynchronous. To account for this situation, we only hide ourselves if we're not running. | 
| 170 |         if (qFuzzyIsNull(d: data.realValue) && !m_running) | 
| 171 |             setVisible(false); | 
| 172 |         break; | 
| 173 |     case ItemVisibleHasChanged: | 
| 174 |         update(); | 
| 175 |         break; | 
| 176 |     default: | 
| 177 |         break; | 
| 178 |     } | 
| 179 | } | 
| 180 |  | 
| 181 | QSGNode *QQuickBasicBusyIndicator::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) | 
| 182 | { | 
| 183 |     QQuickBasicBusyIndicatorNode *node = static_cast<QQuickBasicBusyIndicatorNode *>(oldNode); | 
| 184 |     if (isRunning() && width() > 0 && height() > 0) { | 
| 185 |         if (!node) { | 
| 186 |             node = new QQuickBasicBusyIndicatorNode(this); | 
| 187 |             node->start(); | 
| 188 |         } | 
| 189 |         node->sync(item: this); | 
| 190 |     } else { | 
| 191 |         m_elapsed = node ? node->currentTime() : 0; | 
| 192 |         delete node; | 
| 193 |         node = nullptr; | 
| 194 |     } | 
| 195 |     return node; | 
| 196 | } | 
| 197 |  | 
| 198 | QT_END_NAMESPACE | 
| 199 |  | 
| 200 | #include "moc_qquickbasicbusyindicator_p.cpp" | 
| 201 |  |