From 745182a9636dce0df1aa2bc378975fec81c7dd2c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 12 May 2015 14:39:54 -0700 Subject: [PATCH] Working on web entities --- examples/edit.js | 29 ++++ examples/html/entityProperties.html | 20 +++ .../src/EntityTreeRenderer.cpp | 2 + .../src/RenderableWebEntityItem.cpp | 59 +++++++ .../src/RenderableWebEntityItem.h | 26 ++++ libraries/entities/src/EntityItemProperties.h | 1 + libraries/entities/src/EntityPropertyFlags.h | 4 + libraries/entities/src/EntityTypes.cpp | 2 + libraries/entities/src/EntityTypes.h | 9 +- libraries/entities/src/WebEntityItem.cpp | 145 ++++++++++++++++++ libraries/entities/src/WebEntityItem.h | 59 +++++++ libraries/ui/src/OffscreenUi.cpp | 106 +++++++------ libraries/ui/src/OffscreenUi.h | 103 +++++++------ 13 files changed, 465 insertions(+), 100 deletions(-) create mode 100644 libraries/entities-renderer/src/RenderableWebEntityItem.cpp create mode 100644 libraries/entities-renderer/src/RenderableWebEntityItem.h create mode 100644 libraries/entities/src/WebEntityItem.cpp create mode 100644 libraries/entities/src/WebEntityItem.h diff --git a/examples/edit.js b/examples/edit.js index 12072cf41f..4c953c2211 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -137,6 +137,7 @@ var toolBar = (function () { newSphereButton, newLightButton, newTextButton, + newWebButton, newZoneButton, browseMarketplaceButton; @@ -204,6 +205,16 @@ var toolBar = (function () { alpha: 0.9, visible: false }); + + newWebButton = toolBar.addTool({ + imageURL: toolIconUrl + "add-text.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, + height: toolHeight, + alpha: 0.9, + visible: false + }); + newZoneButton = toolBar.addTool({ imageURL: toolIconUrl + "zonecube_text.svg", subImage: { x: 0, y: 128, width: 128, height: 128 }, @@ -253,6 +264,7 @@ var toolBar = (function () { toolBar.showTool(newSphereButton, doShow); toolBar.showTool(newLightButton, doShow); toolBar.showTool(newTextButton, doShow); + toolBar.showTool(newWebButton, doShow); toolBar.showTool(newZoneButton, doShow); }; @@ -425,6 +437,23 @@ var toolBar = (function () { return true; } + if (newWebButton === toolBar.clicked(clickedOverlay)) { + print("Web"); + var position = getPositionToCreateEntity(); + + if (position.x > 0 && position.y > 0 && position.z > 0) { + placingEntityID = Entities.addEntity({ + type: "Web", + position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS), + dimensions: { x: 0.65, y: 0.3, z: 0.01 }, + source: "http://www.slashdot.org", + }); + } else { + print("Can't create box: Text would be out of bounds."); + } + return true; + } + if (newZoneButton === toolBar.clicked(clickedOverlay)) { var position = getPositionToCreateEntity(); diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index 93ff80ce33..76620c9da2 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -468,6 +468,20 @@ elModelAnimationSettings.value = properties.animationSettings; elModelTextures.value = properties.textures; elModelOriginalTextures.value = properties.originalTextures; + } else if (properties.type == "Web") { + for (var i = 0; i < elTextSections.length; i++) { + elTextSections[i].style.display = 'block'; + } + + elTextText.value = properties.text; + elTextLineHeight.value = properties.lineHeight.toFixed(4); + elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + properties.textColor.green + "," + properties.textColor.blue + ")"; + elTextTextColorRed.value = properties.textColor.red; + elTextTextColorGreen.value = properties.textColor.green; + elTextTextColorBlue.value = properties.textColor.blue; + elTextBackgroundColorRed.value = properties.backgroundColor.red; + elTextBackgroundColorGreen.value = properties.backgroundColor.green; + elTextBackgroundColorBlue.value = properties.backgroundColor.blue; } else if (properties.type == "Text") { for (var i = 0; i < elTextSections.length; i++) { elTextSections[i].style.display = 'block'; @@ -1019,6 +1033,12 @@ +
+
Source URL
+
+ +
+
diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 50477356cb..b79e8ba219 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -35,6 +35,7 @@ #include "RenderableParticleEffectEntityItem.h" #include "RenderableSphereEntityItem.h" #include "RenderableTextEntityItem.h" +#include "RenderableWebEntityItem.h" #include "RenderableZoneEntityItem.h" #include "EntitiesRendererLogging.h" @@ -57,6 +58,7 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Text, RenderableTextEntityItem::factory) + REGISTER_ENTITY_TYPE_WITH_FACTORY(Web, RenderableWebEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(ParticleEffect, RenderableParticleEffectEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Zone, RenderableZoneEntityItem::factory) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp new file mode 100644 index 0000000000..564c49e7bd --- /dev/null +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -0,0 +1,59 @@ +// +// Created by Bradley Austin Davis on 2015/05/12 +// Copyright 2013 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 "RenderableWebEntityItem.h" + +#include + +#include + +#include +#include +#include +#include + +#include "GLMHelpers.h" + +const int FIXED_FONT_POINT_SIZE = 40; + +EntityItem* RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + return new RenderableWebEntityItem(entityID, properties); +} + +void RenderableWebEntityItem::render(RenderArgs* args) { + PerformanceTimer perfTimer("RenderableWebEntityItem::render"); + assert(getType() == EntityTypes::Web); + glm::vec3 position = getPosition(); + glm::vec3 dimensions = getDimensions(); + glm::vec3 halfDimensions = dimensions / 2.0f; + glm::quat rotation = getRotation(); + float leftMargin = 0.1f; + float topMargin = 0.1f; + + //qCDebug(entitytree) << "RenderableWebEntityItem::render() id:" << getEntityItemID() << "text:" << getText(); + + glPushMatrix(); + { + glTranslatef(position.x, position.y, position.z); + glm::vec3 axis = glm::axis(rotation); + 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); + + // 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(); + } + glPopMatrix(); +} + diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h new file mode 100644 index 0000000000..2d639b85a1 --- /dev/null +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -0,0 +1,26 @@ +// +// Created by Bradley Austin Davis on 2015/05/12 +// Copyright 2013 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 +// + +#ifndef hifi_RenderableWebEntityItem_h +#define hifi_RenderableWebEntityItem_h + +#include + +class RenderableWebEntityItem : public WebEntityItem { +public: + static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + RenderableWebEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : + WebEntityItem(entityItemID, properties) + { } + + virtual void render(RenderArgs* args); +}; + + +#endif // hifi_RenderableWebEntityItem_h diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 0accdf0899..ebdbfb4765 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -52,6 +52,7 @@ class EntityItemProperties { friend class TextEntityItem; // TODO: consider removing this friend relationship and use public methods friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods friend class ZoneEntityItem; // TODO: consider removing this friend relationship and use public methods + friend class WebEntityItem; // TODO.... public: EntityItemProperties(); virtual ~EntityItemProperties(); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 93b8c76216..cf7fc671a0 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -148,6 +148,10 @@ enum EntityPropertyList { PROP_SKYBOX_URL = PROP_ANIMATION_FPS, PROP_STAGE_AUTOMATIC_HOURDAY = PROP_ANIMATION_FRAME_INDEX, + // Aliases/Piggyback properties for Web. These properties intentionally reuse the enum values for + // other properties which will never overlap with each other. + PROP_SOURCE_URL = PROP_MODEL_URL, + // WARNING!!! DO NOT ADD PROPS_xxx here unless you really really meant to.... Add them UP above }; diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index f0baa3da93..a9f741e581 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -24,6 +24,7 @@ #include "ParticleEffectEntityItem.h" #include "SphereEntityItem.h" #include "TextEntityItem.h" +#include "WebEntityItem.h" #include "ZoneEntityItem.h" QMap EntityTypes::_typeToNameMap; @@ -36,6 +37,7 @@ const QString ENTITY_TYPE_NAME_UNKNOWN = "Unknown"; // Register Entity the default implementations of entity types here... REGISTER_ENTITY_TYPE(Model) REGISTER_ENTITY_TYPE(Box) +REGISTER_ENTITY_TYPE(Web) REGISTER_ENTITY_TYPE(Sphere) REGISTER_ENTITY_TYPE(Light) REGISTER_ENTITY_TYPE(Text) diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 28cfe2278b..02038829f6 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -30,13 +30,14 @@ class EntityTypes { public: typedef enum EntityType_t { Unknown, - Model, Box, - Sphere, Light, + Model, + ParticleEffect, + Sphere, Text, - ParticleEffect, - Zone, + Web, + Zone, LAST = Zone } EntityType; diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp new file mode 100644 index 0000000000..3771a8831d --- /dev/null +++ b/libraries/entities/src/WebEntityItem.cpp @@ -0,0 +1,145 @@ +// +// Created by Bradley Austin Davis on 2015/05/12 +// Copyright 2013 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 "WebEntityItem.h" + +#include + +#include + +#include +#include + +#include "EntityTree.h" +#include "EntityTreeElement.h" +#include "EntitiesLogging.h" + + +const QString WebEntityItem::DEFAULT_SOURCE_URL("http://www.google.com"); + +EntityItem* WebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + EntityItem* result = new WebEntityItem(entityID, properties); + return result; +} + +WebEntityItem::WebEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : + EntityItem(entityItemID) +{ + _type = EntityTypes::Web; + _created = properties.getCreated(); + setProperties(properties); +} + +const float WEB_ENTITY_ITEM_FIXED_DEPTH = 0.01f; + +void WebEntityItem::setDimensions(const glm::vec3& value) { + // NOTE: Web Entities always have a "depth" of 1cm. + _dimensions = glm::vec3(value.x, value.y, TEXT_ENTITY_ITEM_FIXED_DEPTH); +} + +EntityItemProperties WebEntityItem::getProperties() const { + EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class + COPY_ENTITY_PROPERTY_TO_PROPERTIES(modelURL, getSource); + return properties; +} + +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, setSource); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qCDebug(entities) << "WebEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties._lastEdited); + } + + return somethingChanged; +} + +int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY_STRING(PROP_SOURCE_URL, setSource); + + return bytesRead; +} + + +// TODO: eventually only include properties changed since the params.lastViewFrustumSent time +EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + requestedProperties += PROP_SOURCE_URL; + return requestedProperties; +} + +void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, appendValue, _source); +} + + +bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + void** intersectedObject, bool precisionPicking) const { + + RayIntersectionInfo rayInfo; + rayInfo._rayStart = origin; + rayInfo._rayDirection = direction; + rayInfo._rayLength = std::numeric_limits::max(); + + PlaneShape plane; + + const glm::vec3 UNROTATED_NORMAL(0.0f, 0.0f, -1.0f); + glm::vec3 normal = _rotation * UNROTATED_NORMAL; + plane.setNormal(normal); + plane.setPoint(getPosition()); // the position is definitely a point on our plane + + bool intersects = plane.findRayIntersection(rayInfo); + + if (intersects) { + glm::vec3 hitAt = origin + (direction * rayInfo._hitDistance); + // now we know the point the ray hit our plane + + glm::mat4 rotation = glm::mat4_cast(getRotation()); + glm::mat4 translation = glm::translate(getPosition()); + glm::mat4 entityToWorldMatrix = translation * rotation; + glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); + + glm::vec3 dimensions = getDimensions(); + glm::vec3 registrationPoint = getRegistrationPoint(); + glm::vec3 corner = -(dimensions * registrationPoint); + AABox entityFrameBox(corner, dimensions); + + glm::vec3 entityFrameHitAt = glm::vec3(worldToEntityMatrix * glm::vec4(hitAt, 1.0f)); + + intersects = entityFrameBox.contains(entityFrameHitAt); + } + + if (intersects) { + distance = rayInfo._hitDistance; + } + return intersects; +} diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h new file mode 100644 index 0000000000..35b1c79dc1 --- /dev/null +++ b/libraries/entities/src/WebEntityItem.h @@ -0,0 +1,59 @@ +// +// Created by Bradley Austin Davis on 2015/05/12 +// Copyright 2013 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 +// + +#ifndef hifi_WebEntityItem_h +#define hifi_WebEntityItem_h + +#include "EntityItem.h" + +class WebEntityItem : public EntityItem { +public: + static const QString DEFAULT_SOURCE_URL; + + static EntityItem* factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + WebEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + + ALLOW_INSTANTIATION // This class can be instantiated + + /// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately + virtual void setDimensions(const glm::vec3& value); + virtual ShapeType getShapeType() const { return SHAPE_TYPE_BOX; } + + // methods for getting/setting all properties of an entity + virtual EntityItemProperties getProperties() const; + virtual bool setProperties(const EntityItemProperties& properties); + + // TODO: eventually only include properties changed since the params.lastViewFrustumSent time + virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const; + + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData); + + virtual bool supportsDetailedRayIntersection() const { return true; } + virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + void** intersectedObject, bool precisionPicking) const; + + void setSourceUrl(const QString& value) { _sourceUrl = value; } + const QString& getSource() const { return _sourceUrl; } + +protected: + QString _sourceUrl; +}; + +#endif // hifi_WebEntityItem_h diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 7d13a00324..b133d1e488 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -24,30 +24,10 @@ Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus") // achieve. static const int SMALL_INTERVAL = 5; -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) { +OffscreenQmlSurface::OffscreenQmlSurface() { } -void OffscreenUiRoot::information(const QString& title, const QString& text) { - OffscreenUi::information(title, text); -} - -OffscreenUi::OffscreenUi() { - ::qmlRegisterType("Hifi", 1, 0, "Root"); -} - -OffscreenUi::~OffscreenUi() { +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 @@ -65,7 +45,7 @@ OffscreenUi::~OffscreenUi() { doneCurrent(); } -void OffscreenUi::create(QOpenGLContext* shareContext) { +void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { OffscreenGlCanvas::create(shareContext); makeCurrent(); @@ -87,13 +67,13 @@ void OffscreenUi::create(QOpenGLContext* shareContext) { // a timer with a small interval is used to get better performance. _updateTimer.setSingleShot(true); _updateTimer.setInterval(SMALL_INTERVAL); - connect(&_updateTimer, &QTimer::timeout, this, &OffscreenUi::updateQuick); + 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, &OffscreenUi::requestRender); - connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &OffscreenUi::requestUpdate); + connect(_renderControl, &QQuickRenderControl::renderRequested, this, &OffscreenQmlSurface::requestRender); + connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &OffscreenQmlSurface::requestUpdate); #ifdef DEBUG connect(_quickWindow, &QQuickWindow::focusObjectChanged, [this]{ @@ -110,11 +90,7 @@ void OffscreenUi::create(QOpenGLContext* shareContext) { _renderControl->initialize(&_context); } -void OffscreenUi::addImportPath(const QString& path) { - _qmlEngine->addImportPath(path); -} - -void OffscreenUi::resize(const QSize& newSize) { +void OffscreenQmlSurface::resize(const QSize& newSize) { makeCurrent(); qreal pixelRatio = _renderControl->_renderWindow ? _renderControl->_renderWindow->devicePixelRatio() : 1.0; @@ -141,15 +117,15 @@ void OffscreenUi::resize(const QSize& newSize) { doneCurrent(); } -QQuickItem* OffscreenUi::getRootItem() { +QQuickItem* OffscreenQmlSurface::getRootItem() { return _rootItem; } -void OffscreenUi::setBaseUrl(const QUrl& baseUrl) { +void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) { _qmlEngine->setBaseUrl(baseUrl); } -QObject* OffscreenUi::load(const QUrl& qmlSource, std::function f) { +QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function f) { _qmlComponent->loadUrl(qmlSource); if (_qmlComponent->isLoading()) { connect(_qmlComponent, &QQmlComponent::statusChanged, this, @@ -162,20 +138,20 @@ QObject* OffscreenUi::load(const QUrl& qmlSource, std::function f) { +QObject* OffscreenQmlSurface::finishQmlLoad(std::function f) { disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0); if (_qmlComponent->isError()) { QList errorList = _qmlComponent->errors(); @@ -232,7 +208,7 @@ QObject* OffscreenUi::finishQmlLoad(std::function } -void OffscreenUi::updateQuick() { +void OffscreenQmlSurface::updateQuick() { if (_paused) { return; } @@ -274,7 +250,7 @@ void OffscreenUi::updateQuick() { emit textureUpdated(fbo->texture()); } -QPointF OffscreenUi::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) { +QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) { vec2 sourceSize; if (dynamic_cast(sourceObject)) { sourceSize = toGlm(((QWidget*)sourceObject)->size()); @@ -297,7 +273,7 @@ QPointF OffscreenUi::mapWindowToUi(const QPointF& sourcePosition, QObject* sourc bool OffscreenUi::shouldSwallowShortcut(QEvent* event) { Q_ASSERT(event->type() == QEvent::ShortcutOverride); QObject* focusObject = _quickWindow->focusObject(); - if (focusObject != _quickWindow && focusObject != _rootItem) { + if (focusObject != _quickWindow && focusObject != getRootItem()) { //qDebug() << "Swallowed shortcut " << static_cast(event)->key(); event->accept(); return true; @@ -310,7 +286,7 @@ bool OffscreenUi::shouldSwallowShortcut(QEvent* event) { // Event handling customization // -bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { +bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* event) { // Only intercept events while we're in an active state if (_paused) { return false; @@ -389,37 +365,65 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { return false; } -void OffscreenUi::lockTexture(int texture) { +void OffscreenQmlSurface::lockTexture(int texture) { _fboCache.lockTexture(texture); } -void OffscreenUi::releaseTexture(int texture) { +void OffscreenQmlSurface::releaseTexture(int texture) { _fboCache.releaseTexture(texture); } -void OffscreenUi::pause() { +void OffscreenQmlSurface::pause() { _paused = true; } -void OffscreenUi::resume() { +void OffscreenQmlSurface::resume() { _paused = false; requestRender(); } -bool OffscreenUi::isPaused() const { +bool OffscreenQmlSurface::isPaused() const { return _paused; } -void OffscreenUi::setProxyWindow(QWindow* window) { +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) { +} + +void OffscreenUiRoot::information(const QString& title, const QString& text) { + OffscreenUi::information(title, text); +} + +OffscreenUi::OffscreenUi() { + ::qmlRegisterType("Hifi", 1, 0, "Root"); +} + +OffscreenUi::~OffscreenUi() { +} + + void OffscreenUi::show(const QUrl& url, const QString& name, std::function f) { - QQuickItem* item = _rootItem->findChild(name); + QQuickItem* item = getRootItem()->findChild(name); // First load? if (!item) { load(url, f); - item = _rootItem->findChild(name); + item = getRootItem()->findChild(name); } if (item) { item->setEnabled(true); @@ -427,11 +431,11 @@ void OffscreenUi::show(const QUrl& url, const QString& name, std::function f) { - QQuickItem* item = _rootItem->findChild(name); + QQuickItem* item = getRootItem()->findChild(name); // First load? if (!item) { load(url, f); - item = _rootItem->findChild(name); + item = getRootItem()->findChild(name); } if (item) { item->setEnabled(!item->isEnabled()); diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index ce40bec943..846cd4bfd6 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -96,9 +96,9 @@ private: offscreenUi->load(QML, f); \ } -class OffscreenUi : public OffscreenGlCanvas, public Dependency { +class OffscreenQmlSurface : public OffscreenGlCanvas { Q_OBJECT - +protected: class QMyQuickRenderControl : public QQuickRenderControl { protected: QWindow* renderWindow(QPoint* offset) Q_DECL_OVERRIDE{ @@ -113,36 +113,76 @@ class OffscreenUi : public OffscreenGlCanvas, public Dependency { private: QWindow* _renderWindow{ nullptr }; - friend class OffscreenUi; + friend class OffscreenQmlSurface; }; - public: + OffscreenQmlSurface(); + virtual ~OffscreenQmlSurface(); + using MouseTranslator = std::function; - OffscreenUi(); - virtual ~OffscreenUi(); + 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); } - void show(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); - void toggle(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); - void setBaseUrl(const QUrl& baseUrl); - void addImportPath(const QString& path); - //QQmlContext* getQmlContext(); - QQuickItem* getRootItem(); - void pause(); - void resume(); - bool isPaused() const; + + // Optional values for event handling void setProxyWindow(QWindow* window); - bool shouldSwallowShortcut(QEvent* event); - QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); - virtual bool eventFilter(QObject* originalDestination, QEvent* event); 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 + +public: + OffscreenUi(); + virtual ~OffscreenUi(); + void show(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); + void toggle(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); + bool shouldSwallowShortcut(QEvent* event); // Messagebox replacement functions using ButtonCallback = std::function; @@ -168,33 +208,6 @@ public: static void critical(const QString& title, const QString& text, ButtonCallback callback = NO_OP_CALLBACK, QMessageBox::StandardButtons buttons = QMessageBox::Ok); - -private: - QObject* finishQmlLoad(std::function f); - -private slots: - void updateQuick(); - -public slots: - void requestUpdate(); - void requestRender(); - void lockTexture(int texture); - void releaseTexture(int texture); - -signals: - void textureUpdated(GLuint texture); - -private: - QMyQuickRenderControl* _renderControl{ new QMyQuickRenderControl }; - QQuickWindow* _quickWindow{ nullptr }; - 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