1 // QQuickDrawable.cxx - OSG Drawable using a QQuickRenderControl to draw
2 //
3 // Copyright (C) 2019  James Turner  <james@flightgear.org>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 
19 #include "config.h"
20 
21 #include <simgear/compiler.h>
22 #include <atomic>
23 
24 #include "QQuickDrawable.hxx"
25 
26 #include <QQmlComponent>
27 #include <QQmlContext>
28 #include <QQmlEngine>
29 #include <QQuickRenderControl>
30 
31 #include <QTimer>
32 #include <QCoreApplication>
33 #include <QOpenGLContext>
34 #include <QOpenGLFunctions>
35 #include <QQuickItem>
36 #include <QQuickWindow>
37 #include <QSurfaceFormat>
38 #include <QThread>
39 
40 
41 // private Qt headers, needed to make glue work between Qt and OSG
42 // graphics window unfortunately.
43 #include <private/qopenglcontext_p.h>
44 
45 #if defined(SG_MAC)
46 #  include "fake_qguiapp_p.h"
47 #else
48 #  include <private/qguiapplication_p.h>
49 #endif
50 
51 #include <osg/GraphicsContext>
52 #include <osgGA/GUIEventAdapter>
53 #include <osgGA/GUIEventHandler>
54 #include <osgViewer/GraphicsWindow>
55 #include <osgViewer/Viewer>
56 
57 #include <GUI/DialogStateController.hxx>
58 #include <GUI/FGQQWindowManager.hxx>
59 #include <GUI/FGQmlInstance.hxx>
60 #include <GUI/FGQmlPropertyNode.hxx>
61 #include <Main/fg_props.hxx>
62 #include <Main/globals.hxx>
63 #include <Viewer/OSGQtAdaption.hxx>
64 #include <simgear/structure/commands.hxx>
65 
66 #if defined(HAVE_PUI)
67 #include "FlightGear_pu.h"
68 #endif
69 
70 using namespace osgGA;
71 
72 struct QtKey {
QtKeyQtKey73     QtKey(int _o, int _q, QString _s = {}) : osg(_o), qt(_q), s(_s) {}
74 
75     int osg;
76     int qt;
77     QString s;
78 };
79 
80 const std::initializer_list<QtKey> keymapInit = {
81     // contains all the key mappings except 0..9 and A..Z which are generated
82     // programatically
83 
84     {GUIEventAdapter::KEY_Space, Qt::Key_Space, " "},
85     {GUIEventAdapter::KEY_Escape, Qt::Key_Escape, "\x1B"},
86     {GUIEventAdapter::KEY_Return, Qt::Key_Return, "\r"},
87     {GUIEventAdapter::KEY_Tab, Qt::Key_Tab, "\t"},
88     {GUIEventAdapter::KEY_BackSpace, Qt::Key_Backspace, "\x08"},
89     {GUIEventAdapter::KEY_Delete, Qt::Key_Delete, "\x7f"},
90 
91     {GUIEventAdapter::KEY_Period, Qt::Key_Period, "."},
92     {GUIEventAdapter::KEY_Comma, Qt::Key_Comma, ","},
93     {GUIEventAdapter::KEY_Colon, Qt::Key_Colon, ":"},
94     {GUIEventAdapter::KEY_Quote, Qt::Key_QuoteLeft, "'"},
95     {GUIEventAdapter::KEY_Quotedbl, Qt::Key_QuoteDbl, "\""},
96     {GUIEventAdapter::KEY_Underscore, Qt::Key_Underscore, "_"},
97     {GUIEventAdapter::KEY_Plus, Qt::Key_Plus, "+"},
98     {GUIEventAdapter::KEY_Minus, Qt::Key_Minus, "-"},
99     {GUIEventAdapter::KEY_Asterisk, Qt::Key_Asterisk, "*"},
100     {GUIEventAdapter::KEY_Equals, Qt::Key_Equal, "="},
101     {GUIEventAdapter::KEY_Slash, Qt::Key_Slash, "/"},
102 
103     {GUIEventAdapter::KEY_Left, Qt::Key_Left},
104     {GUIEventAdapter::KEY_Right, Qt::Key_Right},
105     {GUIEventAdapter::KEY_Up, Qt::Key_Up},
106     {GUIEventAdapter::KEY_Down, Qt::Key_Down},
107 
108     {GUIEventAdapter::KEY_Shift_L, Qt::Key_Shift},
109     {GUIEventAdapter::KEY_Shift_R, Qt::Key_Shift},
110     {GUIEventAdapter::KEY_Control_L, Qt::Key_Control},
111     {GUIEventAdapter::KEY_Control_R, Qt::Key_Control},
112     {GUIEventAdapter::KEY_Meta_L, Qt::Key_Meta},
113     {GUIEventAdapter::KEY_Meta_R, Qt::Key_Meta},
114 
115 
116 };
117 
118 std::vector<QtKey> global_keymap;
119 
120 class CustomRenderControl : public QQuickRenderControl
121 {
122 public:
CustomRenderControl(QWindow * win)123     CustomRenderControl(QWindow* win)
124         : _qWindow(win)
125     {
126         // Q_ASSERT(win);
127     }
128 
renderWindow(QPoint * offset)129     QWindow* renderWindow(QPoint* offset) override
130     {
131         if (offset) {
132             *offset = QPoint(0, 0);
133         }
134         return _qWindow;
135     }
136 
137 private:
138     QWindow* _qWindow = nullptr;
139 };
140 
141 class QQuickDrawablePrivate : public QObject
142 {
143     Q_OBJECT
144 public:
QQuickDrawablePrivate()145     QQuickDrawablePrivate() :
146         renderControlInited(false)
147     {
148 
149     }
150 
~QQuickDrawablePrivate()151     ~QQuickDrawablePrivate()
152     {
153 
154     }
155 
156     CustomRenderControl* renderControl = nullptr;
157 
158     QQmlComponent* qmlComponent = nullptr;
159     QQmlEngine* qmlEngine = nullptr;
160     bool syncRequired = true;
161 
162     QQuickItem* rootItem = nullptr;
163 
164     // this window is neither created()-ed nor shown but is needed by
165     // QQuickRenderControl for historical reasons, and gives us a place to
166     // inject events from the OSG side.
167     QQuickWindow* quickWindow = nullptr;
168 
169     // window representing the OSG window, needed
170     // for making our adpoted context current
171     QWindow* foreignOSGWindow = nullptr;
172     QOpenGLContext* qtContext = nullptr;
173     osg::GraphicsContext* osgContext = nullptr;
174 
175     std::atomic_bool renderControlInited;
176     std::atomic_bool syncPending;
177 
frameEvent()178     void frameEvent()
179     {
180         if (syncRequired) {
181             renderControl->polishItems();
182             syncRequired = false;
183             syncPending = true;
184 
185             osgContext->add(flightgear::makeGraphicsOp("Sync QQ2 Render control", [this](osg::GraphicsContext*) {
186                 QOpenGLContextPrivate::setCurrentContext(qtContext);
187                 renderControl->sync();
188                 syncPending = false;
189             }));
190         }
191     }
192 public slots:
onComponentLoaded()193     void onComponentLoaded()
194     {
195         if (qmlComponent->isError()) {
196             QList<QQmlError> errorList = qmlComponent->errors();
197             Q_FOREACH (const QQmlError& error, errorList) {
198                 qWarning() << error.url() << error.line() << error;
199             }
200             return;
201         }
202 
203         QObject* rootObject = qmlComponent->create();
204         if (qmlComponent->isError()) {
205             QList<QQmlError> errorList = qmlComponent->errors();
206             Q_FOREACH (const QQmlError& error, errorList) {
207                 qWarning() << error.url() << error.line() << error;
208             }
209             return;
210         }
211 
212         rootItem = qobject_cast<QQuickItem*>(rootObject);
213         if (!rootItem) {
214             qWarning() << Q_FUNC_INFO << "root object not a QQuickItem" << rootObject;
215             delete rootObject;
216             return;
217         }
218 
219         // The root item is ready. Associate it with the window.
220         rootItem->setParentItem(quickWindow->contentItem());
221         syncRequired = true;
222         rootItem->setWidth(quickWindow->width());
223         rootItem->setHeight(quickWindow->height());
224     }
225 
initRenderControl()226     void initRenderControl()
227     {
228         qtContext = flightgear::qtContextFromOSG(osgContext);
229 
230     #if QT_VERSION < 0x050600
231         SG_LOG(SG_GUI, SG_ALERT, "Qt < 5.6 was used to build FlightGear, multi-threading of QtQuick is not safe");
232     #else
233         renderControl->prepareThread(QThread::currentThread());
234     #endif
235 
236         QOpenGLContextPrivate::setCurrentContext(qtContext);
237         QOpenGLContextPrivate::get(qtContext)->surface = foreignOSGWindow;
238         renderControl->initialize(qtContext);
239 
240         renderControlInited = true;
241     }
242 
onSceneChanged()243     void onSceneChanged()
244     {
245         syncRequired = true;
246     }
247 
onRenderRequested()248     void onRenderRequested()
249     {
250         qWarning() << Q_FUNC_INFO;
251     }
252 
onWindowActiveFocusItemChanged()253     void onWindowActiveFocusItemChanged()
254     {
255         if (quickWindow->activeFocusItem())
256             qInfo() << Q_FUNC_INFO << "Active focus item is now:" << quickWindow->activeFocusItem();
257         else
258             qInfo() << Q_FUNC_INFO << "Active focus cleared";
259     }
260 };
261 
262 
fgqmlinstance_provider(QQmlEngine * engine,QJSEngine * scriptEngine)263 static QObject* fgqmlinstance_provider(QQmlEngine* engine, QJSEngine* scriptEngine)
264 {
265     Q_UNUSED(engine)
266     Q_UNUSED(scriptEngine)
267 
268     FGQmlInstance* instance = new FGQmlInstance;
269     return instance;
270 }
271 
fgqq_windowManager_provider(QQmlEngine * engine,QJSEngine * scriptEngine)272 static QObject* fgqq_windowManager_provider(QQmlEngine* engine, QJSEngine* scriptEngine)
273 {
274     Q_UNUSED(scriptEngine)
275 
276     FGQQWindowManager* instance = new FGQQWindowManager(engine);
277     return instance;
278 }
279 
280 class ReloadCommand : public SGCommandMgr::Command
281 {
282 public:
ReloadCommand(QQuickDrawable * qq)283     ReloadCommand(QQuickDrawable* qq) : _drawable(qq)
284     {
285     }
286 
operator ()(const SGPropertyNode * aNode,SGPropertyNode * root)287     bool operator()(const SGPropertyNode* aNode, SGPropertyNode* root) override
288     {
289         SG_UNUSED(aNode);
290         SG_UNUSED(root);
291 
292         QTimer::singleShot(0, [this]() {
293             std::string rootQMLPath = fgGetString("/sim/gui/qml-root-path");
294             _drawable->reload(QUrl::fromLocalFile(QString::fromStdString(rootQMLPath)));
295         });
296         return true;
297     }
298 
299 private:
300     QQuickDrawable* _drawable;
301 };
302 
303 class QuickEventHandler : public osgGA::GUIEventHandler
304 {
305 public:
QuickEventHandler(QQuickDrawablePrivate * p)306     QuickEventHandler(QQuickDrawablePrivate* p) : _drawable(p)
307     {
308         populateKeymap();
309     }
310 
handle(const GUIEventAdapter & ea,GUIActionAdapter & aa,osg::Object *,osg::NodeVisitor *)311     bool handle(const GUIEventAdapter& ea, GUIActionAdapter& aa, osg::Object*, osg::NodeVisitor*) override
312     {
313         Q_UNUSED(aa);
314 
315         if (ea.getHandled()) return false;
316 
317         // frame event, ho hum ...
318         if (ea.getEventType() == GUIEventAdapter::FRAME) {
319             _drawable->frameEvent();
320             return false;
321         }
322 
323         // Qt expects increasing downward mouse coords
324         const float fixedY = (ea.getMouseYOrientation() == GUIEventAdapter::Y_INCREASING_UPWARDS) ? ea.getWindowHeight() - ea.getY() : ea.getY();
325         const double pixelRatio = _drawable->foreignOSGWindow->devicePixelRatio();
326 
327         QPointF pointInWindow{ea.getX() / pixelRatio, fixedY / pixelRatio};
328         QPointF screenPt = pointInWindow +
329                            QPointF{ea.getWindowX() / pixelRatio, ea.getWindowY() / pixelRatio};
330 
331         //  const int scaledX = static_cast<int>(ea.getX() / static_pixelRatio);
332         // const int scaledY = static_cast<int>(fixedY / static_pixelRatio);
333 
334         switch (ea.getEventType()) {
335         case (GUIEventAdapter::DRAG):
336         case (GUIEventAdapter::MOVE): {
337             QMouseEvent m(QEvent::MouseMove, pointInWindow, pointInWindow, screenPt,
338                           Qt::NoButton,
339                           osgButtonMaskToQt(ea), osgModifiersToQt(ea));
340             QCoreApplication::sendEvent(_drawable->quickWindow, &m);
341             return m.isAccepted();
342         }
343 
344         case (GUIEventAdapter::PUSH):
345         case (GUIEventAdapter::RELEASE): {
346             const bool isUp = (ea.getEventType() == GUIEventAdapter::RELEASE);
347             QMouseEvent m(isUp ? QEvent::MouseButtonRelease : QEvent::MouseButtonPress,
348                           pointInWindow, pointInWindow, screenPt,
349                           osgButtonToQt(ea),
350                           osgButtonMaskToQt(ea), osgModifiersToQt(ea));
351             QCoreApplication::sendEvent(_drawable->quickWindow, &m);
352 
353             if (!isUp) {
354                 if (m.isAccepted()) {
355                     // deactivate PUI
356                     auto active = puActiveWidget();
357                     if (active) {
358                         active->invokeDownCallback();
359                     }
360                 }
361 
362                 // on mouse downs which we don't accept, take focus back
363                 //                    qInfo() << "Clearing QQ focus";
364                 //                    auto focused = _drawable->quickWindow->activeFocusItem();
365                 //                    if (focused) {
366                 //                        focused->setFocus(false, Qt::MouseFocusReason);
367                 //                    }
368             }
369 
370             return m.isAccepted();
371         }
372 
373         case (GUIEventAdapter::KEYDOWN):
374         case (GUIEventAdapter::KEYUP): {
375             if (!_drawable->quickWindow->activeFocusItem()) {
376                 return false;
377             }
378 
379             const bool isKeyRelease = (ea.getEventType() == GUIEventAdapter::KEYUP);
380             const auto& key = osgKeyToQt(ea.getKey());
381             QString s = key.s;
382 
383             QKeyEvent k(isKeyRelease ? QEvent::KeyRelease : QEvent::KeyPress,
384                         key.qt, osgModifiersToQt(ea), s);
385             QCoreApplication::sendEvent(_drawable->quickWindow, &k);
386             return k.isAccepted();
387         }
388 
389         default:
390             return false;
391         }
392 
393         return false;
394     }
395 private:
osgButtonMaskToQt(const osgGA::GUIEventAdapter & ea) const396     Qt::MouseButtons osgButtonMaskToQt(const osgGA::GUIEventAdapter& ea) const
397     {
398         const int mask = ea.getButtonMask();
399         Qt::MouseButtons result = Qt::NoButton;
400         if (mask & osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON)
401             result |= Qt::LeftButton;
402 
403         if (mask & osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON)
404             result |= Qt::MiddleButton;
405 
406         if (mask & osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON)
407             result |= Qt::RightButton;
408 
409         return result;
410     }
411 
osgButtonToQt(const osgGA::GUIEventAdapter & ea) const412     Qt::MouseButton osgButtonToQt(const osgGA::GUIEventAdapter& ea) const
413     {
414         switch (ea.getButton()) {
415         case osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON: return Qt::LeftButton;
416         case osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON: return Qt::MiddleButton;
417         case osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON: return Qt::RightButton;
418 
419         default:
420             break;
421         }
422 
423         return Qt::NoButton;
424     }
425 
osgModifiersToQt(const osgGA::GUIEventAdapter & ea) const426     Qt::KeyboardModifiers osgModifiersToQt(const osgGA::GUIEventAdapter& ea) const
427     {
428         Qt::KeyboardModifiers result = Qt::NoModifier;
429         const int mask = ea.getModKeyMask();
430         if (mask & osgGA::GUIEventAdapter::MODKEY_ALT) result |= Qt::AltModifier;
431         if (mask & osgGA::GUIEventAdapter::MODKEY_CTRL) result |= Qt::ControlModifier;
432         if (mask & osgGA::GUIEventAdapter::MODKEY_META) result |= Qt::MetaModifier;
433         if (mask & osgGA::GUIEventAdapter::MODKEY_SHIFT) result |= Qt::ShiftModifier;
434         return result;
435     }
436 
osgKeyToQt(int code) const437     QtKey osgKeyToQt(int code) const
438     {
439         auto it = std::lower_bound(global_keymap.begin(), global_keymap.end(),
440                                    QtKey{code, 0, {}},
441                                    [](const QtKey& a, const QtKey& b) { return a.osg < b.osg; });
442         if ((it == global_keymap.end()) || (it->osg != code)) {
443             qWarning() << "no mapping defined for OSG key:" << code;
444             return {0, 0, ""};
445         }
446 
447         return *it;
448     }
449 
populateKeymap()450     void populateKeymap()
451     {
452         if (!global_keymap.empty())
453             return;
454 
455         // regular keymappsing for A..Z and 0..9
456         for (int i = 0; i < 10; ++i) {
457             global_keymap.emplace_back(GUIEventAdapter::KEY_0 + i, Qt::Key_0 + i, QString::number(i));
458         }
459 
460         for (int i = 0; i < 26; ++i) {
461             global_keymap.emplace_back(GUIEventAdapter::KEY_A + i, Qt::Key_A + i, QChar::fromLatin1('a' + i));
462         }
463 
464         for (int i = 0; i < 26; ++i) {
465             global_keymap.emplace_back('A' + i, Qt::Key_A + i, QChar::fromLatin1('A' + i));
466         }
467 
468         // custom key mappsing
469         global_keymap.insert(global_keymap.end(), keymapInit);
470 
471         // sort by OSG code for fast lookups
472         std::sort(global_keymap.begin(), global_keymap.end(),
473                   [](const QtKey& a, const QtKey& b) { return a.osg < b.osg; });
474     }
475 
476     QQuickDrawablePrivate* _drawable;
477 };
478 
QQuickDrawable()479 QQuickDrawable::QQuickDrawable() : d(new QQuickDrawablePrivate)
480 {
481     setUseDisplayList(false);
482     setDataVariance(Object::DYNAMIC);
483 
484     osg::StateSet* stateSet = getOrCreateStateSet();
485     stateSet->setRenderBinDetails(1001, "RenderBin");
486 
487     QSurfaceFormat format;
488     format.setRenderableType(QSurfaceFormat::OpenGL);
489     QSurfaceFormat::setDefaultFormat(format);
490 
491     static bool doneQmlRegistration = false;
492     if (!doneQmlRegistration) {
493         doneQmlRegistration = true;
494 
495         // singleton system object
496         qmlRegisterSingletonType<FGQmlInstance>("FlightGear", 1, 0, "System", fgqmlinstance_provider);
497         qmlRegisterSingletonType<FGQmlInstance>("FlightGear", 1, 0, "WindowManager", fgqq_windowManager_provider);
498 
499         // QML types
500         qmlRegisterType<FGQmlPropertyNode>("FlightGear", 1, 0, "Property");
501         qmlRegisterType<DialogStateController>("FlightGear", 1, 0, "DialogStateController");
502     }
503 
504       globals->get_commands()->addCommandObject("reload-quick-gui", new ReloadCommand(this));
505 }
506 
~QQuickDrawable()507 QQuickDrawable::~QQuickDrawable()
508 {
509     delete d->qmlEngine;
510     delete d->renderControl;
511 }
512 
setup(osgViewer::GraphicsWindow * gw,osgViewer::Viewer * viewer)513 void QQuickDrawable::setup(osgViewer::GraphicsWindow *gw, osgViewer::Viewer *viewer)
514 {
515     osg::GraphicsContext* gc = gw;
516 
517     // none of this stuff needs the context current, so we can do it
518     // all safely on the main thread
519 
520     d->foreignOSGWindow = flightgear::qtWindowFromOSG(gw);
521     // d->foreignOSGWindow->setFormat(format);
522     d->foreignOSGWindow->setSurfaceType(QSurface::OpenGLSurface);
523 
524     // QWindow::requestActive would do QPA::makeKey, but on macOS this
525     // is a no-op for foreign windows. So we're going to manually set
526     // the focus window!
527     QGuiApplicationPrivate::focus_window = d->foreignOSGWindow;
528 
529     d->osgContext = gc;
530     d->renderControl = new CustomRenderControl(d->foreignOSGWindow);
531     d->quickWindow = new QQuickWindow(d->renderControl);
532     d->quickWindow->setClearBeforeRendering(false);
533 
534     d->qmlEngine = new QQmlEngine;
535 
536     SGPath rootQMLPath = SGPath::fromUtf8(fgGetString("/sim/gui/qml-root-path"));
537     SG_LOG(SG_GENERAL, SG_INFO, "Root QML dir:" << rootQMLPath.dir());
538     d->qmlEngine->addImportPath(QString::fromStdString(rootQMLPath.dir()));
539     // d->qmlEngine->addImportPath(QStringLiteral("qrc:///"));
540 
541     if (!d->qmlEngine->incubationController())
542         d->qmlEngine->setIncubationController(d->quickWindow->incubationController());
543 
544  //   QObject::connect(d->quickWindow, &QQuickWindow::activeFocusItemChanged,
545   //                    d.get(), &QQuickDrawablePrivate::onWindowActiveFocusItemChanged);
546 
547     QObject::connect(d->renderControl, &QQuickRenderControl::sceneChanged,
548                      d.get(), &QQuickDrawablePrivate::onSceneChanged);
549     QObject::connect(d->renderControl, &QQuickRenderControl::renderRequested,
550                      d.get(), &QQuickDrawablePrivate::onRenderRequested);
551 
552 
553     viewer->getEventHandlers().push_front(new QuickEventHandler(d.get()));
554 }
555 
drawImplementation(osg::RenderInfo & renderInfo) const556 void QQuickDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
557 {
558     if (!d->renderControlInited) {
559         d->initRenderControl();
560     }
561 
562     if (QOpenGLContext::currentContext() != d->qtContext) {
563         QOpenGLContextPrivate::setCurrentContext(d->qtContext);
564     }
565 
566     QOpenGLFunctions* glFuncs = d->qtContext->functions();
567     // prepare any state QQ2 needs
568     d->quickWindow->resetOpenGLState();
569 
570     // and reset these manually
571     glFuncs->glPixelStorei(GL_PACK_ALIGNMENT, 4);
572     glFuncs->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
573     glFuncs->glPixelStorei(GL_PACK_ROW_LENGTH, 0);
574     glFuncs->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
575 
576     d->renderControl->render();
577 
578     // otherwise the PUI camera gets confused
579     d->quickWindow->resetOpenGLState();
580 }
581 
reload(QUrl url)582 void QQuickDrawable::reload(QUrl url)
583 {
584     d->qmlEngine->clearComponentCache();
585     setSource(url);
586 }
587 
setSource(QUrl url)588 void QQuickDrawable::setSource(QUrl url)
589 {
590     if (d->rootItem)
591         delete d->rootItem;
592     if (d->qmlComponent)
593         delete d->qmlComponent;
594     d->rootItem = nullptr;
595 
596     d->qmlComponent = new QQmlComponent(d->qmlEngine, url);
597     if (d->qmlComponent->isLoading()) {
598         QObject::connect(d->qmlComponent, &QQmlComponent::statusChanged,
599                          d.get(), &QQuickDrawablePrivate::onComponentLoaded);
600     } else {
601         d->onComponentLoaded();
602     }
603 }
604 
resize(int width,int height)605 void QQuickDrawable::resize(int width, int height)
606 {
607     // we need to unscale from physical pixels back to logical, otherwise we end up double-scaled.
608     const float currentPixelRatio = static_cast<float>(d->foreignOSGWindow->devicePixelRatio());
609     const int logicalWidth = static_cast<int>(width / currentPixelRatio);
610     const int logicalHeight = static_cast<int>(height / currentPixelRatio);
611 
612     if (d->rootItem) {
613         d->rootItem->setWidth(logicalWidth);
614         d->rootItem->setHeight(logicalHeight);
615     }
616 
617 //    SG_LOG(SG_GUI, SG_INFO, "Resize:, lw=" << logicalWidth << ", lh=" << logicalHeight);
618     d->quickWindow->setGeometry(0, 0, logicalWidth, logicalHeight);
619 }
620 
621 #include "QQuickDrawable.moc"
622