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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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