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