1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dloader_p.h"
5
6#include "qquick3dobject_p.h"
7
8#include <QtQml/qqmlinfo.h>
9
10#include <private/qqmlengine_p.h>
11#include <private/qqmlglobal_p.h>
12
13#include <private/qqmlcomponent_p.h>
14#include <private/qqmlincubator_p.h>
15
16QT_BEGIN_NAMESPACE
17
18void QQuick3DLoaderIncubator::statusChanged(QQmlIncubator::Status status)
19{
20 m_loader->incubatorStateChanged(status);
21}
22
23void QQuick3DLoaderIncubator::setInitialState(QObject *o)
24{
25 m_loader->setInitialState(o);
26}
27
28/*!
29 \qmltype Loader3D
30 \inqmlmodule QtQuick3D
31 \inherits Node
32
33 \brief Allows dynamic loading of a 3D subtree from a URL or Component.
34
35 Loader3D is used to dynamically load QML components for Qt Quick 3D.
36
37 Loader3D can load a
38 QML file (using the \l source property) or a \l Component object (using
39 the \l sourceComponent property). It is useful for delaying the creation
40 of a component until it is required: for example, when a component should
41 be created on demand, or when a component should not be created
42 unnecessarily for performance reasons.
43
44 \note Loader3D works the same way as \l Loader. The difference between the
45 two is that \l Loader provides a way to dynamically load objects that inherit
46 \l Item, whereas Loader3D provides a way to load objects that inherit \l Object3D
47 and is part of a 3D scene.
48*/
49
50QQuick3DLoader::QQuick3DLoader(QQuick3DNode *parent)
51 : QQuick3DNode(parent)
52 , m_item(nullptr)
53 , m_object(nullptr)
54 , m_itemContext(nullptr)
55 , m_incubator(nullptr)
56 , m_active(true)
57 , m_loadingFromSource(false)
58 , m_asynchronous(false)
59{
60}
61
62QQuick3DLoader::~QQuick3DLoader()
63{
64 clear();
65 delete m_incubator;
66 m_incubator = nullptr;
67}
68
69/*!
70 \qmlproperty bool QtQuick3D::Loader3D::active
71 This property is \c true if the Loader3D is currently active.
72 The default value for this property is \c true.
73
74 If the Loader3D is inactive, changing the \l source or \l sourceComponent
75 will not cause the item to be instantiated until the Loader3D is made active.
76
77 Setting the value to inactive will cause any \l item loaded by the loader
78 to be released, but will not affect the \l source or \l sourceComponent.
79
80 The \l status of an inactive loader is always \c Null.
81
82 \sa source, sourceComponent
83 */
84
85bool QQuick3DLoader::active() const
86{
87 return m_active;
88}
89
90void QQuick3DLoader::setActive(bool newVal)
91{
92 if (m_active == newVal)
93 return;
94
95 m_active = newVal;
96 if (newVal) {
97 if (m_loadingFromSource) {
98 loadFromSource();
99 } else {
100 loadFromSourceComponent();
101 }
102 } else {
103 // cancel any current incubation
104 if (m_incubator) {
105 m_incubator->clear();
106 delete m_itemContext;
107 m_itemContext = nullptr;
108 }
109
110 // Prevent any bindings from running while waiting for deletion. Without
111 // this we may get transient errors from use of 'parent', for example.
112 QQmlContext *context = qmlContext(m_object);
113 if (context)
114 QQmlContextData::get(context)->clearContextRecursively();
115
116 if (m_item) {
117 // We can't delete immediately because our item may have triggered
118 // the Loader to load a different item.
119 m_item->setParentItem(nullptr);
120 m_item->setVisible(false);
121 m_item = nullptr;
122 }
123 if (m_object) {
124 m_object->deleteLater();
125 m_object = nullptr;
126 emit itemChanged();
127 }
128 emit statusChanged();
129 }
130 emit activeChanged();
131}
132
133void QQuick3DLoader::setSource(QQmlV4FunctionPtr args)
134{
135 bool ipvError = false;
136 args->setReturnValue(QV4::Encode::undefined());
137 QV4::Scope scope(args->v4engine());
138 QV4::ScopedValue ipv(scope, extractInitialPropertyValues(args, error: &ipvError));
139 if (ipvError)
140 return;
141
142 clear();
143 QUrl sourceUrl = resolveSourceUrl(args);
144 if (!ipv->isUndefined()) {
145 disposeInitialPropertyValues();
146 m_initialPropertyValues.set(engine: args->v4engine(), value: ipv);
147 }
148 m_qmlCallingContext.set(engine: scope.engine, obj: scope.engine->qmlContext());
149
150 setSource(sourceUrl, needsClear: false); // already cleared and set ipv above.
151}
152
153/*!
154 \qmlproperty url QtQuick3D::Loader3D::source
155 This property holds the URL of the QML component to instantiate.
156
157 To unload the currently loaded object, set this property to an empty string,
158 or set \l sourceComponent to \c undefined. Setting \c source to a
159 new URL will also cause the item created by the previous URL to be unloaded.
160
161 \sa sourceComponent, status, progress
162*/
163
164QUrl QQuick3DLoader::source() const
165{
166 return m_source;
167}
168
169void QQuick3DLoader::setSource(const QUrl &url)
170{
171 setSource(sourceUrl: url, needsClear: true);
172}
173
174/*!
175 \qmlproperty Component QtQuick3D::Loader3D::sourceComponent
176 This property holds the \l{Component} to instantiate.
177
178 \qml
179 Item {
180 Component {
181 id: redCube
182 Model {
183 source: "#Cube"
184 materials: DefaultMaterial {
185 diffuseColor: "red"
186 }
187 }
188 }
189
190 Loader3D { sourceComponent: redCube }
191 Loader3D { sourceComponent: redCube; x: 10 }
192 }
193 \endqml
194
195 To unload the currently loaded object, set this property to \c undefined.
196
197 \sa source, progress
198*/
199
200/*!
201 \qmlmethod object QtQuick3D::Loader3D::setSource(url source, object properties)
202
203 Creates an object instance of the given \a source component that will have
204 the given \a properties. The \a properties argument is optional. The instance
205 will be accessible via the \l item property once loading and instantiation
206 is complete.
207
208 If the \l active property is \c false at the time when this function is called,
209 the given \a source component will not be loaded but the \a source and initial
210 \a properties will be cached. When the loader is made \l active, an instance of
211 the \a source component will be created with the initial \a properties set.
212
213 Setting the initial property values of an instance of a component in this manner
214 will \b{not} trigger any associated \l{Behavior}s.
215
216 Note that the cached \a properties will be cleared if the \l source or \l sourceComponent
217 is changed after calling this function but prior to setting the loader \l active.
218
219 \sa source, active
220*/
221
222QQmlComponent *QQuick3DLoader::sourceComponent() const
223{
224 return m_component;
225}
226
227void QQuick3DLoader::setSourceComponent(QQmlComponent *comp)
228{
229 if (comp == m_component)
230 return;
231
232 clear();
233
234 m_component.setObject(obj: comp, parent: this);
235 m_loadingFromSource = false;
236
237 if (m_active)
238 loadFromSourceComponent();
239 else
240 emit sourceComponentChanged();
241}
242
243void QQuick3DLoader::resetSourceComponent()
244{
245 setSourceComponent(nullptr);
246}
247
248/*!
249 \qmlproperty enumeration QtQuick3D::Loader3D::status
250 \readonly
251
252 This property holds the status of QML loading. It can be one of:
253
254 \value Loader3D.Null The loader is inactive or no QML source has been set.
255 \value Loader3D.Ready The QML source has been loaded.
256 \value Loader3D.Loading The QML source is currently being loaded.
257 \value Loader3D.Error An error occurred while loading the QML source.
258
259 Use this status to provide an update or respond to the status change in some way.
260 For example, you could:
261
262 \list
263 \li Trigger a state change:
264 \qml
265 State { name: 'loaded'; when: loader.status == Loader3D.Ready }
266 \endqml
267
268 \li Implement an \c onStatusChanged signal handler:
269 \qml
270 Loader3D {
271 id: loader
272 onStatusChanged: if (loader.status == Loader3D.Ready) console.log('Loaded')
273 }
274 \endqml
275
276 \li Bind to the status value:
277 \qml
278 Text { text: loader.status == Loader3D.Ready ? 'Loaded' : 'Not loaded' }
279 \endqml
280 \endlist
281
282 Note that if the source is a local file, the status will initially be Ready (or Error). While
283 there will be no onStatusChanged signal in that case, the onLoaded will still be invoked.
284
285 \sa progress
286*/
287
288QQuick3DLoader::Status QQuick3DLoader::status() const
289{
290 if (!m_active)
291 return Null;
292
293 if (m_component) {
294 switch (m_component->status()) {
295 case QQmlComponent::Loading:
296 return Loading;
297 case QQmlComponent::Error:
298 return Error;
299 case QQmlComponent::Null:
300 return Null;
301 default:
302 break;
303 }
304 }
305
306 if (m_incubator) {
307 switch (m_incubator->status()) {
308 case QQmlIncubator::Loading:
309 return Loading;
310 case QQmlIncubator::Error:
311 return Error;
312 default:
313 break;
314 }
315 }
316
317 if (m_object)
318 return Ready;
319
320 return m_source.isEmpty() ? Null : Error;
321}
322
323/*!
324 \qmlsignal QtQuick3D::Loader3D::loaded()
325
326 This signal is emitted when the \l status becomes \c Loader3D.Ready, or on successful
327 initial load.
328
329 The corresponding handler is \c onLoaded.
330*/
331
332
333/*!
334 \qmlproperty real QtQuick3D::Loader3D::progress
335 \readonly
336
337 This property holds the progress of loading QML data from the network, from
338 0.0 (nothing loaded) to 1.0 (finished). Most QML files are quite small, so
339 this value will rapidly change from 0 to 1.
340
341 \sa status
342*/
343
344qreal QQuick3DLoader::progress() const
345{
346
347 if (m_object)
348 return 1.0;
349
350 if (m_component)
351 return m_component->progress();
352
353 return 0.0;
354}
355
356/*!
357\qmlproperty bool QtQuick3D::Loader3D::asynchronous
358
359This property holds whether the component will be instantiated asynchronously.
360By default it is \c false.
361
362When used in conjunction with the \l source property, loading and compilation
363will also be performed in a background thread.
364
365Loading asynchronously creates the objects declared by the component
366across multiple frames, and reduces the
367likelihood of glitches in animation. When loading asynchronously the status
368will change to Loader3D.Loading. Once the entire component has been created, the
369\l item will be available and the status will change to Loader.Ready.
370
371Changing the value of this property to \c false while an asynchronous load is in
372progress will force immediate, synchronous completion. This allows beginning an
373asynchronous load and then forcing completion if the Loader3D content must be
374accessed before the asynchronous load has completed.
375
376To avoid seeing the items loading progressively set \c visible appropriately, e.g.
377
378\code
379Loader3D {
380 source: "mycomponent.qml"
381 asynchronous: true
382 visible: status == Loader3D.Ready
383}
384\endcode
385
386Note that this property affects object instantiation only; it is unrelated to
387loading a component asynchronously via a network.
388*/
389
390bool QQuick3DLoader::asynchronous() const
391{
392 return m_asynchronous;
393}
394
395void QQuick3DLoader::setAsynchronous(bool a)
396{
397 if (m_asynchronous == a)
398 return;
399
400 m_asynchronous = a;
401
402 if (!m_asynchronous && isComponentComplete() && m_active) {
403 if (m_loadingFromSource && m_component && m_component->isLoading()) {
404 // Force a synchronous component load
405 QUrl currentSource = m_source;
406 clear();
407 m_source = currentSource;
408 loadFromSource();
409 } else if (m_incubator && m_incubator->isLoading()) {
410 m_incubator->forceCompletion();
411 }
412 }
413
414 emit asynchronousChanged();
415}
416
417/*!
418 \qmlproperty object QtQuick3D::Loader3D::item
419 \readonly
420 This property holds the top-level object that is currently loaded.
421*/
422QObject *QQuick3DLoader::item() const
423{
424 return m_object;
425}
426
427void QQuick3DLoader::componentComplete()
428{
429 QQuick3DNode::componentComplete();
430 if (active()) {
431 if (m_loadingFromSource)
432 createComponent();
433 load();
434 }
435}
436
437void QQuick3DLoader::sourceLoaded()
438{
439 if (!m_component || !m_component->errors().isEmpty()) {
440 if (m_component)
441 QQmlEnginePrivate::warning(qmlEngine(this), m_component->errors());
442 if (m_loadingFromSource)
443 emit sourceChanged();
444 else
445 emit sourceComponentChanged();
446 emit statusChanged();
447 emit progressChanged();
448 emit itemChanged(); //Like clearing source, emit itemChanged even if previous item was also null
449 disposeInitialPropertyValues(); // cleanup
450 return;
451 }
452
453 QQmlContext *creationContext = m_component->creationContext();
454 if (!creationContext) creationContext = qmlContext(this);
455 m_itemContext = new QQmlContext(creationContext);
456 m_itemContext->setContextObject(this);
457
458 delete m_incubator;
459 m_incubator = new QQuick3DLoaderIncubator(this, m_asynchronous ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested);
460
461 m_component->create(*m_incubator, context: m_itemContext);
462
463 if (m_incubator && m_incubator->status() == QQmlIncubator::Loading)
464 emit statusChanged();
465}
466
467void QQuick3DLoader::setSource(const QUrl &sourceUrl, bool needsClear)
468{
469 if (m_source == sourceUrl)
470 return;
471
472 if (needsClear)
473 clear();
474
475 m_source = sourceUrl;
476 m_loadingFromSource = true;
477
478 if (m_active)
479 loadFromSource();
480 else
481 emit sourceChanged();
482}
483
484void QQuick3DLoader::loadFromSource()
485{
486 if (m_source.isEmpty()) {
487 emit sourceChanged();
488 emit statusChanged();
489 emit progressChanged();
490 emit itemChanged();
491 return;
492 }
493
494 if (isComponentComplete()) {
495 if (!m_component)
496 createComponent();
497 load();
498 }
499}
500
501void QQuick3DLoader::loadFromSourceComponent()
502{
503 if (!m_component) {
504 emit sourceComponentChanged();
505 emit statusChanged();
506 emit progressChanged();
507 emit itemChanged();
508 return;
509 }
510
511 if (isComponentComplete())
512 load();
513}
514
515void QQuick3DLoader::clear()
516{
517 disposeInitialPropertyValues();
518
519 if (m_incubator)
520 m_incubator->clear();
521
522 delete m_itemContext;
523 m_itemContext = nullptr;
524
525 // Prevent any bindings from running while waiting for deletion. Without
526 // this we may get transient errors from use of 'parent', for example.
527 QQmlContext *context = qmlContext(m_object);
528 if (context)
529 QQmlContextData::get(context)->clearContextRecursively();
530
531 if (m_loadingFromSource && m_component) {
532 // disconnect since we deleteLater
533 QObject::disconnect(sender: m_component, SIGNAL(statusChanged(QQmlComponent::Status)),
534 receiver: this, SLOT(sourceLoaded()));
535 QObject::disconnect(sender: m_component, SIGNAL(progressChanged(qreal)),
536 receiver: this, SIGNAL(progressChanged()));
537 m_component->deleteLater();
538 m_component.setObject(obj: nullptr, parent: this);
539 } else if (m_component) {
540 m_component.setObject(obj: nullptr, parent: this);
541 }
542 m_source = QUrl();
543
544 if (m_item) {
545 // We can't delete immediately because our item may have triggered
546 // the Loader to load a different item.
547 m_item->setParentItem(nullptr);
548 m_item->setVisible(false);
549 m_item = nullptr;
550 }
551 if (m_object) {
552 m_object->deleteLater();
553 m_object = nullptr;
554 }
555}
556
557void QQuick3DLoader::load()
558{
559
560 if (!isComponentComplete() || !m_component)
561 return;
562
563 if (!m_component->isLoading()) {
564 sourceLoaded();
565 } else {
566 QObject::connect(sender: m_component, SIGNAL(statusChanged(QQmlComponent::Status)),
567 receiver: this, SLOT(sourceLoaded()));
568 QObject::connect(sender: m_component, SIGNAL(progressChanged(qreal)),
569 receiver: this, SIGNAL(progressChanged()));
570 emit statusChanged();
571 emit progressChanged();
572 if (m_loadingFromSource)
573 emit sourceChanged();
574 else
575 emit sourceComponentChanged();
576 emit itemChanged();
577 }
578}
579
580void QQuick3DLoader::incubatorStateChanged(QQmlIncubator::Status status)
581{
582 if (status == QQmlIncubator::Loading || status == QQmlIncubator::Null)
583 return;
584
585 if (status == QQmlIncubator::Ready) {
586 m_object = m_incubator->object();
587 m_item = qmlobject_cast<QQuick3DNode*>(object: m_object);
588 emit itemChanged();
589 m_incubator->clear();
590 } else if (status == QQmlIncubator::Error) {
591 if (!m_incubator->errors().isEmpty())
592 QQmlEnginePrivate::warning(qmlEngine(this), m_incubator->errors());
593 delete m_itemContext;
594 m_itemContext = nullptr;
595 delete m_incubator->object();
596 m_source = QUrl();
597 emit itemChanged();
598 }
599 if (m_loadingFromSource)
600 emit sourceChanged();
601 else
602 emit sourceComponentChanged();
603 emit statusChanged();
604 emit progressChanged();
605 if (status == QQmlIncubator::Ready)
606 emit loaded();
607 disposeInitialPropertyValues(); // cleanup
608}
609
610void QQuick3DLoader::setInitialState(QObject *obj)
611{
612 QQuick3DObject *item = qmlobject_cast<QQuick3DObject*>(object: obj);
613 if (item) {
614 item->setParentItem(this);
615 }
616 if (obj) {
617 QQml_setParent_noEvent(object: m_itemContext, parent: obj);
618 QQml_setParent_noEvent(object: obj, parent: this);
619 m_itemContext = nullptr;
620 }
621
622 if (m_initialPropertyValues.isUndefined())
623 return;
624
625 QQmlComponentPrivate *d = QQmlComponentPrivate::get(c: m_component);
626 Q_ASSERT(d && d->engine);
627 QV4::ExecutionEngine *v4 = d->engine->handle();
628 Q_ASSERT(v4);
629 QV4::Scope scope(v4);
630 QV4::ScopedValue ipv(scope, m_initialPropertyValues.value());
631 QV4::Scoped<QV4::QmlContext> qmlContext(scope, m_qmlCallingContext.value());
632 d->initializeObjectWithInitialProperties(qmlContext, valuemap: ipv, toCreate: obj, requiredProperties: QQmlIncubatorPrivate::get(incubator: m_incubator)->requiredProperties());
633}
634
635void QQuick3DLoader::disposeInitialPropertyValues()
636{
637
638}
639
640QUrl QQuick3DLoader::resolveSourceUrl(QQmlV4FunctionPtr args)
641{
642 QV4::Scope scope(args->v4engine());
643 QV4::ScopedValue v(scope, (*args)[0]);
644 QString arg = v->toQString();
645 if (arg.isEmpty())
646 return QUrl();
647
648 auto context = scope.engine->callingQmlContext();
649 Q_ASSERT(!context.isNull());
650 return context->resolvedUrl(QUrl(arg));
651}
652
653QV4::ReturnedValue QQuick3DLoader::extractInitialPropertyValues(QQmlV4FunctionPtr args, bool *error)
654{
655 QV4::Scope scope(args->v4engine());
656 QV4::ScopedValue valuemap(scope, QV4::Encode::undefined());
657 if (args->length() >= 2) {
658 QV4::ScopedValue v(scope, (*args)[1]);
659 if (!v->isObject() || v->as<QV4::ArrayObject>()) {
660 *error = true;
661 qmlWarning(me: this) << QQuick3DLoader::tr(s: "setSource: value is not an object");
662 } else {
663 *error = false;
664 valuemap = v;
665 }
666 }
667
668 return valuemap->asReturnedValue();
669}
670
671void QQuick3DLoader::createComponent()
672{
673 const QQmlComponent::CompilationMode mode = m_asynchronous
674 ? QQmlComponent::Asynchronous
675 : QQmlComponent::PreferSynchronous;
676 QQmlContext *context = qmlContext(this);
677 m_component.setObject(obj: new QQmlComponent(context->engine(),
678 context->resolvedUrl(m_source),
679 mode,
680 this),
681 parent: this);
682}
683
684QT_END_NAMESPACE
685
686#include "moc_qquick3dloader_p.cpp"
687

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtquick3d/src/quick3d/qquick3dloader.cpp