diff --git a/interface/resources/qml/+android/Web3DOverlay.qml b/interface/resources/qml/+android/Web3DSurface.qml similarity index 100% rename from interface/resources/qml/+android/Web3DOverlay.qml rename to interface/resources/qml/+android/Web3DSurface.qml diff --git a/interface/resources/qml/Web3DOverlay.qml b/interface/resources/qml/Web3DSurface.qml similarity index 100% rename from interface/resources/qml/Web3DOverlay.qml rename to interface/resources/qml/Web3DSurface.qml diff --git a/interface/resources/qml/controls/+android/WebEntityView.qml b/interface/resources/qml/controls/+android/WebEntityView.qml deleted file mode 100644 index 848077cea0..0000000000 --- a/interface/resources/qml/controls/+android/WebEntityView.qml +++ /dev/null @@ -1,76 +0,0 @@ -// -// Web3DOverlay.qml -// -// Created by Gabriel Calero & Cristian Duarte on Jun 22, 2018 -// Copyright 2016 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 -// - -import QtQuick 2.5 -import QtGraphicalEffects 1.0 - -Item { - - property string url - RadialGradient { - anchors.fill: parent - gradient: Gradient { - GradientStop { position: 0.0; color: "#262626" } - GradientStop { position: 1.0; color: "#000000" } - } - } - - function shortUrl(url) { - var hostBegin = url.indexOf("://"); - if (hostBegin > -1) { - url = url.substring(hostBegin + 3); - } - - var portBegin = url.indexOf(":"); - if (portBegin > -1) { - url = url.substring(0, portBegin); - } - - var pathBegin = url.indexOf("/"); - if (pathBegin > -1) { - url = url.substring(0, pathBegin); - } - - if (url.length > 45) { - url = url.substring(0, 45); - } - - return url; - } - - Text { - id: urlText - text: shortUrl(url) - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - anchors.fill: parent - anchors.rightMargin: 10 - anchors.leftMargin: 10 - font.family: "Cairo" - font.weight: Font.DemiBold - font.pointSize: 48 - fontSizeMode: Text.Fit - color: "#FFFFFF" - minimumPixelSize: 5 - } - - Image { - id: hand - source: "../../../icons/hand.svg" - width: 300 - height: 300 - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.bottomMargin: 100 - anchors.rightMargin: 100 - } - - -} diff --git a/interface/resources/qml/controls/WebEntityView.qml b/interface/resources/qml/controls/WebEntityView.qml deleted file mode 100644 index 3bd6aad053..0000000000 --- a/interface/resources/qml/controls/WebEntityView.qml +++ /dev/null @@ -1,18 +0,0 @@ -// -// WebEntityView.qml -// -// Created by Kunal Gosar on 16 March 2017 -// Copyright 2017 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 -// - -import QtQuick 2.5 -import "." - -WebView { - viewProfile: FileTypeProfile; - - urlTag: "noDownload=true"; -} diff --git a/interface/resources/qml/controlsUit/Key.qml b/interface/resources/qml/controlsUit/Key.qml index dd77fc92dc..b8d95acf1c 100644 --- a/interface/resources/qml/controlsUit/Key.qml +++ b/interface/resources/qml/controlsUit/Key.qml @@ -58,7 +58,7 @@ Item { keyItem.state = "mouseOver"; var globalPosition = keyItem.mapToGlobal(mouseArea1.mouseX, mouseArea1.mouseY); - var pointerID = Web3DOverlay.deviceIdByTouchPoint(globalPosition.x, globalPosition.y); + var pointerID = QmlSurface.deviceIdByTouchPoint(globalPosition.x, globalPosition.y); if (Pointers.isLeftHand(pointerID)) { Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, leftHand); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d306c77cce..e2e288cd36 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -151,6 +151,7 @@ #include #include #include +#include #include #include "recording/ClipCache.h" @@ -2316,6 +2317,92 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return DependencyManager::get()->getMyAvatar()->getWorldOrientation() * Vectors::UP; }); + render::entities::WebEntityRenderer::setInitializeWebSurfaceOperator([](QSharedPointer webSurface) { + webSurface = DependencyManager::get()->acquire(render::entities::WebEntityRenderer::QML); + webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get().data()); + webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance()); + webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get().data()); + }); + render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([](const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) { + bool isTablet = url == TabletScriptingInterface::QML; + if (htmlContent) { + // FIXME use the surface cache instead of explicit creation + webSurface = QSharedPointer(new OffscreenQmlSurface(), [](OffscreenQmlSurface* webSurface) { + AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete webSurface; + }); + }); + webSurface->load(render::entities::WebEntityRenderer::QML, [url](QQmlContext* context, QObject* item) { + item->setProperty("url", url); + }); +#if 0 + // This doesn't work for some reason + webSurface = DependencyManager::get()->acquire(render::entities::WebEntityRenderer::QML); + cachedWebSurface = true; + connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, [url, webSurface](QQmlContext* surfaceContext) { + webSurface->getRootItem()->setProperty("url", url); + }); +#endif + } else { + webSurface = QSharedPointer(new OffscreenQmlSurface(), [](OffscreenQmlSurface* webSurface) { + AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete webSurface; + }); + }); + connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, [url](QQmlContext* surfaceContext) { + //setupQmlSurface(isTablet, url == OVERLAY_LOGIN_DIALOG.toString()); + }); + webSurface->load(url); + cachedWebSurface = false; + } + const uint8_t DEFAULT_MAX_FPS = 10; + const uint8_t TABLET_FPS = 90; + webSurface->setMaxFps(isTablet ? TABLET_FPS : DEFAULT_MAX_FPS); + }); + render::entities::WebEntityRenderer::setReleaseWebSurfaceOperator([](QSharedPointer& webSurface, bool& cachedWebSurface, std::vector& connections) { + QQuickItem* rootItem = webSurface->getRootItem(); + if (rootItem && rootItem->objectName() == "tabletRoot") { + auto tabletScriptingInterface = DependencyManager::get(); + if (tabletScriptingInterface) { + tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); + } + } + + // Fix for crash in QtWebEngineCore when rapidly switching domains + // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. + if (rootItem) { + // stop loading + QMetaObject::invokeMethod(rootItem, "stop"); + } + + webSurface->pause(); + + for (auto& connection : connections) { + QObject::disconnect(connection); + } + connections.clear(); + + // If the web surface was fetched out of the cache, release it back into the cache + if (cachedWebSurface) { + // If it's going back into the cache make sure to explicitly set the URL to a blank page + // in order to stop any resource consumption or audio related to the page. + if (rootItem) { + rootItem->setProperty("url", "about:blank"); + } + auto offscreenCache = DependencyManager::get(); + // FIXME prevents crash on shutdown, but we shoudln't have to do this check + if (offscreenCache) { + offscreenCache->release(render::entities::WebEntityRenderer::QML, webSurface); + } + cachedWebSurface = false; + } + webSurface.reset(); + }); + // Preload Tablet sounds DependencyManager::get()->preloadSounds(); DependencyManager::get()->createKeyboard(); @@ -3012,7 +3099,7 @@ void Application::initializeUi() { }); offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1); - offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2); + offscreenSurfaceCache->reserve(render::entities::WebEntityRenderer::QML, 2); #endif flushMenuUpdates(); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index ec6b62e237..3fef4ca61a 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -64,20 +64,13 @@ #include "AboutUtil.h" #include "ResourceRequestObserver.h" +#include + static int MAX_WINDOW_SIZE = 4096; static const float METERS_TO_INCHES = 39.3701f; static const float OPAQUE_ALPHA_THRESHOLD = 0.99f; const QString Web3DOverlay::TYPE = "web3d"; -const QString Web3DOverlay::QML = "Web3DOverlay.qml"; - -static auto qmlSurfaceDeleter = [](OffscreenQmlSurface* surface) { - AbstractViewStateInterface::instance()->sendLambdaEvent([surface] { - // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown - // if the application has already stopped its event loop, delete must be explicit - delete surface; - }); -}; Web3DOverlay::Web3DOverlay() { _touchDevice.setCapabilities(QTouchDevice::Position); @@ -87,34 +80,23 @@ Web3DOverlay::Web3DOverlay() { _geometryId = DependencyManager::get()->allocateID(); connect(this, &Web3DOverlay::requestWebSurface, this, &Web3DOverlay::buildWebSurface); - connect(this, &Web3DOverlay::releaseWebSurface, this, &Web3DOverlay::destroyWebSurface); connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface); //need to be intialized before Tablet 1st open - _webSurface = DependencyManager::get()->acquire(QML); _cachedWebSurface = true; - _webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED - _webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED - _webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance()); - _webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get().data()); + render::entities::WebEntityRenderer::initializeWebSurface(_webSurface); } Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) : Billboard3DOverlay(Web3DOverlay), _url(Web3DOverlay->_url), _scriptURL(Web3DOverlay->_scriptURL), - _dpi(Web3DOverlay->_dpi), - _showKeyboardFocusHighlight(Web3DOverlay->_showKeyboardFocusHighlight) + _dpi(Web3DOverlay->_dpi) { _geometryId = DependencyManager::get()->allocateID(); } Web3DOverlay::~Web3DOverlay() { - disconnect(this, &Web3DOverlay::requestWebSurface, this, nullptr); - disconnect(this, &Web3DOverlay::releaseWebSurface, this, nullptr); - disconnect(this, &Web3DOverlay::resizeWebSurface, this, nullptr); - destroyWebSurface(); auto geometryCache = DependencyManager::get(); if (geometryCache) { @@ -128,81 +110,22 @@ void Web3DOverlay::rebuildWebSurface() { } void Web3DOverlay::destroyWebSurface() { - if (!_webSurface) { - return; + if (_webSurface) { + render::entities::WebEntityRenderer::releaseWebSurface(_webSurface, _cachedWebSurface, _connections); } - - QQuickItem* rootItem = _webSurface->getRootItem(); - - if (rootItem && rootItem->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - if (tabletScriptingInterface) { - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); - } - } - - // Fix for crash in QtWebEngineCore when rapidly switching domains - // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. - if (rootItem) { - // stop loading - QMetaObject::invokeMethod(rootItem, "stop"); - } - - _webSurface->pause(); - - QObject::disconnect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent); - QObject::disconnect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived); - - // If the web surface was fetched out of the cache, release it back into the cache - if (_cachedWebSurface) { - // If it's going back into the cache make sure to explicitly set the URL to a blank page - // in order to stop any resource consumption or audio related to the page. - if (rootItem) { - rootItem->setProperty("url", "about:blank"); - } - auto offscreenCache = DependencyManager::get(); - // FIXME prevents crash on shutdown, but we shoudln't have to do this check - if (offscreenCache) { - offscreenCache->release(QML, _webSurface); - } - _cachedWebSurface = false; - } - _webSurface.reset(); } void Web3DOverlay::buildWebSurface() { if (_webSurface) { return; } - // FIXME the context save here is most likely unecessary since the QML surfaces now render - // off the main thread, and all GL context work is done off the main thread (I *think*) - gl::withSavedContext([&] { - // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces - // and the current rendering load) - if (_currentMaxFPS != _desiredMaxFPS) { - setMaxFPS(_desiredMaxFPS); - } - if (isWebContent()) { - _webSurface = DependencyManager::get()->acquire(QML); - _cachedWebSurface = true; - _webSurface->getRootItem()->setProperty("url", _url); - _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); - } else { - _webSurface = QSharedPointer(new OffscreenQmlSurface(), qmlSurfaceDeleter); - connect(_webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, [this](QQmlContext* surfaceContext) { - setupQmlSurface(_url == TabletScriptingInterface::QML, _url == OVERLAY_LOGIN_DIALOG.toString()); - }); - _webSurface->load(_url); - _cachedWebSurface = false; - } - _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getWorldPosition())); - onResizeWebSurface(); - _webSurface->resume(); - }); + render::entities::WebEntityRenderer::acquireWebSurface(_url, isWebContent(), _webSurface, _cachedWebSurface); + onResizeWebSurface(); + _webSurface->resume(); - QObject::connect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent); - QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived); + _connections.push_back(QObject::connect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent)); + _connections.push_back(QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived)); } void Web3DOverlay::update(float deltatime) { @@ -272,19 +195,16 @@ void Web3DOverlay::setupQmlSurface(bool isTablet, bool isLoginDialog) { _webSurface->getSurfaceContext()->setContextProperty("Workload", qApp->getGameWorkload()._engine->getConfiguration().get()); _webSurface->getSurfaceContext()->setContextProperty("Controller", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Pointers", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Web3DOverlay", this); _webSurface->getSurfaceContext()->setContextProperty("Window", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface()); _webSurface->getSurfaceContext()->setContextProperty("HiFiAbout", AboutUtil::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("WalletScriptingInterface", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("ResourceRequestObserver", DependencyManager::get().data()); - - // Override min fps for tablet UI, for silky smooth scrolling - setMaxFPS(90); } } void Web3DOverlay::setMaxFPS(uint8_t maxFPS) { + // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces and the current rendering load) _desiredMaxFPS = maxFPS; if (_webSurface) { _webSurface->setMaxFps(_desiredMaxFPS); @@ -305,14 +225,6 @@ void Web3DOverlay::onResizeWebSurface() { _webSurface->resize(QSize(dims.x, dims.y)); } -unsigned int Web3DOverlay::deviceIdByTouchPoint(qreal x, qreal y) { - if (_webSurface) { - return _webSurface->deviceIdByTouchPoint(x, y); - } else { - return PointerEvent::INVALID_POINTER_ID; - } -} - void Web3DOverlay::render(RenderArgs* args) { if (!_renderVisible || !getParentVisible()) { return; @@ -506,11 +418,6 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { _desiredMaxFPS = maxFPS.toInt(); } - auto showKeyboardFocusHighlight = properties["showKeyboardFocusHighlight"]; - if (showKeyboardFocusHighlight.isValid()) { - _showKeyboardFocusHighlight = showKeyboardFocusHighlight.toBool(); - } - auto inputModeValue = properties["inputMode"]; if (inputModeValue.isValid()) { QString inputModeStr = inputModeValue.toString(); @@ -573,8 +480,6 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { * @property {Vec2} dimensions=1,1 - The size of the overlay to display the Web page on, in meters. Synonyms: * scale, size. * @property {number} maxFPS=10 - The maximum update rate for the Web overlay content, in frames/second. - * @property {boolean} showKeyboardFocusHighlight=true - If true, the Web overlay is highlighted when it has - * keyboard focus. * @property {string} inputMode=Touch - The user input mode to use - either "Touch" or "Mouse". */ QVariant Web3DOverlay::getProperty(const QString& property) { @@ -590,9 +495,6 @@ QVariant Web3DOverlay::getProperty(const QString& property) { if (property == "maxFPS") { return _desiredMaxFPS; } - if (property == "showKeyboardFocusHighlight") { - return _showKeyboardFocusHighlight; - } if (property == "inputMode") { if (_inputMode == Mouse) { diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 548ad7abe0..03bfcdfa98 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -22,8 +22,6 @@ class Web3DOverlay : public Billboard3DOverlay { using Parent = Billboard3DOverlay; public: - - static const QString QML; static QString const TYPE; virtual QString getType() const override { return TYPE; } @@ -63,8 +61,6 @@ public: void destroyWebSurface(); void onResizeWebSurface(); - Q_INVOKABLE unsigned int deviceIdByTouchPoint(qreal x, qreal y); - public slots: void emitScriptEvent(const QVariant& scriptMessage); @@ -73,7 +69,6 @@ signals: void webEventReceived(const QVariant& message); void resizeWebSurface(); void requestWebSurface(); - void releaseWebSurface(); protected: Transform evalRenderTransform() override; @@ -91,7 +86,6 @@ private: QString _scriptURL; float _dpi { 30.0f }; int _geometryId { 0 }; - bool _showKeyboardFocusHighlight { true }; QTouchDevice _touchDevice; @@ -99,6 +93,8 @@ private: uint8_t _currentMaxFPS { 0 }; bool _mayNeedResize { false }; + + std::vector _connections; }; #endif // hifi_Web3DOverlay_h diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 2942de0ba4..cb2885316c 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -30,19 +30,24 @@ using namespace render; using namespace render::entities; -static const QString WEB_ENTITY_QML = "controls/WebEntityView.qml"; +const QString WebEntityRenderer::QML = "Web3DSurface.qml"; +std::function)> WebEntityRenderer::_initializeWebSurfaceOperator = nullptr; +std::function&, bool&)> WebEntityRenderer::_acquireWebSurfaceOperator = nullptr; +std::function&, bool&, std::vector&)> WebEntityRenderer::_releaseWebSurfaceOperator = nullptr; + +static int MAX_WINDOW_SIZE = 4096; const float METERS_TO_INCHES = 39.3701f; -static uint32_t _currentWebCount{ 0 }; -// Don't allow more than 20 concurrent web views -static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20; +static float OPAQUE_ALPHA_THRESHOLD = 0.99f; + // If a web-view hasn't been rendered for 30 seconds, de-allocate the framebuffer static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND; -static int MAX_WINDOW_SIZE = 4096; -static float OPAQUE_ALPHA_THRESHOLD = 0.99f; -static int DEFAULT_MAX_FPS = 10; -static int YOUTUBE_MAX_FPS = 30; +static uint8_t YOUTUBE_MAX_FPS = 30; + +// Don't allow more than 20 concurrent web views +static uint32_t _currentWebCount { 0 }; +static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20; static QTouchDevice _touchDevice; static const char* URL_PROPERTY = "url"; @@ -71,13 +76,19 @@ WebEntityRenderer::WebEntityRenderer(const EntityItemPointer& entity) : Parent(e _touchDevice.setMaximumTouchPoints(4); }); _geometryId = DependencyManager::get()->allocateID(); + _texture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda()); _texture->setSource(__FUNCTION__); + + // need to be intialized early + _cachedWebSurface = true; + WebEntityRenderer::initializeWebSurface(_webSurface); + _timer.setInterval(MSECS_PER_SECOND); connect(&_timer, &QTimer::timeout, this, &WebEntityRenderer::onTimeout); } -void WebEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity) { +WebEntityRenderer::~WebEntityRenderer() { destroyWebSurface(); auto geometryCache = DependencyManager::get(); @@ -86,6 +97,11 @@ void WebEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity) } } +bool WebEntityRenderer::isTransparent() const { + float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; + return fadeRatio < OPAQUE_ALPHA_THRESHOLD || _alpha < 1.0f; +} + bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { if (_contextPosition != entity->getWorldPosition()) { return true; @@ -101,11 +117,31 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe } } - if (_lastSourceUrl != entity->getSourceUrl()) { + if (_color != entity->getColor()) { return true; } - if (_lastDPI != entity->getDPI()) { + if (_alpha != entity->getAlpha()) { + return true; + } + + if (_sourceURL != entity->getSourceUrl()) { + return true; + } + + if (_dpi != entity->getDPI()) { + return true; + } + + if (_scriptURL != entity->getScriptURL()) { + return true; + } + + if (_maxFPS != entity->getMaxFPS()) { + return true; + } + + if (_inputMode != entity->getInputMode()) { return true; } @@ -113,35 +149,26 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe } bool WebEntityRenderer::needsRenderUpdate() const { - { - QSharedPointer webSurface; - withReadLock([&] { - webSurface = _webSurface; - }); - if (!webSurface) { - // If we have rendered recently, and there is no web surface, we're going to create one - return true; - } + if (resultWithReadLock([&] { + // If we have rendered recently, and there is no web surface, we're going to create one + return !_webSurface; + })) { + return true; } return Parent::needsRenderUpdate(); } void WebEntityRenderer::onTimeout() { - bool needsCheck = resultWithReadLock([&] { + uint64_t lastRenderTime; + if (!resultWithReadLock([&] { + lastRenderTime = _lastRenderTime; return (_lastRenderTime != 0 && (bool)_webSurface); - }); - - if (!needsCheck) { + })) { return; } - uint64_t interval; - withReadLock([&] { - interval = usecTimestampNow() - _lastRenderTime; - }); - - if (interval > MAX_NO_RENDER_INTERVAL) { + if (usecTimestampNow() - lastRenderTime > MAX_NO_RENDER_INTERVAL) { destroyWebSurface(); } } @@ -154,9 +181,9 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene { auto newSourceUrl = entity->getSourceUrl(); auto newContentType = getContentType(newSourceUrl); - auto currentContentType = ContentType::NoContent; + ContentType currentContentType; withReadLock([&] { - urlChanged = _lastSourceUrl != newSourceUrl; + urlChanged = _sourceURL != newSourceUrl; currentContentType = _contentType; }); @@ -168,7 +195,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene } withWriteLock([&] { - _lastSourceUrl = newSourceUrl; + _sourceURL = newSourceUrl; _contentType = newContentType; }); } @@ -176,6 +203,11 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene withWriteLock([&] { + _inputMode = entity->getInputMode(); + _dpi = entity->getDPI(); + _color = entity->getColor(); + _alpha = entity->getAlpha(); + if (_contentType == ContentType::NoContent) { return; } @@ -187,19 +219,39 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene } if (urlChanged && _contentType == ContentType::HtmlContent) { - _webSurface->getRootItem()->setProperty(URL_PROPERTY, _lastSourceUrl); + _webSurface->getRootItem()->setProperty(URL_PROPERTY, _sourceURL); + } + + { + auto scriptURL = entity->getScriptURL(); + if (_scriptURL != scriptURL) { + _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); + _scriptURL = scriptURL; + } + } + + { + auto maxFPS = entity->getMaxFPS(); + if (_maxFPS != maxFPS) { + // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. + // FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the web entity + if (QUrl(_sourceURL).host().endsWith("youtube.com", Qt::CaseInsensitive)) { + _webSurface->setMaxFps(YOUTUBE_MAX_FPS); + } else { + _webSurface->setMaxFps(_maxFPS); + } + _maxFPS = maxFPS; + } + } + + if (_contextPosition != entity->getWorldPosition()) { + _contextPosition = entity->getWorldPosition(); + _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); } void* key = (void*)this; AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] () { withWriteLock([&] { - if (_contextPosition != entity->getWorldPosition()) { - // update globalPosition - _contextPosition = entity->getWorldPosition(); - _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); - } - - _lastDPI = entity->getDPI(); glm::vec2 windowSize = getWindowSize(entity); _webSurface->resize(QSize(windowSize.x, windowSize.y)); @@ -212,19 +264,11 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene } void WebEntityRenderer::doRender(RenderArgs* args) { + PerformanceTimer perfTimer("WebEntityRenderer::render"); withWriteLock([&] { _lastRenderTime = usecTimestampNow(); }); -#ifdef WANT_EXTRA_DEBUGGING - { - gpu::Batch& batch = *args->_batch; - batch.setModelTransform(getTransformToCenter()); // we want to include the scale as well - glm::vec4 cubeColor{ 1.0f, 0.0f, 0.0f, 1.0f }; - DependencyManager::get()->renderWireCube(batch, 1.0f, cubeColor); - } -#endif - // Try to update the texture { QSharedPointer webSurface; @@ -242,20 +286,21 @@ void WebEntityRenderer::doRender(RenderArgs* args) { } } - PerformanceTimer perfTimer("WebEntityRenderer::render"); static const glm::vec2 texMin(0.0f), texMax(1.0f), topLeft(-0.5f), bottomRight(0.5f); gpu::Batch& batch = *args->_batch; + glm::vec4 color; withReadLock([&] { + float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; + color = glm::vec4(toGlm(_color), _alpha * fadeRatio); batch.setModelTransform(_renderTransform); }); batch.setResourceTexture(0, _texture); - float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; // Turn off jitter for these entities batch.pushProjectionJitter(); - DependencyManager::get()->bindWebBrowserProgram(batch, fadeRatio < OPAQUE_ALPHA_THRESHOLD); - DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio), _geometryId); + DependencyManager::get()->bindWebBrowserProgram(batch, color.a < OPAQUE_ALPHA_THRESHOLD); + DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, color, _geometryId); batch.popProjectionJitter(); batch.setResourceTexture(0, nullptr); } @@ -264,89 +309,49 @@ bool WebEntityRenderer::hasWebSurface() { return (bool)_webSurface && _webSurface->getRootItem(); } -static const auto WebSurfaceDeleter = [](OffscreenQmlSurface* webSurface) { - AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] { - // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown - // if the application has already stopped its event loop, delete must be explicit - delete webSurface; - }); -}; - bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { + if (_webSurface && !_webSurface->getRootItem()) { + // We're waiting on the root item + return false; + } + if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) { qWarning() << "Too many concurrent web views to create new view"; return false; } ++_currentWebCount; - - // FIXME use the surface cache instead of explicit creation - _webSurface = QSharedPointer(new OffscreenQmlSurface(), WebSurfaceDeleter); - // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces - // and the current rendering load) - _webSurface->setMaxFps(DEFAULT_MAX_FPS); - QObject::connect(_webSurface.data(), &OffscreenQmlSurface::rootContextCreated, [](QQmlContext* surfaceContext) { - // FIXME - Keyboard HMD only: Possibly add "HMDinfo" object to context for WebView.qml. - surfaceContext->setContextProperty("desktop", QVariant()); - // Let us interact with the keyboard - surfaceContext->setContextProperty("tabletInterface", DependencyManager::get().data()); - }); - - // forward web events to EntityScriptingInterface - auto entities = DependencyManager::get(); - const EntityItemID entityItemID = entity->getID(); - QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, [=](const QVariant& message) { - emit entities->webEventReceived(entityItemID, message); - }); - - if (_contentType == ContentType::HtmlContent) { - // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. - // FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the - // web entity - if (QUrl(_lastSourceUrl).host().endsWith("youtube.com", Qt::CaseInsensitive)) { - _webSurface->setMaxFps(YOUTUBE_MAX_FPS); - } else { - _webSurface->setMaxFps(DEFAULT_MAX_FPS); - } - _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { - item->setProperty(URL_PROPERTY, _lastSourceUrl); - }); - } else if (_contentType == ContentType::QmlContent) { - _webSurface->load(_lastSourceUrl); - } + WebEntityRenderer::acquireWebSurface(_sourceURL, _contentType == ContentType::HtmlContent, _webSurface, _cachedWebSurface); _fadeStartTime = usecTimestampNow(); _webSurface->resume(); + _connections.push_back(QObject::connect(this, &WebEntityRenderer::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent)); + _connections.push_back(QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &WebEntityRenderer::webEventReceived)); + const EntityItemID entityItemID = entity->getID(); + _connections.push_back(QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, [entityItemID](const QVariant& message) { + emit DependencyManager::get()->webEventReceived(entityItemID, message); + })); + return _webSurface->getRootItem(); } void WebEntityRenderer::destroyWebSurface() { QSharedPointer webSurface; - ContentType contentType{ ContentType::NoContent }; + ContentType contentType = ContentType::NoContent; withWriteLock([&] { webSurface.swap(_webSurface); - std::swap(contentType, _contentType); + _contentType = contentType; }); if (webSurface) { --_currentWebCount; - QQuickItem* rootItem = webSurface->getRootItem(); - - // Fix for crash in QtWebEngineCore when rapidly switching domains - // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. - if (rootItem && contentType == ContentType::HtmlContent) { - // stop loading - QMetaObject::invokeMethod(rootItem, "stop"); - } - - webSurface->pause(); - webSurface.reset(); + WebEntityRenderer::releaseWebSurface(webSurface, _cachedWebSurface, _connections); } } glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) const { glm::vec2 dims = glm::vec2(entity->getScaledDimensions()); - dims *= METERS_TO_INCHES * _lastDPI; + dims *= METERS_TO_INCHES * _dpi; // ensure no side is never larger then MAX_WINDOW_SIZE float max = (dims.x > dims.y) ? dims.x : dims.y; @@ -358,29 +363,83 @@ glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) con } void WebEntityRenderer::hoverEnterEntity(const PointerEvent& event) { - if (_webSurface) { + if (_inputMode == WebInputMode::MOUSE) { + handlePointerEvent(event); + } else if (_webSurface) { PointerEvent webEvent = event; - webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI)); + webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); _webSurface->hoverBeginEvent(webEvent, _touchDevice); } } void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) { - if (_webSurface) { + if (_inputMode == WebInputMode::MOUSE) { + PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(), + event.getButton(), event.getButtons(), event.getKeyboardModifiers()); + handlePointerEvent(endEvent); + // QML onReleased is only triggered if a click has happened first. We need to send this "fake" mouse move event to properly trigger an onExited. + PointerEvent endMoveEvent(PointerEvent::Move, event.getID()); + handlePointerEvent(endMoveEvent); + } else if (_webSurface) { PointerEvent webEvent = event; - webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI)); + webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); _webSurface->hoverEndEvent(webEvent, _touchDevice); } } void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) { + if (_inputMode == WebInputMode::TOUCH) { + handlePointerEventAsTouch(event); + } else { + handlePointerEventAsMouse(event); + } +} + +void WebEntityRenderer::handlePointerEventAsTouch(const PointerEvent& event) { if (_webSurface) { PointerEvent webEvent = event; - webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI)); + webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); _webSurface->handlePointerEvent(webEvent, _touchDevice); } } +void WebEntityRenderer::handlePointerEventAsMouse(const PointerEvent& event) { + if (!_webSurface) { + return; + } + + glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); + QPointF windowPoint(windowPos.x, windowPos.y); + + Qt::MouseButtons buttons = Qt::NoButton; + if (event.getButtons() & PointerEvent::PrimaryButton) { + buttons |= Qt::LeftButton; + } + + Qt::MouseButton button = Qt::NoButton; + if (event.getButton() == PointerEvent::PrimaryButton) { + button = Qt::LeftButton; + } + + QEvent::Type type; + switch (event.getType()) { + case PointerEvent::Press: + type = QEvent::MouseButtonPress; + break; + case PointerEvent::Release: + type = QEvent::MouseButtonRelease; + break; + case PointerEvent::Move: + type = QEvent::MouseMove; + break; + default: + return; + } + + QMouseEvent mouseEvent(type, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); + QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); +} + void WebEntityRenderer::setProxyWindow(QWindow* proxyWindow) { if (_webSurface) { _webSurface->setProxyWindow(proxyWindow); @@ -394,8 +453,6 @@ QObject* WebEntityRenderer::getEventHandler() { return _webSurface->getEventHandler(); } -bool WebEntityRenderer::isTransparent() const { - float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; - return fadeRatio < OPAQUE_ALPHA_THRESHOLD; -} - +void WebEntityRenderer::emitScriptEvent(const QVariant& message) { + QMetaObject::invokeMethod(this, "scriptEventReceived", Q_ARG(QVariant, message)); +} \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 12640f697d..737b99de43 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -24,13 +24,35 @@ class WebEntityRenderer : public TypedEntityRenderer { public: WebEntityRenderer(const EntityItemPointer& entity); + ~WebEntityRenderer(); Q_INVOKABLE void hoverEnterEntity(const PointerEvent& event); Q_INVOKABLE void hoverLeaveEntity(const PointerEvent& event); Q_INVOKABLE void handlePointerEvent(const PointerEvent& event); + static const QString QML; + static void setInitializeWebSurfaceOperator(std::function)> initializeWebSurfaceOperator) { _initializeWebSurfaceOperator = initializeWebSurfaceOperator; } + static void initializeWebSurface(QSharedPointer webSurface) { + if (_initializeWebSurfaceOperator) { + _initializeWebSurfaceOperator(webSurface); + } + } + + static void setAcquireWebSurfaceOperator(std::function&, bool&)> acquireWebSurfaceOperator) { _acquireWebSurfaceOperator = acquireWebSurfaceOperator; } + static void acquireWebSurface(const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) { + if (_acquireWebSurfaceOperator) { + _acquireWebSurfaceOperator(url, htmlContent, webSurface, cachedWebSurface); + } + } + + static void setReleaseWebSurfaceOperator(std::function&, bool&, std::vector&)> releaseWebSurfaceOperator) { _releaseWebSurfaceOperator = releaseWebSurfaceOperator; } + static void releaseWebSurface(QSharedPointer& webSurface, bool& cachedWebSurface, std::vector& connections) { + if (_releaseWebSurfaceOperator) { + _releaseWebSurfaceOperator(webSurface, cachedWebSurface, connections); + } + } + protected: - virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override; virtual bool needsRenderUpdate() const override; virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; @@ -39,9 +61,13 @@ protected: virtual bool wantsHandControllerPointerEvents() const override { return true; } virtual bool wantsKeyboardFocus() const override { return true; } + virtual void setProxyWindow(QWindow* proxyWindow) override; virtual QObject* getEventHandler() override; + void handlePointerEventAsTouch(const PointerEvent& event); + void handlePointerEventAsMouse(const PointerEvent& event); + private: void onTimeout(); bool buildWebSurface(const TypedEntityPointer& entity); @@ -49,30 +75,47 @@ private: bool hasWebSurface(); glm::vec2 getWindowSize(const TypedEntityPointer& entity) const; - int _geometryId{ 0 }; enum class ContentType { NoContent, HtmlContent, QmlContent }; - static ContentType getContentType(const QString& urlString); + ContentType _contentType { ContentType::NoContent }; - ContentType _contentType{ ContentType::NoContent }; QSharedPointer _webSurface; - glm::vec3 _contextPosition; + bool _cachedWebSurface { false }; gpu::TexturePointer _texture; - QString _lastSourceUrl; - uint16_t _lastDPI; + + glm::u8vec3 _color; + float _alpha { 1.0f }; + + QString _sourceURL; + uint16_t _dpi; + QString _scriptURL; + uint8_t _maxFPS; + WebInputMode _inputMode; + + glm::vec3 _contextPosition; + QTimer _timer; uint64_t _lastRenderTime { 0 }; + + std::vector _connections; + + static std::function)> _initializeWebSurfaceOperator; + static std::function&, bool&)> _acquireWebSurfaceOperator; + static std::function&, bool&, std::vector&)> _releaseWebSurfaceOperator; + +public slots: + void emitScriptEvent(const QVariant& scriptMessage); + +signals: + void scriptEventReceived(const QVariant& message); + void webEventReceived(const QVariant& message); }; -} } // namespace - -#if 0 - virtual void emitScriptEvent(const QVariant& message) override; -#endif +} } #endif // hifi_RenderableWebEntityItem_h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3ae3a647cf..dfdc7933a9 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1285,6 +1285,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * The entity has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. * @typedef {object} Entities.EntityProperties-Web * @property {Vec3} dimensions=0.1,0.1,0.01 - The dimensions of the entity. + * @property {Color} color=255,255,255 - The color of the web surface. + * @property {number} alpha=1 - The alpha of the web surface. * @property {string} sourceUrl="" - The URL of the Web page to display. This value does not change as you or others navigate * on the Web entity. * @property {number} dpi=30 - The resolution to display the page at, in dots per inch. If you convert this to dots per meter @@ -1689,6 +1691,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool // Web only if (_type == EntityTypes::Web) { + COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DPI, dpi); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT_URL, scriptURL); @@ -3051,6 +3056,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy } if (properties.getType() == EntityTypes::Web) { + APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha()); + APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl()); APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI()); APPEND_ENTITY_PROPERTY(PROP_SCRIPT_URL, properties.getScriptURL()); diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 88612c8be6..7a7e211d0d 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -232,6 +232,12 @@ void ShapeEntityItem::setAlpha(float alpha) { }); } +float ShapeEntityItem::getAlpha() const { + return resultWithReadLock([&] { + return _alpha; + }); +} + void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { const float MAX_FLAT_DIMENSION = 0.0001f; if ((_shape == entity::Shape::Circle || _shape == entity::Shape::Quad) && value.y > MAX_FLAT_DIMENSION) { diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index c89a8934f8..6e36e15a84 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -74,7 +74,7 @@ public: void setShape(const entity::Shape& shape); void setShape(const QString& shape) { setShape(entity::shapeFromString(shape)); } - float getAlpha() const { return _alpha; }; + float getAlpha() const; void setAlpha(float alpha); glm::u8vec3 getColor() const; @@ -102,9 +102,8 @@ public: std::shared_ptr getMaterial() { return _material; } protected: - - float _alpha { 1.0f }; glm::u8vec3 _color; + float _alpha { 1.0f }; entity::Shape _shape { entity::Shape::Sphere }; //! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 38ecbb03df..c0cdb081a2 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -32,20 +32,25 @@ EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const Ent WebEntityItem::WebEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Web; - _dpi = ENTITY_ITEM_DEFAULT_DPI; } -const float WEB_ENTITY_ITEM_FIXED_DEPTH = 0.01f; - void WebEntityItem::setUnscaledDimensions(const glm::vec3& value) { // NOTE: Web Entities always have a "depth" of 1cm. + const float WEB_ENTITY_ITEM_FIXED_DEPTH = 0.01f; EntityItem::setUnscaledDimensions(glm::vec3(value.x, value.y, WEB_ENTITY_ITEM_FIXED_DEPTH)); } EntityItemProperties WebEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class + + COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(sourceUrl, getSourceUrl); COPY_ENTITY_PROPERTY_TO_PROPERTIES(dpi, getDPI); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(scriptURL, getScriptURL); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxFPS, getMaxFPS); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(inputMode, getInputMode); return properties; } @@ -53,8 +58,14 @@ 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(color, setColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(sourceUrl, setSourceUrl); SET_ENTITY_PROPERTY_FROM_PROPERTIES(dpi, setDPI); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(scriptURL, setScriptURL); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxFPS, setMaxFPS); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(inputMode, setInputMode); if (somethingChanged) { bool wantDebug = false; @@ -78,16 +89,28 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i int bytesRead = 0; const unsigned char* dataAt = data; + READ_ENTITY_PROPERTY(PROP_COLOR, glm::u8vec3, setColor); + READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + READ_ENTITY_PROPERTY(PROP_SOURCE_URL, QString, setSourceUrl); READ_ENTITY_PROPERTY(PROP_DPI, uint16_t, setDPI); + READ_ENTITY_PROPERTY(PROP_SCRIPT_URL, QString, setScriptURL); + READ_ENTITY_PROPERTY(PROP_MAX_FPS, uint8_t, setMaxFPS); + READ_ENTITY_PROPERTY(PROP_INPUT_MODE, WebInputMode, setInputMode); return bytesRead; } EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + requestedProperties += PROP_COLOR; + requestedProperties += PROP_ALPHA; + requestedProperties += PROP_SOURCE_URL; requestedProperties += PROP_DPI; + requestedProperties += PROP_SCRIPT_URL; + requestedProperties += PROP_MAX_FPS; + requestedProperties += PROP_INPUT_MODE; return requestedProperties; } @@ -100,8 +123,14 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst OctreeElement::AppendState& appendState) const { bool successPropertyFits = true; - APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, _sourceUrl); - APPEND_ENTITY_PROPERTY(PROP_DPI, _dpi); + APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); + + APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, getSourceUrl()); + APPEND_ENTITY_PROPERTY(PROP_DPI, getDPI()); + APPEND_ENTITY_PROPERTY(PROP_SCRIPT_URL, getScriptURL()); + APPEND_ENTITY_PROPERTY(PROP_MAX_FPS, getMaxFPS()); + APPEND_ENTITY_PROPERTY(PROP_INPUT_MODE, (uint32_t)getInputMode()); } bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, @@ -158,6 +187,30 @@ bool WebEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, co } } +void WebEntityItem::setColor(const glm::u8vec3& value) { + withWriteLock([&] { + _color = value; + }); +} + +glm::u8vec3 WebEntityItem::getColor() const { + return resultWithReadLock([&] { + return _color; + }); +} + +void WebEntityItem::setAlpha(float alpha) { + withWriteLock([&] { + _alpha = alpha; + }); +} + +float WebEntityItem::getAlpha() const { + return resultWithReadLock([&] { + return _alpha; + }); +} + void WebEntityItem::setSourceUrl(const QString& value) { withWriteLock([&] { if (_sourceUrl != value) { @@ -173,17 +226,63 @@ void WebEntityItem::setSourceUrl(const QString& value) { } QString WebEntityItem::getSourceUrl() const { - QString result; - withReadLock([&] { - result = _sourceUrl; + return resultWithReadLock([&] { + return _sourceUrl; }); - return result; } void WebEntityItem::setDPI(uint16_t value) { - _dpi = value; + withWriteLock([&] { + _dpi = value; + }); } uint16_t WebEntityItem::getDPI() const { - return _dpi; + return resultWithReadLock([&] { + return _dpi; + }); } + +void WebEntityItem::setScriptURL(const QString& value) { + withWriteLock([&] { + if (_scriptURL != value) { + auto newURL = QUrl::fromUserInput(value); + + if (newURL.isValid()) { + _scriptURL = newURL.toDisplayString(); + } else { + qCDebug(entities) << "Clearing web entity source URL since" << value << "cannot be parsed to a valid URL."; + } + } + }); +} + +QString WebEntityItem::getScriptURL() const { + return resultWithReadLock([&] { + return _scriptURL; + }); +} + +void WebEntityItem::setMaxFPS(uint8_t value) { + withWriteLock([&] { + _maxFPS = value; + }); +} + +uint8_t WebEntityItem::getMaxFPS() const { + return resultWithReadLock([&] { + return _maxFPS; + }); +} + +void WebEntityItem::setInputMode(const WebInputMode& value) { + withWriteLock([&] { + _inputMode = value; + }); +} + +WebInputMode WebEntityItem::getInputMode() const { + return resultWithReadLock([&] { + return _inputMode; + }); +} \ No newline at end of file diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index f1d1bbb313..c566bcb5f7 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -52,18 +52,38 @@ public: BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + glm::u8vec3 getColor() const; + void setColor(const glm::u8vec3& value); + + float getAlpha() const; + void setAlpha(float alpha); + static const QString DEFAULT_SOURCE_URL; - virtual void setSourceUrl(const QString& value); + void setSourceUrl(const QString& value); QString getSourceUrl() const; void setDPI(uint16_t value); uint16_t getDPI() const; + void setScriptURL(const QString& value); + QString getScriptURL() const; + static const uint8_t DEFAULT_MAX_FPS; + void setMaxFPS(uint8_t value); + uint8_t getMaxFPS() const; + + void setInputMode(const WebInputMode& value); + WebInputMode getInputMode() const; protected: + glm::u8vec3 _color; + float _alpha { 1.0f }; + QString _sourceUrl; uint16_t _dpi; + QString _scriptURL; + uint8_t _maxFPS; + WebInputMode _inputMode; }; #endif // hifi_WebEntityItem_h diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 9417809287..38e45f0206 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -257,6 +257,7 @@ enum class EntityVersion : PacketVersion { UpdatedPolyLines, FixProtocolVersionBumpMismatch, MigrateOverlayRenderProperties, + MissingWebEntityProperties, // Add new versions above here NUM_PACKET_TYPE, diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 5bcca0821f..a064be79bd 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -500,6 +500,9 @@ void SharedObject::onTimer() { } { + if (_maxFps == 0) { + return; + } auto minRenderInterval = USECS_PER_SECOND / _maxFps; auto lastInterval = usecTimestampNow() - _lastRenderTime; // Don't exceed the framerate limit diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index b95a8f117d..b8c6808afa 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -35,7 +35,7 @@ public: Q_INVOKABLE void synthesizeKeyPress(QString key, QObject* targetOverride = nullptr); Q_INVOKABLE void lowerKeyboard(); PointerEvent::EventType choosePointerEventType(QEvent::Type type); - unsigned int deviceIdByTouchPoint(qreal x, qreal y); + Q_INVOKABLE unsigned int deviceIdByTouchPoint(qreal x, qreal y); signals: void focusObjectChanged(QObject* newFocus);