Merge pull request #14711 from SamGondelman/NOverlays10

Case 20585: Add missing Web Overlay functionality to Web Entities
This commit is contained in:
Adam Smith 2019-01-25 14:00:52 -08:00 committed by GitHub
commit f651ad2f67
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 768 additions and 482 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"
@ -2320,6 +2321,76 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
return DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * Vectors::UP;
});
render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([this](const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
bool isTablet = url == TabletScriptingInterface::QML;
if (htmlContent) {
webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(render::entities::WebEntityRenderer::QML);
cachedWebSurface = true;
auto rootItemLoadedFunctor = [url, webSurface] {
webSurface->getRootItem()->setProperty(render::entities::WebEntityRenderer::URL_PROPERTY, url);
};
if (webSurface->getRootItem()) {
rootItemLoadedFunctor();
} else {
QObject::connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor);
}
} else {
// FIXME: the tablet should use the OffscreenQmlSurfaceCache
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;
});
});
auto rootItemLoadedFunctor = [webSurface, url, isTablet] {
Application::setupQmlSurface(webSurface->getSurfaceContext(), isTablet || url == OVERLAY_LOGIN_DIALOG.toString());
};
if (webSurface->getRootItem()) {
rootItemLoadedFunctor();
} else {
QObject::connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor);
}
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([this](QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface, std::vector<QMetaObject::Connection>& connections) {
QQuickItem* rootItem = webSurface->getRootItem();
// 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>();
if (offscreenCache) {
offscreenCache->release(render::entities::WebEntityRenderer::QML, webSurface);
}
cachedWebSurface = false;
}
webSurface.reset();
});
// Preload Tablet sounds
DependencyManager::get<TabletScriptingInterface>()->preloadSounds();
DependencyManager::get<Keyboard>()->createKeyboard();
@ -3021,7 +3092,7 @@ void Application::initializeUi() {
});
offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1);
offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2);
offscreenSurfaceCache->reserve(render::entities::WebEntityRenderer::QML, 2);
#endif
flushMenuUpdates();
@ -3161,6 +3232,61 @@ void Application::onDesktopRootItemCreated(QQuickItem* rootItem) {
#endif
}
void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties) {
surfaceContext->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
surfaceContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
surfaceContext->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
surfaceContext->setContextProperty("Preferences", DependencyManager::get<Preferences>().data());
surfaceContext->setContextProperty("Vec3", new Vec3());
surfaceContext->setContextProperty("Quat", new Quat());
surfaceContext->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
surfaceContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
surfaceContext->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data());
if (setAdditionalContextProperties) {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto flags = tabletScriptingInterface->getFlags();
surfaceContext->setContextProperty("offscreenFlags", flags);
surfaceContext->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
surfaceContext->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
surfaceContext->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance());
// in Qt 5.10.0 there is already an "Audio" object in the QML context
// though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface"
surfaceContext->setContextProperty("AudioScriptingInterface", DependencyManager::get<AudioScriptingInterface>().data());
surfaceContext->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
surfaceContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
surfaceContext->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data());
surfaceContext->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
surfaceContext->setContextProperty("OctreeStats", DependencyManager::get<OctreeStatsProvider>().data());
surfaceContext->setContextProperty("DCModel", DependencyManager::get<DomainConnectionModel>().data());
surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
surfaceContext->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
surfaceContext->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance());
surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get<InputConfiguration>().data());
surfaceContext->setContextProperty("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
surfaceContext->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get());
surfaceContext->setContextProperty("Workload", qApp->getGameWorkload()._engine->getConfiguration().get());
surfaceContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
surfaceContext->setContextProperty("Pointers", DependencyManager::get<PointerScriptingInterface>().data());
surfaceContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
surfaceContext->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface());
surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance());
surfaceContext->setContextProperty("WalletScriptingInterface", DependencyManager::get<WalletScriptingInterface>().data());
surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().data());
}
}
void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
PROFILE_RANGE(render, __FUNCTION__);
PerformanceTimer perfTimer("updateCamera");

View file

