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