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 "qquickitemparticle_p.h"
5#include <QtQuick/qsgnode.h>
6#include <QTimer>
7#include <QQmlComponent>
8#include <QDebug>
9
10QT_BEGIN_NAMESPACE
11
12/*!
13 \qmltype ItemParticle
14 \instantiates QQuickItemParticle
15 \inqmlmodule QtQuick.Particles
16 \inherits ParticlePainter
17 \brief For specifying a delegate to paint particles.
18 \ingroup qtquick-particles
19
20*/
21
22
23/*!
24 \qmlmethod QtQuick.Particles::ItemParticle::freeze(Item item)
25
26 Suspends the flow of time for the logical particle which \a item represents,
27 allowing you to control its movement.
28*/
29
30/*!
31 \qmlmethod QtQuick.Particles::ItemParticle::unfreeze(Item item)
32
33 Restarts the flow of time for the logical particle which \a item represents,
34 allowing it to be moved by the particle system again.
35*/
36
37/*!
38 \qmlmethod QtQuick.Particles::ItemParticle::take(Item item, bool prioritize)
39
40 Asks the ItemParticle to take over control of \a item positioning temporarily.
41 It will follow the movement of a logical particle when one is available.
42
43 By default items form a queue when waiting for a logical particle, but if
44 \a prioritize is \c true, then it will go immediately to the head of the
45 queue.
46
47 ItemParticle does not take ownership of the item, and will relinquish
48 control when the logical particle expires. Commonly at this point you will
49 want to put it back in the queue, you can do this with the below line in
50 the delegate definition:
51
52 \code
53 ItemParticle.onDetached: itemParticleInstance.take(delegateRootItem);
54 \endcode
55
56 or delete it, such as with the below line in the delegate definition:
57
58 \code
59 ItemParticle.onDetached: delegateRootItem.destroy();
60 \endcode
61*/
62
63/*!
64 \qmlmethod QtQuick.Particles::ItemParticle::give(Item item)
65
66 Orders the ItemParticle to give you control of the \a item. It will cease
67 controlling it and the item will lose its association to the logical
68 particle.
69*/
70
71/*!
72 \qmlproperty bool QtQuick.Particles::ItemParticle::fade
73
74 If true, the item will automatically be faded in and out
75 at the ends of its lifetime. If false, you will have to
76 implement any entry effect yourself.
77
78 Default is true.
79*/
80/*!
81 \qmlproperty Component QtQuick.Particles::ItemParticle::delegate
82
83 An instance of the delegate will be created for every logical particle, and
84 moved along with it. As an alternative to using delegate, you can create
85 Item instances yourself and hand them to the ItemParticle to move using the
86 \l take method.
87
88 Any delegate instances created by ItemParticle will be destroyed when
89 the logical particle expires.
90*/
91
92QQuickItemParticle::QQuickItemParticle(QQuickItem *parent) :
93 QQuickParticlePainter(parent), m_fade(true), m_lastT(0), m_activeCount(0), m_delegate(nullptr)
94{
95 setFlag(flag: QQuickItem::ItemHasContents);
96 clock = new Clock(this);
97 clock->start();
98}
99
100QQuickItemParticle::~QQuickItemParticle()
101{
102 delete clock;
103 qDeleteAll(c: m_managed);
104}
105
106void QQuickItemParticle::freeze(QQuickItem* item)
107{
108 m_stasis << item;
109}
110
111
112void QQuickItemParticle::unfreeze(QQuickItem* item)
113{
114 m_stasis.remove(value: item);
115}
116
117void QQuickItemParticle::take(QQuickItem *item, bool prioritize)
118{
119 if (prioritize)
120 m_pendingItems.push_front(t: item);
121 else
122 m_pendingItems.push_back(t: item);
123}
124
125void QQuickItemParticle::give(QQuickItem *item)
126{
127 for (auto groupId : groupIds()) {
128 for (QQuickParticleData* data : std::as_const(t&: m_system->groupData[groupId]->data)) {
129 if (data->delegate == item){
130 m_deletables << item;
131 data->delegate = nullptr;
132 m_system->groupData[groupId]->kill(d: data);
133 return;
134 }
135 }
136 }
137}
138
139void QQuickItemParticle::initialize(int gIdx, int pIdx)
140{
141 Q_UNUSED(gIdx);
142 Q_UNUSED(pIdx);
143}
144
145void QQuickItemParticle::commit(int, int)
146{
147}
148
149void QQuickItemParticle::processDeletables()
150{
151 foreach (QQuickItem* item, m_deletables){
152 if (m_fade)
153 item->setOpacity(0.);
154 item->setVisible(false);
155 QQuickItemParticleAttached* mpa;
156 if ((mpa = qobject_cast<QQuickItemParticleAttached*>(object: qmlAttachedPropertiesObject<QQuickItemParticle>(obj: item)))) {
157 if (mpa->m_parentItem != nullptr)
158 item->setParentItem(mpa->m_parentItem);
159 mpa->detach();
160 }
161 int idx = -1;
162 if ((idx = m_managed.indexOf(t: item)) != -1) {
163 m_managed.takeAt(i: idx);
164 delete item;
165 }
166 m_activeCount--;
167 }
168 m_deletables.clear();
169}
170
171void QQuickItemParticle::tick(int time)
172{
173 Q_UNUSED(time);//only needed because QTickAnimationProxy expects one
174 processDeletables();
175 for (auto groupId : groupIds()) {
176 for (QQuickParticleData* d : std::as_const(t&: m_system->groupData[groupId]->data)) {
177 if (!d->delegate && d->t != -1 && d->stillAlive(system: m_system)) {
178 QQuickItem* parentItem = nullptr;
179 if (!m_pendingItems.isEmpty()){
180 QQuickItem *item = m_pendingItems.front();
181 m_pendingItems.pop_front();
182 parentItem = item->parentItem();
183 d->delegate = item;
184 }else if (m_delegate){
185 d->delegate = qobject_cast<QQuickItem*>(o: m_delegate->create(context: qmlContext(this)));
186 if (d->delegate)
187 m_managed << d->delegate;
188 }
189 if (d && d->delegate){//###Data can be zero if creating an item leads to a reset - this screws things up.
190 d->delegate->setX(d->curX(particleSystem: m_system) - d->delegate->width() / 2); //TODO: adjust for system?
191 d->delegate->setY(d->curY(particleSystem: m_system) - d->delegate->height() / 2);
192 QQuickItemParticleAttached* mpa = qobject_cast<QQuickItemParticleAttached*>(object: qmlAttachedPropertiesObject<QQuickItemParticle>(obj: d->delegate));
193 if (mpa){
194 mpa->m_parentItem = parentItem;
195 mpa->m_mp = this;
196 mpa->attach();
197 }
198 d->delegate->setParentItem(this);
199 if (m_fade)
200 d->delegate->setOpacity(0.);
201 d->delegate->setVisible(false);//Will be set to true when we prepare the next frame
202 m_activeCount++;
203 }
204 }
205 }
206 }
207}
208
209void QQuickItemParticle::reset()
210{
211 QQuickParticlePainter::reset();
212
213 // delete all managed items which had their logical particles cleared
214 // but leave it alone if the logical particle is maintained
215 QSet<QQuickItem*> lost = QSet<QQuickItem*>(m_managed.cbegin(), m_managed.cend());
216 for (auto groupId : groupIds()) {
217 for (QQuickParticleData* d : std::as_const(t&: m_system->groupData[groupId]->data)) {
218 lost.remove(value: d->delegate);
219 }
220 }
221 m_deletables.unite(other: lost);
222 //TODO: This doesn't yet handle calling detach on taken particles in the system reset case
223 processDeletables();
224}
225
226
227QSGNode* QQuickItemParticle::updatePaintNode(QSGNode* n, UpdatePaintNodeData* d)
228{
229 //Dummy update just to get painting tick
230 if (m_pleaseReset)
231 m_pleaseReset = false;
232
233 prepareNextFrame();
234
235 update();//Get called again
236 if (n)
237 n->markDirty(bits: QSGNode::DirtyMaterial);
238 return QQuickItem::updatePaintNode(n,d);
239}
240
241void QQuickItemParticle::prepareNextFrame()
242{
243 if (!m_system)
244 return;
245 qint64 timeStamp = m_system->systemSync(p: this);
246 qreal curT = timeStamp/1000.0;
247 qreal dt = curT - m_lastT;
248 m_lastT = curT;
249 if (!m_activeCount)
250 return;
251
252 //TODO: Size, better fade?
253 for (auto groupId : groupIds()) {
254 for (QQuickParticleData* data : std::as_const(t&: m_system->groupData[groupId]->data)) {
255 QQuickItem* item = data->delegate;
256 if (!item)
257 continue;
258 float t = ((timeStamp / 1000.0f) - data->t) / data->lifeSpan;
259 if (m_stasis.contains(value: item)) {
260 data->t += dt;//Stasis effect
261 continue;
262 }
263 if (t >= 1.0f){//Usually happens from load
264 m_deletables << item;
265 data->delegate = nullptr;
266 }else{//Fade
267 data->delegate->setVisible(true);
268 if (m_fade){
269 float o = 1.f;
270 if (t <0.2f)
271 o = t * 5;
272 if (t > 0.8f)
273 o = (1-t)*5;
274 item->setOpacity(o);
275 }
276 }
277 item->setX(data->curX(particleSystem: m_system) - item->width() / 2 - m_systemOffset.x());
278 item->setY(data->curY(particleSystem: m_system) - item->height() / 2 - m_systemOffset.y());
279 }
280 }
281}
282
283QQuickItemParticleAttached *QQuickItemParticle::qmlAttachedProperties(QObject *object)
284{
285 return new QQuickItemParticleAttached(object);
286}
287
288QT_END_NAMESPACE
289
290#include "moc_qquickitemparticle_p.cpp"
291

source code of qtdeclarative/src/particles/qquickitemparticle.cpp