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 @@
+
+
+
+
\ 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 @@
+
+
+
+
\ 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 @@
+
+
+
+
\ 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) {