From 5007c259bbd009c29833eed4b28a728a1386e724 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Tue, 1 May 2018 23:35:00 +0300 Subject: [PATCH] query real avatar favorites (but use fake urls for now) --- interface/resources/qml/hifi/AvatarApp.qml | 144 ++- .../qml/hifi/avatarapp/AvatarsModel.qml | 12 + interface/src/AvatarBookmarks.cpp | 37 +- interface/src/AvatarBookmarks.h | 4 +- interface/src/Bookmarks.cpp | 4 + interface/src/Bookmarks.h | 2 + interface/src/avatar/MyAvatar.h | 2 +- scripts/system/avatarapp.js | 856 ++---------------- 8 files changed, 207 insertions(+), 854 deletions(-) diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index e27b11cfae..d0a35f8f11 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -12,38 +12,110 @@ Rectangle { height: 706 color: style.colors.white + property string getAvatarsMethod: 'getAvatars' + + signal sendToScript(var message); + function emitSendToScript(message) { + console.debug('AvatarApp.qml: emitting sendToScript: ', JSON.stringify(message, null, '\t')); + sendToScript(message); + } + + function fromScript(message) { + console.debug('AvatarApp.qml: fromScript: ', JSON.stringify(message, null, '\t')) + + if(message.method === 'initialize') { + emitSendToScript({'method' : getAvatarsMethod}); + } else if(message.method === getAvatarsMethod) { + var getAvatarsReply = message.reply; + var i = 0; + + for(var avatarName in getAvatarsReply.bookmarks) { + var avatarEntry = { + 'name' : avatarName, + 'url' : Qt.resolvedUrl(allAvatars.urls[i++ % allAvatars.urls.length]), + 'wearables' : '', + 'entry' : getAvatarsReply.bookmarks[avatarName] + }; + + allAvatars.append(avatarEntry); + } + + var currentAvatar = getAvatarsReply.currentAvatar; + console.debug('currentAvatar: ', JSON.stringify(currentAvatar, null, '\t')); + var selectedAvatarIndex = -1; + + // 2DO: find better way of determining selected avatar in bookmarks + console.debug('allAvatars.count: ', allAvatars.count); + for(var i = 0; i < allAvatars.count; ++i) { + var thesame = true; + for(var prop in currentAvatar) { + console.debug('prop', prop); + + var v1 = currentAvatar[prop]; + var v2 = allAvatars.get(i).entry[prop]; + console.debug('v1', v1, 'v2', v2); + + var s1 = JSON.stringify(v1); + var s2 = JSON.stringify(v2); + + console.debug('comparing\n', s1, 'to\n', s2, '...'); + if(s1 !== s2) { + if(!(Array.isArray(v1) && v1.length === 0 && v2 === undefined)) { + thesame = false; + break; + } + } + console.debug('values seems to be the same...'); + } + if(thesame) { + selectedAvatarIndex = i; + break; + } + } + + console.debug('selectedAvatarIndex = -1, avatar is not favorite') + + if(selectedAvatarIndex === -1) { + var currentAvatarEntry = { + 'name' : '', + 'url' : Qt.resolvedUrl(allAvatars.urls[i++ % allAvatars.urls.length]), + 'wearables' : '', + 'entry' : currentAvatar + }; + + selectedAvatar = currentAvatarEntry; + view.setPage(0); + + console.debug('selectedAvatar = ', JSON.stringify(selectedAvatar, null, '\t')) + } else { + view.selectAvatar(allAvatars.get(selectedAvatarIndex)); + } + } + } + property string selectedAvatarId: '' onSelectedAvatarIdChanged: { console.debug('selectedAvatarId: ', selectedAvatarId) + selectedAvatar = allAvatars.findAvatar(selectedAvatarId); } - property var selectedAvatar: selectedAvatarId !== '' ? allAvatars.findAvatar(selectedAvatarId) : undefined + property var selectedAvatar; onSelectedAvatarChanged: { - console.debug('selectedAvatar: ', selectedAvatar ? selectedAvatar.url : selectedAvatar) + console.debug('onSelectedAvatarChanged.selectedAvatar: ', JSON.stringify(selectedAvatar, null, '\t')); } function isEqualById(avatar, avatarId) { - return (avatar.url + avatar.name) === avatarId + return (avatar.name) === avatarId } property string avatarName: selectedAvatar ? selectedAvatar.name : '' property string avatarUrl: selectedAvatar ? selectedAvatar.url : null property int avatarWearablesCount: selectedAvatar && selectedAvatar.wearables !== '' ? selectedAvatar.wearables.split('|').length : 0 - property bool isAvatarInFavorites: selectedAvatar ? selectedAvatar.favorite : false + property bool isAvatarInFavorites: selectedAvatar ? allAvatars.findAvatar(selectedAvatar.name) !== undefined : false property bool isInManageState: false Component.onCompleted: { - for(var i = 0; i < allAvatars.count; ++i) { - var originalUrl = allAvatars.get(i).url; - if(originalUrl !== '') { - var resolvedUrl = Qt.resolvedUrl(originalUrl); - console.debug('url: ', originalUrl, 'resolved: ', resolvedUrl); - allAvatars.setProperty(i, 'url', resolvedUrl); - } - } - - view.selectAvatar(allAvatars.get(1)); } AvatarAppStyle { @@ -225,8 +297,10 @@ Rectangle { onClicked: { console.debug('selectedAvatar.url', selectedAvatar.url) createFavorite.onSaveClicked = function() { - selectedAvatar.favorite = true; - pageOfAvatars.setProperty(view.currentIndex, 'favorite', selectedAvatar.favorite) + var newAvatar = JSON.parse(JSON.stringify(selectedAvatar)); + newAvatar.name = createFavorite.favoriteNameText; + allAvatars.append(newAvatar); + view.selectAvatar(newAvatar); createFavorite.close(); } @@ -318,10 +392,9 @@ Rectangle { console.debug('adding avatar...'); var avatar = { - 'url': '../../images/samples/hifi-mp-e76946cc-c272-4adf-9bb6-02cde0a4b57d-2.png', + 'url': Qt.resolvedUrl('../../images/samples/hifi-mp-e76946cc-c272-4adf-9bb6-02cde0a4b57d-2.png'), 'name': 'Lexi' + (++debug_newAvatarIndex), - 'wearables': '', - 'favorite': false + 'wearables': '' }; allAvatars.append(avatar) @@ -413,7 +486,7 @@ Rectangle { property int verticalSpacing: 36 function selectAvatar(avatar) { - selectedAvatarId = avatar.url + avatar.name; + selectedAvatarId = avatar.name; var avatarIndex = allAvatars.findAvatarIndex(selectedAvatarId); allAvatars.move(avatarIndex, 0, 1); view.setPage(0); @@ -491,7 +564,7 @@ Rectangle { id: pageOfAvatars property bool isUpdating: false; - property var getMoreAvatars: {'url' : '', 'name' : 'Get More Avatars'} + property var getMoreAvatarsEntry: {'url' : '', 'name' : '', 'getMoreAvatars' : true} function findAvatar(avatarId) { console.debug('pageOfAvatars.findAvatar: ', avatarId); @@ -507,11 +580,11 @@ Rectangle { } function appendGetAvatars() { - append(getMoreAvatars); + append(getMoreAvatarsEntry); } function hasGetAvatars() { - return count != 0 && get(count - 1).url === '' + return count != 0 && get(count - 1).getMoreAvatars } function removeGetAvatars() { @@ -554,12 +627,12 @@ Rectangle { imageUrl: url border.color: container.highlighted ? style.colors.blueHighlight : 'transparent' border.width: container.highlighted ? 2 : 0 - wearablesCount: (wearables && wearables !== '') ? wearables.split('|').length : 0 + wearablesCount: (!getMoreAvatars && wearables && wearables !== '') ? wearables.split('|').length : 0 onWearablesCountChanged: { console.debug('delegate: AvatarThumbnail.wearablesCount: ', wearablesCount) } - visible: url !== '' + visible: !getMoreAvatars MouseArea { id: favoriteAvatarMouseArea @@ -652,7 +725,7 @@ Rectangle { height: 92 radius: 5 color: style.colors.blueHighlight - visible: url === '' && !isInManageState + visible: getMoreAvatars && !isInManageState HiFiGlyphs { anchors.centerIn: parent @@ -698,8 +771,8 @@ Rectangle { verticalAlignment: Text.AlignTop horizontalAlignment: Text.AlignHCenter wrapMode: Text.WrapAtWordBoundaryOrAnywhere - text: name - visible: url !== '' || !isInManageState + text: getMoreAvatars ? 'Get More Avatars' : name + visible: !getMoreAvatars || !isInManageState } } } @@ -763,13 +836,6 @@ Rectangle { // color: 'green' visible: false - onVisibleChanged: { - if(visible) { - console.debug('selectedAvatar.wearables: ', selectedAvatar.wearables) - selectedAvatar.wearables = 'hat|sunglasses|bracelet' - pageOfAvatars.setProperty(view.currentIndex, 'wearables', selectedAvatar.wearables) - } - } Rectangle { width: 442 @@ -808,11 +874,13 @@ Rectangle { onClicked: { gotoAvatarAppPanel.visible = false; + var i = allAvatars.count + 1; + var url = allAvatars.urls[i++ % allAvatars.urls.length] + var avatar = { - 'url': '../../images/samples/hifi-mp-e76946cc-c272-4adf-9bb6-02cde0a4b57d-2.png', + 'url': Qt.resolvedUrl(url), 'name': 'Lexi' + (++newAvatarIndex), - 'wearables': '', - 'favorite': false + 'wearables': 'hat|sunglasses|bracelet' }; allAvatars.append(avatar) diff --git a/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml b/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml index 5672e04731..d736737f3a 100644 --- a/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml +++ b/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml @@ -3,6 +3,17 @@ import QtQuick 2.9 ListModel { id: model + property var urls: [ + '../../images/samples/hifi-mp-e76946cc-c272-4adf-9bb6-02cde0a4b57d.png', + '../../images/samples/hifi-mp-e76946cc-c272-4adf-9bb6-02cde0a4b57d-1.png', + '../../images/samples/hifi-mp-e76946cc-c272-4adf-9bb6-02cde0a4b57d-2.png', + '../../images/samples/hifi-mp-e76946cc-c272-4adf-9bb6-02cde0a4b57d-3.png', + '../../images/samples/hifi-mp-e76946cc-c272-4adf-9bb6-02cde0a4b57d-4.png', + '../../images/samples/hifi-mp-e76946cc-c272-4adf-9bb6-02cde0a4b57d-5.png', + '../../images/samples/hifi-mp-e76946cc-c272-4adf-9bb6-02cde0a4b57d-5.png', + ] + + /* ListElement { url: '../../images/samples/hifi-mp-e76946cc-c272-4adf-9bb6-02cde0a4b57d.png' name: 'Woody' @@ -45,4 +56,5 @@ ListModel { wearables: '' favorite: true } + */ } diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index f97c02bca3..e243890bc3 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -181,22 +181,31 @@ void AvatarBookmarks::addBookmark() { return; } - auto myAvatar = DependencyManager::get()->getMyAvatar(); - - const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString(); - const QVariant& avatarScale = myAvatar->getAvatarScale(); - - // If Avatar attachments ever change, this is where to update them, when saving remember to also append to AVATAR_BOOKMARK_VERSION - QVariantMap bookmark; - bookmark.insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION); - bookmark.insert(ENTRY_AVATAR_URL, avatarUrl); - bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale); - bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant()); - bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant()); - - Bookmarks::addBookmarkToFile(bookmarkName, bookmark); + addBookmark(bookmarkName); }); +} +void AvatarBookmarks::addBookmark(QString bookmarkName) +{ + auto myAvatar = DependencyManager::get()->getMyAvatar(); + + const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString(); + const QVariant& avatarScale = myAvatar->getAvatarScale(); + + // If Avatar attachments ever change, this is where to update them, when saving remember to also append to AVATAR_BOOKMARK_VERSION + QVariantMap bookmark; + bookmark.insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION); + bookmark.insert(ENTRY_AVATAR_URL, avatarUrl); + bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale); + bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant()); + bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant()); + + Bookmarks::addBookmarkToFile(bookmarkName, bookmark); +} + +void AvatarBookmarks::removeBookmark(QString bookmarkName) +{ + Bookmarks::deleteBookmark(bookmarkName); } void AvatarBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) { diff --git a/interface/src/AvatarBookmarks.h b/interface/src/AvatarBookmarks.h index f2ff88c974..8ffe8fa66f 100644 --- a/interface/src/AvatarBookmarks.h +++ b/interface/src/AvatarBookmarks.h @@ -39,8 +39,8 @@ public slots: * @function AvatarBookmarks.addBookMark */ void addBookmark(); - void addBookmark(QString bookmarkName) {} - void removeBookmark(QString bookmark) {} + void addBookmark(QString bookmarkName); + void removeBookmark(QString bookmarkName); QVariantMap getBookmarks() { return _bookmarks; } protected: diff --git a/interface/src/Bookmarks.cpp b/interface/src/Bookmarks.cpp index 6e99b81e50..9a8d8eb279 100644 --- a/interface/src/Bookmarks.cpp +++ b/interface/src/Bookmarks.cpp @@ -46,6 +46,10 @@ void Bookmarks::deleteBookmark() { return; } + deleteBookmark(bookmarkName); +} + +void Bookmarks::deleteBookmark(const QString& bookmarkName) { removeBookmarkFromMenu(Menu::getInstance(), bookmarkName); remove(bookmarkName); diff --git a/interface/src/Bookmarks.h b/interface/src/Bookmarks.h index dc08d4b279..9be43ddfbc 100644 --- a/interface/src/Bookmarks.h +++ b/interface/src/Bookmarks.h @@ -31,6 +31,8 @@ public: QString addressForBookmark(const QString& name) const; protected: + void deleteBookmark(const QString& bookmarkName); + void addBookmarkToFile(const QString& bookmarkName, const QVariant& bookmark); virtual void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) = 0; void enableMenuItems(bool enabled); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a3b07d400f..58a82240db 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -888,7 +888,7 @@ public: bool hasDriveInput() const; - QVariantList getAvatarEntitiesVariant(); + Q_INVOKABLE QVariantList getAvatarEntitiesVariant(); void removeAvatarEntities(); /**jsdoc diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 3bad252db1..78e152f22a 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -3,9 +3,9 @@ /*global Tablet, Settings, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation*/ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ // -// pal.js +// avatarapp.js // -// Created by Howard Stearns on December 9, 2016 +// Created by Alexander Ivash on April 30, 2018 // Copyright 2016 High Fidelity, Inc // // Distributed under the Apache License, Version 2.0 @@ -14,660 +14,44 @@ (function() { // BEGIN LOCAL_SCOPE - var request = Script.require('request').request; - -var populateNearbyUserList, color, textures, removeOverlays, - controllerComputePickRay, onTabletButtonClicked, onTabletScreenChanged, - receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL, - createAudioInterval, tablet, CHANNEL, getConnectionData, findableByChanged, - avatarAdded, avatarRemoved, avatarSessionChanged; // forward references; - -// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed -// something, will revisit as this is sorta horrible. -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") -}; -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") -}; -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") -}; - -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 PAL_QML_SOURCE = "hifi/AvatarApp.qml"; -var conserveResources = true; - +var request = Script.require('request').request; +var AVATARAPP_QML_SOURCE = "hifi/AvatarApp.qml"; Script.include("/~/system/libraries/controllers.js"); -function projectVectorOntoPlane(normalizedVector, planeNormal) { - return Vec3.cross(planeNormal, Vec3.cross(normalizedVector, planeNormal)); -} -function angleBetweenVectorsInPlane(from, to, normal) { - var projectedFrom = projectVectorOntoPlane(from, normal); - var projectedTo = projectVectorOntoPlane(to, normal); - return Vec3.orientedAngle(projectedFrom, projectedTo, normal); -} +// constants from AvatarBookmarks.h +var ENTRY_AVATAR_URL = "avatarUrl"; +var ENTRY_AVATAR_ATTACHMENTS = "attachments"; +var ENTRY_AVATAR_ENTITIES = "avatarEntities"; +var ENTRY_AVATAR_SCALE = "avatarScale"; +var ENTRY_VERSION = "version"; -// -// Overlays. -// -var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. - -function ExtendedOverlay(key, type, properties, selected, hasModel) { // A wrapper around overlays to store the key it is associated with. - overlays[key] = this; - if (hasModel) { - var modelKey = key + "-m"; - this.model = new ExtendedOverlay(modelKey, "model", { - url: Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx"), - textures: textures(selected), - ignoreRayIntersection: true - }, false, false); - } else { - this.model = undefined; - } - this.key = key; - this.selected = selected || false; // not undefined - this.hovering = false; - this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... -} -// Instance methods: -ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay - Overlays.deleteOverlay(this.activeOverlay); - delete overlays[this.key]; -}; - -ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay - Overlays.editOverlay(this.activeOverlay, properties); -}; - -function color(selected, hovering, level) { - var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; - function scale(component) { - var delta = 0xFF - component; - return component + (delta * level); - } - return {red: scale(base.red), green: scale(base.green), blue: scale(base.blue)}; -} - -function textures(selected, hovering) { - return hovering ? HOVER_TEXTURES : selected ? SELECTED_TEXTURES : UNSELECTED_TEXTURES; -} -// so we don't have to traverse the overlays to get the last one -var lastHoveringId = 0; -ExtendedOverlay.prototype.hover = function (hovering) { - this.hovering = hovering; - if (this.key === lastHoveringId) { - if (hovering) { - return; - } - 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) { - 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)}); - if (this.model) { - this.model.editOverlay({textures: textures(selected)}); - } - this.selected = selected; -}; -// Class methods: -var selectedIds = []; -ExtendedOverlay.isSelected = function (id) { - return -1 !== selectedIds.indexOf(id); -}; -ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier - return overlays[key]; -}; -ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy. - var key; - for (key in overlays) { - if (iterator(ExtendedOverlay.get(key))) { - return; - } - } -}; -ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any) - if (lastHoveringId) { - ExtendedOverlay.get(lastHoveringId).hover(false); - } -}; - -// hit(overlay) on the one overlay intersected by pickRay, if any. -// noHit() if no ExtendedOverlay was intersected (helps with hover) -ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) { - var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones. - if (!pickedOverlay.intersects) { - if (noHit) { - return noHit(); - } - return; - } - ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours. - if ((overlay.activeOverlay) === pickedOverlay.overlayID) { - hit(overlay); - return true; - } - }); -}; - - -// -// Similar, for entities -// -function HighlightedEntity(id, entityProperties) { - this.id = id; - this.overlay = Overlays.addOverlay('cube', { - position: entityProperties.position, - rotation: entityProperties.rotation, - dimensions: entityProperties.dimensions, - solid: false, - color: { - red: 0xF3, - green: 0x91, - blue: 0x29 - }, - ignoreRayIntersection: true, - drawInFront: false // Arguable. For now, let's not distract with mysterious wires around the scene. - }); - HighlightedEntity.overlays.push(this); -} -HighlightedEntity.overlays = []; -HighlightedEntity.clearOverlays = function clearHighlightedEntities() { - HighlightedEntity.overlays.forEach(function (highlighted) { - Overlays.deleteOverlay(highlighted.overlay); - }); - HighlightedEntity.overlays = []; -}; -HighlightedEntity.updateOverlays = function updateHighlightedEntities() { - HighlightedEntity.overlays.forEach(function (highlighted) { - var properties = Entities.getEntityProperties(highlighted.id, ['position', 'rotation', 'dimensions']); - Overlays.editOverlay(highlighted.overlay, { - position: properties.position, - rotation: properties.rotation, - dimensions: properties.dimensions - }); - }); -}; - -/* this contains current gain for a given node (by session id). More efficient than - * querying it, plus there isn't a getGain function so why write one */ -var sessionGains = {}; -function convertDbToLinear(decibels) { - // +20db = 10x, 0dB = 1x, -10dB = 0.1x, etc... - // but, your perception is that something 2x as loud is +10db - // so we go from -60 to +20 or 1/64x to 4x. For now, we can - // maybe scale the signal this way?? - return Math.pow(2, decibels / 10.0); -} function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. - var data; + console.debug('fromQml: message = ', JSON.stringify(message, null, '\t')) + switch (message.method) { - case 'selected': - selectedIds = message.params; - ExtendedOverlay.some(function (overlay) { - var id = overlay.key; - var selected = ExtendedOverlay.isSelected(id); - overlay.select(selected); - }); + case 'getAvatars': + var currentAvatar = {} + currentAvatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL; + currentAvatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale(); + currentAvatar[ENTRY_AVATAR_ATTACHMENTS] = MyAvatar.getAttachmentsVariant(); + currentAvatar[ENTRY_AVATAR_ENTITIES] = MyAvatar.getAvatarEntitiesVariant(); - HighlightedEntity.clearOverlays(); - if (selectedIds.length) { - Entities.findEntitiesInFrustum(Camera.frustum).forEach(function (id) { - // Because lastEditedBy is per session, the vast majority of entities won't match, - // so it would probably be worth reducing marshalling costs by asking for just we need. - // However, providing property name(s) is advisory and some additional properties are - // included anyway. As it turns out, asking for 'lastEditedBy' gives 'position', 'rotation', - // and 'dimensions', too, so we might as well make use of them instead of making a second - // getEntityProperties call. - // It would be nice if we could harden this against future changes by specifying all - // and only these four in an array, but see - // https://highfidelity.fogbugz.com/f/cases/2728/Entities-getEntityProperties-id-lastEditedBy-name-lastEditedBy-doesn-t-work - var properties = Entities.getEntityProperties(id, 'lastEditedBy'); - if (ExtendedOverlay.isSelected(properties.lastEditedBy)) { - new HighlightedEntity(id, properties); - } - }); - } - break; - case 'refreshNearby': - data = {}; - ExtendedOverlay.some(function (overlay) { // capture the audio data - data[overlay.key] = overlay; - }); - removeOverlays(); - // If filter is specified from .qml instead of through settings, update the settings. - if (message.params.filter !== undefined) { - Settings.setValue('pal/filtered', !!message.params.filter); - } - populateNearbyUserList(message.params.selected, data); - UserActivityLogger.palAction("refresh_nearby", ""); - break; - case 'refreshConnections': - print('Refreshing Connections...'); - getConnectionData(false); - UserActivityLogger.palAction("refresh_connections", ""); - break; - case 'removeConnection': - connectionUserName = message.params; - request({ - uri: METAVERSE_BASE + '/api/v1/user/connections/' + connectionUserName, - method: 'DELETE' - }, function (error, response) { - if (error || (response.status !== 'success')) { - print("Error: unable to remove connection", connectionUserName, error || response.status); - return; - } - getConnectionData(false); - }); - break + message.reply = { + 'bookmarks' : AvatarBookmarks.getBookmarks(), + 'currentAvatar' : currentAvatar + }; - case 'removeFriend': - friendUserName = message.params; - print("Removing " + friendUserName + " from friends."); - request({ - uri: METAVERSE_BASE + '/api/v1/user/friends/' + friendUserName, - method: 'DELETE' - }, function (error, response) { - if (error || (response.status !== 'success')) { - print("Error: unable to unfriend " + friendUserName, error || response.status); - return; - } - getConnectionData(friendUserName); - }); - break - case 'addFriend': - friendUserName = message.params; - print("Adding " + friendUserName + " to friends."); - request({ - uri: METAVERSE_BASE + '/api/v1/user/friends', - method: 'POST', - json: true, - body: { - username: friendUserName, - } - }, function (error, response) { - if (error || (response.status !== 'success')) { - print("Error: unable to friend " + friendUserName, error || response.status); - return; - } - getConnectionData(friendUserName); - } - ); + sendToQml(message) break; default: - print('Unrecognized message from Pal.qml:', JSON.stringify(message)); + print('Unrecognized message from AvatarApp.qml:', JSON.stringify(message)); } } function sendToQml(message) { tablet.sendToQml(message); } -function updateUser(data) { - print('PAL update:', JSON.stringify(data)); - sendToQml({ method: 'updateUsername', params: data }); -} -// -// User management services -// -// These are prototype versions that will be changed when the back end changes. -var METAVERSE_BASE = Account.metaverseServerURL; -function requestJSON(url, callback) { // callback(data) if successfull. Logs otherwise. - request({ - uri: url - }, function (error, response) { - if (error || (response.status !== 'success')) { - print("Error: unable to get", url, error || response.status); - return; - } - callback(response.data); - }); -} -function getProfilePicture(username, callback) { // callback(url) if successfull. (Logs otherwise) - // FIXME Prototype scrapes profile picture. We should include in user status, and also make available somewhere for myself - request({ - uri: METAVERSE_BASE + '/users/' + username - }, function (error, html) { - var matched = !error && html.match(/img class="users-img" src="([^"]*)"/); - if (!matched) { - print('Error: Unable to get profile picture for', username, error); - callback(''); - return; - } - callback(matched[1]); - }); -} -function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise) - url = METAVERSE_BASE + '/api/v1/users?' - if (domain) { - url += 'status=' + domain.slice(1, -1); // without curly braces - } else { - url += 'filter=connections'; // regardless of whether online - } - requestJSON(url, function (connectionsData) { - callback(connectionsData.users); - }); -} -function getInfoAboutUser(specificUsername, callback) { - url = METAVERSE_BASE + '/api/v1/users?filter=connections' - requestJSON(url, function (connectionsData) { - for (user in connectionsData.users) { - if (connectionsData.users[user].username === specificUsername) { - callback(connectionsData.users[user]); - return; - } - } - callback(false); - }); -} -function getConnectionData(specificUsername, domain) { // Update all the usernames that I am entitled to see, using my login but not dependent on canKick. - function frob(user) { // get into the right format - var formattedSessionId = user.location.node_id || ''; - if (formattedSessionId !== '' && formattedSessionId.indexOf("{") != 0) { - formattedSessionId = "{" + formattedSessionId + "}"; - } - return { - sessionId: formattedSessionId, - userName: user.username, - connection: user.connection, - profileUrl: user.images.thumbnail, - placeName: (user.location.root || user.location.domain || {}).name || '' - }; - } - if (specificUsername) { - getInfoAboutUser(specificUsername, function (user) { - if (user) { - updateUser(frob(user)); - } else { - print('Error: Unable to find information about ' + specificUsername + ' in connectionsData!'); - } - }); - } else { - getAvailableConnections(domain, function (users) { - if (domain) { - users.forEach(function (user) { - updateUser(frob(user)); - }); - } else { - sendToQml({ method: 'connections', params: users.map(frob) }); - } - }); - } -} - -// -// Main operations. -// -function addAvatarNode(id) { - var selected = ExtendedOverlay.isSelected(id); - return new ExtendedOverlay(id, "sphere", { - drawInFront: true, - solid: true, - alpha: 0.8, - color: color(selected, false, 0.0), - ignoreRayIntersection: false - }, selected, !conserveResources); -} -// Each open/refresh will capture a stable set of avatarsOfInterest, within the specified filter. -var avatarsOfInterest = {}; -function populateNearbyUserList(selectData, oldAudioData) { - var filter = Settings.getValue('pal/filtered') && {distance: Settings.getValue('pal/nearDistance')}, - data = [], - avatars = AvatarList.getAvatarIdentifiers(), - myPosition = filter && Camera.position, - frustum = filter && Camera.frustum, - verticalHalfAngle = filter && (frustum.fieldOfView / 2), - horizontalHalfAngle = filter && (verticalHalfAngle * frustum.aspectRatio), - orientation = filter && Camera.orientation, - forward = filter && Quat.getForward(orientation), - verticalAngleNormal = filter && Quat.getRight(orientation), - horizontalAngleNormal = filter && Quat.getUp(orientation); - avatarsOfInterest = {}; - avatars.forEach(function (id) { - var avatar = AvatarList.getAvatar(id); - var name = avatar.sessionDisplayName; - if (!name) { - // Either we got a data packet but no identity yet, or something is really messed up. In any case, - // we won't be able to do anything with this user, so don't include them. - // In normal circumstances, a refresh will bring in the new user, but if we're very heavily loaded, - // we could be losing and gaining people randomly. - print('No avatar identity data for', id); - return; - } - if (id && myPosition && (Vec3.distance(avatar.position, myPosition) > filter.distance)) { - return; - } - var normal = id && filter && Vec3.normalize(Vec3.subtract(avatar.position, myPosition)); - var horizontal = normal && angleBetweenVectorsInPlane(normal, forward, horizontalAngleNormal); - var vertical = normal && angleBetweenVectorsInPlane(normal, forward, verticalAngleNormal); - if (id && filter && ((Math.abs(horizontal) > horizontalHalfAngle) || (Math.abs(vertical) > verticalHalfAngle))) { - return; - } - var oldAudio = oldAudioData && oldAudioData[id]; - var avatarPalDatum = { - profileUrl: '', - displayName: name, - userName: '', - connection: '', - sessionId: id || '', - audioLevel: (oldAudio && oldAudio.audioLevel) || 0.0, - avgAudioLevel: (oldAudio && oldAudio.avgAudioLevel) || 0.0, - admin: false, - personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null - ignore: !!id && Users.getIgnoreStatus(id), // ditto - isPresent: true, - isReplicated: avatar.isReplicated - }; - // Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin. - Users.requestUsernameFromID(id); - if (id) { - addAvatarNode(id); // No overlay for ourselves - avatarsOfInterest[id] = true; - } else { - // Return our username from the Account API - avatarPalDatum.userName = Account.username; - } - data.push(avatarPalDatum); - print('PAL data:', JSON.stringify(avatarPalDatum)); - }); - getConnectionData(false, location.domainId); // Even admins don't get relationship data in requestUsernameFromID (which is still needed for admin status, which comes from domain). - conserveResources = Object.keys(avatarsOfInterest).length > 20; - sendToQml({ method: 'nearbyUsers', params: data }); - if (selectData) { - selectData[2] = true; - sendToQml({ method: 'select', params: selectData }); - } -} - -// The function that handles the reply from the server -function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { - var data = { - sessionId: (MyAvatar.sessionUUID === id) ? '' : id, // Pal.qml recognizes empty id specially. - // If we get username (e.g., if in future we receive it when we're friends), use it. - // Otherwise, use valid machineFingerprint (which is not valid when not an admin). - userName: username || (Users.canKick && machineFingerprint) || '', - admin: isAdmin - }; - // Ship the data off to QML - updateUser(data); -} - -var pingPong = true; -function updateOverlays() { - var eye = Camera.position; - AvatarList.getAvatarIdentifiers().forEach(function (id) { - if (!id || !avatarsOfInterest[id]) { - return; // don't update ourself, or avatars we're not interested in - } - var avatar = AvatarList.getAvatar(id); - if (!avatar) { - return; // will be deleted below if there had been an overlay. - } - 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); - overlay = addAvatarNode(id); - } - var target = avatar.position; - var distance = Vec3.distance(target, eye); - var offset = 0.2; - var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position) - var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can - if (headIndex > 0) { - offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2; - } - - // move a bit in front, towards the camera - target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); - - // now bump it up a bit - target.y = target.y + offset; - - overlay.ping = pingPong; - overlay.editOverlay({ - color: color(ExtendedOverlay.isSelected(id), overlay.hovering, overlay.audioLevel), - position: target, - dimensions: 0.032 * distance - }); - if (overlay.model) { - overlay.model.ping = pingPong; - overlay.model.editOverlay({ - position: target, - scale: 0.2 * distance, // constant apparent size - rotation: Camera.orientation - }); - } - }); - pingPong = !pingPong; - ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) - if (overlay.ping === pingPong) { - overlay.deleteOverlay(); - } - }); - // We could re-populateNearbyUserList if anything added or removed, but not for now. - HighlightedEntity.updateOverlays(); -} -function removeOverlays() { - selectedIds = []; - lastHoveringId = 0; - HighlightedEntity.clearOverlays(); - ExtendedOverlay.some(function (overlay) { - overlay.deleteOverlay(); - }); -} - -// -// Clicks. -// -function handleClick(pickRay) { - ExtendedOverlay.applyPickRay(pickRay, function (overlay) { - // Don't select directly. Tell qml, who will give us back a list of ids. - var message = {method: 'select', params: [[overlay.key], !overlay.selected, false]}; - sendToQml(message); - return true; - }); -} -function handleMouseEvent(mousePressEvent) { // handleClick if we get one. - if (!mousePressEvent.isLeftButton) { - return; - } - handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y)); -} -function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic - ExtendedOverlay.applyPickRay(pickRay, function (overlay) { - overlay.hover(true); - }, function () { - ExtendedOverlay.unHover(); - }); -} - -// handy global to keep track of which hand is the mouse (if any) -var currentHandPressed = 0; -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) { - pickRay = controllerComputePickRay(currentHandPressed); - } else { - // nothing should hover, so - ExtendedOverlay.unHover(); - return; - } - } else { - pickRay = Camera.computePickRay(event.x, event.y); - } - handleMouseMove(pickRay); -} -function handleTriggerPressed(hand, value) { - // The idea is if you press one trigger, it is the one - // we will consider the mouse. Even if the other is pressed, - // we ignore it until this one is no longer pressed. - var isPressed = value > TRIGGER_PRESS_THRESHOLD; - if (currentHandPressed === 0) { - currentHandPressed = isPressed ? hand : 0; - return; - } - if (currentHandPressed === hand) { - currentHandPressed = isPressed ? hand : 0; - return; - } - // otherwise, the other hand is still triggered - // so do nothing. -} - -// We get mouseMoveEvents from the handControllers, via handControllerPointer. -// But we don't get mousePressEvents. -var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); -var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); -function controllerComputePickRay(hand) { - var controllerPose = getControllerWorldLocation(hand, true); - if (controllerPose.valid) { - return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) }; - } -} -function makeClickHandler(hand) { - return function (clicked) { - if (clicked > TRIGGER_CLICK_THRESHOLD) { - var pickRay = controllerComputePickRay(hand); - handleClick(pickRay); - } - }; -} -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)); -triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); -triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); // // Manage the connection between the button and the window. // @@ -685,80 +69,56 @@ function startup() { }); button.clicked.connect(onTabletButtonClicked); tablet.screenChanged.connect(onTabletScreenChanged); - Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); - Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); - Messages.subscribe(CHANNEL); - Messages.messageReceived.connect(receiveMessage); - Users.avatarDisconnected.connect(avatarDisconnected); - AvatarList.avatarAddedEvent.connect(avatarAdded); - AvatarList.avatarRemovedEvent.connect(avatarRemoved); - AvatarList.avatarSessionChangedEvent.connect(avatarSessionChanged); +// Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); +// Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); +// Users.avatarDisconnected.connect(avatarDisconnected); +// AvatarList.avatarAddedEvent.connect(avatarAdded); +// AvatarList.avatarRemovedEvent.connect(avatarRemoved); +// AvatarList.avatarSessionChangedEvent.connect(avatarSessionChanged); } startup(); var isWired = false; -var audioTimer; -var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too) -var AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS = 300; function off() { if (isWired) { // It is not ok to disconnect these twice, hence guard. - Script.update.disconnect(updateOverlays); - Controller.mousePressEvent.disconnect(handleMouseEvent); - Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); + //Controller.mousePressEvent.disconnect(handleMouseEvent); + //Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); tablet.tabletShownChanged.disconnect(tabletVisibilityChanged); - Users.usernameFromIDReply.disconnect(usernameFromIDReply); isWired = false; - ContextOverlay.enabled = true } - if (audioTimer) { - Script.clearInterval(audioTimer); - } - triggerMapping.disable(); // It's ok if we disable twice. - triggerPressMapping.disable(); // see above - removeOverlays(); - Users.requestsDomainListData = false; } function tabletVisibilityChanged() { if (!tablet.tabletShown) { - ContextOverlay.enabled = true; tablet.gotoHomeScreen(); } } -var onPalScreen = false; +var onAvatarAppScreen = false; function onTabletButtonClicked() { - if (onPalScreen) { + if (onAvatarAppScreen) { // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); - ContextOverlay.enabled = true; } else { ContextOverlay.enabled = false; - tablet.loadQMLSource(PAL_QML_SOURCE); + tablet.loadQMLSource(AVATARAPP_QML_SOURCE); tablet.tabletShownChanged.connect(tabletVisibilityChanged); - Users.requestsDomainListData = true; - populateNearbyUserList(); isWired = true; - Script.update.connect(updateOverlays); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - Users.usernameFromIDReply.connect(usernameFromIDReply); - triggerMapping.enable(); - triggerPressMapping.enable(); - audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); } } var hasEventBridge = false; function wireEventBridge(on) { if (on) { if (!hasEventBridge) { + console.debug('tablet.fromQml.connect') tablet.fromQml.connect(fromQml); hasEventBridge = true; } } else { if (hasEventBridge) { + console.debug('tablet.fromQml.disconnect') tablet.fromQml.disconnect(fromQml); hasEventBridge = false; } @@ -766,139 +126,37 @@ function wireEventBridge(on) { } function onTabletScreenChanged(type, url) { - onPalScreen = (type === "QML" && url === PAL_QML_SOURCE); - wireEventBridge(onPalScreen); - // for toolbar mode: change button to active when window is first openend, false otherwise. - button.editProperties({isActive: onPalScreen}); + console.debug('avatarapp.js: onTabletScreenChanged: ', type, url); - // disable sphere overlays when not on pal screen. - if (!onPalScreen) { + onAvatarAppScreen = (type === "QML" && url === AVATARAPP_QML_SOURCE); + wireEventBridge(onAvatarAppScreen); + // for toolbar mode: change button to active when window is first openend, false otherwise. + button.editProperties({isActive: onAvatarAppScreen}); + + if (onAvatarAppScreen) { + sendToQml({'method' : 'initialize'}) + } + + console.debug('onAvatarAppScreen: ', onAvatarAppScreen); + + // disable sphere overlays when not on avatarapp screen. + if (!onAvatarAppScreen) { off(); } } -// -// Message from other scripts, such as edit.js -// -var CHANNEL = 'com.highfidelity.pal'; -function receiveMessage(channel, messageString, senderID) { - if ((channel !== CHANNEL) || (senderID !== MyAvatar.sessionUUID)) { - return; - } - var message = JSON.parse(messageString); - switch (message.method) { - case 'select': - sendToQml(message); // Accepts objects, not just strings. - break; - default: - print('Unrecognized PAL message', messageString); - } -} - -var AVERAGING_RATIO = 0.05; -var LOUDNESS_FLOOR = 11.0; -var LOUDNESS_SCALE = 2.8 / 5.0; -var LOG2 = Math.log(2.0); -var AUDIO_PEAK_DECAY = 0.02; -var myData = {}; // we're not includied in ExtendedOverlay.get. - -function scaleAudio(val) { - var audioLevel = 0.0; - if (val <= LOUDNESS_FLOOR) { - audioLevel = val / LOUDNESS_FLOOR * LOUDNESS_SCALE; - } else { - audioLevel = (val - (LOUDNESS_FLOOR - 1)) * LOUDNESS_SCALE; - } - if (audioLevel > 1.0) { - audioLevel = 1; - } - return audioLevel; -} - -function getAudioLevel(id) { - // the VU meter should work similarly to the one in AvatarInputs: log scale, exponentially averaged - // But of course it gets the data at a different rate, so we tweak the averaging ratio and frequency - // of updating (the latter for efficiency too). - var avatar = AvatarList.getAvatar(id); - var audioLevel = 0.0; - var avgAudioLevel = 0.0; - var data = id ? ExtendedOverlay.get(id) : myData; - if (data) { - - // we will do exponential moving average by taking some the last loudness and averaging - data.accumulatedLevel = AVERAGING_RATIO * (data.accumulatedLevel || 0) + (1 - AVERAGING_RATIO) * (avatar.audioLoudness); - - // add 1 to insure we don't go log() and hit -infinity. Math.log is - // natural log, so to get log base 2, just divide by ln(2). - audioLevel = scaleAudio(Math.log(data.accumulatedLevel + 1) / LOG2); - - // decay avgAudioLevel - avgAudioLevel = Math.max((1 - AUDIO_PEAK_DECAY) * (data.avgAudioLevel || 0), audioLevel); - - data.avgAudioLevel = avgAudioLevel; - data.audioLevel = audioLevel; - - // now scale for the gain. Also, asked to boost the low end, so one simple way is - // to take sqrt of the value. Lets try that, see how it feels. - avgAudioLevel = Math.min(1.0, Math.sqrt(avgAudioLevel * (sessionGains[id] || 0.75))); - } - return [audioLevel, avgAudioLevel]; -} - -function createAudioInterval(interval) { - // we will update the audioLevels periodically - // TODO: tune for efficiency - expecially with large numbers of avatars - return Script.setInterval(function () { - var param = {}; - AvatarList.getAvatarIdentifiers().forEach(function (id) { - var level = getAudioLevel(id), - userId = id || 0; // qml didn't like an object with null/empty string for a key, so... - param[userId] = level; - }); - sendToQml({method: 'updateAudioLevel', params: param}); - }, interval); -} - -function avatarDisconnected(nodeID) { - // remove from the pal list - sendToQml({method: 'avatarDisconnected', params: [nodeID]}); -} - -function clearLocalQMLDataAndClosePAL() { - sendToQml({ method: 'clearLocalQMLData' }); - if (onPalScreen) { - ContextOverlay.enabled = true; - tablet.gotoHomeScreen(); - } -} - -function avatarAdded(avatarID) { - sendToQml({ method: 'palIsStale', params: [avatarID, 'avatarAdded'] }); -} - -function avatarRemoved(avatarID) { - sendToQml({ method: 'palIsStale', params: [avatarID, 'avatarRemoved'] }); -} - -function avatarSessionChanged(avatarID) { - sendToQml({ method: 'palIsStale', params: [avatarID, 'avatarSessionChanged'] }); -} - function shutdown() { - if (onPalScreen) { + if (onAvatarAppScreen) { tablet.gotoHomeScreen(); } button.clicked.disconnect(onTabletButtonClicked); tablet.removeButton(button); tablet.screenChanged.disconnect(onTabletScreenChanged); - Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL); - Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL); - Messages.subscribe(CHANNEL); - Messages.messageReceived.disconnect(receiveMessage); - Users.avatarDisconnected.disconnect(avatarDisconnected); - AvatarList.avatarAddedEvent.disconnect(avatarAdded); - AvatarList.avatarRemovedEvent.disconnect(avatarRemoved); - AvatarList.avatarSessionChangedEvent.disconnect(avatarSessionChanged); +// Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL); +// Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL); +// AvatarList.avatarAddedEvent.disconnect(avatarAdded); +// AvatarList.avatarRemovedEvent.disconnect(avatarRemoved); +// AvatarList.avatarSessionChangedEvent.disconnect(avatarSessionChanged); off(); }