| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the test suite of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** 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 https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| #include <QtTest/QtTest> |
| #include "../../shared/util.h" |
| #include <QtQuick/qquickview.h> |
| #include <QtQuickTest/QtQuickTest> |
| #include <private/qabstractanimation_p.h> |
| #include <private/qquickanimatedsprite_p.h> |
| #include <private/qquickitem_p.h> |
| #include <QtCore/qscopedpointer.h> |
| #include <QtGui/qpainter.h> |
| #include <QtGui/qopenglcontext.h> |
| #include <QtGui/qopenglfunctions.h> |
| #include <QtGui/qoffscreensurface.h> |
| #include <QtQml/qqmlproperty.h> |
| |
| class tst_qquickanimatedsprite : public QQmlDataTest |
| { |
| Q_OBJECT |
| public: |
| tst_qquickanimatedsprite(){} |
| |
| private slots: |
| void initTestCase(); |
| void test_properties(); |
| void test_runningChangedSignal(); |
| void test_startStop(); |
| void test_frameChangedSignal(); |
| void test_largeAnimation_data(); |
| void test_largeAnimation(); |
| void test_reparenting(); |
| void test_changeSourceToSmallerImgKeepingBigFrameSize(); |
| void test_infiniteLoops(); |
| void test_implicitSize(); |
| void test_finishBehavior(); |
| }; |
| |
| void tst_qquickanimatedsprite::initTestCase() |
| { |
| QQmlDataTest::initTestCase(); |
| QUnifiedTimer::instance()->setConsistentTiming(true); |
| } |
| |
| void tst_qquickanimatedsprite::test_properties() |
| { |
| QScopedPointer<QQuickView> window(new QQuickView); |
| |
| window->setSource(testFileUrl("basic.qml")); |
| window->show(); |
| QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
| |
| QVERIFY(window->rootObject()); |
| QQuickAnimatedSprite* sprite = window->rootObject()->findChild<QQuickAnimatedSprite*>("sprite"); |
| QVERIFY(sprite); |
| |
| QTRY_VERIFY(sprite->running()); |
| QVERIFY(!sprite->paused()); |
| QVERIFY(sprite->interpolate()); |
| QCOMPARE(sprite->loops(), 30); |
| |
| QSignalSpy finishedSpy(sprite, SIGNAL(finished())); |
| QVERIFY(finishedSpy.isValid()); |
| |
| sprite->setRunning(false); |
| QVERIFY(!sprite->running()); |
| // The finished() signal shouldn't be emitted when running is manually set to false. |
| QCOMPARE(finishedSpy.count(), 0); |
| sprite->setInterpolate(false); |
| QVERIFY(!sprite->interpolate()); |
| } |
| |
| void tst_qquickanimatedsprite::test_runningChangedSignal() |
| { |
| QScopedPointer<QQuickView> window(new QQuickView); |
| |
| window->setSource(testFileUrl("runningChange.qml")); |
| window->show(); |
| QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
| |
| QVERIFY(window->rootObject()); |
| QQuickAnimatedSprite* sprite = window->rootObject()->findChild<QQuickAnimatedSprite*>("sprite"); |
| QVERIFY(sprite); |
| |
| QVERIFY(!sprite->running()); |
| |
| QSignalSpy runningChangedSpy(sprite, SIGNAL(runningChanged(bool))); |
| QSignalSpy finishedSpy(sprite, SIGNAL(finished())); |
| QVERIFY(finishedSpy.isValid()); |
| |
| sprite->setRunning(true); |
| QTRY_COMPARE(runningChangedSpy.count(), 1); |
| QCOMPARE(finishedSpy.count(), 0); |
| QTRY_VERIFY(!sprite->running()); |
| QTRY_COMPARE(runningChangedSpy.count(), 2); |
| QCOMPARE(finishedSpy.count(), 1); |
| } |
| |
| void tst_qquickanimatedsprite::test_startStop() |
| { |
| QScopedPointer<QQuickView> window(new QQuickView); |
| |
| window->setSource(testFileUrl("runningChange.qml")); |
| window->show(); |
| QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
| |
| QVERIFY(window->rootObject()); |
| QQuickAnimatedSprite* sprite = window->rootObject()->findChild<QQuickAnimatedSprite*>("sprite"); |
| QVERIFY(sprite); |
| |
| QVERIFY(!sprite->running()); |
| |
| QSignalSpy runningChangedSpy(sprite, SIGNAL(runningChanged(bool))); |
| QSignalSpy finishedSpy(sprite, SIGNAL(finished())); |
| QVERIFY(finishedSpy.isValid()); |
| |
| sprite->start(); |
| QVERIFY(sprite->running()); |
| QTRY_COMPARE(runningChangedSpy.count(), 1); |
| QCOMPARE(finishedSpy.count(), 0); |
| sprite->stop(); |
| QVERIFY(!sprite->running()); |
| QTRY_COMPARE(runningChangedSpy.count(), 2); |
| QCOMPARE(finishedSpy.count(), 0); |
| |
| sprite->setCurrentFrame(2); |
| sprite->start(); |
| QVERIFY(sprite->running()); |
| QCOMPARE(sprite->currentFrame(), 0); |
| QTRY_VERIFY(sprite->currentFrame() > 0); |
| sprite->stop(); |
| QVERIFY(!sprite->running()); |
| } |
| |
| template <typename T> |
| static bool isWithinRange(T min, T value, T max) |
| { |
| Q_ASSERT(min < max); |
| return min <= value && value <= max; |
| } |
| |
| void tst_qquickanimatedsprite::test_frameChangedSignal() |
| { |
| QScopedPointer<QQuickView> window(new QQuickView); |
| |
| window->setSource(testFileUrl("frameChange.qml")); |
| window->show(); |
| |
| QVERIFY(window->rootObject()); |
| QQuickAnimatedSprite* sprite = window->rootObject()->findChild<QQuickAnimatedSprite*>("sprite"); |
| QSignalSpy frameChangedSpy(sprite, SIGNAL(currentFrameChanged(int))); |
| QVERIFY(sprite); |
| QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
| |
| QVERIFY(!sprite->running()); |
| QVERIFY(!sprite->paused()); |
| QCOMPARE(sprite->loops(), 3); |
| QCOMPARE(sprite->frameCount(), 6); |
| QCOMPARE(frameChangedSpy.count(), 0); |
| |
| frameChangedSpy.clear(); |
| sprite->setRunning(true); |
| QTRY_VERIFY(!sprite->running()); |
| QCOMPARE(frameChangedSpy.count(), 3*6 + 1); |
| |
| int prevFrame = -1; |
| int loopCounter = 0; |
| int maxFrame = 0; |
| while (!frameChangedSpy.isEmpty()) { |
| QList<QVariant> args = frameChangedSpy.takeFirst(); |
| int frame = args.first().toInt(); |
| if (frame < prevFrame) { |
| ++loopCounter; |
| } else { |
| QVERIFY(frame > prevFrame); |
| } |
| maxFrame = qMax(frame, maxFrame); |
| prevFrame = frame; |
| } |
| QCOMPARE(loopCounter, 3); |
| } |
| |
| void tst_qquickanimatedsprite::test_largeAnimation_data() |
| { |
| QTest::addColumn<bool>("frameSync"); |
| |
| QTest::newRow("frameSync") << true; |
| QTest::newRow("no_frameSync") << false; |
| |
| } |
| |
| class AnimationImageProvider : public QQuickImageProvider |
| { |
| public: |
| AnimationImageProvider() |
| : QQuickImageProvider(QQuickImageProvider::Pixmap) |
| { |
| } |
| |
| QPixmap requestPixmap(const QString &/*id*/, QSize *size, const QSize &requestedSize) |
| { |
| if (requestedSize.isValid()) |
| qWarning() << "requestPixmap called with requestedSize of" << requestedSize; |
| // 40 frames. |
| const int nFrames = 40; // 40 is good for texture max width of 4096, 64 is good for 16384 |
| |
| const int frameWidth = 512; |
| const int frameHeight = 64; |
| |
| const QSize pixSize(frameWidth, nFrames * frameHeight); |
| QPixmap pixmap(pixSize); |
| pixmap.fill(); |
| |
| for (int i = 0; i < nFrames; ++i) { |
| QImage frame(frameWidth, frameHeight, QImage::Format_ARGB32_Premultiplied); |
| frame.fill(Qt::white); |
| QPainter p1(&frame); |
| p1.setRenderHint(QPainter::Antialiasing, true); |
| QRect r(0,0, frameWidth, frameHeight); |
| p1.setBrush(QBrush(Qt::red, Qt::SolidPattern)); |
| p1.drawPie(r, i*360*16/nFrames, 90*16); |
| p1.drawText(r, QString::number(i)); |
| p1.end(); |
| |
| QPainter p2(&pixmap); |
| p2.drawImage(0, i * frameHeight, frame); |
| } |
| |
| if (size) |
| *size = pixSize; |
| return pixmap; |
| } |
| }; |
| |
| void tst_qquickanimatedsprite::test_largeAnimation() |
| { |
| QFETCH(bool, frameSync); |
| |
| QScopedPointer<QQuickView> window(new QQuickView); |
| window->engine()->addImageProvider(QLatin1String("test"), new AnimationImageProvider); |
| window->setSource(testFileUrl("largeAnimation.qml")); |
| window->show(); |
| QVERIFY(QTest::qWaitForWindowExposed(window.data())); |
| |
| QVERIFY(window->rootObject()); |
| QQuickAnimatedSprite* sprite = window->rootObject()->findChild<QQuickAnimatedSprite*>("sprite"); |
| |
| QVERIFY(sprite); |
| |
| QVERIFY(!sprite->running()); |
| QVERIFY(!sprite->paused()); |
| QCOMPARE(sprite->loops(), 3); |
| QCOMPARE(sprite->frameCount(), 40); |
| sprite->setProperty("frameSync", frameSync); |
| if (!frameSync) |
| sprite->setProperty("frameDuration", 30); |
| |
| QSignalSpy frameChangedSpy(sprite, SIGNAL(currentFrameChanged(int))); |
| sprite->setRunning(true); |
| QTRY_VERIFY_WITH_TIMEOUT(!sprite->running(), 100000 /* make sure we wait until its done*/ ); |
| if (frameSync) |
| QVERIFY(isWithinRange(3*40, frameChangedSpy.count(), 3*40 + 1)); |
| int prevFrame = -1; |
| int loopCounter = 0; |
| int maxFrame = 0; |
| while (!frameChangedSpy.isEmpty()) { |
| QList<QVariant> args = frameChangedSpy.takeFirst(); |
| int frame = args.first().toInt(); |
| if (frame < prevFrame) { |
| ++loopCounter; |
| } else { |
| QVERIFY(frame > prevFrame); |
| } |
| maxFrame = qMax(frame, maxFrame); |
| prevFrame = frame; |
| } |
| int maxTextureSize; |
| QOpenGLContext ctx; |
| ctx.create(); |
| QOffscreenSurface offscreenSurface; |
| offscreenSurface.setFormat(ctx.format()); |
| offscreenSurface.create(); |
| QVERIFY(ctx.makeCurrent(&offscreenSurface)); |
| ctx.functions()->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); |
| ctx.doneCurrent(); |
| maxTextureSize /= 512; |
| QVERIFY(maxFrame > maxTextureSize); // make sure we go beyond the texture width limitation |
| QCOMPARE(loopCounter, sprite->loops()); |
| } |
| |
| void tst_qquickanimatedsprite::test_reparenting() |
| { |
| QQuickView window; |
| window.setSource(testFileUrl("basic.qml")); |
| window.show(); |
| QVERIFY(QTest::qWaitForWindowExposed(&window)); |
| |
| QVERIFY(window.rootObject()); |
| QQuickAnimatedSprite* sprite = window.rootObject()->findChild<QQuickAnimatedSprite*>("sprite"); |
| QVERIFY(sprite); |
| |
| QTRY_VERIFY(sprite->running()); |
| sprite->setParentItem(nullptr); |
| |
| sprite->setParentItem(window.rootObject()); |
| // don't crash (QTBUG-51162) |
| sprite->polish(); |
| QVERIFY(QQuickTest::qIsPolishScheduled(sprite)); |
| QVERIFY(QQuickTest::qWaitForItemPolished(sprite)); |
| } |
| |
| class KillerThread : public QThread |
| { |
| Q_OBJECT |
| protected: |
| void run() override { |
| sleep(3); |
| qFatal("Either the GUI or the render thread is stuck in an infinite loop."); |
| } |
| }; |
| |
| // Regression test for QTBUG-53937 |
| void tst_qquickanimatedsprite::test_changeSourceToSmallerImgKeepingBigFrameSize() |
| { |
| QQuickView window; |
| window.setSource(testFileUrl("sourceSwitch.qml")); |
| window.show(); |
| QVERIFY(QTest::qWaitForWindowExposed(&window)); |
| |
| QVERIFY(window.rootObject()); |
| QQuickAnimatedSprite* sprite = qobject_cast<QQuickAnimatedSprite*>(window.rootObject()); |
| QVERIFY(sprite); |
| |
| QQmlProperty big(sprite, "big"); |
| big.write(QVariant::fromValue(false)); |
| |
| QScopedPointer<KillerThread> killer(new KillerThread); |
| killer->start(); // will kill us in case the GUI or render thread enters an infinite loop |
| |
| QTest::qWait(50); // let it draw with the new source. |
| |
| // If we reach this point it's because we didn't hit QTBUG-53937 |
| |
| killer->terminate(); |
| killer->wait(); |
| } |
| |
| void tst_qquickanimatedsprite::test_implicitSize() |
| { |
| QQuickView window; |
| window.setSource(testFileUrl("basic.qml")); |
| window.show(); |
| QVERIFY(QTest::qWaitForWindowExposed(&window)); |
| QVERIFY(window.rootObject()); |
| |
| QQuickAnimatedSprite* sprite = window.rootObject()->findChild<QQuickAnimatedSprite*>("sprite"); |
| QVERIFY(sprite); |
| QCOMPARE(sprite->frameWidth(), 31); |
| QCOMPARE(sprite->frameHeight(), 30); |
| QCOMPARE(sprite->implicitWidth(), 31); |
| QCOMPARE(sprite->implicitHeight(), 30); |
| |
| // Ensure that implicitWidth matches frameWidth. |
| QSignalSpy frameWidthChangedSpy(sprite, SIGNAL(frameWidthChanged(int))); |
| QVERIFY(frameWidthChangedSpy.isValid()); |
| |
| QSignalSpy frameImplicitWidthChangedSpy(sprite, SIGNAL(implicitWidthChanged())); |
| QVERIFY(frameImplicitWidthChangedSpy.isValid()); |
| |
| sprite->setFrameWidth(20); |
| QCOMPARE(frameWidthChangedSpy.count(), 1); |
| QCOMPARE(frameImplicitWidthChangedSpy.count(), 1); |
| |
| // Ensure that implicitHeight matches frameHeight. |
| QSignalSpy frameHeightChangedSpy(sprite, SIGNAL(frameHeightChanged(int))); |
| QVERIFY(frameHeightChangedSpy.isValid()); |
| |
| QSignalSpy frameImplicitHeightChangedSpy(sprite, SIGNAL(implicitHeightChanged())); |
| QVERIFY(frameImplicitHeightChangedSpy.isValid()); |
| |
| sprite->setFrameHeight(20); |
| QCOMPARE(frameHeightChangedSpy.count(), 1); |
| QCOMPARE(frameImplicitHeightChangedSpy.count(), 1); |
| } |
| |
| void tst_qquickanimatedsprite::test_infiniteLoops() |
| { |
| QQuickView window; |
| window.setSource(testFileUrl("infiniteLoops.qml")); |
| window.show(); |
| QVERIFY(QTest::qWaitForWindowExposed(&window)); |
| QVERIFY(window.rootObject()); |
| |
| QQuickAnimatedSprite* sprite = qobject_cast<QQuickAnimatedSprite*>(window.rootObject()); |
| QVERIFY(sprite); |
| |
| QTRY_VERIFY(sprite->running()); |
| |
| QSignalSpy finishedSpy(sprite, SIGNAL(finished())); |
| QVERIFY(finishedSpy.isValid()); |
| |
| // The finished() signal shouldn't be emitted for infinite animations. |
| const int previousFrame = sprite->currentFrame(); |
| QTRY_VERIFY(sprite->currentFrame() != previousFrame); |
| QCOMPARE(finishedSpy.count(), 0); |
| } |
| |
| void tst_qquickanimatedsprite::test_finishBehavior() |
| { |
| QQuickView window; |
| window.setSource(testFileUrl("finishBehavior.qml")); |
| window.show(); |
| QVERIFY(QTest::qWaitForWindowExposed(&window)); |
| QVERIFY(window.rootObject()); |
| |
| QQuickAnimatedSprite* sprite = window.rootObject()->findChild<QQuickAnimatedSprite*>("sprite"); |
| QVERIFY(sprite); |
| |
| QTRY_VERIFY(sprite->running()); |
| |
| // correctly stops at last frame |
| QSignalSpy finishedSpy(sprite, SIGNAL(finished())); |
| QVERIFY(finishedSpy.wait(2000)); |
| QCOMPARE(sprite->running(), false); |
| QCOMPARE(sprite->currentFrame(), 5); |
| |
| // correctly starts a second time |
| sprite->start(); |
| QTRY_VERIFY(sprite->running()); |
| QTRY_COMPARE(sprite->currentFrame(), 5); |
| } |
| |
| QTEST_MAIN(tst_qquickanimatedsprite) |
| |
| #include "tst_qquickanimatedsprite.moc" |