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 "qquickspritesequence_p.h"
5#include "qquickspritesequence_p_p.h"
6#include "qquicksprite_p.h"
7#include "qquickspriteengine_p.h"
8#include <QtQuick/private/qsgcontext_p.h>
9#include <private/qsgadaptationlayer_p.h>
10#include <QtQuick/qsgnode.h>
11#include <QtQuick/qsgtexturematerial.h>
12#include <QtQuick/qsgtexture.h>
13#include <QtQuick/qquickwindow.h>
14#include <QtQml/qqmlinfo.h>
15#include <QFile>
16#include <cmath>
17#include <qmath.h>
18#include <QDebug>
19
20QT_BEGIN_NAMESPACE
21
22/*!
23 \qmltype SpriteSequence
24 \instantiates QQuickSpriteSequence
25 \inqmlmodule QtQuick
26 \ingroup qtquick-visual-utility
27 \inherits Item
28 \brief Draws a sprite animation.
29
30 SpriteSequence renders and controls a list of animations defined
31 by \l Sprite types.
32
33 For full details, see the \l{Sprite Animations} overview.
34 \sa {Sprite animations with SpriteSequence}
35*/
36/*!
37 \qmlproperty bool QtQuick::SpriteSequence::running
38
39 Whether the sprite is animating or not.
40
41 Default is \c true.
42*/
43/*!
44 \qmlproperty bool QtQuick::SpriteSequence::interpolate
45
46 If \c true, interpolation will occur between sprite frames to make the
47 animation appear smoother.
48
49 Default is \c true.
50*/
51/*!
52 \qmlproperty string QtQuick::SpriteSequence::currentSprite
53
54 The name of the \l Sprite that is currently animating.
55*/
56/*!
57 \qmlproperty string QtQuick::SpriteSequence::goalSprite
58
59 The name of the \l Sprite that the animation should move to.
60
61 Sprite states have defined durations and transitions between them; setting \c goalSprite
62 will cause it to disregard any path weightings (including \c 0) and head down the path
63 that will reach the \c goalSprite quickest (fewest animations). It will pass through
64 intermediate states on that path, and animate them for their duration.
65
66 If it is possible to return to the \c goalSprite from the starting point of the \c goalSprite,
67 it will continue to do so until \c goalSprite is set to \c "" or an unreachable state.
68*/
69/*! \qmlmethod QtQuick::SpriteSequence::jumpTo(string sprite)
70
71 This function causes the SpriteSequence to jump to the specified \a sprite immediately;
72 intermediate sprites are not played.
73*/
74/*!
75 \qmlproperty list<Sprite> QtQuick::SpriteSequence::sprites
76
77 The sprite or sprites to draw. Sprites will be scaled to the size of this item.
78*/
79
80//TODO: Implicitly size element to size of first sprite?
81QQuickSpriteSequence::QQuickSpriteSequence(QQuickItem *parent) :
82 QQuickItem(*(new QQuickSpriteSequencePrivate), parent)
83{
84 setFlag(flag: ItemHasContents);
85 connect(sender: this, SIGNAL(runningChanged(bool)),
86 receiver: this, SLOT(update()));
87}
88
89void QQuickSpriteSequence::jumpTo(const QString &sprite)
90{
91 Q_D(QQuickSpriteSequence);
92 if (!d->m_spriteEngine)
93 return;
94 d->m_spriteEngine->setGoal(state: d->m_spriteEngine->stateIndex(s: sprite), sprite: 0, jump: true);
95}
96
97void QQuickSpriteSequence::setGoalSprite(const QString &sprite)
98{
99 Q_D(QQuickSpriteSequence);
100 if (d->m_goalState != sprite){
101 d->m_goalState = sprite;
102 emit goalSpriteChanged(arg: sprite);
103 if (d->m_spriteEngine)
104 d->m_spriteEngine->setGoal(state: d->m_spriteEngine->stateIndex(s: sprite));
105 }
106}
107
108void QQuickSpriteSequence::setRunning(bool arg)
109{
110 Q_D(QQuickSpriteSequence);
111 if (d->m_running != arg) {
112 d->m_running = arg;
113 Q_EMIT runningChanged(arg);
114 }
115}
116
117void QQuickSpriteSequence::setInterpolate(bool arg)
118{
119 Q_D(QQuickSpriteSequence);
120 if (d->m_interpolate != arg) {
121 d->m_interpolate = arg;
122 Q_EMIT interpolateChanged(arg);
123 }
124}
125
126QQmlListProperty<QQuickSprite> QQuickSpriteSequence::sprites()
127{
128 Q_D(QQuickSpriteSequence);
129 return QQmlListProperty<QQuickSprite>(this, &d->m_sprites,
130 spriteAppend, spriteCount, spriteAt,
131 spriteClear, spriteReplace, spriteRemoveLast);
132}
133
134bool QQuickSpriteSequence::running() const
135{
136 Q_D(const QQuickSpriteSequence);
137 return d->m_running;
138}
139
140bool QQuickSpriteSequence::interpolate() const
141{
142 Q_D(const QQuickSpriteSequence);
143 return d->m_interpolate;
144}
145
146QString QQuickSpriteSequence::goalSprite() const
147{
148 Q_D(const QQuickSpriteSequence);
149 return d->m_goalState;
150}
151
152QString QQuickSpriteSequence::currentSprite() const
153{
154 Q_D(const QQuickSpriteSequence);
155 return d->m_curState;
156}
157
158void QQuickSpriteSequence::createEngine()
159{
160 Q_D(QQuickSpriteSequence);
161 //TODO: delay until component complete
162 if (d->m_spriteEngine)
163 delete d->m_spriteEngine;
164 if (d->m_sprites.size()) {
165 d->m_spriteEngine = new QQuickSpriteEngine(d->m_sprites, this);
166 if (!d->m_goalState.isEmpty())
167 d->m_spriteEngine->setGoal(state: d->m_spriteEngine->stateIndex(s: d->m_goalState));
168 } else {
169 d->m_spriteEngine = nullptr;
170 }
171 reset();
172}
173
174QSGSpriteNode *QQuickSpriteSequence::initNode()
175{
176 Q_D(QQuickSpriteSequence);
177
178 if (!d->m_spriteEngine) {
179 qmlWarning(me: this) << "No sprite engine...";
180 return nullptr;
181 } else if (d->m_spriteEngine->status() == QQuickPixmap::Null) {
182 d->m_spriteEngine->startAssemblingImage();
183 update();//Schedule another update, where we will check again
184 return nullptr;
185 } else if (d->m_spriteEngine->status() == QQuickPixmap::Loading) {
186 update();//Schedule another update, where we will check again
187 return nullptr;
188 }
189
190 QImage image = d->m_spriteEngine->assembledImage(maxSize: d->sceneGraphRenderContext()->maxTextureSize());
191 if (image.isNull())
192 return nullptr;
193
194 QSGSpriteNode *node = d->sceneGraphContext()->createSpriteNode();
195
196 d->m_sheetSize = QSize(image.size() / image.devicePixelRatio());
197 node->setTexture(window()->createTextureFromImage(image));
198 d->m_spriteEngine->start(index: 0);
199 node->setTime(0.0f);
200 node->setSourceA(QPoint(d->m_spriteEngine->spriteX(), d->m_spriteEngine->spriteY()));
201 node->setSourceB(QPoint(d->m_spriteEngine->spriteX(), d->m_spriteEngine->spriteY()));
202 node->setSpriteSize(QSize(d->m_spriteEngine->spriteWidth(), d->m_spriteEngine->spriteHeight()));
203 node->setSheetSize(d->m_sheetSize);
204 node->setSize(QSizeF(width(), height()));
205
206 d->m_curState = d->m_spriteEngine->state(idx: d->m_spriteEngine->curState())->name();
207 emit currentSpriteChanged(arg: d->m_curState);
208 d->m_timestamp.start();
209 return node;
210}
211
212void QQuickSpriteSequence::reset()
213{
214 Q_D(QQuickSpriteSequence);
215 d->m_pleaseReset = true;
216}
217
218QSGNode *QQuickSpriteSequence::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
219{
220 Q_D(QQuickSpriteSequence);
221
222 if (d->m_pleaseReset) {
223 delete oldNode;
224
225 oldNode = nullptr;
226 d->m_pleaseReset = false;
227 }
228
229 QSGSpriteNode *node = static_cast<QSGSpriteNode *>(oldNode);
230 if (!node)
231 node = initNode();
232
233 if (node)
234 prepareNextFrame(node);
235
236 if (d->m_running) {
237 update();
238 }
239
240 return node;
241}
242
243void QQuickSpriteSequence::prepareNextFrame(QSGSpriteNode *node)
244{
245 Q_D(QQuickSpriteSequence);
246
247 uint timeInt = d->m_timestamp.elapsed();
248 qreal time = timeInt / 1000.;
249
250 //Advance State
251 d->m_spriteEngine->updateSprites(time: timeInt);
252 if (d->m_curStateIdx != d->m_spriteEngine->curState()) {
253 d->m_curStateIdx = d->m_spriteEngine->curState();
254 d->m_curState = d->m_spriteEngine->state(idx: d->m_spriteEngine->curState())->name();
255 emit currentSpriteChanged(arg: d->m_curState);
256 d->m_curFrame= -1;
257 }
258
259 //Advance Sprite
260 qreal animT = d->m_spriteEngine->spriteStart()/1000.0;
261 qreal frameCount = d->m_spriteEngine->spriteFrames();
262 qreal frameDuration = d->m_spriteEngine->spriteDuration()/frameCount;
263 double frameAt;
264 qreal progress;
265 if (frameDuration > 0) {
266 qreal frame = (time - animT)/(frameDuration / 1000.0);
267 frame = qBound(min: qreal(0.0), val: frame, max: frameCount - qreal(1.0));//Stop at count-1 frames until we have between anim interpolation
268 progress = std::modf(x: frame,iptr: &frameAt);
269 } else {
270 d->m_curFrame++;
271 if (d->m_curFrame >= frameCount){
272 d->m_curFrame = 0;
273 d->m_spriteEngine->advance();
274 }
275 frameAt = d->m_curFrame;
276 progress = 0;
277 }
278 if (d->m_spriteEngine->sprite()->reverse())
279 frameAt = (d->m_spriteEngine->spriteFrames() - 1) - frameAt;
280 int y = d->m_spriteEngine->spriteY();
281 int w = d->m_spriteEngine->spriteWidth();
282 int h = d->m_spriteEngine->spriteHeight();
283 int x1 = d->m_spriteEngine->spriteX();
284 x1 += frameAt * w;
285 int x2 = x1;
286 if (frameAt < (frameCount-1))
287 x2 += w;
288
289 node->setSourceA(QPoint(x1, y));
290 node->setSourceB(QPoint(x2, y));
291 node->setSpriteSize(QSize(w, h));
292 node->setTime(d->m_interpolate ? progress : 0.0);
293 node->setSize(QSizeF(width(), height()));
294 node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
295 node->update();
296}
297
298QT_END_NAMESPACE
299
300#include "moc_qquickspritesequence_p.cpp"
301

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