diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html
index 5e92d2861a..ad16302ba4 100644
--- a/examples/html/entityProperties.html
+++ b/examples/html/entityProperties.html
@@ -665,6 +665,8 @@
elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent'));
elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff'));
+ elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl'));
+
elModelURL.addEventListener('change', createEmitTextPropertyUpdateFunction('modelURL'));
elShapeType.addEventListener('change', createEmitTextPropertyUpdateFunction('shapeType'));
elCompoundShapeURL.addEventListener('change', createEmitTextPropertyUpdateFunction('compoundShapeURL'));
@@ -1033,7 +1035,7 @@
diff --git a/interface/resources/qml/WebEntity.qml b/interface/resources/qml/WebEntity.qml
new file mode 100644
index 0000000000..a9d8350244
--- /dev/null
+++ b/interface/resources/qml/WebEntity.qml
@@ -0,0 +1,45 @@
+import QtQuick 2.3
+import QtQuick.Controls 1.2
+import QtWebKit 3.0
+
+Item {
+ id: root
+ implicitHeight: 600
+ implicitWidth: 800
+ Rectangle {
+ anchors.margins: 120
+ color: "#7f0000ff"
+ anchors.right: parent.horizontalCenter
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.rightMargin: 10
+ anchors.leftMargin: 10
+ anchors.bottomMargin: 10
+ anchors.topMargin: 10
+ }
+
+ Rectangle {
+ color: "#7Fff0000"
+ anchors.left: parent.horizontalCenter
+ anchors.leftMargin: 10
+ anchors.top: parent.top
+ anchors.margins: 120
+ anchors.right: parent.right
+ anchors.topMargin: 10
+ anchors.bottomMargin: 10
+ anchors.bottom: parent.bottom
+ anchors.rightMargin: 10
+/*
+ ScrollView {
+ id: scrollView
+ anchors.fill: parent
+ WebView {
+ id: webview
+ objectName: "webview"
+ anchors.fill: parent
+ }
+ }
+*/
+ }
+}
diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
index d15e3714d9..487a6cfbb4 100644
--- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
@@ -16,16 +16,52 @@
#include
#include
#include
-
-#include "GLMHelpers.h"
+#include
+#include
+#include
+#include
+#include
const int FIXED_FONT_POINT_SIZE = 40;
+const float DPI = 30.47;
+const float METERS_TO_INCHES = 39.3701;
EntityItem* RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
return new RenderableWebEntityItem(entityID, properties);
}
+RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
+ WebEntityItem(entityItemID, properties) {
+}
+
void RenderableWebEntityItem::render(RenderArgs* args) {
+ QOpenGLContext * currentContext = QOpenGLContext::currentContext();
+ QSurface * currentSurface = currentContext->surface();
+ if (!_webSurface) {
+ _webSurface = new OffscreenQmlSurface();
+ _webSurface->create(currentContext);
+ _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
+ _webSurface->load("WebEntity.qml");
+ _webSurface->resume();
+ updateQmlSourceUrl();
+ QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
+ _webSurface->lockTexture(textureId);
+ assert(!glGetError());
+ std::swap(_texture, textureId);
+ if (textureId) {
+ _webSurface->releaseTexture(textureId);
+ }
+ });
+ }
+
+ glm::vec2 dims = glm::vec2(_dimensions);
+ dims *= METERS_TO_INCHES * DPI;
+ // The offscreen surface is idempotent for resizes (bails early
+ // if it's a no-op), so it's safe to just call resize every frame
+ // without worrying about excessive overhead.
+ _webSurface->resize(QSize(dims.x, dims.y));
+ currentContext->makeCurrent(currentSurface);
+
PerformanceTimer perfTimer("RenderableWebEntityItem::render");
assert(getType() == EntityTypes::Web);
glm::vec3 position = getPosition();
@@ -42,16 +78,50 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
float alpha = 1.0f; //getBackgroundAlpha();
-
- glm::vec3 topLeft(-halfDimensions.x, -halfDimensions.y, 0);
- glm::vec3 bottomRight(halfDimensions.x, halfDimensions.y, 0);
+ static const glm::vec2 texMin(0);
+ static const glm::vec2 texMax(1);
+ glm::vec2 topLeft(-halfDimensions.x, -halfDimensions.y);
+ glm::vec2 bottomRight(halfDimensions.x, halfDimensions.y);
+ if (_texture) {
+ glEnable(GL_TEXTURE_2D);
+ glBindTexture(GL_TEXTURE_2D, _texture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ }
+
// TODO: Determine if we want these entities to have the deferred lighting effect? I think we do, so that the color
// used for a sphere, or box have the same look as those used on a text entity.
- DependencyManager::get()->bindSimpleProgram();
- DependencyManager::get()->renderQuad(topLeft, bottomRight, glm::vec4(0, 1, 0, alpha));
- DependencyManager::get()->releaseSimpleProgram();
- }
+ DependencyManager::get()->renderQuad(topLeft, bottomRight,
+ texMin, texMax, glm::vec4(1));
+
+ if (_texture) {
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glEnable(GL_TEXTURE_2D);
+ }
+ }
glPopMatrix();
}
+void RenderableWebEntityItem::setSourceUrl(const QString& value) {
+ if (_sourceUrl != value) {
+ _sourceUrl = value;
+ // Update the offscreen display
+ updateQmlSourceUrl();
+ }
+}
+
+void RenderableWebEntityItem::updateQmlSourceUrl() {
+ if (!_webSurface) {
+ return;
+ }
+ auto webView = _webSurface->getRootItem()->findChild("webview");
+ if (!webView) {
+ return;
+ }
+ if (!_sourceUrl.isEmpty()) {
+ webView->setProperty("url", _sourceUrl);
+ } else {
+ webView->setProperty("url", QVariant());
+ }
+}
\ No newline at end of file
diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h
index 2d639b85a1..c926314b66 100644
--- a/libraries/entities-renderer/src/RenderableWebEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h
@@ -11,15 +11,22 @@
#include
+class OffscreenQmlSurface;
+
class RenderableWebEntityItem : public WebEntityItem {
public:
static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties);
- RenderableWebEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
- WebEntityItem(entityItemID, properties)
- { }
-
+ RenderableWebEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
virtual void render(RenderArgs* args);
+
+ virtual void setSourceUrl(const QString& value);
+
+private:
+ void updateQmlSourceUrl();
+
+ OffscreenQmlSurface* _webSurface{ nullptr };
+ uint32_t _texture{ 0 };
};
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index ff54ef88fe..0d997cd7cb 100644
--- a/libraries/entities/src/EntityItemProperties.cpp
+++ b/libraries/entities/src/EntityItemProperties.cpp
@@ -86,6 +86,7 @@ EntityItemProperties::EntityItemProperties() :
CONSTRUCT_PROPERTY(keyLightDirection, ZoneEntityItem::DEFAULT_KEYLIGHT_DIRECTION),
CONSTRUCT_PROPERTY(name, ENTITY_ITEM_DEFAULT_NAME),
CONSTRUCT_PROPERTY(backgroundMode, BACKGROUND_MODE_INHERIT),
+ CONSTRUCT_PROPERTY(sourceUrl, ""),
_id(UNKNOWN_ENTITY_ID),
_idSet(false),
@@ -328,6 +329,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_AMBIENT_INTENSITY, keyLightAmbientIntensity);
CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_DIRECTION, keyLightDirection);
CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_MODE, backgroundMode);
+ CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl);
changedProperties += _stage.getChangedProperties();
changedProperties += _atmosphere.getChangedProperties();
@@ -409,6 +411,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(keyLightAmbientIntensity);
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(keyLightDirection);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(backgroundMode, getBackgroundModeAsString());
+ COPY_PROPERTY_TO_QSCRIPTVALUE(sourceUrl);
// Sitting properties support
if (!skipDefaults) {
@@ -510,6 +513,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(keyLightAmbientIntensity, setKeyLightAmbientIntensity);
COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(keyLightDirection, setKeyLightDirection);
COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(backgroundMode, BackgroundMode);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(sourceUrl, setSourceUrl);
_stage.copyFromScriptValue(object, _defaultSettings);
_atmosphere.copyFromScriptValue(object, _defaultSettings);
@@ -666,7 +670,11 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
APPEND_ENTITY_PROPERTY(PROP_LOCKED, appendValue, properties.getLocked());
APPEND_ENTITY_PROPERTY(PROP_USER_DATA, appendValue, properties.getUserData());
APPEND_ENTITY_PROPERTY(PROP_SIMULATOR_ID, appendValue, properties.getSimulatorID());
-
+
+ if (properties.getType() == EntityTypes::Web) {
+ APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, appendValue, properties.getSourceUrl());
+ }
+
if (properties.getType() == EntityTypes::Text) {
APPEND_ENTITY_PROPERTY(PROP_TEXT, appendValue, properties.getText());
APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, appendValue, properties.getLineHeight());
@@ -922,6 +930,10 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_USER_DATA, setUserData);
READ_ENTITY_PROPERTY_UUID_TO_PROPERTIES(PROP_SIMULATOR_ID, setSimulatorID);
+ if (properties.getType() == EntityTypes::Web) {
+ READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_SOURCE_URL, setSourceUrl);
+ }
+
if (properties.getType() == EntityTypes::Text) {
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_TEXT, setText);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_HEIGHT, float, setLineHeight);
@@ -1073,6 +1085,7 @@ void EntityItemProperties::markAllChanged() {
_atmosphere.markAllChanged();
_skybox.markAllChanged();
+ _sourceUrlChanged = true;
}
/// The maximum bounding cube for the entity, independent of it's rotation.
diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h
index ef6ab9089f..46c4c39e10 100644
--- a/libraries/entities/src/EntityItemProperties.h
+++ b/libraries/entities/src/EntityItemProperties.h
@@ -140,6 +140,8 @@ public:
DEFINE_PROPERTY_GROUP(Stage, stage, StagePropertyGroup);
DEFINE_PROPERTY_GROUP(Atmosphere, atmosphere, AtmospherePropertyGroup);
DEFINE_PROPERTY_GROUP(Skybox, skybox, SkyboxPropertyGroup);
+ DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString);
+
static QString getBackgroundModeString(BackgroundMode mode);
diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp
index b7c3efd821..90dd13334d 100644
--- a/libraries/entities/src/WebEntityItem.cpp
+++ b/libraries/entities/src/WebEntityItem.cpp
@@ -44,7 +44,7 @@ void WebEntityItem::setDimensions(const glm::vec3& value) {
EntityItemProperties WebEntityItem::getProperties() const {
EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class
- COPY_ENTITY_PROPERTY_TO_PROPERTIES(modelURL, getSource);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(sourceUrl, getSourceUrl);
return properties;
}
@@ -52,7 +52,7 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) {
bool somethingChanged = false;
somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
- SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setSourceUrl);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(sourceUrl, setSourceUrl);
if (somethingChanged) {
bool wantDebug = false;
@@ -143,3 +143,11 @@ bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const g
}
return intersects;
}
+
+void WebEntityItem::setSourceUrl(const QString& value) {
+ if (_sourceUrl != value) {
+ _sourceUrl = value;
+ }
+}
+
+const QString& WebEntityItem::getSourceUrl() const { return _sourceUrl; }
\ No newline at end of file
diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h
index ee8d4cf450..35e98b2092 100644
--- a/libraries/entities/src/WebEntityItem.h
+++ b/libraries/entities/src/WebEntityItem.h
@@ -49,8 +49,8 @@ public:
bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face,
void** intersectedObject, bool precisionPicking) const;
- void setSourceUrl(const QString& value) { _sourceUrl = value; }
- const QString& getSourceUrl() const { return _sourceUrl; }
+ virtual void setSourceUrl(const QString& value);
+ const QString& getSourceUrl() const;
protected:
QString _sourceUrl;
diff --git a/libraries/render-utils/src/OffscreenQmlSurface.cpp b/libraries/render-utils/src/OffscreenQmlSurface.cpp
new file mode 100644
index 0000000000..1893c77595
--- /dev/null
+++ b/libraries/render-utils/src/OffscreenQmlSurface.cpp
@@ -0,0 +1,372 @@
+//
+// OffscreenUi.cpp
+// interface/src/render-utils
+//
+// Created by Bradley Austin Davis on 2015-04-04
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#include "OffscreenQmlSurface.h"
+
+#include
+#include
+#include
+#include
+
+Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
+Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
+
+// Time between receiving a request to render the offscreen UI actually triggering
+// the render. Could possibly be increased depending on the framerate we expect to
+// achieve.
+static const int SMALL_INTERVAL = 5;
+
+OffscreenQmlSurface::OffscreenQmlSurface() {
+}
+
+OffscreenQmlSurface::~OffscreenQmlSurface() {
+ // Make sure the context is current while doing cleanup. Note that we use the
+ // offscreen surface here because passing 'this' at this point is not safe: the
+ // underlying platform window may already be destroyed. To avoid all the trouble, use
+ // another surface that is valid for sure.
+ makeCurrent();
+
+ // Delete the render control first since it will free the scenegraph resources.
+ // Destroy the QQuickWindow only afterwards.
+ delete _renderControl;
+
+ delete _qmlComponent;
+ delete _quickWindow;
+ delete _qmlEngine;
+
+ doneCurrent();
+}
+
+void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
+ OffscreenGlCanvas::create(shareContext);
+
+ makeCurrent();
+
+ // Create a QQuickWindow that is associated with out render control. Note that this
+ // window never gets created or shown, meaning that it will never get an underlying
+ // native (platform) window.
+ QQuickWindow::setDefaultAlphaBuffer(true);
+ _quickWindow = new QQuickWindow(_renderControl);
+ _quickWindow->setColor(QColor(255, 255, 255, 0));
+ _quickWindow->setFlags(_quickWindow->flags() | static_cast(Qt::WA_TranslucentBackground));
+ // Create a QML engine.
+ _qmlEngine = new QQmlEngine;
+ if (!_qmlEngine->incubationController()) {
+ _qmlEngine->setIncubationController(_quickWindow->incubationController());
+ }
+
+ // When Quick says there is a need to render, we will not render immediately. Instead,
+ // a timer with a small interval is used to get better performance.
+ _updateTimer.setSingleShot(true);
+ _updateTimer.setInterval(SMALL_INTERVAL);
+ connect(&_updateTimer, &QTimer::timeout, this, &OffscreenQmlSurface::updateQuick);
+
+ // Now hook up the signals. For simplicy we don't differentiate between
+ // renderRequested (only render is needed, no sync) and sceneChanged (polish and sync
+ // is needed too).
+ connect(_renderControl, &QQuickRenderControl::renderRequested, this, &OffscreenQmlSurface::requestRender);
+ connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &OffscreenQmlSurface::requestUpdate);
+
+#ifdef DEBUG
+ connect(_quickWindow, &QQuickWindow::focusObjectChanged, [this]{
+ qCDebug(offscreenFocus) << "New focus item " << _quickWindow->focusObject();
+ });
+ connect(_quickWindow, &QQuickWindow::activeFocusItemChanged, [this] {
+ qCDebug(offscreenFocus) << "New active focus item " << _quickWindow->activeFocusItem();
+ });
+#endif
+
+ _qmlComponent = new QQmlComponent(_qmlEngine);
+ // Initialize the render control and our OpenGL resources.
+ makeCurrent();
+ _renderControl->initialize(&_context);
+}
+
+void OffscreenQmlSurface::resize(const QSize& newSize) {
+
+ qreal pixelRatio = _renderControl->_renderWindow ? _renderControl->_renderWindow->devicePixelRatio() : 1.0;
+ QSize newOffscreenSize = newSize * pixelRatio;
+ if (newOffscreenSize == _fboCache.getSize()) {
+ return;
+ }
+
+ // Clear out any fbos with the old size
+ makeCurrent();
+ qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio;
+ _fboCache.setSize(newSize * pixelRatio);
+
+ if (_quickWindow) {
+ _quickWindow->setGeometry(QRect(QPoint(), newSize));
+ _quickWindow->contentItem()->setSize(newSize);
+ }
+
+
+ // Update our members
+ if (_rootItem) {
+ _rootItem->setSize(newSize);
+ }
+
+ doneCurrent();
+}
+
+QQuickItem* OffscreenQmlSurface::getRootItem() {
+ return _rootItem;
+}
+
+void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) {
+ _qmlEngine->setBaseUrl(baseUrl);
+}
+
+QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function f) {
+ _qmlComponent->loadUrl(qmlSource);
+ if (_qmlComponent->isLoading()) {
+ connect(_qmlComponent, &QQmlComponent::statusChanged, this,
+ [this, f](QQmlComponent::Status){
+ finishQmlLoad(f);
+ });
+ return nullptr;
+ }
+
+ return finishQmlLoad(f);
+}
+
+void OffscreenQmlSurface::requestUpdate() {
+ _polish = true;
+ if (!_updateTimer.isActive()) {
+ _updateTimer.start();
+ }
+}
+
+void OffscreenQmlSurface::requestRender() {
+ if (!_updateTimer.isActive()) {
+ _updateTimer.start();
+ }
+}
+
+QObject* OffscreenQmlSurface::finishQmlLoad(std::function f) {
+ disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0);
+ if (_qmlComponent->isError()) {
+ QList errorList = _qmlComponent->errors();
+ foreach(const QQmlError& error, errorList) {
+ qWarning() << error.url() << error.line() << error;
+ }
+ return nullptr;
+ }
+
+ QQmlContext* newContext = new QQmlContext(_qmlEngine, qApp);
+ QObject* newObject = _qmlComponent->beginCreate(newContext);
+ if (_qmlComponent->isError()) {
+ QList errorList = _qmlComponent->errors();
+ foreach(const QQmlError& error, errorList)
+ qWarning() << error.url() << error.line() << error;
+ if (!_rootItem) {
+ qFatal("Unable to finish loading QML root");
+ }
+ return nullptr;
+ }
+
+ f(newContext, newObject);
+ _qmlComponent->completeCreate();
+
+
+ // All quick items should be focusable
+ QQuickItem* newItem = qobject_cast(newObject);
+ if (newItem) {
+ // Make sure we make items focusable (critical for
+ // supporting keyboard shortcuts)
+ newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
+ }
+
+ // If we already have a root, just set a couple of flags and the ancestry
+ if (_rootItem) {
+ // Allow child windows to be destroyed from JS
+ QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership);
+ newObject->setParent(_rootItem);
+ if (newItem) {
+ newItem->setParentItem(_rootItem);
+ }
+ return newObject;
+ }
+
+ if (!newItem) {
+ qFatal("Could not load object as root item");
+ return nullptr;
+ }
+ // The root item is ready. Associate it with the window.
+ _rootItem = newItem;
+ _rootItem->setParentItem(_quickWindow->contentItem());
+ _rootItem->setSize(_quickWindow->renderTargetSize());
+ return _rootItem;
+}
+
+
+void OffscreenQmlSurface::updateQuick() {
+ if (_paused) {
+ return;
+ }
+ if (!makeCurrent()) {
+ return;
+ }
+
+ // Polish, synchronize and render the next frame (into our fbo). In this example
+ // everything happens on the same thread and therefore all three steps are performed
+ // in succession from here. In a threaded setup the render() call would happen on a
+ // separate thread.
+ if (_polish) {
+ _renderControl->polishItems();
+ _renderControl->sync();
+ _polish = false;
+ }
+
+ QOpenGLFramebufferObject* fbo = _fboCache.getReadyFbo();
+
+ _quickWindow->setRenderTarget(fbo);
+ fbo->bind();
+
+ glClearColor(0, 0, 0, 1);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ _renderControl->render();
+ // FIXME The web browsers seem to be leaving GL in an error state.
+ // Need a debug context with sync logging to figure out why.
+ // for now just clear the errors
+ glGetError();
+// Q_ASSERT(!glGetError());
+
+ _quickWindow->resetOpenGLState();
+
+ QOpenGLFramebufferObject::bindDefault();
+ // Force completion of all the operations before we emit the texture as being ready for use
+ glFinish();
+
+ emit textureUpdated(fbo->texture());
+}
+
+QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) {
+ vec2 sourceSize;
+ if (dynamic_cast(sourceObject)) {
+ sourceSize = toGlm(((QWidget*)sourceObject)->size());
+ } else if (dynamic_cast(sourceObject)) {
+ sourceSize = toGlm(((QWindow*)sourceObject)->size());
+ }
+ vec2 offscreenPosition = toGlm(sourcePosition);
+ offscreenPosition /= sourceSize;
+ offscreenPosition *= vec2(toGlm(_quickWindow->size()));
+ return QPointF(offscreenPosition.x, offscreenPosition.y);
+}
+
+///////////////////////////////////////////////////////
+//
+// Event handling customization
+//
+
+bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* event) {
+ // Only intercept events while we're in an active state
+ if (_paused) {
+ return false;
+ }
+
+#ifdef DEBUG
+ // Don't intercept our own events, or we enter an infinite recursion
+ QObject* recurseTest = originalDestination;
+ while (recurseTest) {
+ Q_ASSERT(recurseTest != _rootItem && recurseTest != _quickWindow);
+ recurseTest = recurseTest->parent();
+ }
+#endif
+
+
+ switch (event->type()) {
+ case QEvent::Resize: {
+ QResizeEvent* resizeEvent = static_cast(event);
+ QGLWidget* widget = dynamic_cast(originalDestination);
+ if (widget) {
+ this->resize(resizeEvent->size());
+ }
+ break;
+ }
+
+ case QEvent::KeyPress:
+ case QEvent::KeyRelease: {
+ event->ignore();
+ if (QCoreApplication::sendEvent(_quickWindow, event)) {
+ return event->isAccepted();
+ }
+ break;
+ }
+
+ case QEvent::Wheel: {
+ QWheelEvent* wheelEvent = static_cast(event);
+ QWheelEvent mappedEvent(
+ mapWindowToUi(wheelEvent->pos(), originalDestination),
+ wheelEvent->delta(), wheelEvent->buttons(),
+ wheelEvent->modifiers(), wheelEvent->orientation());
+ mappedEvent.ignore();
+ if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
+ return mappedEvent.isAccepted();
+ }
+ break;
+ }
+
+ // Fall through
+ case QEvent::MouseButtonDblClick:
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonRelease:
+ case QEvent::MouseMove: {
+ QMouseEvent* mouseEvent = static_cast(event);
+ QPointF originalPos = mouseEvent->localPos();
+ QPointF transformedPos = _mouseTranslator(originalPos);
+ transformedPos = mapWindowToUi(transformedPos, originalDestination);
+ QMouseEvent mappedEvent(mouseEvent->type(),
+ transformedPos,
+ mouseEvent->screenPos(), mouseEvent->button(),
+ mouseEvent->buttons(), mouseEvent->modifiers());
+ if (event->type() == QEvent::MouseMove) {
+ _qmlEngine->rootContext()->setContextProperty("lastMousePosition", transformedPos);
+ }
+ mappedEvent.ignore();
+ if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
+ return mappedEvent.isAccepted();
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+void OffscreenQmlSurface::lockTexture(int texture) {
+ _fboCache.lockTexture(texture);
+}
+
+void OffscreenQmlSurface::releaseTexture(int texture) {
+ _fboCache.releaseTexture(texture);
+}
+
+void OffscreenQmlSurface::pause() {
+ _paused = true;
+}
+
+void OffscreenQmlSurface::resume() {
+ _paused = false;
+ requestRender();
+}
+
+bool OffscreenQmlSurface::isPaused() const {
+ return _paused;
+}
+
+void OffscreenQmlSurface::setProxyWindow(QWindow* window) {
+ _renderControl->_renderWindow = window;
+}
+
diff --git a/libraries/render-utils/src/OffscreenQmlSurface.h b/libraries/render-utils/src/OffscreenQmlSurface.h
new file mode 100644
index 0000000000..dcb3e1e17e
--- /dev/null
+++ b/libraries/render-utils/src/OffscreenQmlSurface.h
@@ -0,0 +1,108 @@
+//
+// Created by Bradley Austin Davis on 2015-04-04
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+#ifndef hifi_OffscreenQmlSurface_h
+#define hifi_OffscreenQmlSurface_h
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+
+#include "OffscreenGlCanvas.h"
+#include "FboCache.h"
+
+class OffscreenQmlSurface : public OffscreenGlCanvas {
+ Q_OBJECT
+protected:
+ class QMyQuickRenderControl : public QQuickRenderControl {
+ protected:
+ QWindow* renderWindow(QPoint* offset) Q_DECL_OVERRIDE{
+ if (nullptr == _renderWindow) {
+ return QQuickRenderControl::renderWindow(offset);
+ }
+ if (nullptr != offset) {
+ offset->rx() = offset->ry() = 0;
+ }
+ return _renderWindow;
+ }
+
+ private:
+ QWindow* _renderWindow{ nullptr };
+ friend class OffscreenQmlSurface;
+ };
+public:
+ OffscreenQmlSurface();
+ virtual ~OffscreenQmlSurface();
+
+ using MouseTranslator = std::function;
+
+ void create(QOpenGLContext* context);
+ void resize(const QSize& size);
+ QObject* load(const QUrl& qmlSource, std::function f = [](QQmlContext*, QObject*) {});
+ QObject* load(const QString& qmlSourceFile, std::function f = [](QQmlContext*, QObject*) {}) {
+ return load(QUrl(qmlSourceFile), f);
+ }
+
+ // Optional values for event handling
+ void setProxyWindow(QWindow* window);
+ void setMouseTranslator(MouseTranslator mouseTranslator) {
+ _mouseTranslator = mouseTranslator;
+ }
+
+ void pause();
+ void resume();
+ bool isPaused() const;
+
+ void setBaseUrl(const QUrl& baseUrl);
+ QQuickItem* getRootItem();
+
+ virtual bool eventFilter(QObject* originalDestination, QEvent* event);
+
+signals:
+ void textureUpdated(GLuint texture);
+
+public slots:
+ void requestUpdate();
+ void requestRender();
+ void lockTexture(int texture);
+ void releaseTexture(int texture);
+
+private:
+ QObject* finishQmlLoad(std::function f);
+ QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
+
+private slots:
+ void updateQuick();
+
+protected:
+ QQuickWindow* _quickWindow{ nullptr };
+
+private:
+ QMyQuickRenderControl* _renderControl{ new QMyQuickRenderControl };
+ QQmlEngine* _qmlEngine{ nullptr };
+ QQmlComponent* _qmlComponent{ nullptr };
+ QQuickItem* _rootItem{ nullptr };
+ QTimer _updateTimer;
+ FboCache _fboCache;
+ bool _polish{ true };
+ bool _paused{ true };
+ MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p; } };
+};
+
+#endif
diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp
index b133d1e488..449657ca04 100644
--- a/libraries/ui/src/OffscreenUi.cpp
+++ b/libraries/ui/src/OffscreenUi.cpp
@@ -16,252 +16,18 @@
#include "MessageDialog.h"
-Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
-Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
+class OffscreenUiRoot : public QQuickItem {
+ Q_OBJECT
+public:
-// Time between receiving a request to render the offscreen UI actually triggering
-// the render. Could possibly be increased depending on the framerate we expect to
-// achieve.
-static const int SMALL_INTERVAL = 5;
-
-OffscreenQmlSurface::OffscreenQmlSurface() {
-}
-
-OffscreenQmlSurface::~OffscreenQmlSurface() {
- // Make sure the context is current while doing cleanup. Note that we use the
- // offscreen surface here because passing 'this' at this point is not safe: the
- // underlying platform window may already be destroyed. To avoid all the trouble, use
- // another surface that is valid for sure.
- makeCurrent();
-
- // Delete the render control first since it will free the scenegraph resources.
- // Destroy the QQuickWindow only afterwards.
- delete _renderControl;
-
- delete _qmlComponent;
- delete _quickWindow;
- delete _qmlEngine;
-
- doneCurrent();
-}
-
-void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
- OffscreenGlCanvas::create(shareContext);
-
- makeCurrent();
-
- // Create a QQuickWindow that is associated with out render control. Note that this
- // window never gets created or shown, meaning that it will never get an underlying
- // native (platform) window.
- QQuickWindow::setDefaultAlphaBuffer(true);
- _quickWindow = new QQuickWindow(_renderControl);
- _quickWindow->setColor(QColor(255, 255, 255, 0));
- _quickWindow->setFlags(_quickWindow->flags() | static_cast(Qt::WA_TranslucentBackground));
- // Create a QML engine.
- _qmlEngine = new QQmlEngine;
- if (!_qmlEngine->incubationController()) {
- _qmlEngine->setIncubationController(_quickWindow->incubationController());
- }
-
- // When Quick says there is a need to render, we will not render immediately. Instead,
- // a timer with a small interval is used to get better performance.
- _updateTimer.setSingleShot(true);
- _updateTimer.setInterval(SMALL_INTERVAL);
- connect(&_updateTimer, &QTimer::timeout, this, &OffscreenQmlSurface::updateQuick);
-
- // Now hook up the signals. For simplicy we don't differentiate between
- // renderRequested (only render is needed, no sync) and sceneChanged (polish and sync
- // is needed too).
- connect(_renderControl, &QQuickRenderControl::renderRequested, this, &OffscreenQmlSurface::requestRender);
- connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &OffscreenQmlSurface::requestUpdate);
-
-#ifdef DEBUG
- connect(_quickWindow, &QQuickWindow::focusObjectChanged, [this]{
- qCDebug(offscreenFocus) << "New focus item " << _quickWindow->focusObject();
- });
- connect(_quickWindow, &QQuickWindow::activeFocusItemChanged, [this] {
- qCDebug(offscreenFocus) << "New active focus item " << _quickWindow->activeFocusItem();
- });
-#endif
-
- _qmlComponent = new QQmlComponent(_qmlEngine);
- // Initialize the render control and our OpenGL resources.
- makeCurrent();
- _renderControl->initialize(&_context);
-}
-
-void OffscreenQmlSurface::resize(const QSize& newSize) {
- makeCurrent();
-
- qreal pixelRatio = _renderControl->_renderWindow ? _renderControl->_renderWindow->devicePixelRatio() : 1.0;
- QSize newOffscreenSize = newSize * pixelRatio;
- if (newOffscreenSize == _fboCache.getSize()) {
- return;
- }
-
- // Clear out any fbos with the old size
- qDebug() << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio;
- _fboCache.setSize(newSize * pixelRatio);
-
- if (_quickWindow) {
- _quickWindow->setGeometry(QRect(QPoint(), newSize));
- _quickWindow->contentItem()->setSize(newSize);
+ OffscreenUiRoot(QQuickItem* parent = 0);
+ Q_INVOKABLE void information(const QString& title, const QString& text);
+ Q_INVOKABLE void loadChild(const QUrl& url) {
+ DependencyManager::get()->load(url);
}
+};
- // Update our members
- if (_rootItem) {
- _rootItem->setSize(newSize);
- }
-
- doneCurrent();
-}
-
-QQuickItem* OffscreenQmlSurface::getRootItem() {
- return _rootItem;
-}
-
-void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) {
- _qmlEngine->setBaseUrl(baseUrl);
-}
-
-QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function f) {
- _qmlComponent->loadUrl(qmlSource);
- if (_qmlComponent->isLoading()) {
- connect(_qmlComponent, &QQmlComponent::statusChanged, this,
- [this, f](QQmlComponent::Status){
- finishQmlLoad(f);
- });
- return nullptr;
- }
-
- return finishQmlLoad(f);
-}
-
-void OffscreenQmlSurface::requestUpdate() {
- _polish = true;
- if (!_updateTimer.isActive()) {
- _updateTimer.start();
- }
-}
-
-void OffscreenQmlSurface::requestRender() {
- if (!_updateTimer.isActive()) {
- _updateTimer.start();
- }
-}
-
-QObject* OffscreenQmlSurface::finishQmlLoad(std::function f) {
- disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0);
- if (_qmlComponent->isError()) {
- QList errorList = _qmlComponent->errors();
- foreach(const QQmlError& error, errorList) {
- qWarning() << error.url() << error.line() << error;
- }
- return nullptr;
- }
-
- QQmlContext* newContext = new QQmlContext(_qmlEngine, qApp);
- QObject* newObject = _qmlComponent->beginCreate(newContext);
- if (_qmlComponent->isError()) {
- QList errorList = _qmlComponent->errors();
- foreach(const QQmlError& error, errorList)
- qWarning() << error.url() << error.line() << error;
- if (!_rootItem) {
- qFatal("Unable to finish loading QML root");
- }
- return nullptr;
- }
-
- f(newContext, newObject);
- _qmlComponent->completeCreate();
-
-
- // All quick items should be focusable
- QQuickItem* newItem = qobject_cast(newObject);
- if (newItem) {
- // Make sure we make items focusable (critical for
- // supporting keyboard shortcuts)
- newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
- }
-
- // If we already have a root, just set a couple of flags and the ancestry
- if (_rootItem) {
- // Allow child windows to be destroyed from JS
- QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership);
- newObject->setParent(_rootItem);
- if (newItem) {
- newItem->setParentItem(_rootItem);
- }
- return newObject;
- }
-
- if (!newItem) {
- qFatal("Could not load object as root item");
- return nullptr;
- }
- // The root item is ready. Associate it with the window.
- _rootItem = newItem;
- _rootItem->setParentItem(_quickWindow->contentItem());
- _rootItem->setSize(_quickWindow->renderTargetSize());
- return _rootItem;
-}
-
-
-void OffscreenQmlSurface::updateQuick() {
- if (_paused) {
- return;
- }
- if (!makeCurrent()) {
- return;
- }
-
- // Polish, synchronize and render the next frame (into our fbo). In this example
- // everything happens on the same thread and therefore all three steps are performed
- // in succession from here. In a threaded setup the render() call would happen on a
- // separate thread.
- if (_polish) {
- _renderControl->polishItems();
- _renderControl->sync();
- _polish = false;
- }
-
- QOpenGLFramebufferObject* fbo = _fboCache.getReadyFbo();
-
- _quickWindow->setRenderTarget(fbo);
- fbo->bind();
-
- glClearColor(0, 0, 0, 1);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
- _renderControl->render();
- // FIXME The web browsers seem to be leaving GL in an error state.
- // Need a debug context with sync logging to figure out why.
- // for now just clear the errors
- glGetError();
-// Q_ASSERT(!glGetError());
-
- _quickWindow->resetOpenGLState();
-
- QOpenGLFramebufferObject::bindDefault();
- // Force completion of all the operations before we emit the texture as being ready for use
- glFinish();
-
- emit textureUpdated(fbo->texture());
-}
-
-QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) {
- vec2 sourceSize;
- if (dynamic_cast(sourceObject)) {
- sourceSize = toGlm(((QWidget*)sourceObject)->size());
- } else if (dynamic_cast(sourceObject)) {
- sourceSize = toGlm(((QWindow*)sourceObject)->size());
- }
- vec2 offscreenPosition = toGlm(sourcePosition);
- offscreenPosition /= sourceSize;
- offscreenPosition *= vec2(toGlm(_quickWindow->size()));
- return QPointF(offscreenPosition.x, offscreenPosition.y);
-}
// This hack allows the QML UI to work with keys that are also bound as
// shortcuts at the application level. However, it seems as though the
@@ -281,128 +47,6 @@ bool OffscreenUi::shouldSwallowShortcut(QEvent* event) {
return false;
}
-///////////////////////////////////////////////////////
-//
-// Event handling customization
-//
-
-bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* event) {
- // Only intercept events while we're in an active state
- if (_paused) {
- return false;
- }
-
-
-#ifdef DEBUG
- // Don't intercept our own events, or we enter an infinite recursion
- QObject* recurseTest = originalDestination;
- while (recurseTest) {
- Q_ASSERT(recurseTest != _rootItem && recurseTest != _quickWindow);
- recurseTest = recurseTest->parent();
- }
-#endif
-
-
- switch (event->type()) {
- case QEvent::Resize: {
- QResizeEvent* resizeEvent = static_cast(event);
- QGLWidget* widget = dynamic_cast(originalDestination);
- if (widget) {
- this->resize(resizeEvent->size());
- }
- break;
- }
-
- case QEvent::KeyPress:
- case QEvent::KeyRelease: {
- event->ignore();
- if (QCoreApplication::sendEvent(_quickWindow, event)) {
- return event->isAccepted();
- }
- break;
- }
-
- case QEvent::Wheel: {
- QWheelEvent* wheelEvent = static_cast(event);
- QWheelEvent mappedEvent(
- mapWindowToUi(wheelEvent->pos(), originalDestination),
- wheelEvent->delta(), wheelEvent->buttons(),
- wheelEvent->modifiers(), wheelEvent->orientation());
- mappedEvent.ignore();
- if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
- return mappedEvent.isAccepted();
- }
- break;
- }
-
- // Fall through
- case QEvent::MouseButtonDblClick:
- case QEvent::MouseButtonPress:
- case QEvent::MouseButtonRelease:
- case QEvent::MouseMove: {
- QMouseEvent* mouseEvent = static_cast(event);
- QPointF originalPos = mouseEvent->localPos();
- QPointF transformedPos = _mouseTranslator(originalPos);
- transformedPos = mapWindowToUi(transformedPos, originalDestination);
- QMouseEvent mappedEvent(mouseEvent->type(),
- transformedPos,
- mouseEvent->screenPos(), mouseEvent->button(),
- mouseEvent->buttons(), mouseEvent->modifiers());
- if (event->type() == QEvent::MouseMove) {
- _qmlEngine->rootContext()->setContextProperty("lastMousePosition", transformedPos);
- }
- mappedEvent.ignore();
- if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) {
- return mappedEvent.isAccepted();
- }
- break;
- }
-
- default:
- break;
- }
-
- return false;
-}
-
-void OffscreenQmlSurface::lockTexture(int texture) {
- _fboCache.lockTexture(texture);
-}
-
-void OffscreenQmlSurface::releaseTexture(int texture) {
- _fboCache.releaseTexture(texture);
-}
-
-void OffscreenQmlSurface::pause() {
- _paused = true;
-}
-
-void OffscreenQmlSurface::resume() {
- _paused = false;
- requestRender();
-}
-
-bool OffscreenQmlSurface::isPaused() const {
- return _paused;
-}
-
-void OffscreenQmlSurface::setProxyWindow(QWindow* window) {
- _renderControl->_renderWindow = window;
-}
-
-
-class OffscreenUiRoot : public QQuickItem {
- Q_OBJECT
-public:
-
- OffscreenUiRoot(QQuickItem* parent = 0);
- Q_INVOKABLE void information(const QString& title, const QString& text);
- Q_INVOKABLE void loadChild(const QUrl& url) {
- DependencyManager::get()->load(url);
- }
-};
-
-
OffscreenUiRoot::OffscreenUiRoot(QQuickItem* parent) : QQuickItem(parent) {
}
diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h
index 846cd4bfd6..d3567bbb5e 100644
--- a/libraries/ui/src/OffscreenUi.h
+++ b/libraries/ui/src/OffscreenUi.h
@@ -12,25 +12,10 @@
#ifndef hifi_OffscreenUi_h
#define hifi_OffscreenUi_h
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
+#include "OffscreenQmlSurface.h"
-#include
-#include
-
-#include
-#include
#include
-#include "OffscreenGlCanvas.h"
-#include "FboCache.h"
-#include
#define HIFI_QML_DECL \
private: \
@@ -96,84 +81,6 @@ private:
offscreenUi->load(QML, f); \
}
-class OffscreenQmlSurface : public OffscreenGlCanvas {
- Q_OBJECT
-protected:
- class QMyQuickRenderControl : public QQuickRenderControl {
- protected:
- QWindow* renderWindow(QPoint* offset) Q_DECL_OVERRIDE{
- if (nullptr == _renderWindow) {
- return QQuickRenderControl::renderWindow(offset);
- }
- if (nullptr != offset) {
- offset->rx() = offset->ry() = 0;
- }
- return _renderWindow;
- }
-
- private:
- QWindow* _renderWindow{ nullptr };
- friend class OffscreenQmlSurface;
- };
-public:
- OffscreenQmlSurface();
- virtual ~OffscreenQmlSurface();
-
- using MouseTranslator = std::function;
-
- void create(QOpenGLContext* context);
- void resize(const QSize& size);
- QObject* load(const QUrl& qmlSource, std::function f = [](QQmlContext*, QObject*) {});
- QObject* load(const QString& qmlSourceFile, std::function f = [](QQmlContext*, QObject*) {}) {
- return load(QUrl(qmlSourceFile), f);
- }
-
- // Optional values for event handling
- void setProxyWindow(QWindow* window);
- void setMouseTranslator(MouseTranslator mouseTranslator) {
- _mouseTranslator = mouseTranslator;
- }
-
- void pause();
- void resume();
- bool isPaused() const;
-
- void setBaseUrl(const QUrl& baseUrl);
- QQuickItem* getRootItem();
-
- virtual bool eventFilter(QObject* originalDestination, QEvent* event);
-
-signals:
- void textureUpdated(GLuint texture);
-
-public slots:
- void requestUpdate();
- void requestRender();
- void lockTexture(int texture);
- void releaseTexture(int texture);
-
-private:
- QObject* finishQmlLoad(std::function f);
- QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
-
-private slots:
- void updateQuick();
-
-protected:
- QQuickWindow* _quickWindow{ nullptr };
-
-private:
- QMyQuickRenderControl* _renderControl{ new QMyQuickRenderControl };
- QQmlEngine* _qmlEngine{ nullptr };
- QQmlComponent* _qmlComponent{ nullptr };
- QQuickItem* _rootItem{ nullptr };
- QTimer _updateTimer;
- FboCache _fboCache;
- bool _polish{ true };
- bool _paused{ true };
- MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p; } };
-};
-
class OffscreenUi : public OffscreenQmlSurface, public Dependency {
Q_OBJECT