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 | |
29 | #include <QtTest/QtTest> |
30 | #include <QtCore/qpropertyanimation.h> |
31 | #include <QtCore/qvariantanimation.h> |
32 | #include <private/qabstractanimation_p.h> |
33 | #include <QtGui/qtouchdevice.h> |
34 | #include <QtWidgets/qwidget.h> |
35 | |
36 | Q_DECLARE_METATYPE(QAbstractAnimation::State) |
37 | |
38 | class UncontrolledAnimation : public QPropertyAnimation |
39 | { |
40 | Q_OBJECT |
41 | public: |
42 | int duration() const { return -1; /* not time driven */ } |
43 | |
44 | protected: |
45 | void updateCurrentTime(int currentTime) |
46 | { |
47 | QPropertyAnimation::updateCurrentTime(currentTime); |
48 | if (currentTime >= QPropertyAnimation::duration() || currentLoop() >= 1) |
49 | stop(); |
50 | } |
51 | }; |
52 | |
53 | class MyObject : public QObject |
54 | { |
55 | Q_OBJECT |
56 | Q_PROPERTY(qreal x READ x WRITE setX) |
57 | public: |
58 | MyObject() : m_x(0) { } |
59 | qreal x() const { return m_x; } |
60 | void setX(qreal x) { m_x = x; } |
61 | private: |
62 | qreal m_x; |
63 | }; |
64 | |
65 | class DummyPropertyAnimation : public QPropertyAnimation |
66 | { |
67 | public: |
68 | DummyPropertyAnimation(QObject *parent = 0) : QPropertyAnimation(parent) |
69 | { |
70 | setTargetObject(&o); |
71 | this->setPropertyName("x" ); |
72 | setEndValue(100); |
73 | } |
74 | |
75 | MyObject o; |
76 | }; |
77 | |
78 | class TestAnimationDriver : public QAnimationDriver |
79 | { |
80 | public: |
81 | TestAnimationDriver() |
82 | : QAnimationDriver() |
83 | , m_elapsed(0) |
84 | { |
85 | QUnifiedTimer::instance()->installAnimationDriver(driver: this); |
86 | } |
87 | |
88 | ~TestAnimationDriver() |
89 | { |
90 | // This is to ensure that running animations are removed from the list of actual running |
91 | // animations. |
92 | QCoreApplication::sendPostedEvents(); |
93 | QUnifiedTimer::instance()->uninstallAnimationDriver(driver: this); |
94 | } |
95 | |
96 | void wait(qint64 ms) |
97 | { |
98 | /* |
99 | * When QAbstractAnimation::start() is called it will end up calling |
100 | * QAnimationTimer::registerAnimation(). This will do |
101 | * |
102 | * QMetaObject::invokeMethod(inst, "startAnimations", Qt::QueuedConnection); // typeof(inst) == QAnimationTimer |
103 | * |
104 | * startAnimations() will again fire a queued connection to actually add the animation |
105 | * to the list of running animations: |
106 | * |
107 | * QMetaObject::invokeMethod(inst, "startTimers", Qt::QueuedConnection); // typeof(inst) == QUnifiedTimer |
108 | * |
109 | * We therefore have to call QCoreApplication::sendPostedEvents() twice here. |
110 | */ |
111 | QCoreApplication::sendPostedEvents(); |
112 | QCoreApplication::sendPostedEvents(); |
113 | |
114 | // Simulates the ideal animation update freqency (approx. 60Hz) |
115 | static const int interval = 1000/60; |
116 | qint64 until = m_elapsed + ms; |
117 | while (m_elapsed < until) { |
118 | advanceAnimation(timeStep: m_elapsed); |
119 | m_elapsed += interval; |
120 | } |
121 | advanceAnimation(timeStep: m_elapsed); |
122 | // This is to make sure that animations that were started with DeleteWhenStopped |
123 | // will actually delete themselves within the test function. |
124 | // Normally, they won't be deleted until the main event loop is processed. |
125 | // Therefore, have to explicitly say that we want to process DeferredDelete events. Same |
126 | // trick is used by QTest::qWait(). |
127 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
128 | } |
129 | |
130 | qint64 elapsed() const override |
131 | { |
132 | return m_elapsed; |
133 | } |
134 | |
135 | void start() override |
136 | { |
137 | d_func()->running = true; |
138 | m_elapsed = 0; |
139 | emit started(); |
140 | } |
141 | |
142 | void stop() override |
143 | { |
144 | d_func()->running = false; |
145 | emit stopped(); |
146 | } |
147 | |
148 | private: |
149 | qint64 m_elapsed; |
150 | Q_DECLARE_PRIVATE(QAnimationDriver) |
151 | }; |
152 | |
153 | class tst_QPropertyAnimation : public QObject |
154 | { |
155 | Q_OBJECT |
156 | public Q_SLOTS: |
157 | void initTestCase(); |
158 | |
159 | private slots: |
160 | void construction(); |
161 | void setCurrentTime_data(); |
162 | void setCurrentTime(); |
163 | void statesAndSignals_data(); |
164 | void statesAndSignals(); |
165 | void deletion1(); |
166 | void deletion2(); |
167 | void deletion3(); |
168 | void duration0(); |
169 | void noStartValue(); |
170 | void noStartValueWithLoop(); |
171 | void startWhenAnotherIsRunning(); |
172 | void easingcurve_data(); |
173 | void easingcurve(); |
174 | void startWithoutStartValue(); |
175 | void startBackwardWithoutEndValue(); |
176 | void playForwardBackward(); |
177 | void interpolated(); |
178 | void setStartEndValues_data(); |
179 | void setStartEndValues(); |
180 | void zeroDurationStart(); |
181 | void zeroDurationForwardBackward(); |
182 | void operationsInStates_data(); |
183 | void operationsInStates(); |
184 | void oneKeyValue(); |
185 | void updateOnSetKeyValues(); |
186 | void restart(); |
187 | void valueChanged(); |
188 | void twoAnimations(); |
189 | void deletedInUpdateCurrentTime(); |
190 | void totalDuration(); |
191 | void zeroLoopCount(); |
192 | void recursiveAnimations(); |
193 | }; |
194 | |
195 | void tst_QPropertyAnimation::initTestCase() |
196 | { |
197 | qRegisterMetaType<QAbstractAnimation::State>(typeName: "QAbstractAnimation::State" ); |
198 | qRegisterMetaType<QAbstractAnimation::DeletionPolicy>(typeName: "QAbstractAnimation::DeletionPolicy" ); |
199 | } |
200 | |
201 | class AnimationObject : public QObject |
202 | { |
203 | Q_OBJECT |
204 | Q_PROPERTY(int value READ value WRITE setValue) |
205 | Q_PROPERTY(qreal realValue READ realValue WRITE setRealValue) |
206 | public: |
207 | AnimationObject(int startValue = 0) |
208 | : v(startValue), rv(startValue) |
209 | { } |
210 | |
211 | int value() const { return v; } |
212 | void setValue(int value) { v = value; } |
213 | |
214 | qreal realValue() const { return rv; } |
215 | void setRealValue(qreal value) { rv = value; } |
216 | |
217 | int v; |
218 | qreal rv; |
219 | }; |
220 | |
221 | |
222 | void tst_QPropertyAnimation::construction() |
223 | { |
224 | QPropertyAnimation panimation; |
225 | } |
226 | |
227 | void tst_QPropertyAnimation::setCurrentTime_data() |
228 | { |
229 | QTest::addColumn<int>(name: "duration" ); |
230 | QTest::addColumn<int>(name: "loopCount" ); |
231 | QTest::addColumn<int>(name: "currentTime" ); |
232 | QTest::addColumn<int>(name: "testCurrentTime" ); |
233 | QTest::addColumn<int>(name: "testCurrentLoop" ); |
234 | |
235 | QTest::newRow(dataTag: "-1" ) << -1 << 1 << 0 << 0 << 0; |
236 | QTest::newRow(dataTag: "0" ) << 0 << 1 << 0 << 0 << 0; |
237 | QTest::newRow(dataTag: "1" ) << 0 << 1 << 1 << 0 << 0; |
238 | QTest::newRow(dataTag: "2" ) << 0 << 2 << 1 << 0 << 0; |
239 | QTest::newRow(dataTag: "3" ) << 1 << 1 << 0 << 0 << 0; |
240 | QTest::newRow(dataTag: "4" ) << 1 << 1 << 1 << 1 << 0; |
241 | QTest::newRow(dataTag: "5" ) << 1 << 2 << 1 << 0 << 1; |
242 | QTest::newRow(dataTag: "6" ) << 1 << 2 << 2 << 1 << 1; |
243 | QTest::newRow(dataTag: "7" ) << 1 << 2 << 3 << 1 << 1; |
244 | QTest::newRow(dataTag: "8" ) << 1 << 3 << 2 << 0 << 2; |
245 | QTest::newRow(dataTag: "9" ) << 1 << 3 << 3 << 1 << 2; |
246 | QTest::newRow(dataTag: "a" ) << 10 << 1 << 0 << 0 << 0; |
247 | QTest::newRow(dataTag: "b" ) << 10 << 1 << 1 << 1 << 0; |
248 | QTest::newRow(dataTag: "c" ) << 10 << 1 << 10 << 10 << 0; |
249 | QTest::newRow(dataTag: "d" ) << 10 << 2 << 10 << 0 << 1; |
250 | QTest::newRow(dataTag: "e" ) << 10 << 2 << 11 << 1 << 1; |
251 | QTest::newRow(dataTag: "f" ) << 10 << 2 << 20 << 10 << 1; |
252 | QTest::newRow(dataTag: "g" ) << 10 << 2 << 21 << 10 << 1; |
253 | QTest::newRow(dataTag: "negloop 0" ) << 10 << -1 << 0 << 0 << 0; |
254 | QTest::newRow(dataTag: "negloop 1" ) << 10 << -1 << 10 << 0 << 1; |
255 | QTest::newRow(dataTag: "negloop 2" ) << 10 << -1 << 15 << 5 << 1; |
256 | QTest::newRow(dataTag: "negloop 3" ) << 10 << -1 << 20 << 0 << 2; |
257 | QTest::newRow(dataTag: "negloop 4" ) << 10 << -1 << 30 << 0 << 3; |
258 | } |
259 | |
260 | void tst_QPropertyAnimation::setCurrentTime() |
261 | { |
262 | QFETCH(int, duration); |
263 | QFETCH(int, loopCount); |
264 | QFETCH(int, currentTime); |
265 | QFETCH(int, testCurrentTime); |
266 | QFETCH(int, testCurrentLoop); |
267 | |
268 | QPropertyAnimation animation; |
269 | if (duration < 0) |
270 | QTest::ignoreMessage(type: QtWarningMsg, message: "QVariantAnimation::setDuration: cannot set a negative duration" ); |
271 | animation.setDuration(duration); |
272 | animation.setLoopCount(loopCount); |
273 | animation.setCurrentTime(currentTime); |
274 | |
275 | QCOMPARE(animation.currentLoopTime(), testCurrentTime); |
276 | QCOMPARE(animation.currentLoop(), testCurrentLoop); |
277 | } |
278 | |
279 | void tst_QPropertyAnimation::statesAndSignals_data() |
280 | { |
281 | QTest::addColumn<bool>(name: "uncontrolled" ); |
282 | QTest::newRow(dataTag: "normal animation" ) << false; |
283 | QTest::newRow(dataTag: "animation with undefined duration" ) << true; |
284 | } |
285 | |
286 | void tst_QPropertyAnimation::statesAndSignals() |
287 | { |
288 | QFETCH(bool, uncontrolled); |
289 | std::unique_ptr<QPropertyAnimation> anim; |
290 | if (uncontrolled) |
291 | anim.reset(p: new UncontrolledAnimation); |
292 | else |
293 | anim.reset(p: new DummyPropertyAnimation); |
294 | anim->setDuration(100); |
295 | |
296 | QSignalSpy finishedSpy(anim.get(), &QPropertyAnimation::finished); |
297 | QSignalSpy runningSpy(anim.get(), &QPropertyAnimation::stateChanged); |
298 | QSignalSpy currentLoopSpy(anim.get(), &QPropertyAnimation::currentLoopChanged); |
299 | |
300 | QVERIFY(finishedSpy.isValid()); |
301 | QVERIFY(runningSpy.isValid()); |
302 | QVERIFY(currentLoopSpy.isValid()); |
303 | |
304 | anim->setCurrentTime(1); |
305 | anim->setCurrentTime(100); |
306 | QCOMPARE(finishedSpy.count(), 0); |
307 | QCOMPARE(runningSpy.count(), 0); |
308 | QCOMPARE(currentLoopSpy.count(), 0); |
309 | QCOMPARE(anim->state(), QAnimationGroup::Stopped); |
310 | |
311 | anim->setLoopCount(3); |
312 | anim->setCurrentTime(101); |
313 | |
314 | if (uncontrolled) |
315 | QSKIP("Uncontrolled animations don't handle looping" ); |
316 | |
317 | QCOMPARE(currentLoopSpy.count(), 1); |
318 | QCOMPARE(anim->currentLoop(), 1); |
319 | |
320 | anim->setCurrentTime(0); |
321 | QCOMPARE(currentLoopSpy.count(), 2); |
322 | QCOMPARE(anim->currentLoop(), 0); |
323 | |
324 | anim->start(); |
325 | QCOMPARE(anim->state(), QAnimationGroup::Running); |
326 | QCOMPARE(runningSpy.count(), 1); //anim must have started |
327 | QCOMPARE(anim->currentLoop(), 0); |
328 | runningSpy.clear(); |
329 | |
330 | anim->stop(); |
331 | QCOMPARE(anim->state(), QAnimationGroup::Stopped); |
332 | QCOMPARE(runningSpy.count(), 1); //anim must have stopped |
333 | QCOMPARE(finishedSpy.count(), 0); |
334 | QCOMPARE(anim->currentLoopTime(), 0); |
335 | QCOMPARE(anim->currentLoop(), 0); |
336 | QCOMPARE(currentLoopSpy.count(), 2); |
337 | runningSpy.clear(); |
338 | |
339 | { |
340 | TestAnimationDriver timeDriver; |
341 | anim->start(); |
342 | timeDriver.wait(ms: 1000); |
343 | QCOMPARE(anim->state(), QAnimationGroup::Stopped); |
344 | QCOMPARE(runningSpy.count(), 2); //started and stopped again |
345 | runningSpy.clear(); |
346 | QCOMPARE(finishedSpy.count(), 1); |
347 | QCOMPARE(anim->currentLoopTime(), 100); |
348 | QCOMPARE(anim->currentLoop(), 2); |
349 | QCOMPARE(currentLoopSpy.count(), 4); |
350 | |
351 | anim->start(); // auto-rewinds |
352 | QCOMPARE(anim->state(), QAnimationGroup::Running); |
353 | QCOMPARE(anim->currentTime(), 0); |
354 | QCOMPARE(anim->currentLoop(), 0); |
355 | QCOMPARE(currentLoopSpy.count(), 5); |
356 | QCOMPARE(runningSpy.count(), 1); // anim has started |
357 | QCOMPARE(finishedSpy.count(), 1); |
358 | QCOMPARE(anim->currentLoop(), 0); |
359 | runningSpy.clear(); |
360 | |
361 | timeDriver.wait(ms: 1000); |
362 | |
363 | QCOMPARE(currentLoopSpy.count(), 7); |
364 | QCOMPARE(anim->state(), QAnimationGroup::Stopped); |
365 | QCOMPARE(anim->currentLoop(), 2); |
366 | QCOMPARE(runningSpy.count(), 1); // anim has stopped |
367 | QCOMPARE(finishedSpy.count(), 2); |
368 | QCOMPARE(anim->currentLoopTime(), 100); |
369 | } |
370 | } |
371 | |
372 | void tst_QPropertyAnimation::deletion1() |
373 | { |
374 | TestAnimationDriver timeDriver; |
375 | QObject *object = new QWidget; |
376 | QPointer<QPropertyAnimation> anim = new QPropertyAnimation(object, "minimumWidth" ); |
377 | |
378 | //test that the animation is deleted correctly depending of the deletion flag passed in start() |
379 | QSignalSpy runningSpy(anim.data(), &QPropertyAnimation::stateChanged); |
380 | QSignalSpy finishedSpy(anim.data(), &QPropertyAnimation::finished); |
381 | QVERIFY(runningSpy.isValid()); |
382 | QVERIFY(finishedSpy.isValid()); |
383 | anim->setStartValue(10); |
384 | anim->setEndValue(20); |
385 | anim->setDuration(200); |
386 | anim->start(); |
387 | QCOMPARE(runningSpy.count(), 1); |
388 | QCOMPARE(finishedSpy.count(), 0); |
389 | |
390 | QVERIFY(anim); |
391 | QCOMPARE(anim->state(), QAnimationGroup::Running); |
392 | timeDriver.wait(ms: 100); |
393 | QVERIFY(anim); |
394 | QCOMPARE(anim->state(), QAnimationGroup::Running); |
395 | timeDriver.wait(ms: 150); |
396 | QVERIFY(anim); //The animation should not have been deleted |
397 | QCOMPARE(anim->state(), QAnimationGroup::Stopped); |
398 | QCOMPARE(runningSpy.count(), 2); |
399 | QCOMPARE(finishedSpy.count(), 1); |
400 | |
401 | anim->start(policy: QVariantAnimation::DeleteWhenStopped); |
402 | QVERIFY(anim); |
403 | QCOMPARE(anim->state(), QAnimationGroup::Running); |
404 | timeDriver.wait(ms: 100); |
405 | QVERIFY(anim); |
406 | QCOMPARE(anim->state(), QAnimationGroup::Running); |
407 | timeDriver.wait(ms: 150); |
408 | QCOMPARE(runningSpy.count(), 4); |
409 | QCOMPARE(finishedSpy.count(), 2); |
410 | QVERIFY(!anim); //The animation must have been deleted |
411 | delete object; |
412 | } |
413 | |
414 | void tst_QPropertyAnimation::deletion2() |
415 | { |
416 | TestAnimationDriver timeDriver; |
417 | // test that the animation does not get deleted if the object is deleted |
418 | QObject *object = new QWidget; |
419 | QPointer<QPropertyAnimation> anim = new QPropertyAnimation(object,"minimumWidth" ); |
420 | QVERIFY(anim->parent() != object); |
421 | anim->setStartValue(10); |
422 | anim->setEndValue(20); |
423 | anim->setDuration(200); |
424 | |
425 | QSignalSpy runningSpy(anim.data(), &QPropertyAnimation::stateChanged); |
426 | QSignalSpy finishedSpy(anim.data(), &QPropertyAnimation::finished); |
427 | |
428 | QVERIFY(runningSpy.isValid()); |
429 | QVERIFY(finishedSpy.isValid()); |
430 | |
431 | anim->setStartValue(10); |
432 | anim->setEndValue(20); |
433 | anim->setDuration(200); |
434 | anim->start(); |
435 | |
436 | timeDriver.wait(ms: 50); |
437 | QVERIFY(anim); |
438 | QCOMPARE(anim->state(), QAnimationGroup::Running); |
439 | |
440 | QCOMPARE(runningSpy.count(), 1); |
441 | QCOMPARE(finishedSpy.count(), 0); |
442 | |
443 | //we can't call deletaLater directly because the delete would only happen in the next loop of _this_ event loop |
444 | QTimer::singleShot(msec: 0, receiver: object, SLOT(deleteLater())); |
445 | timeDriver.wait(ms: 50); |
446 | |
447 | QVERIFY(anim); |
448 | QVERIFY(!anim->targetObject()); |
449 | |
450 | delete anim; |
451 | } |
452 | |
453 | void tst_QPropertyAnimation::deletion3() |
454 | { |
455 | //test that the stopped signal is emit when the animation is destroyed |
456 | TestAnimationDriver timeDriver; |
457 | QWidget w; |
458 | QObject *object = &w; |
459 | QPropertyAnimation *anim = new QPropertyAnimation(object,"minimumWidth" ); |
460 | anim->setStartValue(10); |
461 | anim->setEndValue(20); |
462 | anim->setDuration(200); |
463 | |
464 | QSignalSpy runningSpy(anim, &QPropertyAnimation::stateChanged); |
465 | QSignalSpy finishedSpy(anim, &QPropertyAnimation::finished); |
466 | |
467 | QVERIFY(runningSpy.isValid()); |
468 | QVERIFY(finishedSpy.isValid()); |
469 | |
470 | anim->start(); |
471 | |
472 | timeDriver.wait(ms: 50); |
473 | QCOMPARE(anim->state(), QAnimationGroup::Running); |
474 | QCOMPARE(runningSpy.count(), 1); |
475 | QCOMPARE(finishedSpy.count(), 0); |
476 | delete anim; |
477 | QCOMPARE(runningSpy.count(), 2); |
478 | QCOMPARE(finishedSpy.count(), 0); |
479 | } |
480 | |
481 | void tst_QPropertyAnimation::duration0() |
482 | { |
483 | QObject o; |
484 | o.setProperty(name: "ole" , value: 42); |
485 | QCOMPARE(o.property("ole" ).toInt(), 42); |
486 | |
487 | QPropertyAnimation animation(&o, "ole" ); |
488 | animation.setEndValue(43); |
489 | QVERIFY(!animation.currentValue().isValid()); |
490 | QCOMPARE(animation.currentValue().toInt(), 0); |
491 | animation.setStartValue(42); |
492 | QVERIFY(animation.currentValue().isValid()); |
493 | QCOMPARE(animation.currentValue().toInt(), 42); |
494 | |
495 | QCOMPARE(o.property("ole" ).toInt(), 42); |
496 | animation.setDuration(0); |
497 | QCOMPARE(animation.currentValue().toInt(), 43); //it is at the end |
498 | animation.start(); |
499 | QCOMPARE(animation.state(), QAnimationGroup::Stopped); |
500 | QCOMPARE(animation.currentTime(), 0); |
501 | QCOMPARE(o.property("ole" ).toInt(), 43); |
502 | } |
503 | |
504 | class StartValueTester : public QObject |
505 | { |
506 | Q_OBJECT |
507 | Q_PROPERTY(int ole READ ole WRITE setOle) |
508 | public: |
509 | StartValueTester() : o(0) { } |
510 | int ole() const { return o; } |
511 | void setOle(int v) { o = v; values << v; } |
512 | |
513 | int o; |
514 | QVector<int> values; |
515 | }; |
516 | |
517 | void tst_QPropertyAnimation::noStartValue() |
518 | { |
519 | TestAnimationDriver timeDriver; |
520 | StartValueTester o; |
521 | o.setProperty(name: "ole" , value: 42); |
522 | o.values.clear(); |
523 | |
524 | QPropertyAnimation a(&o, "ole" ); |
525 | a.setEndValue(420); |
526 | a.setDuration(250); |
527 | a.start(); |
528 | |
529 | timeDriver.wait(ms: a.duration()); |
530 | QCOMPARE(o.values.value(o.values.size() - 1, -1), 420); |
531 | QCOMPARE(o.values.first(), 42); |
532 | } |
533 | |
534 | void tst_QPropertyAnimation::noStartValueWithLoop() |
535 | { |
536 | StartValueTester o; |
537 | o.setProperty(name: "ole" , value: 42); |
538 | o.values.clear(); |
539 | |
540 | QPropertyAnimation a(&o, "ole" ); |
541 | a.setEndValue(420); |
542 | a.setDuration(250); |
543 | a.setLoopCount(2); |
544 | a.start(); |
545 | |
546 | a.setCurrentTime(250); |
547 | QCOMPARE(o.values.first(), 42); |
548 | QCOMPARE(a.currentValue().toInt(), 42); |
549 | QCOMPARE(o.values.last(), 42); |
550 | |
551 | a.setCurrentTime(500); |
552 | QCOMPARE(a.currentValue().toInt(), 420); |
553 | } |
554 | |
555 | void tst_QPropertyAnimation::startWhenAnotherIsRunning() |
556 | { |
557 | StartValueTester o; |
558 | o.setProperty(name: "ole" , value: 42); |
559 | o.values.clear(); |
560 | TestAnimationDriver timeDriver; |
561 | |
562 | { |
563 | //normal case: the animation finishes and is deleted |
564 | QPointer<QVariantAnimation> anim = new QPropertyAnimation(&o, "ole" ); |
565 | anim->setEndValue(100); |
566 | QSignalSpy runningSpy(anim.data(), &QVariantAnimation::stateChanged); |
567 | QVERIFY(runningSpy.isValid()); |
568 | anim->start(policy: QVariantAnimation::DeleteWhenStopped); |
569 | timeDriver.wait(ms: anim->duration()); |
570 | QCOMPARE(runningSpy.count(), 2); //started and then stopped |
571 | QVERIFY(!anim); |
572 | } |
573 | { |
574 | QPointer<QVariantAnimation> anim = new QPropertyAnimation(&o, "ole" ); |
575 | anim->setEndValue(100); |
576 | QSignalSpy runningSpy(anim.data(), &QVariantAnimation::stateChanged); |
577 | QVERIFY(runningSpy.isValid()); |
578 | anim->start(policy: QVariantAnimation::DeleteWhenStopped); |
579 | timeDriver.wait(ms: anim->duration()/2); |
580 | QPointer<QVariantAnimation> anim2 = new QPropertyAnimation(&o, "ole" ); |
581 | anim2->setEndValue(100); |
582 | QCOMPARE(runningSpy.count(), 1); |
583 | QCOMPARE(anim->state(), QVariantAnimation::Running); |
584 | |
585 | //anim2 will interrupt anim1 |
586 | QMetaObject::invokeMethod(obj: anim2, member: "start" , type: Qt::QueuedConnection, Q_ARG(QAbstractAnimation::DeletionPolicy, QVariantAnimation::DeleteWhenStopped)); |
587 | timeDriver.wait(ms: 50); |
588 | QVERIFY(!anim); //anim should have been deleted |
589 | QVERIFY(anim2); |
590 | timeDriver.wait(ms: anim2->duration()); |
591 | QVERIFY(!anim2); //anim2 is finished: it should have been deleted by now |
592 | QVERIFY(!anim); |
593 | } |
594 | |
595 | } |
596 | |
597 | // copy from easing.cpp in case that function changes definition |
598 | static qreal easeInOutBack(qreal t) |
599 | { |
600 | qreal s = 1.70158; |
601 | qreal t_adj = 2.0f * (qreal)t; |
602 | if (t_adj < 1) { |
603 | s *= 1.525f; |
604 | return 1.0/2*(t_adj*t_adj*((s+1)*t_adj - s)); |
605 | } else { |
606 | t_adj -= 2; |
607 | s *= 1.525f; |
608 | return 1.0/2*(t_adj*t_adj*((s+1)*t_adj + s) + 2); |
609 | } |
610 | } |
611 | |
612 | void tst_QPropertyAnimation::easingcurve_data() |
613 | { |
614 | QTest::addColumn<int>(name: "currentTime" ); |
615 | QTest::addColumn<int>(name: "expectedvalue" ); |
616 | |
617 | QTest::newRow(dataTag: "interpolation1" ) << 0 << 0; |
618 | QTest::newRow(dataTag: "interpolation2" ) << 1000 << 1000; |
619 | QTest::newRow(dataTag: "extrapolationbelow" ) << 250 << -99; |
620 | QTest::newRow(dataTag: "extrapolationabove" ) << 750 << 1099; |
621 | } |
622 | |
623 | void tst_QPropertyAnimation::easingcurve() |
624 | { |
625 | QFETCH(int, currentTime); |
626 | QFETCH(int, expectedvalue); |
627 | QObject o; |
628 | o.setProperty(name: "ole" , value: 42); |
629 | QCOMPARE(o.property("ole" ).toInt(), 42); |
630 | |
631 | QPropertyAnimation pAnimation(&o, "ole" ); |
632 | pAnimation.setStartValue(0); |
633 | pAnimation.setEndValue(1000); |
634 | pAnimation.setDuration(1000); |
635 | |
636 | // this easingcurve assumes that we extrapolate before startValue and after endValue |
637 | QEasingCurve easingCurve; |
638 | easingCurve.setCustomType(easeInOutBack); |
639 | pAnimation.setEasingCurve(easingCurve); |
640 | pAnimation.start(); |
641 | pAnimation.pause(); |
642 | pAnimation.setCurrentTime(currentTime); |
643 | QCOMPARE(o.property("ole" ).toInt(), expectedvalue); |
644 | } |
645 | |
646 | void tst_QPropertyAnimation::startWithoutStartValue() |
647 | { |
648 | TestAnimationDriver timeDriver; |
649 | QObject o; |
650 | o.setProperty(name: "ole" , value: 42); |
651 | QCOMPARE(o.property("ole" ).toInt(), 42); |
652 | |
653 | QPropertyAnimation anim(&o, "ole" ); |
654 | anim.setEndValue(100); |
655 | |
656 | anim.start(); |
657 | |
658 | timeDriver.wait(ms: 100); |
659 | int current = anim.currentValue().toInt(); |
660 | //it is somewhere in the animation |
661 | QVERIFY(current > 42); |
662 | QVERIFY(current < 100); |
663 | |
664 | timeDriver.wait(ms: 200); |
665 | QCOMPARE(anim.state(), QVariantAnimation::Stopped); |
666 | current = anim.currentValue().toInt(); |
667 | QCOMPARE(current, 100); |
668 | QCOMPARE(o.property("ole" ).toInt(), current); |
669 | |
670 | anim.setEndValue(110); |
671 | anim.start(); |
672 | current = anim.currentValue().toInt(); |
673 | // the default start value will reevaluate the current property |
674 | // and set it to the end value of the last iteration |
675 | QCOMPARE(current, 100); |
676 | timeDriver.wait(ms: 100); |
677 | current = anim.currentValue().toInt(); |
678 | //it is somewhere in the animation |
679 | QVERIFY(current >= 100); |
680 | QVERIFY(current <= 110); |
681 | } |
682 | |
683 | void tst_QPropertyAnimation::startBackwardWithoutEndValue() |
684 | { |
685 | TestAnimationDriver timeDriver; |
686 | QObject o; |
687 | o.setProperty(name: "ole" , value: 42); |
688 | QCOMPARE(o.property("ole" ).toInt(), 42); |
689 | |
690 | QPropertyAnimation anim(&o, "ole" ); |
691 | anim.setStartValue(100); |
692 | anim.setDirection(QAbstractAnimation::Backward); |
693 | |
694 | //we start without an end value |
695 | anim.start(); |
696 | QCOMPARE(anim.state(), QAbstractAnimation::Running); |
697 | QCOMPARE(o.property("ole" ).toInt(), 42); //the initial value |
698 | |
699 | timeDriver.wait(ms: 100); |
700 | int current = anim.currentValue().toInt(); |
701 | //it is somewhere in the animation |
702 | QVERIFY(current > 42); |
703 | QVERIFY(current < 100); |
704 | |
705 | timeDriver.wait(ms: 200); |
706 | QCOMPARE(anim.state(), QVariantAnimation::Stopped); |
707 | current = anim.currentValue().toInt(); |
708 | QCOMPARE(current, 100); |
709 | QCOMPARE(o.property("ole" ).toInt(), current); |
710 | |
711 | anim.setStartValue(110); |
712 | anim.start(); |
713 | current = anim.currentValue().toInt(); |
714 | // the default start value will reevaluate the current property |
715 | // and set it to the end value of the last iteration |
716 | QCOMPARE(current, 100); |
717 | timeDriver.wait(ms: 100); |
718 | current = anim.currentValue().toInt(); |
719 | //it is somewhere in the animation |
720 | QVERIFY(current >= 100); |
721 | QVERIFY(current <= 110); |
722 | } |
723 | |
724 | |
725 | void tst_QPropertyAnimation::playForwardBackward() |
726 | { |
727 | TestAnimationDriver timeDriver; |
728 | QObject o; |
729 | o.setProperty(name: "ole" , value: 0); |
730 | QCOMPARE(o.property("ole" ).toInt(), 0); |
731 | |
732 | QPropertyAnimation anim(&o, "ole" ); |
733 | anim.setStartValue(0); |
734 | anim.setEndValue(100); |
735 | anim.start(); |
736 | timeDriver.wait(ms: anim.duration()); |
737 | QCOMPARE(anim.state(), QAbstractAnimation::Stopped); |
738 | QCOMPARE(anim.currentTime(), anim.duration()); |
739 | |
740 | //the animation is at the end |
741 | anim.setDirection(QVariantAnimation::Backward); |
742 | anim.start(); |
743 | QCOMPARE(anim.state(), QAbstractAnimation::Running); |
744 | timeDriver.wait(ms: anim.duration()); |
745 | QCOMPARE(anim.state(), QAbstractAnimation::Stopped); |
746 | QCOMPARE(anim.currentTime(), 0); |
747 | |
748 | //the direction is backward |
749 | //restarting should jump to the end |
750 | anim.start(); |
751 | QCOMPARE(anim.state(), QAbstractAnimation::Running); |
752 | QCOMPARE(anim.currentTime(), anim.duration()); |
753 | timeDriver.wait(ms: anim.duration()); |
754 | QCOMPARE(anim.state(), QAbstractAnimation::Stopped); |
755 | QCOMPARE(anim.currentTime(), 0); |
756 | } |
757 | |
758 | struct Number |
759 | { |
760 | Number() {} |
761 | Number(int n) |
762 | : n(n) {} |
763 | |
764 | bool operator==(const Number &other) const { |
765 | return n == other.n; |
766 | } |
767 | |
768 | int n; |
769 | }; |
770 | QT_BEGIN_NAMESPACE |
771 | Q_DECLARE_TYPEINFO(Number, Q_PRIMITIVE_TYPE); |
772 | QT_END_NAMESPACE |
773 | |
774 | Q_DECLARE_METATYPE(Number) |
775 | |
776 | QVariant numberInterpolator(const Number &f, const Number &t, qreal progress) |
777 | { |
778 | return QVariant::fromValue<Number>(value: Number(f.n + (t.n - f.n)*progress)); |
779 | } |
780 | |
781 | QVariant xaxisQPointInterpolator(const QPointF &f, const QPointF &t, qreal progress) |
782 | { |
783 | return QPointF(f.x() + (t.x() - f.x())*progress, f.y()); |
784 | } |
785 | |
786 | void tst_QPropertyAnimation::interpolated() |
787 | { |
788 | QObject o; |
789 | o.setProperty(name: "point" , value: QPointF()); //this will avoid warnings |
790 | o.setProperty(name: "number" , value: QVariant::fromValue<Number>(value: Number(42))); |
791 | QCOMPARE(qvariant_cast<Number>(o.property("number" )), Number(42)); |
792 | { |
793 | qRegisterAnimationInterpolator<Number>(func: numberInterpolator); |
794 | QPropertyAnimation anim(&o, "number" ); |
795 | anim.setStartValue(QVariant::fromValue<Number>(value: Number(0))); |
796 | anim.setEndValue(QVariant::fromValue<Number>(value: Number(100))); |
797 | anim.setDuration(1000); |
798 | anim.start(); |
799 | anim.pause(); |
800 | anim.setCurrentTime(100); |
801 | Number t(qvariant_cast<Number>(v: o.property(name: "number" ))); |
802 | QCOMPARE(t, Number(10)); |
803 | anim.setCurrentTime(500); |
804 | QCOMPARE(qvariant_cast<Number>(o.property("number" )), Number(50)); |
805 | } |
806 | { |
807 | qRegisterAnimationInterpolator<QPointF>(func: xaxisQPointInterpolator); |
808 | QPropertyAnimation anim(&o, "point" ); |
809 | anim.setStartValue(QPointF(0,0)); |
810 | anim.setEndValue(QPointF(100, 100)); |
811 | anim.setDuration(1000); |
812 | anim.start(); |
813 | anim.pause(); |
814 | anim.setCurrentTime(100); |
815 | QCOMPARE(o.property("point" ), QVariant(QPointF(10, 0))); |
816 | anim.setCurrentTime(500); |
817 | QCOMPARE(o.property("point" ), QVariant(QPointF(50, 0))); |
818 | } |
819 | { |
820 | // unregister it and see if we get back the default behaviour |
821 | qRegisterAnimationInterpolator<QPointF>(func: 0); |
822 | QPropertyAnimation anim(&o, "point" ); |
823 | anim.setStartValue(QPointF(0,0)); |
824 | anim.setEndValue(QPointF(100, 100)); |
825 | anim.setDuration(1000); |
826 | anim.start(); |
827 | anim.pause(); |
828 | anim.setCurrentTime(100); |
829 | QCOMPARE(o.property("point" ).toPointF(), QPointF(10, 10)); |
830 | anim.setCurrentTime(500); |
831 | QCOMPARE(o.property("point" ).toPointF(), QPointF(50, 50)); |
832 | } |
833 | |
834 | { |
835 | // Interpolate a qreal property with a int interpolator |
836 | AnimationObject o1; |
837 | o1.setRealValue(42.42); |
838 | QPropertyAnimation anim(&o1, "realValue" ); |
839 | anim.setStartValue(0); |
840 | anim.setEndValue(100); |
841 | anim.start(); |
842 | QCOMPARE(o1.realValue(), qreal(0)); |
843 | anim.setCurrentTime(250); |
844 | QCOMPARE(o1.realValue(), qreal(100)); |
845 | } |
846 | } |
847 | |
848 | |
849 | void tst_QPropertyAnimation::setStartEndValues_data() |
850 | { |
851 | QTest::addColumn<QByteArray>(name: "propertyName" ); |
852 | QTest::addColumn<QVariant>(name: "initialValue" ); |
853 | QTest::addColumn<QVariant>(name: "startValue" ); |
854 | QTest::addColumn<QVariant>(name: "endValue" ); |
855 | |
856 | QTest::newRow(dataTag: "dynamic property" ) << QByteArray("ole" ) << QVariant(42) << QVariant(0) << QVariant(10); |
857 | QTest::newRow(dataTag: "real property, with unmatching types" ) << QByteArray("x" ) << QVariant(42.) << QVariant(0) << QVariant(10.); |
858 | } |
859 | |
860 | void tst_QPropertyAnimation::setStartEndValues() |
861 | { |
862 | MyObject object; |
863 | QFETCH(QByteArray, propertyName); |
864 | QFETCH(QVariant, initialValue); |
865 | QFETCH(QVariant, startValue); |
866 | QFETCH(QVariant, endValue); |
867 | |
868 | //this tests the start value, end value and default start value |
869 | object.setProperty(name: propertyName, value: initialValue); |
870 | QPropertyAnimation anim(&object, propertyName); |
871 | QVariantAnimation::KeyValues values; |
872 | QCOMPARE(anim.keyValues(), values); |
873 | |
874 | //let's add a start value |
875 | anim.setStartValue(startValue); |
876 | values << QVariantAnimation::KeyValue(0, startValue); |
877 | QCOMPARE(anim.keyValues(), values); |
878 | |
879 | anim.setEndValue(endValue); |
880 | values << QVariantAnimation::KeyValue(1, endValue); |
881 | QCOMPARE(anim.keyValues(), values); |
882 | |
883 | //now we can play with objects |
884 | QCOMPARE(object.property(propertyName).toDouble(), initialValue.toDouble()); |
885 | anim.start(); |
886 | QVERIFY(anim.startValue().isValid()); |
887 | QCOMPARE(object.property(propertyName), anim.startValue()); |
888 | anim.setCurrentTime(anim.duration()/2); |
889 | QCOMPARE(object.property(propertyName).toDouble(), (startValue.toDouble() + endValue.toDouble())/2 ); //just in the middle of the animation |
890 | anim.setCurrentTime(anim.duration()); //we go to the end of the animation |
891 | QCOMPARE(anim.state(), QAnimationGroup::Stopped); //it should have stopped |
892 | QVERIFY(anim.endValue().isValid()); |
893 | QCOMPARE(object.property(propertyName), anim.endValue()); //end of the animations |
894 | |
895 | //now we remove the explicit start value and test the implicit one |
896 | anim.stop(); |
897 | object.setProperty(name: propertyName, value: initialValue); |
898 | |
899 | //let's reset the start value |
900 | values.remove(i: 0); |
901 | anim.setStartValue(QVariant()); |
902 | QCOMPARE(anim.keyValues(), values); |
903 | QVERIFY(!anim.startValue().isValid()); |
904 | |
905 | anim.start(); |
906 | QCOMPARE(object.property(propertyName), initialValue); |
907 | anim.setCurrentTime(anim.duration()/2); |
908 | QCOMPARE(object.property(propertyName).toDouble(), (initialValue.toDouble() + endValue.toDouble())/2 ); //just in the middle of the animation |
909 | anim.setCurrentTime(anim.duration()); //we go to the end of the animation |
910 | QCOMPARE(anim.state(), QAnimationGroup::Stopped); //it should have stopped |
911 | QVERIFY(anim.endValue().isValid()); |
912 | QCOMPARE(object.property(propertyName), anim.endValue()); //end of the animations |
913 | |
914 | //now we set back the startValue |
915 | anim.setStartValue(startValue); |
916 | QVERIFY(anim.startValue().isValid()); |
917 | anim.start(); |
918 | QCOMPARE(object.property(propertyName), startValue); |
919 | } |
920 | |
921 | void tst_QPropertyAnimation::zeroDurationStart() |
922 | { |
923 | DummyPropertyAnimation anim; |
924 | QSignalSpy spy(&anim, &DummyPropertyAnimation::stateChanged); |
925 | QVERIFY(spy.isValid()); |
926 | anim.setDuration(0); |
927 | QCOMPARE(anim.state(), QAbstractAnimation::Stopped); |
928 | anim.start(); |
929 | //the animation stops immediately |
930 | QCOMPARE(anim.state(), QAbstractAnimation::Stopped); |
931 | QCOMPARE(spy.count(), 2); |
932 | |
933 | //let's check the first state change |
934 | const QVariantList firstChange = spy.first(); |
935 | //old state |
936 | QCOMPARE(qvariant_cast<QAbstractAnimation::State>(firstChange.last()), QAbstractAnimation::Stopped); |
937 | //new state |
938 | QCOMPARE(qvariant_cast<QAbstractAnimation::State>(firstChange.first()), QAbstractAnimation::Running); |
939 | |
940 | //let's check the first state change |
941 | const QVariantList secondChange = spy.last(); |
942 | //old state |
943 | QCOMPARE(qvariant_cast<QAbstractAnimation::State>(secondChange.last()), QAbstractAnimation::Running); |
944 | //new state |
945 | QCOMPARE(qvariant_cast<QAbstractAnimation::State>(secondChange.first()), QAbstractAnimation::Stopped); |
946 | } |
947 | |
948 | void tst_QPropertyAnimation::zeroDurationForwardBackward() |
949 | { |
950 | QObject o; o.setProperty(name: "test" , value: 1); |
951 | QObject o2; o2.setProperty(name: "test" , value: 2); |
952 | QObject o3; o3.setProperty(name: "test" , value: 3); |
953 | QObject o4; o4.setProperty(name: "test" , value: 4); |
954 | QPropertyAnimation prop(&o, "test" ); prop.setDuration(0); prop.setStartValue(1); prop.setEndValue(2); |
955 | |
956 | prop.start(); |
957 | QCOMPARE(o.property("test" ).toInt(), 2); |
958 | prop.setDirection(QAbstractAnimation::Backward); |
959 | prop.start(); |
960 | QCOMPARE(o.property("test" ).toInt(), 1); |
961 | |
962 | prop.setDirection(QAbstractAnimation::Forward); |
963 | QPropertyAnimation prop2(&o2, "test" ); prop2.setDuration(0); prop2.setStartValue(2); prop2.setEndValue(3); |
964 | QPropertyAnimation prop3(&o3, "test" ); prop3.setDuration(0); prop3.setStartValue(3); prop3.setEndValue(4); |
965 | QPropertyAnimation prop4(&o4, "test" ); prop4.setDuration(0); prop4.setStartValue(4); prop4.setEndValue(5); |
966 | QSequentialAnimationGroup group; |
967 | group.addAnimation(animation: &prop); |
968 | group.addAnimation(animation: &prop2); |
969 | group.addAnimation(animation: &prop3); |
970 | group.addAnimation(animation: &prop4); |
971 | group.start(); |
972 | |
973 | QCOMPARE(o.property("test" ).toInt(), 2); |
974 | QCOMPARE(o2.property("test" ).toInt(), 3); |
975 | QCOMPARE(o3.property("test" ).toInt(), 4); |
976 | QCOMPARE(o4.property("test" ).toInt(), 5); |
977 | |
978 | group.setDirection(QAbstractAnimation::Backward); |
979 | group.start(); |
980 | |
981 | QCOMPARE(o.property("test" ).toInt(), 1); |
982 | QCOMPARE(o2.property("test" ).toInt(), 2); |
983 | QCOMPARE(o3.property("test" ).toInt(), 3); |
984 | QCOMPARE(o4.property("test" ).toInt(), 4); |
985 | |
986 | group.removeAnimation(animation: &prop); |
987 | group.removeAnimation(animation: &prop2); |
988 | group.removeAnimation(animation: &prop3); |
989 | group.removeAnimation(animation: &prop4); |
990 | } |
991 | |
992 | #define Pause 1 |
993 | #define Start 2 |
994 | #define Resume 3 |
995 | #define Stop 4 |
996 | |
997 | void tst_QPropertyAnimation::operationsInStates_data() |
998 | { |
999 | QTest::addColumn<QAbstractAnimation::State>(name: "originState" ); |
1000 | QTest::addColumn<int>(name: "operation" ); |
1001 | QTest::addColumn<QString>(name: "expectedWarning" ); |
1002 | QTest::addColumn<QAbstractAnimation::State>(name: "expectedState" ); |
1003 | |
1004 | QString pauseWarn(QLatin1String("QAbstractAnimation::pause: Cannot pause a stopped animation" )); |
1005 | QString resumeWarn(QLatin1String("QAbstractAnimation::resume: Cannot resume an animation that is not paused" )); |
1006 | |
1007 | QTest::newRow(dataTag: "S-pause" ) << QAbstractAnimation::Stopped << Pause << pauseWarn << QAbstractAnimation::Stopped; |
1008 | QTest::newRow(dataTag: "S-start" ) << QAbstractAnimation::Stopped << Start << QString() << QAbstractAnimation::Running; |
1009 | QTest::newRow(dataTag: "S-resume" ) << QAbstractAnimation::Stopped << Resume << resumeWarn << QAbstractAnimation::Stopped; |
1010 | QTest::newRow(dataTag: "S-stop" ) << QAbstractAnimation::Stopped << Stop << QString() << QAbstractAnimation::Stopped; |
1011 | |
1012 | QTest::newRow(dataTag: "P-pause" ) << QAbstractAnimation::Paused << Pause << QString() << QAbstractAnimation::Paused; |
1013 | QTest::newRow(dataTag: "P-start" ) << QAbstractAnimation::Paused << Start << QString() << QAbstractAnimation::Running; |
1014 | QTest::newRow(dataTag: "P-resume" ) << QAbstractAnimation::Paused << Resume << QString() << QAbstractAnimation::Running; |
1015 | QTest::newRow(dataTag: "P-stop" ) << QAbstractAnimation::Paused << Stop << QString() << QAbstractAnimation::Stopped; |
1016 | |
1017 | QTest::newRow(dataTag: "R-pause" ) << QAbstractAnimation::Running << Pause << QString() << QAbstractAnimation::Paused; |
1018 | QTest::newRow(dataTag: "R-start" ) << QAbstractAnimation::Running << Start << QString() << QAbstractAnimation::Running; |
1019 | QTest::newRow(dataTag: "R-resume" ) << QAbstractAnimation::Running << Resume << resumeWarn << QAbstractAnimation::Running; |
1020 | QTest::newRow(dataTag: "R-stop" ) << QAbstractAnimation::Running << Stop << QString() << QAbstractAnimation::Stopped; |
1021 | } |
1022 | |
1023 | void tst_QPropertyAnimation::operationsInStates() |
1024 | { |
1025 | /** |
1026 | * | pause() |start() |resume() |stop() |
1027 | * ----------+------------+-----------+-----------+-------------------+ |
1028 | * Stopped | Stopped |Running |Stopped |Stopped | |
1029 | * _| qWarning |restart |qWarning | | |
1030 | * Paused | Paused |Running |Running |Stopped | |
1031 | * _| | | | | |
1032 | * Running | Paused |Running |Running |Stopped | |
1033 | * | |restart |qWarning | | |
1034 | * ----------+------------+-----------+-----------+-------------------+ |
1035 | **/ |
1036 | |
1037 | QFETCH(QAbstractAnimation::State, originState); |
1038 | QFETCH(int, operation); |
1039 | QFETCH(QString, expectedWarning); |
1040 | QFETCH(QAbstractAnimation::State, expectedState); |
1041 | |
1042 | QObject o; |
1043 | o.setProperty(name: "ole" , value: 42); |
1044 | QPropertyAnimation anim(&o, "ole" ); |
1045 | anim.setEndValue(100); |
1046 | QSignalSpy spy(&anim, &QPropertyAnimation::stateChanged); |
1047 | QVERIFY(spy.isValid()); |
1048 | |
1049 | anim.stop(); |
1050 | switch (originState) { |
1051 | case QAbstractAnimation::Stopped: |
1052 | break; |
1053 | case QAbstractAnimation::Paused: |
1054 | anim.start(); |
1055 | anim.pause(); |
1056 | break; |
1057 | case QAbstractAnimation::Running: |
1058 | anim.start(); |
1059 | break; |
1060 | } |
1061 | if (!expectedWarning.isEmpty()) { |
1062 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(expectedWarning)); |
1063 | } |
1064 | QCOMPARE(anim.state(), originState); |
1065 | switch (operation) { |
1066 | case Pause: |
1067 | anim.pause(); |
1068 | break; |
1069 | case Start: |
1070 | anim.start(); |
1071 | break; |
1072 | case Resume: |
1073 | anim.resume(); |
1074 | break; |
1075 | case Stop: |
1076 | anim.stop(); |
1077 | break; |
1078 | } |
1079 | |
1080 | QCOMPARE(anim.state(), expectedState); |
1081 | } |
1082 | #undef Pause |
1083 | #undef Start |
1084 | #undef Resume |
1085 | #undef Stop |
1086 | |
1087 | void tst_QPropertyAnimation::oneKeyValue() |
1088 | { |
1089 | QObject o; |
1090 | o.setProperty(name: "ole" , value: 42); |
1091 | QCOMPARE(o.property("ole" ).toInt(), 42); |
1092 | |
1093 | QPropertyAnimation animation(&o, "ole" ); |
1094 | animation.setStartValue(43); |
1095 | animation.setEndValue(44); |
1096 | animation.setDuration(100); |
1097 | |
1098 | animation.setCurrentTime(0); |
1099 | |
1100 | QVERIFY(animation.currentValue().isValid()); |
1101 | QCOMPARE(animation.currentValue().toInt(), 43); |
1102 | QCOMPARE(o.property("ole" ).toInt(), 42); |
1103 | |
1104 | // remove the last key value |
1105 | animation.setKeyValueAt(step: 1.0, value: QVariant()); |
1106 | |
1107 | // we will neither interpolate, nor update the current value |
1108 | // since there is only one 1 key value defined |
1109 | animation.setCurrentTime(100); |
1110 | |
1111 | // the animation should not have been modified |
1112 | QVERIFY(animation.currentValue().isValid()); |
1113 | QCOMPARE(animation.currentValue().toInt(), 43); |
1114 | QCOMPARE(o.property("ole" ).toInt(), 42); |
1115 | } |
1116 | |
1117 | void tst_QPropertyAnimation::updateOnSetKeyValues() |
1118 | { |
1119 | QObject o; |
1120 | o.setProperty(name: "ole" , value: 100); |
1121 | QCOMPARE(o.property("ole" ).toInt(), 100); |
1122 | |
1123 | QPropertyAnimation animation(&o, "ole" ); |
1124 | animation.setStartValue(100); |
1125 | animation.setEndValue(200); |
1126 | animation.setDuration(100); |
1127 | |
1128 | animation.setCurrentTime(50); |
1129 | QCOMPARE(animation.currentValue().toInt(), 150); |
1130 | animation.setKeyValueAt(step: 0.0, value: 300); |
1131 | QCOMPARE(animation.currentValue().toInt(), 250); |
1132 | |
1133 | o.setProperty(name: "ole" , value: 100); |
1134 | QPropertyAnimation animation2(&o, "ole" ); |
1135 | QVariantAnimation::KeyValues kValues; |
1136 | kValues << QVariantAnimation::KeyValue(0.0, 100) << QVariantAnimation::KeyValue(1.0, 200); |
1137 | animation2.setKeyValues(kValues); |
1138 | animation2.setDuration(100); |
1139 | animation2.setCurrentTime(50); |
1140 | QCOMPARE(animation2.currentValue().toInt(), 150); |
1141 | |
1142 | kValues.clear(); |
1143 | kValues << QVariantAnimation::KeyValue(0.0, 300) << QVariantAnimation::KeyValue(1.0, 200); |
1144 | animation2.setKeyValues(kValues); |
1145 | |
1146 | QCOMPARE(animation2.currentValue().toInt(), animation.currentValue().toInt()); |
1147 | } |
1148 | |
1149 | |
1150 | //this class will 'throw' an error in the test lib |
1151 | // if the property ole is set to ErrorValue |
1152 | class MyErrorObject : public QObject |
1153 | { |
1154 | Q_OBJECT |
1155 | Q_PROPERTY(int ole READ ole WRITE setOle) |
1156 | public: |
1157 | |
1158 | static const int ErrorValue = 10000; |
1159 | |
1160 | MyErrorObject() : m_ole(0) { } |
1161 | int ole() const { return m_ole; } |
1162 | void setOle(int o) |
1163 | { |
1164 | QVERIFY(o != ErrorValue); |
1165 | m_ole = o; |
1166 | } |
1167 | |
1168 | private: |
1169 | int m_ole; |
1170 | |
1171 | |
1172 | }; |
1173 | |
1174 | void tst_QPropertyAnimation::restart() |
1175 | { |
1176 | //here we check that be restarting an animation |
1177 | //it doesn't get an bogus intermediate value (end value) |
1178 | //because the time is not yet reset to 0 |
1179 | MyErrorObject o; |
1180 | o.setOle(100); |
1181 | QCOMPARE(o.property("ole" ).toInt(), 100); |
1182 | |
1183 | QPropertyAnimation anim(&o, "ole" ); |
1184 | anim.setEndValue(200); |
1185 | anim.start(); |
1186 | anim.setCurrentTime(anim.duration()); |
1187 | QCOMPARE(anim.state(), QAbstractAnimation::Stopped); |
1188 | QCOMPARE(o.property("ole" ).toInt(), 200); |
1189 | |
1190 | //we'll check that the animation never gets a wrong value when starting it |
1191 | //after having changed the end value |
1192 | anim.setEndValue(MyErrorObject::ErrorValue); |
1193 | anim.start(); |
1194 | } |
1195 | |
1196 | void tst_QPropertyAnimation::valueChanged() |
1197 | { |
1198 | TestAnimationDriver timeDriver; |
1199 | //we check that we receive the valueChanged signal |
1200 | MyErrorObject o; |
1201 | o.setOle(0); |
1202 | QCOMPARE(o.property("ole" ).toInt(), 0); |
1203 | QPropertyAnimation anim(&o, "ole" ); |
1204 | anim.setEndValue(5); |
1205 | anim.setDuration(200); |
1206 | QSignalSpy spy(&anim, &QPropertyAnimation::valueChanged); |
1207 | QVERIFY(spy.isValid()); |
1208 | anim.start(); |
1209 | // Drive animation forward to its end |
1210 | timeDriver.wait(ms: anim.duration()); |
1211 | |
1212 | QCOMPARE(anim.state(), QAbstractAnimation::Stopped); |
1213 | QCOMPARE(anim.currentTime(), anim.duration()); |
1214 | |
1215 | //let's check that the values go forward |
1216 | QCOMPARE(spy.count(), 6); //we should have got everything from 0 to 5 |
1217 | for (int i = 0; i < spy.count(); ++i) { |
1218 | QCOMPARE(qvariant_cast<QVariant>(spy.at(i).first()).toInt(), i); |
1219 | } |
1220 | } |
1221 | |
1222 | //this class will help us make sure that 2 animations started |
1223 | //at the same time also end at the same time |
1224 | class MySyncObject : public MyErrorObject |
1225 | { |
1226 | Q_OBJECT |
1227 | public: |
1228 | MySyncObject() : anim(this, "ole" ) |
1229 | { |
1230 | anim.setEndValue(1000); |
1231 | } |
1232 | public slots: |
1233 | void checkAnimationFinished() |
1234 | { |
1235 | QCOMPARE(anim.state(), QAbstractAnimation::Stopped); |
1236 | QCOMPARE(ole(), 1000); |
1237 | } |
1238 | |
1239 | public: |
1240 | QPropertyAnimation anim; |
1241 | }; |
1242 | |
1243 | void tst_QPropertyAnimation::twoAnimations() |
1244 | { |
1245 | TestAnimationDriver timeDriver; |
1246 | MySyncObject o1, o2; |
1247 | o1.setOle(0); |
1248 | o2.setOle(0); |
1249 | |
1250 | //when the animation in o1 is finished |
1251 | //the animation in o2 should stop around the same time |
1252 | //We use a queued connection to check just after the tick from the common timer |
1253 | //the other way is true too |
1254 | QObject::connect(sender: &o1.anim, SIGNAL(finished()), |
1255 | receiver: &o2, SLOT(checkAnimationFinished()), Qt::QueuedConnection); |
1256 | QObject::connect(sender: &o2.anim, SIGNAL(finished()), |
1257 | receiver: &o1, SLOT(checkAnimationFinished()), Qt::QueuedConnection); |
1258 | |
1259 | o1.anim.start(); |
1260 | o2.anim.start(); |
1261 | |
1262 | timeDriver.wait(ms: o1.anim.duration()); |
1263 | QCOMPARE(o1.anim.state(), QAbstractAnimation::Stopped); |
1264 | QCOMPARE(o2.anim.state(), QAbstractAnimation::Stopped); |
1265 | |
1266 | QCOMPARE(o1.ole(), 1000); |
1267 | QCOMPARE(o2.ole(), 1000); |
1268 | } |
1269 | |
1270 | class MyComposedAnimation : public QPropertyAnimation |
1271 | { |
1272 | Q_OBJECT |
1273 | public: |
1274 | MyComposedAnimation(QObject *target, const QByteArray &propertyName, const QByteArray &innerPropertyName) |
1275 | : QPropertyAnimation(target, propertyName) |
1276 | { |
1277 | innerAnim = new QPropertyAnimation(target, innerPropertyName); |
1278 | this->setEndValue(1000); |
1279 | innerAnim->setEndValue(1000); |
1280 | innerAnim->setDuration(duration() + 100); |
1281 | } |
1282 | |
1283 | void start() |
1284 | { |
1285 | QPropertyAnimation::start(); |
1286 | innerAnim->start(); |
1287 | } |
1288 | |
1289 | void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) |
1290 | { |
1291 | QPropertyAnimation::updateState(newState, oldState); |
1292 | if (newState == QAbstractAnimation::Stopped) |
1293 | delete innerAnim; |
1294 | } |
1295 | |
1296 | public: |
1297 | QPropertyAnimation *innerAnim; |
1298 | }; |
1299 | |
1300 | void tst_QPropertyAnimation::deletedInUpdateCurrentTime() |
1301 | { |
1302 | TestAnimationDriver timeDriver; |
1303 | // this test case reproduces an animation being deleted in the updateCurrentTime of |
1304 | // another animation(was causing segfault). |
1305 | // the deleted animation must have been started after the animation that is deleting. |
1306 | AnimationObject o; |
1307 | o.setValue(0); |
1308 | o.setRealValue(0.0); |
1309 | |
1310 | MyComposedAnimation composedAnimation(&o, "value" , "realValue" ); |
1311 | composedAnimation.start(); |
1312 | QCOMPARE(composedAnimation.state(), QAbstractAnimation::Running); |
1313 | timeDriver.wait(ms: composedAnimation.duration()); |
1314 | |
1315 | QCOMPARE(composedAnimation.state(), QAbstractAnimation::Stopped); |
1316 | QCOMPARE(o.value(), 1000); |
1317 | } |
1318 | |
1319 | void tst_QPropertyAnimation::totalDuration() |
1320 | { |
1321 | QPropertyAnimation anim; |
1322 | QCOMPARE(anim.totalDuration(), 250); |
1323 | anim.setLoopCount(2); |
1324 | QCOMPARE(anim.totalDuration(), 2*250); |
1325 | anim.setLoopCount(-1); |
1326 | QCOMPARE(anim.totalDuration(), -1); |
1327 | anim.setDuration(0); |
1328 | QCOMPARE(anim.totalDuration(), 0); |
1329 | } |
1330 | |
1331 | void tst_QPropertyAnimation::zeroLoopCount() |
1332 | { |
1333 | DummyPropertyAnimation animation; |
1334 | auto *anim = &animation; |
1335 | anim->setStartValue(0); |
1336 | anim->setDuration(20); |
1337 | anim->setLoopCount(0); |
1338 | |
1339 | QSignalSpy runningSpy(anim, &QPropertyAnimation::stateChanged); |
1340 | QSignalSpy finishedSpy(anim, &QPropertyAnimation::finished); |
1341 | |
1342 | QVERIFY(runningSpy.isValid()); |
1343 | QVERIFY(finishedSpy.isValid()); |
1344 | |
1345 | QCOMPARE(anim->state(), QAnimationGroup::Stopped); |
1346 | QCOMPARE(anim->currentValue().toInt(), 0); |
1347 | QCOMPARE(runningSpy.count(), 0); |
1348 | QCOMPARE(finishedSpy.count(), 0); |
1349 | |
1350 | anim->start(); |
1351 | |
1352 | QCOMPARE(anim->state(), QAnimationGroup::Stopped); |
1353 | QCOMPARE(anim->currentValue().toInt(), 0); |
1354 | QCOMPARE(runningSpy.count(), 0); |
1355 | QCOMPARE(finishedSpy.count(), 0); |
1356 | } |
1357 | |
1358 | |
1359 | class RecursiveObject : public QObject |
1360 | { |
1361 | Q_OBJECT |
1362 | Q_PROPERTY(qreal x READ x WRITE setX) |
1363 | Q_PROPERTY(qreal y READ y WRITE setY) |
1364 | public: |
1365 | RecursiveObject() : m_x(0), m_y(0) { |
1366 | animation.setTargetObject(this); |
1367 | animation.setPropertyName("y" ); |
1368 | animation.setDuration(30); |
1369 | } |
1370 | qreal x() const { return m_x; } |
1371 | void setX(qreal x) { |
1372 | m_x = x; |
1373 | animation.setEndValue(x); |
1374 | animation.start(); |
1375 | } |
1376 | qreal y() const { return m_y; } |
1377 | void setY(qreal y) { m_y = y; } |
1378 | |
1379 | qreal m_x; |
1380 | qreal m_y; |
1381 | QPropertyAnimation animation; |
1382 | }; |
1383 | |
1384 | |
1385 | void tst_QPropertyAnimation::recursiveAnimations() |
1386 | { |
1387 | TestAnimationDriver timeDriver; |
1388 | RecursiveObject o; |
1389 | QPropertyAnimation anim; |
1390 | anim.setTargetObject(&o); |
1391 | anim.setPropertyName("x" ); |
1392 | anim.setDuration(30); |
1393 | |
1394 | anim.setEndValue(4000); |
1395 | anim.start(); |
1396 | timeDriver.wait(ms: anim.duration() + o.animation.duration()); |
1397 | QCOMPARE(anim.state(), QAbstractAnimation::Stopped); |
1398 | QCOMPARE(o.animation.state(), QAbstractAnimation::Stopped); |
1399 | QCOMPARE(o.y(), qreal(4000)); |
1400 | } |
1401 | |
1402 | |
1403 | QTEST_MAIN(tst_QPropertyAnimation) |
1404 | #include "tst_qpropertyanimation.moc" |
1405 | |