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

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