diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 9186c4b086..f72bbd5c7d 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -3,6 +3,7 @@ import QtQuick 2.0 Item { id: tabletRoot objectName: "tabletRoot" + property var eventBridge; function loadSource(url) { loader.source = url; @@ -19,6 +20,13 @@ Item { width: parent.width height: parent.height + + onLoaded: { + // propogate eventBridge to WebEngineView + if (loader.item.hasOwnProperty("eventBridge")) { + loader.item.eventBridge = eventBridge; + } + } } width: 480 diff --git a/interface/resources/qml/hifi/tablet/TabletWebView.qml b/interface/resources/qml/hifi/tablet/TabletWebView.qml index 1bdba0ad22..0f697d634e 100644 --- a/interface/resources/qml/hifi/tablet/TabletWebView.qml +++ b/interface/resources/qml/hifi/tablet/TabletWebView.qml @@ -1,9 +1,10 @@ import QtQuick 2.0 import QtWebEngine 1.2 -WebEngineView { - id: webEngineView - height: parent.height - width: parent.width +import "../../controls" as Controls + +Controls.WebView { + } + diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 89ec70ca22..fe48b05f5e 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -61,7 +61,7 @@ Web3DOverlay::~Web3DOverlay() { if (rootItem && rootItem->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); + tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr, nullptr); } // Fix for crash in QtWebEngineCore when rapidly switching domains @@ -138,7 +138,7 @@ void Web3DOverlay::loadSourceURL() { if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem()); + tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data()); } } } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index bdb84fadc7..df28769964 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -268,7 +268,7 @@ void RenderableWebEntityItem::loadSourceURL() { if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem()); + tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data()); } } } @@ -372,7 +372,7 @@ void RenderableWebEntityItem::destroyWebSurface() { if (rootItem && rootItem->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); + tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr, nullptr); } // Fix for crash in QtWebEngineCore when rapidly switching domains diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index 3831e6cf05..97dfe1e80f 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -29,10 +29,10 @@ QObject* TabletScriptingInterface::getTablet(const QString& tabletId) { } } -void TabletScriptingInterface::setQmlTabletRoot(QString tabletId, QQuickItem* qmlTabletRoot) { +void TabletScriptingInterface::setQmlTabletRoot(QString tabletId, QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface) { TabletProxy* tablet = qobject_cast(getTablet(tabletId)); - if (tablet) { - tablet->setQmlTabletRoot(qmlTabletRoot); + if (tablet && qmlOffscreenSurface) { + tablet->setQmlTabletRoot(qmlTabletRoot, qmlOffscreenSurface); } else { qCWarning(scriptengine) << "TabletScriptingInterface::setupTablet() bad tablet object"; } @@ -72,10 +72,12 @@ static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy->setQmlButton(qobject_cast(qmlButton)); } -void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot) { +void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface) { std::lock_guard guard(_mutex); + _qmlOffscreenSurface = qmlOffscreenSurface; _qmlTabletRoot = qmlTabletRoot; - if (_qmlTabletRoot) { + if (_qmlTabletRoot && _qmlOffscreenSurface) { + QObject::connect(_qmlOffscreenSurface, SIGNAL(webEventReceived(QVariant)), this, SIGNAL(webEventReceived(QVariant))); gotoHomeScreen(); } else { removeButtonsFromHomeScreen(); @@ -150,6 +152,12 @@ void TabletProxy::updateAudioBar(const double micLevel) { } } +void TabletProxy::emitScriptEvent(QVariant msg) { + if (_qmlOffscreenSurface) { + QMetaObject::invokeMethod(_qmlOffscreenSurface, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg)); + } +} + void TabletProxy::addButtonsToHomeScreen() { auto tablet = getQmlTablet(); if (!tablet) { diff --git a/libraries/script-engine/src/TabletScriptingInterface.h b/libraries/script-engine/src/TabletScriptingInterface.h index 5ccd4689e3..7268a3520c 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.h +++ b/libraries/script-engine/src/TabletScriptingInterface.h @@ -37,7 +37,7 @@ public: */ Q_INVOKABLE QObject* getTablet(const QString& tabletId); - void setQmlTabletRoot(QString tabletId, QQuickItem* qmlTabletRoot); + void setQmlTabletRoot(QString tabletId, QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface); protected: std::mutex _mutex; @@ -46,7 +46,7 @@ protected: /**jsdoc * @class TabletProxy - * @property name {string} name of this tablet + * @property name {string} READ_ONLY: name of this tablet */ class TabletProxy : public QObject { Q_OBJECT @@ -54,32 +54,32 @@ class TabletProxy : public QObject { public: TabletProxy(QString name); - void setQmlTabletRoot(QQuickItem* qmlTabletRoot); + void setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface); /**jsdoc - * @function TabletProxy#gotoHomeScreen * transition to the home screen + * @function TabletProxy#gotoHomeScreen */ Q_INVOKABLE void gotoHomeScreen(); /**jsdoc - * @function TabletProxy#gotoWebScreen * show the specified web url on the tablet. + * @function TabletProxy#gotoWebScreen * @param url {string} */ Q_INVOKABLE void gotoWebScreen(const QString& url); /**jsdoc - * @function TabletProxy#addButton * Creates a new button, adds it to this and returns it. + * @function TabletProxy#addButton * @param properties {Object} button properties UI_TABLET_HACK: enumerate these when we figure out what they should be! * @returns {TabletButtonProxy} */ Q_INVOKABLE QObject* addButton(const QVariant& properties); /**jsdoc - * @function TabletProxy#removeButton * removes button from the tablet + * @function TabletProxy.removeButton * @param tabletButtonProxy {TabletButtonProxy} button to be removed */ Q_INVOKABLE void removeButton(QObject* tabletButtonProxy); @@ -90,8 +90,25 @@ public: * @param micLevel {double} mic level value between 0 and 1 */ Q_INVOKABLE void updateAudioBar(const double micLevel); - + QString getName() const { return _name; } + + /**jsdoc + * Used to send an event to the html/js embedded in the tablet + * @function TabletProxy#emitScriptEvent + * @param msg {object|string} + */ + Q_INVOKABLE void emitScriptEvent(QVariant msg); + +signals: + /**jsdoc + * Signaled when this tablet receives an event from the html/js embedded in the tablet + * @function TabletProxy#webEventReceived + * @param msg {object|string} + * @returns {Signal} + */ + void webEventReceived(QVariant msg); + protected: void addButtonsToHomeScreen(); @@ -102,19 +119,23 @@ protected: std::mutex _mutex; std::vector> _tabletButtonProxies; QQuickItem* _qmlTabletRoot { nullptr }; + QObject* _qmlOffscreenSurface { nullptr }; }; /**jsdoc * @class TabletButtonProxy - * @property imageUrl {string} + * @property uuid {QUuid} READ_ONLY: uniquely identifies this button */ class TabletButtonProxy : public QObject { Q_OBJECT + Q_PROPERTY(QUuid uuid READ getUuid) public: TabletButtonProxy(const QVariantMap& properties); void setQmlButton(QQuickItem* qmlButton); + QUuid getUuid() const { return _uuid; } + /**jsdoc * Returns the current value of this button's properties * @function TabletButtonProxy#getProperties diff --git a/scripts/developer/tests/tabletEventBridgeTest.js b/scripts/developer/tests/tabletEventBridgeTest.js new file mode 100644 index 0000000000..1fa935bef2 --- /dev/null +++ b/scripts/developer/tests/tabletEventBridgeTest.js @@ -0,0 +1,81 @@ +// +// tabletEventBridgeTest.js +// +// Created by Anthony J. Thibault on 2016-12-15 +// 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 +// + +// Adds a button to the tablet that will switch to a web page. +// This web page contains buttons that will use the event bridge to trigger sounds. + +/* globals Tablet */ + + +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var tabletButton = tablet.addButton({ + text: "SOUNDS" +}); + +var WEB_BRIDGE_TEST_HTML = "https://s3.amazonaws.com/hifi-public/tony/webBridgeTest.html?2"; + +var TROMBONE_URL = "https://s3.amazonaws.com/hifi-public/tony/audio/sad-trombone.wav"; +var tromboneSound = SoundCache.getSound(TROMBONE_URL); +var tromboneInjector; + +var SCREAM_URL = "https://s3.amazonaws.com/hifi-public/tony/audio/wilhelm-scream.wav"; +var screamSound = SoundCache.getSound(SCREAM_URL); +var screamInjector; + +tabletButton.clicked.connect(function () { + tablet.gotoWebScreen(WEB_BRIDGE_TEST_HTML); +}); + +// hook up to the event bridge +tablet.webEventReceived.connect(function (msg) { + Script.print("HIFI: recv web event = " + JSON.stringify(msg)); + if (msg === "button-1-play") { + + // play sad trombone + if (tromboneSound.downloaded) { + if (tromboneInjector) { + tromboneInjector.restart(); + } else { + tromboneInjector = Audio.playSound(tromboneSound, { position: MyAvatar.position, + volume: 1.0, + loop: false }); + } + } + + // wait until sound is finished then send a done event + Script.setTimeout(function () { + tablet.emitScriptEvent("button-1-done"); + }, 3500); + } + + if (msg === "button-2-play") { + + // play scream + if (screamSound.downloaded) { + if (screamInjector) { + screamInjector.restart(); + } else { + screamInjector = Audio.playSound(screamSound, { position: MyAvatar.position, + volume: 1.0, + loop: false }); + } + } + + // wait until sound is finished then send a done event + Script.setTimeout(function () { + tablet.emitScriptEvent("button-2-done"); + }, 1000); + } +}); + +Script.scriptEnding.connect(function () { + tablet.removeButton(tabletButton); +}); +