From f8055a027ec14095de24e3637e1d3c96750f6010 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 10 Feb 2017 15:06:55 -0800 Subject: [PATCH 01/32] Refactor of pal.js to dynamically switch between toolbar and tablet. --- scripts/system/pal.js | 258 ++++++++++++++++++++++++------------------ 1 file changed, 146 insertions(+), 112 deletions(-) diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 2e07a2d431..3e52eec4dd 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, OverlayWindow, Toolbars, Vec3, Quat, Controller, print, getControllerWorldLocation */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ // // pal.js // @@ -13,21 +14,24 @@ (function() { // BEGIN LOCAL_SCOPE -// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed +// 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; @@ -87,24 +91,24 @@ ExtendedOverlay.prototype.hover = function (hovering) { } else { lastHoveringId = 0; } - } + } this.editOverlay({color: color(this.selected, hovering, this.audioLevel)}); if (this.model) { this.model.editOverlay({textures: textures(this.selected, 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; } - + UserActivityLogger.palAction(selected ? "avatar_selected" : "avatar_deselected", this.key); this.editOverlay({color: color(selected, this.hovering, this.audioLevel)}); @@ -204,6 +208,7 @@ var pal = new OverlayWindow({ 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 +255,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,9 +266,9 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } function sendToQml(message) { - if (Settings.getValue("HUDUIEnabled")) { + if (currentUIMode === "toolbar") { pal.sendToQml(message); - } else { + } else if (currentUIMode === "tablet") { tablet.sendToQml(message); } } @@ -273,7 +278,7 @@ function sendToQml(message) { // function addAvatarNode(id) { var selected = ExtendedOverlay.isSelected(id); - return new ExtendedOverlay(id, "sphere", { + return new ExtendedOverlay(id, "sphere", { drawInFront: true, solid: true, alpha: 0.8, @@ -339,7 +344,7 @@ function updateOverlays() { if (!id) { return; // don't update ourself } - + var overlay = ExtendedOverlay.get(id); if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. print('Adding non-PAL avatar node', id); @@ -349,7 +354,7 @@ function updateOverlays() { var target = avatar.position; var distance = Vec3.distance(target, eye); var offset = 0.2; - + // base offset on 1/2 distance from hips to head if we can var headIndex = avatar.getJointIndex("Head"); if (headIndex > 0) { @@ -358,7 +363,7 @@ function updateOverlays() { // get diff between target and eye (a vector pointing to the eye from avatar position) var diff = Vec3.subtract(target, eye); - + // move a bit in front, towards the camera target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); @@ -369,12 +374,12 @@ function updateOverlays() { overlay.editOverlay({ color: color(ExtendedOverlay.isSelected(id), overlay.hovering, overlay.audioLevel), position: target, - dimensions: 0.032 * distance + dimensions: 0.032 * distance }); if (overlay.model) { overlay.model.ping = pingPong; overlay.model.editOverlay({ - position: target, + position: target, scale: 0.2 * distance, // constant apparent size rotation: Camera.orientation }); @@ -393,7 +398,9 @@ function removeOverlays() { selectedIds = []; lastHoveringId = 0; HighlightedEntity.clearOverlays(); - ExtendedOverlay.some(function (overlay) { overlay.deleteOverlay(); }); + ExtendedOverlay.some(function (overlay) { + overlay.deleteOverlay(); + }); } // @@ -423,12 +430,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 @@ -441,18 +449,18 @@ function handleMouseMoveEvent(event) { // find out which overlay (if any) is ove handleMouseMove(pickRay); } function handleTriggerPressed(hand, value) { - // The idea is if you press one trigger, it is the one + // 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; - } + } // otherwise, the other hand is still triggered // so do nothing. } @@ -478,7 +486,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)); @@ -491,25 +499,56 @@ 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 { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - button = tablet.addButton({ - text: buttonName, - icon: "icons/tablet-icons/people-i.svg", - sortOrder: 7 - }); - tablet.fromQml.connect(fromQml); + +var currentUIMode; + +function onTabletScreenChanged(type, url) { + if (type !== "QML" || url !== "../Pal.qml") { + off(); + } } +// @param mode {string} "tablet" or "toolbar" +function startup(mode) { + if (mode === "toolbar") { + 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); + button.clicked.connect(onToolbarButtonClicked); + pal.visibleChanged.connect(onVisibleChanged); + pal.closed.connect(off); + } else if (mode === "tablet") { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + text: buttonName, + icon: "icons/tablet-icons/people-i.svg", + sortOrder: 7 + }); + tablet.fromQml.connect(fromQml); + button.clicked.connect(onTabletButtonClicked); + tablet.screenChanged.connect(onTabletScreenChanged); + } else { + print("ERROR: pal.js: bad ui mode"); + } + + Users.usernameFromIDReply.connect(usernameFromIDReply); + Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); + Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); + Messages.subscribe(CHANNEL); + Messages.messageReceived.connect(receiveMessage); + Users.avatarDisconnected.connect(avatarDisconnected); + + currentUIMode = mode; +} + +// var mode = Settings.getValue("HUDUIEnabled"); +startup(HMD.active ? "tablet" : "toolbar"); + var isWired = false; var audioTimer; var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too) @@ -521,33 +560,20 @@ 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"); + +function onToolbarButtonClicked() { + if (!pal.visible) { Users.requestsDomainListData = true; populateUserList(); + pal.raise(); isWired = true; Script.update.connect(updateOverlays); Controller.mousePressEvent.connect(handleMouseEvent); @@ -555,7 +581,23 @@ function onClicked() { triggerMapping.enable(); triggerPressMapping.enable(); audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); + } else { + off(); } + pal.setVisible(!pal.visible); +} + +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); } // @@ -570,8 +612,8 @@ function receiveMessage(channel, messageString, senderID) { var message = JSON.parse(messageString); switch (message.method) { case 'select': - if (!pal.visible) { - onClicked(); + if (currentUIMode === "toolbar" && !pal.visible) { + onToolbarButtonClicked(); } sendToQml(message); // Accepts objects, not just strings. break; @@ -579,8 +621,6 @@ function receiveMessage(channel, messageString, senderID) { print('Unrecognized PAL message', messageString); } } -Messages.subscribe(CHANNEL); -Messages.messageReceived.connect(receiveMessage); var AVERAGING_RATIO = 0.05; @@ -638,57 +678,51 @@ 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 + if (currentUIMode === "toolbar" && pal.visible) { + onToolbarButtonClicked(); // Close the PAL } } -Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); -Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); + +function shutdown() { + if (currentUIMode === "toolbar") { + button.clicked.disconnect(onToolbarButtonClicked); + toolBar.removeButton(buttonName); + pal.visibleChanged.disconnect(onVisibleChanged); + pal.closed.disconnect(off); + } else if (currentUIMode === "tablet") { + 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); + +HMD.displayModeChanged.connect(function () { + shutdown(); + startup(HMD.active ? "tablet" : "toolbar"); }); - }()); // END LOCAL_SCOPE From 5e25d073f3492da7709b64d50984a1a17c557b5e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Feb 2017 13:19:13 -0800 Subject: [PATCH 02/32] WIP commit --- interface/src/Application.cpp | 2 + .../src/TabletScriptingInterface.cpp | 65 ++++++++++++++++++- .../src/TabletScriptingInterface.h | 21 +++++- 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 488e97b5e6..745cadbbdd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5462,6 +5462,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, diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index 7e8fdd6bc3..d048da93d2 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -22,6 +22,22 @@ 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); @@ -145,6 +161,21 @@ TabletProxy::TabletProxy(QString name) : _name(name) { ; } +void TabletProxy::setToolbarMode(bool toolbarMode) { + if (toolbarMode == _toolbarMode) { + return; + } + + if (toolbarMode) { + removeButtonsFromHomeScreen(); + addButtonsToToolbar(); + } else { + removeButtonsFromToolbar(); + addButtonsToHomeScreen(); + } + _toolbarMode = toolbarMode; +} + static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) { QVariant resultVar; Qt::ConnectionType connectionType = Qt::AutoConnection; @@ -379,12 +410,34 @@ void TabletProxy::addButtonsToMenuScreen() { } void TabletProxy::removeButtonsFromHomeScreen() { - auto tabletScriptingInterface = DependencyManager::get(); for (auto& buttonProxy : _tabletButtonProxies) { buttonProxy->setQmlButton(nullptr); } } +void TabletProxy::addButtonsToToolbar() { + auto tabletScriptingInterface = DependencyManager::get(); + QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); + for (auto& buttonProxy : _tabletButtonProxies) { + // copy properties from tablet button proxy to toolbar button proxy. + Qt::ConnectionType connectionType = Qt::AutoConnection; + if (QThread::currentThread() != toolbarProxy->thread()) { + connectionType = Qt::BlockingQueuedConnection; + } + 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"; + } + } +} + +void TabletProxy::removeButtonsFromToolbar() { + // +} + QQuickItem* TabletProxy::getQmlTablet() const { if (!_qmlTabletRoot) { return nullptr; @@ -444,6 +497,11 @@ void TabletButtonProxy::setQmlButton(QQuickItem* qmlButton) { _qmlButton = qmlButton; } +void TabletButtonProxy::setToolbarButtonProxy(QObject* toolbarButtonProxy) { + std::lock_guard guard(_mutex); + _toolbarButtonProxy = toolbarButtonProxy; +} + QVariantMap TabletButtonProxy::getProperties() const { std::lock_guard guard(_mutex); return _properties; @@ -451,6 +509,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 +518,10 @@ void TabletButtonProxy::editProperties(QVariantMap properties) { } ++iter; } + + if (_toolbarButtonProxy) { + QMetaObject::invokeMethod(_toolbarButtonProxy, "editProperties", Qt::AutoConnection, Q_ARG(QVariant, properties)); + } } #include "TabletScriptingInterface.moc" diff --git a/libraries/script-engine/src/TabletScriptingInterface.h b/libraries/script-engine/src/TabletScriptingInterface.h index 8ba69ccdde..b2f9f2ab47 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.h +++ b/libraries/script-engine/src/TabletScriptingInterface.h @@ -35,6 +35,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 @@ -58,15 +61,19 @@ private: protected: std::mutex _mutex; std::map> _tabletProxies; + QObject* _toolbarScriptingInterface { nullptr }; }; /**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 +81,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 +132,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 @@ -169,17 +179,20 @@ signals: */ void screenChanged(QVariant type, QVariant url); -private slots: +protected slots: void addButtonsToHomeScreen(); void addButtonsToMenuScreen(); protected: void removeButtonsFromHomeScreen(); + void addButtonsToToolbar(); + void removeButtonsFromToolbar(); QString _name; std::mutex _mutex; std::vector> _tabletButtonProxies; QQuickItem* _qmlTabletRoot { nullptr }; QObject* _qmlOffscreenSurface { nullptr }; + bool _toolbarMode { false }; enum class State { Uninitialized, Home, Web, Menu, QML }; State _state { State::Uninitialized }; @@ -196,6 +209,7 @@ public: TabletButtonProxy(const QVariantMap& properties); void setQmlButton(QQuickItem* qmlButton); + void setToolbarButtonProxy(QObject* toolbarButtonProxy); QUuid getUuid() const { return _uuid; } @@ -229,6 +243,7 @@ protected: int _stableOrder; mutable std::mutex _mutex; QQuickItem* _qmlButton { nullptr }; + QObject* _toolbarButtonProxy { nullptr }; QVariantMap _properties; }; From 45039236651f197e8b7e4521d3b400b4d49cfa33 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Feb 2017 17:43:03 -0800 Subject: [PATCH 03/32] Initial version of TabletProxy.toolbarMode boolean Copies all tablet buttons between the tablet and the system toolbar. To test type this in the console: Tablet.getTablet("com.highfidelity.interface.tablet.system").toolbarMode = true; --- .../resources/icons/tablet-icons/blank.svg | 48 +++++++++++ .../tablet-icons/empty-toolbar-button.svg | 79 +++++++++++++++++++ .../qml/hifi/toolbars/StateImage.qml | 1 + .../qml/hifi/toolbars/ToolbarButton.qml | 36 ++++++++- .../src/TabletScriptingInterface.cpp | 29 +++++-- 5 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/blank.svg create mode 100644 interface/resources/icons/tablet-icons/empty-toolbar-button.svg diff --git a/interface/resources/icons/tablet-icons/blank.svg b/interface/resources/icons/tablet-icons/blank.svg new file mode 100644 index 0000000000..ae463c4242 --- /dev/null +++ b/interface/resources/icons/tablet-icons/blank.svg @@ -0,0 +1,48 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/empty-toolbar-button.svg b/interface/resources/icons/tablet-icons/empty-toolbar-button.svg new file mode 100644 index 0000000000..5ba101c618 --- /dev/null +++ b/interface/resources/icons/tablet-icons/empty-toolbar-button.svg @@ -0,0 +1,79 @@ + + + +image/svg+xml \ No newline at end of file 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/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index 91c992bf0d..0e4423268b 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -11,12 +11,23 @@ StateImage { property int imageOnOut: 0 property int imageOnIn: 2 + property string text: "" + property string icon: "icons/tablet-icons/blank.svg" + 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 +49,7 @@ StateImage { running: false onTriggered: button.clicked(); } - + MouseArea { id: mouseArea hoverEnabled: true @@ -53,5 +64,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.icon) + } + + Text { + id: caption + color: "#ffffff" + text: 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/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index d048da93d2..576fec0ca3 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -166,6 +166,8 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { return; } + _toolbarMode = toolbarMode; + if (toolbarMode) { removeButtonsFromHomeScreen(); addButtonsToToolbar(); @@ -173,7 +175,6 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { removeButtonsFromToolbar(); addButtonsToHomeScreen(); } - _toolbarMode = toolbarMode; } static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) { @@ -373,7 +374,7 @@ void TabletProxy::sendToQml(QVariant msg) { void TabletProxy::addButtonsToHomeScreen() { auto tablet = getQmlTablet(); - if (!tablet) { + if (!tablet || _toolbarMode) { return; } @@ -410,7 +411,11 @@ void TabletProxy::addButtonsToMenuScreen() { } 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); } } @@ -418,12 +423,14 @@ void TabletProxy::removeButtonsFromHomeScreen() { 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) { // copy properties from tablet button proxy to toolbar button proxy. - Qt::ConnectionType connectionType = Qt::AutoConnection; - if (QThread::currentThread() != toolbarProxy->thread()) { - connectionType = Qt::BlockingQueuedConnection; - } QObject* toolbarButtonProxy = nullptr; bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, buttonProxy->getProperties())); if (hasResult) { @@ -435,7 +442,13 @@ void TabletProxy::addButtonsToToolbar() { } 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); + } } QQuickItem* TabletProxy::getQmlTablet() const { @@ -483,12 +496,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; } From 912e8aa04a18cd486a4baaf68a5d6e5aaeb88816 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 14 Feb 2017 15:18:24 -0800 Subject: [PATCH 04/32] Tablet buttons now work in toolbar. Switching between tablet and toolbar is not as dynamic as I'd like but it's a start. --- .../resources/qml/hifi/tablet/TabletRoot.qml | 4 +- .../resources/qml/hifi/tablet/WindowRoot.qml | 94 +++++++++++++++ .../qml/hifi/toolbars/ToolbarButton.qml | 8 +- interface/resources/qml/windows/Window.qml | 4 + .../src/TabletScriptingInterface.cpp | 113 ++++++++++++++---- .../src/TabletScriptingInterface.h | 2 + libraries/ui/src/InfoView.cpp | 26 ++-- scripts/system/pal.js | 7 +- 8 files changed, 211 insertions(+), 47 deletions(-) create mode 100644 interface/resources/qml/hifi/tablet/WindowRoot.qml diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 481c7846a9..792c8d5082 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -33,6 +33,8 @@ Item { } } + function setShown(value) {} + SoundEffect { id: buttonClickSound volume: 0.1 @@ -85,5 +87,5 @@ Item { } width: 480 - height: 720 + height: 706 } diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml new file mode 100644 index 0000000000..14e969b3a6 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -0,0 +1,94 @@ +// +// 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; + shown: false + resizable: false + + signal showDesktop(); + + function loadSource(url) { + 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 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); + } + loader.item.forceActiveFocus(); + } + } + + implicitWidth: 480 + implicitHeight: 706 +} diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index 0e4423268b..591544d614 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -12,7 +12,9 @@ StateImage { property int imageOnIn: 2 property string text: "" + property string activeText: button.text property string icon: "icons/tablet-icons/blank.svg" + property string activeIcon: button.icon signal clicked() @@ -73,13 +75,13 @@ StateImage { anchors.bottomMargin: 0 anchors.horizontalCenter: parent.horizontalCenter fillMode: Image.Stretch - source: urlHelper(button.icon) + source: urlHelper(button.isActive ? button.activeIcon : button.icon) } Text { id: caption - color: "#ffffff" - text: button.text + color: button.isActive ? "#000000" : "#ffffff" + text: button.isActive ? button.activeText : button.text font.bold: false font.pixelSize: 9 anchors.bottom: parent.bottom 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/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index 576fec0ca3..dc6b1d6aa3 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -15,7 +15,8 @@ #include #include "ScriptEngineLogging.h" #include "DependencyManager.h" -#include "OffscreenUi.h" +#include +#include #include "SoundEffect.h" TabletScriptingInterface::TabletScriptingInterface() { @@ -156,9 +157,10 @@ QObject* TabletScriptingInterface::getFlags() 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"; +static int s_windowNameCounter = 1; TabletProxy::TabletProxy(QString name) : _name(name) { - ; + } void TabletProxy::setToolbarMode(bool toolbarMode) { @@ -171,9 +173,27 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { if (toolbarMode) { removeButtonsFromHomeScreen(); addButtonsToToolbar(); + + // create new desktop window + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([=, this] { + InfoView::registerType(); + QString name = _name + QString("%1").arg(s_windowNameCounter++); + offscreenUi->show(QUrl("hifi/tablet/WindowRoot.qml"), name, [&](QQmlContext* context, QObject* newObject) { + QQuickItem* item = dynamic_cast(newObject); + _desktopWindow = item; + QObject::connect(_desktopWindow, SIGNAL(windowClosed), this, SLOT(desktopWindowClosed())); + }); + }); } else { removeButtonsFromToolbar(); addButtonsToHomeScreen(); + + // destroy desktop window + if (_desktopWindow) { + _desktopWindow->deleteLater(); + _desktopWindow = nullptr; + } } } @@ -246,39 +266,63 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr } void TabletProxy::gotoMenuScreen(const QString& submenu) { - if (_qmlTabletRoot) { + + QObject* root = nullptr; + if (!_toolbarMode && _qmlTabletRoot) { + root = _qmlTabletRoot; + } else if (_toolbarMode && _desktopWindow) { + root = _desktopWindow; + } + + if (root) { if (_state != State::Menu) { removeButtonsFromHomeScreen(); - QMetaObject::invokeMethod(_qmlTabletRoot, "setOption", Q_ARG(const QVariant&, QVariant(submenu))); - auto loader = _qmlTabletRoot->findChild("loader"); + QMetaObject::invokeMethod(root, "setOption", Q_ARG(const QVariant&, QVariant(submenu))); + auto loader = root->findChild("loader"); QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToMenuScreen()), Qt::DirectConnection); - QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL))); + 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; + } + + 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) { + QMetaObject::invokeMethod(_desktopWindow, "setShown", Q_ARG(const QVariant&, QVariant(false))); + } } + _state = State::Home; + emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL)); } } @@ -287,17 +331,24 @@ void TabletProxy::gotoWebScreen(const QString& url) { } void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl) { - if (_qmlTabletRoot) { - if (_state == State::Home) { - removeButtonsFromHomeScreen(); + + QObject* root = nullptr; + if (!_toolbarMode && _qmlTabletRoot) { + root = _qmlTabletRoot; + } else if (_toolbarMode && _desktopWindow) { + root = _desktopWindow; + } + + if (_state != State::Web) { + if (root) { + QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL))); } - 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))); + _state = State::Web; + emit screenChanged(QVariant("Web"), QVariant(url)); + } + if (root) { + 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))); } } @@ -391,11 +442,18 @@ QObject* TabletProxy::getTabletSurface() { } void TabletProxy::addButtonsToMenuScreen() { - if (!_qmlTabletRoot) { + QObject* root = nullptr; + if (!_toolbarMode && _qmlTabletRoot) { + root = _qmlTabletRoot; + } else if (_toolbarMode && _desktopWindow) { + root = _desktopWindow; + } + + if (!root) { return; } - auto loader = _qmlTabletRoot->findChild("loader"); + auto loader = root->findChild("loader"); if (!loader) { return; } @@ -420,6 +478,10 @@ void TabletProxy::removeButtonsFromHomeScreen() { } } +void TabletProxy::desktopWindowClosed() { + gotoHomeScreen(); +} + void TabletProxy::addButtonsToToolbar() { auto tabletScriptingInterface = DependencyManager::get(); QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); @@ -515,6 +577,9 @@ void TabletButtonProxy::setQmlButton(QQuickItem* 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 { diff --git a/libraries/script-engine/src/TabletScriptingInterface.h b/libraries/script-engine/src/TabletScriptingInterface.h index b2f9f2ab47..c9aee518fc 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.h +++ b/libraries/script-engine/src/TabletScriptingInterface.h @@ -182,6 +182,7 @@ signals: protected slots: void addButtonsToHomeScreen(); void addButtonsToMenuScreen(); + void desktopWindowClosed(); protected: void removeButtonsFromHomeScreen(); void addButtonsToToolbar(); @@ -192,6 +193,7 @@ protected: std::vector> _tabletButtonProxies; QQuickItem* _qmlTabletRoot { nullptr }; QObject* _qmlOffscreenSurface { nullptr }; + QQuickItem* _desktopWindow { nullptr }; bool _toolbarMode { false }; enum class State { Uninitialized, Home, Web, Menu, QML }; 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/scripts/system/pal.js b/scripts/system/pal.js index 3e52eec4dd..de869ad7de 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -547,7 +547,7 @@ function startup(mode) { } // var mode = Settings.getValue("HUDUIEnabled"); -startup(HMD.active ? "tablet" : "toolbar"); +startup("tablet"); var isWired = false; var audioTimer; @@ -720,9 +720,4 @@ function shutdown() { // Script.scriptEnding.connect(shutdown); -HMD.displayModeChanged.connect(function () { - shutdown(); - startup(HMD.active ? "tablet" : "toolbar"); -}); - }()); // END LOCAL_SCOPE From ed11edad56a760c811db401cc54325a2b35d61c0 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 15 Feb 2017 13:59:04 -0800 Subject: [PATCH 05/32] EventBridge support --- .../resources/qml/hifi/tablet/TabletRoot.qml | 1 + .../resources/qml/hifi/tablet/WindowRoot.qml | 1 + .../src/TabletScriptingInterface.cpp | 83 +++++++++++++------ .../src/TabletScriptingInterface.h | 3 +- libraries/ui/src/QmlWindowClass.h | 6 +- 5 files changed, 65 insertions(+), 29 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 792c8d5082..edc2666c27 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -15,6 +15,7 @@ Item { } function loadSource(url) { + loader.source = ""; // HACK: make sure we load the qml fresh each time. loader.source = url; } diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml index 14e969b3a6..6fb41512c2 100644 --- a/interface/resources/qml/hifi/tablet/WindowRoot.qml +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -25,6 +25,7 @@ Windows.ScrollingWindow { signal showDesktop(); function loadSource(url) { + loader.source = ""; // HACK: make sure we load the qml fresh each time. loader.source = url; } diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index dc6b1d6aa3..3142e27d15 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -11,10 +11,11 @@ #include #include +#include "DependencyManager.h" #include +#include #include #include "ScriptEngineLogging.h" -#include "DependencyManager.h" #include #include #include "SoundEffect.h" @@ -159,6 +160,10 @@ static const char* WEB_VIEW_SOURCE_URL = "TabletWebView.qml"; static const char* VRMENU_SOURCE_URL = "TabletMenu.qml"; static int s_windowNameCounter = 1; +class TabletRootWindow : public QmlWindowClass { + virtual QString qmlSource() const { return "hifi/tablet/WindowRoot.qml"; } +}; + TabletProxy::TabletProxy(QString name) : _name(name) { } @@ -177,13 +182,16 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { // create new desktop window auto offscreenUi = DependencyManager::get(); offscreenUi->executeOnUiThread([=, this] { - InfoView::registerType(); - QString name = _name + QString("%1").arg(s_windowNameCounter++); - offscreenUi->show(QUrl("hifi/tablet/WindowRoot.qml"), name, [&](QQmlContext* context, QObject* newObject) { - QQuickItem* item = dynamic_cast(newObject); - _desktopWindow = item; - QObject::connect(_desktopWindow, SIGNAL(windowClosed), this, SLOT(desktopWindowClosed())); - }); + 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(tabletRootWindow, SIGNAL(webEventReceived(QVariant)), this, SIGNAL(webEventReceived(QVariant))); + + // forward qml surface events to interface js + connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml); }); } else { removeButtonsFromToolbar(); @@ -271,7 +279,7 @@ void TabletProxy::gotoMenuScreen(const QString& submenu) { if (!_toolbarMode && _qmlTabletRoot) { root = _qmlTabletRoot; } else if (_toolbarMode && _desktopWindow) { - root = _desktopWindow; + root = _desktopWindow->asQuickItem(); } if (root) { @@ -294,7 +302,7 @@ void TabletProxy::loadQMLSource(const QVariant& path) { if (!_toolbarMode && _qmlTabletRoot) { root = _qmlTabletRoot; } else if (_toolbarMode && _desktopWindow) { - root = _desktopWindow; + root = _desktopWindow->asQuickItem(); } if (root) { @@ -317,8 +325,8 @@ void TabletProxy::gotoHomeScreen() { QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound"); } else if (_toolbarMode && _desktopWindow) { // close desktop window - if (_desktopWindow) { - QMetaObject::invokeMethod(_desktopWindow, "setShown", Q_ARG(const QVariant&, QVariant(false))); + if (_desktopWindow->asQuickItem()) { + QMetaObject::invokeMethod(_desktopWindow->asQuickItem(), "setShown", Q_ARG(const QVariant&, QVariant(false))); } } _state = State::Home; @@ -336,33 +344,47 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS if (!_toolbarMode && _qmlTabletRoot) { root = _qmlTabletRoot; } else if (_toolbarMode && _desktopWindow) { - root = _desktopWindow; + root = _desktopWindow->asQuickItem(); } - if (_state != State::Web) { - if (root) { - QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL))); - } - _state = State::Web; - emit screenChanged(QVariant("Web"), QVariant(url)); - } 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(); } @@ -381,11 +403,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 { @@ -412,14 +441,18 @@ 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)); } } @@ -446,7 +479,7 @@ void TabletProxy::addButtonsToMenuScreen() { if (!_toolbarMode && _qmlTabletRoot) { root = _qmlTabletRoot; } else if (_toolbarMode && _desktopWindow) { - root = _desktopWindow; + root = _desktopWindow->asQuickItem(); } if (!root) { diff --git a/libraries/script-engine/src/TabletScriptingInterface.h b/libraries/script-engine/src/TabletScriptingInterface.h index c9aee518fc..88e1cca60b 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 @@ -193,7 +194,7 @@ protected: std::vector> _tabletButtonProxies; QQuickItem* _qmlTabletRoot { nullptr }; QObject* _qmlOffscreenSurface { nullptr }; - QQuickItem* _desktopWindow { nullptr }; + QmlWindowClass* _desktopWindow { nullptr }; bool _toolbarMode { false }; enum class State { Uninitialized, Home, Web, Menu, QML }; 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 }; From 67302cbd2bd0883584caeffd0d8ba2384bc3d123 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 15 Feb 2017 15:53:08 -0800 Subject: [PATCH 06/32] toolbar work to better support tablet buttons --- .../tablet-icons/empty-toolbar-button.svg | 20 +++++++------ .../resources/qml/hifi/tablet/TabletRoot.qml | 4 +-- .../resources/qml/hifi/tablet/WindowRoot.qml | 8 +++++ .../resources/qml/hifi/toolbars/Toolbar.qml | 29 +++++++++++++++++++ .../qml/hifi/toolbars/ToolbarButton.qml | 12 ++++++-- libraries/script-engine/src/SoundEffect.cpp | 3 -- .../src/TabletScriptingInterface.cpp | 11 ++++++- 7 files changed, 70 insertions(+), 17 deletions(-) diff --git a/interface/resources/icons/tablet-icons/empty-toolbar-button.svg b/interface/resources/icons/tablet-icons/empty-toolbar-button.svg index 5ba101c618..19791e6c29 100644 --- a/interface/resources/icons/tablet-icons/empty-toolbar-button.svg +++ b/interface/resources/icons/tablet-icons/empty-toolbar-button.svg @@ -20,7 +20,7 @@ sodipodi:docname="empty-toolbar-button.svg">image/svg+xml \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/finger-paint-i.svg b/interface/resources/icons/tablet-icons/finger-paint-i.svg new file mode 100644 index 0000000000..d7c8948e01 --- /dev/null +++ b/interface/resources/icons/tablet-icons/finger-paint-i.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/fingerPaint.js b/scripts/system/fingerPaint.js new file mode 100644 index 0000000000..c154f2d81d --- /dev/null +++ b/scripts/system/fingerPaint.js @@ -0,0 +1,44 @@ +// +// 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; + + function onButtonClicked() { + isFingerPainting = !isFingerPainting; + button.editProperties({ isActive: isFingerPainting }); + } + + function setUp() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + if (tablet) { + button = tablet.addButton({ + icon: "icons/tablet-icons/bubble-i.svg", + activeIcon: "icons/tablet-icons/bubble-a.svg", + text: BUTTON_NAME, + isActive: isFingerPainting + }); + button.clicked.connect(onButtonClicked); + } + } + + function tearDown() { + if (tablet) { + button.clicked.disconnect(onButtonClicked); + tablet.removeButton(button); + } + } + + setUp(); + Script.scriptEnding.connect(tearDown); +}()); \ No newline at end of file From bad57fa03f4f6e792ca9f2bbde9f0d6155a08a27 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 17 Feb 2017 08:50:55 +1300 Subject: [PATCH 16/32] Controller and painting code framework --- scripts/system/fingerPaint.js | 153 +++++++++++++++++++++++++++++++--- 1 file changed, 141 insertions(+), 12 deletions(-) diff --git a/scripts/system/fingerPaint.js b/scripts/system/fingerPaint.js index c154f2d81d..7f1f90c640 100644 --- a/scripts/system/fingerPaint.js +++ b/scripts/system/fingerPaint.js @@ -12,31 +12,160 @@ var tablet, button, BUTTON_NAME = "PAINT", - isFingerPainting = false; + isFingerPainting = false, + leftHand, + rightHand, + leftBrush, + rightBrush, + CONTROLLER_MAPPING_NAME = "com.highfidelity.fingerPaint"; + + function paintBrush(name) { + // Paints in 3D. + + var brushName = name, + BRUSH_COLOR = { red: 250, green: 0, blue: 0 }, + ERASE_SEARCH_DISTANCE = 0.1; // m + + function startLine(position, width) { + } + + function drawLine(position, width) { + } + + function finishLine(position, width) { + } + + function cancelLine() { + } + + function eraseClosestLine(position) { + } + + function tearDown() { + } + + 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, + triggerPressed, // Callback. + triggerPressing, // "" + triggerReleased, // "" + gripPressed, // "" + isEnabled = false; + + function setEnabled(enabled) { + isEnabled = enabled; + } + + function onTriggerPress(value) { + if (!isEnabled) { + return; + } + } + + function onGripPress(value) { + if (!isEnabled) { + return; + } + } + + function tearDown() { + } + + return { + setEnabled: setEnabled, + onTriggerPress: onTriggerPress, + onGripPress: onGripPress, + triggerPressed: triggerPressed, + triggerPressing: triggerPressing, + triggerReleased: triggerReleased, + gripPressed: gripPressed, + tearDown: tearDown + }; + } function onButtonClicked() { + var wasFingerPainting = isFingerPainting; + isFingerPainting = !isFingerPainting; button.editProperties({ isActive: isFingerPainting }); + + leftHand.setEnabled(isFingerPainting); + rightHand.setEnabled(isFingerPainting); + + if (wasFingerPainting) { + leftBrush.cancelLine(); + rightBrush.cancelLine(); + } } function setUp() { tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - if (tablet) { - button = tablet.addButton({ - icon: "icons/tablet-icons/bubble-i.svg", - activeIcon: "icons/tablet-icons/bubble-a.svg", - text: BUTTON_NAME, - isActive: isFingerPainting - }); - button.clicked.connect(onButtonClicked); + if (!tablet) { + return; } + + // Tablet button. + button = tablet.addButton({ + icon: "icons/tablet-icons/bubble-i.svg", + activeIcon: "icons/tablet-icons/bubble-a.svg", + text: BUTTON_NAME, + isActive: isFingerPainting + }); + button.clicked.connect(onButtonClicked); + + // 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.triggerPressed = leftBrush.startLine; + leftHand.triggerPressing = leftBrush.drawLine; + leftHand.trigerRelease = leftBrush.finishLine; + leftHand.gripPressed = leftBrush.eraseLine; + rightBrush = paintBrush("right"); + rightHand.triggerPressed = rightBrush.startLine; + rightHand.triggerPressing = rightBrush.drawLine; + rightHand.trigerRelease = rightBrush.finishLine; + rightHand.gripPressed = rightBrush.eraseLine; } function tearDown() { - if (tablet) { - button.clicked.disconnect(onButtonClicked); - tablet.removeButton(button); + if (!tablet) { + return; } + + button.clicked.disconnect(onButtonClicked); + tablet.removeButton(button); + + Controller.disableMapping(CONTROLLER_MAPPING_NAME); + + leftBrush.tearDown(); + leftBrush = null; + leftHand.tearDown(); + leftHand = null; + + rightBrush.tearDown(); + rightBrush = null; + rightHand.tearDown(); + rightHand = null; } setUp(); From 867e24762b713182be2515b86a8a09922461a83e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 17 Feb 2017 09:23:07 +1300 Subject: [PATCH 17/32] Disable lasers and grabbing while painting if tablet not displayed --- scripts/system/fingerPaint.js | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/scripts/system/fingerPaint.js b/scripts/system/fingerPaint.js index 7f1f90c640..d35e053d6b 100644 --- a/scripts/system/fingerPaint.js +++ b/scripts/system/fingerPaint.js @@ -17,7 +17,9 @@ rightHand, leftBrush, rightBrush, - CONTROLLER_MAPPING_NAME = "com.highfidelity.fingerPaint"; + CONTROLLER_MAPPING_NAME = "com.highfidelity.fingerPaint", + isTabletDisplayed = false, + HIFI_GRAB_DISABLE_MESSAGE_CHANNEL = "Hifi-Grab-Disable"; function paintBrush(name) { // Paints in 3D. @@ -94,6 +96,17 @@ }; } + function updateHandControllerGrab() { + // Send message to handControllerGrab.js to handle. + var enabled = !isFingerPainting || isTabletDisplayed; + + Messages.sendMessage(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ + holdEnabled: enabled, + nearGrabEnabled: enabled, + farGrabEnabled: enabled + })); + } + function onButtonClicked() { var wasFingerPainting = isFingerPainting; @@ -107,6 +120,15 @@ leftBrush.cancelLine(); rightBrush.cancelLine(); } + + updateHandControllerGrab(); + } + + function onTabletScreenChanged(type, url) { + var TABLET_SCREEN_CLOSED = "Closed"; + + isTabletDisplayed = type !== TABLET_SCREEN_CLOSED; + updateHandControllerGrab(); } function setUp() { @@ -124,6 +146,9 @@ }); button.clicked.connect(onButtonClicked); + // Track whether tablet is displayed or not. + tablet.screenChanged.connect(onTabletScreenChanged); + // Connect controller API to handController objects. leftHand = handController("left"); rightHand = handController("right"); @@ -145,6 +170,9 @@ rightHand.triggerPressing = rightBrush.drawLine; rightHand.trigerRelease = rightBrush.finishLine; rightHand.gripPressed = rightBrush.eraseLine; + + // Messages channel for disabling/enabling laser pointers and grabbing. + Messages.subscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); } function tearDown() { @@ -155,6 +183,8 @@ button.clicked.disconnect(onButtonClicked); tablet.removeButton(button); + tablet.screenChanged.disconnect(onTabletScreenChanged); + Controller.disableMapping(CONTROLLER_MAPPING_NAME); leftBrush.tearDown(); @@ -166,6 +196,8 @@ rightBrush = null; rightHand.tearDown(); rightHand = null; + + Messages.unsubscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); } setUp(); From 7b36669d8026ebbd9befb52c8ad2194a0171102a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 17 Feb 2017 10:20:13 +1300 Subject: [PATCH 18/32] Point index finger while painting if tablet not displayed --- scripts/system/controllers/squeezeHands.js | 33 ++++++++++++++++++++++ scripts/system/fingerPaint.js | 16 +++++++---- 2 files changed, 44 insertions(+), 5 deletions(-) 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 index d35e053d6b..7dbcc4c370 100644 --- a/scripts/system/fingerPaint.js +++ b/scripts/system/fingerPaint.js @@ -19,6 +19,7 @@ rightBrush, CONTROLLER_MAPPING_NAME = "com.highfidelity.fingerPaint", isTabletDisplayed = false, + HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index", HIFI_GRAB_DISABLE_MESSAGE_CHANNEL = "Hifi-Grab-Disable"; function paintBrush(name) { @@ -96,8 +97,8 @@ }; } - function updateHandControllerGrab() { - // Send message to handControllerGrab.js to handle. + function updateHandFunctions() { + // Update other scripts' hand functions. var enabled = !isFingerPainting || isTabletDisplayed; Messages.sendMessage(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ @@ -105,6 +106,9 @@ nearGrabEnabled: enabled, farGrabEnabled: enabled })); + Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify({ + pointIndex: !enabled + })); } function onButtonClicked() { @@ -121,14 +125,14 @@ rightBrush.cancelLine(); } - updateHandControllerGrab(); + updateHandFunctions(); } function onTabletScreenChanged(type, url) { var TABLET_SCREEN_CLOSED = "Closed"; isTabletDisplayed = type !== TABLET_SCREEN_CLOSED; - updateHandControllerGrab(); + updateHandFunctions(); } function setUp() { @@ -171,7 +175,8 @@ rightHand.trigerRelease = rightBrush.finishLine; rightHand.gripPressed = rightBrush.eraseLine; - // Messages channel for disabling/enabling laser pointers and grabbing. + // Messages channels for enabling/disabling other scripts' functions. + Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); Messages.subscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); } @@ -197,6 +202,7 @@ rightHand.tearDown(); rightHand = null; + Messages.unsubscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); Messages.unsubscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); } From 5236f123c1d349dfeb713288aaa3ac05fb0c7bea Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 17 Feb 2017 12:42:10 +1300 Subject: [PATCH 19/32] Interpret trigger and grip values --- scripts/system/fingerPaint.js | 97 +++++++++++++++++++++++++++++------ 1 file changed, 80 insertions(+), 17 deletions(-) diff --git a/scripts/system/fingerPaint.js b/scripts/system/fingerPaint.js index 7dbcc4c370..4f21419a87 100644 --- a/scripts/system/fingerPaint.js +++ b/scripts/system/fingerPaint.js @@ -60,39 +60,108 @@ function handController(name) { // Translates controller data into application events. var handName = name, - triggerPressed, // Callback. - triggerPressing, // "" - triggerReleased, // "" - gripPressed, // "" - isEnabled = false; + isEnabled = false, + + triggerPressedCallback, + triggerPressingCallback, + triggerReleasedCallback, + gripPressedCallback, + + triggerValue = 0.0, + isTriggerPressed = false, + TRIGGER_SMOOTH_RATIO = 0.1, + TRIGGER_OFF = 0.05, + TRIGGER_ON = 0.1, + TRIGGER_START_WIDTH = 0.15, + TRIGGER_FINISH_WIDTH = 1.0, + TRIGGER_RANGE_WIDTH = TRIGGER_FINISH_WIDTH - TRIGGER_START_WIDTH, + START_LINE_WIDTH = 0.005, + FINISH_LINE_WIDTH = 0.010, + RANGE_LINE_WIDTH = FINISH_LINE_WIDTH - START_LINE_WIDTH, + + gripValue = 0.0, + isGripPressed = false, + GRIP_SMOOTH_RATIO = 0.1, + GRIP_OFF = 0.05, + GRIP_ON = 0.1; function setEnabled(enabled) { isEnabled = enabled; } function onTriggerPress(value) { + var wasTriggerPressed, + fingerTipPosition, + lineWidth; + if (!isEnabled) { return; } + + triggerValue = triggerValue * TRIGGER_SMOOTH_RATIO + value * (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" ? "LeftHandIndex3" : "RightHandIndex3"); + if (triggerValue < TRIGGER_START_WIDTH) { + lineWidth = START_LINE_WIDTH; + } else { + lineWidth = START_LINE_WIDTH + + (triggerValue - TRIGGER_START_WIDTH) / TRIGGER_RANGE_WIDTH * RANGE_LINE_WIDTH; + } + + if (!wasTriggerPressed && isTriggerPressed) { + triggerPressedCallback(fingerTipPosition, lineWidth); + } else if (wasTriggerPressed && isTriggerPressed) { + triggerPressingCallback(fingerTipPosition, lineWidth); + } else { + triggerReleasedCallback(fingerTipPosition, lineWidth); + } + } } function onGripPress(value) { + var fingerTipPosition; + if (!isEnabled) { return; } + + gripValue = gripValue * GRIP_SMOOTH_RATIO + value * (1.0 - GRIP_SMOOTH_RATIO); + + if (isGripPressed) { + isGripPressed = gripValue > GRIP_OFF; + } else { + isGripPressed = gripValue > GRIP_ON; + if (isGripPressed) { + fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex3" : "RightHandIndex3"); + gripPressedCallback(fingerTipPosition); + } + } + } + + function setUp(onTriggerPressed, onTriggerPressing, onTriggerReleased, onGripPressed) { + triggerPressedCallback = onTriggerPressed; + triggerPressingCallback = onTriggerPressing; + triggerReleasedCallback = onTriggerReleased; + gripPressedCallback = onGripPressed; } function tearDown() { + // Nothing to do. } return { setEnabled: setEnabled, onTriggerPress: onTriggerPress, onGripPress: onGripPress, - triggerPressed: triggerPressed, - triggerPressing: triggerPressing, - triggerReleased: triggerReleased, - gripPressed: gripPressed, + setUp: setUp, tearDown: tearDown }; } @@ -165,15 +234,9 @@ // Connect handController outputs to paintBrush objects. leftBrush = paintBrush("left"); - leftHand.triggerPressed = leftBrush.startLine; - leftHand.triggerPressing = leftBrush.drawLine; - leftHand.trigerRelease = leftBrush.finishLine; - leftHand.gripPressed = leftBrush.eraseLine; + leftHand.setUp(leftBrush.startLine, leftBrush.drawLine, leftBrush.finishLine, leftBrush.eraseClosestLine); rightBrush = paintBrush("right"); - rightHand.triggerPressed = rightBrush.startLine; - rightHand.triggerPressing = rightBrush.drawLine; - rightHand.trigerRelease = rightBrush.finishLine; - rightHand.gripPressed = rightBrush.eraseLine; + 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); From 9c1bb37a349f073f078379aa01ef295b4deddbea Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 17 Feb 2017 14:45:44 +1300 Subject: [PATCH 20/32] Paint lines --- scripts/system/fingerPaint.js | 87 +++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 5 deletions(-) diff --git a/scripts/system/fingerPaint.js b/scripts/system/fingerPaint.js index 4f21419a87..10b84b66fa 100644 --- a/scripts/system/fingerPaint.js +++ b/scripts/system/fingerPaint.js @@ -24,27 +24,104 @@ function paintBrush(name) { // Paints in 3D. - var brushName = name, - BRUSH_COLOR = { red: 250, green: 0, blue: 0 }, - ERASE_SEARCH_DISTANCE = 0.1; // m + STROKE_COLOR = { red: 250, green: 0, blue: 0 }, + ERASE_SEARCH_DISTANCE = 0.1, // m + isDrawingLine = false, + entityID, + basePosition, + strokePoints, + strokeNormals, + strokeWidths, + MIN_STROKE_LENGTH = 0.02, + MAX_POINTS_PER_LINE = 100; + + 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]; + + 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; + + if (!isDrawingLine) { + print("ERROR: drawLine() called when not drawing line"); + return; + } + + localPosition = Vec3.subtract(position, basePosition); + + if (Vec3.distance(localPosition, strokePoints[strokePoints.length - 1]) >= MIN_STROKE_LENGTH + && strokePoints.length < MAX_POINTS_PER_LINE) { + strokePoints.push(localPosition); + strokeNormals.push(strokeNormal()); + strokeWidths.push(width); + + 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) { } function tearDown() { + cancelLine(); } return { @@ -108,7 +185,7 @@ } if (wasTriggerPressed || isTriggerPressed) { - fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex3" : "RightHandIndex3"); + fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4"); if (triggerValue < TRIGGER_START_WIDTH) { lineWidth = START_LINE_WIDTH; } else { @@ -140,7 +217,7 @@ } else { isGripPressed = gripValue > GRIP_ON; if (isGripPressed) { - fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex3" : "RightHandIndex3"); + fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4"); gripPressedCallback(fingerTipPosition); } } From 25761adf6c58065f36ef8705f2c926ef1b33ab43 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 17 Feb 2017 15:21:59 +1300 Subject: [PATCH 21/32] Erase closest line --- scripts/system/fingerPaint.js | 39 ++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/scripts/system/fingerPaint.js b/scripts/system/fingerPaint.js index 10b84b66fa..ce9428b246 100644 --- a/scripts/system/fingerPaint.js +++ b/scripts/system/fingerPaint.js @@ -26,7 +26,7 @@ // Paints in 3D. var brushName = name, STROKE_COLOR = { red: 250, green: 0, blue: 0 }, - ERASE_SEARCH_DISTANCE = 0.1, // m + ERASE_SEARCH_RADIUS = 0.1, // m isDrawingLine = false, entityID, basePosition, @@ -118,6 +118,43 @@ } function eraseClosestLine(position) { + // Erase closest line that is within search radius of finger tip. + var entities, + entitiesLength, + properties, + entityID, + entityDistance, + 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() { From bdfc9fd418b5db5d82564bf52c975d89f53635e6 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 17 Feb 2017 16:39:10 +1300 Subject: [PATCH 22/32] Tidying --- scripts/system/fingerPaint.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/scripts/system/fingerPaint.js b/scripts/system/fingerPaint.js index ce9428b246..4742e20410 100644 --- a/scripts/system/fingerPaint.js +++ b/scripts/system/fingerPaint.js @@ -122,8 +122,6 @@ var entities, entitiesLength, properties, - entityID, - entityDistance, i, pointsLength, j, @@ -186,12 +184,12 @@ TRIGGER_SMOOTH_RATIO = 0.1, TRIGGER_OFF = 0.05, TRIGGER_ON = 0.1, - TRIGGER_START_WIDTH = 0.15, - TRIGGER_FINISH_WIDTH = 1.0, - TRIGGER_RANGE_WIDTH = TRIGGER_FINISH_WIDTH - TRIGGER_START_WIDTH, - START_LINE_WIDTH = 0.005, - FINISH_LINE_WIDTH = 0.010, - RANGE_LINE_WIDTH = FINISH_LINE_WIDTH - START_LINE_WIDTH, + 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, gripValue = 0.0, isGripPressed = false, @@ -223,11 +221,11 @@ if (wasTriggerPressed || isTriggerPressed) { fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4"); - if (triggerValue < TRIGGER_START_WIDTH) { - lineWidth = START_LINE_WIDTH; + if (triggerValue < TRIGGER_START_WIDTH_RAMP) { + lineWidth = MIN_LINE_WIDTH; } else { - lineWidth = START_LINE_WIDTH - + (triggerValue - TRIGGER_START_WIDTH) / TRIGGER_RANGE_WIDTH * RANGE_LINE_WIDTH; + lineWidth = MIN_LINE_WIDTH + + (triggerValue - TRIGGER_START_WIDTH_RAMP) / TRIGGER_RAMP_WIDTH * RAMP_LINE_WIDTH; } if (!wasTriggerPressed && isTriggerPressed) { @@ -362,6 +360,9 @@ return; } + isFingerPainting = false; + updateHandFunctions(); + button.clicked.disconnect(onButtonClicked); tablet.removeButton(button); From 3712aea8613435fa1a04483d1f3c77836f93a249 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 17 Feb 2017 19:21:20 +1300 Subject: [PATCH 23/32] Handle trigger values of 1.0 not repeating --- scripts/system/fingerPaint.js | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/scripts/system/fingerPaint.js b/scripts/system/fingerPaint.js index 4742e20410..1c4527b6ad 100644 --- a/scripts/system/fingerPaint.js +++ b/scripts/system/fingerPaint.js @@ -179,6 +179,7 @@ triggerReleasedCallback, gripPressedCallback, + rawTriggerValue = 0.0, triggerValue = 0.0, isTriggerPressed = false, TRIGGER_SMOOTH_RATIO = 0.1, @@ -191,6 +192,7 @@ 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, @@ -202,6 +204,11 @@ } 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; @@ -210,7 +217,7 @@ return; } - triggerValue = triggerValue * TRIGGER_SMOOTH_RATIO + value * (1.0 - TRIGGER_SMOOTH_RATIO); + triggerValue = triggerValue * TRIGGER_SMOOTH_RATIO + rawTriggerValue * (1.0 - TRIGGER_SMOOTH_RATIO); wasTriggerPressed = isTriggerPressed; if (isTriggerPressed) { @@ -239,13 +246,18 @@ } function onGripPress(value) { + // Controller values are only updated when they change so store latest for use in update. + rawGripValue = value; + } + + function updateGripPress() { var fingerTipPosition; if (!isEnabled) { return; } - gripValue = gripValue * GRIP_SMOOTH_RATIO + value * (1.0 - GRIP_SMOOTH_RATIO); + gripValue = gripValue * GRIP_SMOOTH_RATIO + rawGripValue * (1.0 - GRIP_SMOOTH_RATIO); if (isGripPressed) { isGripPressed = gripValue > GRIP_OFF; @@ -258,6 +270,11 @@ } } + function onUpdate() { + updateTriggerPress(); + updateGripPress(); + } + function setUp(onTriggerPressed, onTriggerPressing, onTriggerReleased, onGripPressed) { triggerPressedCallback = onTriggerPressed; triggerPressingCallback = onTriggerPressing; @@ -273,6 +290,7 @@ setEnabled: setEnabled, onTriggerPress: onTriggerPress, onGripPress: onGripPress, + onUpdate: onUpdate, setUp: setUp, tearDown: tearDown }; @@ -343,6 +361,8 @@ controllerMapping.from(Controller.Standard.RT).to(rightHand.onTriggerPress); controllerMapping.from(Controller.Standard.RightGrip).to(rightHand.onGripPress); Controller.enableMapping(CONTROLLER_MAPPING_NAME); + Script.update.connect(leftHand.onUpdate); + Script.update.connect(rightHand.onUpdate); // Connect handController outputs to paintBrush objects. leftBrush = paintBrush("left"); @@ -360,6 +380,9 @@ return; } + Script.update.disconnect(leftHand.onUpdate); + Script.update.disconnect(rightHand.onUpdate); + isFingerPainting = false; updateHandFunctions(); From e4076e6244cedf30ce433ba205ceb4339a6c2572 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 17 Feb 2017 09:45:13 -0800 Subject: [PATCH 24/32] warning fixes --- libraries/script-engine/src/TabletScriptingInterface.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index 4f269ef041..c78ce251c8 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -170,7 +170,6 @@ QObject* TabletScriptingInterface::getFlags() 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"; -static int s_windowNameCounter = 1; class TabletRootWindow : public QmlWindowClass { virtual QString qmlSource() const { return "hifi/tablet/WindowRoot.qml"; } @@ -193,7 +192,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { // create new desktop window auto offscreenUi = DependencyManager::get(); - offscreenUi->executeOnUiThread([=, this] { + offscreenUi->executeOnUiThread([=] { auto tabletRootWindow = new TabletRootWindow(); tabletRootWindow->initQml(QVariantMap()); auto quickItem = tabletRootWindow->asQuickItem(); From 5963c6ddc3c66d0237eeb21e31b22efe49d5fcbb Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 17 Feb 2017 11:04:04 -0800 Subject: [PATCH 25/32] Fix for toolbar visibility after snapshots. --- .../resources/qml/hifi/toolbars/Toolbar.qml | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml index f62e4dea19..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; } } } From b8b5927351f0fc8a930ca734142e78d5d8cb5d38 Mon Sep 17 00:00:00 2001 From: kunalgosar Date: Fri, 17 Feb 2017 13:21:01 -0800 Subject: [PATCH 26/32] Logging display mode changes --- interface/src/Application.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 488e97b5e6..ea01b13c0f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6679,6 +6679,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); From 777a0b51c11775615dd7fbdc6ac9a201679650e8 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 18 Feb 2017 13:35:16 +1300 Subject: [PATCH 27/32] Ignore spurious finger tip position values --- scripts/system/fingerPaint.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/system/fingerPaint.js b/scripts/system/fingerPaint.js index 1c4527b6ad..416b0dede1 100644 --- a/scripts/system/fingerPaint.js +++ b/scripts/system/fingerPaint.js @@ -70,7 +70,9 @@ function drawLine(position, width) { // Add a stroke to the polyline if stroke is a sufficient length. - var localPosition; + var localPosition, + distanceToPrevious, + MAX_DISTANCE_TO_PREVIOUS = 1.0; if (!isDrawingLine) { print("ERROR: drawLine() called when not drawing line"); @@ -78,8 +80,14 @@ } localPosition = Vec3.subtract(position, basePosition); + distanceToPrevious = Vec3.distance(localPosition, strokePoints[strokePoints.length - 1]); - if (Vec3.distance(localPosition, strokePoints[strokePoints.length - 1]) >= MIN_STROKE_LENGTH + if (distanceToPrevious > MAX_DISTANCE_TO_PREVIOUS) { + // Ignore occasional spurious finger tip positions. + return; + } + + if (distanceToPrevious >= MIN_STROKE_LENGTH && strokePoints.length < MAX_POINTS_PER_LINE) { strokePoints.push(localPosition); strokeNormals.push(strokeNormal()); From 0689557ba57d64516522dc8f4a1c2464c005d6aa Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 18 Feb 2017 13:39:59 +1300 Subject: [PATCH 28/32] Draw smoother lines --- scripts/system/fingerPaint.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/system/fingerPaint.js b/scripts/system/fingerPaint.js index 416b0dede1..ebcdca44b5 100644 --- a/scripts/system/fingerPaint.js +++ b/scripts/system/fingerPaint.js @@ -33,8 +33,10 @@ strokePoints, strokeNormals, strokeWidths, - MIN_STROKE_LENGTH = 0.02, - MAX_POINTS_PER_LINE = 100; + 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); @@ -53,6 +55,7 @@ strokePoints = [Vec3.ZERO]; strokeNormals = [strokeNormal()]; strokeWidths = [width]; + timeOfLastPoint = Date.now(); entityID = Entities.addEntity({ type: "PolyLine", @@ -88,10 +91,12 @@ } 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, From 2c02b42c967354be7dd559c67e8a0a05768a7d5b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 18 Feb 2017 14:21:54 +1300 Subject: [PATCH 29/32] Finger paint button icon files --- .../icons/tablet-icons/finger-paint-a.svg | 94 +++++++------------ .../icons/tablet-icons/finger-paint-i.svg | 56 ++++------- scripts/system/fingerPaint.js | 4 +- 3 files changed, 54 insertions(+), 100 deletions(-) diff --git a/interface/resources/icons/tablet-icons/finger-paint-a.svg b/interface/resources/icons/tablet-icons/finger-paint-a.svg index 553636bfbb..acc93608d9 100644 --- a/interface/resources/icons/tablet-icons/finger-paint-a.svg +++ b/interface/resources/icons/tablet-icons/finger-paint-a.svg @@ -10,19 +10,19 @@ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" + id="Layer_1" x="0px" y="0px" viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve" - id="svg2" - inkscape:version="0.91 r13725" - sodipodi:docname="bubble-a.svg">image/svg+xml \ No newline at end of file + d="M18.3,19.6c0.7,0.1,1.2,0.5,1.4,1.1c0.4,1.3,2,5.6,2.4,6.9c2.2,0,7.9,0.1,9.9,0.1c0.2,0,0.6,0,0.8,0.1 c0.5,0.1,0.8,0.4,1.1,0.9c0.1,0.2,0.2,0.4,0.2,0.6c0.8,2.9,0.7,2,1.5,4.9c0.8,3,0.1,5.7-2.3,7.7c-1.9,1.6-3.6,2.3-5.7,1.9 c-0.6-0.1-1.2-0.3-1.8-0.5c-3.6-1.5-7.3-2.9-10.9-4.3c-0.9-0.4-1.4-1.2-1.3-2c0.1-0.9,0.9-1.5,1.8-1.6c0.1,0,0.3,0,0.4,0 c0.2,0,0.4,0.1,0.7,0.2c1.6,0.6,2.6,1.4,4.2,2c0,0,0,0,0,0c0,0,0.1,0,0.2,0c-0.1-0.2-0.1-0.4-0.2-0.6c-1.4-4.9-2.9-9.8-4.3-14.7 c-0.2-0.7-0.3-1.4,0.2-2c0.4-0.6,1-0.8,1.7-0.8C18.2,19.5,18.2,19.5,18.3,19.6 M18.9,16.3c-0.1,0-0.3,0-0.4-0.1 c-1.9-0.2-3.6,0.6-4.7,2.1c-1.5,2-0.9,4.1-0.7,4.8c0.9,3,1.8,6,2.6,9c-0.1,0-0.3,0-0.4,0c-2.5,0-4.6,1.9-5,4.3 c-0.2,1.2,0.1,2.4,0.7,3.4c0.6,1,1.5,1.7,2.7,2.2c1.2,0.5,2.4,0.9,3.6,1.4c2.4,0.9,4.9,1.9,7.3,2.9c0.9,0.4,1.7,0.6,2.5,0.7 c3.9,0.7,6.7-1.2,8.5-2.7c3.4-2.8,4.6-6.7,3.4-11.1c-0.5-1.7-0.6-2.1-0.8-2.7c-0.1-0.4-0.3-1-0.7-2.2c-0.1-0.4-0.2-0.7-0.4-1.1 c-0.7-1.5-1.8-2.5-3.4-2.7c-0.6-0.1-1.2-0.1-1.8-0.1c-1.2,0-3.4,0-5.4,0c-0.6,0-1.3-0.1-1.9-0.1c-0.2-0.6-0.5-1.3-0.7-2 c-0.4-1.1-0.7-2.1-0.9-2.6C22.3,17.9,20.8,16.7,18.9,16.3L18.9,16.3z" + id="path13" + style="fill:#000000;fill-opacity:1" /> \ 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 index d7c8948e01..b295727d8a 100644 --- a/interface/resources/icons/tablet-icons/finger-paint-i.svg +++ b/interface/resources/icons/tablet-icons/finger-paint-i.svg @@ -1,46 +1,30 @@ - - - - - - - - - - - - - - - + + + + + diff --git a/scripts/system/fingerPaint.js b/scripts/system/fingerPaint.js index ebcdca44b5..725c04f876 100644 --- a/scripts/system/fingerPaint.js +++ b/scripts/system/fingerPaint.js @@ -355,8 +355,8 @@ // Tablet button. button = tablet.addButton({ - icon: "icons/tablet-icons/bubble-i.svg", - activeIcon: "icons/tablet-icons/bubble-a.svg", + icon: "icons/tablet-icons/finger-paint-i.svg", + activeIcon: "icons/tablet-icons/finger-paint-a.svg", text: BUTTON_NAME, isActive: isFingerPainting }); From 692fa52acaa3a8d1debeefa6932bd86907675613 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 18 Feb 2017 15:21:48 +1300 Subject: [PATCH 30/32] Disable drawing pointer lasers while painting --- .../controllers/handControllerPointer.js | 23 +++++++++++++++++-- scripts/system/fingerPaint.js | 8 ++++++- 2 files changed, 28 insertions(+), 3 deletions(-) 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/fingerPaint.js b/scripts/system/fingerPaint.js index 725c04f876..4824f777aa 100644 --- a/scripts/system/fingerPaint.js +++ b/scripts/system/fingerPaint.js @@ -20,7 +20,8 @@ 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_GRAB_DISABLE_MESSAGE_CHANNEL = "Hifi-Grab-Disable", + HIFI_POINTER_DISABLE_MESSAGE_CHANNEL = "Hifi-Pointer-Disable"; function paintBrush(name) { // Paints in 3D. @@ -318,6 +319,9 @@ nearGrabEnabled: enabled, farGrabEnabled: enabled })); + Messages.sendMessage(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ + pointerEnabled: enabled + })); Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify({ pointIndex: !enabled })); @@ -386,6 +390,7 @@ // 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); } function tearDown() { @@ -418,6 +423,7 @@ Messages.unsubscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); Messages.unsubscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL); + Messages.unssubscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); } setUp(); From 05c63e16cacd8d601bcb30b038550ee118e81dea Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 18 Feb 2017 20:40:27 +1300 Subject: [PATCH 31/32] Send messages local only --- scripts/system/fingerPaint.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/system/fingerPaint.js b/scripts/system/fingerPaint.js index 4824f777aa..c5547b40ee 100644 --- a/scripts/system/fingerPaint.js +++ b/scripts/system/fingerPaint.js @@ -318,13 +318,13 @@ 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 onButtonClicked() { From 71ba38afb05c3712b3fc34a52b7851b41ec21fbc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 18 Feb 2017 21:28:30 +1300 Subject: [PATCH 32/32] Process controller and script updates only when finger painting is on --- scripts/system/fingerPaint.js | 134 +++++++++++++++++----------------- 1 file changed, 68 insertions(+), 66 deletions(-) diff --git a/scripts/system/fingerPaint.js b/scripts/system/fingerPaint.js index c5547b40ee..959f594212 100644 --- a/scripts/system/fingerPaint.js +++ b/scripts/system/fingerPaint.js @@ -13,10 +13,10 @@ button, BUTTON_NAME = "PAINT", isFingerPainting = false, - leftHand, - rightHand, - leftBrush, - rightBrush, + leftHand = null, + rightHand = null, + leftBrush = null, + rightBrush = null, CONTROLLER_MAPPING_NAME = "com.highfidelity.fingerPaint", isTabletDisplayed = false, HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index", @@ -186,7 +186,6 @@ function handController(name) { // Translates controller data into application events. var handName = name, - isEnabled = false, triggerPressedCallback, triggerPressingCallback, @@ -213,10 +212,6 @@ GRIP_OFF = 0.05, GRIP_ON = 0.1; - function setEnabled(enabled) { - isEnabled = enabled; - } - function onTriggerPress(value) { // Controller values are only updated when they change so store latest for use in update. rawTriggerValue = value; @@ -227,10 +222,6 @@ fingerTipPosition, lineWidth; - if (!isEnabled) { - return; - } - triggerValue = triggerValue * TRIGGER_SMOOTH_RATIO + rawTriggerValue * (1.0 - TRIGGER_SMOOTH_RATIO); wasTriggerPressed = isTriggerPressed; @@ -267,10 +258,6 @@ function updateGripPress() { var fingerTipPosition; - if (!isEnabled) { - return; - } - gripValue = gripValue * GRIP_SMOOTH_RATIO + rawGripValue * (1.0 - GRIP_SMOOTH_RATIO); if (isGripPressed) { @@ -301,7 +288,6 @@ } return { - setEnabled: setEnabled, onTriggerPress: onTriggerPress, onGripPress: onGripPress, onUpdate: onUpdate, @@ -327,21 +313,76 @@ }), 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 }); - leftHand.setEnabled(isFingerPainting); - rightHand.setEnabled(isFingerPainting); + print("Finger painting: " + isFingerPainting ? "on" : "off"); if (wasFingerPainting) { leftBrush.cancelLine(); rightBrush.cancelLine(); } + if (isFingerPainting) { + enableProcessing(); + } + updateHandFunctions(); + + if (!isFingerPainting) { + disableProcessing(); + } } function onTabletScreenChanged(type, url) { @@ -368,29 +409,6 @@ // Track whether tablet is displayed or not. tablet.screenChanged.connect(onTabletScreenChanged); - - // 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); - Script.update.connect(leftHand.onUpdate); - Script.update.connect(rightHand.onUpdate); - - // 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); } function tearDown() { @@ -398,32 +416,16 @@ return; } - Script.update.disconnect(leftHand.onUpdate); - Script.update.disconnect(rightHand.onUpdate); - - isFingerPainting = false; - updateHandFunctions(); - - button.clicked.disconnect(onButtonClicked); - tablet.removeButton(button); + if (isFingerPainting) { + isFingerPainting = false; + updateHandFunctions(); + disableProcessing(); + } tablet.screenChanged.disconnect(onTabletScreenChanged); - 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.unssubscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL); + button.clicked.disconnect(onButtonClicked); + tablet.removeButton(button); } setUp();