more web entity wip

This commit is contained in:
SamGondelman 2019-01-10 09:25:21 -08:00
parent 45ec7fe3e1
commit 4b67a79561
18 changed files with 488 additions and 361 deletions

View file

@ -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
}
}

View file

@ -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";
}

View file

@ -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);

View file

@ -151,6 +151,7 @@
#include <trackers/EyeTracker.h>
#include <avatars-renderer/ScriptAvatar.h>
#include <RenderableEntityItem.h>
#include <RenderableWebEntityItem.h>
#include <model-networking/MaterialCache.h>
#include "recording/ClipCache.h"
@ -2316,6 +2317,92 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
return DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * Vectors::UP;
});
render::entities::WebEntityRenderer::setInitializeWebSurfaceOperator([](QSharedPointer<OffscreenQmlSurface> webSurface) {
webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(render::entities::WebEntityRenderer::QML);
webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance());
webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
});
render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([](const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
bool isTablet = url == TabletScriptingInterface::QML;
if (htmlContent) {
// FIXME use the surface cache instead of explicit creation
webSurface = QSharedPointer<OffscreenQmlSurface>(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<OffscreenQmlSurfaceCache>()->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<OffscreenQmlSurface>(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<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface, std::vector<QMetaObject::Connection>& connections) {
QQuickItem* rootItem = webSurface->getRootItem();
if (rootItem && rootItem->objectName() == "tabletRoot") {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
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<OffscreenQmlSurfaceCache>();
// 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<TabletScriptingInterface>()->preloadSounds();
DependencyManager::get<Keyboard>()->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();

View file

@ -64,20 +64,13 @@
#include "AboutUtil.h"
#include "ResourceRequestObserver.h"
#include <RenderableWebEntityItem.h>
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<GeometryCache>()->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<OffscreenQmlSurfaceCache>()->acquire(QML);
_cachedWebSurface = true;
_webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().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<AddressManager>().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<GeometryCache>()->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<GeometryCache>();
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<TabletScriptingInterface>();
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<OffscreenQmlSurfaceCache>();
// 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<OffscreenQmlSurfaceCache>()->acquire(QML);
_cachedWebSurface = true;
_webSurface->getRootItem()->setProperty("url", _url);
_webSurface->getRootItem()->setProperty("scriptURL", _scriptURL);
} else {
_webSurface = QSharedPointer<OffscreenQmlSurface>(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<controller::ScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Pointers", DependencyManager::get<PointerScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Web3DOverlay", this);
_webSurface->getSurfaceContext()->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface());
_webSurface->getSurfaceContext()->setContextProperty("HiFiAbout", AboutUtil::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("WalletScriptingInterface", DependencyManager::get<WalletScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().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:
* <code>scale</code>, <code>size</code>.
* @property {number} maxFPS=10 - The maximum update rate for the Web overlay content, in frames/second.
* @property {boolean} showKeyboardFocusHighlight=true - If <code>true</code>, the Web overlay is highlighted when it has
* keyboard focus.
* @property {string} inputMode=Touch - The user input mode to use - either <code>"Touch"</code> or <code>"Mouse"</code>.
*/
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) {

View file

@ -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<QMetaObject::Connection> _connections;
};
#endif // hifi_Web3DOverlay_h

View file

@ -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<void(QSharedPointer<OffscreenQmlSurface>)> WebEntityRenderer::_initializeWebSurfaceOperator = nullptr;
std::function<void(QString, bool, QSharedPointer<OffscreenQmlSurface>&, bool&)> WebEntityRenderer::_acquireWebSurfaceOperator = nullptr;
std::function<void(QSharedPointer<OffscreenQmlSurface>&, bool&, std::vector<QMetaObject::Connection>&)> 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<GeometryCache>()->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<GeometryCache>();
@ -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<OffscreenQmlSurface> 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<bool>([&] {
// 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<bool>([&] {
uint64_t lastRenderTime;
if (!resultWithReadLock<bool>([&] {
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<GeometryCache>()->renderWireCube(batch, 1.0f, cubeColor);
}
#endif
// Try to update the texture
{
QSharedPointer<OffscreenQmlSurface> 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<GeometryCache>()->bindWebBrowserProgram(batch, fadeRatio < OPAQUE_ALPHA_THRESHOLD);
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio), _geometryId);
DependencyManager::get<GeometryCache>()->bindWebBrowserProgram(batch, color.a < OPAQUE_ALPHA_THRESHOLD);
DependencyManager::get<GeometryCache>()->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<OffscreenQmlSurface>(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<TabletScriptingInterface>().data());
});
// forward web events to EntityScriptingInterface
auto entities = DependencyManager::get<EntityScriptingInterface>();
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<EntityScriptingInterface>()->webEventReceived(entityItemID, message);
}));
return _webSurface->getRootItem();
}
void WebEntityRenderer::destroyWebSurface() {
QSharedPointer<OffscreenQmlSurface> 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));
}

View file

@ -24,13 +24,35 @@ class WebEntityRenderer : public TypedEntityRenderer<WebEntityItem> {
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<void(QSharedPointer<OffscreenQmlSurface>)> initializeWebSurfaceOperator) { _initializeWebSurfaceOperator = initializeWebSurfaceOperator; }
static void initializeWebSurface(QSharedPointer<OffscreenQmlSurface> webSurface) {
if (_initializeWebSurfaceOperator) {
_initializeWebSurfaceOperator(webSurface);
}
}
static void setAcquireWebSurfaceOperator(std::function<void(const QString&, bool, QSharedPointer<OffscreenQmlSurface>&, bool&)> acquireWebSurfaceOperator) { _acquireWebSurfaceOperator = acquireWebSurfaceOperator; }
static void acquireWebSurface(const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
if (_acquireWebSurfaceOperator) {
_acquireWebSurfaceOperator(url, htmlContent, webSurface, cachedWebSurface);
}
}
static void setReleaseWebSurfaceOperator(std::function<void(QSharedPointer<OffscreenQmlSurface>&, bool&, std::vector<QMetaObject::Connection>&)> releaseWebSurfaceOperator) { _releaseWebSurfaceOperator = releaseWebSurfaceOperator; }
static void releaseWebSurface(QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface, std::vector<QMetaObject::Connection>& 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<OffscreenQmlSurface> _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<QMetaObject::Connection> _connections;
static std::function<void(QSharedPointer<OffscreenQmlSurface>)> _initializeWebSurfaceOperator;
static std::function<void(QString, bool, QSharedPointer<OffscreenQmlSurface>&, bool&)> _acquireWebSurfaceOperator;
static std::function<void(QSharedPointer<OffscreenQmlSurface>&, bool&, std::vector<QMetaObject::Connection>&)> _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

View file

@ -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());

View file

@ -232,6 +232,12 @@ void ShapeEntityItem::setAlpha(float alpha) {
});
}
float ShapeEntityItem::getAlpha() const {
return resultWithReadLock<float>([&] {
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) {

View file

@ -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<graphics::Material> 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

View file

@ -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<glm::u8vec3>([&] {
return _color;
});
}
void WebEntityItem::setAlpha(float alpha) {
withWriteLock([&] {
_alpha = alpha;
});
}
float WebEntityItem::getAlpha() const {
return resultWithReadLock<float>([&] {
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<QString>([&] {
return _sourceUrl;
});
return result;
}
void WebEntityItem::setDPI(uint16_t value) {
_dpi = value;
withWriteLock([&] {
_dpi = value;
});
}
uint16_t WebEntityItem::getDPI() const {
return _dpi;
return resultWithReadLock<uint16_t>([&] {
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<QString>([&] {
return _scriptURL;
});
}
void WebEntityItem::setMaxFPS(uint8_t value) {
withWriteLock([&] {
_maxFPS = value;
});
}
uint8_t WebEntityItem::getMaxFPS() const {
return resultWithReadLock<uint8_t>([&] {
return _maxFPS;
});
}
void WebEntityItem::setInputMode(const WebInputMode& value) {
withWriteLock([&] {
_inputMode = value;
});
}
WebInputMode WebEntityItem::getInputMode() const {
return resultWithReadLock<WebInputMode>([&] {
return _inputMode;
});
}

View file

@ -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

View file

@ -257,6 +257,7 @@ enum class EntityVersion : PacketVersion {
UpdatedPolyLines,
FixProtocolVersionBumpMismatch,
MigrateOverlayRenderProperties,
MissingWebEntityProperties,
// Add new versions above here
NUM_PACKET_TYPE,

View file

@ -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

View file

@ -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);