diff --git a/.eslintrc.js b/.eslintrc.js index 6183fa8aec..c708decc51 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -40,6 +40,7 @@ module.exports = { "Settings": false, "SoundCache": false, "Stats": false, + "Tablet": false, "TextureCache": false, "Toolbars": false, "Uuid": false, @@ -61,7 +62,7 @@ module.exports = { "eqeqeq": ["error", "always"], "indent": ["error", 4, { "SwitchCase": 1 }], "keyword-spacing": ["error", { "before": true, "after": true }], - "max-len": ["error", 128, 4], + "max-len": ["error", 192, 4], "new-cap": ["error"], "no-floating-decimal": ["error"], //"no-magic-numbers": ["error", { "ignore": [0, 1], "ignoreArrayIndexes": true }], diff --git a/interface/resources/icons/tablet-icons/blank.svg b/interface/resources/icons/tablet-icons/blank.svg new file mode 100644 index 0000000000..ae463c4242 --- /dev/null +++ b/interface/resources/icons/tablet-icons/blank.svg @@ -0,0 +1,48 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/empty-toolbar-button.svg b/interface/resources/icons/tablet-icons/empty-toolbar-button.svg new file mode 100644 index 0000000000..19791e6c29 --- /dev/null +++ b/interface/resources/icons/tablet-icons/empty-toolbar-button.svg @@ -0,0 +1,81 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/finger-paint-a.svg b/interface/resources/icons/tablet-icons/finger-paint-a.svg new file mode 100644 index 0000000000..acc93608d9 --- /dev/null +++ b/interface/resources/icons/tablet-icons/finger-paint-a.svg @@ -0,0 +1,66 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/finger-paint-i.svg b/interface/resources/icons/tablet-icons/finger-paint-i.svg new file mode 100644 index 0000000000..b295727d8a --- /dev/null +++ b/interface/resources/icons/tablet-icons/finger-paint-i.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 4c81027211..3e6e5b6764 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -48,7 +48,16 @@ OriginalDesktop.Desktop { // This used to create sysToolbar dynamically with a call to getToolbar() within onCompleted. // Beginning with QT 5.6, this stopped working, as anything added to toolbars too early got // wiped during startup. - + Toolbar { + id: sysToolbar; + objectName: "com.highfidelity.interface.toolbar.system"; + anchors.horizontalCenter: settings.constrainToolbarToCenterX ? desktop.horizontalCenter : undefined; + // Literal 50 is overwritten by settings from previous session, and sysToolbar.x comes from settings when not constrained. + x: sysToolbar.x + y: 50 + shown: false + } + Settings { id: settings; category: "toolbar"; @@ -58,8 +67,9 @@ OriginalDesktop.Desktop { settings.constrainToolbarToCenterX = constrain; } property var toolbars: (function (map) { // answer dictionary preloaded with sysToolbar - return map; })({}); - + map[sysToolbar.objectName] = sysToolbar; + return map; + })({}); Component.onCompleted: { WebEngine.settings.javascriptCanOpenWindows = true; diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index c154ac0f49..e0deab64b6 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -97,10 +97,12 @@ FocusScope { menuPopperUpper.closeLastMenu(); } - function setRootMenu(menu) { - tabletMenu.rootMenu = menu + function setRootMenu(rootMenu, subMenu) { + tabletMenu.subMenu = subMenu; + tabletMenu.rootMenu = rootMenu; buildMenu() } + function buildMenu() { // Build submenu if specified. if (subMenu !== "") { diff --git a/interface/resources/qml/hifi/tablet/TabletMenuView.qml b/interface/resources/qml/hifi/tablet/TabletMenuView.qml index 1845396230..92e7f59524 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuView.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuView.qml @@ -83,7 +83,7 @@ FocusScope { } function recalcSize() { - if (model.count !== count || !visible) { + if (!model || model.count !== count || !visible) { return; } diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 481c7846a9..1fb31e5619 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -6,7 +6,9 @@ Item { objectName: "tabletRoot" property string username: "Unknown user" property var eventBridge; - property string option: "" + + property var rootMenu; + property string subMenu: "" signal showDesktop(); @@ -14,7 +16,13 @@ Item { option = value; } + function setMenuProperties(rootMenu, subMenu) { + tabletRoot.rootMenu = rootMenu; + tabletRoot.subMenu = subMenu; + } + function loadSource(url) { + loader.source = ""; // make sure we load the qml fresh each time. loader.source = url; } @@ -77,13 +85,15 @@ Item { if (loader.item.hasOwnProperty("sendToScript")) { loader.item.sendToScript.connect(tabletRoot.sendToScript); } - if (loader.item.hasOwnProperty("subMenu")) { - loader.item.subMenu = option; + if (loader.item.hasOwnProperty("setRootMenu")) { + loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu); } loader.item.forceActiveFocus(); } } width: 480 - height: 720 + height: 706 + + function setShown(value) {} } diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml new file mode 100644 index 0000000000..5f842df7b7 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -0,0 +1,111 @@ +// +// WindowRoot.qml +// +// Created by Anthony Thibault on 14 Feb 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 +// +// This qml is used when tablet content is shown on the 2d overlay ui +// TODO: FIXME: this is practically identical to TabletRoot.qml + +import "../../windows" as Windows +import QtQuick 2.0 +import Hifi 1.0 + +Windows.ScrollingWindow { + id: tabletRoot + objectName: "tabletRoot" + property string username: "Unknown user" + property var eventBridge; + + property var rootMenu; + property string subMenu: "" + + shown: false + resizable: false + + signal showDesktop(); + + function setMenuProperties(rootMenu, subMenu) { + tabletRoot.rootMenu = rootMenu; + tabletRoot.subMenu = subMenu; + } + + function loadSource(url) { + loader.source = ""; // make sure we load the qml fresh each time. + loader.source = url; + } + + function loadWebUrl(url, injectedJavaScriptUrl) { + loader.item.url = url; + loader.item.scriptURL = injectedJavaScriptUrl; + } + + // used to send a message from qml to interface script. + signal sendToScript(var message); + + // used to receive messages from interface script + function fromScript(message) { + if (loader.item.hasOwnProperty("fromScript")) { + loader.item.fromScript(message); + } + } + + SoundEffect { + id: buttonClickSound + volume: 0.1 + source: "../../../sounds/Gamemaster-Audio-button-click.wav" + } + + function playButtonClickSound() { + // Because of the asynchronous nature of initalization, it is possible for this function to be + // called before the C++ has set the globalPosition context variable. + if (typeof globalPosition !== 'undefined') { + buttonClickSound.play(globalPosition); + } + } + + function toggleMicEnabled() { + ApplicationInterface.toggleMuteAudio(); + } + + function setUsername(newUsername) { + username = newUsername; + } + + Loader { + id: loader + objectName: "loader" + asynchronous: false + + height: pane.scrollHeight + width: pane.contentWidth + anchors.left: parent.left + anchors.top: parent.top + + onLoaded: { + if (loader.item.hasOwnProperty("eventBridge")) { + loader.item.eventBridge = eventBridge; + + // Hook up callback for clara.io download from the marketplace. + eventBridge.webEventReceived.connect(function (event) { + if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") { + ApplicationInterface.addAssetToWorldFromURL(event.slice(18)); + } + }); + } + if (loader.item.hasOwnProperty("sendToScript")) { + loader.item.sendToScript.connect(tabletRoot.sendToScript); + } + if (loader.item.hasOwnProperty("setRootMenu")) { + loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu); + } + loader.item.forceActiveFocus(); + } + } + + implicitWidth: 480 + implicitHeight: 706 +} diff --git a/interface/resources/qml/hifi/toolbars/StateImage.qml b/interface/resources/qml/hifi/toolbars/StateImage.qml index ee0778626d..e0389c5e02 100644 --- a/interface/resources/qml/hifi/toolbars/StateImage.qml +++ b/interface/resources/qml/hifi/toolbars/StateImage.qml @@ -29,6 +29,7 @@ Item { id: image y: -parent.yOffset; width: parent.width + source: "../../../icons/tablet-icons/empty-toolbar-button.svg" } } diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml index 01ce74cf6e..0080e49815 100644 --- a/interface/resources/qml/hifi/toolbars/Toolbar.qml +++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml @@ -25,7 +25,7 @@ Window { property real buttonSize: 50; property var buttons: [] property var container: horizontal ? row : column - + Settings { category: "toolbar/" + window.objectName property alias x: window.x @@ -49,6 +49,7 @@ Window { id: content implicitHeight: horizontal ? row.height : column.height implicitWidth: horizontal ? row.width : column.width + property bool wasVisibleBeforeBeingPinned: false Row { id: row @@ -65,19 +66,11 @@ Window { Connections { target: desktop onPinnedChanged: { - if (!window.pinned) { - return; - } - var newPinned = desktop.pinned; - for (var i in buttons) { - var child = buttons[i]; - if (desktop.pinned) { - if (!child.pinned) { - child.visible = false; - } - } else { - child.visible = true; - } + if (desktop.pinned) { + content.wasVisibleBeforeBeingPinned = window.visible; + window.visible = false; + } else { + window.visible = content.wasVisibleBeforeBeingPinned; } } } @@ -106,6 +99,24 @@ Window { return buttons[index]; } + function sortButtons() { + var children = []; + for (var i = 0; i < container.children.length; i++) { + children[i] = container.children[i]; + } + + children.sort(function (a, b) { + if (a.sortOrder === b.sortOrder) { + // subsort by stableOrder, because JS sort is not stable in qml. + return a.stableOrder - b.stableOrder; + } else { + return a.sortOrder - b.sortOrder; + } + }); + + container.children = children; + } + function addButton(properties) { properties = properties || {} @@ -123,8 +134,12 @@ Window { properties.opacity = 0; result = toolbarButtonBuilder.createObject(container, properties); buttons.push(result); + result.opacity = 1; updatePinned(); + + sortButtons(); + return result; } @@ -137,6 +152,10 @@ Window { buttons[index].destroy(); buttons.splice(index, 1); updatePinned(); + + if (buttons.length === 0) { + visible = false; + } } function updatePinned() { diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index 91c992bf0d..cab5b14d5c 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -11,12 +11,33 @@ StateImage { property int imageOnOut: 0 property int imageOnIn: 2 + property string text: "" + property string hoverText: button.text + property string activeText: button.text + property string activeHoverText: button.activeText + + property string icon: "icons/tablet-icons/blank.svg" + property string hoverIcon: button.icon + property string activeIcon: button.icon + property string activeHoverIcon: button.activeIcon + + property int sortOrder: 100 + property int stableSortOrder: 0 + signal clicked() function changeProperty(key, value) { button[key] = value; } + function urlHelper(src) { + if (src.match(/\bhttp/)) { + return src; + } else { + return "../../../" + src; + } + } + function updateState() { if (!button.isEntered && !button.isActive) { buttonState = imageOffOut; @@ -38,7 +59,7 @@ StateImage { running: false onTriggered: button.clicked(); } - + MouseArea { id: mouseArea hoverEnabled: true @@ -53,5 +74,28 @@ StateImage { updateState(); } } + + Image { + id: icon + width: 28 + height: 28 + anchors.bottom: caption.top + anchors.bottomMargin: 0 + anchors.horizontalCenter: parent.horizontalCenter + fillMode: Image.Stretch + source: urlHelper(button.isActive ? (button.isEntered ? button.activeHoverIcon : button.activeIcon) : (button.isEntered ? button.hoverIcon : button.icon)) + } + + Text { + id: caption + color: button.isActive ? "#000000" : "#ffffff" + text: button.isActive ? (button.isEntered ? button.activeHoverText : button.activeText) : (button.isEntered ? button.hoverText : button.text) + font.bold: false + font.pixelSize: 9 + anchors.bottom: parent.bottom + anchors.bottomMargin: 5 + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + } } diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index d22d8ecbe8..20216ed7ae 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -85,6 +85,10 @@ Fadable { function setDefaultFocus() {} // Default function; can be overridden by dialogs. + function setShown(value) { + window.shown = value; + } + property var rectifier: Timer { property bool executing: false; interval: 100 diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 488e97b5e6..2aab31ca71 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -545,6 +545,8 @@ Setting::Handle sessionRunTime{ "sessionRunTime", 0 }; const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 100.0f; const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f; +const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true; +const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false; Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) : QApplication(argc, argv), @@ -565,6 +567,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _fieldOfView("fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES), _hmdTabletScale("hmdTabletScale", DEFAULT_HMD_TABLET_SCALE_PERCENT), _desktopTabletScale("desktopTabletScale", DEFAULT_DESKTOP_TABLET_SCALE_PERCENT), + _desktopTabletBecomesToolbarSetting("desktopTabletBecomesToolbar", DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR), + _hmdTabletBecomesToolbarSetting("hmdTabletBecomesToolbar", DEFAULT_HMD_TABLET_BECOMES_TOOLBAR), _constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true), _scaleMirror(1.0f), _rotateMirror(0.0f), @@ -831,6 +835,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress); connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateThreadPoolCount); + connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateSystemTabletMode); // Save avatar location immediately after a teleport. connect(myAvatar.get(), &MyAvatar::positionGoneTo, @@ -1537,6 +1542,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(this, &QCoreApplication::aboutToQuit, this, &Application::addAssetToWorldMessageClose); connect(&domainHandler, &DomainHandler::hostnameChanged, this, &Application::addAssetToWorldMessageClose); + + updateSystemTabletMode(); } void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) { @@ -2330,6 +2337,16 @@ void Application::setDesktopTabletScale(float desktopTabletScale) { _desktopTabletScale.set(desktopTabletScale); } +void Application::setDesktopTabletBecomesToolbarSetting(bool value) { + _desktopTabletBecomesToolbarSetting.set(value); + updateSystemTabletMode(); +} + +void Application::setHmdTabletBecomesToolbarSetting(bool value) { + _hmdTabletBecomesToolbarSetting.set(value); + updateSystemTabletMode(); +} + void Application::setSettingConstrainToolbarPosition(bool setting) { _constrainToolbarPosition.set(setting); DependencyManager::get()->setConstrainToolbarToCenterX(setting); @@ -5462,6 +5479,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("Desktop", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get().data()); + DependencyManager::get().data()->setToolbarScriptingInterface(DependencyManager::get().data()); + scriptEngine->registerGlobalObject("Window", DependencyManager::get().data()); qScriptRegisterMetaType(scriptEngine, CustomPromptResultToScriptValue, CustomPromptResultFromScriptValue); scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, @@ -6679,6 +6698,12 @@ void Application::updateDisplayMode() { } emit activeDisplayPluginChanged(); + + if (_displayPlugin->isHmd()) { + qCDebug(interfaceapp) << "Entering into HMD Mode"; + } else { + qCDebug(interfaceapp) << "Entering into Desktop Mode"; + } // reset the avatar, to set head and hand palms back to a reasonable default pose. getMyAvatar()->reset(false); @@ -6854,6 +6879,14 @@ void Application::updateThreadPoolCount() const { QThreadPool::globalInstance()->setMaxThreadCount(threadPoolSize); } +void Application::updateSystemTabletMode() { + if (isHMDMode()) { + DependencyManager::get()->setToolbarMode(getHmdTabletBecomesToolbarSetting()); + } else { + DependencyManager::get()->setToolbarMode(getDesktopTabletBecomesToolbarSetting()); + } +} + void Application::toggleMuteAudio() { auto menu = Menu::getInstance(); menu->setIsOptionChecked(MenuOption::MuteAudio, !menu->isOptionChecked(MenuOption::MuteAudio)); diff --git a/interface/src/Application.h b/interface/src/Application.h index cab830ec88..5fc79bedb5 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -214,6 +214,11 @@ public: float getDesktopTabletScale() { return _desktopTabletScale.get(); } void setDesktopTabletScale(float desktopTabletScale); + bool getDesktopTabletBecomesToolbarSetting() { return _desktopTabletBecomesToolbarSetting.get(); } + void setDesktopTabletBecomesToolbarSetting(bool value); + bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); } + void setHmdTabletBecomesToolbarSetting(bool value); + float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); } void setSettingConstrainToolbarPosition(bool setting); @@ -310,6 +315,7 @@ public slots: bool exportEntities(const QString& filename, float x, float y, float z, float scale); bool importEntities(const QString& url); void updateThreadPoolCount() const; + void updateSystemTabletMode(); static void setLowVelocityFilter(bool lowVelocityFilter); Q_INVOKABLE void loadDialog(); @@ -550,6 +556,8 @@ private: Setting::Handle _fieldOfView; Setting::Handle _hmdTabletScale; Setting::Handle _desktopTabletScale; + Setting::Handle _desktopTabletBecomesToolbarSetting; + Setting::Handle _hmdTabletBecomesToolbarSetting; Setting::Handle _constrainToolbarPosition; float _scaleMirror; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 6377cda281..dd05d5c0e1 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -92,6 +92,16 @@ void setupPreferences() { preference->setMax(500); preferences->addPreference(preference); } + { + auto getter = []()->bool { return qApp->getDesktopTabletBecomesToolbarSetting(); }; + auto setter = [](bool value) { qApp->setDesktopTabletBecomesToolbarSetting(value); }; + preferences->addPreference(new CheckPreference(UI_CATEGORY, "Desktop Tablet Becomes Toolbar", getter, setter)); + } + { + auto getter = []()->bool { return qApp->getHmdTabletBecomesToolbarSetting(); }; + auto setter = [](bool value) { qApp->setHmdTabletBecomesToolbarSetting(value); }; + preferences->addPreference(new CheckPreference(UI_CATEGORY, "HMD Tablet Becomes Toolbar", getter, setter)); + } // Snapshots static const QString SNAPSHOTS { "Snapshots" }; diff --git a/libraries/script-engine/src/SoundEffect.cpp b/libraries/script-engine/src/SoundEffect.cpp index 1c78ae84bf..bfc0ad2100 100644 --- a/libraries/script-engine/src/SoundEffect.cpp +++ b/libraries/script-engine/src/SoundEffect.cpp @@ -5,9 +5,6 @@ #include SoundEffect::~SoundEffect() { - if (_sound) { - _sound->deleteLater(); - } if (_injector) { // stop will cause the AudioInjector to delete itself. _injector->stop(); diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index 7e8fdd6bc3..c78ce251c8 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -11,17 +11,36 @@ #include #include +#include "DependencyManager.h" #include +#include +#include #include #include "ScriptEngineLogging.h" -#include "DependencyManager.h" -#include "OffscreenUi.h" +#include +#include #include "SoundEffect.h" TabletScriptingInterface::TabletScriptingInterface() { qmlRegisterType("Hifi", 1, 0, "SoundEffect"); } +QObject* TabletScriptingInterface::getSystemToolbarProxy() { + const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system"; + Qt::ConnectionType connectionType = Qt::AutoConnection; + if (QThread::currentThread() != _toolbarScriptingInterface->thread()) { + connectionType = Qt::BlockingQueuedConnection; + } + QObject* toolbarProxy = nullptr; + bool hasResult = QMetaObject::invokeMethod(_toolbarScriptingInterface, "getToolbar", connectionType, Q_RETURN_ARG(QObject*, toolbarProxy), Q_ARG(QString, SYSTEM_TOOLBAR)); + if (hasResult) { + return toolbarProxy; + } else { + qCWarning(scriptengine) << "ToolbarScriptingInterface getToolbar has no result"; + return nullptr; + } +} + QObject* TabletScriptingInterface::getTablet(const QString& tabletId) { std::lock_guard guard(_mutex); @@ -35,10 +54,21 @@ QObject* TabletScriptingInterface::getTablet(const QString& tabletId) { // allocate a new tablet, add it to the map then return it. auto tabletProxy = QSharedPointer(new TabletProxy(tabletId)); _tabletProxies[tabletId] = tabletProxy; + tabletProxy->setToolbarMode(_toolbarMode); return tabletProxy.data(); } } +void TabletScriptingInterface::setToolbarMode(bool toolbarMode) { + std::lock_guard guard(_mutex); + + _toolbarMode = toolbarMode; + + for (auto& iter : _tabletProxies) { + iter.second->setToolbarMode(toolbarMode); + } +} + void TabletScriptingInterface::setQmlTabletRoot(QString tabletId, QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface) { TabletProxy* tablet = qobject_cast(getTablet(tabletId)); if (tablet) { @@ -141,8 +171,51 @@ static const char* TABLET_SOURCE_URL = "Tablet.qml"; static const char* WEB_VIEW_SOURCE_URL = "TabletWebView.qml"; static const char* VRMENU_SOURCE_URL = "TabletMenu.qml"; +class TabletRootWindow : public QmlWindowClass { + virtual QString qmlSource() const { return "hifi/tablet/WindowRoot.qml"; } +}; + TabletProxy::TabletProxy(QString name) : _name(name) { - ; + +} + +void TabletProxy::setToolbarMode(bool toolbarMode) { + if (toolbarMode == _toolbarMode) { + return; + } + + _toolbarMode = toolbarMode; + + if (toolbarMode) { + removeButtonsFromHomeScreen(); + addButtonsToToolbar(); + + // create new desktop window + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([=] { + auto tabletRootWindow = new TabletRootWindow(); + tabletRootWindow->initQml(QVariantMap()); + auto quickItem = tabletRootWindow->asQuickItem(); + _desktopWindow = tabletRootWindow; + QMetaObject::invokeMethod(quickItem, "setShown", Q_ARG(const QVariant&, QVariant(false))); + + QObject::connect(quickItem, SIGNAL(windowClosed()), this, SLOT(desktopWindowClosed())); + + QObject::connect(tabletRootWindow, SIGNAL(webEventReceived(QVariant)), this, SIGNAL(webEventReceived(QVariant))); + + // forward qml surface events to interface js + connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml); + }); + } else { + removeButtonsFromToolbar(); + addButtonsToHomeScreen(); + + // destroy desktop window + if (_desktopWindow) { + _desktopWindow->deleteLater(); + _desktopWindow = nullptr; + } + } } static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) { @@ -195,6 +268,13 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr } }); + if (_toolbarMode) { + // if someone creates the tablet in toolbar mode, make sure to display the home screen on the tablet. + auto loader = _qmlTabletRoot->findChild("loader"); + QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()), Qt::DirectConnection); + QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL))); + } + gotoHomeScreen(); QMetaObject::invokeMethod(_qmlTabletRoot, "setUsername", Q_ARG(const QVariant&, QVariant(getUsername()))); @@ -214,39 +294,61 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr } void TabletProxy::gotoMenuScreen(const QString& submenu) { - if (_qmlTabletRoot) { - if (_state != State::Menu) { - removeButtonsFromHomeScreen(); - QMetaObject::invokeMethod(_qmlTabletRoot, "setOption", Q_ARG(const QVariant&, QVariant(submenu))); - auto loader = _qmlTabletRoot->findChild("loader"); - QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToMenuScreen()), Qt::DirectConnection); - QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL))); - _state = State::Menu; - emit screenChanged(QVariant("Menu"), QVariant(VRMENU_SOURCE_URL)); - } + + QObject* root = nullptr; + if (!_toolbarMode && _qmlTabletRoot) { + root = _qmlTabletRoot; + } else if (_toolbarMode && _desktopWindow) { + root = _desktopWindow->asQuickItem(); + } + + if (root) { + removeButtonsFromHomeScreen(); + auto offscreenUi = DependencyManager::get(); + QObject* menu = offscreenUi->getRootMenu(); + QMetaObject::invokeMethod(root, "setMenuProperties", Q_ARG(QVariant, QVariant::fromValue(menu)), Q_ARG(const QVariant&, QVariant(submenu))); + QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL))); + _state = State::Menu; + emit screenChanged(QVariant("Menu"), QVariant(VRMENU_SOURCE_URL)); + QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); } } void TabletProxy::loadQMLSource(const QVariant& path) { - if (_qmlTabletRoot) { + + QObject* root = nullptr; + if (!_toolbarMode && _qmlTabletRoot) { + root = _qmlTabletRoot; + } else if (_toolbarMode && _desktopWindow) { + root = _desktopWindow->asQuickItem(); + } + + if (root) { if (_state != State::QML) { removeButtonsFromHomeScreen(); - QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, path)); + QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path)); _state = State::QML; emit screenChanged(QVariant("QML"), path); + QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); } } } + void TabletProxy::gotoHomeScreen() { - if (_qmlTabletRoot) { - if (_state != State::Home) { + if (_state != State::Home) { + if (!_toolbarMode && _qmlTabletRoot) { auto loader = _qmlTabletRoot->findChild("loader"); QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()), Qt::DirectConnection); QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL))); QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound"); - _state = State::Home; - emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL)); + } else if (_toolbarMode && _desktopWindow) { + // close desktop window + if (_desktopWindow->asQuickItem()) { + QMetaObject::invokeMethod(_desktopWindow->asQuickItem(), "setShown", Q_ARG(const QVariant&, QVariant(false))); + } } + _state = State::Home; + emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL)); } } @@ -255,31 +357,52 @@ void TabletProxy::gotoWebScreen(const QString& url) { } void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl) { - if (_qmlTabletRoot) { - if (_state == State::Home) { - removeButtonsFromHomeScreen(); - } - if (_state != State::Web) { - QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL))); - _state = State::Web; - emit screenChanged(QVariant("Web"), QVariant(url)); - } - QMetaObject::invokeMethod(_qmlTabletRoot, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), - Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); + + QObject* root = nullptr; + if (!_toolbarMode && _qmlTabletRoot) { + root = _qmlTabletRoot; + } else if (_toolbarMode && _desktopWindow) { + root = _desktopWindow->asQuickItem(); } + + if (root) { + QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL))); + QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); + QMetaObject::invokeMethod(root, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); + } + _state = State::Web; + emit screenChanged(QVariant("Web"), QVariant(url)); } QObject* TabletProxy::addButton(const QVariant& properties) { auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); std::lock_guard guard(_mutex); _tabletButtonProxies.push_back(tabletButtonProxy); - if (_qmlTabletRoot) { + if (!_toolbarMode && _qmlTabletRoot) { auto tablet = getQmlTablet(); if (tablet) { addButtonProxyToQmlTablet(tablet, tabletButtonProxy.data()); } else { qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml"; } + } else if (_toolbarMode) { + + auto tabletScriptingInterface = DependencyManager::get(); + QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); + + Qt::ConnectionType connectionType = Qt::AutoConnection; + if (QThread::currentThread() != toolbarProxy->thread()) { + connectionType = Qt::BlockingQueuedConnection; + } + + // copy properties from tablet button proxy to toolbar button proxy. + QObject* toolbarButtonProxy = nullptr; + bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, tabletButtonProxy->getProperties())); + if (hasResult) { + tabletButtonProxy->setToolbarButtonProxy(toolbarButtonProxy); + } else { + qCWarning(scriptengine) << "ToolbarProxy addButton has no result"; + } } return tabletButtonProxy.data(); } @@ -298,11 +421,18 @@ void TabletProxy::removeButton(QObject* tabletButtonProxy) { auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy); if (iter != _tabletButtonProxies.end()) { - if (_qmlTabletRoot) { + if (!_toolbarMode && _qmlTabletRoot) { (*iter)->setQmlButton(nullptr); if (tablet) { QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getProperties())); } + } else if (_toolbarMode) { + auto tabletScriptingInterface = DependencyManager::get(); + QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); + + // remove button from toolbarProxy + QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getUuid().toString())); + (*iter)->setToolbarButtonProxy(nullptr); } _tabletButtonProxies.erase(iter); } else { @@ -329,20 +459,24 @@ void TabletProxy::updateAudioBar(const double micLevel) { } void TabletProxy::emitScriptEvent(QVariant msg) { - if (_qmlOffscreenSurface) { + if (!_toolbarMode && _qmlOffscreenSurface) { QMetaObject::invokeMethod(_qmlOffscreenSurface, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg)); + } else if (_toolbarMode && _desktopWindow) { + QMetaObject::invokeMethod(_desktopWindow, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg)); } } void TabletProxy::sendToQml(QVariant msg) { - if (_qmlOffscreenSurface) { + if (!_toolbarMode && _qmlOffscreenSurface) { QMetaObject::invokeMethod(_qmlOffscreenSurface, "sendToQml", Qt::AutoConnection, Q_ARG(QVariant, msg)); + } else if (_toolbarMode && _desktopWindow) { + QMetaObject::invokeMethod(_desktopWindow, "sendToQml", Qt::AutoConnection, Q_ARG(QVariant, msg)); } } void TabletProxy::addButtonsToHomeScreen() { auto tablet = getQmlTablet(); - if (!tablet) { + if (!tablet || _toolbarMode) { return; } @@ -358,30 +492,51 @@ QObject* TabletProxy::getTabletSurface() { return _qmlOffscreenSurface; } -void TabletProxy::addButtonsToMenuScreen() { - if (!_qmlTabletRoot) { - return; +void TabletProxy::removeButtonsFromHomeScreen() { + auto tablet = getQmlTablet(); + for (auto& buttonProxy : _tabletButtonProxies) { + if (tablet) { + QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties())); + } + buttonProxy->setQmlButton(nullptr); } - - auto loader = _qmlTabletRoot->findChild("loader"); - if (!loader) { - return; - } - - QQuickItem* VrMenu = loader->findChild("tabletMenu"); - if (VrMenu) { - auto offscreenUi = DependencyManager::get(); - QObject* menu = offscreenUi->getRootMenu(); - QMetaObject::invokeMethod(VrMenu, "setRootMenu", Qt::AutoConnection, Q_ARG(QVariant, QVariant::fromValue(menu))); - } - - QObject::disconnect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToMenuScreen())); } -void TabletProxy::removeButtonsFromHomeScreen() { +void TabletProxy::desktopWindowClosed() { + gotoHomeScreen(); +} + +void TabletProxy::addButtonsToToolbar() { auto tabletScriptingInterface = DependencyManager::get(); + QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); + + Qt::ConnectionType connectionType = Qt::AutoConnection; + if (QThread::currentThread() != toolbarProxy->thread()) { + connectionType = Qt::BlockingQueuedConnection; + } + for (auto& buttonProxy : _tabletButtonProxies) { - buttonProxy->setQmlButton(nullptr); + // copy properties from tablet button proxy to toolbar button proxy. + QObject* toolbarButtonProxy = nullptr; + bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, buttonProxy->getProperties())); + if (hasResult) { + buttonProxy->setToolbarButtonProxy(toolbarButtonProxy); + } else { + qCWarning(scriptengine) << "ToolbarProxy addButton has no result"; + } + } + + // make the toolbar visible + QMetaObject::invokeMethod(toolbarProxy, "writeProperty", Qt::AutoConnection, Q_ARG(QString, "visible"), Q_ARG(QVariant, QVariant(true))); +} + +void TabletProxy::removeButtonsFromToolbar() { + auto tabletScriptingInterface = DependencyManager::get(); + QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); + for (auto& buttonProxy : _tabletButtonProxies) { + // remove button from toolbarProxy + QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getUuid().toString())); + buttonProxy->setToolbarButtonProxy(nullptr); } } @@ -430,12 +585,14 @@ QQuickItem* TabletProxy::getQmlMenu() const { // const QString UUID_KEY = "uuid"; +const QString OBJECT_NAME_KEY = "objectName"; const QString STABLE_ORDER_KEY = "stableOrder"; static int s_stableOrder = 1; TabletButtonProxy::TabletButtonProxy(const QVariantMap& properties) : _uuid(QUuid::createUuid()), _stableOrder(++s_stableOrder), _properties(properties) { // this is used to uniquely identify this button. _properties[UUID_KEY] = _uuid; + _properties[OBJECT_NAME_KEY] = _uuid.toString(); _properties[STABLE_ORDER_KEY] = _stableOrder; } @@ -444,6 +601,14 @@ void TabletButtonProxy::setQmlButton(QQuickItem* qmlButton) { _qmlButton = qmlButton; } +void TabletButtonProxy::setToolbarButtonProxy(QObject* toolbarButtonProxy) { + std::lock_guard guard(_mutex); + _toolbarButtonProxy = toolbarButtonProxy; + if (_toolbarButtonProxy) { + QObject::connect(_toolbarButtonProxy, SIGNAL(clicked()), this, SLOT(clickedSlot())); + } +} + QVariantMap TabletButtonProxy::getProperties() const { std::lock_guard guard(_mutex); return _properties; @@ -451,6 +616,7 @@ QVariantMap TabletButtonProxy::getProperties() const { void TabletButtonProxy::editProperties(QVariantMap properties) { std::lock_guard guard(_mutex); + QVariantMap::const_iterator iter = properties.constBegin(); while (iter != properties.constEnd()) { _properties[iter.key()] = iter.value(); @@ -459,6 +625,10 @@ void TabletButtonProxy::editProperties(QVariantMap properties) { } ++iter; } + + if (_toolbarButtonProxy) { + QMetaObject::invokeMethod(_toolbarButtonProxy, "editProperties", Qt::AutoConnection, Q_ARG(QVariantMap, properties)); + } } #include "TabletScriptingInterface.moc" diff --git a/libraries/script-engine/src/TabletScriptingInterface.h b/libraries/script-engine/src/TabletScriptingInterface.h index 8ba69ccdde..e450923758 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.h +++ b/libraries/script-engine/src/TabletScriptingInterface.h @@ -26,6 +26,7 @@ class TabletProxy; class TabletButtonProxy; +class QmlWindowClass; /**jsdoc * @namespace Tablet @@ -35,6 +36,9 @@ class TabletScriptingInterface : public QObject, public Dependency { public: TabletScriptingInterface(); + void setToolbarScriptingInterface(QObject* toolbarScriptingInterface) { _toolbarScriptingInterface = toolbarScriptingInterface; } + QObject* getSystemToolbarProxy(); + /**jsdoc * Creates or retruns a new TabletProxy and returns it. * @function Tablet.getTablet @@ -43,6 +47,8 @@ public: */ Q_INVOKABLE QObject* getTablet(const QString& tabletId); + void setToolbarMode(bool toolbarMode); + void setQmlTabletRoot(QString tabletId, QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface); void processEvent(const QKeyEvent* event); @@ -58,15 +64,20 @@ private: protected: std::mutex _mutex; std::map> _tabletProxies; + QObject* _toolbarScriptingInterface { nullptr }; + bool _toolbarMode { false }; }; /**jsdoc * @class TabletProxy * @property name {string} READ_ONLY: name of this tablet + * @property toolbarMode {bool} - used to transition this tablet into and out of toolbar mode. + * When tablet is in toolbar mode, all its buttons will appear in a floating toolbar. */ class TabletProxy : public QObject { Q_OBJECT Q_PROPERTY(QString name READ getName) + Q_PROPERTY(bool toolbarMode READ getToolbarMode WRITE setToolbarMode) public: TabletProxy(QString name); @@ -74,6 +85,11 @@ public: Q_INVOKABLE void gotoMenuScreen(const QString& submenu = ""); + QString getName() const { return _name; } + + bool getToolbarMode() const { return _toolbarMode; } + void setToolbarMode(bool toolbarMode); + /**jsdoc * transition to the home screen * @function TabletProxy#gotoHomeScreen @@ -120,8 +136,6 @@ public: */ 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 @@ -162,24 +176,28 @@ signals: void fromQml(QVariant msg); /**jsdoc - * Signales when this tablet screen changes. + * Signaled when this tablet screen changes. * @function TabletProxy#screenChanged * @param type {string} - "Home", "Web", "Menu", "QML", "Closed" * @param url {string} - only valid for Web and QML. */ void screenChanged(QVariant type, QVariant url); -private slots: +protected slots: void addButtonsToHomeScreen(); - void addButtonsToMenuScreen(); + void desktopWindowClosed(); protected: void removeButtonsFromHomeScreen(); + void addButtonsToToolbar(); + void removeButtonsFromToolbar(); QString _name; std::mutex _mutex; std::vector> _tabletButtonProxies; QQuickItem* _qmlTabletRoot { nullptr }; QObject* _qmlOffscreenSurface { nullptr }; + QmlWindowClass* _desktopWindow { nullptr }; + bool _toolbarMode { false }; enum class State { Uninitialized, Home, Web, Menu, QML }; State _state { State::Uninitialized }; @@ -196,6 +214,7 @@ public: TabletButtonProxy(const QVariantMap& properties); void setQmlButton(QQuickItem* qmlButton); + void setToolbarButtonProxy(QObject* toolbarButtonProxy); QUuid getUuid() const { return _uuid; } @@ -229,6 +248,7 @@ protected: int _stableOrder; mutable std::mutex _mutex; QQuickItem* _qmlButton { nullptr }; + QObject* _toolbarButtonProxy { nullptr }; QVariantMap _properties; }; diff --git a/libraries/ui/src/InfoView.cpp b/libraries/ui/src/InfoView.cpp index d2c72bf5f2..cb80e3f6db 100644 --- a/libraries/ui/src/InfoView.cpp +++ b/libraries/ui/src/InfoView.cpp @@ -20,17 +20,22 @@ const QString InfoView::NAME{ "InfoView" }; Setting::Handle infoVersion("info-version", QString()); -InfoView::InfoView(QQuickItem* parent) : QQuickItem(parent) { +static bool registered{ false }; +InfoView::InfoView(QQuickItem* parent) : QQuickItem(parent) { + registerType(); } -void InfoView::registerType() { - qmlRegisterType("Hifi", 1, 0, NAME.toLocal8Bit().constData()); -} +void InfoView::registerType() { + if (!registered) { + qmlRegisterType("Hifi", 1, 0, NAME.toLocal8Bit().constData()); + registered = true; + } +} QString fetchVersion(const QUrl& url) { QXmlQuery query; - query.bindVariable("file", QVariant(url)); + query.bindVariable("file", QVariant(url)); query.setQuery("string((doc($file)//input[@id='version'])[1]/@value)"); QString r; query.evaluateTo(&r); @@ -38,14 +43,10 @@ QString fetchVersion(const QUrl& url) { } void InfoView::show(const QString& path, bool firstOrChangedOnly, QString urlQuery) { - static bool registered{ false }; - if (!registered) { - registerType(); - registered = true; - } + registerType(); QUrl url; if (QDir(path).isRelative()) { - url = QUrl::fromLocalFile(PathUtils::resourcesPath() + path); + url = QUrl::fromLocalFile(PathUtils::resourcesPath() + path); } else { url = QUrl::fromLocalFile(path); } @@ -56,7 +57,7 @@ void InfoView::show(const QString& path, bool firstOrChangedOnly, QString urlQue const QString version = fetchVersion(url); // If we have version information stored if (lastVersion != QString::null) { - // Check to see the document version. If it's valid and matches + // Check to see the document version. If it's valid and matches // the stored version, we're done, so exit if (version == QString::null || version == lastVersion) { return; @@ -87,4 +88,3 @@ void InfoView::setUrl(const QUrl& url) { emit urlChanged(); } } - diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index a6f59104fd..95777718bf 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -31,6 +31,9 @@ public: QmlWindowClass(); ~QmlWindowClass(); + virtual void initQml(QVariantMap properties); + QQuickItem* asQuickItem() const; + public slots: bool isVisible() const; void setVisible(bool visible); @@ -81,9 +84,6 @@ protected: virtual QString qmlSource() const { return "QmlWindow.qml"; } - virtual void initQml(QVariantMap properties); - QQuickItem* asQuickItem() const; - // FIXME needs to be initialized in the ctor once we have support // for tool window panes in QML bool _toolWindow { false }; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index dd49f944ea..c0fdb43b40 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -9,49 +9,30 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ (function() { // BEGIN LOCAL_SCOPE -var button; -var TOOLBAR_BUTTON_NAME = "MUTE"; var TABLET_BUTTON_NAME = "AUDIO"; -var toolBar = null; -var tablet = null; -var isHUDUIEnabled = Settings.getValue("HUDUIEnabled"); var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; function onMuteToggled() { - if (isHUDUIEnabled) { - button.editProperties({ isActive: AudioDevice.getMuted() }); - } + button.editProperties({ isActive: AudioDevice.getMuted() }); } function onClicked(){ - if (isHUDUIEnabled) { - var menuItem = "Mute Microphone"; - Menu.setIsOptionChecked(menuItem, !Menu.isOptionChecked(menuItem)); - } else { - var entity = HMD.tabletID; - Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) }); - tablet.gotoMenuScreen("Audio"); - } + var entity = HMD.tabletID; + Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) }); + tablet.gotoMenuScreen("Audio"); } -if (Settings.getValue("HUDUIEnabled")) { - toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); - button = toolBar.addButton({ - objectName: TOOLBAR_BUTTON_NAME, - imageURL: Script.resolvePath("assets/images/tools/mic.svg"), - visible: true, - alpha: 0.9 - }); -} else { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - button = tablet.addButton({ - icon: "icons/tablet-icons/mic-i.svg", - text: TABLET_BUTTON_NAME, - sortOrder: 1 - }); -} +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var button = tablet.addButton({ + icon: "icons/tablet-icons/mic-unmute-i.svg", + activeIcon: "icons/tablet-icons/mic-mute-a.svg", + text: TABLET_BUTTON_NAME, + sortOrder: 1 +}); + onMuteToggled(); button.clicked.connect(onClicked); @@ -60,12 +41,7 @@ AudioDevice.muteToggled.connect(onMuteToggled); Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); AudioDevice.muteToggled.disconnect(onMuteToggled); - if (tablet) { - tablet.removeButton(button); - } - if (toolBar) { - toolBar.removeButton(TOOLBAR_BUTTON_NAME); - } + tablet.removeButton(button); }); }()); // END LOCAL_SCOPE diff --git a/scripts/system/bubble.js b/scripts/system/bubble.js index ff262e3d6e..8d103c93de 100644 --- a/scripts/system/bubble.js +++ b/scripts/system/bubble.js @@ -10,11 +10,9 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Toolbars, Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */ - +/* global Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */ (function () { // BEGIN LOCAL_SCOPE - var button; // Used for animating and disappearing the bubble var bubbleOverlayTimestamp; @@ -23,7 +21,7 @@ // Used for flashing the HUD button upon activation var bubbleButtonTimestamp; // Affects bubble height - const BUBBLE_HEIGHT_SCALE = 0.15; + var BUBBLE_HEIGHT_SCALE = 0.15; // The bubble model itself var bubbleOverlay = Overlays.addOverlay("model", { url: Script.resolvePath("assets/models/Bubble-v14.fbx"), // If you'd like to change the model, modify this line (and the dimensions below) @@ -39,16 +37,8 @@ // Is the update() function connected? var updateConnected = false; - const BUBBLE_VISIBLE_DURATION_MS = 3000; - const BUBBLE_RAISE_ANIMATION_DURATION_MS = 750; - const BUBBLE_HUD_ICON_FLASH_INTERVAL_MS = 500; - - var ASSETS_PATH = Script.resolvePath("assets"); - var TOOLS_PATH = Script.resolvePath("assets/images/tools/"); - - function buttonImageURL() { - return TOOLS_PATH + 'bubble.svg'; - } + var BUBBLE_VISIBLE_DURATION_MS = 3000; + var BUBBLE_RAISE_ANIMATION_DURATION_MS = 750; // Hides the bubble model overlay and resets the button flash state function hideOverlays() { @@ -94,7 +84,7 @@ } // The bubble script's update function - update = function () { + function update() { var timestamp = Date.now(); var delay = (timestamp - bubbleOverlayTimestamp); var overlayAlpha = 1.0 - (delay / BUBBLE_VISIBLE_DURATION_MS); @@ -146,7 +136,7 @@ var bubbleActive = Users.getIgnoreRadiusEnabled(); writeButtonProperties(bubbleActive); } - }; + } // When the space bubble is toggled... function onBubbleToggled() { @@ -165,38 +155,26 @@ // Setup the bubble button var buttonName = "BUBBLE"; - if (Settings.getValue("HUDUIEnabled")) { - var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); - button = toolbar.addButton({ - objectName: 'bubble', - imageURL: buttonImageURL(), - visible: true, - alpha: 0.9 - }); - } else { - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - button = tablet.addButton({ - icon: "icons/tablet-icons/bubble-i.svg", - activeIcon: "icons/tablet-icons/bubble-a.svg", - text: buttonName, - sortOrder: 4 - }); - } + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + icon: "icons/tablet-icons/bubble-i.svg", + activeIcon: "icons/tablet-icons/bubble-a.svg", + text: buttonName, + sortOrder: 4 + }); + onBubbleToggled(); button.clicked.connect(Users.toggleIgnoreRadius); Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled); Users.enteredIgnoreRadius.connect(enteredIgnoreRadius); - // Cleanup the toolbar button and overlays when script is stopped + // Cleanup the tablet button and overlays when script is stopped Script.scriptEnding.connect(function () { button.clicked.disconnect(Users.toggleIgnoreRadius); if (tablet) { tablet.removeButton(button); } - if (toolbar) { - toolbar.removeButton('bubble'); - } Users.ignoreRadiusEnabledChanged.disconnect(onBubbleToggled); Users.enteredIgnoreRadius.disconnect(enteredIgnoreRadius); Overlays.deleteOverlay(bubbleOverlay); diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index f38d17fa2f..95c05c2717 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1680,6 +1680,7 @@ function MyController(hand) { } else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) { if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) { this.grabbedEntity = entity; + this.grabbedDistance = rayPickInfo.distance; this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); return; } else { @@ -2006,7 +2007,7 @@ function MyController(hand) { this.currentObjectTime = now; this.currentCameraOrientation = Camera.orientation; - this.grabRadius = Vec3.distance(this.currentObjectPosition, worldControllerPosition); + this.grabRadius = this.grabbedDistance; this.grabRadialVelocity = 0.0; // offset between controller vector at the grab radius and the entity position @@ -2160,7 +2161,7 @@ function MyController(hand) { var rayPickInfo = this.calcRayPickInfo(this.hand); - this.overlayLineOn(rayPickInfo.searchRay.origin, grabbedProperties.position, COLORS_GRAB_DISTANCE_HOLD); + this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), COLORS_GRAB_DISTANCE_HOLD); var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition)); var success = Entities.updateAction(this.grabbedEntity, this.actionID, { diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 6bebbf0498..f8a336a017 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -480,6 +480,10 @@ var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: LASER_ var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: LASER_ALPHA}; var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1}; var systemLaserOn = false; + +var HIFI_POINTER_DISABLE_MESSAGE_CHANNEL = "Hifi-Pointer-Disable"; +var isPointerEnabled = true; + function clearSystemLaser() { if (!systemLaserOn) { return; @@ -542,9 +546,8 @@ function update() { return off(); } - // If there's a HUD element at the (newly moved) reticle, just make it visible and bail. - if (isPointingAtOverlay(hudPoint2d)) { + if (isPointingAtOverlay(hudPoint2d) && isPointerEnabled) { if (HMD.active) { Reticle.depth = hudReticleDistance(); @@ -579,9 +582,25 @@ function checkSettings() { } checkSettings(); +// Enable/disable pointer. +function handleMessages(channel, message, sender) { + if (sender === MyAvatar.sessionUUID && channel === HIFI_POINTER_DISABLE_MESSAGE_CHANNEL) { + var data = JSON.parse(message); + if (data.pointerEnabled !== undefined) { + print("pointerEnabled: " + data.pointerEnabled); + isPointerEnabled = data.pointerEnabled; + } + } +} + +Messages.subscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); +Messages.messageReceived.connect(handleMessages); + var settingsChecker = Script.setInterval(checkSettings, SETTINGS_CHANGE_RECHECK_INTERVAL); Script.update.connect(update); Script.scriptEnding.connect(function () { + Messages.unsubscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); + Messages.messageReceived.disconnect(handleMessages); Script.clearInterval(settingsChecker); Script.update.disconnect(update); OffscreenFlags.navigationFocusDisabled = false; diff --git a/scripts/system/controllers/squeezeHands.js b/scripts/system/controllers/squeezeHands.js index 1e94c29521..3f1d21b46c 100644 --- a/scripts/system/controllers/squeezeHands.js +++ b/scripts/system/controllers/squeezeHands.js @@ -25,6 +25,11 @@ var OVERLAY_RAMP_RATE = 8.0; var animStateHandlerID; +var isPointingIndex = false; +var HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index"; + +var indexfingerJointNames = ["LeftHandIndex1", "LeftHandIndex2", "LeftHandIndex3", "RightHandIndex1", "RightHandIndex2", "RightHandIndex3"]; + function clamp(val, min, max) { return Math.min(Math.max(val, min), max); } @@ -43,6 +48,8 @@ function init() { animStateHandler, ["leftHandOverlayAlpha", "rightHandOverlayAlpha", "leftHandGraspAlpha", "rightHandGraspAlpha"] ); + Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); + Messages.messageReceived.connect(handleMessages); } function animStateHandler(props) { @@ -76,11 +83,37 @@ function update(dt) { } else { rightHandOverlayAlpha = clamp(rightHandOverlayAlpha - OVERLAY_RAMP_RATE * dt, 0, 1); } + + // Point index finger. + if (isPointingIndex) { + var zeroRotation = { x: 0, y: 0, z: 0, w: 1 }; + for (var i = 0; i < indexfingerJointNames.length; i++) { + MyAvatar.setJointRotation(indexfingerJointNames[i], zeroRotation); + } + } +} + +function handleMessages(channel, message, sender) { + if (sender === MyAvatar.sessionUUID && channel === HIFI_POINT_INDEX_MESSAGE_CHANNEL) { + var data = JSON.parse(message); + if (data.pointIndex !== undefined) { + print("pointIndex: " + data.pointIndex); + isPointingIndex = data.pointIndex; + + if (!isPointingIndex) { + for (var i = 0; i < indexfingerJointNames.length; i++) { + MyAvatar.clearJointData(indexfingerJointNames[i]); + } + } + } + } } function shutdown() { Script.update.disconnect(update); MyAvatar.removeAnimationStateHandler(animStateHandlerID); + Messages.unsubscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); + Messages.messageReceived.disconnect(handleMessages); } Script.scriptEnding.connect(shutdown); diff --git a/scripts/system/fingerPaint.js b/scripts/system/fingerPaint.js new file mode 100644 index 0000000000..959f594212 --- /dev/null +++ b/scripts/system/fingerPaint.js @@ -0,0 +1,433 @@ +// +// fingerPaint.js +// +// Created by David Rowe on 15 Feb 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 +// + +(function () { + var tablet, + button, + BUTTON_NAME = "PAINT", + isFingerPainting = false, + leftHand = null, + rightHand = null, + leftBrush = null, + rightBrush = null, + CONTROLLER_MAPPING_NAME = "com.highfidelity.fingerPaint", + isTabletDisplayed = false, + HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index", + HIFI_GRAB_DISABLE_MESSAGE_CHANNEL = "Hifi-Grab-Disable", + HIFI_POINTER_DISABLE_MESSAGE_CHANNEL = "Hifi-Pointer-Disable"; + + function paintBrush(name) { + // Paints in 3D. + var brushName = name, + STROKE_COLOR = { red: 250, green: 0, blue: 0 }, + ERASE_SEARCH_RADIUS = 0.1, // m + isDrawingLine = false, + entityID, + basePosition, + strokePoints, + strokeNormals, + strokeWidths, + timeOfLastPoint, + MIN_STROKE_LENGTH = 0.005, // m + MIN_STROKE_INTERVAL = 66, // ms + MAX_POINTS_PER_LINE = 70; // Hard-coded limit in PolyLineEntityItem.h. + + function strokeNormal() { + return Vec3.multiplyQbyV(Camera.getOrientation(), Vec3.UNIT_NEG_Z); + } + + function startLine(position, width) { + // Start drawing a polyline. + + if (isDrawingLine) { + print("ERROR: startLine() called when already drawing line"); + // Nevertheless, continue on and start a new line. + } + + basePosition = position; + + strokePoints = [Vec3.ZERO]; + strokeNormals = [strokeNormal()]; + strokeWidths = [width]; + timeOfLastPoint = Date.now(); + + entityID = Entities.addEntity({ + type: "PolyLine", + name: "fingerPainting", + color: STROKE_COLOR, + position: position, + linePoints: strokePoints, + normals: strokeNormals, + strokeWidths: strokeWidths, + dimensions: { x: 10, y: 10, z: 10 } + }); + + isDrawingLine = true; + } + + function drawLine(position, width) { + // Add a stroke to the polyline if stroke is a sufficient length. + var localPosition, + distanceToPrevious, + MAX_DISTANCE_TO_PREVIOUS = 1.0; + + if (!isDrawingLine) { + print("ERROR: drawLine() called when not drawing line"); + return; + } + + localPosition = Vec3.subtract(position, basePosition); + distanceToPrevious = Vec3.distance(localPosition, strokePoints[strokePoints.length - 1]); + + if (distanceToPrevious > MAX_DISTANCE_TO_PREVIOUS) { + // Ignore occasional spurious finger tip positions. + return; + } + + if (distanceToPrevious >= MIN_STROKE_LENGTH + && (Date.now() - timeOfLastPoint) >= MIN_STROKE_INTERVAL + && strokePoints.length < MAX_POINTS_PER_LINE) { + strokePoints.push(localPosition); + strokeNormals.push(strokeNormal()); + strokeWidths.push(width); + timeOfLastPoint = Date.now(); + + Entities.editEntity(entityID, { + linePoints: strokePoints, + normals: strokeNormals, + strokeWidths: strokeWidths + }); + } + } + + function finishLine(position, width) { + // Finish drawing polyline; delete if it has only 1 point. + + if (!isDrawingLine) { + print("ERROR: finishLine() called when not drawing line"); + return; + } + + if (strokePoints.length === 1) { + // Delete "empty" line. + Entities.deleteEntity(entityID); + } + + isDrawingLine = false; + } + + function cancelLine() { + // Cancel any line being drawn. + if (isDrawingLine) { + Entities.deleteEntity(entityID); + isDrawingLine = false; + } + } + + function eraseClosestLine(position) { + // Erase closest line that is within search radius of finger tip. + var entities, + entitiesLength, + properties, + i, + pointsLength, + j, + distance, + found = false, + foundID, + foundDistance = ERASE_SEARCH_RADIUS; + + // Find entities with bounding box within search radius. + entities = Entities.findEntities(position, ERASE_SEARCH_RADIUS); + + // Fine polyline entity with closest point within search radius. + for (i = 0, entitiesLength = entities.length; i < entitiesLength; i += 1) { + properties = Entities.getEntityProperties(entities[i], ["type", "position", "linePoints"]); + if (properties.type === "PolyLine") { + basePosition = properties.position; + for (j = 0, pointsLength = properties.linePoints.length; j < pointsLength; j += 1) { + distance = Vec3.distance(position, Vec3.sum(basePosition, properties.linePoints[j])); + if (distance <= foundDistance) { + found = true; + foundID = entities[i]; + foundDistance = distance; + } + } + } + } + + // Delete found entity. + if (found) { + Entities.deleteEntity(foundID); + } + } + + function tearDown() { + cancelLine(); + } + + return { + startLine: startLine, + drawLine: drawLine, + finishLine: finishLine, + cancelLine: cancelLine, + eraseClosestLine: eraseClosestLine, + tearDown: tearDown + }; + } + + function handController(name) { + // Translates controller data into application events. + var handName = name, + + triggerPressedCallback, + triggerPressingCallback, + triggerReleasedCallback, + gripPressedCallback, + + rawTriggerValue = 0.0, + triggerValue = 0.0, + isTriggerPressed = false, + TRIGGER_SMOOTH_RATIO = 0.1, + TRIGGER_OFF = 0.05, + TRIGGER_ON = 0.1, + TRIGGER_START_WIDTH_RAMP = 0.15, + TRIGGER_FINISH_WIDTH_RAMP = 1.0, + TRIGGER_RAMP_WIDTH = TRIGGER_FINISH_WIDTH_RAMP - TRIGGER_START_WIDTH_RAMP, + MIN_LINE_WIDTH = 0.005, + MAX_LINE_WIDTH = 0.03, + RAMP_LINE_WIDTH = MAX_LINE_WIDTH - MIN_LINE_WIDTH, + + rawGripValue = 0.0, + gripValue = 0.0, + isGripPressed = false, + GRIP_SMOOTH_RATIO = 0.1, + GRIP_OFF = 0.05, + GRIP_ON = 0.1; + + function onTriggerPress(value) { + // Controller values are only updated when they change so store latest for use in update. + rawTriggerValue = value; + } + + function updateTriggerPress(value) { + var wasTriggerPressed, + fingerTipPosition, + lineWidth; + + triggerValue = triggerValue * TRIGGER_SMOOTH_RATIO + rawTriggerValue * (1.0 - TRIGGER_SMOOTH_RATIO); + + wasTriggerPressed = isTriggerPressed; + if (isTriggerPressed) { + isTriggerPressed = triggerValue > TRIGGER_OFF; + } else { + isTriggerPressed = triggerValue > TRIGGER_ON; + } + + if (wasTriggerPressed || isTriggerPressed) { + fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4"); + if (triggerValue < TRIGGER_START_WIDTH_RAMP) { + lineWidth = MIN_LINE_WIDTH; + } else { + lineWidth = MIN_LINE_WIDTH + + (triggerValue - TRIGGER_START_WIDTH_RAMP) / TRIGGER_RAMP_WIDTH * RAMP_LINE_WIDTH; + } + + if (!wasTriggerPressed && isTriggerPressed) { + triggerPressedCallback(fingerTipPosition, lineWidth); + } else if (wasTriggerPressed && isTriggerPressed) { + triggerPressingCallback(fingerTipPosition, lineWidth); + } else { + triggerReleasedCallback(fingerTipPosition, lineWidth); + } + } + } + + function onGripPress(value) { + // Controller values are only updated when they change so store latest for use in update. + rawGripValue = value; + } + + function updateGripPress() { + var fingerTipPosition; + + gripValue = gripValue * GRIP_SMOOTH_RATIO + rawGripValue * (1.0 - GRIP_SMOOTH_RATIO); + + if (isGripPressed) { + isGripPressed = gripValue > GRIP_OFF; + } else { + isGripPressed = gripValue > GRIP_ON; + if (isGripPressed) { + fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4"); + gripPressedCallback(fingerTipPosition); + } + } + } + + function onUpdate() { + updateTriggerPress(); + updateGripPress(); + } + + function setUp(onTriggerPressed, onTriggerPressing, onTriggerReleased, onGripPressed) { + triggerPressedCallback = onTriggerPressed; + triggerPressingCallback = onTriggerPressing; + triggerReleasedCallback = onTriggerReleased; + gripPressedCallback = onGripPressed; + } + + function tearDown() { + // Nothing to do. + } + + return { + onTriggerPress: onTriggerPress, + onGripPress: onGripPress, + onUpdate: onUpdate, + setUp: setUp, + tearDown: tearDown + }; + } + + function updateHandFunctions() { + // Update other scripts' hand functions. + var enabled = !isFingerPainting || isTabletDisplayed; + + Messages.sendMessage(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ + holdEnabled: enabled, + nearGrabEnabled: enabled, + farGrabEnabled: enabled + }), true); + Messages.sendMessage(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ + pointerEnabled: enabled + }), true); + Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify({ + pointIndex: !enabled + }), true); + } + + function enableProcessing() { + // Connect controller API to handController objects. + leftHand = handController("left"); + rightHand = handController("right"); + var controllerMapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); + controllerMapping.from(Controller.Standard.LT).to(leftHand.onTriggerPress); + controllerMapping.from(Controller.Standard.LeftGrip).to(leftHand.onGripPress); + controllerMapping.from(Controller.Standard.RT).to(rightHand.onTriggerPress); + controllerMapping.from(Controller.Standard.RightGrip).to(rightHand.onGripPress); + Controller.enableMapping(CONTROLLER_MAPPING_NAME); + + // Connect handController outputs to paintBrush objects. + leftBrush = paintBrush("left"); + leftHand.setUp(leftBrush.startLine, leftBrush.drawLine, leftBrush.finishLine, leftBrush.eraseClosestLine); + rightBrush = paintBrush("right"); + rightHand.setUp(rightBrush.startLine, rightBrush.drawLine, rightBrush.finishLine, rightBrush.eraseClosestLine); + + // Messages channels for enabling/disabling other scripts' functions. + Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); + Messages.subscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); + Messages.subscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); + + // Update hand controls. + Script.update.connect(leftHand.onUpdate); + Script.update.connect(rightHand.onUpdate); + } + + function disableProcessing() { + Script.update.disconnect(leftHand.onUpdate); + Script.update.disconnect(rightHand.onUpdate); + + Controller.disableMapping(CONTROLLER_MAPPING_NAME); + + leftBrush.tearDown(); + leftBrush = null; + leftHand.tearDown(); + leftHand = null; + + rightBrush.tearDown(); + rightBrush = null; + rightHand.tearDown(); + rightHand = null; + + Messages.unsubscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); + Messages.unsubscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); + Messages.unsubscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); + } + + function onButtonClicked() { + var wasFingerPainting = isFingerPainting; + + isFingerPainting = !isFingerPainting; + button.editProperties({ isActive: isFingerPainting }); + + print("Finger painting: " + isFingerPainting ? "on" : "off"); + + if (wasFingerPainting) { + leftBrush.cancelLine(); + rightBrush.cancelLine(); + } + + if (isFingerPainting) { + enableProcessing(); + } + + updateHandFunctions(); + + if (!isFingerPainting) { + disableProcessing(); + } + } + + function onTabletScreenChanged(type, url) { + var TABLET_SCREEN_CLOSED = "Closed"; + + isTabletDisplayed = type !== TABLET_SCREEN_CLOSED; + updateHandFunctions(); + } + + function setUp() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + if (!tablet) { + return; + } + + // Tablet button. + button = tablet.addButton({ + icon: "icons/tablet-icons/finger-paint-i.svg", + activeIcon: "icons/tablet-icons/finger-paint-a.svg", + text: BUTTON_NAME, + isActive: isFingerPainting + }); + button.clicked.connect(onButtonClicked); + + // Track whether tablet is displayed or not. + tablet.screenChanged.connect(onTabletScreenChanged); + } + + function tearDown() { + if (!tablet) { + return; + } + + if (isFingerPainting) { + isFingerPainting = false; + updateHandFunctions(); + disableProcessing(); + } + + tablet.screenChanged.disconnect(onTabletScreenChanged); + + button.clicked.disconnect(onButtonClicked); + tablet.removeButton(button); + } + + setUp(); + Script.scriptEnding.connect(tearDown); +}()); \ No newline at end of file diff --git a/scripts/system/help.js b/scripts/system/help.js index 4e7788a758..5a1b712fb5 100644 --- a/scripts/system/help.js +++ b/scripts/system/help.js @@ -10,48 +10,21 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* globals Tablet, Toolbars, Script, HMD, Controller, Menu */ +/* globals Tablet, Script, HMD, Controller, Menu */ (function() { // BEGIN LOCAL_SCOPE - var button; var buttonName = "HELP"; - var toolBar = null; - var tablet = null; - if (Settings.getValue("HUDUIEnabled")) { - toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); - button = toolBar.addButton({ - objectName: buttonName, - imageURL: Script.resolvePath("assets/images/tools/help.svg"), - visible: true, - alpha: 0.9 - }); - } else { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - button = tablet.addButton({ - icon: "icons/tablet-icons/help-i.svg", - activeIcon: "icons/tablet-icons/help-a.svg", - text: buttonName, - sortOrder: 6 - }); - } + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + icon: "icons/tablet-icons/help-i.svg", + activeIcon: "icons/tablet-icons/help-a.svg", + text: buttonName, + sortOrder: 6 + }); + var enabled = false; function onClicked() { - // Similar logic to Application::showHelp() - var defaultTab = "kbm"; - var handControllerName = "vive"; - if (HMD.active) { - if ("Vive" in Controller.Hardware) { - defaultTab = "handControllers"; - handControllerName = "vive"; - } else if ("OculusTouch" in Controller.Hardware) { - defaultTab = "handControllers"; - handControllerName = "oculus"; - } - } else if ("SDL2" in Controller.Hardware) { - defaultTab = "gamepad"; - } - if (enabled) { Menu.closeInfoView('InfoView_html/help.html'); enabled = !enabled; @@ -80,9 +53,6 @@ if (tablet) { tablet.removeButton(button); } - if (toolBar) { - toolBar.removeButton(buttonName); - } }); }()); // END LOCAL_SCOPE diff --git a/scripts/system/hmd.js b/scripts/system/hmd.js index 3493215ba3..c206a76e3f 100644 --- a/scripts/system/hmd.js +++ b/scripts/system/hmd.js @@ -10,7 +10,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/*globals HMD, Toolbars, Script, Menu, Tablet, Camera */ +/* globals HMD, Script, Menu, Tablet, Camera */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ (function() { // BEGIN LOCAL_SCOPE @@ -37,20 +38,13 @@ function updateControllerDisplay() { } var button; -var toolBar = null; -var tablet = null; - -if (Settings.getValue("HUDUIEnabled")) { - toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); -} else { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); -} +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); // Independent and Entity mode make people sick. Third Person and Mirror have traps that we need to work through. // Disable them in hmd. var desktopOnlyViews = ['Mirror', 'Independent Mode', 'Entity Mode']; + function onHmdChanged(isHmd) { - //TODO change button icon when the hmd changes if (isHmd) { button.editProperties({ icon: "icons/tablet-icons/switch-desk-i.svg", @@ -67,25 +61,18 @@ function onHmdChanged(isHmd) { }); updateControllerDisplay(); } -function onClicked(){ + +function onClicked() { var isDesktop = Menu.isOptionChecked(desktopMenuItemName); Menu.setIsOptionChecked(isDesktop ? headset : desktopMenuItemName, true); } + if (headset) { - if (Settings.getValue("HUDUIEnabled")) { - button = toolBar.addButton({ - objectName: "hmdToggle", - imageURL: Script.resolvePath("assets/images/tools/switch.svg"), - visible: true, - alpha: 0.9 - }); - } else { - button = tablet.addButton({ - icon: HMD.active ? "icons/tablet-icons/switch-desk-i.svg" : "icons/tablet-icons/switch-vr-i.svg", - text: HMD.active ? "DESKTOP" : "VR", - sortOrder: 2 - }); - } + button = tablet.addButton({ + icon: HMD.active ? "icons/tablet-icons/switch-desk-i.svg" : "icons/tablet-icons/switch-vr-i.svg", + text: HMD.active ? "DESKTOP" : "VR", + sortOrder: 2 + }); onHmdChanged(HMD.active); button.clicked.connect(onClicked); @@ -97,9 +84,6 @@ if (headset) { if (tablet) { tablet.removeButton(button); } - if (toolBar) { - toolBar.removeButton("hmdToggle"); - } HMD.displayModeChanged.disconnect(onHmdChanged); Camera.modeUpdated.disconnect(updateControllerDisplay); }); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 0803f753c7..c5ce5a634b 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Tablet, Script, HMD, Toolbars, UserActivityLogger, Entities */ +/* global Tablet, Script, HMD, UserActivityLogger, Entities */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ (function() { // BEGIN LOCAL_SCOPE @@ -33,8 +33,6 @@ var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; -var marketplaceWindow = null; - var CLARA_DOWNLOAD_TITLE = "Preparing Download"; var messageBox = null; var isDownloadBeingCancelled = false; @@ -57,52 +55,47 @@ Window.messageBoxClosed.connect(onMessageBoxClosed); function showMarketplace() { UserActivityLogger.openedMarketplace(); - if (tablet) { - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - tablet.webEventReceived.connect(function (message) { - if (message === GOTO_DIRECTORY) { - tablet.gotoWebScreen(MARKETPLACES_URL); - } + tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + tablet.webEventReceived.connect(function (message) { - if (message === QUERY_CAN_WRITE_ASSETS) { - tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets()); - } + if (message === GOTO_DIRECTORY) { + tablet.gotoWebScreen(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); + } - if (message === WARN_USER_NO_PERMISSIONS) { - Window.alert(NO_PERMISSIONS_ERROR_MESSAGE); - } + if (message === QUERY_CAN_WRITE_ASSETS) { + tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets()); + } - if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) { - if (isDownloadBeingCancelled) { - return; - } + if (message === WARN_USER_NO_PERMISSIONS) { + Window.alert(NO_PERMISSIONS_ERROR_MESSAGE); + } - var text = message.slice(CLARA_IO_STATUS.length); - if (messageBox === null) { - messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); - } else { - Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); - } + if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) { + if (isDownloadBeingCancelled) { return; } - if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) { - if (messageBox !== null) { - Window.closeMessageBox(messageBox); - messageBox = null; - } - return; + var text = message.slice(CLARA_IO_STATUS.length); + if (messageBox === null) { + messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); + } else { + Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); } + return; + } - if (message === CLARA_IO_CANCELLED_DOWNLOAD) { - isDownloadBeingCancelled = false; + if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) { + if (messageBox !== null) { + Window.closeMessageBox(messageBox); + messageBox = null; } - }); - } else { - marketplaceWindow.setURL(MARKETPLACE_URL_INITIAL); - marketplaceWindow.setVisible(true); - marketplaceVisible = true; - } + return; + } + + if (message === CLARA_IO_CANCELLED_DOWNLOAD) { + isDownloadBeingCancelled = false; + } + }); } function toggleMarketplace() { @@ -111,33 +104,12 @@ function toggleMarketplace() { showMarketplace(); } -var tablet = null; -var toolBar = null; -var marketplaceButton = null; -if (Settings.getValue("HUDUIEnabled")) { - marketplaceWindow = new OverlayWebWindow({ - title: "Marketplace", - source: "about:blank", - width: 900, - height: 700, - visible: false - }); - marketplaceWindow.setScriptURL(MARKETPLACES_INJECT_SCRIPT_URL); - toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); - var toolIconUrl = Script.resolvePath("../assets/images/tools/"); - marketplaceButton = toolBar.addButton({ - imageURL: toolIconUrl + "market.svg", - objectName: "marketplace", - alpha: 0.9 - }); -} else { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - marketplaceButton = tablet.addButton({ - icon: "icons/tablet-icons/market-i.svg", - text: "MARKET", - sortOrder: 9 - }); -} +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var marketplaceButton = tablet.addButton({ + icon: "icons/tablet-icons/market-i.svg", + text: "MARKET", + sortOrder: 9 +}); function onCanWriteAssetsChanged() { var message = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets(); @@ -152,9 +124,6 @@ marketplaceButton.clicked.connect(onClick); Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); Script.scriptEnding.connect(function () { - if (toolBar) { - toolBar.removeButton("marketplace"); - } if (tablet) { tablet.removeButton(marketplaceButton); } diff --git a/scripts/system/pal.js b/scripts/system/pal.js index b5d7c3885d..e9f5ba0a67 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -1,6 +1,7 @@ "use strict"; -/*jslint vars: true, plusplus: true, forin: true*/ -/*globals Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, OverlayWindow, Toolbars, Vec3, Quat, Controller, print, getControllerWorldLocation */ +/* jslint vars: true, plusplus: true, forin: true*/ +/* globals Tablet, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, Controller, print, getControllerWorldLocation */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ // // pal.js // @@ -15,19 +16,22 @@ // hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed // something, will revisit as this is sorta horrible. -const UNSELECTED_TEXTURES = {"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"), - "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png") +var UNSELECTED_TEXTURES = { + "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"), + "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png") }; -const SELECTED_TEXTURES = { "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"), - "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png") +var SELECTED_TEXTURES = { + "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"), + "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png") }; -const HOVER_TEXTURES = { "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png"), - "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png") +var HOVER_TEXTURES = { + "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png"), + "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png") }; -const UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6}; -const SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29}; -const HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now +var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6}; +var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29}; +var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now var conserveResources = true; @@ -94,12 +98,12 @@ ExtendedOverlay.prototype.hover = function (hovering) { } if (hovering) { // un-hover the last hovering overlay - if (lastHoveringId && lastHoveringId != this.key) { + if (lastHoveringId && lastHoveringId !== this.key) { ExtendedOverlay.get(lastHoveringId).hover(false); } lastHoveringId = this.key; } -} +}; ExtendedOverlay.prototype.select = function (selected) { if (this.selected === selected) { return; @@ -193,17 +197,8 @@ HighlightedEntity.updateOverlays = function updateHighlightedEntities() { }); }; -// -// The qml window and communications. -// -var pal = new OverlayWindow({ - title: 'People Action List', - source: 'hifi/Pal.qml', - width: 580, - height: 640, - visible: false -}); function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. + var data; switch (message.method) { case 'selected': selectedIds = message.params; @@ -250,7 +245,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } break; case 'displayNameUpdate': - if (MyAvatar.displayName != message.params) { + if (MyAvatar.displayName !== message.params) { MyAvatar.displayName = message.params; UserActivityLogger.palAction("display_name_change", ""); } @@ -261,11 +256,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } function sendToQml(message) { - if (Settings.getValue("HUDUIEnabled")) { - pal.sendToQml(message); - } else { - tablet.sendToQml(message); - } + tablet.sendToQml(message); } // @@ -386,7 +377,9 @@ function removeOverlays() { selectedIds = []; lastHoveringId = 0; HighlightedEntity.clearOverlays(); - ExtendedOverlay.some(function (overlay) { overlay.deleteOverlay(); }); + ExtendedOverlay.some(function (overlay) { + overlay.deleteOverlay(); + }); } // @@ -416,12 +409,13 @@ function handleMouseMove(pickRay) { // given the pickRay, just do the hover logi // handy global to keep track of which hand is the mouse (if any) var currentHandPressed = 0; -const TRIGGER_CLICK_THRESHOLD = 0.85; -const TRIGGER_PRESS_THRESHOLD = 0.05; +var TRIGGER_CLICK_THRESHOLD = 0.85; +var TRIGGER_PRESS_THRESHOLD = 0.05; function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position + var pickRay; if (HMD.active) { - if (currentHandPressed != 0) { + if (currentHandPressed !== 0) { pickRay = controllerComputePickRay(currentHandPressed); } else { // nothing should hover, so @@ -437,12 +431,12 @@ function handleTriggerPressed(hand, value) { // The idea is if you press one trigger, it is the one // we will consider the mouse. Even if the other is pressed, // we ignore it until this one is no longer pressed. - isPressed = value > TRIGGER_PRESS_THRESHOLD; - if (currentHandPressed == 0) { + var isPressed = value > TRIGGER_PRESS_THRESHOLD; + if (currentHandPressed === 0) { currentHandPressed = isPressed ? hand : 0; return; } - if (currentHandPressed == hand) { + if (currentHandPressed === hand) { currentHandPressed = isPressed ? hand : 0; return; } @@ -471,7 +465,7 @@ function makeClickHandler(hand) { function makePressHandler(hand) { return function (value) { handleTriggerPressed(hand, value); - } + }; } triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); @@ -483,17 +477,14 @@ triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Cont var button; var buttonName = "PEOPLE"; var tablet = null; -var toolBar = null; -if (Settings.getValue("HUDUIEnabled")) { - toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); - button = toolBar.addButton({ - objectName: buttonName, - imageURL: Script.resolvePath("assets/images/tools/people.svg"), - visible: true, - alpha: 0.9 - }); - pal.fromQml.connect(fromQml); -} else { + +function onTabletScreenChanged(type, url) { + if (type !== "QML" || url !== "../Pal.qml") { + off(); + } +} + +function startup() { tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton({ text: buttonName, @@ -501,8 +492,19 @@ if (Settings.getValue("HUDUIEnabled")) { sortOrder: 7 }); tablet.fromQml.connect(fromQml); + button.clicked.connect(onTabletButtonClicked); + tablet.screenChanged.connect(onTabletScreenChanged); + + Users.usernameFromIDReply.connect(usernameFromIDReply); + Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); + Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); + Messages.subscribe(CHANNEL); + Messages.messageReceived.connect(receiveMessage); + Users.avatarDisconnected.connect(avatarDisconnected); } +startup(); + var isWired = false; var audioTimer; var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too) @@ -514,41 +516,26 @@ function off() { Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); isWired = false; } - if (audioTimer) { Script.clearInterval(audioTimer); } + if (audioTimer) { + Script.clearInterval(audioTimer); + } triggerMapping.disable(); // It's ok if we disable twice. triggerPressMapping.disable(); // see above removeOverlays(); Users.requestsDomainListData = false; } -function onClicked() { - if (Settings.getValue("HUDUIEnabled")) { - if (!pal.visible) { - Users.requestsDomainListData = true; - populateUserList(); - pal.raise(); - isWired = true; - Script.update.connect(updateOverlays); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - triggerMapping.enable(); - triggerPressMapping.enable(); - audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); - } else { - off(); - } - pal.setVisible(!pal.visible); - } else { - tablet.loadQMLSource("../Pal.qml"); - Users.requestsDomainListData = true; - populateUserList(); - isWired = true; - Script.update.connect(updateOverlays); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - triggerMapping.enable(); - triggerPressMapping.enable(); - audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); - } + +function onTabletButtonClicked() { + tablet.loadQMLSource("../Pal.qml"); + Users.requestsDomainListData = true; + populateUserList(); + isWired = true; + Script.update.connect(updateOverlays); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); + audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); } // @@ -563,17 +550,12 @@ function receiveMessage(channel, messageString, senderID) { var message = JSON.parse(messageString); switch (message.method) { case 'select': - if (!pal.visible) { - onClicked(); - } sendToQml(message); // Accepts objects, not just strings. break; default: print('Unrecognized PAL message', messageString); } } -Messages.subscribe(CHANNEL); -Messages.messageReceived.connect(receiveMessage); var AVERAGING_RATIO = 0.05; var LONG_AVERAGING_RATIO = 0.75; @@ -638,57 +620,29 @@ function avatarDisconnected(nodeID) { // remove from the pal list sendToQml({method: 'avatarDisconnected', params: [nodeID]}); } -// -// Button state. -// -function onVisibleChanged() { - button.editProperties({isActive: pal.visible}); -} -button.clicked.connect(onClicked); -pal.visibleChanged.connect(onVisibleChanged); -pal.closed.connect(off); - -if (!Settings.getValue("HUDUIEnabled")) { - tablet.screenChanged.connect(function (type, url) { - if (type !== "QML" || url !== "../Pal.qml") { - off(); - } - }); -} - -Users.usernameFromIDReply.connect(usernameFromIDReply); -Users.avatarDisconnected.connect(avatarDisconnected); function clearLocalQMLDataAndClosePAL() { sendToQml({ method: 'clearLocalQMLData' }); - if (pal.visible) { - onClicked(); // Close the PAL - } } -Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); -Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); + +function shutdown() { + button.clicked.disconnect(onTabletButtonClicked); + tablet.removeButton(button); + tablet.screenChanged.disconnect(onTabletScreenChanged); + + Users.usernameFromIDReply.disconnect(usernameFromIDReply); + Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL); + Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL); + Messages.subscribe(CHANNEL); + Messages.messageReceived.disconnect(receiveMessage); + Users.avatarDisconnected.disconnect(avatarDisconnected); + + off(); +} // // Cleanup. // -Script.scriptEnding.connect(function () { - button.clicked.disconnect(onClicked); - if (tablet) { - tablet.removeButton(button); - } - if (toolBar) { - toolBar.removeButton(buttonName); - } - pal.visibleChanged.disconnect(onVisibleChanged); - pal.closed.disconnect(off); - Users.usernameFromIDReply.disconnect(usernameFromIDReply); - Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL); - Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL); - Messages.unsubscribe(CHANNEL); - Messages.messageReceived.disconnect(receiveMessage); - Users.avatarDisconnected.disconnect(avatarDisconnected); - off(); -}); - +Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index c9462bbe7f..8f918c9cb2 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -7,7 +7,8 @@ // Distributed under the Apache License, Version 2.0 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* globals Tablet, Toolbars, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar */ +/* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ (function() { // BEGIN LOCAL_SCOPE @@ -17,29 +18,15 @@ var resetOverlays; var reticleVisible; var clearOverlayWhenMoving; -var button; var buttonName = "SNAP"; -var tablet = null; -var toolBar = null; - var buttonConnected = false; -if (Settings.getValue("HUDUIEnabled")) { - toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); - button = toolBar.addButton({ - objectName: buttonName, - imageURL: Script.resolvePath("assets/images/tools/snap.svg"), - visible: true, - alpha: 0.9, - }); -} else { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - button = tablet.addButton({ - icon: "icons/tablet-icons/snap-i.svg", - text: buttonName, - sortOrder: 5 - }); -} +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var button = tablet.addButton({ + icon: "icons/tablet-icons/snap-i.svg", + text: buttonName, + sortOrder: 5 +}); function shouldOpenFeedAfterShare() { var persisted = Settings.getValue('openFeedAfterShare', true); // might answer true, false, "true", or "false" @@ -63,42 +50,42 @@ function confirmShare(data) { var isLoggedIn; var needsLogin = false; switch (message) { - case 'ready': - dialog.emitScriptEvent(data); // Send it. - outstanding = 0; - break; - case 'openSettings': - Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog"); - break; - case 'setOpenFeedFalse': - Settings.setValue('openFeedAfterShare', false); - break; - case 'setOpenFeedTrue': - Settings.setValue('openFeedAfterShare', true); - break; - default: - dialog.webEventReceived.disconnect(onMessage); - dialog.close(); - isLoggedIn = Account.isLoggedIn(); - message.forEach(function (submessage) { - if (submessage.share && !isLoggedIn) { - needsLogin = true; - submessage.share = false; - } - if (submessage.share) { - print('sharing', submessage.localPath); - outstanding++; - Window.shareSnapshot(submessage.localPath, submessage.href); - } else { - print('not sharing', submessage.localPath); - } - }); - if (!outstanding && shouldOpenFeedAfterShare()) { - showFeedWindow(); + case 'ready': + dialog.emitScriptEvent(data); // Send it. + outstanding = 0; + break; + case 'openSettings': + Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog"); + break; + case 'setOpenFeedFalse': + Settings.setValue('openFeedAfterShare', false); + break; + case 'setOpenFeedTrue': + Settings.setValue('openFeedAfterShare', true); + break; + default: + dialog.webEventReceived.disconnect(onMessage); + dialog.close(); + isLoggedIn = Account.isLoggedIn(); + message.forEach(function (submessage) { + if (submessage.share && !isLoggedIn) { + needsLogin = true; + submessage.share = false; } - if (needsLogin) { // after the possible feed, so that the login is on top - Account.checkAndSignalForAccessToken(); + if (submessage.share) { + print('sharing', submessage.localPath); + outstanding++; + Window.shareSnapshot(submessage.localPath, submessage.href); + } else { + print('not sharing', submessage.localPath); } + }); + if (!outstanding && shouldOpenFeedAfterShare()) { + showFeedWindow(); + } + if (needsLogin) { // after the possible feed, so that the login is on top + Account.checkAndSignalForAccessToken(); + } } } dialog.webEventReceived.connect(onMessage); @@ -159,7 +146,7 @@ function isDomainOpen(id) { var url = location.metaverseServerUrl + "/api/v1/user_stories?" + options.join('&'); request.open("GET", url, false); request.send(); - if (request.status != 200) { + if (request.status !== 200) { return false; } var response = JSON.parse(request.response); // Not parsed for us. @@ -229,9 +216,6 @@ Script.scriptEnding.connect(function () { if (tablet) { tablet.removeButton(button); } - if (toolBar) { - toolBar.removeButton(buttonName); - } Window.snapshotShared.disconnect(snapshotShared); Window.processingGif.disconnect(processingGif); }); diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 5283df6127..6c3e12cd9b 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -12,54 +12,27 @@ // (function() { // BEGIN LOCAL_SCOPE - var gotoQmlSource = "TabletAddressDialog.qml"; - var button; + var gotoQmlSource = "TabletAddressDialog.qml"; var buttonName = "GOTO"; - var toolBar = null; - var tablet = null; - function onAddressBarShown(visible) { - if (toolBar) { - button.editProperties({isActive: visible}); - } - } function onClicked(){ - if (toolBar) { - DialogsManager.toggleAddressBar(); - } else { - tablet.loadQMLSource(gotoQmlSource); - } + tablet.loadQMLSource(gotoQmlSource); } - if (Settings.getValue("HUDUIEnabled")) { - toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); - button = toolBar.addButton({ - objectName: buttonName, - imageURL: Script.resolvePath("assets/images/tools/directory.svg"), - visible: true, - alpha: 0.9 - }); - } else { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - button = tablet.addButton({ - icon: "icons/tablet-icons/goto-i.svg", - activeIcon: "icons/tablet-icons/goto-a.svg", - text: buttonName, - sortOrder: 8 - }); - } - + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + icon: "icons/tablet-icons/goto-i.svg", + activeIcon: "icons/tablet-icons/goto-a.svg", + text: buttonName, + sortOrder: 8 + }); + button.clicked.connect(onClicked); - DialogsManager.addressBarShown.connect(onAddressBarShown); - + Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); if (tablet) { tablet.removeButton(button); } - if (toolBar) { - toolBar.removeButton(buttonName); - } - DialogsManager.addressBarShown.disconnect(onAddressBarShown); }); - + }()); // END LOCAL_SCOPE diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index 1dc6b7fef8..632cb40bb5 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -52,6 +52,15 @@ } function updateShowTablet() { + + // close the WebTablet if it we go into toolbar mode. + var toolbarMode = Tablet.getTablet("com.highfidelity.interface.tablet.system").toolbarMode; + if (tabletShown && toolbarMode) { + hideTabletUI(); + HMD.closeTablet(); + return; + } + if (tabletShown) { var MUTE_MICROPHONE_MENU_ITEM = "Mute Microphone"; var currentMicEnabled = !Menu.isOptionChecked(MUTE_MICROPHONE_MENU_ITEM); @@ -67,7 +76,7 @@ // other reason, close the tablet. hideTabletUI(); HMD.closeTablet(); - } else if (HMD.showTablet && !tabletShown) { + } else if (HMD.showTablet && !tabletShown && !toolbarMode) { UserActivityLogger.openedTablet(); showTabletUI(); } else if (!HMD.showTablet && tabletShown) {