1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28#include <QtTest/QtTest>
29#include "../../shared/util.h"
30#include <QtQuick/qquickview.h>
31#include <QtQuickTest/QtQuickTest>
32#include <private/qabstractanimation_p.h>
33#include <private/qquickanimatedsprite_p.h>
34#include <private/qquickitem_p.h>
35#include <QtCore/qscopedpointer.h>
36#include <QtGui/qpainter.h>
37#include <QtGui/qopenglcontext.h>
38#include <QtGui/qopenglfunctions.h>
39#include <QtGui/qoffscreensurface.h>
40#include <QtQml/qqmlproperty.h>
41
42class tst_qquickanimatedsprite : public QQmlDataTest
43{
44 Q_OBJECT
45public:
46 tst_qquickanimatedsprite(){}
47
48private slots:
49 void initTestCase();
50 void test_properties();
51 void test_runningChangedSignal();
52 void test_startStop();
53 void test_frameChangedSignal();
54 void test_largeAnimation_data();
55 void test_largeAnimation();
56 void test_reparenting();
57 void test_changeSourceToSmallerImgKeepingBigFrameSize();
58 void test_infiniteLoops();
59 void test_implicitSize();
60 void test_finishBehavior();
61};
62
63void tst_qquickanimatedsprite::initTestCase()
64{
65 QQmlDataTest::initTestCase();
66 QUnifiedTimer::instance()->setConsistentTiming(true);
67}
68
69void tst_qquickanimatedsprite::test_properties()
70{
71 QScopedPointer<QQuickView> window(new QQuickView);
72
73 window->setSource(testFileUrl(fileName: "basic.qml"));
74 window->show();
75 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
76
77 QVERIFY(window->rootObject());
78 QQuickAnimatedSprite* sprite = window->rootObject()->findChild<QQuickAnimatedSprite*>(aName: "sprite");
79 QVERIFY(sprite);
80
81 QTRY_VERIFY(sprite->running());
82 QVERIFY(!sprite->paused());
83 QVERIFY(sprite->interpolate());
84 QCOMPARE(sprite->loops(), 30);
85
86 QSignalSpy finishedSpy(sprite, SIGNAL(finished()));
87 QVERIFY(finishedSpy.isValid());
88
89 sprite->setRunning(false);
90 QVERIFY(!sprite->running());
91 // The finished() signal shouldn't be emitted when running is manually set to false.
92 QCOMPARE(finishedSpy.count(), 0);
93 sprite->setInterpolate(false);
94 QVERIFY(!sprite->interpolate());
95}
96
97void tst_qquickanimatedsprite::test_runningChangedSignal()
98{
99 QScopedPointer<QQuickView> window(new QQuickView);
100
101 window->setSource(testFileUrl(fileName: "runningChange.qml"));
102 window->show();
103 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
104
105 QVERIFY(window->rootObject());
106 QQuickAnimatedSprite* sprite = window->rootObject()->findChild<QQuickAnimatedSprite*>(aName: "sprite");
107 QVERIFY(sprite);
108
109 QVERIFY(!sprite->running());
110
111 QSignalSpy runningChangedSpy(sprite, SIGNAL(runningChanged(bool)));
112 QSignalSpy finishedSpy(sprite, SIGNAL(finished()));
113 QVERIFY(finishedSpy.isValid());
114
115 sprite->setRunning(true);
116 QTRY_COMPARE(runningChangedSpy.count(), 1);
117 QCOMPARE(finishedSpy.count(), 0);
118 QTRY_VERIFY(!sprite->running());
119 QTRY_COMPARE(runningChangedSpy.count(), 2);
120 QCOMPARE(finishedSpy.count(), 1);
121}
122
123void tst_qquickanimatedsprite::test_startStop()
124{
125 QScopedPointer<QQuickView> window(new QQuickView);
126
127 window->setSource(testFileUrl(fileName: "runningChange.qml"));
128 window->show();
129 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
130
131 QVERIFY(window->rootObject());
132 QQuickAnimatedSprite* sprite = window->rootObject()->findChild<QQuickAnimatedSprite*>(aName: "sprite");
133 QVERIFY(sprite);
134
135 QVERIFY(!sprite->running());
136
137 QSignalSpy runningChangedSpy(sprite, SIGNAL(runningChanged(bool)));
138 QSignalSpy finishedSpy(sprite, SIGNAL(finished()));
139 QVERIFY(finishedSpy.isValid());
140
141 sprite->start();
142 QVERIFY(sprite->running());
143 QTRY_COMPARE(runningChangedSpy.count(), 1);
144 QCOMPARE(finishedSpy.count(), 0);
145 sprite->stop();
146 QVERIFY(!sprite->running());
147 QTRY_COMPARE(runningChangedSpy.count(), 2);
148 QCOMPARE(finishedSpy.count(), 0);
149
150 sprite->setCurrentFrame(2);
151 sprite->start();
152 QVERIFY(sprite->running());
153 QCOMPARE(sprite->currentFrame(), 0);
154 QTRY_VERIFY(sprite->currentFrame() > 0);
155 sprite->stop();
156 QVERIFY(!sprite->running());
157}
158
159template <typename T>
160static bool isWithinRange(T min, T value, T max)
161{
162 Q_ASSERT(min < max);
163 return min <= value && value <= max;
164}
165
166void tst_qquickanimatedsprite::test_frameChangedSignal()
167{
168 QScopedPointer<QQuickView> window(new QQuickView);
169
170 window->setSource(testFileUrl(fileName: "frameChange.qml"));
171 window->show();
172
173 QVERIFY(window->rootObject());
174 QQuickAnimatedSprite* sprite = window->rootObject()->findChild<QQuickAnimatedSprite*>(aName: "sprite");
175 QSignalSpy frameChangedSpy(sprite, SIGNAL(currentFrameChanged(int)));
176 QVERIFY(sprite);
177 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
178
179 QVERIFY(!sprite->running());
180 QVERIFY(!sprite->paused());
181 QCOMPARE(sprite->loops(), 3);
182 QCOMPARE(sprite->frameCount(), 6);
183 QCOMPARE(frameChangedSpy.count(), 0);
184
185 frameChangedSpy.clear();
186 sprite->setRunning(true);
187 QTRY_VERIFY(!sprite->running());
188 QCOMPARE(frameChangedSpy.count(), 3*6 + 1);
189
190 int prevFrame = -1;
191 int loopCounter = 0;
192 int maxFrame = 0;
193 while (!frameChangedSpy.isEmpty()) {
194 QList<QVariant> args = frameChangedSpy.takeFirst();
195 int frame = args.first().toInt();
196 if (frame < prevFrame) {
197 ++loopCounter;
198 } else {
199 QVERIFY(frame > prevFrame);
200 }
201 maxFrame = qMax(a: frame, b: maxFrame);
202 prevFrame = frame;
203 }
204 QCOMPARE(loopCounter, 3);
205}
206
207void tst_qquickanimatedsprite::test_largeAnimation_data()
208{
209 QTest::addColumn<bool>(name: "frameSync");
210
211 QTest::newRow(dataTag: "frameSync") << true;
212 QTest::newRow(dataTag: "no_frameSync") << false;
213
214}
215
216class AnimationImageProvider : public QQuickImageProvider
217{
218public:
219 AnimationImageProvider()
220 : QQuickImageProvider(QQuickImageProvider::Pixmap)
221 {
222 }
223
224 QPixmap requestPixmap(const QString &/*id*/, QSize *size, const QSize &requestedSize)
225 {
226 if (requestedSize.isValid())
227 qWarning() << "requestPixmap called with requestedSize of" << requestedSize;
228 // 40 frames.
229 const int nFrames = 40; // 40 is good for texture max width of 4096, 64 is good for 16384
230
231 const int frameWidth = 512;
232 const int frameHeight = 64;
233
234 const QSize pixSize(frameWidth, nFrames * frameHeight);
235 QPixmap pixmap(pixSize);
236 pixmap.fill();
237
238 for (int i = 0; i < nFrames; ++i) {
239 QImage frame(frameWidth, frameHeight, QImage::Format_ARGB32_Premultiplied);
240 frame.fill(color: Qt::white);
241 QPainter p1(&frame);
242 p1.setRenderHint(hint: QPainter::Antialiasing, on: true);
243 QRect r(0,0, frameWidth, frameHeight);
244 p1.setBrush(QBrush(Qt::red, Qt::SolidPattern));
245 p1.drawPie(rect: r, a: i*360*16/nFrames, alen: 90*16);
246 p1.drawText(r, text: QString::number(i));
247 p1.end();
248
249 QPainter p2(&pixmap);
250 p2.drawImage(x: 0, y: i * frameHeight, image: frame);
251 }
252
253 if (size)
254 *size = pixSize;
255 return pixmap;
256 }
257};
258
259void tst_qquickanimatedsprite::test_largeAnimation()
260{
261 QFETCH(bool, frameSync);
262
263 QScopedPointer<QQuickView> window(new QQuickView);
264 window->engine()->addImageProvider(id: QLatin1String("test"), new AnimationImageProvider);
265 window->setSource(testFileUrl(fileName: "largeAnimation.qml"));
266 window->show();
267 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
268
269 QVERIFY(window->rootObject());
270 QQuickAnimatedSprite* sprite = window->rootObject()->findChild<QQuickAnimatedSprite*>(aName: "sprite");
271
272 QVERIFY(sprite);
273
274 QVERIFY(!sprite->running());
275 QVERIFY(!sprite->paused());
276 QCOMPARE(sprite->loops(), 3);
277 QCOMPARE(sprite->frameCount(), 40);
278 sprite->setProperty(name: "frameSync", value: frameSync);
279 if (!frameSync)
280 sprite->setProperty(name: "frameDuration", value: 30);
281
282 QSignalSpy frameChangedSpy(sprite, SIGNAL(currentFrameChanged(int)));
283 sprite->setRunning(true);
284 QTRY_VERIFY_WITH_TIMEOUT(!sprite->running(), 100000 /* make sure we wait until its done*/ );
285 if (frameSync)
286 QVERIFY(isWithinRange(3*40, frameChangedSpy.count(), 3*40 + 1));
287 int prevFrame = -1;
288 int loopCounter = 0;
289 int maxFrame = 0;
290 while (!frameChangedSpy.isEmpty()) {
291 QList<QVariant> args = frameChangedSpy.takeFirst();
292 int frame = args.first().toInt();
293 if (frame < prevFrame) {
294 ++loopCounter;
295 } else {
296 QVERIFY(frame > prevFrame);
297 }
298 maxFrame = qMax(a: frame, b: maxFrame);
299 prevFrame = frame;
300 }
301 int maxTextureSize;
302 QOpenGLContext ctx;
303 ctx.create();
304 QOffscreenSurface offscreenSurface;
305 offscreenSurface.setFormat(ctx.format());
306 offscreenSurface.create();
307 QVERIFY(ctx.makeCurrent(&offscreenSurface));
308 ctx.functions()->glGetIntegerv(GL_MAX_TEXTURE_SIZE, params: &maxTextureSize);
309 ctx.doneCurrent();
310 maxTextureSize /= 512;
311 QVERIFY(maxFrame > maxTextureSize); // make sure we go beyond the texture width limitation
312 QCOMPARE(loopCounter, sprite->loops());
313}
314
315void tst_qquickanimatedsprite::test_reparenting()
316{
317 QQuickView window;
318 window.setSource(testFileUrl(fileName: "basic.qml"));
319 window.show();
320 QVERIFY(QTest::qWaitForWindowExposed(&window));
321
322 QVERIFY(window.rootObject());
323 QQuickAnimatedSprite* sprite = window.rootObject()->findChild<QQuickAnimatedSprite*>(aName: "sprite");
324 QVERIFY(sprite);
325
326 QTRY_VERIFY(sprite->running());
327 sprite->setParentItem(nullptr);
328
329 sprite->setParentItem(window.rootObject());
330 // don't crash (QTBUG-51162)
331 sprite->polish();
332 QVERIFY(QQuickTest::qIsPolishScheduled(sprite));
333 QVERIFY(QQuickTest::qWaitForItemPolished(sprite));
334}
335
336class KillerThread : public QThread
337{
338 Q_OBJECT
339protected:
340 void run() override {
341 sleep(3);
342 qFatal(msg: "Either the GUI or the render thread is stuck in an infinite loop.");
343 }
344};
345
346// Regression test for QTBUG-53937
347void tst_qquickanimatedsprite::test_changeSourceToSmallerImgKeepingBigFrameSize()
348{
349 QQuickView window;
350 window.setSource(testFileUrl(fileName: "sourceSwitch.qml"));
351 window.show();
352 QVERIFY(QTest::qWaitForWindowExposed(&window));
353
354 QVERIFY(window.rootObject());
355 QQuickAnimatedSprite* sprite = qobject_cast<QQuickAnimatedSprite*>(object: window.rootObject());
356 QVERIFY(sprite);
357
358 QQmlProperty big(sprite, "big");
359 big.write(QVariant::fromValue(value: false));
360
361 QScopedPointer<KillerThread> killer(new KillerThread);
362 killer->start(); // will kill us in case the GUI or render thread enters an infinite loop
363
364 QTest::qWait(ms: 50); // let it draw with the new source.
365
366 // If we reach this point it's because we didn't hit QTBUG-53937
367
368 killer->terminate();
369 killer->wait();
370}
371
372void tst_qquickanimatedsprite::test_implicitSize()
373{
374 QQuickView window;
375 window.setSource(testFileUrl(fileName: "basic.qml"));
376 window.show();
377 QVERIFY(QTest::qWaitForWindowExposed(&window));
378 QVERIFY(window.rootObject());
379
380 QQuickAnimatedSprite* sprite = window.rootObject()->findChild<QQuickAnimatedSprite*>(aName: "sprite");
381 QVERIFY(sprite);
382 QCOMPARE(sprite->frameWidth(), 31);
383 QCOMPARE(sprite->frameHeight(), 30);
384 QCOMPARE(sprite->implicitWidth(), 31);
385 QCOMPARE(sprite->implicitHeight(), 30);
386
387 // Ensure that implicitWidth matches frameWidth.
388 QSignalSpy frameWidthChangedSpy(sprite, SIGNAL(frameWidthChanged(int)));
389 QVERIFY(frameWidthChangedSpy.isValid());
390
391 QSignalSpy frameImplicitWidthChangedSpy(sprite, SIGNAL(implicitWidthChanged()));
392 QVERIFY(frameImplicitWidthChangedSpy.isValid());
393
394 sprite->setFrameWidth(20);
395 QCOMPARE(frameWidthChangedSpy.count(), 1);
396 QCOMPARE(frameImplicitWidthChangedSpy.count(), 1);
397
398 // Ensure that implicitHeight matches frameHeight.
399 QSignalSpy frameHeightChangedSpy(sprite, SIGNAL(frameHeightChanged(int)));
400 QVERIFY(frameHeightChangedSpy.isValid());
401
402 QSignalSpy frameImplicitHeightChangedSpy(sprite, SIGNAL(implicitHeightChanged()));
403 QVERIFY(frameImplicitHeightChangedSpy.isValid());
404
405 sprite->setFrameHeight(20);
406 QCOMPARE(frameHeightChangedSpy.count(), 1);
407 QCOMPARE(frameImplicitHeightChangedSpy.count(), 1);
408}
409
410void tst_qquickanimatedsprite::test_infiniteLoops()
411{
412 QQuickView window;
413 window.setSource(testFileUrl(fileName: "infiniteLoops.qml"));
414 window.show();
415 QVERIFY(QTest::qWaitForWindowExposed(&window));
416 QVERIFY(window.rootObject());
417
418 QQuickAnimatedSprite* sprite = qobject_cast<QQuickAnimatedSprite*>(object: window.rootObject());
419 QVERIFY(sprite);
420
421 QTRY_VERIFY(sprite->running());
422
423 QSignalSpy finishedSpy(sprite, SIGNAL(finished()));
424 QVERIFY(finishedSpy.isValid());
425
426 // The finished() signal shouldn't be emitted for infinite animations.
427 const int previousFrame = sprite->currentFrame();
428 QTRY_VERIFY(sprite->currentFrame() != previousFrame);
429 QCOMPARE(finishedSpy.count(), 0);
430}
431
432void tst_qquickanimatedsprite::test_finishBehavior()
433{
434 QQuickView window;
435 window.setSource(testFileUrl(fileName: "finishBehavior.qml"));
436 window.show();
437 QVERIFY(QTest::qWaitForWindowExposed(&window));
438 QVERIFY(window.rootObject());
439
440 QQuickAnimatedSprite* sprite = window.rootObject()->findChild<QQuickAnimatedSprite*>(aName: "sprite");
441 QVERIFY(sprite);
442
443 QTRY_VERIFY(sprite->running());
444
445 // correctly stops at last frame
446 QSignalSpy finishedSpy(sprite, SIGNAL(finished()));
447 QVERIFY(finishedSpy.wait(2000));
448 QCOMPARE(sprite->running(), false);
449 QCOMPARE(sprite->currentFrame(), 5);
450
451 // correctly starts a second time
452 sprite->start();
453 QTRY_VERIFY(sprite->running());
454 QTRY_COMPARE(sprite->currentFrame(), 5);
455}
456
457QTEST_MAIN(tst_qquickanimatedsprite)
458
459#include "tst_qquickanimatedsprite.moc"
460

source code of qtdeclarative/tests/auto/quick/qquickanimatedsprite/tst_qquickanimatedsprite.cpp