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 | |
20 | QT_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? |
81 | QQuickSpriteSequence::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 | |
89 | void 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 | |
97 | void 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 | |
108 | void 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 | |
117 | void 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 | |
126 | QQmlListProperty<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 | |
134 | bool QQuickSpriteSequence::running() const |
135 | { |
136 | Q_D(const QQuickSpriteSequence); |
137 | return d->m_running; |
138 | } |
139 | |
140 | bool QQuickSpriteSequence::interpolate() const |
141 | { |
142 | Q_D(const QQuickSpriteSequence); |
143 | return d->m_interpolate; |
144 | } |
145 | |
146 | QString QQuickSpriteSequence::goalSprite() const |
147 | { |
148 | Q_D(const QQuickSpriteSequence); |
149 | return d->m_goalState; |
150 | } |
151 | |
152 | QString QQuickSpriteSequence::currentSprite() const |
153 | { |
154 | Q_D(const QQuickSpriteSequence); |
155 | return d->m_curState; |
156 | } |
157 | |
158 | void 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 | |
174 | QSGSpriteNode *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 | |
212 | void QQuickSpriteSequence::reset() |
213 | { |
214 | Q_D(QQuickSpriteSequence); |
215 | d->m_pleaseReset = true; |
216 | } |
217 | |
218 | QSGNode *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 | |
243 | void 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 | |
298 | QT_END_NAMESPACE |
299 | |
300 | #include "moc_qquickspritesequence_p.cpp" |
301 | |