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