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