diff --git a/interface/resources/qml/hifi/tablet/Tablet.qml b/interface/resources/qml/hifi/tablet/Tablet.qml index c6b38b538f..4ea1891737 100644 --- a/interface/resources/qml/hifi/tablet/Tablet.qml +++ b/interface/resources/qml/hifi/tablet/Tablet.qml @@ -3,12 +3,34 @@ import QtGraphicalEffects 1.0 Item { id: tablet + objectName: "tablet" property double miclevel: 0.8 width: 480 height: 720 + // called by C++ code when a button should be added to the tablet + function addButtonProxy(properties) { + var component = Qt.createComponent("TabletButton.qml"); + var button = component.createObject(flowMain); + if (properties.icon) { + button.icon = properties.icon; + } + if (properties.color) { + button.color = properties.color; + } + if (properties.text) { + button.text = properties.text; + } + return button; + } + + // called by C++ code when a button should be removed from the tablet + function removeButtonProxy(properties) { + console.log("TABLET_UI_HACK: removeButtonProxy, NOT IMPLEMENTED!, properties = " + JSON.stringify(properties)); + } + Rectangle { id: bgAudio height: 90 diff --git a/interface/resources/qml/hifi/tablet/TabletButton.qml b/interface/resources/qml/hifi/tablet/TabletButton.qml index 75f5423f41..92a7ee2865 100644 --- a/interface/resources/qml/hifi/tablet/TabletButton.qml +++ b/interface/resources/qml/hifi/tablet/TabletButton.qml @@ -9,6 +9,8 @@ Item { width: 132 height: 132 + signal clicked() + Rectangle { id: buttonBg color: tabletButton.color @@ -62,6 +64,7 @@ Item { MouseArea { anchors.fill: parent hoverEnabled: true + onClicked: tabletButton.clicked(); onEntered: { console.log("Tablet Button Hovered!"); tabletButton.state = "hover state"; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5eca9590dd..a4d72a3e1d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -104,6 +104,7 @@ #include #include #include +#include #include #include #include @@ -478,6 +479,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); diff --git a/interface/src/scripting/QmlWrapper.h b/interface/src/scripting/QmlWrapper.h new file mode 100644 index 0000000000..7dd319e445 --- /dev/null +++ b/interface/src/scripting/QmlWrapper.h @@ -0,0 +1,63 @@ +// +// Created by Anthony J. Thibault on 2016-12-12 +// Copyright 2013-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 +// + +#ifndef hifi_QmlWrapper_h +#define hifi_QmlWrapper_h + +#include +#include +#include + +class QmlWrapper : public QObject { + Q_OBJECT +public: + QmlWrapper(QObject* qmlObject, QObject* parent = nullptr) + : QObject(parent), _qmlObject(qmlObject) { + } + + Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([=] { + _qmlObject->setProperty(propertyName.toStdString().c_str(), propertyValue); + }); + } + + Q_INVOKABLE void writeProperties(QVariant propertyMap) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([=] { + QVariantMap map = propertyMap.toMap(); + for (const QString& key : map.keys()) { + _qmlObject->setProperty(key.toStdString().c_str(), map[key]); + } + }); + } + + Q_INVOKABLE QVariant readProperty(const QString& propertyName) { + auto offscreenUi = DependencyManager::get(); + return offscreenUi->returnFromUiThread([&]()->QVariant { + return _qmlObject->property(propertyName.toStdString().c_str()); + }); + } + + Q_INVOKABLE QVariant readProperties(const QVariant& propertyList) { + auto offscreenUi = DependencyManager::get(); + return offscreenUi->returnFromUiThread([&]()->QVariant { + QVariantMap result; + for (const QVariant& property : propertyList.toList()) { + QString propertyString = property.toString(); + result.insert(propertyString, _qmlObject->property(propertyString.toStdString().c_str())); + } + return result; + }); + } + +protected: + QObject* _qmlObject{ nullptr }; +}; + +#endif \ No newline at end of file diff --git a/interface/src/scripting/ToolbarScriptingInterface.cpp b/interface/src/scripting/ToolbarScriptingInterface.cpp index 0cb314615a..1f2228c6d9 100644 --- a/interface/src/scripting/ToolbarScriptingInterface.cpp +++ b/interface/src/scripting/ToolbarScriptingInterface.cpp @@ -8,58 +8,11 @@ #include "ToolbarScriptingInterface.h" + #include #include - -class QmlWrapper : public QObject { - Q_OBJECT -public: - QmlWrapper(QObject* qmlObject, QObject* parent = nullptr) - : QObject(parent), _qmlObject(qmlObject) { - } - - Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue) { - auto offscreenUi = DependencyManager::get(); - offscreenUi->executeOnUiThread([=] { - _qmlObject->setProperty(propertyName.toStdString().c_str(), propertyValue); - }); - } - - Q_INVOKABLE void writeProperties(QVariant propertyMap) { - auto offscreenUi = DependencyManager::get(); - offscreenUi->executeOnUiThread([=] { - QVariantMap map = propertyMap.toMap(); - for (const QString& key : map.keys()) { - _qmlObject->setProperty(key.toStdString().c_str(), map[key]); - } - }); - } - - Q_INVOKABLE QVariant readProperty(const QString& propertyName) { - auto offscreenUi = DependencyManager::get(); - return offscreenUi->returnFromUiThread([&]()->QVariant { - return _qmlObject->property(propertyName.toStdString().c_str()); - }); - } - - Q_INVOKABLE QVariant readProperties(const QVariant& propertyList) { - auto offscreenUi = DependencyManager::get(); - return offscreenUi->returnFromUiThread([&]()->QVariant { - QVariantMap result; - for (const QVariant& property : propertyList.toList()) { - QString propertyString = property.toString(); - result.insert(propertyString, _qmlObject->property(propertyString.toStdString().c_str())); - } - return result; - }); - } - - -protected: - QObject* _qmlObject{ nullptr }; -}; - +#include "QmlWrapper.h" class ToolbarButtonProxy : public QmlWrapper { Q_OBJECT diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index b48fd4263a..cba6842ba9 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "EntityTreeRenderer.h" #include "EntitiesRendererLogging.h" @@ -255,7 +256,13 @@ void RenderableWebEntityItem::loadSourceURL() { } else { _contentType = qmlContent; _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath())); - _webSurface->load(_sourceUrl, [&](QQmlContext* context, QObject* obj) { }); + _webSurface->load(_sourceUrl, [&](QQmlContext* context, QObject* obj) {}); + + // TABLET_UI_HACK: move this to overlays as well! + if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tablet") { + auto tabletScriptingInterface = DependencyManager::get(); + tabletScriptingInterface->setQmlTablet("com.highfidelity.interface.tablet.system", _webSurface->getRootItem()); + } } } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 070bc98dbc..8e8ce6a851 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -62,6 +62,7 @@ #include "WebSocketClass.h" #include "RecordingScriptingInterface.h" #include "ScriptEngines.h" +#include "TabletScriptingInterface.h" #include "MIDIEvent.h" @@ -542,6 +543,8 @@ void ScriptEngine::init() { auto recordingInterface = DependencyManager::get(); registerGlobalObject("Recording", recordingInterface.data()); + registerGlobalObject("Tablet", DependencyManager::get().data()); + registerGlobalObject("Assets", &_assetScriptingInterface); registerGlobalObject("Resources", DependencyManager::get().data()); } diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp new file mode 100644 index 0000000000..1bcdfc6013 --- /dev/null +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -0,0 +1,133 @@ +// +// Created by Bradley Austin Davis on 2016-06-16 +// Copyright 2013-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 +// + +#include "TabletScriptingInterface.h" + +#include + +QObject* TabletScriptingInterface::getTablet(const QString& tabletId) { + + std::lock_guard guard(_tabletProxiesMutex); + + // look up tabletId in the map. + auto iter = _tabletProxies.find(tabletId); + if (iter != _tabletProxies.end()) { + // tablet already exists, just return it. + return iter->second.data(); + } else { + // allocate a new tablet, add it to the map then return it. + auto tabletProxy = QSharedPointer(new TabletProxy(tabletId)); + _tabletProxies[tabletId] = tabletProxy; + return tabletProxy.data(); + } +} + +void TabletScriptingInterface::setQmlTablet(QString tabletId, QQuickItem* qmlTablet) { + TabletProxy* tablet = qobject_cast(getTablet(tabletId)); + if (tablet) { + tablet->setQmlTablet(qmlTablet); + } else { + qWarning() << "TabletScriptingInterface::setupTablet() bad tablet object"; + } +} + + +// +// TabletProxy +// + +TabletProxy::TabletProxy(QString name) : _name(name) { + ; +} + +static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) { + QVariant resultVar; + Qt::ConnectionType connectionType = Qt::AutoConnection; + if (QThread::currentThread() != qmlTablet->thread()) { + connectionType = Qt::BlockingQueuedConnection; + } + bool hasResult = QMetaObject::invokeMethod(qmlTablet, "addButtonProxy", connectionType, + Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, buttonProxy->getProperties())); + if (!hasResult) { + qWarning() << "TabletScriptingInterface addButtonProxyToQmlTablet has no result"; + return; + } + + QObject* qmlButton = qvariant_cast(resultVar); + if (!qmlButton) { + qWarning() << "TabletScriptingInterface addButtonProxyToQmlTablet result not a QObject"; + return; + } + QObject::connect(qmlButton, SIGNAL(clicked()), buttonProxy, SLOT(clickedSlot())); +} + +void TabletProxy::setQmlTablet(QQuickItem* qmlTablet) { + if (qmlTablet) { + _qmlTablet = qmlTablet; + std::lock_guard guard(_tabletButtonProxiesMutex); + for (auto& buttonProxy : _tabletButtonProxies) { + addButtonProxyToQmlTablet(_qmlTablet, buttonProxy.data()); + } + } else { + _qmlTablet = nullptr; + } + +} + +QObject* TabletProxy::addButton(const QVariant& properties) { + auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); + std::lock_guard guard(_tabletButtonProxiesMutex); + _tabletButtonProxies.push_back(tabletButtonProxy); + if (_qmlTablet) { + addButtonProxyToQmlTablet(_qmlTablet, tabletButtonProxy.data()); + } + return tabletButtonProxy.data(); +} + +void TabletProxy::removeButton(QObject* tabletButtonProxy) { + std::lock_guard guard(_tabletButtonProxiesMutex); + auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy); + if (iter != _tabletButtonProxies.end()) { + if (_qmlTablet) { + QMetaObject::invokeMethod(_qmlTablet, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getProperties())); + } + _tabletButtonProxies.erase(iter); + } else { + qWarning() << "TabletProxy::removeButton() could not find button " << tabletButtonProxy; + } +} + +// +// TabletButtonProxy +// + +TabletButtonProxy::TabletButtonProxy(const QVariantMap& properties) : _properties(properties) { + ; +} + +void TabletButtonProxy::setInitRequestHandler(const QScriptValue& handler) { + _initRequestHandler = handler; +} + +// TABLET_UI_HACK remove +/* +static QString IMAGE_URL_KEY = "imageUrl"; +static QString IMAGE_URL_DEFAULT = ""; + +QString TabletButtonProxy::getImageUrl() const { + std::lock_guard guard(_propertiesMutex); + return _properties.value(IMAGE_URL_KEY, IMAGE_URL_DEFAULT).toString(); +} + +void TabletButtonProxy::setImageUrl(QString imageUrl) { + std::lock_guard guard(_propertiesMutex); + _properties[IMAGE_URL_KEY] = imageUrl; +} +*/ + +#include "TabletScriptingInterface.moc" diff --git a/libraries/script-engine/src/TabletScriptingInterface.h b/libraries/script-engine/src/TabletScriptingInterface.h new file mode 100644 index 0000000000..13d7219306 --- /dev/null +++ b/libraries/script-engine/src/TabletScriptingInterface.h @@ -0,0 +1,115 @@ +// +// Created by Anthony J. Thibault on 2016-12-12 +// Copyright 2013-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 +// + +#ifndef hifi_TabletScriptingInterface_h +#define hifi_TabletScriptingInterface_h + +#include + +#include +#include +#include +#include + +#include + + +class TabletProxy; +class TabletButtonProxy; + +/**jsdoc + * @namespace Tablet + */ +class TabletScriptingInterface : public QObject, public Dependency { + Q_OBJECT +public: + /**jsdoc + * Creates or retruns a new TabletProxy and returns it. + * @function Tablet.getTablet + * @param name {String} tablet name + * @return {TabletProxy} tablet instance + */ + Q_INVOKABLE QObject* getTablet(const QString& tabletId); + + void setQmlTablet(QString tabletId, QQuickItem* qmlTablet); + +protected: + std::mutex _tabletProxiesMutex; + std::map> _tabletProxies; +}; + +/**jsdoc + * @class TabletProxy + * @property name {string} name of this tablet + */ +class TabletProxy : public QObject { + Q_OBJECT + Q_PROPERTY(QString name READ getName) +public: + TabletProxy(QString name); + + void setQmlTablet(QQuickItem* qmlTablet); + + /**jsdoc + * @function TabletProxy#addButton + * Creates a new button, adds it to this and returns it. + * @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 + * @param tabletButtonProxy {TabletButtonProxy} button to be removed + */ + Q_INVOKABLE void removeButton(QObject* tabletButtonProxy); + + QString getName() const { return _name; } +protected: + QString _name; + std::mutex _tabletButtonProxiesMutex; + std::vector> _tabletButtonProxies; + QQuickItem* _qmlTablet { nullptr }; +}; + +/**jsdoc + * @class TabletButtonProxy + * @property imageUrl {string} + */ +class TabletButtonProxy : public QObject { + Q_OBJECT +public: + TabletButtonProxy(const QVariantMap& properties); + + /**jsdoc + * @function TabletButtonProxy#setInitRequestHandler + * @param handler {Function} A function used by the system to request the current button state from JavaScript. + */ + Q_INVOKABLE void setInitRequestHandler(const QScriptValue& handler); + + const QVariantMap& getProperties() const { return _properties; } + +public slots: + void clickedSlot() { emit clicked(); } + +signals: + /**jsdoc + * Signaled when this button has been clicked on by the user. + * @function TabletButtonProxy#clicked + * @returns {Signal} + */ + void clicked(); + +protected: + mutable std::mutex _propertiesMutex; + QVariantMap _properties; + QScriptValue _initRequestHandler; +}; + +#endif // hifi_TabletScriptingInterface_h diff --git a/scripts/system/help.js b/scripts/system/help.js index e79ed0444c..a577688032 100644 --- a/scripts/system/help.js +++ b/scripts/system/help.js @@ -13,20 +13,14 @@ (function() { // BEGIN LOCAL_SCOPE - var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); - var buttonName = "help"; // matching location reserved in Desktop.qml - var button = toolBar.addButton({ - objectName: buttonName, - imageURL: Script.resolvePath("assets/images/tools/help.svg"), - visible: true, - hoverState: 2, - defaultState: 1, - buttonState: 1, - alpha: 0.9 + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + //icon: "help.svg", + color: "#ff6f6f", + text: "HELP" }); // TODO: make button state reflect whether the window is opened or closed (independently from us). - function onClicked(){ Menu.triggerOption('Help...') } @@ -34,7 +28,7 @@ button.clicked.connect(onClicked); Script.scriptEnding.connect(function () { - toolBar.removeButton(buttonName); + tablet.removeButton(buttonName); button.clicked.disconnect(onClicked); });