From a757e8ca0a6f7bb628ac7a57cafa9f0ba5167d51 Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Mon, 21 Dec 2015 11:50:06 +0100 Subject: Implement default ProgressBar according to designs Change-Id: I7f973deec7453c4b80c03b3dc063f4d0dbe850c5 Reviewed-by: J-P Nurmi --- src/imports/controls/ProgressBar.qml | 36 +-- src/imports/controls/controls.pro | 6 +- src/imports/controls/qquickprogressstrip.cpp | 313 ++++++++++++++++++++++++++ src/imports/controls/qquickprogressstrip_p.h | 96 ++++++++ src/imports/controls/qtlabscontrolsplugin.cpp | 3 + tests/auto/controls/data/tst_progressbar.qml | 15 ++ 6 files changed, 439 insertions(+), 30 deletions(-) create mode 100644 src/imports/controls/qquickprogressstrip.cpp create mode 100644 src/imports/controls/qquickprogressstrip_p.h diff --git a/src/imports/controls/ProgressBar.qml b/src/imports/controls/ProgressBar.qml index 732ae11d..112f3afb 100644 --- a/src/imports/controls/ProgressBar.qml +++ b/src/imports/controls/ProgressBar.qml @@ -36,6 +36,7 @@ import QtQuick 2.6 import Qt.labs.templates 1.0 as T +import Qt.labs.controls.impl 1.0 T.ProgressBar { id: control @@ -46,40 +47,19 @@ T.ProgressBar { indicator ? indicator.implicitHeight : 0) + topPadding + bottomPadding //! [indicator] - indicator: Item { + indicator: ProgressStrip { + id: strip x: control.leftPadding y: control.topPadding width: control.availableWidth height: control.availableHeight - scale: control.mirrored ? -1 : 1 + progress: control.position + indeterminate: control.indeterminate - Repeater { - model: indeterminate ? 2 : 1 - - Rectangle { - property real offset: indeterminate ? 0 : control.position - - x: indeterminate ? offset * parent.width : 0 - y: (parent.height - height) / 2 - width: offset * (parent.width - x) - height: 6 - - color: control.enabled ? "#353637" : "#bdbebf" - - SequentialAnimation on offset { - loops: Animation.Infinite - running: indeterminate && visible - PauseAnimation { duration: index ? 520 : 0 } - NumberAnimation { - easing.type: Easing.OutCubic - duration: 1240 - from: 0 - to: 1 - } - PauseAnimation { duration: index ? 0 : 520 } - } - } + ProgressStripAnimator { + target: strip + running: control.visible && control.indeterminate } } //! [indicator] diff --git a/src/imports/controls/controls.pro b/src/imports/controls/controls.pro index dc24d8b1..30f8e223 100644 --- a/src/imports/controls/controls.pro +++ b/src/imports/controls/controls.pro @@ -13,11 +13,13 @@ OTHER_FILES += \ qmldir HEADERS += \ - $$PWD/qquickbusyindicatorring_p.h + $$PWD/qquickbusyindicatorring_p.h \ + $$PWD/qquickprogressstrip_p.h SOURCES += \ $$PWD/qtlabscontrolsplugin.cpp \ - $$PWD/qquickbusyindicatorring.cpp + $$PWD/qquickbusyindicatorring.cpp \ + $$PWD/qquickprogressstrip.cpp RESOURCES += \ $$PWD/qtlabscontrolsplugin.qrc diff --git a/src/imports/controls/qquickprogressstrip.cpp b/src/imports/controls/qquickprogressstrip.cpp new file mode 100644 index 00000000..046fd77c --- /dev/null +++ b/src/imports/controls/qquickprogressstrip.cpp @@ -0,0 +1,313 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 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 "qquickprogressstrip_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QQuickProgressAnimatorJob : public QQuickAnimatorJob +{ +public: + QQuickProgressAnimatorJob(); + ~QQuickProgressAnimatorJob(); + + void initialize(QQuickAnimatorController *controller) Q_DECL_OVERRIDE; + void afterNodeSync() Q_DECL_OVERRIDE; + void updateCurrentTime(int time) Q_DECL_OVERRIDE; + void writeBack() Q_DECL_OVERRIDE; + void nodeWasDestroyed() Q_DECL_OVERRIDE; + +private: + QSGNode *m_node; +}; + +QQuickProgressStrip::QQuickProgressStrip(QQuickItem *parent) : + QQuickItem(parent), + m_progress(0), + m_indeterminate(false) +{ + setFlag(QQuickItem::ItemHasContents); + setImplicitWidth(116); + setImplicitHeight(6); +} + +QQuickProgressStrip::~QQuickProgressStrip() +{ +} + +qreal QQuickProgressStrip::progress() const +{ + return m_progress; +} + +void QQuickProgressStrip::setProgress(qreal progress) +{ + if (progress == m_progress) + return; + + m_progress = progress; + update(); + emit progressChanged(); +} + +bool QQuickProgressStrip::isIndeterminate() const +{ + return m_indeterminate; +} + +void QQuickProgressStrip::setIndeterminate(bool indeterminate) +{ + if (indeterminate == m_indeterminate) + return; + + m_indeterminate = indeterminate; + setClip(m_indeterminate); + update(); + emit indeterminateChanged(); +} + +static const int blocks = 4; +static const int blockWidth = 16; +static const int blockRestingSpacing = 4; +static const int blockMovingSpacing = 48; +static const int blockSpan = blocks * (blockWidth + blockRestingSpacing) - blockRestingSpacing; +static const int animationDuration = 4000; +static const int secondPhaseStart = animationDuration * 0.4; +static const int thirdPhaseStart = animationDuration * 0.6; + +static inline qreal blockStartX(int blockIndex) +{ + return ((blockIndex + 1) * -blockWidth) - (blockIndex * blockMovingSpacing); +} + +static inline qreal blockRestX(int blockIndex, qreal availableWidth) +{ + const qreal spanRightEdgePos = availableWidth / 2 + blockSpan / 2; + return spanRightEdgePos - (blockIndex + 1) * blockWidth - (blockIndex * blockRestingSpacing); +} + +static inline qreal blockEndX(int blockIndex, qreal availableWidth) +{ + return availableWidth - blockStartX(blocks - 1 - blockIndex) - blockWidth; +} + +QSGNode *QQuickProgressStrip::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) +{ + QQuickItemPrivate *d = QQuickItemPrivate::get(this); + + if (!oldNode) + oldNode = new QSGSimpleRectNode(boundingRect(), Qt::transparent); + static_cast(oldNode)->setRect(boundingRect()); + + QSGTransformNode *rootTransformNode = static_cast(oldNode->firstChild()); + if (!rootTransformNode) { + rootTransformNode = new QSGTransformNode; + oldNode->appendChildNode(rootTransformNode); + } + Q_ASSERT(rootTransformNode->type() == QSGNode::TransformNodeType); + + if (m_indeterminate) { + if (rootTransformNode->childCount() != blocks) { + // This was previously a regular progress bar; remove the old nodes. + rootTransformNode->removeAllChildNodes(); + } + + QSGTransformNode *transformNode = static_cast(rootTransformNode->firstChild()); + for (int i = 0; i < blocks; ++i) { + if (!transformNode) { + transformNode = new QSGTransformNode; + rootTransformNode->appendChildNode(transformNode); + } + + QSGRectangleNode *rectNode = static_cast(transformNode->firstChild()); + if (!rectNode) { + rectNode = d->sceneGraphContext()->createRectangleNode(); + rectNode->setColor(QColor(0x35, 0x36, 0x37)); + transformNode->appendChildNode(rectNode); + } + + QMatrix4x4 m; + m.translate(blockStartX(i), 0); + transformNode->setMatrix(m); + + rectNode->setRect(QRectF(QPointF(), QSizeF(blockWidth, height()))); + rectNode->update(); + + transformNode = static_cast(transformNode->nextSibling()); + } + } else { + if (rootTransformNode->childCount() > 1) { + // This was previously an indeterminate progress bar; remove the old nodes. + rootTransformNode->removeAllChildNodes(); + } + + QSGRectangleNode *rectNode = static_cast(rootTransformNode->firstChild()); + if (!rectNode) { + rectNode = d->sceneGraphContext()->createRectangleNode(); + rectNode->setColor(QColor(0x35, 0x36, 0x37)); + rootTransformNode->appendChildNode(rectNode); + } + + rectNode->setRect(QRectF(QPointF(), QSizeF(m_progress * width(), height()))); + rectNode->update(); + } + + return oldNode; +} + +QQuickProgressAnimator::QQuickProgressAnimator(QObject *parent) : + QQuickAnimator(parent) +{ + setDuration(animationDuration); + setLoops(QQuickAnimator::Infinite); +} + +QString QQuickProgressAnimator::propertyName() const +{ + return QString(); +} + +QQuickAnimatorJob *QQuickProgressAnimator::createJob() const +{ + return new QQuickProgressAnimatorJob; +} + +QQuickProgressAnimatorJob::QQuickProgressAnimatorJob() : + m_node(Q_NULLPTR) +{ +} + +QQuickProgressAnimatorJob::~QQuickProgressAnimatorJob() +{ +} + +void QQuickProgressAnimatorJob::initialize(QQuickAnimatorController *controller) +{ + QQuickAnimatorJob::initialize(controller); + m_node = QQuickItemPrivate::get(m_target)->childContainerNode(); +} + +void QQuickProgressAnimatorJob::afterNodeSync() +{ + m_node = QQuickItemPrivate::get(m_target)->childContainerNode(); +} + +void QQuickProgressAnimatorJob::updateCurrentTime(int time) +{ + if (!m_node) + return; + + QSGSimpleRectNode *rootRectNode = static_cast(m_node->firstChild()); + if (!rootRectNode) + return; + Q_ASSERT(rootRectNode->type() == QSGNode::GeometryNodeType); + + QSGTransformNode *rootTransformNode = static_cast(rootRectNode->firstChild()); + Q_ASSERT(rootTransformNode->type() == QSGNode::TransformNodeType); + + QSGTransformNode *transformNode = static_cast(rootTransformNode->firstChild()); + // This function can be called without the relevant nodes having been created yet, + // which can happen if you set indeterminate to true at runtime. + if (!transformNode || transformNode->type() != QSGNode::TransformNodeType) + return; + + const qreal pixelsPerSecond = rootRectNode->rect().width(); + + for (int i = 0; i < blocks; ++i) { + QSGRectangleNode *rectNode = static_cast(transformNode->firstChild()); + Q_ASSERT(rectNode->type() == QSGNode::GeometryNodeType); + + QMatrix4x4 m; + const qreal restX = blockRestX(i, rootRectNode->rect().width()); + const qreal timeInSeconds = time / 1000.0; + + if (time < secondPhaseStart) { + // Move into the resting position for the first phase. + QEasingCurve easingCurve(QEasingCurve::InQuad); + const qreal easedCompletion = easingCurve.valueForProgress(time / qreal(secondPhaseStart)); + const qreal distance = pixelsPerSecond * (easedCompletion * (secondPhaseStart / 1000.0)); + const qreal position = blockStartX(i) + distance; + const qreal destination = restX; + m.translate(qMin(position, destination), 0); + } else if (time < thirdPhaseStart) { + // Stay in the same position for the second phase. + m.translate(restX, 0); + } else { + // Move out of view for the third phase. + const int thirdPhaseSubKickoff = (blockMovingSpacing / pixelsPerSecond) * 1000; + const int subphase = (time - thirdPhaseStart) / thirdPhaseSubKickoff; + // If we're not at this subphase yet, don't try to animate movement, + // because it will be incorrect. + if (subphase < i) + return; + + const qreal timeSinceSecondPhase = timeInSeconds - (thirdPhaseStart / 1000.0); + // We only want to start keeping track of time once our subphase has started, + // otherwise we move too much because we account for time that has already elapsed. + // For example, if we were 60 milliseconds into the third subphase: + // + // 0 ..... 1 ..... 2 ... + // 100 100 60 + // + // i == 0, timeSinceOurKickoff == 260 + // i == 1, timeSinceOurKickoff == 160 + // i == 2, timeSinceOurKickoff == 60 + const qreal timeSinceOurKickoff = timeSinceSecondPhase - (thirdPhaseSubKickoff / 1000.0 * i); + const qreal position = restX + (pixelsPerSecond * (timeSinceOurKickoff)); + const qreal destination = blockEndX(i, rootRectNode->rect().width()); + m.translate(qMin(position, destination), 0); + } + + transformNode->setMatrix(m); + rectNode->update(); + + transformNode = static_cast(transformNode->nextSibling()); + } +} + +void QQuickProgressAnimatorJob::writeBack() +{ +} + +void QQuickProgressAnimatorJob::nodeWasDestroyed() +{ + m_node = Q_NULLPTR; +} + +QT_END_NAMESPACE diff --git a/src/imports/controls/qquickprogressstrip_p.h b/src/imports/controls/qquickprogressstrip_p.h new file mode 100644 index 00000000..c34698dc --- /dev/null +++ b/src/imports/controls/qquickprogressstrip_p.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 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$ +** +****************************************************************************/ + +#ifndef QQUICKPROGRESSSTRIP_P_H +#define QQUICKPROGRESSSTRIP_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QQuickProgressStrip : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(bool indeterminate READ isIndeterminate WRITE setIndeterminate NOTIFY indeterminateChanged FINAL) + Q_PROPERTY(qreal progress READ progress WRITE setProgress NOTIFY progressChanged FINAL) + +public: + explicit QQuickProgressStrip(QQuickItem *parent = Q_NULLPTR); + ~QQuickProgressStrip(); + + bool isIndeterminate() const; + void setIndeterminate(bool indeterminate); + + qreal progress() const; + void setProgress(qreal progress); + +Q_SIGNALS: + void progressChanged(); + void indeterminateChanged(); + +protected: + QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) Q_DECL_OVERRIDE; + +private: + qreal m_progress; + bool m_indeterminate; +}; + +class QQuickProgressAnimator : public QQuickAnimator +{ +public: + QQuickProgressAnimator(QObject *parent = Q_NULLPTR); + +protected: + QString propertyName() const Q_DECL_OVERRIDE; + QQuickAnimatorJob *createJob() const Q_DECL_OVERRIDE; +}; + +QT_END_NAMESPACE + +#endif // QQUICKPROGRESSSTRIP_P_H diff --git a/src/imports/controls/qtlabscontrolsplugin.cpp b/src/imports/controls/qtlabscontrolsplugin.cpp index c822d9be..b56df31a 100644 --- a/src/imports/controls/qtlabscontrolsplugin.cpp +++ b/src/imports/controls/qtlabscontrolsplugin.cpp @@ -46,6 +46,7 @@ #include #include "qquickbusyindicatorring_p.h" +#include "qquickprogressstrip_p.h" static inline void initResources() { @@ -119,6 +120,8 @@ void QtLabsControlsPlugin::initializeEngine(QQmlEngine *engine, const char *uri) const QByteArray import = QByteArray(uri) + ".impl"; qmlRegisterType(import, 1, 0, "BusyRing"); qmlRegisterType(import, 1, 0, "BusyRingAnimator"); + qmlRegisterType(import, 1, 0, "ProgressStrip"); + qmlRegisterType(import, 1, 0, "ProgressStripAnimator"); } QT_END_NAMESPACE diff --git a/tests/auto/controls/data/tst_progressbar.qml b/tests/auto/controls/data/tst_progressbar.qml index d5ee3e52..086d9ad4 100644 --- a/tests/auto/controls/data/tst_progressbar.qml +++ b/tests/auto/controls/data/tst_progressbar.qml @@ -181,4 +181,19 @@ TestCase { control.destroy() } + + function test_indeterminate() { + skip("skipping until https://codereview.qt-project.org/#/c/145140/ is merged") + var control = progressBar.createObject(testCase) + verify(control) + compare(control.indeterminate, false) + + wait(100) + control.indeterminate = true + wait(100) + // Shouldn't crash... + control.indeterminate = false + + control.destroy() + } } -- cgit v1.2.3