1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickanimatedsprite_p.h"
5#include "qquickanimatedsprite_p_p.h"
6#include "qquicksprite_p.h"
7#include "qquickspriteengine_p.h"
8#include <QtQuick/private/qsgcontext_p.h>
9#include <QtQuick/private/qquickitem_p.h>
10#include <private/qsgadaptationlayer_p.h>
11#include <private/qqmlglobal_p.h>
12#include <QtQuick/qsgnode.h>
13#include <QtQuick/qsgtexturematerial.h>
14#include <QtQuick/qsgtexture.h>
15#include <QtQuick/qquickwindow.h>
16#include <QtQml/qqmlinfo.h>
17#include <QFile>
18#include <cmath>
19#include <qmath.h>
20#include <QDebug>
21
22QT_BEGIN_NAMESPACE
23
24/*!
25 \qmltype AnimatedSprite
26 \instantiates QQuickAnimatedSprite
27 \inqmlmodule QtQuick
28 \inherits Item
29 \ingroup qtquick-visual
30 \brief Draws a sprite animation.
31
32 AnimatedSprite provides rendering and control over animations which are provided
33 as multiple frames in the same image file. You can play it at a fixed speed, at the
34 frame rate of your display, or manually advance and control the progress.
35
36 Consider the following sprite sheet:
37
38 \image animatedsprite-loading.png
39
40 It can be divided up into four frames:
41
42 \image animatedsprite-loading-frames.png
43
44 To play each of these frames at a speed of 500 milliseconds per frame, the
45 following code can be used:
46
47 \table
48 \header
49 \li Code
50 \li Result
51 \row
52 \li
53 \code
54 AnimatedSprite {
55 source: "loading.png"
56 frameWidth: 64
57 frameHeight: 64
58 frameCount: 4
59 frameDuration: 500
60 }
61 \endcode
62 \li
63 \image animatedsprite-loading-interpolated.gif
64 \endtable
65
66 By default, the frames are interpolated (blended together) to make the
67 animation appear smoother. To disable this, set \l interpolate to \c false:
68
69 \table
70 \header
71 \li Code
72 \li Result
73 \row
74 \li
75 \code
76 AnimatedSprite {
77 source: "loading.png"
78 frameWidth: 64
79 frameHeight: 64
80 frameCount: 4
81 frameDuration: 500
82 interpolate: false
83 }
84 \endcode
85 \li
86 \image animatedsprite-loading.gif
87 \endtable
88
89 To control how AnimatedSprite responds to being scaled, use the
90 \l {Item::}{smooth} property.
91
92 Note that unlike \l SpriteSequence, the AnimatedSprite type does not use
93 \l Sprite to define multiple animations, but instead encapsulates a
94 single animation itself.
95
96 \sa {Sprite Animations}
97*/
98
99/*!
100 \qmlproperty bool QtQuick::AnimatedSprite::running
101
102 Whether the sprite is animating or not.
103
104 Default is true
105*/
106
107/*!
108 \qmlproperty bool QtQuick::AnimatedSprite::interpolate
109
110 If true, interpolation will occur between sprite frames to make the
111 animation appear smoother.
112
113 Default is true.
114*/
115
116/*!
117 \qmlproperty qreal QtQuick::AnimatedSprite::frameRate
118
119 Frames per second to show in the animation. Values less than or equal to \c 0 are invalid.
120
121 If \c frameRate is valid, it will be used to calculate the duration of the frames.
122 If not, and \l frameDuration is valid, \c frameDuration will be used.
123
124 Changing this parameter will restart the animation.
125*/
126
127/*!
128 \qmlproperty int QtQuick::AnimatedSprite::frameDuration
129
130 Duration of each frame of the animation in milliseconds. Values less than or equal to \c 0 are invalid.
131
132 If frameRate is valid, it will be used to calculate the duration of the frames.
133 If not, and \l frameDuration is valid, \c frameDuration will be used.
134
135 Changing this parameter will restart the animation.
136*/
137
138/*!
139 \qmlproperty int QtQuick::AnimatedSprite::frameCount
140
141 Number of frames in this AnimatedSprite.
142*/
143/*!
144 \qmlproperty int QtQuick::AnimatedSprite::frameHeight
145
146 Height of a single frame in this AnimatedSprite.
147
148 May be omitted if it is the only sprite in the file.
149*/
150/*!
151 \qmlproperty int QtQuick::AnimatedSprite::frameWidth
152
153 Width of a single frame in this AnimatedSprite.
154
155 May be omitted if it is the only sprite in the file.
156*/
157/*!
158 \qmlproperty int QtQuick::AnimatedSprite::frameX
159
160 The X coordinate in the image file of the first frame of the AnimatedSprite.
161
162 May be omitted if the first frame starts in the upper left corner of the file.
163*/
164/*!
165 \qmlproperty int QtQuick::AnimatedSprite::frameY
166
167 The Y coordinate in the image file of the first frame of the AnimatedSprite.
168
169 May be omitted if the first frame starts in the upper left corner of the file.
170*/
171/*!
172 \qmlproperty url QtQuick::AnimatedSprite::source
173
174 The image source for the animation.
175
176 If frameHeight and frameWidth are not specified, it is assumed to be a single long row of square frames.
177 Otherwise, it can be multiple contiguous rows or rectangluar frames, when one row runs out the next will be used.
178
179 If frameX and frameY are specified, the row of frames will be taken with that x/y coordinate as the upper left corner.
180*/
181
182/*!
183 \qmlproperty bool QtQuick::AnimatedSprite::reverse
184
185 If \c true, the animation will be played in reverse.
186
187 Default is \c false.
188*/
189
190/*!
191 \qmlproperty bool QtQuick::AnimatedSprite::frameSync
192
193 If \c true, the animation will have no duration. Instead, the animation will advance
194 one frame each time a frame is rendered to the screen. This synchronizes it with the painting
195 rate as opposed to elapsed time.
196
197 If frameSync is set to true, it overrides both frameRate and frameDuration.
198
199 Default is \c false.
200
201 Changing this parameter will restart the animation.
202*/
203
204/*!
205 \qmlproperty int QtQuick::AnimatedSprite::loops
206
207 After playing the animation this many times, the animation will automatically stop. Negative values are invalid.
208
209 If this is set to \c AnimatedSprite.Infinite the animation will not stop playing on its own.
210
211 Default is \c AnimatedSprite.Infinite
212*/
213
214/*!
215 \qmlproperty bool QtQuick::AnimatedSprite::paused
216
217 When paused, the current frame can be advanced manually.
218
219 Default is \c false.
220*/
221
222/*!
223 \qmlproperty int QtQuick::AnimatedSprite::currentFrame
224
225 When paused, the current frame can be advanced manually by setting this property or calling \l advance().
226
227*/
228
229/*!
230 \qmlproperty enumeration QtQuick::AnimatedSprite::finishBehavior
231
232 The behavior when the animation finishes on its own.
233
234 \value FinishAtInitialFrame
235 When the animation finishes it returns to the initial frame.
236 This is the default behavior.
237
238 \value FinishAtFinalFrame
239 When the animation finishes it stays on the final frame.
240*/
241
242/*!
243 \qmlmethod int QtQuick::AnimatedSprite::restart()
244
245 Stops, then starts the sprite animation.
246*/
247
248/*!
249 \qmlsignal QtQuick::AnimatedSprite::finished()
250 \since 5.12
251
252 This signal is emitted when the sprite has finished animating.
253
254 It is not emitted when running is set to \c false, nor for sprites whose
255 \l loops property is set to \c AnimatedSprite.Infinite.
256*/
257
258QQuickAnimatedSprite::QQuickAnimatedSprite(QQuickItem *parent) :
259 QQuickItem(*(new QQuickAnimatedSpritePrivate), parent)
260{
261 Q_D(QQuickAnimatedSprite);
262 d->m_sprite = new QQuickSprite(this);
263
264 setFlag(flag: ItemHasContents);
265 connect(sender: this, SIGNAL(widthChanged()),
266 receiver: this, SLOT(reset()));
267 connect(sender: this, SIGNAL(heightChanged()),
268 receiver: this, SLOT(reset()));
269}
270
271bool QQuickAnimatedSprite::running() const
272{
273 Q_D(const QQuickAnimatedSprite);
274 return d->m_running;
275}
276
277bool QQuickAnimatedSprite::interpolate() const
278{
279 Q_D(const QQuickAnimatedSprite);
280 return d->m_interpolate;
281}
282
283QUrl QQuickAnimatedSprite::source() const
284{
285 Q_D(const QQuickAnimatedSprite);
286 return d->m_sprite->source();
287}
288
289bool QQuickAnimatedSprite::reverse() const
290{
291 Q_D(const QQuickAnimatedSprite);
292 return d->m_sprite->reverse();
293}
294
295bool QQuickAnimatedSprite::frameSync() const
296{
297 Q_D(const QQuickAnimatedSprite);
298 return d->m_sprite->frameSync();
299}
300
301int QQuickAnimatedSprite::frameCount() const
302{
303 Q_D(const QQuickAnimatedSprite);
304 return d->m_sprite->frames();
305}
306
307int QQuickAnimatedSprite::frameHeight() const
308{
309 Q_D(const QQuickAnimatedSprite);
310 return d->m_sprite->frameHeight();
311}
312
313int QQuickAnimatedSprite::frameWidth() const
314{
315 Q_D(const QQuickAnimatedSprite);
316 return d->m_sprite->frameWidth();
317}
318
319int QQuickAnimatedSprite::frameX() const
320{
321 Q_D(const QQuickAnimatedSprite);
322 return d->m_sprite->frameX();
323}
324
325int QQuickAnimatedSprite::frameY() const
326{
327 Q_D(const QQuickAnimatedSprite);
328 return d->m_sprite->frameY();
329}
330
331qreal QQuickAnimatedSprite::frameRate() const
332{
333 Q_D(const QQuickAnimatedSprite);
334 return d->m_sprite->frameRate();
335}
336
337int QQuickAnimatedSprite::frameDuration() const
338{
339 Q_D(const QQuickAnimatedSprite);
340 return d->m_sprite->frameDuration();
341}
342
343int QQuickAnimatedSprite::loops() const
344{
345 Q_D(const QQuickAnimatedSprite);
346 return d->m_loops;
347}
348
349bool QQuickAnimatedSprite::paused() const
350{
351 Q_D(const QQuickAnimatedSprite);
352 return d->m_paused;
353}
354
355int QQuickAnimatedSprite::currentFrame() const
356{
357 Q_D(const QQuickAnimatedSprite);
358 return d->m_curFrame;
359}
360
361QQuickAnimatedSprite::FinishBehavior QQuickAnimatedSprite::finishBehavior() const
362{
363 Q_D(const QQuickAnimatedSprite);
364 return d->m_finishBehavior;
365}
366
367bool QQuickAnimatedSprite::isCurrentFrameChangedConnected()
368{
369 IS_SIGNAL_CONNECTED(this, QQuickAnimatedSprite, currentFrameChanged, (int));
370}
371
372void QQuickAnimatedSprite::reloadImage()
373{
374 if (!isComponentComplete())
375 return;
376 createEngine();//### It's not as inefficient as it sounds, but it still sucks having to recreate the engine
377}
378
379void QQuickAnimatedSprite::componentComplete()
380{
381 Q_D(QQuickAnimatedSprite);
382 createEngine();
383 QQuickItem::componentComplete();
384 if (d->m_running) {
385 d->m_running = false;
386 start();
387 }
388}
389
390/*!
391 \qmlmethod QtQuick::AnimatedSprite::start()
392 \since 5.15
393
394 Starts the sprite animation. If the animation is already running, calling
395 this method has no effect.
396
397 \sa stop()
398*/
399void QQuickAnimatedSprite::start()
400{
401 Q_D(QQuickAnimatedSprite);
402 if (d->m_running)
403 return;
404 d->m_running = true;
405 if (!isComponentComplete())
406 return;
407 d->m_curLoop = 0;
408 d->m_curFrame = 0;
409 d->m_timestamp.start();
410 if (d->m_spriteEngine) {
411 d->m_spriteEngine->stop(index: 0);
412 d->m_spriteEngine->updateSprites(time: 0);
413 d->m_spriteEngine->start(index: 0);
414 }
415 emit currentFrameChanged(arg: 0);
416 emit runningChanged(arg: true);
417 maybeUpdate();
418}
419
420/*!
421 \qmlmethod QtQuick::AnimatedSprite::stop()
422 \since 5.15
423
424 Stops the sprite animation. If the animation is not running, calling this
425 method has no effect.
426
427 \sa start()
428*/
429void QQuickAnimatedSprite::stop()
430{
431 Q_D(QQuickAnimatedSprite);
432 if (!d->m_running)
433 return;
434 d->m_running = false;
435 if (!isComponentComplete())
436 return;
437 d->m_pauseOffset = 0;
438 emit runningChanged(arg: false);
439 maybeUpdate();
440}
441
442/*!
443 \qmlmethod int QtQuick::AnimatedSprite::advance()
444
445 Advances the sprite animation by one frame.
446*/
447void QQuickAnimatedSprite::advance(int frames)
448{
449 Q_D(QQuickAnimatedSprite);
450 if (!frames)
451 return;
452 //TODO-C: May not work when running - only when paused
453 d->m_curFrame += frames;
454 while (d->m_curFrame < 0)
455 d->m_curFrame += d->m_spriteEngine->maxFrames();
456 d->m_curFrame = d->m_curFrame % d->m_spriteEngine->maxFrames();
457 emit currentFrameChanged(arg: d->m_curFrame);
458 maybeUpdate();
459}
460
461void QQuickAnimatedSprite::maybeUpdate()
462{
463 QQuickItemPrivate *priv = QQuickItemPrivate::get(item: this);
464 const auto &extraData = priv->extra;
465 if ((extraData.isAllocated() && extraData->effectRefCount > 0) || priv->effectiveVisible)
466 update();
467}
468
469void QQuickAnimatedSprite::itemChange(ItemChange change, const ItemChangeData &value)
470{
471 Q_D(QQuickAnimatedSprite);
472 if (change == ItemVisibleHasChanged && d->m_running && !d->m_paused)
473 maybeUpdate();
474 QQuickItem::itemChange(change, value);
475}
476
477/*!
478 \qmlmethod int QtQuick::AnimatedSprite::pause()
479
480 Pauses the sprite animation. This does nothing if
481 \l paused is \c true.
482
483 \sa resume()
484*/
485void QQuickAnimatedSprite::pause()
486{
487 Q_D(QQuickAnimatedSprite);
488
489 if (d->m_paused)
490 return;
491 d->m_pauseOffset = d->m_timestamp.elapsed();
492 d->m_paused = true;
493 emit pausedChanged(arg: true);
494 maybeUpdate();
495}
496
497/*!
498 \qmlmethod int QtQuick::AnimatedSprite::resume()
499
500 Resumes the sprite animation if \l paused is \c true;
501 otherwise, this does nothing.
502
503 \sa pause()
504*/
505void QQuickAnimatedSprite::resume()
506{
507 Q_D(QQuickAnimatedSprite);
508
509 if (!d->m_paused)
510 return;
511 d->m_pauseOffset = d->m_pauseOffset - d->m_timestamp.elapsed();
512 d->m_paused = false;
513 emit pausedChanged(arg: false);
514 maybeUpdate();
515}
516
517void QQuickAnimatedSprite::setRunning(bool arg)
518{
519 Q_D(QQuickAnimatedSprite);
520
521 if (d->m_running != arg) {
522 if (d->m_running)
523 stop();
524 else
525 start();
526 }
527}
528
529void QQuickAnimatedSprite::setPaused(bool arg)
530{
531 Q_D(const QQuickAnimatedSprite);
532
533 if (d->m_paused != arg) {
534 if (d->m_paused)
535 resume();
536 else
537 pause();
538 }
539}
540
541void QQuickAnimatedSprite::setInterpolate(bool arg)
542{
543 Q_D(QQuickAnimatedSprite);
544
545 if (d->m_interpolate != arg) {
546 d->m_interpolate = arg;
547 Q_EMIT interpolateChanged(arg);
548 }
549}
550
551void QQuickAnimatedSprite::setSource(const QUrl &arg)
552{
553 Q_D(QQuickAnimatedSprite);
554
555 if (d->m_sprite->m_source != arg) {
556 const qreal targetDevicePixelRatio = (window() ? window()->effectiveDevicePixelRatio() : qApp->devicePixelRatio());
557 d->m_sprite->setDevicePixelRatio(targetDevicePixelRatio);
558 d->m_sprite->setSource(arg);
559 Q_EMIT sourceChanged(arg);
560 reloadImage();
561 }
562}
563
564void QQuickAnimatedSprite::setReverse(bool arg)
565{
566 Q_D(QQuickAnimatedSprite);
567
568 if (d->m_sprite->m_reverse != arg) {
569 d->m_sprite->setReverse(arg);
570 Q_EMIT reverseChanged(arg);
571 }
572}
573
574void QQuickAnimatedSprite::setFrameSync(bool arg)
575{
576 Q_D(QQuickAnimatedSprite);
577
578 if (d->m_sprite->m_frameSync != arg) {
579 d->m_sprite->setFrameSync(arg);
580 Q_EMIT frameSyncChanged(arg);
581 if (d->m_running)
582 restart();
583 }
584}
585
586void QQuickAnimatedSprite::setFrameCount(int arg)
587{
588 Q_D(QQuickAnimatedSprite);
589
590 if (d->m_sprite->m_frames != arg) {
591 d->m_sprite->setFrameCount(arg);
592 Q_EMIT frameCountChanged(arg);
593 reloadImage();
594 }
595}
596
597void QQuickAnimatedSprite::setFrameHeight(int arg)
598{
599 Q_D(QQuickAnimatedSprite);
600
601 if (d->m_sprite->m_frameHeight != arg) {
602 d->m_sprite->setFrameHeight(arg);
603 Q_EMIT frameHeightChanged(arg);
604 setImplicitHeight(frameHeight());
605 reloadImage();
606 }
607}
608
609void QQuickAnimatedSprite::setFrameWidth(int arg)
610{
611 Q_D(QQuickAnimatedSprite);
612
613 if (d->m_sprite->m_frameWidth != arg) {
614 d->m_sprite->setFrameWidth(arg);
615 Q_EMIT frameWidthChanged(arg);
616 setImplicitWidth(frameWidth());
617 reloadImage();
618 }
619}
620
621void QQuickAnimatedSprite::setFrameX(int arg)
622{
623 Q_D(QQuickAnimatedSprite);
624
625 if (d->m_sprite->m_frameX != arg) {
626 d->m_sprite->setFrameX(arg);
627 Q_EMIT frameXChanged(arg);
628 reloadImage();
629 }
630}
631
632void QQuickAnimatedSprite::setFrameY(int arg)
633{
634 Q_D(QQuickAnimatedSprite);
635
636 if (d->m_sprite->m_frameY != arg) {
637 d->m_sprite->setFrameY(arg);
638 Q_EMIT frameYChanged(arg);
639 reloadImage();
640 }
641}
642
643void QQuickAnimatedSprite::setFrameRate(qreal arg)
644{
645 Q_D(QQuickAnimatedSprite);
646
647 if (d->m_sprite->m_frameRate != arg) {
648 d->m_sprite->setFrameRate(arg);
649 Q_EMIT frameRateChanged(arg);
650 if (d->m_running)
651 restart();
652 }
653}
654
655void QQuickAnimatedSprite::setFrameDuration(int arg)
656{
657 Q_D(QQuickAnimatedSprite);
658
659 if (d->m_sprite->m_frameDuration != arg) {
660 d->m_sprite->setFrameDuration(arg);
661 Q_EMIT frameDurationChanged(arg);
662 if (d->m_running)
663 restart();
664 }
665}
666
667void QQuickAnimatedSprite::resetFrameRate()
668{
669 setFrameRate(-1.0);
670}
671
672void QQuickAnimatedSprite::resetFrameDuration()
673{
674 setFrameDuration(-1);
675}
676
677void QQuickAnimatedSprite::setLoops(int arg)
678{
679 Q_D(QQuickAnimatedSprite);
680
681 if (d->m_loops != arg) {
682 d->m_loops = arg;
683 Q_EMIT loopsChanged(arg);
684 }
685}
686
687void QQuickAnimatedSprite::setCurrentFrame(int arg) //TODO-C: Probably only works when paused
688{
689 Q_D(QQuickAnimatedSprite);
690
691 if (d->m_curFrame != arg) {
692 d->m_curFrame = arg;
693 Q_EMIT currentFrameChanged(arg); //TODO-C Only emitted on manual advance!
694 update();
695 }
696}
697
698void QQuickAnimatedSprite::setFinishBehavior(FinishBehavior arg)
699{
700 Q_D(QQuickAnimatedSprite);
701
702 if (d->m_finishBehavior != arg) {
703 d->m_finishBehavior = arg;
704 Q_EMIT finishBehaviorChanged(arg);
705 }
706}
707
708void QQuickAnimatedSprite::createEngine()
709{
710 Q_D(QQuickAnimatedSprite);
711
712 if (d->m_spriteEngine)
713 delete d->m_spriteEngine;
714 QList<QQuickSprite*> spriteList;
715 spriteList << d->m_sprite;
716 d->m_spriteEngine = new QQuickSpriteEngine(QList<QQuickSprite*>(spriteList), this);
717 d->m_spriteEngine->startAssemblingImage();
718 reset();
719}
720
721QSGSpriteNode* QQuickAnimatedSprite::initNode()
722{
723 Q_D(QQuickAnimatedSprite);
724
725 if (!d->m_spriteEngine) {
726 qmlWarning(me: this) << "No sprite engine...";
727 return nullptr;
728 } else if (d->m_spriteEngine->status() == QQuickPixmap::Null) {
729 d->m_spriteEngine->startAssemblingImage();
730 maybeUpdate();//Schedule another update, where we will check again
731 return nullptr;
732 } else if (d->m_spriteEngine->status() == QQuickPixmap::Loading) {
733 maybeUpdate();//Schedule another update, where we will check again
734 return nullptr;
735 }
736
737 QImage image = d->m_spriteEngine->assembledImage(maxSize: d->sceneGraphRenderContext()->maxTextureSize()); //Engine prints errors if there are any
738 if (image.isNull())
739 return nullptr;
740
741 // If frameWidth or frameHeight are not explicitly set, frameWidth
742 // will be set to the width of the image divided by the number of frames,
743 // and frameHeight will be set to the height of the image.
744 // In this case, QQuickAnimatedSprite currently won't emit frameWidth/HeightChanged
745 // at all, so we have to do this here, as it's the only place where assembledImage()
746 // is called (which calculates the "implicit" frameWidth/Height.
747 // In addition, currently the "implicit" frameWidth/Height are only calculated once,
748 // even after changing to a different source.
749 setImplicitWidth(frameWidth());
750 setImplicitHeight(frameHeight());
751
752 QSGSpriteNode *node = d->sceneGraphContext()->createSpriteNode();
753
754 d->m_sheetSize = QSize(image.size() / image.devicePixelRatio());
755 node->setTexture(window()->createTextureFromImage(image));
756 d->m_spriteEngine->start(index: 0);
757 node->setTime(0.0f);
758 node->setSourceA(QPoint(d->m_spriteEngine->spriteX(), d->m_spriteEngine->spriteY()));
759 node->setSourceB(QPoint(d->m_spriteEngine->spriteX(), d->m_spriteEngine->spriteY()));
760 node->setSpriteSize(QSize(d->m_spriteEngine->spriteWidth(), d->m_spriteEngine->spriteHeight()));
761 node->setSheetSize(d->m_sheetSize);
762 node->setSize(QSizeF(width(), height()));
763 return node;
764}
765
766void QQuickAnimatedSprite::reset()
767{
768 Q_D(QQuickAnimatedSprite);
769 d->m_pleaseReset = true;
770 maybeUpdate();
771}
772
773QSGNode *QQuickAnimatedSprite::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
774{
775 Q_D(QQuickAnimatedSprite);
776
777 if (d->m_pleaseReset) {
778 delete oldNode;
779
780 oldNode = nullptr;
781 d->m_pleaseReset = false;
782 }
783
784 QSGSpriteNode *node = static_cast<QSGSpriteNode *>(oldNode);
785 if (!node)
786 node = initNode();
787
788 if (node)
789 prepareNextFrame(node);
790
791 if (d->m_running && !d->m_paused)
792 maybeUpdate();
793
794 return node;
795}
796
797void QQuickAnimatedSprite::prepareNextFrame(QSGSpriteNode *node)
798{
799 Q_D(QQuickAnimatedSprite);
800
801 int timeInt = d->m_timestamp.elapsed() + d->m_pauseOffset;
802 qreal time = timeInt / 1000.;
803
804 int frameAt;
805 qreal progress = 0.0;
806 int lastFrame = d->m_curFrame;
807 if (d->m_running && !d->m_paused) {
808 const int nColumns = d->m_sheetSize.width() / d->m_spriteEngine->spriteWidth();
809 //Advance State (keeps time for psuedostates)
810 d->m_spriteEngine->updateSprites(time: timeInt);
811
812 //Advance AnimatedSprite
813 qreal animT = d->m_spriteEngine->spriteStart()/1000.0;
814 const int frameCountInRow = d->m_spriteEngine->spriteFrames();
815 const qreal frameDuration = d->m_spriteEngine->spriteDuration() / frameCountInRow;
816 if (frameDuration > 0) {
817 qreal frame = (time - animT)/(frameDuration / 1000.0);
818 bool lastLoop = d->m_loops > 0 && d->m_curLoop == d->m_loops-1;
819 //don't visually interpolate for the last frame of the last loop
820 const int max = lastLoop ? frameCountInRow - 1 : frameCountInRow;
821 frame = qBound(min: qreal(0.0), val: frame, max: qreal(max));
822 double intpart;
823 progress = std::modf(x: frame,iptr: &intpart);
824 frameAt = (int)intpart;
825 const int rowIndex = d->m_spriteEngine->spriteY()/frameHeight();
826 const int newFrame = rowIndex * nColumns + frameAt;
827 if (d->m_curFrame > newFrame) //went around
828 d->m_curLoop++;
829 d->m_curFrame = newFrame;
830 } else {
831 d->m_curFrame++;
832 if (d->m_curFrame >= d->m_spriteEngine->maxFrames()) { // maxFrames: total number of frames including all rows
833 d->m_curFrame = 0;
834 d->m_curLoop++;
835 }
836 frameAt = d->m_curFrame % nColumns;
837 if (frameAt == 0)
838 d->m_spriteEngine->advance();
839 progress = 0;
840 }
841 if (d->m_loops > 0 && d->m_curLoop >= d->m_loops) {
842 if (d->m_finishBehavior == FinishAtInitialFrame)
843 frameAt = 0;
844 else
845 frameAt = frameCount() - 1;
846 d->m_curFrame = frameAt;
847 d->m_running = false;
848 emit runningChanged(arg: false);
849 emit finished();
850 maybeUpdate();
851 }
852 } else {
853 frameAt = d->m_curFrame;
854 }
855 if (d->m_curFrame != lastFrame) {
856 if (isCurrentFrameChangedConnected())
857 emit currentFrameChanged(arg: d->m_curFrame);
858 maybeUpdate();
859 }
860
861 int frameCount = d->m_spriteEngine->spriteFrames();
862 bool reverse = d->m_spriteEngine->sprite()->reverse();
863 if (reverse)
864 frameAt = (frameCount - 1) - frameAt;
865
866 int w = d->m_spriteEngine->spriteWidth();
867 int h = d->m_spriteEngine->spriteHeight();
868 int x1;
869 int y1;
870 if (d->m_paused) {
871 int spriteY = d->m_spriteEngine->spriteY();
872 if (reverse) {
873 int rows = d->m_spriteEngine->maxFrames() * d->m_spriteEngine->spriteWidth() / d->m_sheetSize.width();
874 spriteY -= rows * d->m_spriteEngine->spriteHeight();
875 frameAt = (frameCount - 1) - frameAt;
876 }
877
878 int position = frameAt * d->m_spriteEngine->spriteWidth() + d->m_spriteEngine->spriteX();
879 int row = position / d->m_sheetSize.width();
880
881 x1 = (position - (row * d->m_sheetSize.width()));
882 y1 = (row * d->m_spriteEngine->spriteHeight() + spriteY);
883 } else {
884 x1 = d->m_spriteEngine->spriteX() + frameAt * w;
885 y1 = d->m_spriteEngine->spriteY();
886 }
887
888 //### hard-coded 0/1 work because we are the only
889 // images in the sprite sheet (without this we cannot assume
890 // where in the sheet we begin/end).
891 int x2;
892 int y2;
893 if (reverse) {
894 if (frameAt > 0) {
895 x2 = x1 - w;
896 y2 = y1;
897 } else {
898 x2 = d->m_sheetSize.width() - w;
899 y2 = y1 - h;
900 if (y2 < 0) {
901 //the last row may not fill the entire width
902 int maxRowFrames = d->m_sheetSize.width() / d->m_spriteEngine->spriteWidth();
903 if (d->m_spriteEngine->maxFrames() % maxRowFrames)
904 x2 = ((d->m_spriteEngine->maxFrames() % maxRowFrames) - 1) * w;
905
906 y2 = d->m_sheetSize.height() - h;
907 }
908 }
909 } else {
910 if (frameAt < (frameCount-1)) {
911 x2 = x1 + w;
912 y2 = y1;
913 } else {
914 x2 = 0;
915 y2 = y1 + h;
916 if (y2 >= d->m_sheetSize.height())
917 y2 = 0;
918 }
919 }
920
921 node->setSourceA(QPoint(x1, y1));
922 node->setSourceB(QPoint(x2, y2));
923 node->setSpriteSize(QSize(w, h));
924 node->setTime(d->m_interpolate ? progress : 0.0);
925 node->setSize(QSizeF(width(), height()));
926 node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
927 node->update();
928}
929
930QT_END_NAMESPACE
931
932#include "moc_qquickanimatedsprite_p.cpp"
933

source code of qtdeclarative/src/quick/items/qquickanimatedsprite.cpp