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
qquick3dloader.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
5
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
17
18void QQuick3DLoaderIncubator::statusChanged(QQmlIncubator::Status status)
19{
20 m_loader->incubatorStateChanged(status);
21}
22
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, &ipvError));
139 if (ipvError)
140 return;
141
142 clear();
143 QUrl sourceUrl = resolveSourceUrl(args);
144 if (!ipv->isUndefined()) {
145 disposeInitialPropertyValues();
146 m_initialPropertyValues.set(args->v4engine(), ipv);
147 }
148 m_qmlCallingContext.set(scope.engine, scope.engine->qmlContext());
149
150 setSource(sourceUrl, 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(url, 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(comp, 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)
455 creationContext = qmlContext(this);
456
457 QQmlComponentPrivate *cp = QQmlComponentPrivate::get(m_component);
458 QQmlContext *context = [&](){
459 if (cp->isBound())
460 return creationContext;
461 m_itemContext = new QQmlContext(creationContext);
462 m_itemContext->setContextObject(this);
463 return m_itemContext;
464 }();
465
466 delete m_incubator;
467 m_incubator = new QQuick3DLoaderIncubator(this, m_asynchronous ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested);
468
469 m_component->create(*m_incubator, context);
470
471 if (m_incubator && m_incubator->status() == QQmlIncubator::Loading)
472 emit statusChanged();
473}
474
475void QQuick3DLoader::setSource(const QUrl &sourceUrl, bool needsClear)
476{
477 if (m_source == sourceUrl)
478 return;
479
480 if (needsClear)
481 clear();
482
483 m_source = sourceUrl;
484 m_loadingFromSource = true;
485
486 if (m_active)
487 loadFromSource();
488 else
489 emit sourceChanged();
490}
491
492void QQuick3DLoader::loadFromSource()
493{
494 if (m_source.isEmpty()) {
495 emit sourceChanged();
496 emit statusChanged();
497 emit progressChanged();
498 emit itemChanged();
499 return;
500 }
501
502 if (isComponentComplete()) {
503 if (!m_component)
504 createComponent();
505 load();
506 }
507}
508
509void QQuick3DLoader::loadFromSourceComponent()
510{
511 if (!m_component) {
512 emit sourceComponentChanged();
513 emit statusChanged();
514 emit progressChanged();
515 emit itemChanged();
516 return;
517 }
518
519 if (isComponentComplete())
520 load();
521}
522
523void QQuick3DLoader::clear()
524{
525 disposeInitialPropertyValues();
526
527 if (m_incubator)
528 m_incubator->clear();
529
530 delete m_itemContext;
531 m_itemContext = nullptr;
532
533 // Prevent any bindings from running while waiting for deletion. Without
534 // this we may get transient errors from use of 'parent', for example.
535 QQmlContext *context = qmlContext(m_object);
536 if (context)
537 QQmlContextData::get(context)->clearContextRecursively();
538
539 if (m_loadingFromSource && m_component) {
540 // disconnect since we deleteLater
541 QObject::disconnect(m_component, SIGNAL(statusChanged(QQmlComponent::Status)),
542 this, SLOT(sourceLoaded()));
543 QObject::disconnect(m_component, SIGNAL(progressChanged(qreal)),
544 this, SIGNAL(progressChanged()));
545 m_component->deleteLater();
546 m_component.setObject(nullptr, this);
547 } else if (m_component) {
548 m_component.setObject(nullptr, this);
549 }
550 m_source = QUrl();
551
552 if (m_item) {
553 // We can't delete immediately because our item may have triggered
554 // the Loader to load a different item.
555 m_item->setParentItem(nullptr);
556 m_item->setVisible(false);
557 m_item = nullptr;
558 }
559 if (m_object) {
560 m_object->deleteLater();
561 m_object = nullptr;
562 }
563}
564
565void QQuick3DLoader::load()
566{
567
568 if (!isComponentComplete() || !m_component)
569 return;
570
571 if (!m_component->isLoading()) {
572 sourceLoaded();
573 } else {
574 QObject::connect(m_component, SIGNAL(statusChanged(QQmlComponent::Status)),
575 this, SLOT(sourceLoaded()));
576 QObject::connect(m_component, SIGNAL(progressChanged(qreal)),
577 this, SIGNAL(progressChanged()));
578 emit statusChanged();
579 emit progressChanged();
580 if (m_loadingFromSource)
581 emit sourceChanged();
582 else
583 emit sourceComponentChanged();
584 emit itemChanged();
585 }
586}
587
588void QQuick3DLoader::incubatorStateChanged(QQmlIncubator::Status status)
589{
590 if (status == QQmlIncubator::Loading || status == QQmlIncubator::Null)
591 return;
592
593 if (status == QQmlIncubator::Ready) {
594 m_object = m_incubator->object();
595 m_item = qmlobject_cast<QQuick3DNode*>(m_object);
596 emit itemChanged();
597 m_incubator->clear();
598 } else if (status == QQmlIncubator::Error) {
599 if (!m_incubator->errors().isEmpty())
600 QQmlEnginePrivate::warning(qmlEngine(this), m_incubator->errors());
601 delete m_itemContext;
602 m_itemContext = nullptr;
603 delete m_incubator->object();
604 m_source = QUrl();
605 emit itemChanged();
606 }
607 if (m_loadingFromSource)
608 emit sourceChanged();
609 else
610 emit sourceComponentChanged();
611 emit statusChanged();
612 emit progressChanged();
613 if (status == QQmlIncubator::Ready)
614 emit loaded();
615 disposeInitialPropertyValues(); // cleanup
616}
617
618void QQuick3DLoader::setInitialState(QObject *obj)
619{
620 QQuick3DObject *item = qmlobject_cast<QQuick3DObject*>(obj);
621 if (item) {
622 item->setParentItem(this);
623 }
624 if (obj) {
625 if (m_itemContext)
626 QQml_setParent_noEvent(m_itemContext, obj);
627 QQml_setParent_noEvent(obj, this);
628 m_itemContext = nullptr;
629 }
630
631 if (m_initialPropertyValues.isUndefined())
632 return;
633
634 QQmlComponentPrivate *d = QQmlComponentPrivate::get(m_component);
635 Q_ASSERT(d && d->engine());
636 QV4::ExecutionEngine *v4 = d->engine()->handle();
637 Q_ASSERT(v4);
638 QV4::Scope scope(v4);
639 QV4::ScopedValue ipv(scope, m_initialPropertyValues.value());
640 QV4::Scoped<QV4::QmlContext> qmlContext(scope, m_qmlCallingContext.value());
641 d->initializeObjectWithInitialProperties(qmlContext, ipv, obj, QQmlIncubatorPrivate::get(m_incubator)->requiredProperties());
642}
643
644void QQuick3DLoader::disposeInitialPropertyValues()
645{
646
647}
648
649QUrl QQuick3DLoader::resolveSourceUrl(QQmlV4FunctionPtr args)
650{
651 QV4::Scope scope(args->v4engine());
652 QV4::ScopedValue v(scope, (*args)[0]);
653 QString arg = v->toQString();
654 if (arg.isEmpty())
655 return QUrl();
656
657 auto context = scope.engine->callingQmlContext();
658 Q_ASSERT(!context.isNull());
659 return context->resolvedUrl(QUrl(arg));
660}
661
662QV4::ReturnedValue QQuick3DLoader::extractInitialPropertyValues(QQmlV4FunctionPtr args, bool *error)
663{
664 QV4::Scope scope(args->v4engine());
665 QV4::ScopedValue valuemap(scope, QV4::Encode::undefined());
666 if (args->length() >= 2) {
667 QV4::ScopedValue v(scope, (*args)[1]);
668 if (!v->isObject() || v->as<QV4::ArrayObject>()) {
669 *error = true;
670 qmlWarning(this) << QQuick3DLoader::tr("setSource: value is not an object");
671 } else {
672 *error = false;
673 valuemap = v;
674 }
675 }
676
677 return valuemap->asReturnedValue();
678}
679
680void QQuick3DLoader::createComponent()
681{
682 const QQmlComponent::CompilationMode mode = m_asynchronous
683 ? QQmlComponent::Asynchronous
684 : QQmlComponent::PreferSynchronous;
685 QQmlContext *context = qmlContext(this);
686 m_component.setObject(new QQmlComponent(context->engine(),
687 context->resolvedUrl(m_source),
688 mode,
689 this),
690 this);
691}
692
693QT_END_NAMESPACE
694
695#include "moc_qquick3dloader_p.cpp"
void statusChanged(Status) override
Called when the status of the incubator changes.
void setInitialState(QObject *) override
Called after the object is first created, but before complex property bindings are evaluated and,...