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 | |
42 | class tst_qquickanimatedsprite : public QQmlDataTest |
43 | { |
44 | Q_OBJECT |
45 | public: |
46 | tst_qquickanimatedsprite(){} |
47 | |
48 | private 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 | |
63 | void tst_qquickanimatedsprite::initTestCase() |
64 | { |
65 | QQmlDataTest::initTestCase(); |
66 | QUnifiedTimer::instance()->setConsistentTiming(true); |
67 | } |
68 | |
69 | void 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 | |
97 | void 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 | |
123 | void 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 | |
159 | template <typename T> |
160 | static bool isWithinRange(T min, T value, T max) |
161 | { |
162 | Q_ASSERT(min < max); |
163 | return min <= value && value <= max; |
164 | } |
165 | |
166 | void 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 | |
207 | void 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 | |
216 | class AnimationImageProvider : public QQuickImageProvider |
217 | { |
218 | public: |
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 | |
259 | void 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 | |
315 | void 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 | |
336 | class KillerThread : public QThread |
337 | { |
338 | Q_OBJECT |
339 | protected: |
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 |
347 | void 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 | |
372 | void 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 | |
410 | void 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 | |
432 | void 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 | |
457 | QTEST_MAIN(tst_qquickanimatedsprite) |
458 | |
459 | #include "tst_qquickanimatedsprite.moc" |
460 | |