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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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