mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 07:19:05 +02:00
more web entity wip
This commit is contained in:
parent
45ec7fe3e1
commit
4b67a79561
18 changed files with 488 additions and 361 deletions
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -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";
|
|
||||||
}
|
|
|
@ -58,7 +58,7 @@ Item {
|
||||||
keyItem.state = "mouseOver";
|
keyItem.state = "mouseOver";
|
||||||
|
|
||||||
var globalPosition = keyItem.mapToGlobal(mouseArea1.mouseX, mouseArea1.mouseY);
|
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)) {
|
if (Pointers.isLeftHand(pointerID)) {
|
||||||
Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, leftHand);
|
Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, leftHand);
|
||||||
|
|
|
@ -151,6 +151,7 @@
|
||||||
#include <trackers/EyeTracker.h>
|
#include <trackers/EyeTracker.h>
|
||||||
#include <avatars-renderer/ScriptAvatar.h>
|
#include <avatars-renderer/ScriptAvatar.h>
|
||||||
#include <RenderableEntityItem.h>
|
#include <RenderableEntityItem.h>
|
||||||
|
#include <RenderableWebEntityItem.h>
|
||||||
#include <model-networking/MaterialCache.h>
|
#include <model-networking/MaterialCache.h>
|
||||||
#include "recording/ClipCache.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;
|
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
|
// Preload Tablet sounds
|
||||||
DependencyManager::get<TabletScriptingInterface>()->preloadSounds();
|
DependencyManager::get<TabletScriptingInterface>()->preloadSounds();
|
||||||
DependencyManager::get<Keyboard>()->createKeyboard();
|
DependencyManager::get<Keyboard>()->createKeyboard();
|
||||||
|
@ -3012,7 +3099,7 @@ void Application::initializeUi() {
|
||||||
});
|
});
|
||||||
|
|
||||||
offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1);
|
offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1);
|
||||||
offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2);
|
offscreenSurfaceCache->reserve(render::entities::WebEntityRenderer::QML, 2);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
flushMenuUpdates();
|
flushMenuUpdates();
|
||||||
|
|
|
@ -64,20 +64,13 @@
|
||||||
#include "AboutUtil.h"
|
#include "AboutUtil.h"
|
||||||
#include "ResourceRequestObserver.h"
|
#include "ResourceRequestObserver.h"
|
||||||
|
|
||||||
|
#include <RenderableWebEntityItem.h>
|
||||||
|
|
||||||
static int MAX_WINDOW_SIZE = 4096;
|
static int MAX_WINDOW_SIZE = 4096;
|
||||||
static const float METERS_TO_INCHES = 39.3701f;
|
static const float METERS_TO_INCHES = 39.3701f;
|
||||||
static const float OPAQUE_ALPHA_THRESHOLD = 0.99f;
|
static const float OPAQUE_ALPHA_THRESHOLD = 0.99f;
|
||||||
|
|
||||||
const QString Web3DOverlay::TYPE = "web3d";
|
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() {
|
Web3DOverlay::Web3DOverlay() {
|
||||||
_touchDevice.setCapabilities(QTouchDevice::Position);
|
_touchDevice.setCapabilities(QTouchDevice::Position);
|
||||||
|
@ -87,34 +80,23 @@ Web3DOverlay::Web3DOverlay() {
|
||||||
|
|
||||||
_geometryId = DependencyManager::get<GeometryCache>()->allocateID();
|
_geometryId = DependencyManager::get<GeometryCache>()->allocateID();
|
||||||
connect(this, &Web3DOverlay::requestWebSurface, this, &Web3DOverlay::buildWebSurface);
|
connect(this, &Web3DOverlay::requestWebSurface, this, &Web3DOverlay::buildWebSurface);
|
||||||
connect(this, &Web3DOverlay::releaseWebSurface, this, &Web3DOverlay::destroyWebSurface);
|
|
||||||
connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface);
|
connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface);
|
||||||
|
|
||||||
//need to be intialized before Tablet 1st open
|
//need to be intialized before Tablet 1st open
|
||||||
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(QML);
|
|
||||||
_cachedWebSurface = true;
|
_cachedWebSurface = true;
|
||||||
_webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
|
render::entities::WebEntityRenderer::initializeWebSurface(_webSurface);
|
||||||
_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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) :
|
Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) :
|
||||||
Billboard3DOverlay(Web3DOverlay),
|
Billboard3DOverlay(Web3DOverlay),
|
||||||
_url(Web3DOverlay->_url),
|
_url(Web3DOverlay->_url),
|
||||||
_scriptURL(Web3DOverlay->_scriptURL),
|
_scriptURL(Web3DOverlay->_scriptURL),
|
||||||
_dpi(Web3DOverlay->_dpi),
|
_dpi(Web3DOverlay->_dpi)
|
||||||
_showKeyboardFocusHighlight(Web3DOverlay->_showKeyboardFocusHighlight)
|
|
||||||
{
|
{
|
||||||
_geometryId = DependencyManager::get<GeometryCache>()->allocateID();
|
_geometryId = DependencyManager::get<GeometryCache>()->allocateID();
|
||||||
}
|
}
|
||||||
|
|
||||||
Web3DOverlay::~Web3DOverlay() {
|
Web3DOverlay::~Web3DOverlay() {
|
||||||
disconnect(this, &Web3DOverlay::requestWebSurface, this, nullptr);
|
|
||||||
disconnect(this, &Web3DOverlay::releaseWebSurface, this, nullptr);
|
|
||||||
disconnect(this, &Web3DOverlay::resizeWebSurface, this, nullptr);
|
|
||||||
|
|
||||||
destroyWebSurface();
|
destroyWebSurface();
|
||||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||||
if (geometryCache) {
|
if (geometryCache) {
|
||||||
|
@ -128,81 +110,22 @@ void Web3DOverlay::rebuildWebSurface() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Web3DOverlay::destroyWebSurface() {
|
void Web3DOverlay::destroyWebSurface() {
|
||||||
if (!_webSurface) {
|
if (_webSurface) {
|
||||||
return;
|
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() {
|
void Web3DOverlay::buildWebSurface() {
|
||||||
if (_webSurface) {
|
if (_webSurface) {
|
||||||
return;
|
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()) {
|
render::entities::WebEntityRenderer::acquireWebSurface(_url, isWebContent(), _webSurface, _cachedWebSurface);
|
||||||
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(QML);
|
onResizeWebSurface();
|
||||||
_cachedWebSurface = true;
|
_webSurface->resume();
|
||||||
_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();
|
|
||||||
});
|
|
||||||
|
|
||||||
QObject::connect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent);
|
_connections.push_back(QObject::connect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent));
|
||||||
QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived);
|
_connections.push_back(QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Web3DOverlay::update(float deltatime) {
|
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("Workload", qApp->getGameWorkload()._engine->getConfiguration().get());
|
||||||
_webSurface->getSurfaceContext()->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
|
_webSurface->getSurfaceContext()->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
|
||||||
_webSurface->getSurfaceContext()->setContextProperty("Pointers", DependencyManager::get<PointerScriptingInterface>().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("Window", DependencyManager::get<WindowScriptingInterface>().data());
|
||||||
_webSurface->getSurfaceContext()->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface());
|
_webSurface->getSurfaceContext()->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface());
|
||||||
_webSurface->getSurfaceContext()->setContextProperty("HiFiAbout", AboutUtil::getInstance());
|
_webSurface->getSurfaceContext()->setContextProperty("HiFiAbout", AboutUtil::getInstance());
|
||||||
_webSurface->getSurfaceContext()->setContextProperty("WalletScriptingInterface", DependencyManager::get<WalletScriptingInterface>().data());
|
_webSurface->getSurfaceContext()->setContextProperty("WalletScriptingInterface", DependencyManager::get<WalletScriptingInterface>().data());
|
||||||
_webSurface->getSurfaceContext()->setContextProperty("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().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) {
|
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;
|
_desiredMaxFPS = maxFPS;
|
||||||
if (_webSurface) {
|
if (_webSurface) {
|
||||||
_webSurface->setMaxFps(_desiredMaxFPS);
|
_webSurface->setMaxFps(_desiredMaxFPS);
|
||||||
|
@ -305,14 +225,6 @@ void Web3DOverlay::onResizeWebSurface() {
|
||||||
_webSurface->resize(QSize(dims.x, dims.y));
|
_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) {
|
void Web3DOverlay::render(RenderArgs* args) {
|
||||||
if (!_renderVisible || !getParentVisible()) {
|
if (!_renderVisible || !getParentVisible()) {
|
||||||
return;
|
return;
|
||||||
|
@ -506,11 +418,6 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) {
|
||||||
_desiredMaxFPS = maxFPS.toInt();
|
_desiredMaxFPS = maxFPS.toInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto showKeyboardFocusHighlight = properties["showKeyboardFocusHighlight"];
|
|
||||||
if (showKeyboardFocusHighlight.isValid()) {
|
|
||||||
_showKeyboardFocusHighlight = showKeyboardFocusHighlight.toBool();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto inputModeValue = properties["inputMode"];
|
auto inputModeValue = properties["inputMode"];
|
||||||
if (inputModeValue.isValid()) {
|
if (inputModeValue.isValid()) {
|
||||||
QString inputModeStr = inputModeValue.toString();
|
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:
|
* @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>.
|
* <code>scale</code>, <code>size</code>.
|
||||||
* @property {number} maxFPS=10 - The maximum update rate for the Web overlay content, in frames/second.
|
* @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>.
|
* @property {string} inputMode=Touch - The user input mode to use - either <code>"Touch"</code> or <code>"Mouse"</code>.
|
||||||
*/
|
*/
|
||||||
QVariant Web3DOverlay::getProperty(const QString& property) {
|
QVariant Web3DOverlay::getProperty(const QString& property) {
|
||||||
|
@ -590,9 +495,6 @@ QVariant Web3DOverlay::getProperty(const QString& property) {
|
||||||
if (property == "maxFPS") {
|
if (property == "maxFPS") {
|
||||||
return _desiredMaxFPS;
|
return _desiredMaxFPS;
|
||||||
}
|
}
|
||||||
if (property == "showKeyboardFocusHighlight") {
|
|
||||||
return _showKeyboardFocusHighlight;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (property == "inputMode") {
|
if (property == "inputMode") {
|
||||||
if (_inputMode == Mouse) {
|
if (_inputMode == Mouse) {
|
||||||
|
|
|
@ -22,8 +22,6 @@ class Web3DOverlay : public Billboard3DOverlay {
|
||||||
using Parent = Billboard3DOverlay;
|
using Parent = Billboard3DOverlay;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static const QString QML;
|
|
||||||
static QString const TYPE;
|
static QString const TYPE;
|
||||||
virtual QString getType() const override { return TYPE; }
|
virtual QString getType() const override { return TYPE; }
|
||||||
|
|
||||||
|
@ -63,8 +61,6 @@ public:
|
||||||
void destroyWebSurface();
|
void destroyWebSurface();
|
||||||
void onResizeWebSurface();
|
void onResizeWebSurface();
|
||||||
|
|
||||||
Q_INVOKABLE unsigned int deviceIdByTouchPoint(qreal x, qreal y);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void emitScriptEvent(const QVariant& scriptMessage);
|
void emitScriptEvent(const QVariant& scriptMessage);
|
||||||
|
|
||||||
|
@ -73,7 +69,6 @@ signals:
|
||||||
void webEventReceived(const QVariant& message);
|
void webEventReceived(const QVariant& message);
|
||||||
void resizeWebSurface();
|
void resizeWebSurface();
|
||||||
void requestWebSurface();
|
void requestWebSurface();
|
||||||
void releaseWebSurface();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Transform evalRenderTransform() override;
|
Transform evalRenderTransform() override;
|
||||||
|
@ -91,7 +86,6 @@ private:
|
||||||
QString _scriptURL;
|
QString _scriptURL;
|
||||||
float _dpi { 30.0f };
|
float _dpi { 30.0f };
|
||||||
int _geometryId { 0 };
|
int _geometryId { 0 };
|
||||||
bool _showKeyboardFocusHighlight { true };
|
|
||||||
|
|
||||||
QTouchDevice _touchDevice;
|
QTouchDevice _touchDevice;
|
||||||
|
|
||||||
|
@ -99,6 +93,8 @@ private:
|
||||||
uint8_t _currentMaxFPS { 0 };
|
uint8_t _currentMaxFPS { 0 };
|
||||||
|
|
||||||
bool _mayNeedResize { false };
|
bool _mayNeedResize { false };
|
||||||
|
|
||||||
|
std::vector<QMetaObject::Connection> _connections;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_Web3DOverlay_h
|
#endif // hifi_Web3DOverlay_h
|
||||||
|
|
|
@ -30,19 +30,24 @@
|
||||||
using namespace render;
|
using namespace render;
|
||||||
using namespace render::entities;
|
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;
|
const float METERS_TO_INCHES = 39.3701f;
|
||||||
static uint32_t _currentWebCount{ 0 };
|
static float OPAQUE_ALPHA_THRESHOLD = 0.99f;
|
||||||
// Don't allow more than 20 concurrent web views
|
|
||||||
static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20;
|
|
||||||
// If a web-view hasn't been rendered for 30 seconds, de-allocate the framebuffer
|
// 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 uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND;
|
||||||
|
|
||||||
static int MAX_WINDOW_SIZE = 4096;
|
static uint8_t YOUTUBE_MAX_FPS = 30;
|
||||||
static float OPAQUE_ALPHA_THRESHOLD = 0.99f;
|
|
||||||
static int DEFAULT_MAX_FPS = 10;
|
// Don't allow more than 20 concurrent web views
|
||||||
static int YOUTUBE_MAX_FPS = 30;
|
static uint32_t _currentWebCount { 0 };
|
||||||
|
static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20;
|
||||||
|
|
||||||
static QTouchDevice _touchDevice;
|
static QTouchDevice _touchDevice;
|
||||||
static const char* URL_PROPERTY = "url";
|
static const char* URL_PROPERTY = "url";
|
||||||
|
@ -71,13 +76,19 @@ WebEntityRenderer::WebEntityRenderer(const EntityItemPointer& entity) : Parent(e
|
||||||
_touchDevice.setMaximumTouchPoints(4);
|
_touchDevice.setMaximumTouchPoints(4);
|
||||||
});
|
});
|
||||||
_geometryId = DependencyManager::get<GeometryCache>()->allocateID();
|
_geometryId = DependencyManager::get<GeometryCache>()->allocateID();
|
||||||
|
|
||||||
_texture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda());
|
_texture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda());
|
||||||
_texture->setSource(__FUNCTION__);
|
_texture->setSource(__FUNCTION__);
|
||||||
|
|
||||||
|
// need to be intialized early
|
||||||
|
_cachedWebSurface = true;
|
||||||
|
WebEntityRenderer::initializeWebSurface(_webSurface);
|
||||||
|
|
||||||
_timer.setInterval(MSECS_PER_SECOND);
|
_timer.setInterval(MSECS_PER_SECOND);
|
||||||
connect(&_timer, &QTimer::timeout, this, &WebEntityRenderer::onTimeout);
|
connect(&_timer, &QTimer::timeout, this, &WebEntityRenderer::onTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity) {
|
WebEntityRenderer::~WebEntityRenderer() {
|
||||||
destroyWebSurface();
|
destroyWebSurface();
|
||||||
|
|
||||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
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 {
|
bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
|
||||||
if (_contextPosition != entity->getWorldPosition()) {
|
if (_contextPosition != entity->getWorldPosition()) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -101,11 +117,31 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_lastSourceUrl != entity->getSourceUrl()) {
|
if (_color != entity->getColor()) {
|
||||||
return true;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,35 +149,26 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebEntityRenderer::needsRenderUpdate() const {
|
bool WebEntityRenderer::needsRenderUpdate() const {
|
||||||
{
|
if (resultWithReadLock<bool>([&] {
|
||||||
QSharedPointer<OffscreenQmlSurface> webSurface;
|
// If we have rendered recently, and there is no web surface, we're going to create one
|
||||||
withReadLock([&] {
|
return !_webSurface;
|
||||||
webSurface = _webSurface;
|
})) {
|
||||||
});
|
return true;
|
||||||
if (!webSurface) {
|
|
||||||
// If we have rendered recently, and there is no web surface, we're going to create one
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Parent::needsRenderUpdate();
|
return Parent::needsRenderUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebEntityRenderer::onTimeout() {
|
void WebEntityRenderer::onTimeout() {
|
||||||
bool needsCheck = resultWithReadLock<bool>([&] {
|
uint64_t lastRenderTime;
|
||||||
|
if (!resultWithReadLock<bool>([&] {
|
||||||
|
lastRenderTime = _lastRenderTime;
|
||||||
return (_lastRenderTime != 0 && (bool)_webSurface);
|
return (_lastRenderTime != 0 && (bool)_webSurface);
|
||||||
});
|
})) {
|
||||||
|
|
||||||
if (!needsCheck) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t interval;
|
if (usecTimestampNow() - lastRenderTime > MAX_NO_RENDER_INTERVAL) {
|
||||||
withReadLock([&] {
|
|
||||||
interval = usecTimestampNow() - _lastRenderTime;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (interval > MAX_NO_RENDER_INTERVAL) {
|
|
||||||
destroyWebSurface();
|
destroyWebSurface();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,9 +181,9 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene
|
||||||
{
|
{
|
||||||
auto newSourceUrl = entity->getSourceUrl();
|
auto newSourceUrl = entity->getSourceUrl();
|
||||||
auto newContentType = getContentType(newSourceUrl);
|
auto newContentType = getContentType(newSourceUrl);
|
||||||
auto currentContentType = ContentType::NoContent;
|
ContentType currentContentType;
|
||||||
withReadLock([&] {
|
withReadLock([&] {
|
||||||
urlChanged = _lastSourceUrl != newSourceUrl;
|
urlChanged = _sourceURL != newSourceUrl;
|
||||||
currentContentType = _contentType;
|
currentContentType = _contentType;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -168,7 +195,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene
|
||||||
}
|
}
|
||||||
|
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
_lastSourceUrl = newSourceUrl;
|
_sourceURL = newSourceUrl;
|
||||||
_contentType = newContentType;
|
_contentType = newContentType;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -176,6 +203,11 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene
|
||||||
|
|
||||||
|
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
|
_inputMode = entity->getInputMode();
|
||||||
|
_dpi = entity->getDPI();
|
||||||
|
_color = entity->getColor();
|
||||||
|
_alpha = entity->getAlpha();
|
||||||
|
|
||||||
if (_contentType == ContentType::NoContent) {
|
if (_contentType == ContentType::NoContent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -187,19 +219,39 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urlChanged && _contentType == ContentType::HtmlContent) {
|
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;
|
void* key = (void*)this;
|
||||||
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] () {
|
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] () {
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
if (_contextPosition != entity->getWorldPosition()) {
|
|
||||||
// update globalPosition
|
|
||||||
_contextPosition = entity->getWorldPosition();
|
|
||||||
_webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition));
|
|
||||||
}
|
|
||||||
|
|
||||||
_lastDPI = entity->getDPI();
|
|
||||||
|
|
||||||
glm::vec2 windowSize = getWindowSize(entity);
|
glm::vec2 windowSize = getWindowSize(entity);
|
||||||
_webSurface->resize(QSize(windowSize.x, windowSize.y));
|
_webSurface->resize(QSize(windowSize.x, windowSize.y));
|
||||||
|
@ -212,19 +264,11 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebEntityRenderer::doRender(RenderArgs* args) {
|
void WebEntityRenderer::doRender(RenderArgs* args) {
|
||||||
|
PerformanceTimer perfTimer("WebEntityRenderer::render");
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
_lastRenderTime = usecTimestampNow();
|
_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
|
// Try to update the texture
|
||||||
{
|
{
|
||||||
QSharedPointer<OffscreenQmlSurface> webSurface;
|
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);
|
static const glm::vec2 texMin(0.0f), texMax(1.0f), topLeft(-0.5f), bottomRight(0.5f);
|
||||||
|
|
||||||
gpu::Batch& batch = *args->_batch;
|
gpu::Batch& batch = *args->_batch;
|
||||||
|
glm::vec4 color;
|
||||||
withReadLock([&] {
|
withReadLock([&] {
|
||||||
|
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
||||||
|
color = glm::vec4(toGlm(_color), _alpha * fadeRatio);
|
||||||
batch.setModelTransform(_renderTransform);
|
batch.setModelTransform(_renderTransform);
|
||||||
});
|
});
|
||||||
batch.setResourceTexture(0, _texture);
|
batch.setResourceTexture(0, _texture);
|
||||||
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
|
||||||
|
|
||||||
// Turn off jitter for these entities
|
// Turn off jitter for these entities
|
||||||
batch.pushProjectionJitter();
|
batch.pushProjectionJitter();
|
||||||
DependencyManager::get<GeometryCache>()->bindWebBrowserProgram(batch, fadeRatio < OPAQUE_ALPHA_THRESHOLD);
|
DependencyManager::get<GeometryCache>()->bindWebBrowserProgram(batch, color.a < 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>()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, color, _geometryId);
|
||||||
batch.popProjectionJitter();
|
batch.popProjectionJitter();
|
||||||
batch.setResourceTexture(0, nullptr);
|
batch.setResourceTexture(0, nullptr);
|
||||||
}
|
}
|
||||||
|
@ -264,89 +309,49 @@ bool WebEntityRenderer::hasWebSurface() {
|
||||||
return (bool)_webSurface && _webSurface->getRootItem();
|
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) {
|
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) {
|
if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) {
|
||||||
qWarning() << "Too many concurrent web views to create new view";
|
qWarning() << "Too many concurrent web views to create new view";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
++_currentWebCount;
|
++_currentWebCount;
|
||||||
|
WebEntityRenderer::acquireWebSurface(_sourceURL, _contentType == ContentType::HtmlContent, _webSurface, _cachedWebSurface);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
_fadeStartTime = usecTimestampNow();
|
_fadeStartTime = usecTimestampNow();
|
||||||
_webSurface->resume();
|
_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();
|
return _webSurface->getRootItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebEntityRenderer::destroyWebSurface() {
|
void WebEntityRenderer::destroyWebSurface() {
|
||||||
QSharedPointer<OffscreenQmlSurface> webSurface;
|
QSharedPointer<OffscreenQmlSurface> webSurface;
|
||||||
ContentType contentType{ ContentType::NoContent };
|
ContentType contentType = ContentType::NoContent;
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
webSurface.swap(_webSurface);
|
webSurface.swap(_webSurface);
|
||||||
std::swap(contentType, _contentType);
|
_contentType = contentType;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (webSurface) {
|
if (webSurface) {
|
||||||
--_currentWebCount;
|
--_currentWebCount;
|
||||||
QQuickItem* rootItem = webSurface->getRootItem();
|
WebEntityRenderer::releaseWebSurface(webSurface, _cachedWebSurface, _connections);
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) const {
|
glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) const {
|
||||||
glm::vec2 dims = glm::vec2(entity->getScaledDimensions());
|
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
|
// ensure no side is never larger then MAX_WINDOW_SIZE
|
||||||
float max = (dims.x > dims.y) ? dims.x : dims.y;
|
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) {
|
void WebEntityRenderer::hoverEnterEntity(const PointerEvent& event) {
|
||||||
if (_webSurface) {
|
if (_inputMode == WebInputMode::MOUSE) {
|
||||||
|
handlePointerEvent(event);
|
||||||
|
} else if (_webSurface) {
|
||||||
PointerEvent webEvent = event;
|
PointerEvent webEvent = event;
|
||||||
webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI));
|
webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi));
|
||||||
_webSurface->hoverBeginEvent(webEvent, _touchDevice);
|
_webSurface->hoverBeginEvent(webEvent, _touchDevice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) {
|
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;
|
PointerEvent webEvent = event;
|
||||||
webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI));
|
webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi));
|
||||||
_webSurface->hoverEndEvent(webEvent, _touchDevice);
|
_webSurface->hoverEndEvent(webEvent, _touchDevice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) {
|
void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) {
|
||||||
|
if (_inputMode == WebInputMode::TOUCH) {
|
||||||
|
handlePointerEventAsTouch(event);
|
||||||
|
} else {
|
||||||
|
handlePointerEventAsMouse(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebEntityRenderer::handlePointerEventAsTouch(const PointerEvent& event) {
|
||||||
if (_webSurface) {
|
if (_webSurface) {
|
||||||
PointerEvent webEvent = event;
|
PointerEvent webEvent = event;
|
||||||
webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI));
|
webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi));
|
||||||
_webSurface->handlePointerEvent(webEvent, _touchDevice);
|
_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) {
|
void WebEntityRenderer::setProxyWindow(QWindow* proxyWindow) {
|
||||||
if (_webSurface) {
|
if (_webSurface) {
|
||||||
_webSurface->setProxyWindow(proxyWindow);
|
_webSurface->setProxyWindow(proxyWindow);
|
||||||
|
@ -394,8 +453,6 @@ QObject* WebEntityRenderer::getEventHandler() {
|
||||||
return _webSurface->getEventHandler();
|
return _webSurface->getEventHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebEntityRenderer::isTransparent() const {
|
void WebEntityRenderer::emitScriptEvent(const QVariant& message) {
|
||||||
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
QMetaObject::invokeMethod(this, "scriptEventReceived", Q_ARG(QVariant, message));
|
||||||
return fadeRatio < OPAQUE_ALPHA_THRESHOLD;
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -24,13 +24,35 @@ class WebEntityRenderer : public TypedEntityRenderer<WebEntityItem> {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WebEntityRenderer(const EntityItemPointer& entity);
|
WebEntityRenderer(const EntityItemPointer& entity);
|
||||||
|
~WebEntityRenderer();
|
||||||
|
|
||||||
Q_INVOKABLE void hoverEnterEntity(const PointerEvent& event);
|
Q_INVOKABLE void hoverEnterEntity(const PointerEvent& event);
|
||||||
Q_INVOKABLE void hoverLeaveEntity(const PointerEvent& event);
|
Q_INVOKABLE void hoverLeaveEntity(const PointerEvent& event);
|
||||||
Q_INVOKABLE void handlePointerEvent(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:
|
protected:
|
||||||
virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override;
|
|
||||||
virtual bool needsRenderUpdate() const override;
|
virtual bool needsRenderUpdate() const override;
|
||||||
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
|
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
|
||||||
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) 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 wantsHandControllerPointerEvents() const override { return true; }
|
||||||
virtual bool wantsKeyboardFocus() const override { return true; }
|
virtual bool wantsKeyboardFocus() const override { return true; }
|
||||||
|
|
||||||
virtual void setProxyWindow(QWindow* proxyWindow) override;
|
virtual void setProxyWindow(QWindow* proxyWindow) override;
|
||||||
virtual QObject* getEventHandler() override;
|
virtual QObject* getEventHandler() override;
|
||||||
|
|
||||||
|
void handlePointerEventAsTouch(const PointerEvent& event);
|
||||||
|
void handlePointerEventAsMouse(const PointerEvent& event);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onTimeout();
|
void onTimeout();
|
||||||
bool buildWebSurface(const TypedEntityPointer& entity);
|
bool buildWebSurface(const TypedEntityPointer& entity);
|
||||||
|
@ -49,30 +75,47 @@ private:
|
||||||
bool hasWebSurface();
|
bool hasWebSurface();
|
||||||
glm::vec2 getWindowSize(const TypedEntityPointer& entity) const;
|
glm::vec2 getWindowSize(const TypedEntityPointer& entity) const;
|
||||||
|
|
||||||
|
|
||||||
int _geometryId{ 0 };
|
int _geometryId{ 0 };
|
||||||
enum class ContentType {
|
enum class ContentType {
|
||||||
NoContent,
|
NoContent,
|
||||||
HtmlContent,
|
HtmlContent,
|
||||||
QmlContent
|
QmlContent
|
||||||
};
|
};
|
||||||
|
|
||||||
static ContentType getContentType(const QString& urlString);
|
static ContentType getContentType(const QString& urlString);
|
||||||
|
ContentType _contentType { ContentType::NoContent };
|
||||||
|
|
||||||
ContentType _contentType{ ContentType::NoContent };
|
|
||||||
QSharedPointer<OffscreenQmlSurface> _webSurface;
|
QSharedPointer<OffscreenQmlSurface> _webSurface;
|
||||||
glm::vec3 _contextPosition;
|
bool _cachedWebSurface { false };
|
||||||
gpu::TexturePointer _texture;
|
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;
|
QTimer _timer;
|
||||||
uint64_t _lastRenderTime { 0 };
|
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
|
#endif // hifi_RenderableWebEntityItem_h
|
||||||
|
|
|
@ -1285,6 +1285,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
||||||
* The entity has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}.
|
* The entity has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}.
|
||||||
* @typedef {object} Entities.EntityProperties-Web
|
* @typedef {object} Entities.EntityProperties-Web
|
||||||
* @property {Vec3} dimensions=0.1,0.1,0.01 - The dimensions of the entity.
|
* @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
|
* @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.
|
* 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
|
* @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
|
// Web only
|
||||||
if (_type == EntityTypes::Web) {
|
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_SOURCE_URL, sourceUrl);
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DPI, dpi);
|
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DPI, dpi);
|
||||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT_URL, scriptURL);
|
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT_URL, scriptURL);
|
||||||
|
@ -3051,6 +3056,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
|
||||||
}
|
}
|
||||||
|
|
||||||
if (properties.getType() == EntityTypes::Web) {
|
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_SOURCE_URL, properties.getSourceUrl());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI());
|
APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_SCRIPT_URL, properties.getScriptURL());
|
APPEND_ENTITY_PROPERTY(PROP_SCRIPT_URL, properties.getScriptURL());
|
||||||
|
|
|
@ -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) {
|
void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) {
|
||||||
const float MAX_FLAT_DIMENSION = 0.0001f;
|
const float MAX_FLAT_DIMENSION = 0.0001f;
|
||||||
if ((_shape == entity::Shape::Circle || _shape == entity::Shape::Quad) && value.y > MAX_FLAT_DIMENSION) {
|
if ((_shape == entity::Shape::Circle || _shape == entity::Shape::Quad) && value.y > MAX_FLAT_DIMENSION) {
|
||||||
|
|
|
@ -74,7 +74,7 @@ public:
|
||||||
void setShape(const entity::Shape& shape);
|
void setShape(const entity::Shape& shape);
|
||||||
void setShape(const QString& shape) { setShape(entity::shapeFromString(shape)); }
|
void setShape(const QString& shape) { setShape(entity::shapeFromString(shape)); }
|
||||||
|
|
||||||
float getAlpha() const { return _alpha; };
|
float getAlpha() const;
|
||||||
void setAlpha(float alpha);
|
void setAlpha(float alpha);
|
||||||
|
|
||||||
glm::u8vec3 getColor() const;
|
glm::u8vec3 getColor() const;
|
||||||
|
@ -102,9 +102,8 @@ public:
|
||||||
std::shared_ptr<graphics::Material> getMaterial() { return _material; }
|
std::shared_ptr<graphics::Material> getMaterial() { return _material; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
float _alpha { 1.0f };
|
|
||||||
glm::u8vec3 _color;
|
glm::u8vec3 _color;
|
||||||
|
float _alpha { 1.0f };
|
||||||
entity::Shape _shape { entity::Shape::Sphere };
|
entity::Shape _shape { entity::Shape::Sphere };
|
||||||
|
|
||||||
//! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain
|
//! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain
|
||||||
|
|
|
@ -32,20 +32,25 @@ EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const Ent
|
||||||
|
|
||||||
WebEntityItem::WebEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
|
WebEntityItem::WebEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
|
||||||
_type = EntityTypes::Web;
|
_type = EntityTypes::Web;
|
||||||
_dpi = ENTITY_ITEM_DEFAULT_DPI;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const float WEB_ENTITY_ITEM_FIXED_DEPTH = 0.01f;
|
|
||||||
|
|
||||||
void WebEntityItem::setUnscaledDimensions(const glm::vec3& value) {
|
void WebEntityItem::setUnscaledDimensions(const glm::vec3& value) {
|
||||||
// NOTE: Web Entities always have a "depth" of 1cm.
|
// 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));
|
EntityItem::setUnscaledDimensions(glm::vec3(value.x, value.y, WEB_ENTITY_ITEM_FIXED_DEPTH));
|
||||||
}
|
}
|
||||||
|
|
||||||
EntityItemProperties WebEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const {
|
EntityItemProperties WebEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const {
|
||||||
EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class
|
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(sourceUrl, getSourceUrl);
|
||||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(dpi, getDPI);
|
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;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,8 +58,14 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) {
|
||||||
bool somethingChanged = false;
|
bool somethingChanged = false;
|
||||||
somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
|
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(sourceUrl, setSourceUrl);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(dpi, setDPI);
|
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) {
|
if (somethingChanged) {
|
||||||
bool wantDebug = false;
|
bool wantDebug = false;
|
||||||
|
@ -78,16 +89,28 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i
|
||||||
int bytesRead = 0;
|
int bytesRead = 0;
|
||||||
const unsigned char* dataAt = data;
|
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_SOURCE_URL, QString, setSourceUrl);
|
||||||
READ_ENTITY_PROPERTY(PROP_DPI, uint16_t, setDPI);
|
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;
|
return bytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||||
|
requestedProperties += PROP_COLOR;
|
||||||
|
requestedProperties += PROP_ALPHA;
|
||||||
|
|
||||||
requestedProperties += PROP_SOURCE_URL;
|
requestedProperties += PROP_SOURCE_URL;
|
||||||
requestedProperties += PROP_DPI;
|
requestedProperties += PROP_DPI;
|
||||||
|
requestedProperties += PROP_SCRIPT_URL;
|
||||||
|
requestedProperties += PROP_MAX_FPS;
|
||||||
|
requestedProperties += PROP_INPUT_MODE;
|
||||||
return requestedProperties;
|
return requestedProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,8 +123,14 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst
|
||||||
OctreeElement::AppendState& appendState) const {
|
OctreeElement::AppendState& appendState) const {
|
||||||
|
|
||||||
bool successPropertyFits = true;
|
bool successPropertyFits = true;
|
||||||
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, _sourceUrl);
|
APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_DPI, _dpi);
|
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,
|
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) {
|
void WebEntityItem::setSourceUrl(const QString& value) {
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
if (_sourceUrl != value) {
|
if (_sourceUrl != value) {
|
||||||
|
@ -173,17 +226,63 @@ void WebEntityItem::setSourceUrl(const QString& value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WebEntityItem::getSourceUrl() const {
|
QString WebEntityItem::getSourceUrl() const {
|
||||||
QString result;
|
return resultWithReadLock<QString>([&] {
|
||||||
withReadLock([&] {
|
return _sourceUrl;
|
||||||
result = _sourceUrl;
|
|
||||||
});
|
});
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebEntityItem::setDPI(uint16_t value) {
|
void WebEntityItem::setDPI(uint16_t value) {
|
||||||
_dpi = value;
|
withWriteLock([&] {
|
||||||
|
_dpi = value;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t WebEntityItem::getDPI() const {
|
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;
|
||||||
|
});
|
||||||
|
}
|
|
@ -52,18 +52,38 @@ public:
|
||||||
BoxFace& face, glm::vec3& surfaceNormal,
|
BoxFace& face, glm::vec3& surfaceNormal,
|
||||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
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;
|
static const QString DEFAULT_SOURCE_URL;
|
||||||
virtual void setSourceUrl(const QString& value);
|
void setSourceUrl(const QString& value);
|
||||||
QString getSourceUrl() const;
|
QString getSourceUrl() const;
|
||||||
|
|
||||||
void setDPI(uint16_t value);
|
void setDPI(uint16_t value);
|
||||||
uint16_t getDPI() const;
|
uint16_t getDPI() const;
|
||||||
|
|
||||||
|
void setScriptURL(const QString& value);
|
||||||
|
QString getScriptURL() const;
|
||||||
|
|
||||||
static const uint8_t DEFAULT_MAX_FPS;
|
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:
|
protected:
|
||||||
|
glm::u8vec3 _color;
|
||||||
|
float _alpha { 1.0f };
|
||||||
|
|
||||||
QString _sourceUrl;
|
QString _sourceUrl;
|
||||||
uint16_t _dpi;
|
uint16_t _dpi;
|
||||||
|
QString _scriptURL;
|
||||||
|
uint8_t _maxFPS;
|
||||||
|
WebInputMode _inputMode;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_WebEntityItem_h
|
#endif // hifi_WebEntityItem_h
|
||||||
|
|
|
@ -257,6 +257,7 @@ enum class EntityVersion : PacketVersion {
|
||||||
UpdatedPolyLines,
|
UpdatedPolyLines,
|
||||||
FixProtocolVersionBumpMismatch,
|
FixProtocolVersionBumpMismatch,
|
||||||
MigrateOverlayRenderProperties,
|
MigrateOverlayRenderProperties,
|
||||||
|
MissingWebEntityProperties,
|
||||||
|
|
||||||
// Add new versions above here
|
// Add new versions above here
|
||||||
NUM_PACKET_TYPE,
|
NUM_PACKET_TYPE,
|
||||||
|
|
|
@ -500,6 +500,9 @@ void SharedObject::onTimer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
if (_maxFps == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto minRenderInterval = USECS_PER_SECOND / _maxFps;
|
auto minRenderInterval = USECS_PER_SECOND / _maxFps;
|
||||||
auto lastInterval = usecTimestampNow() - _lastRenderTime;
|
auto lastInterval = usecTimestampNow() - _lastRenderTime;
|
||||||
// Don't exceed the framerate limit
|
// Don't exceed the framerate limit
|
||||||
|
|
|
@ -35,7 +35,7 @@ public:
|
||||||
Q_INVOKABLE void synthesizeKeyPress(QString key, QObject* targetOverride = nullptr);
|
Q_INVOKABLE void synthesizeKeyPress(QString key, QObject* targetOverride = nullptr);
|
||||||
Q_INVOKABLE void lowerKeyboard();
|
Q_INVOKABLE void lowerKeyboard();
|
||||||
PointerEvent::EventType choosePointerEventType(QEvent::Type type);
|
PointerEvent::EventType choosePointerEventType(QEvent::Type type);
|
||||||
unsigned int deviceIdByTouchPoint(qreal x, qreal y);
|
Q_INVOKABLE unsigned int deviceIdByTouchPoint(qreal x, qreal y);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void focusObjectChanged(QObject* newFocus);
|
void focusObjectChanged(QObject* newFocus);
|
||||||
|
|
Loading…
Reference in a new issue