diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index be32084f1d..296cbf7fa2 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -114,7 +114,7 @@ Rectangle { jointNames = message.data.jointNames; emitSendToScript({'method' : getAvatarsMethod}); } else if(message.method === 'wearableUpdated') { - adjustWearables.refreshWearable(message.entityID, message.wearableIndex, message.properties); + adjustWearables.refreshWearable(message.entityID, message.wearableIndex, message.properties, message.updateUI); } else if(message.method === 'wearablesUpdated') { var wearablesModel = currentAvatar.wearables; wearablesModel.clear(); diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index 533a0a1789..4a05abe500 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -67,24 +67,40 @@ Rectangle { wearablesCombobox.currentIndex = 0; } - function refreshWearable(wearableID, wearableIndex, properties) { - var wearable = wearablesCombobox.model.get(wearableIndex); - for(var prop in properties) { - wearable[prop] = properties[prop]; - - // 2do: consider removing 'properties' and manipulating localPosition/localRotation directly - var wearablesModelProps = wearablesModel.get(wearableIndex).properties; - wearablesModelProps[prop] = properties[prop]; - wearablesModel.setProperty(wearableIndex, 'properties', wearablesModelProps); - - console.debug('updated wearable', prop, - 'old = ', JSON.stringify(wearable[prop], 0, 4), - 'new = ', JSON.stringify(properties[prop], 0, 4), - 'model = ', JSON.stringify(wearablesCombobox.model.get(wearableIndex)[prop]), - 'wearablesModel = ', JSON.stringify(wearablesModel.get(wearableIndex).properties[prop], 0, 4) - ); + function refreshWearable(wearableID, wearableIndex, properties, updateUI) { + if(wearableIndex === -1) { + wearableIndex = wearablesCombobox.model.findIndexById(wearableID); } + var wearable = wearablesCombobox.model.get(wearableIndex); + console.debug('refreshWearable: ', wearableID, JSON.stringify(wearable, 0, 4)); + + if(!wearable) { + return; + } + + var wearableModelItem = wearablesModel.get(wearableIndex); + + for(var prop in properties) { + console.debug('updated wearable', prop, + 'old = ', JSON.stringify(wearable[prop], 0, 4), + 'new = ', JSON.stringify(properties[prop], 0, 4)); + + wearable[prop] = properties[prop]; + wearableModelItem.properties[prop] = wearable[prop]; + + if(updateUI) { + if(prop === 'localPosition') { + position.set(wearable[prop]); + } else if(prop === 'localRotationAngles') { + rotation.set(wearable[prop]); + } else if(prop === 'dimensions') { + scalespinner.set(wearable[prop].x / wearable.naturalDimensions.x); + } + } + } + + wearablesModel.setProperty(wearableIndex, 'properties', wearableModelItem.properties); console.debug('wearablesModel.get(wearableIndex).properties: ', JSON.stringify(wearablesModel.get(wearableIndex).properties, 0, 4)) } @@ -133,6 +149,17 @@ Rectangle { comboBox.textRole: "text" model: ListModel { + function findIndexById(id) { + + for(var i = 0; i < count; ++i) { + var wearable = get(i); + if(wearable.id === id) { + return i; + } + } + + return -1; + } } comboBox.onCurrentIndexChanged: { @@ -141,24 +168,9 @@ Rectangle { var currentWearable = getCurrentWearable(); if(currentWearable) { - position.notify = false; - position.xvalue = currentWearable.localPosition.x - position.yvalue = currentWearable.localPosition.y - position.zvalue = currentWearable.localPosition.z - console.debug('currentWearable.localPosition = ', JSON.stringify(currentWearable.localPosition, 0, 4)) - position.notify = true; - - rotation.notify = false; - rotation.xvalue = currentWearable.localRotationAngles.x - rotation.yvalue = currentWearable.localRotationAngles.y - rotation.zvalue = currentWearable.localRotationAngles.z - console.debug('currentWearable.localRotationAngles = ', JSON.stringify(currentWearable.localRotationAngles, 0, 4)) - rotation.notify = true; - - scalespinner.notify = false; - scalespinner.realValue = currentWearable.dimensions.x / currentWearable.naturalDimensions.x - console.debug('currentWearable.scale = ', scalespinner.realValue) - scalespinner.notify = true; + position.set(currentWearable.localPosition); + rotation.set(currentWearable.localRotationAngles); + scalespinner.set(currentWearable.dimensions.x / currentWearable.naturalDimensions.x) wearableSelected(currentWearable.id); } @@ -187,6 +199,16 @@ Rectangle { id: position backgroundColor: "lightgray" + function set(localPosition) { + notify = false; + xvalue = localPosition.x + yvalue = localPosition.y + zvalue = localPosition.z + notify = true; + + console.debug('position.set: localPosition = ', JSON.stringify(localPosition, 0, 4)) + } + function notifyPositionChanged() { modified = true; var properties = { @@ -231,6 +253,16 @@ Rectangle { id: rotation backgroundColor: "lightgray" + function set(localRotationAngles) { + notify = false; + xvalue = localRotationAngles.x + yvalue = localRotationAngles.y + zvalue = localRotationAngles.z + notify = true; + + console.debug('localRotationAngles = ', JSON.stringify(localRotationAngles, 0, 4)) + } + function notifyRotationChanged() { modified = true; var properties = { @@ -279,6 +311,14 @@ Rectangle { property bool notify: false; onValueChanged: if(notify) notifyScaleChanged(); + function set(value) { + notify = false; + realValue = value + notify = true; + + console.debug('scale = ', realValue) + } + function notifyScaleChanged() { modified = true; var currentWearable = getCurrentWearable(); diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 8711f25c7f..e0f48b64a5 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -152,7 +152,8 @@ function onAnimGraphUrlChanged(url) { } var selectedAvatarEntityGrabbable = false; -var selectedAvatarEntity = null; +var selectedAvatarEntityID = null; +var grabbedAvatarEntityChangeNotifier = null; var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; @@ -195,14 +196,17 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } console.debug('Entities.editEntity(message.entityID, message.properties)'.replace('message.entityID', message.entityID).replace('message.properties', JSON.stringify(message.properties))); Entities.editEntity(message.entityID, message.properties); - sendToQml({'method' : 'wearableUpdated', 'wearable' : message.entityID, wearableIndex : message.wearableIndex, properties : message.properties}) + sendToQml({'method' : 'wearableUpdated', 'entityID' : message.entityID, wearableIndex : message.wearableIndex, properties : message.properties, updateUI : false}) break; case 'adjustWearablesOpened': console.debug('avatarapp.js: adjustWearablesOpened'); currentAvatarWearablesBackup = getMyAvatarWearables(); adjustWearables.setOpened(true); + Entities.mousePressOnEntity.connect(onSelectedEntity); + Messages.subscribe('Hifi-Object-Manipulation'); + Messages.messageReceived.connect(handleWearableMessages); break; case 'adjustWearablesClosed': console.debug('avatarapp.js: adjustWearablesClosed'); @@ -222,6 +226,8 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See adjustWearables.setOpened(false); ensureWearableSelected(null); Entities.mousePressOnEntity.disconnect(onSelectedEntity); + Messages.messageReceived.disconnect(handleWearableMessages); + Messages.unsubscribe('Hifi-Object-Manipulation'); break; case 'selectWearable': ensureWearableSelected(message.entityID); @@ -334,17 +340,23 @@ function setGrabbable(entityID, grabbable) { } function ensureWearableSelected(entityID) { - if(selectedAvatarEntity !== entityID) { - - if(selectedAvatarEntity !== null) { - setGrabbable(selectedAvatarEntity, selectedAvatarEntityGrabbable); + if(selectedAvatarEntityID !== entityID) { + if(grabbedAvatarEntityChangeNotifier !== null) { + Script.clearInterval(grabbedAvatarEntityChangeNotifier); + grabbedAvatarEntityChangeNotifier = null; } - selectedAvatarEntity = entityID; + if(selectedAvatarEntityID !== null) { + setGrabbable(selectedAvatarEntityID, selectedAvatarEntityGrabbable); + } + + selectedAvatarEntityID = entityID; + console.debug('ensureWearableSelected: selectedAvatarEntity = ', selectedAvatarEntityID); + selectedAvatarEntityGrabbable = isGrabbable(entityID); - if(selectedAvatarEntity !== null) { - setGrabbable(selectedAvatarEntity, true); + if(selectedAvatarEntityID !== null) { + setGrabbable(selectedAvatarEntityID, true); } return true; @@ -358,11 +370,69 @@ function isEntityBeingWorn(entityID) { }; function onSelectedEntity(entityID, pointerEvent) { - if (isEntityBeingWorn(entityID)) { - console.debug('onSelectedEntity: clicked on wearable', entityID); + console.debug('onSelectedEntity: clicked on wearable', entityID); + + if(selectedAvatarEntityID !== entityID && isEntityBeingWorn(entityID)) + { + console.debug('onSelectedEntity: clicked on worn wearable', entityID); if(ensureWearableSelected(entityID)) { - sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntity}); + sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID}); + } + } +} + +function handleWearableMessages(channel, message, sender) { + if (channel !== 'Hifi-Object-Manipulation') { + return; + } + + var parsedMessage = null; + + try { + parsedMessage = JSON.parse(message); + console.debug('handleWearableMessages: ', JSON.stringify(parsedMessage, null, 4)); + } catch (e) { + print('error parsing wearable message'); + return; + } + + var entityID = parsedMessage.grabbedEntity; + console.debug('item worn: ', isEntityBeingWorn(entityID)) + + if(parsedMessage.action === 'grab') { + console.debug('handleWearableMessages: grab: ', JSON.stringify(parsedMessage, null, 4)); + + if(selectedAvatarEntityID !== entityID) { + ensureWearableSelected(entityID); + sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID}); + } + + console.debug('creating grabbedAvatarEntityChangeNotifier: ', grabbedAvatarEntityChangeNotifier); + + grabbedAvatarEntityChangeNotifier = Script.setInterval(function() { + console.debug('grabbedAvatarEntityChangeNotifier callback') + + // for some reasons Entities.getEntityProperties returns more than was asked.. + var propertyNames = ['localPosition', 'localRotation', 'dimensions', 'naturalDimensions']; + var entityProperties = Entities.getEntityProperties(selectedAvatarEntityID, propertyNames); + var properties = {} + + propertyNames.forEach(function(propertyName) { + properties[propertyName] = entityProperties[propertyName]; + }) + + properties.localRotationAngles = Quat.safeEulerAngles(properties.localRotation); + sendToQml({'method' : 'wearableUpdated', 'entityID' : selectedAvatarEntityID, 'wearableIndex' : -1, 'properties' : properties, updateUI : true}) + + }, 1000); + } else if(parsedMessage.action === 'release') { + console.debug('handleWearableMessages: release: ', JSON.stringify(parsedMessage, null, 4)); + + if(grabbedAvatarEntityChangeNotifier !== null) { + console.debug('clearing grabbedAvatarEntityChangeNotifier: ', grabbedAvatarEntityChangeNotifier); + Script.clearInterval(grabbedAvatarEntityChangeNotifier); + grabbedAvatarEntityChangeNotifier = null; } } } @@ -415,9 +485,9 @@ startup(); var isWired = false; function off() { + console.debug('entering avatarapp.js: off'); + if (isWired) { // It is not ok to disconnect these twice, hence guard. - //Controller.mousePressEvent.disconnect(handleMouseEvent); - //Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); tablet.tabletShownChanged.disconnect(tabletVisibilityChanged); isWired = false; } @@ -426,6 +496,9 @@ function off() { adjustWearables.setOpened(false); ensureWearableSelected(null); Entities.mousePressOnEntity.disconnect(onSelectedEntity); + + Messages.messageReceived.disconnect(handleWearableMessages); + Messages.unsubscribe('Hifi-Object-Manipulation'); } AvatarBookmarks.bookmarkLoaded.disconnect(onBookmarkLoaded); @@ -437,9 +510,13 @@ function off() { MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged); + + console.debug('exiting avatarapp.js: off'); } function on() { + console.debug('entering avatarapp.js: on'); + AvatarBookmarks.bookmarkLoaded.connect(onBookmarkLoaded); AvatarBookmarks.bookmarkDeleted.connect(onBookmarkDeleted); AvatarBookmarks.bookmarkAdded.connect(onBookmarkAdded); @@ -449,6 +526,8 @@ function on() { MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.connect(onTargetScaleChanged); + + console.debug('exiting avatarapp.js: on'); } function tabletVisibilityChanged() {