@ -589,6 +589,8 @@ private:
void maybeToggleMenuVisible(QMouseEvent* event) const;
void toggleTabletUI(bool shouldOpen = false) const;
static void setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties);
MainWindow* _window;
QElapsedTimer& _sessionRunTimer;

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,21 @@ 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());
buildWebSurface(true);
}
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,74 +108,22 @@ void Web3DOverlay::rebuildWebSurface() {
}
void Web3DOverlay::destroyWebSurface() {
if (!_webSurface) {
return;
if (_webSurface) {
render::entities::WebEntityRenderer::releaseWebSurface(_webSurface, _cachedWebSurface, _connections);
}
QQuickItem* rootItem = _webSurface->getRootItem();
// 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() {
void Web3DOverlay::buildWebSurface(bool overrideWeb) {
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, overrideWeb || 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) {
@ -215,69 +143,8 @@ bool Web3DOverlay::isWebContent() const {
return false;
}
void Web3DOverlay::setupQmlSurface(bool isTablet, bool isLoginDialog) {
_webSurface->getSurfaceContext()->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Preferences", DependencyManager::get<Preferences>().data());
_webSurface->getSurfaceContext()->setContextProperty("Vec3", new Vec3());
_webSurface->getSurfaceContext()->setContextProperty("Quat", new Quat());
_webSurface->getSurfaceContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
_webSurface->getSurfaceContext()->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data());
if (isTablet || isLoginDialog) {
_webSurface->getSurfaceContext()->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
_webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
}
if (isTablet) {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto flags = tabletScriptingInterface->getFlags();
_webSurface->getSurfaceContext()->setContextProperty("offscreenFlags", flags);
_webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
_webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
_webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance());
// in Qt 5.10.0 there is already an "Audio" object in the QML context
// though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface"
_webSurface->getSurfaceContext()->setContextProperty("AudioScriptingInterface", DependencyManager::get<AudioScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
_webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("fileDialogHelper", new FileDialogHelper());
_webSurface->getSurfaceContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
_webSurface->getSurfaceContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
_webSurface->getSurfaceContext()->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
_webSurface->getSurfaceContext()->setContextProperty("OctreeStats", DependencyManager::get<OctreeStatsProvider>().data());
_webSurface->getSurfaceContext()->setContextProperty("DCModel", DependencyManager::get<DomainConnectionModel>().data());
_webSurface->getSurfaceContext()->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
_webSurface->getSurfaceContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("InputConfiguration", DependencyManager::get<InputConfiguration>().data());
_webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
_webSurface->getSurfaceContext()->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get());
_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);
@ -298,21 +165,13 @@ 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;
}
if (!_webSurface) {
emit requestWebSurface();
emit requestWebSurface(false);
return;
}
@ -499,11 +358,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();
@ -566,8 +420,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) {
@ -583,9 +435,6 @@ QVariant Web3DOverlay::getProperty(const QString& property) {
if (property == "maxFPS") {
return _desiredMaxFPS;
}
if (property == "showKeyboardFocusHighlight") {
return _showKeyboardFocusHighlight;
}
if (property == "inputMode") {
if (_inputMode == Mouse) {
@ -605,14 +454,14 @@ void Web3DOverlay::setURL(const QString& url) {
if (wasWebContent && isWebContent()) {
// If we're just targeting a new web URL, then switch to that without messing around
// with the underlying QML
AbstractViewStateInterface::instance()->postLambdaEvent([this, url] {
_webSurface->getRootItem()->setProperty("url", _url);
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
_webSurface->getRootItem()->setProperty(render::entities::WebEntityRenderer::URL_PROPERTY, _url);
_webSurface->getRootItem()->setProperty("scriptURL", _scriptURL);
});
} else {
// If we're switching to or from web content, or between different QML content
// we need to destroy and rebuild the entire QML surface
AbstractViewStateInterface::instance()->postLambdaEvent([this, url] {
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
rebuildWebSurface();
});
}

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; }
@ -59,12 +57,10 @@ public:
Mouse
};
void buildWebSurface();
void buildWebSurface(bool overrideWeb = false);
void destroyWebSurface();
void onResizeWebSurface();
Q_INVOKABLE unsigned int deviceIdByTouchPoint(qreal x, qreal y);
public slots:
void emitScriptEvent(const QVariant& scriptMessage);
@ -72,26 +68,23 @@ signals:
void scriptEventReceived(const QVariant& message);
void webEventReceived(const QVariant& message);
void resizeWebSurface();
void requestWebSurface();
void releaseWebSurface();
void requestWebSurface(bool overrideWeb);
protected:
Transform evalRenderTransform() override;
private:
void setupQmlSurface(bool isTablet, bool isLoginDialog);
void rebuildWebSurface();
bool isWebContent() const;
InputMode _inputMode { Touch };
QSharedPointer<OffscreenQmlSurface> _webSurface;
bool _cachedWebSurface{ false };
bool _cachedWebSurface { false };
gpu::TexturePointer _texture;
QString _url;
QString _scriptURL;
float _dpi { 30.0f };
int _geometryId { 0 };
bool _showKeyboardFocusHighlight { true };
QTouchDevice _touchDevice;
@ -99,6 +92,8 @@ private:
uint8_t _currentMaxFPS { 0 };
bool _mayNeedResize { false };
std::vector<QMetaObject::Connection> _connections;
};
#endif // hifi_Web3DOverlay_h

View file

@ -30,22 +30,26 @@
using namespace render;
using namespace render::entities;
static const QString WEB_ENTITY_QML = "controls/WebEntityView.qml";
const QString WebEntityRenderer::QML = "Web3DSurface.qml";
const char* WebEntityRenderer::URL_PROPERTY = "url";
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";
WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& urlString) {
if (urlString.isEmpty()) {
@ -71,13 +75,18 @@ WebEntityRenderer::WebEntityRenderer(const EntityItemPointer& entity) : Parent(e
_touchDevice.setMaximumTouchPoints(4);
});
_geometryId = DependencyManager::get<GeometryCache>()->allocateID();
_texture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda());
_texture->setSource(__FUNCTION__);
_contentType = ContentType::HtmlContent;
buildWebSurface(entity, "");
_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 +95,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 +115,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 +147,25 @@ 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>([this] {
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();
}
}
@ -151,80 +175,100 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene
// destroy the existing surface (because surfaces don't support changing the root
// object, so subsequent loads of content just overlap the existing content
bool urlChanged = false;
auto newSourceURL = entity->getSourceUrl();
{
auto newSourceUrl = entity->getSourceUrl();
auto newContentType = getContentType(newSourceUrl);
auto currentContentType = ContentType::NoContent;
auto newContentType = getContentType(newSourceURL);
ContentType currentContentType;
withReadLock([&] {
urlChanged = _lastSourceUrl != newSourceUrl;
urlChanged = _sourceURL != newSourceURL;
currentContentType = _contentType;
});
if (urlChanged) {
if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) {
destroyWebSurface();
// If we destroyed the surface, the URL change will be implicitly handled by the re-creation
urlChanged = false;
}
withWriteLock([&] {
_lastSourceUrl = newSourceUrl;
_contentType = newContentType;
});
if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) {
destroyWebSurface();
}
}
}
withWriteLock([&] {
_inputMode = entity->getInputMode();
_dpi = entity->getDPI();
_color = entity->getColor();
_alpha = entity->getAlpha();
if (_contentType == ContentType::NoContent) {
return;
}
// This work must be done on the main thread
// If we couldn't create a new web surface, exit
if (!hasWebSurface() && !buildWebSurface(entity)) {
return;
if (!_webSurface) {
buildWebSurface(entity, newSourceURL);
}
if (urlChanged && _contentType == ContentType::HtmlContent) {
_webSurface->getRootItem()->setProperty(URL_PROPERTY, _lastSourceUrl);
}
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));
if (_webSurface && _webSurface->getRootItem()) {
if (_webSurface->getRootItem()) {
if (_contentType == ContentType::HtmlContent && urlChanged) {
_webSurface->getRootItem()->setProperty(URL_PROPERTY, newSourceURL);
_sourceURL = newSourceURL;
}
_lastDPI = entity->getDPI();
{
auto scriptURL = entity->getScriptURL();
if (_scriptURL != scriptURL) {
_webSurface->getRootItem()->setProperty("scriptURL", _scriptURL);
_scriptURL = scriptURL;
}
}
glm::vec2 windowSize = getWindowSize(entity);
_webSurface->resize(QSize(windowSize.x, windowSize.y));
updateModelTransformAndBound();
_renderTransform = getModelTransform();
_renderTransform.postScale(entity->getScaledDimensions());
{
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;
}
}
{
auto contextPosition = entity->getWorldPosition();
if (_contextPosition != contextPosition) {
_webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(contextPosition));
_contextPosition = contextPosition;
}
}
}
void* key = (void*)this;
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity]() {
withWriteLock([&] {
glm::vec2 windowSize = getWindowSize(entity);
_webSurface->resize(QSize(windowSize.x, windowSize.y));
updateModelTransformAndBound();
_renderTransform = getModelTransform();
_renderTransform.postScale(entity->getScaledDimensions());
});
});
});
}
});
}
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,111 +286,61 @@ 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);
}
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) {
void WebEntityRenderer::buildWebSurface(const EntityItemPointer& entity, const QString& newSourceURL) {
if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) {
qWarning() << "Too many concurrent web views to create new view";
return false;
return;
}
++_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(newSourceURL, _contentType == ContentType::HtmlContent, _webSurface, _cachedWebSurface);
_fadeStartTime = usecTimestampNow();
_webSurface->resume();
return _webSurface->getRootItem();
_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);
}));
}
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 +352,84 @@ 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) {
qDebug() << "boop5" << this << _webSurface << _webSurface->getRootItem();
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 +443,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,30 @@ 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 const char* URL_PROPERTY;
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,40 +56,59 @@ 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);
void buildWebSurface(const EntityItemPointer& entity, const QString& newSourceURL);
void destroyWebSurface();
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;
QSharedPointer<OffscreenQmlSurface> _webSurface { nullptr };
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(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

@ -97,6 +97,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
// requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA; // not sent over the wire
requestedProperties += PROP_RENDER_LAYER;
requestedProperties += PROP_PRIMITIVE_MODE;
requestedProperties += PROP_IGNORE_PICK_INTERSECTION;
withReadLock([&] {
requestedProperties += _grabProperties.getEntityProperties(params);
});
@ -280,6 +281,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
// APPEND_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, getIsVisibleInSecondaryCamera()); // not sent over the wire
APPEND_ENTITY_PROPERTY(PROP_RENDER_LAYER, (uint32_t)getRenderLayer());
APPEND_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, (uint32_t)getPrimitiveMode());
APPEND_ENTITY_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, getIgnorePickIntersection());
withReadLock([&] {
_grabProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties,
propertyFlags, propertiesDidntFit, propertyCount, appendState);
@ -848,6 +850,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
// READ_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, bool, setIsVisibleInSecondaryCamera); // not sent over the wire
READ_ENTITY_PROPERTY(PROP_RENDER_LAYER, RenderLayer, setRenderLayer);
READ_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, PrimitiveMode, setPrimitiveMode);
READ_ENTITY_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, bool, setIgnorePickIntersection);
withWriteLock([&] {
int bytesFromGrab = _grabProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData,
@ -1321,6 +1324,7 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire
COPY_ENTITY_PROPERTY_TO_PROPERTIES(isVisibleInSecondaryCamera, isVisibleInSecondaryCamera);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(renderLayer, getRenderLayer);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(primitiveMode, getPrimitiveMode);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(ignorePickIntersection, getIgnorePickIntersection);
withReadLock([&] {
_grabProperties.getProperties(properties);
});
@ -1467,6 +1471,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(isVisibleInSecondaryCamera, setIsVisibleInSecondaryCamera);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(renderLayer, setRenderLayer);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(primitiveMode, setPrimitiveMode);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(ignorePickIntersection, setIgnorePickIntersection);
withWriteLock([&] {
bool grabPropertiesChanged = _grabProperties.setProperties(properties);
somethingChanged |= grabPropertiesChanged;
@ -2984,6 +2989,18 @@ void EntityItem::setPrimitiveMode(PrimitiveMode value) {
}
}
bool EntityItem::getIgnorePickIntersection() const {
return resultWithReadLock<bool>([&] {
return _ignorePickIntersection;
});
}
void EntityItem::setIgnorePickIntersection(bool value) {
withWriteLock([&] {
_ignorePickIntersection = value;
});
}
bool EntityItem::getCanCastShadow() const {
bool result;
withReadLock([&] {

View file

@ -299,6 +299,9 @@ public:
PrimitiveMode getPrimitiveMode() const;
void setPrimitiveMode(PrimitiveMode value);
bool getIgnorePickIntersection() const;
void setIgnorePickIntersection(bool value);
bool getCanCastShadow() const;
void setCanCastShadow(bool value);
@ -630,6 +633,7 @@ protected:
RenderLayer _renderLayer { RenderLayer::WORLD };
PrimitiveMode _primitiveMode { PrimitiveMode::SOLID };
bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW };
bool _ignorePickIntersection { false };
bool _collisionless { ENTITY_ITEM_DEFAULT_COLLISIONLESS };
uint16_t _collisionMask { ENTITY_COLLISION_MASK_DEFAULT };
bool _dynamic { ENTITY_ITEM_DEFAULT_DYNAMIC };

View file

@ -404,6 +404,32 @@ void EntityItemProperties::setPrimitiveModeFromString(const QString& primitiveMo
}
}
QHash<QString, WebInputMode> stringToWebInputModeLookup;
void addWebInputMode(WebInputMode mode) {
stringToWebInputModeLookup[WebInputModeHelpers::getNameForWebInputMode(mode)] = mode;
}
void buildStringToWebInputModeLookup() {
addWebInputMode(WebInputMode::TOUCH);
addWebInputMode(WebInputMode::MOUSE);
}
QString EntityItemProperties::getInputModeAsString() const {
return WebInputModeHelpers::getNameForWebInputMode(_inputMode);
}
void EntityItemProperties::setInputModeFromString(const QString& webInputMode) {
if (stringToWebInputModeLookup.empty()) {
buildStringToWebInputModeLookup();
}
auto webInputModeItr = stringToWebInputModeLookup.find(webInputMode.toLower());
if (webInputModeItr != stringToWebInputModeLookup.end()) {
_inputMode = webInputModeItr.value();
_inputModeChanged = true;
}
}
EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
EntityPropertyFlags changedProperties;
@ -430,6 +456,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera);
CHECK_PROPERTY_CHANGE(PROP_RENDER_LAYER, renderLayer);
CHECK_PROPERTY_CHANGE(PROP_PRIMITIVE_MODE, primitiveMode);
CHECK_PROPERTY_CHANGE(PROP_IGNORE_PICK_INTERSECTION, ignorePickIntersection);
changedProperties += _grab.getChangedProperties();
// Physics
@ -584,6 +611,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
// Web
CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl);
CHECK_PROPERTY_CHANGE(PROP_DPI, dpi);
CHECK_PROPERTY_CHANGE(PROP_SCRIPT_URL, scriptURL);
CHECK_PROPERTY_CHANGE(PROP_MAX_FPS, maxFPS);
CHECK_PROPERTY_CHANGE(PROP_INPUT_MODE, inputMode);
// Polyline
CHECK_PROPERTY_CHANGE(PROP_LINE_POINTS, linePoints);
@ -669,6 +699,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @property {boolean} isVisibleInSecondaryCamera=true - Whether or not the entity is rendered in the secondary camera. If <code>true</code> then the entity is rendered.
* @property {RenderLayer} renderLayer="world" - In which layer this entity renders.
* @property {PrimitiveMode} primitiveMode="solid" - How this entity's geometry is rendered.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the entity.
*
* @property {Vec3} position=0,0,0 - The position of the entity.
* @property {Quat} rotation=0,0,0,1 - The orientation of the entity with respect to world coordinates.
@ -1269,11 +1300,16 @@ 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
* (multiply by 1 / 0.0254 = 39.3701) then multiply <code>dimensions.x</code> and <code>dimensions.y</code> by that value
* you get the resolution in pixels.
* @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page.
* @property {number} maxFPS=10 - The maximum update rate for the Web content, in frames/second.
* @property {WebInputMode} inputMode="touch" - The user input mode to use.
* @example <caption>Create a Web entity displaying at 1920 x 1080 resolution.</caption>
* var METERS_TO_INCHES = 39.3701;
* var entity = Entities.addEntity({
@ -1482,6 +1518,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_RENDER_LAYER, renderLayer, getRenderLayerAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_PRIMITIVE_MODE, primitiveMode, getPrimitiveModeAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IGNORE_PICK_INTERSECTION, ignorePickIntersection);
_grab.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
// Physics
@ -1669,8 +1706,14 @@ 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);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MAX_FPS, maxFPS);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_INPUT_MODE, inputMode, getInputModeAsString());
}
// PolyVoxel only
@ -1866,6 +1909,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(isVisibleInSecondaryCamera, bool, setIsVisibleInSecondaryCamera);
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(renderLayer, RenderLayer);
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(primitiveMode, PrimitiveMode);
COPY_PROPERTY_FROM_QSCRIPTVALUE(ignorePickIntersection, bool, setIgnorePickIntersection);
_grab.copyFromScriptValue(object, _defaultSettings);
// Physics
@ -2025,6 +2069,9 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
// Web
COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl);
COPY_PROPERTY_FROM_QSCRIPTVALUE(dpi, uint16_t, setDPI);
COPY_PROPERTY_FROM_QSCRIPTVALUE(scriptURL, QString, setScriptURL);
COPY_PROPERTY_FROM_QSCRIPTVALUE(maxFPS, uint8_t, setMaxFPS);
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(inputMode, InputMode);
// Polyline
COPY_PROPERTY_FROM_QSCRIPTVALUE(linePoints, qVectorVec3, setLinePoints);
@ -2140,6 +2187,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
COPY_PROPERTY_IF_CHANGED(isVisibleInSecondaryCamera);
COPY_PROPERTY_IF_CHANGED(renderLayer);
COPY_PROPERTY_IF_CHANGED(primitiveMode);
COPY_PROPERTY_IF_CHANGED(ignorePickIntersection);
_grab.merge(other._grab);
// Physics
@ -2294,6 +2342,9 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
// Web
COPY_PROPERTY_IF_CHANGED(sourceUrl);
COPY_PROPERTY_IF_CHANGED(dpi);
COPY_PROPERTY_IF_CHANGED(scriptURL);
COPY_PROPERTY_IF_CHANGED(maxFPS);
COPY_PROPERTY_IF_CHANGED(inputMode);
// Polyline
COPY_PROPERTY_IF_CHANGED(linePoints);
@ -2413,6 +2464,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
ADD_PROPERTY_TO_MAP(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool);
ADD_PROPERTY_TO_MAP(PROP_RENDER_LAYER, RenderLayer, renderLayer, RenderLayer);
ADD_PROPERTY_TO_MAP(PROP_PRIMITIVE_MODE, PrimitiveMode, primitiveMode, PrimitiveMode);
ADD_PROPERTY_TO_MAP(PROP_IGNORE_PICK_INTERSECTION, IgnorePickIntersection, ignorePickIntersection, bool);
{ // Grab
ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_GRABBABLE, Grab, grab, Grabbable, grabbable);
ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_KINEMATIC, Grab, grab, GrabKinematic, grabKinematic);
@ -2663,6 +2715,9 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
// Web
ADD_PROPERTY_TO_MAP(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString);
ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t);
ADD_PROPERTY_TO_MAP(PROP_SCRIPT_URL, ScriptURL, scriptURL, QString);
ADD_PROPERTY_TO_MAP(PROP_MAX_FPS, MaxFPS, maxFPS, uint8_t);
ADD_PROPERTY_TO_MAP(PROP_INPUT_MODE, InputMode, inputMode, WebInputMode);
// Polyline
ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector<vec3>);
@ -2845,6 +2900,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
// APPEND_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, properties.getIsVisibleInSecondaryCamera()); // not sent over the wire
APPEND_ENTITY_PROPERTY(PROP_RENDER_LAYER, (uint32_t)properties.getRenderLayer());
APPEND_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, (uint32_t)properties.getPrimitiveMode());
APPEND_ENTITY_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, properties.getIgnorePickIntersection());
_staticGrab.setProperties(properties);
_staticGrab.appendToEditPacket(packetData, requestedProperties, propertyFlags,
propertiesDidntFit, propertyCount, appendState);
@ -3026,8 +3082,14 @@ 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());
APPEND_ENTITY_PROPERTY(PROP_MAX_FPS, properties.getMaxFPS());
APPEND_ENTITY_PROPERTY(PROP_INPUT_MODE, (uint32_t)properties.getInputMode());
}
if (properties.getType() == EntityTypes::Line) {
@ -3289,6 +3351,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
// READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE_IN_SECONDARY_CAMERA, bool, setIsVisibleInSecondaryCamera); // not sent over the wire
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RENDER_LAYER, RenderLayer, setRenderLayer);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PRIMITIVE_MODE, PrimitiveMode, setPrimitiveMode);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IGNORE_PICK_INTERSECTION, bool, setIgnorePickIntersection);
properties.getGrab().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
// Physics
@ -3459,8 +3522,14 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
}
if (properties.getType() == EntityTypes::Web) {
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT_URL, QString, setScriptURL);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAX_FPS, uint8_t, setMaxFPS);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_INPUT_MODE, WebInputMode, setInputMode);
}
if (properties.getType() == EntityTypes::Line) {
@ -3674,6 +3743,7 @@ void EntityItemProperties::markAllChanged() {
_isVisibleInSecondaryCameraChanged = true;
_renderLayerChanged = true;
_primitiveModeChanged = true;
_ignorePickIntersectionChanged = true;
_grab.markAllChanged();
// Physics
@ -3821,6 +3891,9 @@ void EntityItemProperties::markAllChanged() {
// Web
_sourceUrlChanged = true;
_dpiChanged = true;
_scriptURLChanged = true;
_maxFPSChanged = true;
_inputModeChanged = true;
// Polyline
_linePointsChanged = true;
@ -4053,6 +4126,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
if (primitiveModeChanged()) {
out += "primitiveMode";
}
if (ignorePickIntersectionChanged()) {
out += "ignorePickIntersection";
}
getGrab().listChangedProperties(out);
// Physics
@ -4432,6 +4508,15 @@ QList<QString> EntityItemProperties::listChangedProperties() {
if (dpiChanged()) {
out += "dpi";
}
if (scriptURLChanged()) {
out += "scriptURL";
}
if (maxFPSChanged()) {
out += "maxFPS";
}
if (inputModeChanged()) {
out += "inputMode";
}
// Polyline
if (linePointsChanged()) {

View file

@ -30,29 +30,33 @@
#include <ShapeInfo.h>
#include <ColorUtils.h>
#include "AnimationPropertyGroup.h"
#include "EntityItemID.h"
#include "EntityItemPropertiesDefaults.h"
#include "EntityItemPropertiesMacros.h"
#include "EntityTypes.h"
#include "EntityPropertyFlags.h"
#include "EntityPsuedoPropertyFlags.h"
#include "LightEntityItem.h"
#include "LineEntityItem.h"
#include "ParticleEffectEntityItem.h"
#include "PolyVoxEntityItem.h"
#include "SimulationOwner.h"
#include "TextEntityItem.h"
#include "WebEntityItem.h"
#include "ParticleEffectEntityItem.h"
#include "LineEntityItem.h"
#include "PolyVoxEntityItem.h"
#include "GridEntityItem.h"
#include "LightEntityItem.h"
#include "ZoneEntityItem.h"
#include "AnimationPropertyGroup.h"
#include "SkyboxPropertyGroup.h"
#include "HazePropertyGroup.h"
#include "BloomPropertyGroup.h"
#include "TextEntityItem.h"
#include "ZoneEntityItem.h"
#include "GridEntityItem.h"
#include "MaterialMappingMode.h"
#include "BillboardMode.h"
#include "RenderLayer.h"
#include "PrimitiveMode.h"
#include "WebInputMode.h"
const quint64 UNKNOWN_CREATED_TIME = 0;
@ -173,6 +177,7 @@ public:
DEFINE_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool, ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA);
DEFINE_PROPERTY_REF_ENUM(PROP_RENDER_LAYER, RenderLayer, renderLayer, RenderLayer, RenderLayer::WORLD);
DEFINE_PROPERTY_REF_ENUM(PROP_PRIMITIVE_MODE, PrimitiveMode, primitiveMode, PrimitiveMode, PrimitiveMode::SOLID);
DEFINE_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, IgnorePickIntersection, ignorePickIntersection, bool, false);
DEFINE_PROPERTY_GROUP(Grab, grab, GrabPropertyGroup);
// Physics
@ -325,8 +330,11 @@ public:
DEFINE_PROPERTY_REF(PROP_Z_P_NEIGHBOR_ID, ZPNeighborID, zPNeighborID, EntityItemID, UNKNOWN_ENTITY_ID);
// Web
DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, "");
DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, WebEntityItem::DEFAULT_SOURCE_URL);
DEFINE_PROPERTY_REF(PROP_DPI, DPI, dpi, uint16_t, ENTITY_ITEM_DEFAULT_DPI);
DEFINE_PROPERTY_REF(PROP_SCRIPT_URL, ScriptURL, scriptURL, QString, "");
DEFINE_PROPERTY_REF(PROP_MAX_FPS, MaxFPS, maxFPS, uint8_t, WebEntityItem::DEFAULT_MAX_FPS);
DEFINE_PROPERTY_REF_ENUM(PROP_INPUT_MODE, InputMode, inputMode, WebInputMode, WebInputMode::TOUCH);
// Polyline
DEFINE_PROPERTY_REF(PROP_LINE_POINTS, LinePoints, linePoints, QVector<glm::vec3>, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC);

View file

@ -41,6 +41,7 @@ enum EntityPropertyList {
PROP_VISIBLE_IN_SECONDARY_CAMERA, // not sent over the wire
PROP_RENDER_LAYER,
PROP_PRIMITIVE_MODE,
PROP_IGNORE_PICK_INTERSECTION,
// Grab
PROP_GRAB_GRABBABLE,
PROP_GRAB_KINEMATIC,
@ -287,6 +288,9 @@ enum EntityPropertyList {
// Web
PROP_SOURCE_URL = PROP_DERIVED_0,
PROP_DPI = PROP_DERIVED_1,
PROP_SCRIPT_URL = PROP_DERIVED_2,
PROP_MAX_FPS = PROP_DERIVED_3,
PROP_INPUT_MODE = PROP_DERIVED_4,
// Polyline
PROP_LINE_POINTS = PROP_DERIVED_0,

View file

@ -2707,7 +2707,6 @@ void convertGrabUserDataToProperties(EntityItemProperties& properties) {
bool EntityTree::readFromMap(QVariantMap& map) {
// These are needed to deal with older content (before adding inheritance modes)
int contentVersion = map["Version"].toInt();
bool needsConversion = (contentVersion < (int)EntityVersion::ZoneLightInheritModes);
if (map.contains("Id")) {
_persistID = map["Id"].toUuid();
@ -2782,7 +2781,7 @@ bool EntityTree::readFromMap(QVariantMap& map) {
}
// Fix for older content not containing mode fields in the zones
if (needsConversion && (properties.getType() == EntityTypes::EntityType::Zone)) {
if (contentVersion < (int)EntityVersion::ZoneLightInheritModes && (properties.getType() == EntityTypes::EntityType::Zone)) {
// The legacy version had no keylight mode - this is set to on
properties.setKeyLightMode(COMPONENT_MODE_ENABLED);

View file

@ -187,6 +187,10 @@ EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& ori
// only called if we do intersect our bounding cube, but find if we actually intersect with entities...
EntityItemID entityID;
forEachEntity([&](EntityItemPointer entity) {
if (entity->getIgnorePickIntersection()) {
return;
}
// use simple line-sphere for broadphase check
// (this is faster and more likely to cull results than the filter check below so we do it first)
bool success;
@ -327,6 +331,10 @@ EntityItemID EntityTreeElement::evalDetailedParabolaIntersection(const glm::vec3
// only called if we do intersect our bounding cube, but find if we actually intersect with entities...
EntityItemID entityID;
forEachEntity([&](EntityItemPointer entity) {
if (entity->getIgnorePickIntersection()) {
return;
}
// use simple line-sphere for broadphase check
// (this is faster and more likely to cull results than the filter check below so we do it first)
bool success;

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

@ -21,7 +21,8 @@
#include "EntityTree.h"
#include "EntityTreeElement.h"
const QString WebEntityItem::DEFAULT_SOURCE_URL("http://www.google.com");
const QString WebEntityItem::DEFAULT_SOURCE_URL = "http://www.google.com";
const uint8_t WebEntityItem::DEFAULT_MAX_FPS = 10;
EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItemPointer entity(new WebEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); });
@ -31,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;
}
@ -52,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;
@ -77,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;
}
@ -99,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,
@ -157,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) {
@ -172,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

@ -13,8 +13,6 @@
class WebEntityItem : public EntityItem {
public:
static const QString DEFAULT_SOURCE_URL;
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
WebEntityItem(const EntityItemID& entityItemID);
@ -54,15 +52,38 @@ public:
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
virtual void setSourceUrl(const QString& value);
glm::u8vec3 getColor() const;
void setColor(const glm::u8vec3& value);
float getAlpha() const;
void setAlpha(float alpha);
static const QString DEFAULT_SOURCE_URL;
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

@ -37,6 +37,7 @@
#include "BillboardMode.h"
#include "RenderLayer.h"
#include "PrimitiveMode.h"
#include "WebInputMode.h"
#include "OctreeConstants.h"
#include "OctreeElement.h"
@ -267,6 +268,7 @@ public:
static int unpackDataFromBytes(const unsigned char* dataBytes, BillboardMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, RenderLayer& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, PrimitiveMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, WebInputMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec2& result);
static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec3& result);
static int unpackDataFromBytes(const unsigned char* dataBytes, glm::u8vec3& result);

View file

@ -66,6 +66,7 @@ void RenderEventHandler::onInitalize() {
qFatal("Unable to make QML rendering context current on render thread");
}
_shared->initializeRenderControl(_canvas.getContext());
_initialized = true;
}
void RenderEventHandler::resize() {
@ -150,22 +151,24 @@ void RenderEventHandler::onRender() {
}
void RenderEventHandler::onQuit() {
if (_canvas.getContext() != QOpenGLContextWrapper::currentContext()) {
qFatal("QML rendering context not current on render thread");
}
if (_initialized) {
if (_canvas.getContext() != QOpenGLContextWrapper::currentContext()) {
qFatal("QML rendering context not current on render thread");
}
if (_depthStencil) {
glDeleteRenderbuffers(1, &_depthStencil);
_depthStencil = 0;
}
if (_depthStencil) {
glDeleteRenderbuffers(1, &_depthStencil);
_depthStencil = 0;
}
if (_fbo) {
glDeleteFramebuffers(1, &_fbo);
_fbo = 0;
}
if (_fbo) {
glDeleteFramebuffers(1, &_fbo);
_fbo = 0;
}
_shared->shutdownRendering(_canvas, _currentSize);
_canvas.doneCurrent();
_shared->shutdownRendering(_canvas, _currentSize);
_canvas.doneCurrent();
}
_canvas.moveToThreadWithContext(qApp->thread());
moveToThread(qApp->thread());
QThread::currentThread()->quit();

View file

@ -53,6 +53,8 @@ private:
uint32_t _fbo{ 0 };
uint32_t _depthStencil{ 0 };
bool _initialized { false };
};
}}} // namespace hifi::qml::impl

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

@ -0,0 +1,24 @@
//
// Created by Sam Gondelman on 1/9/19
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "WebInputMode.h"
const char* webInputModeNames[] = {
"touch",
"mouse"
};
static const size_t WEB_INPUT_MODE_NAMES = (sizeof(webInputModeNames) / sizeof(webInputModeNames[0]));
QString WebInputModeHelpers::getNameForWebInputMode(WebInputMode mode) {
if (((int)mode <= 0) || ((int)mode >= (int)WEB_INPUT_MODE_NAMES)) {
mode = (WebInputMode)0;
}
return webInputModeNames[(int)mode];
}

View file

@ -0,0 +1,39 @@
//
// Created by Sam Gondelman on 1/9/19.
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_WebInputMode_h
#define hifi_WebInputMode_h
#include "QString"
/**jsdoc
* <p>Controls how the web surface processed PointerEvents</p>
* <table>
* <thead>
* <tr><th>Value</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>touch</code></td><td>Events are processed as touch events.</td></tr>
* <tr><td><code>mouse</code></td><td>Events are processed as mouse events.</td></tr>
* </tbody>
* </table>
* @typedef {string} WebInputMode
*/
enum class WebInputMode {
TOUCH = 0,
MOUSE,
};
class WebInputModeHelpers {
public:
static QString getNameForWebInputMode(WebInputMode mode);
};
#endif // hifi_WebInputMode_h

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