From 04afbe9e3b7aae98a48337ffd0b4d99858533309 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 18 Oct 2016 13:11:03 +0200 Subject: Material: rewrite the busy indicator animation Use a simple animated node instead of using the private animator API. Task-number: QTBUG-56601 Change-Id: I9dc474f75b5c5a6fcd8d11735970c1a354ed5b56 Reviewed-by: Mitch Curtis --- .../material/qquickmaterialbusyindicator.cpp | 244 +++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 src/imports/controls/material/qquickmaterialbusyindicator.cpp (limited to 'src/imports/controls/material/qquickmaterialbusyindicator.cpp') diff --git a/src/imports/controls/material/qquickmaterialbusyindicator.cpp b/src/imports/controls/material/qquickmaterialbusyindicator.cpp new file mode 100644 index 00000000..d55b131b --- /dev/null +++ b/src/imports/controls/material/qquickmaterialbusyindicator.cpp @@ -0,0 +1,244 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickmaterialbusyindicator_p.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/* + Relevant Android code: + + - core/res/res/anim/progress_indeterminate_rotation_material.xml contains + the rotation animation data. + - core/res/res/anim/progress_indeterminate_material.xml contains the trim + animation data. + - core/res/res/interpolator/trim_start_interpolator.xml and + core/res/res/interpolator/trim_end_interpolator.xml contain the start + and end trim path interpolators. + - addCommand() in core/java/android/util/PathParser.java has a list of the + different path commands available. +*/ + +static const int SpanAnimationDuration = 700; +static const int RotationAnimationDuration = SpanAnimationDuration * 6; +static const int TargetRotation = 720; +static const int OneDegree = 16; +static const qreal MinSweepSpan = 10 * OneDegree; +static const qreal MaxSweepSpan = 300 * OneDegree; + +class QQuickMaterialBusyIndicatorNode : public QObject, public QSGNode +{ +public: + QQuickMaterialBusyIndicatorNode(QQuickMaterialBusyIndicator *item); + + int elapsed() const; + + void animate(); + void sync(QQuickMaterialBusyIndicator *item); + +private: + int m_offset; + int m_lastStartAngle; + int m_lastEndAngle; + qreal m_width; + qreal m_height; + qreal m_devicePixelRatio; + QSGNode *m_containerNode; + QQuickWindow *m_window; + QElapsedTimer m_timer; + QColor m_color; +}; + +QQuickMaterialBusyIndicatorNode::QQuickMaterialBusyIndicatorNode(QQuickMaterialBusyIndicator *item) : + m_offset(item->elapsed()), + m_lastStartAngle(0), + m_lastEndAngle(0), + m_width(0), + m_height(0), + m_devicePixelRatio(1), + m_window(item->window()) +{ + connect(m_window, &QQuickWindow::frameSwapped, m_window, &QQuickWindow::update); + connect(m_window, &QQuickWindow::beforeRendering, this, &QQuickMaterialBusyIndicatorNode::animate); + + QSGImageNode *textureNode = m_window->createImageNode(); + textureNode->setOwnsTexture(true); + appendChildNode(textureNode); + + // A texture seems to be required here, but we don't have one yet, as we haven't drawn anything, + // so just use a blank image. + QImage blankImage(item->width(), item->height(), QImage::Format_ARGB32_Premultiplied); + blankImage.fill(Qt::transparent); + textureNode->setTexture(m_window->createTextureFromImage(blankImage)); + + m_timer.restart(); +} + +int QQuickMaterialBusyIndicatorNode::elapsed() const +{ + return m_timer.elapsed() + m_offset; +} + +void QQuickMaterialBusyIndicatorNode::animate() +{ + qint64 time = m_timer.elapsed() + m_offset; + if (time >= RotationAnimationDuration) { + m_timer.restart(); + m_offset = 0; + } + + const qreal w = m_width; + const qreal h = m_height; + const qreal size = qMin(w, h); + const qreal dx = (w - size) / 2; + const qreal dy = (h - size) / 2; + + QImage image(size * m_devicePixelRatio, size * m_devicePixelRatio, QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::transparent); + + QPainter painter(&image); + painter.setRenderHint(QPainter::Antialiasing); + + QPen pen; + QSGImageNode *textureNode = static_cast(firstChild()); + pen.setColor(m_color); + pen.setWidth(4 * m_devicePixelRatio); + painter.setPen(pen); + + const qreal percentageComplete = time / qreal(RotationAnimationDuration); + const qreal spanPercentageComplete = (time % SpanAnimationDuration) / qreal(SpanAnimationDuration); + const int iteration = time / SpanAnimationDuration; + int startAngle = 0; + int endAngle = 0; + + if (iteration % 2 == 0) { + if (m_lastStartAngle > 360 * OneDegree) + m_lastStartAngle -= 360 * OneDegree; + + // The start angle is only affected by the rotation animation for the "grow" phase. + startAngle = m_lastStartAngle; + QEasingCurve angleCurve(QEasingCurve::OutQuad); + const qreal percentage = angleCurve.valueForProgress(spanPercentageComplete); + endAngle = m_lastStartAngle + MinSweepSpan + percentage * (MaxSweepSpan - MinSweepSpan); + m_lastEndAngle = endAngle; + } else { + // Both the start angle *and* the span are affected by the "shrink" phase. + QEasingCurve angleCurve(QEasingCurve::InQuad); + const qreal percentage = angleCurve.valueForProgress(spanPercentageComplete); + startAngle = m_lastEndAngle - MaxSweepSpan + percentage * (MaxSweepSpan - MinSweepSpan); + endAngle = m_lastEndAngle; + m_lastStartAngle = startAngle; + } + + const int halfPen = pen.width() / 2; + const QRectF arcBounds = QRectF(halfPen, halfPen, + m_devicePixelRatio * size - pen.width(), + m_devicePixelRatio * size - pen.width()); + // The current angle of the rotation animation. + const qreal rotation = OneDegree * percentageComplete * -TargetRotation; + startAngle -= rotation; + endAngle -= rotation; + const int angleSpan = endAngle - startAngle; + painter.drawArc(arcBounds, -startAngle, -angleSpan); + painter.end(); + + textureNode->setRect(QRectF(dx, dy, size, size)); + textureNode->setTexture(m_window->createTextureFromImage(image)); +} + +void QQuickMaterialBusyIndicatorNode::sync(QQuickMaterialBusyIndicator *item) +{ + m_color = item->color(); + m_width = item->width(); + m_height = item->height(); + m_devicePixelRatio = m_window->effectiveDevicePixelRatio(); +} + +QQuickMaterialBusyIndicator::QQuickMaterialBusyIndicator(QQuickItem *parent) : + QQuickItem(parent) +{ + setFlag(ItemHasContents); +} + +QColor QQuickMaterialBusyIndicator::color() const +{ + return m_color; +} + +void QQuickMaterialBusyIndicator::setColor(QColor color) +{ + if (m_color == color) + return; + + m_color = color; + update(); +} + +int QQuickMaterialBusyIndicator::elapsed() const +{ + return m_elapsed; +} + +void QQuickMaterialBusyIndicator::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) +{ + QQuickItem::itemChange(change, data); + if (change == ItemVisibleHasChanged) + update(); +} + +QSGNode *QQuickMaterialBusyIndicator::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + QQuickMaterialBusyIndicatorNode *node = static_cast(oldNode); + if (isVisible() && width() > 0 && height() > 0) { + if (!node) + node = new QQuickMaterialBusyIndicatorNode(this); + node->sync(this); + } else { + m_elapsed = node ? node->elapsed() : 0; + delete node; + node = nullptr; + } + return node; +} + +QT_END_NAMESPACE -- cgit v1.2.3