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