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