1 | // Copyright (C) 2021 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 "qquickwidget.h" |
5 | #include "qquickwidget_p.h" |
6 | #include "qaccessiblequickwidgetfactory_p.h" |
7 | #include <QtWidgets/private/qwidgetrepaintmanager_p.h> |
8 | |
9 | #include "private/qquickwindow_p.h" |
10 | #include "private/qquickitem_p.h" |
11 | #include "private/qquickitemchangelistener_p.h" |
12 | #include "private/qquickrendercontrol_p.h" |
13 | #include "private/qsgrhisupport_p.h" |
14 | |
15 | #include "private/qsgsoftwarerenderer_p.h" |
16 | |
17 | #include <private/qqmldebugconnector_p.h> |
18 | #include <private/qquickprofiler_p.h> |
19 | #include <private/qqmldebugserviceinterfaces_p.h> |
20 | |
21 | #include <QtQml/qqmlengine.h> |
22 | #include <private/qqmlengine_p.h> |
23 | #include <QtCore/qbasictimer.h> |
24 | #include <QtGui/QOffscreenSurface> |
25 | #include <QtGui/private/qguiapplication_p.h> |
26 | #include <QtGui/qpa/qplatformintegration.h> |
27 | |
28 | #include <QtGui/QPainter> |
29 | |
30 | #include <QtQuick/QSGRendererInterface> |
31 | |
32 | #ifdef Q_OS_WIN |
33 | #if QT_CONFIG(messagebox) |
34 | # include <QtWidgets/QMessageBox> |
35 | #endif |
36 | # include <QtCore/QLibraryInfo> |
37 | # include <QtCore/qt_windows.h> |
38 | #endif |
39 | |
40 | #include <QtQuick/qquickgraphicsdevice.h> |
41 | #include <QtQuick/qquickrendertarget.h> |
42 | |
43 | #include "private/qwidget_p.h" |
44 | |
45 | #if QT_CONFIG(graphicsview) |
46 | #include <QtWidgets/qgraphicsscene.h> |
47 | #include <QtWidgets/qgraphicsview.h> |
48 | #endif |
49 | |
50 | QT_BEGIN_NAMESPACE |
51 | |
52 | QQuickWidgetOffscreenWindow::QQuickWidgetOffscreenWindow(QQuickWindowPrivate &dd, QQuickRenderControl *control) |
53 | :QQuickWindow(dd, control) |
54 | { |
55 | setTitle(QString::fromLatin1(ba: "Offscreen" )); |
56 | setObjectName(QString::fromLatin1(ba: "QQuickWidgetOffscreenWindow" )); |
57 | } |
58 | |
59 | // override setVisble to prevent accidental offscreen window being created |
60 | // by base class. |
61 | class QQuickWidgetOffscreenWindowPrivate: public QQuickWindowPrivate { |
62 | public: |
63 | void setVisible(bool visible) override { |
64 | Q_Q(QWindow); |
65 | // this stays always invisible |
66 | visibility = visible ? QWindow::Windowed : QWindow::Hidden; |
67 | q->visibilityChanged(visibility); // workaround for QTBUG-49054 |
68 | } |
69 | }; |
70 | |
71 | class QQuickWidgetRenderControlPrivate; |
72 | |
73 | class QQuickWidgetRenderControl : public QQuickRenderControl |
74 | { |
75 | Q_DECLARE_PRIVATE(QQuickWidgetRenderControl) |
76 | public: |
77 | QQuickWidgetRenderControl(QQuickWidget *quickwidget); |
78 | QWindow *renderWindow(QPoint *offset) override; |
79 | |
80 | }; |
81 | |
82 | class QQuickWidgetRenderControlPrivate : public QQuickRenderControlPrivate |
83 | { |
84 | public: |
85 | Q_DECLARE_PUBLIC(QQuickWidgetRenderControl) |
86 | QQuickWidgetRenderControlPrivate(QQuickWidgetRenderControl *renderControl, QQuickWidget *qqw) |
87 | : QQuickRenderControlPrivate(renderControl) |
88 | , m_quickWidget(qqw) |
89 | { |
90 | } |
91 | |
92 | bool isRenderWindow(const QWindow *w) override { |
93 | #if QT_CONFIG(graphicsview) |
94 | QWidgetPrivate *widgetd = QWidgetPrivate::get(w: m_quickWidget); |
95 | auto *proxy = (widgetd && widgetd->extra) ? widgetd->extra->proxyWidget : nullptr; |
96 | auto *scene = proxy ? proxy->scene() : nullptr; |
97 | if (scene) { |
98 | for (const auto &view : scene->views()) { |
99 | if (view->window()->windowHandle() == w) |
100 | return true; |
101 | } |
102 | } |
103 | |
104 | return m_quickWidget->window()->windowHandle() == w; |
105 | #endif |
106 | } |
107 | QQuickWidget *m_quickWidget; |
108 | }; |
109 | |
110 | QQuickWidgetRenderControl::QQuickWidgetRenderControl(QQuickWidget *quickWidget) |
111 | : QQuickRenderControl(*(new QQuickWidgetRenderControlPrivate(this, quickWidget)), nullptr) |
112 | { |
113 | } |
114 | |
115 | QWindow *QQuickWidgetRenderControl::renderWindow(QPoint *offset) |
116 | { |
117 | Q_D(QQuickWidgetRenderControl); |
118 | if (offset) |
119 | *offset = d->m_quickWidget->mapTo(d->m_quickWidget->window(), QPoint()); |
120 | |
121 | QWindow *result = nullptr; |
122 | #if QT_CONFIG(graphicsview) |
123 | QWidgetPrivate *widgetd = QWidgetPrivate::get(w: d->m_quickWidget); |
124 | if (widgetd->extra) { |
125 | if (auto proxy = widgetd->extra->proxyWidget) { |
126 | auto scene = proxy->scene(); |
127 | if (scene) { |
128 | const auto views = scene->views(); |
129 | if (!views.isEmpty()) { |
130 | // Get the first QGV containing the proxy. Not ideal, but the callers |
131 | // of this function aren't prepared to handle more than one render window. |
132 | auto candidateView = views.first(); |
133 | result = candidateView->window()->windowHandle(); |
134 | } |
135 | } |
136 | } |
137 | } |
138 | #endif |
139 | if (!result) |
140 | result = d->m_quickWidget->window()->windowHandle(); |
141 | |
142 | return result; |
143 | } |
144 | |
145 | void QQuickWidgetPrivate::initOffscreenWindow() |
146 | { |
147 | Q_Q(QQuickWidget); |
148 | |
149 | ensureBackingScene(); |
150 | offscreenWindow->setScreen(q->screen()); |
151 | // Do not call create() on offscreenWindow. |
152 | |
153 | QWidget::connect(sender: offscreenWindow, SIGNAL(sceneGraphInitialized()), receiver: q, SLOT(createFramebufferObject())); |
154 | QWidget::connect(sender: offscreenWindow, SIGNAL(sceneGraphInvalidated()), receiver: q, SLOT(destroyFramebufferObject())); |
155 | QWidget::connect(sender: offscreenWindow, signal: &QQuickWindow::focusObjectChanged, context: q, slot: &QQuickWidget::propagateFocusObjectChanged); |
156 | |
157 | #if QT_CONFIG(accessibility) |
158 | QAccessible::installFactory(&qAccessibleQuickWidgetFactory); |
159 | #endif |
160 | } |
161 | |
162 | void QQuickWidgetPrivate::ensureBackingScene() |
163 | { |
164 | // This should initialize, if not already done, the absolute minimum set of |
165 | // mandatory backing resources, meaning the QQuickWindow and its |
166 | // QQuickRenderControl. This function may be called very early on upon |
167 | // construction, including before init() even. |
168 | |
169 | Q_Q(QQuickWidget); |
170 | if (!renderControl) |
171 | renderControl = new QQuickWidgetRenderControl(q); |
172 | if (!offscreenWindow) |
173 | offscreenWindow = new QQuickWidgetOffscreenWindow(*new QQuickWidgetOffscreenWindowPrivate(), renderControl); |
174 | |
175 | // Check if the Software Adaptation is being used |
176 | auto sgRendererInterface = offscreenWindow->rendererInterface(); |
177 | if (sgRendererInterface && sgRendererInterface->graphicsApi() == QSGRendererInterface::Software) |
178 | useSoftwareRenderer = true; |
179 | } |
180 | |
181 | void QQuickWidgetPrivate::init(QQmlEngine* e) |
182 | { |
183 | Q_Q(QQuickWidget); |
184 | |
185 | initOffscreenWindow(); |
186 | |
187 | if (!useSoftwareRenderer) { |
188 | if (QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::RhiBasedRendering)) |
189 | setRenderToTexture(); |
190 | else |
191 | qWarning(msg: "QQuickWidget is not supported on this platform." ); |
192 | } |
193 | |
194 | engine = e; |
195 | |
196 | if (!engine.isNull() && !engine.data()->incubationController()) |
197 | engine.data()->setIncubationController(offscreenWindow->incubationController()); |
198 | |
199 | #if QT_CONFIG(quick_draganddrop) |
200 | q->setAcceptDrops(true); |
201 | #endif |
202 | |
203 | QObject::connect(sender: renderControl, SIGNAL(renderRequested()), receiver: q, SLOT(triggerUpdate())); |
204 | QObject::connect(sender: renderControl, SIGNAL(sceneChanged()), receiver: q, SLOT(triggerUpdate())); |
205 | } |
206 | |
207 | void QQuickWidgetPrivate::ensureEngine() const |
208 | { |
209 | Q_Q(const QQuickWidget); |
210 | if (!engine.isNull()) |
211 | return; |
212 | |
213 | engine = new QQmlEngine(const_cast<QQuickWidget*>(q)); |
214 | engine.data()->setIncubationController(offscreenWindow->incubationController()); |
215 | } |
216 | |
217 | void QQuickWidgetPrivate::invalidateRenderControl() |
218 | { |
219 | if (!useSoftwareRenderer && rhi) { |
220 | // For the user's own OpenGL code connected to some QQuickWindow signals. |
221 | rhi->makeThreadLocalNativeContextCurrent(); |
222 | } |
223 | |
224 | renderControl->invalidate(); |
225 | } |
226 | |
227 | void QQuickWidgetPrivate::handleWindowChange() |
228 | { |
229 | Q_Q(QQuickWidget); |
230 | |
231 | if (offscreenWindow->isPersistentSceneGraph() |
232 | && qGuiApp->testAttribute(attribute: Qt::AA_ShareOpenGLContexts) |
233 | && rhiConfig().api() == QPlatformBackingStoreRhiConfig::OpenGL) |
234 | { |
235 | return; |
236 | } |
237 | |
238 | // In case of !isPersistentSceneGraph or when we need a new context due to |
239 | // the need to share resources with the new window's context, we must both |
240 | // invalidate the scenegraph and destroy the context. QQuickRenderControl |
241 | // must be recreated because its RHI will contain a dangling pointer to |
242 | // the context. |
243 | |
244 | QScopedPointer<QQuickWindow> oldOffScreenWindow(offscreenWindow); // Do not delete before reparenting sgItem |
245 | offscreenWindow = nullptr; |
246 | delete renderControl; |
247 | |
248 | renderControl = new QQuickWidgetRenderControl(q); |
249 | initOffscreenWindow(); |
250 | |
251 | QObject::connect(sender: renderControl, SIGNAL(renderRequested()), receiver: q, SLOT(triggerUpdate())); |
252 | QObject::connect(sender: renderControl, SIGNAL(sceneChanged()), receiver: q, SLOT(triggerUpdate())); |
253 | |
254 | if (!source.isEmpty()) |
255 | execute(); |
256 | else if (QQuickItem *sgItem = qobject_cast<QQuickItem *>(o: root)) |
257 | sgItem->setParentItem(offscreenWindow->contentItem()); |
258 | } |
259 | |
260 | QQuickWidgetPrivate::QQuickWidgetPrivate() |
261 | : root(nullptr) |
262 | , component(nullptr) |
263 | , offscreenWindow(nullptr) |
264 | , renderControl(nullptr) |
265 | , rhi(nullptr) |
266 | , outputTexture(nullptr) |
267 | , depthStencil(nullptr) |
268 | , msaaBuffer(nullptr) |
269 | , rt(nullptr) |
270 | , rtRp(nullptr) |
271 | , resizeMode(QQuickWidget::SizeViewToRootObject) |
272 | , initialSize(0,0) |
273 | , eventPending(false) |
274 | , updatePending(false) |
275 | , fakeHidden(false) |
276 | , requestedSamples(0) |
277 | , useSoftwareRenderer(false) |
278 | , forceFullUpdate(false) |
279 | , deviceLost(false) |
280 | { |
281 | } |
282 | |
283 | void QQuickWidgetPrivate::destroy() |
284 | { |
285 | Q_Q(QQuickWidget); |
286 | invalidateRenderControl(); |
287 | q->destroyFramebufferObject(); |
288 | delete offscreenWindow; |
289 | delete renderControl; |
290 | offscreenRenderer.reset(); |
291 | } |
292 | |
293 | void QQuickWidgetPrivate::execute() |
294 | { |
295 | Q_Q(QQuickWidget); |
296 | ensureEngine(); |
297 | |
298 | if (root) { |
299 | delete root; |
300 | root = nullptr; |
301 | } |
302 | if (component) { |
303 | delete component; |
304 | component = nullptr; |
305 | } |
306 | if (!source.isEmpty()) { |
307 | component = new QQmlComponent(engine.data(), source, q); |
308 | if (!component->isLoading()) { |
309 | q->continueExecute(); |
310 | } else { |
311 | QObject::connect(sender: component, SIGNAL(statusChanged(QQmlComponent::Status)), |
312 | receiver: q, SLOT(continueExecute())); |
313 | } |
314 | } |
315 | } |
316 | |
317 | void QQuickWidgetPrivate::itemGeometryChanged(QQuickItem *resizeItem, QQuickGeometryChange change, |
318 | const QRectF &oldGeometry) |
319 | { |
320 | Q_Q(QQuickWidget); |
321 | if (resizeItem == root && resizeMode == QQuickWidget::SizeViewToRootObject) { |
322 | // wait for both width and height to be changed |
323 | resizetimer.start(msec: 0,obj: q); |
324 | } |
325 | QQuickItemChangeListener::itemGeometryChanged(resizeItem, change, oldGeometry); |
326 | } |
327 | |
328 | void QQuickWidgetPrivate::render(bool needsSync) |
329 | { |
330 | Q_Q(QQuickWidget); |
331 | if (!useSoftwareRenderer) { |
332 | if (deviceLost) { |
333 | deviceLost = false; |
334 | initializeWithRhi(); |
335 | q->createFramebufferObject(); |
336 | } |
337 | |
338 | if (!rhi) { |
339 | qWarning(msg: "QQuickWidget: Attempted to render scene with no rhi" ); |
340 | return; |
341 | } |
342 | |
343 | // createFramebufferObject() bails out when the size is empty. In this case |
344 | // we cannot render either. |
345 | if (!outputTexture) |
346 | return; |
347 | |
348 | renderControl->beginFrame(); |
349 | QQuickRenderControlPrivate::FrameStatus frameStatus = QQuickRenderControlPrivate::get(renderControl)->frameStatus; |
350 | if (frameStatus == QQuickRenderControlPrivate::DeviceLostInBeginFrame) { |
351 | // graphics resources controlled by us must be released |
352 | invalidateRenderControl(); |
353 | // skip this round and hope that the tlw's repaint manager will manage to reinitialize |
354 | deviceLost = true; |
355 | return; |
356 | } |
357 | if (frameStatus != QQuickRenderControlPrivate::RecordingFrame) { |
358 | qWarning(msg: "QQuickWidget: Failed to begin recording a frame" ); |
359 | return; |
360 | } |
361 | |
362 | if (needsSync) { |
363 | renderControl->polishItems(); |
364 | renderControl->sync(); |
365 | } |
366 | |
367 | renderControl->render(); |
368 | |
369 | renderControl->endFrame(); |
370 | } else { |
371 | //Software Renderer |
372 | if (needsSync) { |
373 | renderControl->polishItems(); |
374 | renderControl->sync(); |
375 | } |
376 | if (!offscreenWindow) |
377 | return; |
378 | QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: offscreenWindow); |
379 | auto softwareRenderer = static_cast<QSGSoftwareRenderer*>(cd->renderer); |
380 | if (softwareRenderer && !softwareImage.isNull()) { |
381 | softwareRenderer->setCurrentPaintDevice(&softwareImage); |
382 | if (forceFullUpdate) { |
383 | softwareRenderer->markDirty(); |
384 | forceFullUpdate = false; |
385 | } |
386 | renderControl->render(); |
387 | |
388 | updateRegion += softwareRenderer->flushRegion(); |
389 | } |
390 | } |
391 | } |
392 | |
393 | void QQuickWidgetPrivate::renderSceneGraph() |
394 | { |
395 | Q_Q(QQuickWidget); |
396 | updatePending = false; |
397 | |
398 | if (!q->isVisible() || fakeHidden) |
399 | return; |
400 | |
401 | render(needsSync: true); |
402 | |
403 | #if QT_CONFIG(graphicsview) |
404 | if (q->window()->graphicsProxyWidget()) |
405 | QWidgetPrivate::nearestGraphicsProxyWidget(origin: q)->update(); |
406 | else |
407 | #endif |
408 | { |
409 | if (!useSoftwareRenderer) |
410 | q->update(); // schedule composition |
411 | else if (!updateRegion.isEmpty()) |
412 | q->update(updateRegion); |
413 | } |
414 | } |
415 | |
416 | QImage QQuickWidgetPrivate::grabFramebuffer() |
417 | { |
418 | if (!useSoftwareRenderer && !rhi) |
419 | return QImage(); |
420 | |
421 | // grabWindow() does not work for the rhi case, we are in control of the |
422 | // render target, and so it is up to us to read it back. When the software |
423 | // renderer is in use, just call grabWindow(). |
424 | |
425 | if (outputTexture) { |
426 | render(needsSync: true); |
427 | QRhiCommandBuffer *cb = nullptr; |
428 | rhi->beginOffscreenFrame(cb: &cb); |
429 | QRhiResourceUpdateBatch *resUpd = rhi->nextResourceUpdateBatch(); |
430 | QRhiReadbackResult readResult; |
431 | resUpd->readBackTexture(rb: QRhiReadbackDescription(outputTexture), result: &readResult); |
432 | cb->resourceUpdate(resourceUpdates: resUpd); |
433 | rhi->endOffscreenFrame(); |
434 | if (!readResult.data.isEmpty()) { |
435 | QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), |
436 | readResult.pixelSize.width(), readResult.pixelSize.height(), |
437 | QImage::Format_RGBA8888_Premultiplied); |
438 | if (rhi->isYUpInFramebuffer()) |
439 | return wrapperImage.mirrored(); |
440 | else |
441 | return wrapperImage.copy(); |
442 | } |
443 | return QImage(); |
444 | } |
445 | |
446 | return offscreenWindow->grabWindow(); |
447 | } |
448 | |
449 | // Intentionally not overriding the QQuickWindow's focusObject. |
450 | // Key events should go to our key event handlers, and then to the |
451 | // QQuickWindow, not any in-scene item. |
452 | |
453 | /*! |
454 | \module QtQuickWidgets |
455 | \title Qt Quick Widgets C++ Classes |
456 | \ingroup modules |
457 | \brief The C++ API provided by the Qt Quick Widgets module. |
458 | \qtcmakepackage QuickWidgets |
459 | \qtvariable quickwidgets |
460 | |
461 | To link against the module, add this line to your \l qmake |
462 | \c .pro file: |
463 | |
464 | \code |
465 | QT += quickwidgets |
466 | \endcode |
467 | |
468 | For more information, see the QQuickWidget class documentation. |
469 | */ |
470 | |
471 | /*! |
472 | \class QQuickWidget |
473 | \since 5.3 |
474 | \brief The QQuickWidget class provides a widget for displaying a Qt Quick user interface. |
475 | |
476 | \inmodule QtQuickWidgets |
477 | |
478 | This is a convenience wrapper for QQuickWindow which will automatically load and display a QML |
479 | scene when given the URL of the main source file. Alternatively, you can instantiate your own |
480 | objects using QQmlComponent and place them in a manually set up QQuickWidget. |
481 | |
482 | Typical usage: |
483 | |
484 | \code |
485 | QQuickWidget *view = new QQuickWidget; |
486 | view->setSource(QUrl::fromLocalFile("myqmlfile.qml")); |
487 | view->show(); |
488 | \endcode |
489 | |
490 | To receive errors related to loading and executing QML with QQuickWidget, |
491 | you can connect to the statusChanged() signal and monitor for QQuickWidget::Error. |
492 | The errors are available via QQuickWidget::errors(). |
493 | |
494 | QQuickWidget also manages sizing of the view and root object. By default, the \l resizeMode |
495 | is SizeViewToRootObject, which will load the component and resize it to the |
496 | size of the view. Alternatively the resizeMode may be set to SizeRootObjectToView which |
497 | will resize the view to the size of the root object. |
498 | |
499 | \section1 Performance Considerations |
500 | |
501 | QQuickWidget is an alternative to using QQuickView and QWidget::createWindowContainer(). |
502 | The restrictions on stacking order do not apply, making QQuickWidget the more flexible |
503 | alternative, behaving more like an ordinary widget. |
504 | |
505 | However, the above mentioned advantages come at the expense of performance: |
506 | \list |
507 | |
508 | \li Unlike QQuickWindow and QQuickView, QQuickWidget involves at least one |
509 | additional render pass targeting an offscreen color buffer, typically a 2D |
510 | texture, followed by drawing a texture quad. This means increased load |
511 | especially for the fragment processing of the GPU. |
512 | |
513 | \li Using QQuickWidget disables the \l{threaded_render_loop}{threaded render loop} on all |
514 | platforms. This means that some of the benefits of threaded rendering, for example |
515 | \l Animator classes and vsync driven animations, will not be available. |
516 | \endlist |
517 | |
518 | \note Avoid calling winId() on a QQuickWidget. This function triggers the creation of |
519 | a native window, resulting in reduced performance and possibly rendering glitches. The |
520 | entire purpose of QQuickWidget is to render Quick scenes without a separate native |
521 | window, hence making it a native widget should always be avoided. |
522 | |
523 | \section1 Graphics API Support |
524 | |
525 | QQuickWidget is functional with all the 3D graphics APIs supported by Qt |
526 | Quick, as well as the \c software backend. Other backends, for example |
527 | OpenVG, are not compatible however and attempting to construct a |
528 | QQuickWidget will lead to problems. |
529 | |
530 | Overriding the platform's default graphics API is done the same way as with |
531 | QQuickWindow and QQuickView: either by calling |
532 | QQuickWindow::setGraphicsApi() early on before constructing the first |
533 | QQuickWidget, or by setting the \c{QSG_RHI_BACKEND} environment variable. |
534 | |
535 | \note One top-level window can only use one single graphics API for |
536 | rendering. For example, attempting to place a QQuickWidget using Vulkan and |
537 | a QOpenGLWidget in the widget hierarchy of the same top-level window, |
538 | problems will occur and one of the widgets will not be rendering as |
539 | expected. |
540 | |
541 | \section1 Scene Graph and Context Persistency |
542 | |
543 | QQuickWidget honors QQuickWindow::isPersistentSceneGraph(), meaning that |
544 | applications can decide - by calling |
545 | QQuickWindow::setPersistentSceneGraph() on the window returned from the |
546 | quickWindow() function - to let scenegraph nodes and other Qt Quick scene |
547 | related resources be released whenever the widget becomes hidden. By default |
548 | persistency is enabled, just like with QQuickWindow. |
549 | |
550 | When running with the OpenGL, QQuickWindow offers the possibility to |
551 | disable persistent OpenGL contexts as well. This setting is currently |
552 | ignored by QQuickWidget and the context is always persistent. The OpenGL |
553 | context is thus not destroyed when hiding the widget. The context is |
554 | destroyed only when the widget is destroyed or when the widget gets |
555 | reparented into another top-level widget's child hierarchy. However, some |
556 | applications, in particular those that have their own graphics resources |
557 | due to performing custom OpenGL rendering in the Qt Quick scene, may wish |
558 | to disable the latter since they may not be prepared to handle the loss of |
559 | the context when moving a QQuickWidget into another window. Such |
560 | applications can set the QCoreApplication::AA_ShareOpenGLContexts |
561 | attribute. For a discussion on the details of resource initialization and |
562 | cleanup, refer to the QOpenGLWidget documentation. |
563 | |
564 | \note QQuickWidget offers less fine-grained control over its internal |
565 | OpenGL context than QOpenGLWidget, and there are subtle differences, most |
566 | notably that disabling the persistent scene graph will lead to destroying |
567 | the context on a window change regardless of the presence of |
568 | QCoreApplication::AA_ShareOpenGLContexts. |
569 | |
570 | \section1 Limitations |
571 | |
572 | Putting other widgets underneath and making the QQuickWidget transparent will not lead |
573 | to the expected results: the widgets underneath will not be visible. This is because |
574 | in practice the QQuickWidget is drawn before all other regular, non-OpenGL widgets, |
575 | and so see-through types of solutions are not feasible. Other type of layouts, like |
576 | having widgets on top of the QQuickWidget, will function as expected. |
577 | |
578 | When absolutely necessary, this limitation can be overcome by setting the |
579 | Qt::WA_AlwaysStackOnTop attribute on the QQuickWidget. Be aware, however that this |
580 | breaks stacking order. For example it will not be possible to have other widgets on |
581 | top of the QQuickWidget, so it should only be used in situations where a |
582 | semi-transparent QQuickWidget with other widgets visible underneath is required. |
583 | |
584 | This limitation only applies when there are other widgets underneath the QQuickWidget |
585 | inside the same window. Making the window semi-transparent, with other applications |
586 | and the desktop visible in the background, is done in the traditional way: Set |
587 | Qt::WA_TranslucentBackground on the top-level window, request an alpha channel, and |
588 | change the Qt Quick Scenegraph's clear color to Qt::transparent via setClearColor(). |
589 | |
590 | \section1 Tab Key Handling |
591 | |
592 | On press of the \c[TAB] key, the item inside the QQuickWidget gets focus. If |
593 | this item can handle \c[TAB] key press, focus will change accordingly within |
594 | the item, otherwise the next widget in the focus chain gets focus. |
595 | |
596 | \sa {Exposing Attributes of C++ Types to QML}, {Qt Quick Widgets Example}, QQuickView |
597 | */ |
598 | |
599 | |
600 | /*! |
601 | \fn void QQuickWidget::statusChanged(QQuickWidget::Status status) |
602 | This signal is emitted when the component's current \a status changes. |
603 | */ |
604 | |
605 | /*! |
606 | Constructs a QQuickWidget with the given \a parent. |
607 | The default value of \a parent is 0. |
608 | |
609 | */ |
610 | QQuickWidget::QQuickWidget(QWidget *parent) |
611 | : QWidget(*(new QQuickWidgetPrivate), parent, {}) |
612 | { |
613 | setMouseTracking(true); |
614 | setFocusPolicy(Qt::StrongFocus); |
615 | setAttribute(Qt::WA_AcceptTouchEvents); |
616 | d_func()->init(); |
617 | } |
618 | |
619 | /*! |
620 | Constructs a QQuickWidget with the given QML \a source and \a parent. |
621 | The default value of \a parent is 0. |
622 | |
623 | */ |
624 | QQuickWidget::QQuickWidget(const QUrl &source, QWidget *parent) |
625 | : QQuickWidget(parent) |
626 | { |
627 | setSource(source); |
628 | } |
629 | |
630 | /*! |
631 | Constructs a QQuickWidget with the given QML \a engine and \a parent. |
632 | |
633 | Note: In this case, the QQuickWidget does not own the given \a engine object; |
634 | it is the caller's responsibility to destroy the engine. If the \a engine is deleted |
635 | before the view, status() will return QQuickWidget::Error. |
636 | |
637 | \sa Status, status(), errors() |
638 | */ |
639 | QQuickWidget::QQuickWidget(QQmlEngine* engine, QWidget *parent) |
640 | : QWidget(*(new QQuickWidgetPrivate), parent, {}) |
641 | { |
642 | setMouseTracking(true); |
643 | setFocusPolicy(Qt::StrongFocus); |
644 | d_func()->init(e: engine); |
645 | } |
646 | |
647 | /*! |
648 | Destroys the QQuickWidget. |
649 | */ |
650 | QQuickWidget::~QQuickWidget() |
651 | { |
652 | // Ensure that the component is destroyed before the engine; the engine may |
653 | // be a child of the QQuickWidgetPrivate, and will be destroyed by its dtor |
654 | Q_D(QQuickWidget); |
655 | delete d->root; |
656 | d->root = nullptr; |
657 | |
658 | // NB! resetting graphics resources must be done from this destructor, |
659 | // *not* from the private class' destructor. This is due to how destruction |
660 | // works and due to the QWidget dtor (for toplevels) destroying the repaint |
661 | // manager and rhi before the (QObject) private gets destroyed. Hence must |
662 | // do it here early on. |
663 | d->destroy(); |
664 | } |
665 | |
666 | /*! |
667 | \property QQuickWidget::source |
668 | \brief The URL of the source of the QML component. |
669 | |
670 | Ensure that the URL provided is full and correct, in particular, use |
671 | \l QUrl::fromLocalFile() when loading a file from the local filesystem. |
672 | |
673 | \note Setting a source URL will result in the QML component being |
674 | instantiated, even if the URL is unchanged from the current value. |
675 | */ |
676 | |
677 | /*! |
678 | Sets the source to the \a url, loads the QML component and instantiates it. |
679 | |
680 | Ensure that the URL provided is full and correct, in particular, use |
681 | \l QUrl::fromLocalFile() when loading a file from the local filesystem. |
682 | |
683 | Calling this method multiple times with the same URL will result |
684 | in the QML component being reinstantiated. |
685 | */ |
686 | void QQuickWidget::setSource(const QUrl& url) |
687 | { |
688 | Q_D(QQuickWidget); |
689 | d->source = url; |
690 | d->execute(); |
691 | } |
692 | |
693 | /*! |
694 | \internal |
695 | |
696 | Sets the source \a url, \a component and content \a item (root of the QML object hierarchy) directly. |
697 | */ |
698 | void QQuickWidget::setContent(const QUrl& url, QQmlComponent *component, QObject* item) |
699 | { |
700 | Q_D(QQuickWidget); |
701 | d->source = url; |
702 | d->component = component; |
703 | |
704 | if (d->component && d->component->isError()) { |
705 | const QList<QQmlError> errorList = d->component->errors(); |
706 | for (const QQmlError &error : errorList) { |
707 | QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), nullptr).warning() |
708 | << error; |
709 | } |
710 | emit statusChanged(status()); |
711 | return; |
712 | } |
713 | |
714 | d->setRootObject(item); |
715 | emit statusChanged(status()); |
716 | } |
717 | |
718 | /*! |
719 | Returns the source URL, if set. |
720 | |
721 | \sa setSource() |
722 | */ |
723 | QUrl QQuickWidget::source() const |
724 | { |
725 | Q_D(const QQuickWidget); |
726 | return d->source; |
727 | } |
728 | |
729 | /*! |
730 | Returns a pointer to the QQmlEngine used for instantiating |
731 | QML Components. |
732 | */ |
733 | QQmlEngine* QQuickWidget::engine() const |
734 | { |
735 | Q_D(const QQuickWidget); |
736 | d->ensureEngine(); |
737 | return const_cast<QQmlEngine *>(d->engine.data()); |
738 | } |
739 | |
740 | /*! |
741 | This function returns the root of the context hierarchy. Each QML |
742 | component is instantiated in a QQmlContext. QQmlContext's are |
743 | essential for passing data to QML components. In QML, contexts are |
744 | arranged hierarchically and this hierarchy is managed by the |
745 | QQmlEngine. |
746 | */ |
747 | QQmlContext* QQuickWidget::rootContext() const |
748 | { |
749 | Q_D(const QQuickWidget); |
750 | d->ensureEngine(); |
751 | return d->engine.data()->rootContext(); |
752 | } |
753 | |
754 | /*! |
755 | \enum QQuickWidget::Status |
756 | Specifies the loading status of the QQuickWidget. |
757 | |
758 | \value Null This QQuickWidget has no source set. |
759 | \value Ready This QQuickWidget has loaded and created the QML component. |
760 | \value Loading This QQuickWidget is loading network data. |
761 | \value Error One or more errors occurred. Call errors() to retrieve a list |
762 | of errors. |
763 | */ |
764 | |
765 | /*! \enum QQuickWidget::ResizeMode |
766 | |
767 | This enum specifies how to resize the view. |
768 | |
769 | \value SizeViewToRootObject The view resizes with the root item in the QML. |
770 | \value SizeRootObjectToView The view will automatically resize the root item to the size of the view. |
771 | */ |
772 | |
773 | /*! |
774 | \fn void QQuickWidget::sceneGraphError(QQuickWindow::SceneGraphError error, const QString &message) |
775 | |
776 | This signal is emitted when an \a error occurred during scene graph initialization. |
777 | |
778 | Applications should connect to this signal if they wish to handle errors, |
779 | like OpenGL context creation failures, in a custom way. When no slot is |
780 | connected to the signal, the behavior will be different: Quick will print |
781 | the \a message, or show a message box, and terminate the application. |
782 | |
783 | This signal will be emitted from the GUI thread. |
784 | |
785 | \sa QQuickWindow::sceneGraphError() |
786 | */ |
787 | |
788 | /*! |
789 | \property QQuickWidget::status |
790 | The component's current \l{QQuickWidget::Status} {status}. |
791 | */ |
792 | |
793 | QQuickWidget::Status QQuickWidget::status() const |
794 | { |
795 | Q_D(const QQuickWidget); |
796 | if (!d->engine && !d->source.isEmpty()) |
797 | return QQuickWidget::Error; |
798 | |
799 | if (!d->component) |
800 | return QQuickWidget::Null; |
801 | |
802 | if (d->component->status() == QQmlComponent::Ready && !d->root) |
803 | return QQuickWidget::Error; |
804 | |
805 | return QQuickWidget::Status(d->component->status()); |
806 | } |
807 | |
808 | /*! |
809 | Return the list of errors that occurred during the last compile or create |
810 | operation. When the status is not \l Error, an empty list is returned. |
811 | |
812 | \sa status |
813 | */ |
814 | QList<QQmlError> QQuickWidget::errors() const |
815 | { |
816 | Q_D(const QQuickWidget); |
817 | QList<QQmlError> errs; |
818 | |
819 | if (d->component) |
820 | errs = d->component->errors(); |
821 | |
822 | if (!d->engine && !d->source.isEmpty()) { |
823 | QQmlError error; |
824 | error.setDescription(QLatin1String("QQuickWidget: invalid qml engine." )); |
825 | errs << error; |
826 | } |
827 | if (d->component && d->component->status() == QQmlComponent::Ready && !d->root) { |
828 | QQmlError error; |
829 | error.setDescription(QLatin1String("QQuickWidget: invalid root object." )); |
830 | errs << error; |
831 | } |
832 | |
833 | return errs; |
834 | } |
835 | |
836 | /*! |
837 | \property QQuickWidget::resizeMode |
838 | \brief Determines whether the view should resize the window contents. |
839 | |
840 | If this property is set to SizeViewToRootObject (the default), the view |
841 | resizes to the size of the root item in the QML. |
842 | |
843 | If this property is set to SizeRootObjectToView, the view will |
844 | automatically resize the root item to the size of the view. |
845 | |
846 | Regardless of this property, the sizeHint of the view |
847 | is the initial size of the root item. Note though that |
848 | since QML may load dynamically, that size may change. |
849 | |
850 | \sa initialSize() |
851 | */ |
852 | |
853 | void QQuickWidget::setResizeMode(ResizeMode mode) |
854 | { |
855 | Q_D(QQuickWidget); |
856 | if (d->resizeMode == mode) |
857 | return; |
858 | |
859 | if (d->root) { |
860 | if (d->resizeMode == SizeViewToRootObject) { |
861 | QQuickItemPrivate *p = QQuickItemPrivate::get(item: d->root); |
862 | p->removeItemChangeListener(d, types: QQuickItemPrivate::Geometry); |
863 | } |
864 | } |
865 | |
866 | d->resizeMode = mode; |
867 | if (d->root) { |
868 | d->initResize(); |
869 | } |
870 | } |
871 | |
872 | void QQuickWidgetPrivate::initResize() |
873 | { |
874 | if (root) { |
875 | if (resizeMode == QQuickWidget::SizeViewToRootObject) { |
876 | QQuickItemPrivate *p = QQuickItemPrivate::get(item: root); |
877 | p->addItemChangeListener(listener: this, types: QQuickItemPrivate::Geometry); |
878 | } |
879 | } |
880 | updateSize(); |
881 | } |
882 | |
883 | void QQuickWidgetPrivate::updateSize() |
884 | { |
885 | Q_Q(QQuickWidget); |
886 | if (!root) |
887 | return; |
888 | |
889 | if (resizeMode == QQuickWidget::SizeViewToRootObject) { |
890 | QSize newSize = QSize(root->width(), root->height()); |
891 | if (newSize.isValid()) { |
892 | if (newSize != q->size()) { |
893 | q->resize(newSize); |
894 | q->updateGeometry(); |
895 | } else if (offscreenWindow->size().isEmpty()) { |
896 | // QQuickDeliveryAgentPrivate::deliverHoverEvent() ignores events that |
897 | // occur outside of QQuickRootItem's geometry, so we need it to match root's size. |
898 | offscreenWindow->contentItem()->setSize(newSize); |
899 | } |
900 | } |
901 | } else if (resizeMode == QQuickWidget::SizeRootObjectToView) { |
902 | const bool needToUpdateWidth = !qFuzzyCompare(p1: q->width(), p2: root->width()); |
903 | const bool needToUpdateHeight = !qFuzzyCompare(p1: q->height(), p2: root->height()); |
904 | |
905 | if (needToUpdateWidth && needToUpdateHeight) { |
906 | // Make sure that we have realistic sizing behavior by following |
907 | // what on-screen windows would do and resize everything, not just |
908 | // the root item. We do this because other types may be relying on |
909 | // us to behave correctly. |
910 | const QSizeF newSize(q->width(), q->height()); |
911 | offscreenWindow->resize(newSize: newSize.toSize()); |
912 | offscreenWindow->contentItem()->setSize(newSize); |
913 | root->setSize(newSize); |
914 | } else if (needToUpdateWidth) { |
915 | const int newWidth = q->width(); |
916 | offscreenWindow->setWidth(newWidth); |
917 | offscreenWindow->contentItem()->setWidth(newWidth); |
918 | root->setWidth(newWidth); |
919 | } else if (needToUpdateHeight) { |
920 | const int newHeight = q->height(); |
921 | offscreenWindow->setHeight(newHeight); |
922 | offscreenWindow->contentItem()->setHeight(newHeight); |
923 | root->setHeight(newHeight); |
924 | } |
925 | } |
926 | } |
927 | |
928 | /*! |
929 | \internal |
930 | |
931 | Update the position of the offscreen window, so it matches the position of the QQuickWidget. |
932 | */ |
933 | void QQuickWidgetPrivate::updatePosition() |
934 | { |
935 | Q_Q(QQuickWidget); |
936 | if (offscreenWindow == nullptr) |
937 | return; |
938 | |
939 | const QPoint &pos = q->mapToGlobal(QPoint(0, 0)); |
940 | if (offscreenWindow->position() != pos) |
941 | offscreenWindow->setPosition(pos); |
942 | } |
943 | |
944 | QSize QQuickWidgetPrivate::rootObjectSize() const |
945 | { |
946 | QSize rootObjectSize(0,0); |
947 | int widthCandidate = -1; |
948 | int heightCandidate = -1; |
949 | if (root) { |
950 | widthCandidate = root->width(); |
951 | heightCandidate = root->height(); |
952 | } |
953 | if (widthCandidate > 0) { |
954 | rootObjectSize.setWidth(widthCandidate); |
955 | } |
956 | if (heightCandidate > 0) { |
957 | rootObjectSize.setHeight(heightCandidate); |
958 | } |
959 | return rootObjectSize; |
960 | } |
961 | |
962 | void QQuickWidgetPrivate::handleContextCreationFailure(const QSurfaceFormat &) |
963 | { |
964 | Q_Q(QQuickWidget); |
965 | |
966 | QString translatedMessage; |
967 | QString untranslatedMessage; |
968 | QQuickWindowPrivate::rhiCreationFailureMessage(backendName: QLatin1String("QRhi" ), translatedMessage: &translatedMessage, untranslatedMessage: &untranslatedMessage); |
969 | |
970 | static const QMetaMethod errorSignal = QMetaMethod::fromSignal(signal: &QQuickWidget::sceneGraphError); |
971 | const bool signalConnected = q->isSignalConnected(signal: errorSignal); |
972 | if (signalConnected) |
973 | emit q->sceneGraphError(error: QQuickWindow::ContextNotAvailable, message: translatedMessage); |
974 | |
975 | #if defined(Q_OS_WIN) && QT_CONFIG(messagebox) |
976 | if (!signalConnected && !QLibraryInfo::isDebugBuild() && !GetConsoleWindow()) |
977 | QMessageBox::critical(q, QCoreApplication::applicationName(), translatedMessage); |
978 | #endif // Q_OS_WIN |
979 | if (!signalConnected) |
980 | qFatal(msg: "%s" , qPrintable(untranslatedMessage)); |
981 | } |
982 | |
983 | static inline QPlatformBackingStoreRhiConfig::Api graphicsApiToBackingStoreRhiApi(QSGRendererInterface::GraphicsApi api) |
984 | { |
985 | switch (api) { |
986 | case QSGRendererInterface::OpenGL: |
987 | return QPlatformBackingStoreRhiConfig::OpenGL; |
988 | case QSGRendererInterface::Vulkan: |
989 | return QPlatformBackingStoreRhiConfig::Vulkan; |
990 | case QSGRendererInterface::Direct3D11: |
991 | return QPlatformBackingStoreRhiConfig::D3D11; |
992 | case QSGRendererInterface::Direct3D12: |
993 | return QPlatformBackingStoreRhiConfig::D3D12; |
994 | case QSGRendererInterface::Metal: |
995 | return QPlatformBackingStoreRhiConfig::Metal; |
996 | default: |
997 | return QPlatformBackingStoreRhiConfig::Null; |
998 | } |
999 | } |
1000 | |
1001 | // Never called by Software Rendering backend |
1002 | void QQuickWidgetPrivate::initializeWithRhi() |
1003 | { |
1004 | Q_Q(QQuickWidget); |
1005 | |
1006 | QWidgetPrivate *tlwd = QWidgetPrivate::get(w: q->window()); |
1007 | // when reparenting, the rhi may suddenly be different |
1008 | if (rhi) { |
1009 | QRhi *tlwRhi = nullptr; |
1010 | if (QWidgetRepaintManager *repaintManager = tlwd->maybeRepaintManager()) |
1011 | tlwRhi = repaintManager->rhi(); |
1012 | if (tlwRhi && rhi != tlwRhi) |
1013 | rhi = nullptr; |
1014 | } |
1015 | |
1016 | // On hide-show we may invalidate() (when !isPersistentSceneGraph) but our |
1017 | // context is kept. We may need to initialize() again, though. |
1018 | const bool onlyNeedsSgInit = rhi && !offscreenWindow->isSceneGraphInitialized(); |
1019 | |
1020 | if (!onlyNeedsSgInit) { |
1021 | if (rhi) |
1022 | return; |
1023 | |
1024 | if (QWidgetRepaintManager *repaintManager = tlwd->maybeRepaintManager()) |
1025 | rhi = repaintManager->rhi(); |
1026 | |
1027 | if (!rhi) { |
1028 | // The widget (and its parent chain, if any) may not be shown at |
1029 | // all, yet one may still want to use it for grabs. This is |
1030 | // ridiculous of course because the rendering infrastructure is |
1031 | // tied to the top-level widget that initializes upon expose, but |
1032 | // it has to be supported. |
1033 | offscreenRenderer.setConfig(rhiConfig()); |
1034 | offscreenRenderer.setFormat(q->format()); |
1035 | // no window passed in, so no swapchain, but we get a functional QRhi which we own |
1036 | if (offscreenRenderer.create()) |
1037 | rhi = offscreenRenderer.rhi(); |
1038 | } |
1039 | |
1040 | // Could be that something else already initialized the window with some |
1041 | // other graphics API for the QRhi, that's not good. |
1042 | if (rhi && rhi->backend() != QBackingStoreRhiSupport::apiToRhiBackend(api: graphicsApiToBackingStoreRhiApi(api: QQuickWindow::graphicsApi()))) { |
1043 | qWarning(msg: "The top-level window is not using the expected graphics API for composition, " |
1044 | "'%s' is not compatible with this QQuickWidget" , |
1045 | rhi->backendName()); |
1046 | rhi = nullptr; |
1047 | } |
1048 | } |
1049 | |
1050 | if (rhi) { |
1051 | if (!offscreenWindow->isSceneGraphInitialized()) { |
1052 | offscreenWindow->setGraphicsDevice(QQuickGraphicsDevice::fromRhi(rhi)); |
1053 | #if QT_CONFIG(vulkan) |
1054 | if (QWindow *w = q->window()->windowHandle()) |
1055 | offscreenWindow->setVulkanInstance(w->vulkanInstance()); |
1056 | #endif |
1057 | renderControl->initialize(); |
1058 | } |
1059 | } else { |
1060 | qWarning(msg: "QQuickWidget: Failed to get a QRhi from the top-level widget's window" ); |
1061 | } |
1062 | } |
1063 | |
1064 | void QQuickWidget::createFramebufferObject() |
1065 | { |
1066 | Q_D(QQuickWidget); |
1067 | |
1068 | // Could come from Show -> initializeWithRhi -> sceneGraphInitialized in which case the size may |
1069 | // still be invalid on some platforms. Bail out. A resize will come later on. |
1070 | if (size().isEmpty()) |
1071 | return; |
1072 | |
1073 | // Even though this is just an offscreen window we should set the position on it, as it might be |
1074 | // useful for an item to know the actual position of the scene. |
1075 | // Note: The position will be update when we get a move event (see: updatePosition()). |
1076 | const QPoint &globalPos = mapToGlobal(QPoint(0, 0)); |
1077 | d->offscreenWindow->setGeometry(posx: globalPos.x(), posy: globalPos.y(), w: width(), h: height()); |
1078 | |
1079 | if (d->useSoftwareRenderer) { |
1080 | const QSize imageSize = size() * devicePixelRatio(); |
1081 | d->softwareImage = QImage(imageSize, QImage::Format_ARGB32_Premultiplied); |
1082 | d->softwareImage.setDevicePixelRatio(devicePixelRatio()); |
1083 | d->forceFullUpdate = true; |
1084 | return; |
1085 | } |
1086 | |
1087 | if (!d->rhi) { |
1088 | qWarning(msg: "QQuickWidget: Attempted to create output texture with no QRhi" ); |
1089 | return; |
1090 | } |
1091 | |
1092 | int samples = d->requestedSamples; |
1093 | if (d->rhi->isFeatureSupported(feature: QRhi::MultisampleRenderBuffer)) |
1094 | samples = QSGRhiSupport::chooseSampleCount(samples, rhi: d->rhi); |
1095 | else |
1096 | samples = 0; |
1097 | |
1098 | const QSize fboSize = size() * devicePixelRatio(); |
1099 | |
1100 | // Could be a simple hide - show, in which case the previous texture is just fine. |
1101 | if (!d->outputTexture) { |
1102 | d->outputTexture = d->rhi->newTexture(format: QRhiTexture::RGBA8, pixelSize: fboSize, sampleCount: 1, flags: QRhiTexture::RenderTarget); |
1103 | if (!d->outputTexture->create()) { |
1104 | qWarning(msg: "QQuickWidget: failed to create output texture of size %dx%d" , |
1105 | fboSize.width(), fboSize.height()); |
1106 | } |
1107 | } |
1108 | if (!d->depthStencil) { |
1109 | d->depthStencil = d->rhi->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil, pixelSize: fboSize, sampleCount: samples); |
1110 | if (!d->depthStencil->create()) { |
1111 | qWarning(msg: "QQuickWidget: failed to create depth/stencil buffer of size %dx%d and sample count %d" , |
1112 | fboSize.width(), fboSize.height(), samples); |
1113 | } |
1114 | } |
1115 | if (samples > 1 && !d->msaaBuffer) { |
1116 | d->msaaBuffer = d->rhi->newRenderBuffer(type: QRhiRenderBuffer::Color, pixelSize: fboSize, sampleCount: samples); |
1117 | if (!d->msaaBuffer->create()) { |
1118 | qWarning(msg: "QQuickWidget: failed to create multisample renderbuffer of size %dx%d and sample count %d" , |
1119 | fboSize.width(), fboSize.height(), samples); |
1120 | } |
1121 | } |
1122 | if (!d->rt) { |
1123 | QRhiTextureRenderTargetDescription rtDesc; |
1124 | QRhiColorAttachment colorAtt; |
1125 | if (samples <= 1) { |
1126 | colorAtt.setTexture(d->outputTexture); |
1127 | } else { |
1128 | colorAtt.setRenderBuffer(d->msaaBuffer); |
1129 | colorAtt.setResolveTexture(d->outputTexture); |
1130 | } |
1131 | rtDesc.setColorAttachments({ colorAtt }); |
1132 | rtDesc.setDepthStencilBuffer(d->depthStencil); |
1133 | d->rt = d->rhi->newTextureRenderTarget(desc: rtDesc); |
1134 | d->rtRp = d->rt->newCompatibleRenderPassDescriptor(); |
1135 | d->rt->setRenderPassDescriptor(d->rtRp); |
1136 | d->rt->create(); |
1137 | } |
1138 | if (d->outputTexture->pixelSize() != fboSize) { |
1139 | d->outputTexture->setPixelSize(fboSize); |
1140 | if (!d->outputTexture->create()) { |
1141 | qWarning(msg: "QQuickWidget: failed to create resized output texture of size %dx%d" , |
1142 | fboSize.width(), fboSize.height()); |
1143 | } |
1144 | d->depthStencil->setPixelSize(fboSize); |
1145 | if (!d->depthStencil->create()) { |
1146 | qWarning(msg: "QQuickWidget: failed to create resized depth/stencil buffer of size %dx%d" , |
1147 | fboSize.width(), fboSize.height()); |
1148 | } |
1149 | if (d->msaaBuffer) { |
1150 | d->msaaBuffer->setPixelSize(fboSize); |
1151 | if (!d->msaaBuffer->create()) { |
1152 | qWarning(msg: "QQuickWidget: failed to create resized multisample renderbuffer of size %dx%d" , |
1153 | fboSize.width(), fboSize.height()); |
1154 | } |
1155 | } |
1156 | } |
1157 | |
1158 | d->offscreenWindow->setRenderTarget(QQuickRenderTarget::fromRhiRenderTarget(renderTarget: d->rt)); |
1159 | |
1160 | d->renderControl->setSamples(samples); |
1161 | |
1162 | // Sanity check: The window must not have an underlying platform window. |
1163 | // Having one would mean create() was called and platforms that only support |
1164 | // a single native window were in trouble. |
1165 | Q_ASSERT(!d->offscreenWindow->handle()); |
1166 | } |
1167 | |
1168 | void QQuickWidget::destroyFramebufferObject() |
1169 | { |
1170 | Q_D(QQuickWidget); |
1171 | |
1172 | if (d->useSoftwareRenderer) { |
1173 | d->softwareImage = QImage(); |
1174 | return; |
1175 | } |
1176 | |
1177 | delete d->rt; |
1178 | d->rt = nullptr; |
1179 | delete d->rtRp; |
1180 | d->rtRp = nullptr; |
1181 | delete d->depthStencil; |
1182 | d->depthStencil = nullptr; |
1183 | delete d->msaaBuffer; |
1184 | d->msaaBuffer = nullptr; |
1185 | delete d->outputTexture; |
1186 | d->outputTexture = nullptr; |
1187 | } |
1188 | |
1189 | QQuickWidget::ResizeMode QQuickWidget::resizeMode() const |
1190 | { |
1191 | Q_D(const QQuickWidget); |
1192 | return d->resizeMode; |
1193 | } |
1194 | |
1195 | /*! |
1196 | \internal |
1197 | */ |
1198 | void QQuickWidget::continueExecute() |
1199 | { |
1200 | Q_D(QQuickWidget); |
1201 | disconnect(sender: d->component, SIGNAL(statusChanged(QQmlComponent::Status)), receiver: this, SLOT(continueExecute())); |
1202 | |
1203 | if (d->component->isError()) { |
1204 | const QList<QQmlError> errorList = d->component->errors(); |
1205 | for (const QQmlError &error : errorList) { |
1206 | QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), nullptr).warning() |
1207 | << error; |
1208 | } |
1209 | emit statusChanged(status()); |
1210 | return; |
1211 | } |
1212 | |
1213 | QObject *obj = d->component->create(); |
1214 | |
1215 | if (d->component->isError()) { |
1216 | const QList<QQmlError> errorList = d->component->errors(); |
1217 | for (const QQmlError &error : errorList) { |
1218 | QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), nullptr).warning() |
1219 | << error; |
1220 | } |
1221 | emit statusChanged(status()); |
1222 | return; |
1223 | } |
1224 | |
1225 | d->setRootObject(obj); |
1226 | emit statusChanged(status()); |
1227 | } |
1228 | |
1229 | |
1230 | /*! |
1231 | \internal |
1232 | */ |
1233 | void QQuickWidgetPrivate::setRootObject(QObject *obj) |
1234 | { |
1235 | Q_Q(QQuickWidget); |
1236 | if (root == obj) |
1237 | return; |
1238 | if (QQuickItem *sgItem = qobject_cast<QQuickItem *>(o: obj)) { |
1239 | root = sgItem; |
1240 | sgItem->setParentItem(offscreenWindow->contentItem()); |
1241 | } else if (qobject_cast<QWindow *>(o: obj)) { |
1242 | qWarning() << "QQuickWidget does not support using windows as a root item." << Qt::endl |
1243 | << Qt::endl |
1244 | << "If you wish to create your root window from QML, consider using QQmlApplicationEngine instead." << Qt::endl; |
1245 | } else { |
1246 | qWarning() << "QQuickWidget only supports loading of root objects that derive from QQuickItem." << Qt::endl |
1247 | << Qt::endl |
1248 | << "Ensure your QML code is written for QtQuick 2, and uses a root that is or" << Qt::endl |
1249 | << "inherits from QtQuick's Item (not a Timer, QtObject, etc)." << Qt::endl; |
1250 | delete obj; |
1251 | root = nullptr; |
1252 | } |
1253 | if (root) { |
1254 | initialSize = rootObjectSize(); |
1255 | bool resized = q->testAttribute(attribute: Qt::WA_Resized); |
1256 | if ((resizeMode == QQuickWidget::SizeViewToRootObject || !resized) && |
1257 | initialSize != q->size()) { |
1258 | q->resize(initialSize); |
1259 | } |
1260 | initResize(); |
1261 | } |
1262 | } |
1263 | |
1264 | QPlatformBackingStoreRhiConfig QQuickWidgetPrivate::rhiConfig() const |
1265 | { |
1266 | const_cast<QQuickWidgetPrivate *>(this)->ensureBackingScene(); |
1267 | if (useSoftwareRenderer) |
1268 | return {}; |
1269 | |
1270 | QPlatformBackingStoreRhiConfig config(graphicsApiToBackingStoreRhiApi(api: QQuickWindow::graphicsApi())); |
1271 | |
1272 | QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: offscreenWindow); |
1273 | // This is only here to support some of the env.vars. (such as |
1274 | // QSG_RHI_DEBUG_LAYER). There is currently no way to set a |
1275 | // QQuickGraphicsConfiguration for a QQuickWidget, which means things like |
1276 | // the pipeline cache are just not available. That is something to support |
1277 | // on the widget/backingstore level since that's where the QRhi is |
1278 | // controlled in this case. |
1279 | const bool debugLayerRequested = wd->graphicsConfig.isDebugLayerEnabled(); |
1280 | config.setDebugLayer(debugLayerRequested); |
1281 | return config; |
1282 | } |
1283 | |
1284 | QWidgetPrivate::TextureData QQuickWidgetPrivate::texture() const |
1285 | { |
1286 | Q_Q(const QQuickWidget); |
1287 | if (!q->isWindow() && q->internalWinId()) { |
1288 | qWarning() << "QQuickWidget cannot be used as a native child widget." |
1289 | << "Consider setting Qt::AA_DontCreateNativeWidgetSiblings" ; |
1290 | return {}; |
1291 | } |
1292 | return { .textureLeft: outputTexture, .textureRight: nullptr }; |
1293 | } |
1294 | |
1295 | QPlatformTextureList::Flags QQuickWidgetPrivate::textureListFlags() |
1296 | { |
1297 | QPlatformTextureList::Flags flags = QWidgetPrivate::textureListFlags(); |
1298 | flags |= QPlatformTextureList::NeedsPremultipliedAlphaBlending; |
1299 | return flags; |
1300 | } |
1301 | |
1302 | /*! |
1303 | \internal |
1304 | Handle item resize and scene updates. |
1305 | */ |
1306 | void QQuickWidget::timerEvent(QTimerEvent* e) |
1307 | { |
1308 | Q_D(QQuickWidget); |
1309 | if (!e || e->timerId() == d->resizetimer.timerId()) { |
1310 | d->updateSize(); |
1311 | d->resizetimer.stop(); |
1312 | } else if (e->timerId() == d->updateTimer.timerId()) { |
1313 | d->eventPending = false; |
1314 | d->updateTimer.stop(); |
1315 | if (d->updatePending) |
1316 | d->renderSceneGraph(); |
1317 | } |
1318 | } |
1319 | |
1320 | /*! |
1321 | \internal |
1322 | Preferred size follows the root object geometry. |
1323 | */ |
1324 | QSize QQuickWidget::sizeHint() const |
1325 | { |
1326 | Q_D(const QQuickWidget); |
1327 | QSize rootObjectSize = d->rootObjectSize(); |
1328 | if (rootObjectSize.isEmpty()) { |
1329 | return size(); |
1330 | } else { |
1331 | return rootObjectSize; |
1332 | } |
1333 | } |
1334 | |
1335 | /*! |
1336 | Returns the initial size of the root object. |
1337 | |
1338 | If \l resizeMode is SizeRootObjectToView, the root object will be |
1339 | resized to the size of the view. This function returns the size of the |
1340 | root object before it was resized. |
1341 | */ |
1342 | QSize QQuickWidget::initialSize() const |
1343 | { |
1344 | Q_D(const QQuickWidget); |
1345 | return d->initialSize; |
1346 | } |
1347 | |
1348 | /*! |
1349 | Returns the view's root \l {QQuickItem} {item}. Can be null |
1350 | when setSource() has not been called, if it was called with |
1351 | broken QtQuick code or while the QtQuick contents are being created. |
1352 | */ |
1353 | QQuickItem *QQuickWidget::rootObject() const |
1354 | { |
1355 | Q_D(const QQuickWidget); |
1356 | return d->root; |
1357 | } |
1358 | |
1359 | /*! |
1360 | \internal |
1361 | This function handles the \l {QResizeEvent} {resize event} |
1362 | \a e. |
1363 | */ |
1364 | void QQuickWidget::resizeEvent(QResizeEvent *e) |
1365 | { |
1366 | Q_D(QQuickWidget); |
1367 | if (d->resizeMode == SizeRootObjectToView) |
1368 | d->updateSize(); |
1369 | |
1370 | if (e->size().isEmpty()) { |
1371 | //stop rendering |
1372 | d->fakeHidden = true; |
1373 | return; |
1374 | } |
1375 | |
1376 | bool needsSync = false; |
1377 | if (d->fakeHidden) { |
1378 | //restart rendering |
1379 | d->fakeHidden = false; |
1380 | needsSync = true; |
1381 | } |
1382 | |
1383 | // Software Renderer |
1384 | if (d->useSoftwareRenderer) { |
1385 | needsSync = true; |
1386 | if (d->softwareImage.size() != size() * devicePixelRatio()) { |
1387 | createFramebufferObject(); |
1388 | } |
1389 | } else { |
1390 | if (d->rhi) { |
1391 | // Bail out when receiving a resize after scenegraph invalidation. This can happen |
1392 | // during hide - resize - show sequences and also during application exit. |
1393 | if (!d->outputTexture && !d->offscreenWindow->isSceneGraphInitialized()) |
1394 | return; |
1395 | if (!d->outputTexture || d->outputTexture->pixelSize() != size() * devicePixelRatio()) { |
1396 | needsSync = true; |
1397 | createFramebufferObject(); |
1398 | } |
1399 | } else { |
1400 | // This will result in a scenegraphInitialized() signal which |
1401 | // is connected to createFramebufferObject(). |
1402 | needsSync = true; |
1403 | d->initializeWithRhi(); |
1404 | } |
1405 | |
1406 | if (!d->rhi) { |
1407 | qWarning(msg: "QQuickWidget::resizeEvent() no QRhi" ); |
1408 | return; |
1409 | } |
1410 | } |
1411 | |
1412 | d->render(needsSync); |
1413 | } |
1414 | |
1415 | /*! \reimp */ |
1416 | bool QQuickWidget::focusNextPrevChild(bool next) |
1417 | { |
1418 | Q_D(QQuickWidget); |
1419 | QKeyEvent event(QEvent::KeyPress, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier); |
1420 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyPress, event.key(), |
1421 | Qt::NoModifier); |
1422 | QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &event); |
1423 | |
1424 | QKeyEvent releaseEvent(QEvent::KeyRelease, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier); |
1425 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyRelease, releaseEvent.key(), |
1426 | Qt::NoModifier); |
1427 | QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &releaseEvent); |
1428 | return event.isAccepted(); |
1429 | } |
1430 | |
1431 | /*! \reimp */ |
1432 | void QQuickWidget::keyPressEvent(QKeyEvent *e) |
1433 | { |
1434 | Q_D(QQuickWidget); |
1435 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyPress, e->key(), |
1436 | e->modifiers()); |
1437 | |
1438 | QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: e); |
1439 | } |
1440 | |
1441 | /*! \reimp */ |
1442 | void QQuickWidget::keyReleaseEvent(QKeyEvent *e) |
1443 | { |
1444 | Q_D(QQuickWidget); |
1445 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyRelease, e->key(), |
1446 | e->modifiers()); |
1447 | |
1448 | QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: e); |
1449 | } |
1450 | |
1451 | /*! \reimp */ |
1452 | void QQuickWidget::mouseMoveEvent(QMouseEvent *e) |
1453 | { |
1454 | Q_D(QQuickWidget); |
1455 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseMove, e->position().x(), |
1456 | e->position().y()); |
1457 | |
1458 | // Put position into the event's position and scenePosition, and globalPosition into the |
1459 | // event's globalPosition. This way the scenePosition in e is ignored and is replaced by |
1460 | // position. This is necessary because QQuickWindow thinks of itself as a |
1461 | // top-level window always. |
1462 | QMouseEvent mappedEvent(e->type(), e->position(), e->position(), e->globalPosition(), |
1463 | e->button(), e->buttons(), e->modifiers(), e->source()); |
1464 | // It's not just the timestamp but also the globalPressPosition, velocity etc. |
1465 | mappedEvent.setTimestamp(e->timestamp()); |
1466 | QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &mappedEvent); |
1467 | e->setAccepted(mappedEvent.isAccepted()); |
1468 | } |
1469 | |
1470 | /*! \reimp */ |
1471 | void QQuickWidget::mouseDoubleClickEvent(QMouseEvent *e) |
1472 | { |
1473 | Q_D(QQuickWidget); |
1474 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseDoubleClick, |
1475 | e->button(), e->buttons()); |
1476 | |
1477 | // As the second mouse press is suppressed in widget windows we emulate it here for QML. |
1478 | // See QTBUG-25831 |
1479 | QMouseEvent pressEvent(QEvent::MouseButtonPress, e->position(), e->position(), e->globalPosition(), |
1480 | e->button(), e->buttons(), e->modifiers(), e->source()); |
1481 | pressEvent.setTimestamp(e->timestamp()); |
1482 | QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &pressEvent); |
1483 | e->setAccepted(pressEvent.isAccepted()); |
1484 | QMouseEvent mappedEvent(e->type(), e->position(), e->position(), e->globalPosition(), |
1485 | e->button(), e->buttons(), e->modifiers(), e->source()); |
1486 | mappedEvent.setTimestamp(e->timestamp()); |
1487 | QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &mappedEvent); |
1488 | } |
1489 | |
1490 | /*! \reimp */ |
1491 | void QQuickWidget::showEvent(QShowEvent *) |
1492 | { |
1493 | Q_D(QQuickWidget); |
1494 | bool shouldTriggerUpdate = true; |
1495 | |
1496 | if (!d->useSoftwareRenderer) { |
1497 | d->initializeWithRhi(); |
1498 | |
1499 | if (d->offscreenWindow->isSceneGraphInitialized()) { |
1500 | shouldTriggerUpdate = false; |
1501 | d->render(needsSync: true); |
1502 | // render() may have led to a QQuickWindow::update() call (for |
1503 | // example, having a scene with a QQuickFramebufferObject::Renderer |
1504 | // calling update() in its render()) which in turn results in |
1505 | // renderRequested in the rendercontrol, ending up in |
1506 | // triggerUpdate. In this case just calling update() is not |
1507 | // acceptable, we need the full renderSceneGraph issued from |
1508 | // timerEvent(). |
1509 | if (!d->eventPending && d->updatePending) { |
1510 | d->updatePending = false; |
1511 | update(); |
1512 | } |
1513 | } |
1514 | } |
1515 | |
1516 | if (shouldTriggerUpdate) |
1517 | triggerUpdate(); |
1518 | |
1519 | // note offscreenWindow is "QQuickWidgetOffscreenWindow" instance |
1520 | d->offscreenWindow->setVisible(true); |
1521 | if (QQmlInspectorService *service = QQmlDebugConnector::service<QQmlInspectorService>()) |
1522 | service->setParentWindow(d->offscreenWindow, window()->windowHandle()); |
1523 | } |
1524 | |
1525 | /*! \reimp */ |
1526 | void QQuickWidget::hideEvent(QHideEvent *) |
1527 | { |
1528 | Q_D(QQuickWidget); |
1529 | if (!d->offscreenWindow->isPersistentSceneGraph()) |
1530 | d->invalidateRenderControl(); |
1531 | // note offscreenWindow is "QQuickWidgetOffscreenWindow" instance |
1532 | d->offscreenWindow->setVisible(false); |
1533 | if (QQmlInspectorService *service = QQmlDebugConnector::service<QQmlInspectorService>()) |
1534 | service->setParentWindow(d->offscreenWindow, d->offscreenWindow); |
1535 | } |
1536 | |
1537 | /*! \reimp */ |
1538 | void QQuickWidget::mousePressEvent(QMouseEvent *e) |
1539 | { |
1540 | Q_D(QQuickWidget); |
1541 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMousePress, e->button(), |
1542 | e->buttons()); |
1543 | |
1544 | QMouseEvent mappedEvent(e->type(), e->position(), e->position(), e->globalPosition(), |
1545 | e->button(), e->buttons(), e->modifiers(), e->source()); |
1546 | mappedEvent.setTimestamp(e->timestamp()); |
1547 | QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &mappedEvent); |
1548 | e->setAccepted(mappedEvent.isAccepted()); |
1549 | } |
1550 | |
1551 | /*! \reimp */ |
1552 | void QQuickWidget::mouseReleaseEvent(QMouseEvent *e) |
1553 | { |
1554 | Q_D(QQuickWidget); |
1555 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseRelease, e->button(), |
1556 | e->buttons()); |
1557 | |
1558 | QMouseEvent mappedEvent(e->type(), e->position(), e->position(), e->globalPosition(), |
1559 | e->button(), e->buttons(), e->modifiers(), e->source()); |
1560 | mappedEvent.setTimestamp(e->timestamp()); |
1561 | QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &mappedEvent); |
1562 | e->setAccepted(mappedEvent.isAccepted()); |
1563 | } |
1564 | |
1565 | #if QT_CONFIG(wheelevent) |
1566 | /*! \reimp */ |
1567 | void QQuickWidget::wheelEvent(QWheelEvent *e) |
1568 | { |
1569 | Q_D(QQuickWidget); |
1570 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseWheel, |
1571 | e->angleDelta().x(), e->angleDelta().y()); |
1572 | |
1573 | // Wheel events only have local and global positions, no need to map. |
1574 | QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: e); |
1575 | } |
1576 | #endif |
1577 | |
1578 | /*! |
1579 | \reimp |
1580 | */ |
1581 | void QQuickWidget::focusInEvent(QFocusEvent * event) |
1582 | { |
1583 | Q_D(QQuickWidget); |
1584 | d->offscreenWindow->focusInEvent(event); |
1585 | } |
1586 | |
1587 | /*! |
1588 | \reimp |
1589 | */ |
1590 | void QQuickWidget::focusOutEvent(QFocusEvent * event) |
1591 | { |
1592 | Q_D(QQuickWidget); |
1593 | d->offscreenWindow->focusOutEvent(event); |
1594 | } |
1595 | |
1596 | static Qt::WindowState resolveWindowState(Qt::WindowStates states) |
1597 | { |
1598 | // No more than one of these 3 can be set |
1599 | if (states & Qt::WindowMinimized) |
1600 | return Qt::WindowMinimized; |
1601 | if (states & Qt::WindowMaximized) |
1602 | return Qt::WindowMaximized; |
1603 | if (states & Qt::WindowFullScreen) |
1604 | return Qt::WindowFullScreen; |
1605 | |
1606 | // No state means "windowed" - we ignore Qt::WindowActive |
1607 | return Qt::WindowNoState; |
1608 | } |
1609 | |
1610 | static void remapInputMethodQueryEvent(QObject *object, QInputMethodQueryEvent *e) |
1611 | { |
1612 | auto item = qobject_cast<QQuickItem *>(o: object); |
1613 | if (!item) |
1614 | return; |
1615 | |
1616 | // Remap all QRectF values. |
1617 | for (auto query : {Qt::ImCursorRectangle, Qt::ImAnchorRectangle, Qt::ImInputItemClipRectangle}) { |
1618 | if (e->queries() & query) { |
1619 | auto value = e->value(query); |
1620 | if (value.canConvert<QRectF>()) |
1621 | e->setValue(query, value: item->mapRectToScene(rect: value.toRectF())); |
1622 | } |
1623 | } |
1624 | // Remap all QPointF values. |
1625 | if (e->queries() & Qt::ImCursorPosition) { |
1626 | auto value = e->value(query: Qt::ImCursorPosition); |
1627 | if (value.canConvert<QPointF>()) |
1628 | e->setValue(query: Qt::ImCursorPosition, value: item->mapToScene(point: value.toPointF())); |
1629 | } |
1630 | } |
1631 | |
1632 | /*! \reimp */ |
1633 | bool QQuickWidget::event(QEvent *e) |
1634 | { |
1635 | Q_D(QQuickWidget); |
1636 | |
1637 | switch (e->type()) { |
1638 | |
1639 | case QEvent::Leave: |
1640 | case QEvent::TouchBegin: |
1641 | case QEvent::TouchEnd: |
1642 | case QEvent::TouchUpdate: |
1643 | case QEvent::TouchCancel: { |
1644 | // Touch events only have local and global positions, no need to map. |
1645 | bool res = QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: e); |
1646 | if (e->isAccepted() && e->type() == QEvent::TouchBegin) { |
1647 | // If the TouchBegin got accepted, then make sure all points that have |
1648 | // an exclusive grabber are also accepted so that the widget code for |
1649 | // delivering touch events make this widget an implicit grabber of those |
1650 | // points. |
1651 | QPointerEvent *pointerEvent = static_cast<QPointerEvent *>(e); |
1652 | auto deliveredPoints = pointerEvent->points(); |
1653 | for (auto &point : deliveredPoints) { |
1654 | if (pointerEvent->exclusiveGrabber(point)) |
1655 | point.setAccepted(true); |
1656 | } |
1657 | } |
1658 | return res; |
1659 | } |
1660 | |
1661 | case QEvent::FocusAboutToChange: |
1662 | return QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: e); |
1663 | |
1664 | case QEvent::InputMethod: |
1665 | return QCoreApplication::sendEvent(receiver: d->offscreenWindow->focusObject(), event: e); |
1666 | case QEvent::InputMethodQuery: |
1667 | { |
1668 | bool eventResult = QCoreApplication::sendEvent(receiver: d->offscreenWindow->focusObject(), event: e); |
1669 | // The result in focusObject are based on offscreenWindow. But |
1670 | // the inputMethodTransform won't get updated because the focus |
1671 | // is on QQuickWidget. We need to remap the value based on the |
1672 | // widget. |
1673 | remapInputMethodQueryEvent(object: d->offscreenWindow->focusObject(), e: static_cast<QInputMethodQueryEvent *>(e)); |
1674 | return eventResult; |
1675 | } |
1676 | |
1677 | case QEvent::WindowAboutToChangeInternal: |
1678 | d->invalidateRenderControl(); |
1679 | d->deviceLost = true; |
1680 | d->rhi = nullptr; |
1681 | break; |
1682 | |
1683 | case QEvent::WindowChangeInternal: |
1684 | d->handleWindowChange(); |
1685 | break; |
1686 | |
1687 | case QEvent::ScreenChangeInternal: |
1688 | { |
1689 | QScreen *newScreen = screen(); |
1690 | if (d->offscreenWindow) |
1691 | d->offscreenWindow->setScreen(newScreen); |
1692 | break; |
1693 | } |
1694 | case QEvent::DevicePixelRatioChange: |
1695 | if (d->useSoftwareRenderer || d->outputTexture) { |
1696 | // This will check the size taking the devicePixelRatio into account |
1697 | // and recreate if needed. |
1698 | createFramebufferObject(); |
1699 | d->render(needsSync: true); |
1700 | } |
1701 | if (d->offscreenWindow) { |
1702 | QEvent dprChangeEvent(QEvent::DevicePixelRatioChange); |
1703 | QGuiApplication::sendEvent(receiver: d->offscreenWindow, event: &dprChangeEvent); |
1704 | } |
1705 | break; |
1706 | case QEvent::Show: |
1707 | case QEvent::Move: |
1708 | d->updatePosition(); |
1709 | break; |
1710 | |
1711 | case QEvent::WindowStateChange: |
1712 | d->offscreenWindow->setWindowState(resolveWindowState(states: windowState())); |
1713 | break; |
1714 | |
1715 | case QEvent::ShortcutOverride: |
1716 | return QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: e); |
1717 | |
1718 | case QEvent::Enter: { |
1719 | QEnterEvent *enterEvent = static_cast<QEnterEvent *>(e); |
1720 | QEnterEvent mappedEvent(enterEvent->position(), enterEvent->scenePosition(), |
1721 | enterEvent->globalPosition()); |
1722 | const bool ret = QCoreApplication::sendEvent(receiver: d->offscreenWindow, event: &mappedEvent); |
1723 | e->setAccepted(mappedEvent.isAccepted()); |
1724 | return ret; |
1725 | } |
1726 | default: |
1727 | break; |
1728 | } |
1729 | |
1730 | return QWidget::event(event: e); |
1731 | } |
1732 | |
1733 | #if QT_CONFIG(quick_draganddrop) |
1734 | |
1735 | /*! \reimp */ |
1736 | void QQuickWidget::dragEnterEvent(QDragEnterEvent *e) |
1737 | { |
1738 | Q_D(QQuickWidget); |
1739 | // Don't reject drag events for the entire widget when one |
1740 | // item rejects the drag enter |
1741 | d->offscreenWindow->event(e); |
1742 | e->accept(); |
1743 | } |
1744 | |
1745 | /*! \reimp */ |
1746 | void QQuickWidget::dragMoveEvent(QDragMoveEvent *e) |
1747 | { |
1748 | Q_D(QQuickWidget); |
1749 | // Drag/drop events only have local pos, so no need to map, |
1750 | // but QQuickWindow::event() does not return true |
1751 | d->offscreenWindow->event(e); |
1752 | } |
1753 | |
1754 | /*! \reimp */ |
1755 | void QQuickWidget::dragLeaveEvent(QDragLeaveEvent *e) |
1756 | { |
1757 | Q_D(QQuickWidget); |
1758 | d->offscreenWindow->event(e); |
1759 | } |
1760 | |
1761 | /*! \reimp */ |
1762 | void QQuickWidget::dropEvent(QDropEvent *e) |
1763 | { |
1764 | Q_D(QQuickWidget); |
1765 | d->offscreenWindow->event(e); |
1766 | } |
1767 | |
1768 | #endif // quick_draganddrop |
1769 | |
1770 | // TODO: try to separate the two cases of |
1771 | // 1. render() unconditionally without sync |
1772 | // 2. sync() and then render if necessary |
1773 | void QQuickWidget::triggerUpdate() |
1774 | { |
1775 | Q_D(QQuickWidget); |
1776 | d->updatePending = true; |
1777 | if (!d->eventPending) { |
1778 | // There's no sense in immediately kicking a render off now, as |
1779 | // there may be a number of triggerUpdate calls to come from a multitude |
1780 | // of different sources (network, touch/mouse/keyboard, timers, |
1781 | // animations, ...), and we want to batch them all into single frames as |
1782 | // much as possible for the sake of interactivity and responsiveness. |
1783 | // |
1784 | // To achieve this, we set a timer and only perform the rendering when |
1785 | // this is complete. |
1786 | const int exhaustDelay = 5; |
1787 | d->updateTimer.start(msec: exhaustDelay, t: Qt::PreciseTimer, obj: this); |
1788 | d->eventPending = true; |
1789 | } |
1790 | } |
1791 | |
1792 | /*! |
1793 | Sets the surface \a format for the context and offscreen surface used |
1794 | by this widget. |
1795 | |
1796 | Call this function when there is a need to request a context for a |
1797 | given OpenGL version or profile. The sizes for depth, stencil and |
1798 | alpha buffers are taken care of automatically and there is no need |
1799 | to request those explicitly. |
1800 | |
1801 | \sa QWindow::setFormat(), QWindow::format(), format() |
1802 | */ |
1803 | void QQuickWidget::setFormat(const QSurfaceFormat &format) |
1804 | { |
1805 | Q_D(QQuickWidget); |
1806 | QSurfaceFormat currentFormat = d->offscreenWindow->format(); |
1807 | QSurfaceFormat newFormat = format; |
1808 | newFormat.setDepthBufferSize(qMax(a: newFormat.depthBufferSize(), b: currentFormat.depthBufferSize())); |
1809 | newFormat.setStencilBufferSize(qMax(a: newFormat.stencilBufferSize(), b: currentFormat.stencilBufferSize())); |
1810 | newFormat.setAlphaBufferSize(qMax(a: newFormat.alphaBufferSize(), b: currentFormat.alphaBufferSize())); |
1811 | |
1812 | // Do not include the sample count. Requesting a multisampled context is not necessary |
1813 | // since we render into an FBO, never to an actual surface. What's more, attempting to |
1814 | // create a pbuffer with a multisampled config crashes certain implementations. Just |
1815 | // avoid the entire hassle, the result is the same. |
1816 | d->requestedSamples = newFormat.samples(); |
1817 | newFormat.setSamples(0); |
1818 | |
1819 | d->offscreenWindow->setFormat(newFormat); |
1820 | } |
1821 | |
1822 | /*! |
1823 | Returns the actual surface format. |
1824 | |
1825 | If the widget has not yet been shown, the requested format is returned. |
1826 | |
1827 | \sa setFormat() |
1828 | */ |
1829 | QSurfaceFormat QQuickWidget::format() const |
1830 | { |
1831 | Q_D(const QQuickWidget); |
1832 | return d->offscreenWindow->format(); |
1833 | } |
1834 | |
1835 | /*! |
1836 | Renders a frame and reads it back into an image. |
1837 | |
1838 | \note This is a potentially expensive operation. |
1839 | */ |
1840 | QImage QQuickWidget::grabFramebuffer() const |
1841 | { |
1842 | return const_cast<QQuickWidgetPrivate *>(d_func())->grabFramebuffer(); |
1843 | } |
1844 | |
1845 | /*! |
1846 | Sets the clear \a color. By default this is an opaque color. |
1847 | |
1848 | To get a semi-transparent QQuickWidget, call this function with |
1849 | \a color set to Qt::transparent, set the Qt::WA_TranslucentBackground |
1850 | widget attribute on the top-level window, and request an alpha |
1851 | channel via setFormat(). |
1852 | |
1853 | \sa QQuickWindow::setColor() |
1854 | */ |
1855 | void QQuickWidget::setClearColor(const QColor &color) |
1856 | { |
1857 | Q_D(QQuickWidget); |
1858 | d->offscreenWindow->setColor(color); |
1859 | } |
1860 | |
1861 | /*! |
1862 | \since 5.5 |
1863 | |
1864 | Returns the offscreen QQuickWindow which is used by this widget to drive |
1865 | the Qt Quick rendering. This is useful if you want to use QQuickWindow |
1866 | APIs that are not currently exposed by QQuickWidget, for instance |
1867 | connecting to the QQuickWindow::beforeRendering() signal in order |
1868 | to draw native OpenGL content below Qt Quick's own rendering. |
1869 | |
1870 | \warning Use the return value of this function with caution. In |
1871 | particular, do not ever attempt to show the QQuickWindow, and be |
1872 | very careful when using other QWindow-only APIs. |
1873 | |
1874 | \warning The offscreen window may be deleted (and recreated) during |
1875 | the life time of the QQuickWidget, particularly when the widget is |
1876 | moved to another QQuickWindow. If you need to know when the window |
1877 | has been replaced, connect to its destroyed() signal. |
1878 | */ |
1879 | QQuickWindow *QQuickWidget::quickWindow() const |
1880 | { |
1881 | Q_D(const QQuickWidget); |
1882 | return d->offscreenWindow; |
1883 | } |
1884 | |
1885 | /*! |
1886 | \reimp |
1887 | */ |
1888 | void QQuickWidget::paintEvent(QPaintEvent *event) |
1889 | { |
1890 | Q_D(QQuickWidget); |
1891 | if (d->useSoftwareRenderer) { |
1892 | QPainter painter(this); |
1893 | d->updateRegion = d->updateRegion.united(r: event->region()); |
1894 | if (d->updateRegion.isNull()) { |
1895 | //Paint everything |
1896 | painter.drawImage(r: rect(), image: d->softwareImage); |
1897 | } else { |
1898 | QTransform transform; |
1899 | transform.scale(sx: devicePixelRatio(), sy: devicePixelRatio()); |
1900 | //Paint only the updated areas |
1901 | QRegion targetRegion; |
1902 | d->updateRegion.swap(other&: targetRegion); |
1903 | for (auto targetRect : targetRegion) { |
1904 | auto sourceRect = transform.mapRect(QRectF(targetRect)); |
1905 | painter.drawImage(targetRect, image: d->softwareImage, sourceRect); |
1906 | } |
1907 | } |
1908 | } |
1909 | } |
1910 | |
1911 | void QQuickWidget::propagateFocusObjectChanged(QObject *focusObject) |
1912 | { |
1913 | Q_D(QQuickWidget); |
1914 | if (QApplication::focusObject() != this) |
1915 | return; |
1916 | if (QWindow *window = d->windowHandle(mode: QWidgetPrivate::WindowHandleMode::TopLevel)) |
1917 | emit window->focusObjectChanged(object: focusObject); |
1918 | } |
1919 | |
1920 | QT_END_NAMESPACE |
1921 | |
1922 | #include "moc_qquickwidget_p.cpp" |
1923 | |
1924 | #include "moc_qquickwidget.cpp" |
1925 | |