diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index e8568a7ff3..77f416f31e 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -435,7 +435,11 @@ void AudioMixer::start() { QCoreApplication::processEvents(); } - int numToRetain = nodeList->size() * (1 - _throttlingRatio); + int numToRetain = -1; + assert(_throttlingRatio >= 0.0f && _throttlingRatio <= 1.0f); + if (_throttlingRatio > EPSILON) { + numToRetain = nodeList->size() * (1.0f - _throttlingRatio); + } nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { // mix across slave threads auto mixTimer = _mixTiming.timer(); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 7e0b6a00ad..a037f24345 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -416,7 +416,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // NOTE: Here's where we determine if we are over budget and drop remaining avatars, // or send minimal avatar data in uncommon case of PALIsOpen. int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars; - bool overBudget = (identityBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes) > maxAvatarBytesPerFrame; + auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes; + bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame; if (overBudget) { if (PALIsOpen) { _stats.overBudgetAvatars++; @@ -497,8 +498,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) _stats.avatarDataPackingElapsedTime += (quint64) chrono::duration_cast(endAvatarDataPacking - startAvatarDataPacking).count(); - // use helper to add any changed traits to our packet list - traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); + if (!overBudget) { + // use helper to add any changed traits to our packet list + traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); + } + remainingAvatars--; } diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 346e846748..85bd9e68b3 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -10,10 +10,85 @@ $(document).ready(function(){ function progressBarHTML(extraClass, label) { var html = "
"; html += "
"; - html += label + "
"; + html += ""; return html; } + function showUploadProgress(title) { + swal({ + title: title, + text: progressBarHTML('upload-content-progress', 'Upload'), + html: true, + showConfirmButton: false, + allowEscapeKey: false + }); + } + + function uploadNextChunk(file, offset, id) { + if (offset == undefined) { + offset = 0; + } + if (id == undefined) { + // Identify this upload session + id = Math.round(Math.random() * 2147483647); + } + + var fileSize = file.size; + var filename = file.name; + + var CHUNK_SIZE = 1048576; // 1 MiB + + var isFinal = Boolean(fileSize - offset <= CHUNK_SIZE); + var nextChunkSize = Math.min(fileSize - offset, CHUNK_SIZE); + var chunk = file.slice(offset, offset + nextChunkSize, file.type); + var chunkFormData = new FormData(); + + var formItemName = 'restore-file-chunk'; + if (offset == 0) { + formItemName = isFinal ? 'restore-file-chunk-only' : 'restore-file-chunk-initial'; + } else if (isFinal) { + formItemName = 'restore-file-chunk-final'; + } + + chunkFormData.append(formItemName, chunk, filename); + var ajaxParams = { + url: '/content/upload', + type: 'POST', + timeout: 30000, // 30 s + headers: {"X-Session-Id": id}, + cache: false, + processData: false, + contentType: false, + data: chunkFormData + }; + + var ajaxObject = $.ajax(ajaxParams); + ajaxObject.fail(function (jqXHR, textStatus, errorThrown) { + showErrorMessage( + "Error", + "There was a problem restoring domain content.\n" + + "Please ensure that the content archive or entity file is valid and try again." + ); + }); + + updateProgressBars($('.upload-content-progress'), (offset + nextChunkSize) * 100 / fileSize); + + if (!isFinal) { + ajaxObject.done(function (data, textStatus, jqXHR) + { uploadNextChunk(file, offset + CHUNK_SIZE, id); }); + } else { + ajaxObject.done(function(data, textStatus, jqXHR) { + isRestoring = true; + + // immediately reload backup information since one should be restoring now + reloadBackupInformation(); + + swal.close(); + }); + } + + } + function setupBackupUpload() { // construct the HTML needed for the settings backup panel var html = "
"; @@ -50,34 +125,10 @@ $(document).ready(function(){ "Restore content", function() { var files = $('#' + RESTORE_SETTINGS_FILE_ID).prop('files'); + var file = files[0]; - var fileFormData = new FormData(); - fileFormData.append('restore-file', files[0]); - - showSpinnerAlert("Uploading content to restore"); - - $.ajax({ - url: '/content/upload', - type: 'POST', - timeout: 3600000, // Set timeout to 1h - cache: false, - processData: false, - contentType: false, - data: fileFormData - }).done(function(data, textStatus, jqXHR) { - isRestoring = true; - - // immediately reload backup information since one should be restoring now - reloadBackupInformation(); - - swal.close(); - }).fail(function(jqXHR, textStatus, errorThrown) { - showErrorMessage( - "Error", - "There was a problem restoring domain content.\n" - + "Please ensure that the content archive or entity file is valid and try again." - ); - }); + showUploadProgress("Uploading " + file.name); + uploadNextChunk(file); } ); }); @@ -168,6 +219,11 @@ $(document).ready(function(){ checkBackupStatus(); }); + function updateProgressBars($progressBar, value) { + $progressBar.attr('aria-valuenow', value).attr('style', 'width: ' + value + '%'); + $progressBar.find('.ongoing-msg').html(" " + Math.round(value) + "%"); + } + function reloadBackupInformation() { // make a GET request to get backup information to populate the table $.ajax({ @@ -204,11 +260,6 @@ $(document).ready(function(){ + "
  • Delete
  • "; } - function updateProgressBars($progressBar, value) { - $progressBar.attr('aria-valuenow', value).attr('style', 'width: ' + value + '%'); - $progressBar.find('.sr-only').html(value + "% Complete"); - } - // before we add any new rows and update existing ones // remove our flag for active rows $('.' + ACTIVE_BACKUP_ROW_CLASS).removeClass(ACTIVE_BACKUP_ROW_CLASS); diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 518ed73f9e..3b8180e49e 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -348,6 +348,27 @@ void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise }); } +void DomainContentBackupManager::recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "recoverFromUploadedFile", Q_ARG(MiniPromise::Promise, promise), + Q_ARG(QString, uploadedFilename)); + return; + } + + qDebug() << "Recovering from uploaded file -" << uploadedFilename; + + QFile uploadedFile(uploadedFilename); + QuaZip uploadedZip { &uploadedFile }; + + QString backupName = MANUAL_BACKUP_PREFIX + "uploaded.zip"; + + bool success = recoverFromBackupZip(backupName, uploadedZip); + + promise->resolve({ + { "success", success } + }); +} + std::vector DomainContentBackupManager::getAllBackups() { QDir backupDir { _backupDirectory }; diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index 2b07afe0b3..4af3ae5bfd 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -86,6 +86,7 @@ public slots: void createManualBackup(MiniPromise::Promise promise, const QString& name); void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName); void recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup); + void recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename); void deleteBackup(MiniPromise::Promise promise, const QString& backupName); signals: diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 8cf033c130..69f16af8b3 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2258,46 +2258,18 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url // check the file extension to see what kind of file this is // to make sure we handle this filetype for a content restore auto dispositionValue = QString(firstFormData.first.value("Content-Disposition")); - auto formDataFilenameRegex = QRegExp("filename=\"(.+)\""); - auto matchIndex = formDataFilenameRegex.indexIn(dispositionValue); + QRegExp formDataFieldsRegex(R":(name="(restore-file.*)".*filename="(.+)"):"); + auto matchIndex = formDataFieldsRegex.indexIn(dispositionValue); + QString formItemName = ""; QString uploadedFilename = ""; if (matchIndex != -1) { - uploadedFilename = formDataFilenameRegex.cap(1); - } - - if (uploadedFilename.endsWith(".json", Qt::CaseInsensitive) - || uploadedFilename.endsWith(".json.gz", Qt::CaseInsensitive)) { - // invoke our method to hand the new octree file off to the octree server - QMetaObject::invokeMethod(this, "handleOctreeFileReplacement", - Qt::QueuedConnection, Q_ARG(QByteArray, firstFormData.second)); - - // respond with a 200 for success - connection->respond(HTTPConnection::StatusCode200); - } else if (uploadedFilename.endsWith(".zip", Qt::CaseInsensitive)) { - auto deferred = makePromise("recoverFromUploadedBackup"); - - deferred->then([connectionPtr, JSON_MIME_TYPE](QString error, QVariantMap result) { - if (!connectionPtr) { - return; - } - - QJsonObject rootJSON; - auto success = result["success"].toBool(); - rootJSON["success"] = success; - QJsonDocument docJSON(rootJSON); - connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), - JSON_MIME_TYPE.toUtf8()); - }); - - _contentManager->recoverFromUploadedBackup(deferred, firstFormData.second); - - return true; - } else { - // we don't have handling for this filetype, send back a 400 for failure - connection->respond(HTTPConnection::StatusCode400); + formItemName = formDataFieldsRegex.cap(1); + uploadedFilename = formDataFieldsRegex.cap(2); } + // Received a chunk + processPendingContent(connection, formItemName, uploadedFilename, firstFormData.second); } else { // respond with a 400 for failure connection->respond(HTTPConnection::StatusCode400); @@ -2546,6 +2518,72 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u } } +bool DomainServer::processPendingContent(HTTPConnection* connection, QString itemName, QString filename, QByteArray dataChunk) { + static const QString UPLOAD_SESSION_KEY { "X-Session-Id" }; + QByteArray sessionIdBytes = connection->requestHeader(UPLOAD_SESSION_KEY); + int sessionId = sessionIdBytes.toInt(); + + bool newUpload = itemName == "restore-file" || itemName == "restore-file-chunk-initial" || itemName == "restore-file-chunk-only"; + + if (filename.endsWith(".zip", Qt::CaseInsensitive)) { + static const QString TEMPORARY_CONTENT_FILEPATH { QDir::tempPath() + "/hifiUploadContent_XXXXXX.zip" }; + + if (_pendingContentFiles.find(sessionId) == _pendingContentFiles.end()) { + if (!newUpload) { + return false; + } + std::unique_ptr newTemp(new QTemporaryFile(TEMPORARY_CONTENT_FILEPATH)); + _pendingContentFiles[sessionId] = std::move(newTemp); + } else if (newUpload) { + qCDebug(domain_server) << "New upload received using existing session ID"; + _pendingContentFiles[sessionId]->resize(0); + } + + QTemporaryFile& _pendingFileContent = *_pendingContentFiles[sessionId]; + if (!_pendingFileContent.open()) { + _pendingContentFiles.erase(sessionId); + connection->respond(HTTPConnection::StatusCode400); + return false; + } + _pendingFileContent.seek(_pendingFileContent.size()); + _pendingFileContent.write(dataChunk); + _pendingFileContent.close(); + + // Respond immediately - will timeout if we wait for restore. + connection->respond(HTTPConnection::StatusCode200); + if (itemName == "restore-file" || itemName == "restore-file-chunk-final" || itemName == "restore-file-chunk-only") { + auto deferred = makePromise("recoverFromUploadedBackup"); + + deferred->then([this, sessionId](QString error, QVariantMap result) { + _pendingContentFiles.erase(sessionId); + }); + + _contentManager->recoverFromUploadedFile(deferred, _pendingFileContent.fileName()); + } + } else if (filename.endsWith(".json", Qt::CaseInsensitive) + || filename.endsWith(".json.gz", Qt::CaseInsensitive)) { + if (_pendingUploadedContents.find(sessionId) == _pendingUploadedContents.end() && !newUpload) { + qCDebug(domain_server) << "Json upload with invalid session ID received"; + return false; + } + QByteArray& _pendingUploadedContent = _pendingUploadedContents[sessionId]; + _pendingUploadedContent += dataChunk; + connection->respond(HTTPConnection::StatusCode200); + + if (itemName == "restore-file" || itemName == "restore-file-chunk-final" || itemName == "restore-file-chunk-only") { + // invoke our method to hand the new octree file off to the octree server + QMetaObject::invokeMethod(this, "handleOctreeFileReplacement", + Qt::QueuedConnection, Q_ARG(QByteArray, _pendingUploadedContent)); + _pendingUploadedContents.erase(sessionId); + } + } else { + connection->respond(HTTPConnection::StatusCode400); + return false; + } + + return true; +} + HTTPSConnection* DomainServer::connectionFromReplyWithState(QNetworkReply* reply) { // grab the UUID state property from the reply QUuid stateUUID = reply->property(STATE_QUERY_KEY.toLocal8Bit()).toUuid(); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index e2bddc1aa5..f0c20241a2 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -209,6 +210,8 @@ private: HTTPSConnection* connectionFromReplyWithState(QNetworkReply* reply); + bool processPendingContent(HTTPConnection* connection, QString itemName, QString filename, QByteArray dataChunk); + bool forwardMetaverseAPIRequest(HTTPConnection* connection, const QString& metaversePath, const QString& requestSubobject, @@ -281,6 +284,9 @@ private: QHash> _pendingOAuthConnections; + std::unordered_map _pendingUploadedContents; + std::unordered_map> _pendingContentFiles; + QThread _assetClientThread; }; diff --git a/interface/resources/config/keyboard.json b/interface/resources/config/keyboard.json index 186a9c1084..b3688ef06e 100644 --- a/interface/resources/config/keyboard.json +++ b/interface/resources/config/keyboard.json @@ -701,9 +701,9 @@ "y": 0.04787999764084816 }, "position": { - "x": -0.53203323516845703, + "x": -0.59333323516845703, "y": 0.019300000742077827, - "z": -0.07286686894893646 + "z": 0.037454843521118164 }, "modelURL": "meshes/keyboard/SM_key.fbx", "texture": { @@ -752,7 +752,7 @@ "y": 0.04787999764084816 }, "position": { - "x": -0.59333323516845703, + "x": -0.65333323516845703, "y": 0.019300000742077827, "z": 0.037454843521118164 }, @@ -777,9 +777,9 @@ "y": 0.04787999764084816 }, "position": { - "x": -0.5103323516845703, - "y": 0.019300000742077827, - "z": -0.127054843521118164 + "x": -0.5503323516845703, + "y": 0.019300000742077827, + "z": -0.07282185554504395 }, "modelURL": "meshes/keyboard/SM_enter.fbx", "texture": { @@ -1479,9 +1479,9 @@ "y": 0.04787999764084816 }, "position": { - "x": -0.53203323516845703, + "x": -0.59333323516845703, "y": 0.019300000742077827, - "z": -0.07286686894893646 + "z": 0.037454843521118164 }, "modelURL": "meshes/keyboard/SM_key.fbx", "texture": { @@ -1530,7 +1530,7 @@ "y": 0.04787999764084816 }, "position": { - "x": -0.59333323516845703, + "x": -0.65333323516845703, "y": 0.019300000742077827, "z": 0.037454843521118164 }, @@ -1555,9 +1555,9 @@ "y": 0.04787999764084816 }, "position": { - "x": -0.5103323516845703, - "y": 0.019300000742077827, - "z": -0.127054843521118164 + "x": -0.5503323516845703, + "y": 0.019300000742077827, + "z": -0.07282185554504395 }, "modelURL": "meshes/keyboard/SM_enter.fbx", "texture": { @@ -2305,9 +2305,9 @@ "y": 0.04787999764084816 }, "position": { - "x": -0.53203323516845703, + "x": -0.59333323516845703, "y": 0.019300000742077827, - "z": -0.07286686894893646 + "z": 0.037454843521118164 }, "modelURL": "meshes/keyboard/SM_key.fbx", "texture": { @@ -2356,7 +2356,7 @@ "y": 0.04787999764084816 }, "position": { - "x": -0.59333323516845703, + "x": -0.65333323516845703, "y": 0.019300000742077827, "z": 0.037454843521118164 }, @@ -2381,9 +2381,9 @@ "y": 0.04787999764084816 }, "position": { - "x": -0.5103323516845703, - "y": 0.019300000742077827, - "z": -0.127054843521118164 + "x": -0.5503323516845703, + "y": 0.019300000742077827, + "z": -0.07282185554504395 }, "modelURL": "meshes/keyboard/SM_enter.fbx", "texture": { diff --git a/interface/resources/html/raiseAndLowerKeyboard.js b/interface/resources/html/raiseAndLowerKeyboard.js index 8cdb3c2327..db8ff24fcb 100644 --- a/interface/resources/html/raiseAndLowerKeyboard.js +++ b/interface/resources/html/raiseAndLowerKeyboard.js @@ -18,7 +18,7 @@ window.isKeyboardRaised = false; window.isNumericKeyboard = false; window.isPasswordField = false; - window.lastActiveElement = null; + window.lastActiveInputElement = null; function getActiveElement() { return document.activeElement; @@ -70,11 +70,15 @@ var keyboardRaised = shouldRaiseKeyboard(); var numericKeyboard = shouldSetNumeric(); var passwordField = shouldSetPasswordField(); - var activeElement = getActiveElement(); + var activeInputElement = null; + // Only set the active input element when there is an input element focussed, otherwise it will scroll on body focus as well. + if (keyboardRaised) { + activeInputElement = getActiveElement(); + } if (isWindowFocused && (keyboardRaised !== window.isKeyboardRaised || numericKeyboard !== window.isNumericKeyboard - || passwordField !== window.isPasswordField || activeElement !== window.lastActiveElement)) { + || passwordField !== window.isPasswordField || activeInputElement !== window.lastActiveInputElement)) { if (typeof EventBridge !== "undefined" && EventBridge !== null) { EventBridge.emitWebEvent( @@ -96,7 +100,7 @@ window.isKeyboardRaised = keyboardRaised; window.isNumericKeyboard = numericKeyboard; window.isPasswordField = passwordField; - window.lastActiveElement = activeElement; + window.lastActiveInputElement = activeInputElement; } }, POLL_FREQUENCY); diff --git a/interface/resources/meshes/drumstick.fbx b/interface/resources/meshes/drumstick.fbx index 0243d9fd1b..b384e6a30a 100644 Binary files a/interface/resources/meshes/drumstick.fbx and b/interface/resources/meshes/drumstick.fbx differ diff --git a/interface/resources/qml/dialogs/TabletLoginDialog.qml b/interface/resources/qml/dialogs/TabletLoginDialog.qml index dad2bb91aa..0b19e34b0a 100644 --- a/interface/resources/qml/dialogs/TabletLoginDialog.qml +++ b/interface/resources/qml/dialogs/TabletLoginDialog.qml @@ -141,6 +141,7 @@ TabletModalWindow { Component.onDestruction: { loginKeyboard.raised = false; + KeyboardScriptingInterface.raised = false; } Component.onCompleted: { diff --git a/interface/resources/sounds/keyboardPress.mp3 b/interface/resources/sounds/keyboardPress.mp3 new file mode 100644 index 0000000000..e4cd21b971 Binary files /dev/null and b/interface/resources/sounds/keyboardPress.mp3 differ diff --git a/interface/resources/sounds/keyboard_key.mp3 b/interface/resources/sounds/keyboard_key.mp3 deleted file mode 100644 index e2cec81032..0000000000 Binary files a/interface/resources/sounds/keyboard_key.mp3 and /dev/null differ diff --git a/interface/src/ui/Keyboard.cpp b/interface/src/ui/Keyboard.cpp index 5f83c095c8..6852691634 100644 --- a/interface/src/ui/Keyboard.cpp +++ b/interface/src/ui/Keyboard.cpp @@ -45,6 +45,7 @@ #include "scripting/HMDScriptingInterface.h" #include "scripting/WindowScriptingInterface.h" #include "scripting/SelectionScriptingInterface.h" +#include "scripting/HMDScriptingInterface.h" #include "DependencyManager.h" #include "raypick/StylusPointer.h" @@ -54,9 +55,9 @@ static const int LEFT_HAND_CONTROLLER_INDEX = 0; static const int RIGHT_HAND_CONTROLLER_INDEX = 1; -static const float MALLET_LENGTH = 0.2f; -static const float MALLET_TOUCH_Y_OFFSET = 0.052f; -static const float MALLET_Y_OFFSET = 0.180f; +static const float MALLET_LENGTH = 0.18f; +static const float MALLET_TOUCH_Y_OFFSET = 0.050f; +static const float MALLET_Y_OFFSET = 0.160f; static const glm::quat MALLET_ROTATION_OFFSET{0.70710678f, 0.0f, -0.70710678f, 0.0f}; static const glm::vec3 MALLET_MODEL_DIMENSIONS{0.03f, MALLET_LENGTH, 0.03f}; @@ -65,14 +66,14 @@ static const glm::vec3 MALLET_TIP_OFFSET{0.0f, MALLET_LENGTH - MALLET_TOUCH_Y_OF static const glm::vec3 Z_AXIS {0.0f, 0.0f, 1.0f}; -static const glm::vec3 KEYBOARD_TABLET_OFFSET{0.28f, -0.3f, -0.05f}; +static const glm::vec3 KEYBOARD_TABLET_OFFSET{0.30f, -0.38f, -0.04f}; static const glm::vec3 KEYBOARD_TABLET_DEGREES_OFFSET{-45.0f, 0.0f, 0.0f}; static const glm::vec3 KEYBOARD_TABLET_LANDSCAPE_OFFSET{-0.2f, -0.27f, -0.05f}; static const glm::vec3 KEYBOARD_TABLET_LANDSCAPE_DEGREES_OFFSET{-45.0f, 0.0f, -90.0f}; static const glm::vec3 KEYBOARD_AVATAR_OFFSET{-0.6f, 0.3f, -0.7f}; static const glm::vec3 KEYBOARD_AVATAR_DEGREES_OFFSET{0.0f, 180.0f, 0.0f}; -static const QString SOUND_FILE = PathUtils::resourcesUrl() + "sounds/keyboard_key.mp3"; +static const QString SOUND_FILE = PathUtils::resourcesUrl() + "sounds/keyboardPress.mp3"; static const QString MALLET_MODEL_URL = PathUtils::resourcesUrl() + "meshes/drumstick.fbx"; static const float PULSE_STRENGTH = 0.6f; @@ -221,6 +222,7 @@ Keyboard::Keyboard() { auto pointerManager = DependencyManager::get(); auto windowScriptingInterface = DependencyManager::get(); auto myAvatar = DependencyManager::get()->getMyAvatar(); + auto hmdScriptingInterface = DependencyManager::get(); connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, this, &Keyboard::handleTriggerBegin, Qt::QueuedConnection); connect(pointerManager.data(), &PointerManager::triggerContinueOverlay, this, &Keyboard::handleTriggerContinue, Qt::QueuedConnection); connect(pointerManager.data(), &PointerManager::triggerEndOverlay, this, &Keyboard::handleTriggerEnd, Qt::QueuedConnection); @@ -228,6 +230,7 @@ Keyboard::Keyboard() { connect(pointerManager.data(), &PointerManager::hoverEndOverlay, this, &Keyboard::handleHoverEnd, Qt::QueuedConnection); connect(myAvatar.get(), &MyAvatar::sensorToWorldScaleChanged, this, &Keyboard::scaleKeyboard, Qt::QueuedConnection); connect(windowScriptingInterface.data(), &WindowScriptingInterface::domainChanged, [&]() { setRaised(false); }); + connect(hmdScriptingInterface.data(), &HMDScriptingInterface::displayModeChanged, [&]() { setRaised(false); }); } void Keyboard::registerKeyboardHighlighting() { @@ -483,7 +486,7 @@ void Keyboard::handleTriggerBegin(const OverlayID& overlayID, const PointerEvent AudioInjectorOptions audioOptions; audioOptions.localOnly = true; audioOptions.position = keyWorldPosition; - audioOptions.volume = 0.1f; + audioOptions.volume = 0.05f; AudioInjector::playSoundAndDelete(_keySound, audioOptions); @@ -835,8 +838,8 @@ void Keyboard::loadKeyboardFile(const QString& keyboardFile) { _textDisplay = textDisplay; _ignoreItemsLock.withWriteLock([&] { - _itemsToIgnore.push_back(_textDisplay.overlayID); - _itemsToIgnore.push_back(_anchor.overlayID); + _itemsToIgnore.append(_textDisplay.overlayID); + _itemsToIgnore.append(_anchor.overlayID); }); _layerIndex = 0; auto pointerManager = DependencyManager::get(); diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 7593e12e07..22b123c85d 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -535,7 +535,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay bool bestIsFront = false; bool bestIsTablet = false; auto tabletIDs = qApp->getTabletIDs(); - + const QVector keyboardKeysToDiscard = DependencyManager::get()->getKeysID(); QMutexLocker locker(&_mutex); RayToOverlayIntersectionResult result; QMapIterator i(_overlaysWorld); @@ -545,7 +545,8 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay auto thisOverlay = std::dynamic_pointer_cast(i.value()); if ((overlaysToDiscard.size() > 0 && overlaysToDiscard.contains(thisID)) || - (overlaysToInclude.size() > 0 && !overlaysToInclude.contains(thisID))) { + (overlaysToInclude.size() > 0 && !overlaysToInclude.contains(thisID)) || + (keyboardKeysToDiscard.size() > 0 && keyboardKeysToDiscard.contains(thisID))) { continue; } diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 73a0891fbe..91ca2359b4 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -23,11 +23,39 @@ AnimSkeleton::AnimSkeleton(const HFMModel& hfmModel) { for (auto& joint : hfmModel.joints) { joints.push_back(joint); } - buildSkeletonFromJoints(joints); + buildSkeletonFromJoints(joints, hfmModel.jointRotationOffsets); + + // we make a copy of the inverseBindMatrices in order to prevent mutating the model bind pose + // when we are dealing with a joint offset in the model + for (int i = 0; i < (int)hfmModel.meshes.size(); i++) { + const HFMMesh& mesh = hfmModel.meshes.at(i); + std::vector dummyClustersList; + + for (int j = 0; j < mesh.clusters.size(); j++) { + std::vector bindMatrices; + // cast into a non-const reference, so we can mutate the FBXCluster + HFMCluster& cluster = const_cast(mesh.clusters.at(j)); + + HFMCluster localCluster; + localCluster.jointIndex = cluster.jointIndex; + localCluster.inverseBindMatrix = cluster.inverseBindMatrix; + localCluster.inverseBindTransform.evalFromRawMatrix(localCluster.inverseBindMatrix); + + // if we have a joint offset in the fst file then multiply its inverse by the + // model cluster inverse bind matrix + if (hfmModel.jointRotationOffsets.contains(cluster.jointIndex)) { + AnimPose localOffset(hfmModel.jointRotationOffsets[cluster.jointIndex], glm::vec3()); + localCluster.inverseBindMatrix = (glm::mat4)localOffset.inverse() * cluster.inverseBindMatrix; + localCluster.inverseBindTransform.evalFromRawMatrix(localCluster.inverseBindMatrix); + } + dummyClustersList.push_back(localCluster); + } + _clusterBindMatrixOriginalValues.push_back(dummyClustersList); + } } -AnimSkeleton::AnimSkeleton(const std::vector& joints) { - buildSkeletonFromJoints(joints); +AnimSkeleton::AnimSkeleton(const std::vector& joints, const QMap jointOffsets) { + buildSkeletonFromJoints(joints, jointOffsets); } int AnimSkeleton::nameToJointIndex(const QString& jointName) const { @@ -166,7 +194,8 @@ void AnimSkeleton::mirrorAbsolutePoses(AnimPoseVec& poses) const { } } -void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) { +void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints, const QMap jointOffsets) { + _joints = joints; _jointsSize = (int)joints.size(); // build a cache of bind poses @@ -189,7 +218,7 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) // build relative and absolute default poses glm::mat4 relDefaultMat = glm::translate(_joints[i].translation) * preRotationTransform * glm::mat4_cast(_joints[i].rotation) * postRotationTransform; AnimPose relDefaultPose(relDefaultMat); - _relativeDefaultPoses.push_back(relDefaultPose); + int parentIndex = getParentIndex(i); if (parentIndex >= 0) { _absoluteDefaultPoses.push_back(_absoluteDefaultPoses[parentIndex] * relDefaultPose); @@ -198,6 +227,16 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) } } + for (int k = 0; k < _jointsSize; k++) { + if (jointOffsets.contains(k)) { + AnimPose localOffset(jointOffsets[k], glm::vec3()); + _absoluteDefaultPoses[k] = _absoluteDefaultPoses[k] * localOffset; + } + } + // re-compute relative poses + _relativeDefaultPoses = _absoluteDefaultPoses; + convertAbsolutePosesToRelative(_relativeDefaultPoses); + for (int i = 0; i < _jointsSize; i++) { _jointIndicesByName[_joints[i].name] = i; } diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 3a384388d0..ab89eb643d 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -24,7 +24,8 @@ public: using ConstPointer = std::shared_ptr; explicit AnimSkeleton(const HFMModel& hfmModel); - explicit AnimSkeleton(const std::vector& joints); + explicit AnimSkeleton(const std::vector& joints, const QMap jointOffsets); + int nameToJointIndex(const QString& jointName) const; const QString& getJointName(int jointIndex) const; int getNumJoints() const; @@ -62,9 +63,10 @@ public: void dump(const AnimPoseVec& poses) const; std::vector lookUpJointIndices(const std::vector& jointNames) const; + const HFMCluster getClusterBindMatricesOriginalValues(const int meshIndex, const int clusterIndex) const { return _clusterBindMatrixOriginalValues[meshIndex][clusterIndex]; } protected: - void buildSkeletonFromJoints(const std::vector& joints); + void buildSkeletonFromJoints(const std::vector& joints, const QMap jointOffsets); std::vector _joints; int _jointsSize { 0 }; @@ -76,6 +78,7 @@ protected: std::vector _nonMirroredIndices; std::vector _mirrorMap; QHash _jointIndicesByName; + std::vector> _clusterBindMatrixOriginalValues; // no copies AnimSkeleton(const AnimSkeleton&) = delete; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 8b7b0ba916..128ac05b81 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -360,8 +360,10 @@ void Rig::initJointStates(const HFMModel& hfmModel, const glm::mat4& modelOffset void Rig::reset(const HFMModel& hfmModel) { _geometryOffset = AnimPose(hfmModel.offset); _invGeometryOffset = _geometryOffset.inverse(); + _animSkeleton = std::make_shared(hfmModel); + _internalPoseSet._relativePoses.clear(); _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index d5a372e093..dd10cd30b3 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -417,6 +417,30 @@ QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) { return filepath.mid(filepath.lastIndexOf('/') + 1); } +QMap getJointRotationOffsets(const QVariantHash& mapping) { + QMap jointRotationOffsets; + static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset"; + if (!mapping.isEmpty() && mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) { + auto offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash(); + for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { + QString jointName = itr.key(); + QString line = itr.value().toString(); + auto quatCoords = line.split(','); + if (quatCoords.size() == 4) { + float quatX = quatCoords[0].mid(1).toFloat(); + float quatY = quatCoords[1].toFloat(); + float quatZ = quatCoords[2].toFloat(); + float quatW = quatCoords[3].mid(0, quatCoords[3].size() - 1).toFloat(); + if (!isNaN(quatX) && !isNaN(quatY) && !isNaN(quatZ) && !isNaN(quatW)) { + glm::quat rotationOffset = glm::quat(quatW, quatX, quatY, quatZ); + jointRotationOffsets.insert(jointName, rotationOffset); + } + } + } + } + return jointRotationOffsets; +} + HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString& url) { const FBXNode& node = _rootNode; QMap meshes; @@ -1793,6 +1817,19 @@ HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString& } } } + + auto offsets = getJointRotationOffsets(mapping); + hfmModel.jointRotationOffsets.clear(); + for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { + QString jointName = itr.key(); + glm::quat rotationOffset = itr.value(); + int jointIndex = hfmModel.getJointIndex(jointName); + if (jointIndex != -1) { + hfmModel.jointRotationOffsets.insert(jointIndex, rotationOffset); + } + qCDebug(modelformat) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset; + } + return hfmModelPtr; } diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index 7d4479e681..05e48b6534 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -311,6 +311,8 @@ public: QString getModelNameOfMesh(int meshIndex) const; QList blendshapeChannelNames; + + QMap jointRotationOffsets; }; }; diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index e3edf7bc53..d3f24073c4 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -243,6 +243,13 @@ void GeometryReader::run() { QMetaObject::invokeMethod(resource.data(), "finishedLoading", Q_ARG(bool, false)); } + } catch (QString& e) { + qCWarning(modelnetworking) << "Exception while loading model --" << e; + auto resource = _resource.toStrongRef(); + if (resource) { + QMetaObject::invokeMethod(resource.data(), "finishedLoading", + Q_ARG(bool, false)); + } } } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index c0c5a4d059..620ffb9641 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -234,7 +234,7 @@ private: #ifdef Q_OS_ANDROID Setting::Handle _enableInterstitialMode{ "enableInterstitialMode", false }; #else - Setting::Handle _enableInterstitialMode { "enableInterstitialMode", true }; + Setting::Handle _enableInterstitialMode { "enableInterstitialMode", false }; #endif QSet _domainConnectionRefusals; diff --git a/libraries/octree/src/OctreeEntitiesFileParser.cpp b/libraries/octree/src/OctreeEntitiesFileParser.cpp index 873eaff0e1..962f744c34 100644 --- a/libraries/octree/src/OctreeEntitiesFileParser.cpp +++ b/libraries/octree/src/OctreeEntitiesFileParser.cpp @@ -46,8 +46,13 @@ bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) { bool gotId = false; bool gotVersion = false; - while (!(gotDataVersion && gotEntities && gotId && gotVersion)) { - if (nextToken() != '"') { + int token = nextToken(); + + while (true) { + if (token == '}') { + break; + } + else if (token != '"') { _errorString = "Incorrect key string"; return false; } @@ -144,15 +149,13 @@ bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) { return false; } - if (gotDataVersion && gotEntities && gotId && gotVersion) { - break; - } else if (nextToken() != ',') { - _errorString = "Id/value incorrectly terminated"; - return false; + token = nextToken(); + if (token == ',') { + token = nextToken(); } } - if (nextToken() != '}' || nextToken() != -1) { + if (nextToken() != -1) { _errorString = "Ill-formed end of object"; return false; } diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index c31345bc55..86d4793aa5 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -114,18 +114,22 @@ void CauterizedModel::updateClusterMatrices() { for (int i = 0; i < (int)_meshStates.size(); i++) { Model::MeshState& state = _meshStates[i]; const HFMMesh& mesh = hfmModel.meshes.at(i); + int meshIndex = i; + for (int j = 0; j < mesh.clusters.size(); j++) { const HFMCluster& cluster = mesh.clusters.at(j); + int clusterIndex = j; + if (_useDualQuaternionSkinning) { auto jointPose = _rig.getJointPose(cluster.jointIndex); Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); Transform clusterTransform; - Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); + Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); state.clusterDualQuaternions[j].setCauterizationParameters(0.0f, jointPose.trans()); } else { auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); + glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); } } } @@ -146,9 +150,11 @@ void CauterizedModel::updateClusterMatrices() { for (int i = 0; i < _cauterizeMeshStates.size(); i++) { Model::MeshState& state = _cauterizeMeshStates[i]; const HFMMesh& mesh = hfmModel.meshes.at(i); + int meshIndex = i; for (int j = 0; j < mesh.clusters.size(); j++) { const HFMCluster& cluster = mesh.clusters.at(j); + int clusterIndex = j; if (_useDualQuaternionSkinning) { if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) { @@ -157,7 +163,7 @@ void CauterizedModel::updateClusterMatrices() { } else { Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans()); Transform clusterTransform; - Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); + Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); state.clusterDualQuaternions[j].setCauterizationParameters(1.0f, cauterizePose.trans()); } @@ -166,7 +172,7 @@ void CauterizedModel::updateClusterMatrices() { // not cauterized so just copy the value from the non-cauterized version. state.clusterMatrices[j] = _meshStates[i].clusterMatrices[j]; } else { - glm_mat4u_mul(cauterizeMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); + glm_mat4u_mul(cauterizeMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); } } } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 7da7a45e83..9cefbf65a8 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1418,18 +1418,21 @@ void Model::updateClusterMatrices() { const HFMModel& hfmModel = getHFMModel(); for (int i = 0; i < (int) _meshStates.size(); i++) { MeshState& state = _meshStates[i]; + int meshIndex = i; const HFMMesh& mesh = hfmModel.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { const HFMCluster& cluster = mesh.clusters.at(j); + int clusterIndex = j; + if (_useDualQuaternionSkinning) { auto jointPose = _rig.getJointPose(cluster.jointIndex); Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); Transform clusterTransform; - Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); + Transform::mult(clusterTransform, jointTransform, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindTransform); state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); } else { auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); + glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); } } } diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index f26bad86b0..a6f82e1417 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -46,32 +46,25 @@ void SoftAttachmentModel::updateClusterMatrices() { for (int i = 0; i < (int) _meshStates.size(); i++) { MeshState& state = _meshStates[i]; const HFMMesh& mesh = hfmModel.meshes.at(i); - + int meshIndex = i; for (int j = 0; j < mesh.clusters.size(); j++) { const HFMCluster& cluster = mesh.clusters.at(j); + int clusterIndex = j; // TODO: cache these look-ups as an optimization int jointIndexOverride = getJointIndexOverride(cluster.jointIndex); + glm::mat4 jointMatrix; + if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) { + jointMatrix = _rigOverride.getJointTransform(jointIndexOverride); + } else { + jointMatrix = _rig.getJointTransform(cluster.jointIndex); + } if (_useDualQuaternionSkinning) { - glm::mat4 jointMatrix; - if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) { - jointMatrix = _rigOverride.getJointTransform(jointIndexOverride); - } else { - jointMatrix = _rig.getJointTransform(cluster.jointIndex); - } - glm::mat4 m; - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, m); + glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, m); state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(m); } else { - glm::mat4 jointMatrix; - if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) { - jointMatrix = _rigOverride.getJointTransform(jointIndexOverride); - } else { - jointMatrix = _rig.getJointTransform(cluster.jointIndex); - } - - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); + glm_mat4u_mul(jointMatrix, _rig.getAnimSkeleton()->getClusterBindMatricesOriginalValues(meshIndex, clusterIndex).inverseBindMatrix, state.clusterMatrices[j]); } } } diff --git a/scripts/system/controllers/controllerModules/mouseHMD.js b/scripts/system/controllers/controllerModules/mouseHMD.js index 27fe82ca19..172923a8e2 100644 --- a/scripts/system/controllers/controllerModules/mouseHMD.js +++ b/scripts/system/controllers/controllerModules/mouseHMD.js @@ -29,6 +29,7 @@ function MouseHMD() { var _this = this; + this.hmdWasActive = HMD.active; this.mouseMoved = false; this.mouseActivity = new TimeLock(5000); this.handControllerActivity = new TimeLock(4000); @@ -102,6 +103,8 @@ this.isReady = function(controllerData, deltaTime) { var now = Date.now(); + var hmdChanged = this.hmdWasActive !== HMD.active; + this.hmdWasActive = HMD.active; this.triggersPressed(controllerData, now); if (HMD.active) { if (!this.mouseActivity.expired(now) && _this.handControllerActivity.expired()) { @@ -110,7 +113,7 @@ } else { Reticle.visible = false; } - } else if (!Reticle.visible) { + } else if (hmdChanged && !Reticle.visible) { Reticle.visible = true; } diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 48fdb8e565..077d50ddde 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2223,6 +2223,7 @@ var PropertiesTool = function (opts) { // are selected or if no entity is selected this will be `null`. var currentSelectedEntityID = null; var statusMonitor = null; + var blockPropertyUpdates = false; that.setVisible = function (newVisible) { visible = newVisible; @@ -2260,6 +2261,10 @@ var PropertiesTool = function (opts) { }; function updateSelections(selectionUpdated) { + if (blockPropertyUpdates) { + return; + } + var data = { type: 'update', spaceMode: selectionDisplay.getSpaceMode() @@ -2356,7 +2361,9 @@ var PropertiesTool = function (opts) { } } pushCommandForSelections(); + blockPropertyUpdates = data.blockUpdateCallback === true; selectionManager._update(false, this); + blockPropertyUpdates = false; } else if (data.type === 'saveUserData' || data.type === 'saveMaterialData') { //the event bridge and json parsing handle our avatar id string differently. var actualID = data.id.split('"')[1]; @@ -2466,6 +2473,8 @@ var PropertiesTool = function (opts) { tooltips: Script.require('./assets/data/createAppTooltips.json'), hmdActive: HMD.active, }); + } else if (data.type === "updateProperties") { + updateSelections(true); } }; diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 3a291bf27b..5b5c9e057c 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -590,9 +590,6 @@ div.section[collapsed="true"], div.section[collapsed="true"] > .section-header { background-color: #373737; } -.section-header { - cursor: pointer; -} .section-header span { font-size: 30px; @@ -948,12 +945,12 @@ div.refresh input[type="button"] { } .draggable-number.left-arrow { top: -5px; - right: 106px; + left: 0px; transform: rotate(180deg); } .draggable-number.right-arrow { top: -5px; - left: 106px; + right: 0px; } .draggable-number input[type=number] { position: absolute; @@ -1123,7 +1120,7 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { } -body#entity-list-body { +div#grid-section, body#entity-list-body { padding-bottom: 0; margin: 16px; } @@ -1552,14 +1549,15 @@ input.rename-entity { .container { display: flex; flex-flow: row nowrap; - justify-content: space-around; margin-bottom: 8px; min-height: 28px; } .container > label { margin-top: 6px; - width: 200px; + width: 160px; + min-width: 160px; + max-width: 160px; } .container > div.checkbox { @@ -1600,6 +1598,8 @@ input.rename-entity { .xyz.fstuple, .pyr.fstuple { position: relative; left: -12px; + min-width: 50px; + width: 100px; } .rgb.fstuple .tuple { @@ -1650,3 +1650,15 @@ input.number-slider { display: flex; width: 100%; } + +.spacemode-hidden { + display: none; +} + +#placeholder-property-type { + min-width: 0px; +} + +.collapse-icon { + cursor: pointer; +} diff --git a/scripts/system/html/gridControls.html b/scripts/system/html/gridControls.html index cd646fed51..4be002619a 100644 --- a/scripts/system/html/gridControls.html +++ b/scripts/system/html/gridControls.html @@ -19,47 +19,52 @@ -
    -
    - -
    -
    - +
    +
    +
    + + +
    -
    - +
    +
    + + +
    -
    +
    +
    -
    +
    +
    +
    -
    -
    +
    - -
    -
    -
    - -
    -
    -
    -
    +
    +
    -
    - +
    + +
    +
    +
    +
    +
    + +
    - +
    - \ No newline at end of file + diff --git a/scripts/system/html/js/draggableNumber.js b/scripts/system/html/js/draggableNumber.js index 1f4bc21441..c08cac2ce4 100644 --- a/scripts/system/html/js/draggableNumber.js +++ b/scripts/system/html/js/draggableNumber.js @@ -8,11 +8,14 @@ const DELTA_X_FOCUS_THRESHOLD = 1; -function DraggableNumber(min, max, step, decimals) { +function DraggableNumber(min, max, step, decimals, dragStart, dragEnd) { this.min = min; this.max = max; this.step = step !== undefined ? step : 1; this.decimals = decimals; + this.dragStartFunction = dragStart; + this.dragEndFunction = dragEnd; + this.dragging = false; this.initialMouseEvent = null; this.lastMouseEvent = null; this.valueChangeFunction = null; @@ -32,7 +35,7 @@ DraggableNumber.prototype = { mouseUp: function(event) { if (event.target === this.elText && this.initialMouseEvent) { let dx = event.clientX - this.initialMouseEvent.clientX; - if (dx <= DELTA_X_FOCUS_THRESHOLD) { + if (Math.abs(dx) <= DELTA_X_FOCUS_THRESHOLD) { this.elInput.style.visibility = "visible"; this.elText.style.visibility = "hidden"; } @@ -41,22 +44,32 @@ DraggableNumber.prototype = { }, documentMouseMove: function(event) { - if (this.lastMouseEvent) { - let initialValue = this.elInput.value; - let dx = event.clientX - this.lastMouseEvent.clientX; - let changeValue = dx !== 0; - if (changeValue) { - while (dx !== 0) { - if (dx > 0) { - this.stepUp(); - --dx; - } else { - this.stepDown(); - ++dx; + if (this.initialMouseEvent) { + let dxFromInitial = event.clientX - this.initialMouseEvent.clientX; + if (Math.abs(dxFromInitial) > DELTA_X_FOCUS_THRESHOLD && this.lastMouseEvent) { + let initialValue = this.elInput.value; + let dx = event.clientX - this.lastMouseEvent.clientX; + let changeValue = dx !== 0; + if (changeValue) { + while (dx !== 0) { + if (dx > 0) { + this.elInput.stepUp(); + --dx; + } else { + this.elInput.stepDown(); + ++dx; + } + } + this.inputChange(); + if (this.valueChangeFunction) { + this.valueChangeFunction(); } } - if (this.valueChangeFunction) { - this.valueChangeFunction(); + if (!this.dragging) { + if (this.dragStartFunction) { + this.dragStartFunction(); + } + this.dragging = true; } } this.lastMouseEvent = event; @@ -64,6 +77,12 @@ DraggableNumber.prototype = { }, documentMouseUp: function(event) { + if (this.dragging) { + if (this.dragEndFunction) { + this.dragEndFunction(); + } + this.dragging = false; + } this.lastMouseEvent = null; document.removeEventListener("mousemove", this.onDocumentMouseMove); document.removeEventListener("mouseup", this.onDocumentMouseUp); @@ -72,11 +91,17 @@ DraggableNumber.prototype = { stepUp: function() { this.elInput.stepUp(); this.inputChange(); + if (this.valueChangeFunction) { + this.valueChangeFunction(); + } }, stepDown: function() { this.elInput.stepDown(); this.inputChange(); + if (this.valueChangeFunction) { + this.valueChangeFunction(); + } }, setValue: function(newValue) { diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index c49e0d88f6..78e3cd4dc8 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -29,7 +29,7 @@ const ICON_FOR_TYPE = { const DEGREES_TO_RADIANS = Math.PI / 180.0; -const NO_SELECTION = "w"; +const NO_SELECTION = ","; const PROPERTY_SPACE_MODE = { ALL: 0, @@ -1158,18 +1158,21 @@ const GROUPS = [ label: "Link", type: "string", propertyID: "href", + placeholder: "URL", }, { label: "Script", type: "string", buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], propertyID: "script", + placeholder: "URL", }, { label: "Server Script", type: "string", buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], propertyID: "serverScripts", + placeholder: "URL", }, { label: "Server Script Status", @@ -1244,8 +1247,9 @@ const GROUPS = [ showPropertyRule: { "collisionless": "false" }, }, { - label: "Collision sound URL", + label: "Collision Sound", type: "string", + placeholder: "URL", propertyID: "collisionSoundURL", showPropertyRule: { "collisionless": "false" }, }, @@ -1500,7 +1504,7 @@ function disableProperties() { function showPropertyElement(propertyID, show) { let elProperty = properties[propertyID].elContainer; - elProperty.style.display = show ? "flex" : "none"; + elProperty.style.display = show ? "" : "none"; } function resetProperties() { @@ -1622,10 +1626,11 @@ function updateVisibleSpaceModeProperties() { if (properties.hasOwnProperty(propertyID)) { let property = properties[propertyID]; let propertySpaceMode = property.spaceMode; - if (propertySpaceMode !== PROPERTY_SPACE_MODE.ALL) { - showPropertyElement(propertyID, propertySpaceMode === currentSpaceMode); + let elProperty = properties[propertyID].elContainer; + if (propertySpaceMode !== PROPERTY_SPACE_MODE.ALL && propertySpaceMode !== currentSpaceMode) { + elProperty.classList.add('spacemode-hidden'); } else { - showPropertyElement(propertyID, true); + elProperty.classList.remove('spacemode-hidden'); } } } @@ -1636,7 +1641,7 @@ function updateVisibleSpaceModeProperties() { * PROPERTY UPDATE FUNCTIONS */ -function updateProperty(originalPropertyName, propertyValue, isParticleProperty) { +function updateProperty(originalPropertyName, propertyValue, isParticleProperty, blockUpdateCallback) { let propertyUpdate = {}; // if this is a compound property name (i.e. animation.running) then split it by . up to 3 times let splitPropertyName = originalPropertyName.split('.'); @@ -1662,7 +1667,7 @@ function updateProperty(originalPropertyName, propertyValue, isParticleProperty) }); particleSyncDebounce(); } else { - updateProperties(propertyUpdate); + updateProperties(propertyUpdate, blockUpdateCallback); } } @@ -1671,66 +1676,89 @@ var particleSyncDebounce = _.debounce(function () { particlePropertyUpdates = {}; }, DEBOUNCE_TIMEOUT); -function updateProperties(propertiesToUpdate) { +function updateProperties(propertiesToUpdate, blockUpdateCallback) { + if (blockUpdateCallback === undefined) { + blockUpdateCallback = false; + } EventBridge.emitWebEvent(JSON.stringify({ id: lastEntityID, type: "update", - properties: propertiesToUpdate + properties: propertiesToUpdate, + blockUpdateCallback: blockUpdateCallback })); } -function createEmitTextPropertyUpdateFunction(propertyName, isParticleProperty) { +function createEmitTextPropertyUpdateFunction(property) { return function() { - updateProperty(propertyName, this.value, isParticleProperty); + updateProperty(property.name, this.value, property.isParticleProperty); }; } -function createEmitCheckedPropertyUpdateFunction(propertyName, inverse, isParticleProperty) { +function createEmitCheckedPropertyUpdateFunction(property) { return function() { - updateProperty(propertyName, inverse ? !this.checked : this.checked, isParticleProperty); + updateProperty(property.name, property.data.inverse ? !this.checked : this.checked, property.isParticleProperty); }; } -function createEmitNumberPropertyUpdateFunction(propertyName, multiplier, isParticleProperty) { +function createDragStartFunction(property) { return function() { + property.dragging = true; + }; +} + +function createDragEndFunction(property) { + return function() { + property.dragging = false; + EventBridge.emitWebEvent(JSON.stringify({ + type: "updateProperties" + })); + }; +} + +function createEmitNumberPropertyUpdateFunction(property) { + return function() { + let multiplier = property.data.multiplier; if (multiplier === undefined) { multiplier = 1; } let value = parseFloat(this.value) * multiplier; - updateProperty(propertyName, value, isParticleProperty); + updateProperty(property.name, value, property.isParticleProperty, property.dragging); }; } -function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY, multiplier, isParticleProperty) { +function createEmitVec2PropertyUpdateFunction(property) { return function () { + let multiplier = property.data.multiplier; if (multiplier === undefined) { multiplier = 1; } let newValue = { - x: elX.value * multiplier, - y: elY.value * multiplier + x: property.elNumberX.elInput.value * multiplier, + y: property.elNumberY.elInput.value * multiplier }; - updateProperty(propertyName, newValue, isParticleProperty); + updateProperty(property.name, newValue, property.isParticleProperty, property.dragging); }; } -function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ, multiplier, isParticleProperty) { +function createEmitVec3PropertyUpdateFunction(property) { return function() { + let multiplier = property.data.multiplier; if (multiplier === undefined) { multiplier = 1; } let newValue = { - x: elX.value * multiplier, - y: elY.value * multiplier, - z: elZ.value * multiplier + x: property.elNumberX.elInput.value * multiplier, + y: property.elNumberY.elInput.value * multiplier, + z: property.elNumberZ.elInput.value * multiplier }; - updateProperty(propertyName, newValue, isParticleProperty); + updateProperty(property.name, newValue, property.isParticleProperty, property.dragging); }; } -function createEmitColorPropertyUpdateFunction(propertyName, elRed, elGreen, elBlue, isParticleProperty) { +function createEmitColorPropertyUpdateFunction(property) { return function() { - emitColorPropertyUpdate(propertyName, elRed.value, elGreen.value, elBlue.value, isParticleProperty); + emitColorPropertyUpdate(property.name, property.elNumberR.elInput.value, property.elNumberG.elInput.value, + property.elNumberB.elInput.value, property.isParticleProperty); }; } @@ -1755,10 +1783,10 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen updateProperty(propertyName, propertyValue, isParticleProperty); } -function createImageURLUpdateFunction(propertyName, isParticleProperty) { +function createImageURLUpdateFunction(property) { return function () { let newTextures = JSON.stringify({ "tex.picture": this.value }); - updateProperty(propertyName, newTextures, isParticleProperty); + updateProperty(property.name, newTextures, property.isParticleProperty); }; } @@ -1768,7 +1796,6 @@ function createImageURLUpdateFunction(propertyName, isParticleProperty) { */ function createStringProperty(property, elProperty) { - let propertyName = property.name; let elementID = property.elementID; let propertyData = property.data; @@ -1782,7 +1809,7 @@ function createStringProperty(property, elProperty) { `) - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty)); + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); elProperty.appendChild(elInput); @@ -1821,30 +1848,29 @@ function createBoolProperty(property, elProperty) { elInput, propertyName, property.isParticleProperty); }); } else { - elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, propertyData.inverse, - property.isParticleProperty)); + elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(property)); } return elInput; } function createNumberProperty(property, elProperty) { - let propertyName = property.name; let elementID = property.elementID; let propertyData = property.data; elProperty.className = "draggable-number"; - let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, - propertyData.step, propertyData.decimals); + let dragStartFunction = createDragStartFunction(property); + let dragEndFunction = createDragEndFunction(property); + let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, propertyData.step, + propertyData.decimals, dragStartFunction, dragEndFunction); let defaultValue = propertyData.defaultValue; if (defaultValue !== undefined) { elDraggableNumber.elInput.value = defaultValue; } - let valueChangeFunction = createEmitNumberPropertyUpdateFunction(propertyName, propertyData.multiplier, - property.isParticleProperty); + let valueChangeFunction = createEmitNumberPropertyUpdateFunction(property); elDraggableNumber.setValueChangeFunction(valueChangeFunction); elDraggableNumber.elInput.setAttribute("id", elementID); @@ -1858,22 +1884,18 @@ function createNumberProperty(property, elProperty) { } function createVec3Property(property, elProperty) { - let propertyName = property.name; - let elementID = property.elementID; let propertyData = property.data; elProperty.className = propertyData.vec3Type + " fstuple"; - let elNumberX = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER], - propertyData.min, propertyData.max, propertyData.step, propertyData.decimals); - let elNumberY = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER], - propertyData.min, propertyData.max, propertyData.step, propertyData.decimals); - let elNumberZ = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Z_NUMBER], - propertyData.min, propertyData.max, propertyData.step, propertyData.decimals); + let elNumberX = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]); + let elNumberY = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]); + let elNumberZ = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Z_NUMBER]); + elProperty.appendChild(elNumberX.elDiv); + elProperty.appendChild(elNumberY.elDiv); + elProperty.appendChild(elNumberZ.elDiv); - let valueChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elNumberX.elInput, elNumberY.elInput, - elNumberZ.elInput, propertyData.multiplier, - property.isParticleProperty); + let valueChangeFunction = createEmitVec3PropertyUpdateFunction(property); elNumberX.setValueChangeFunction(valueChangeFunction); elNumberY.setValueChangeFunction(valueChangeFunction); elNumberZ.setValueChangeFunction(valueChangeFunction); @@ -1886,8 +1908,6 @@ function createVec3Property(property, elProperty) { } function createVec2Property(property, elProperty) { - let propertyName = property.name; - let elementID = property.elementID; let propertyData = property.data; elProperty.className = propertyData.vec2Type + " fstuple"; @@ -1897,13 +1917,12 @@ function createVec2Property(property, elProperty) { elProperty.appendChild(elTuple); - let elNumberX = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER], - propertyData.min, propertyData.max, propertyData.step, propertyData.decimals); - let elNumberY = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER], - propertyData.min, propertyData.max, propertyData.step, propertyData.decimals); + let elNumberX = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]); + let elNumberY = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]); + elProperty.appendChild(elNumberX.elDiv); + elProperty.appendChild(elNumberY.elDiv); - let valueChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elNumberX.elInput, elNumberY.elInput, - propertyData.multiplier, property.isParticleProperty); + let valueChangeFunction = createEmitVec2PropertyUpdateFunction(property); elNumberX.setValueChangeFunction(valueChangeFunction); elNumberY.setValueChangeFunction(valueChangeFunction); @@ -1916,6 +1935,7 @@ function createVec2Property(property, elProperty) { function createColorProperty(property, elProperty) { let propertyName = property.name; let elementID = property.elementID; + let propertyData = property.data; elProperty.className = "rgb fstuple"; @@ -1929,12 +1949,24 @@ function createColorProperty(property, elProperty) { elProperty.appendChild(elColorPicker); elProperty.appendChild(elTuple); - let elNumberR = createTupleNumberInput(elTuple, elementID, "red", COLOR_MIN, COLOR_MAX, COLOR_STEP); - let elNumberG = createTupleNumberInput(elTuple, elementID, "green", COLOR_MIN, COLOR_MAX, COLOR_STEP); - let elNumberB = createTupleNumberInput(elTuple, elementID, "blue", COLOR_MIN, COLOR_MAX, COLOR_STEP); + if (propertyData.min === undefined) { + propertyData.min = COLOR_MIN; + } + if (propertyData.max === undefined) { + propertyData.max = COLOR_MAX; + } + if (propertyData.step === undefined) { + propertyData.step = COLOR_STEP; + } - let valueChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elNumberR.elInput, elNumberG.elInput, - elNumberB.elInput, property.isParticleProperty); + let elNumberR = createTupleNumberInput(property, "red"); + let elNumberG = createTupleNumberInput(property, "green"); + let elNumberB = createTupleNumberInput(property, "blue"); + elTuple.appendChild(elNumberR.elDiv); + elTuple.appendChild(elNumberG.elDiv); + elTuple.appendChild(elNumberB.elDiv); + + let valueChangeFunction = createEmitColorPropertyUpdateFunction(property); elNumberR.setValueChangeFunction(valueChangeFunction); elNumberG.setValueChangeFunction(valueChangeFunction); elNumberB.setValueChangeFunction(valueChangeFunction); @@ -1973,7 +2005,6 @@ function createColorProperty(property, elProperty) { } function createDropdownProperty(property, propertyID, elProperty) { - let propertyName = property.name; let elementID = property.elementID; let propertyData = property.data; @@ -1990,7 +2021,7 @@ function createDropdownProperty(property, propertyID, elProperty) { elInput.add(option); } - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty)); + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); elProperty.appendChild(elInput); @@ -1998,7 +2029,6 @@ function createDropdownProperty(property, propertyID, elProperty) { } function createTextareaProperty(property, elProperty) { - let propertyName = property.name; let elementID = property.elementID; let propertyData = property.data; @@ -2010,7 +2040,7 @@ function createTextareaProperty(property, elProperty) { elInput.readOnly = true; } - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty)); + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); elProperty.appendChild(elInput); @@ -2102,7 +2132,9 @@ function createButtonsProperty(property, elProperty, elLabel) { return elProperty; } -function createTupleNumberInput(elTuple, propertyElementID, subLabel, min, max, step, decimals) { +function createTupleNumberInput(property, subLabel) { + let propertyElementID = property.elementID; + let propertyData = property.data; let elementID = propertyElementID + "-" + subLabel.toLowerCase(); let elLabel = document.createElement('label'); @@ -2111,11 +2143,13 @@ function createTupleNumberInput(elTuple, propertyElementID, subLabel, min, max, elLabel.setAttribute("for", elementID); elLabel.style.visibility = "visible"; - let elDraggableNumber = new DraggableNumber(min, max, step, decimals); + let dragStartFunction = createDragStartFunction(property); + let dragEndFunction = createDragEndFunction(property); + let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, propertyData.step, + propertyData.decimals, dragStartFunction, dragEndFunction); elDraggableNumber.elInput.setAttribute("id", elementID); elDraggableNumber.elDiv.className += " fstuple"; elDraggableNumber.elText.insertBefore(elLabel, elDraggableNumber.elLeftArrow); - elTuple.appendChild(elDraggableNumber.elDiv); return elDraggableNumber; } @@ -2388,7 +2422,7 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r userDataElement.value = propertyUpdate.userData; - updateProperties(propertyUpdate); + updateProperties(propertyUpdate, false); } var editor = null; @@ -2769,7 +2803,7 @@ function loaded() { elLegend.appendChild(createElementFromHTML(`
    ${group.label}
    `)); let elSpan = document.createElement('span'); - elSpan.className = ".collapse-icon"; + elSpan.className = "collapse-icon"; elSpan.innerText = "M"; elLegend.appendChild(elSpan); elGroup.appendChild(elLegend); @@ -3306,17 +3340,17 @@ function loaded() { } }); - getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction('textures', false)); + getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction(properties['textures'])); // Collapsible sections - let elCollapsible = document.getElementsByClassName("section-header"); + let elCollapsible = document.getElementsByClassName("collapse-icon"); let toggleCollapsedEvent = function(event) { - let element = this.parentNode; + let element = this.parentNode.parentNode; let isCollapsed = element.dataset.collapsed !== "true"; element.dataset.collapsed = isCollapsed ? "true" : false; element.setAttribute("collapsed", isCollapsed ? "true" : "false"); - element.getElementsByClassName(".collapse-icon")[0].textContent = isCollapsed ? "L" : "M"; + this.textContent = isCollapsed ? "L" : "M"; }; for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { @@ -3401,7 +3435,7 @@ function loaded() { let propertyID = elDropdown.getAttribute("propertyID"); let property = properties[propertyID]; property.elInput = dt; - dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property.name, property.isParticleProperty)); + dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); } elDropdowns = document.getElementsByTagName("select"); diff --git a/scripts/system/html/js/gridControls.js b/scripts/system/html/js/gridControls.js index 70e91071fb..b2d5988938 100644 --- a/scripts/system/html/js/gridControls.js +++ b/scripts/system/html/js/gridControls.js @@ -83,44 +83,26 @@ function loaded() { var gridColor = { red: 255, green: 255, blue: 255 }; var elColor = document.getElementById("grid-color"); - var elColorRed = document.getElementById("grid-color-red"); - var elColorGreen = document.getElementById("grid-color-green"); - var elColorBlue = document.getElementById("grid-color-blue"); elColor.style.backgroundColor = "rgb(" + gridColor.red + "," + gridColor.green + "," + gridColor.blue + ")"; - elColorRed.value = gridColor.red; - elColorGreen.value = gridColor.green; - elColorBlue.value = gridColor.blue; - - var colorChangeFunction = function () { - gridColor = { red: elColorRed.value, green: elColorGreen.value, blue: elColorBlue.value }; - elColor.style.backgroundColor = "rgb(" + gridColor.red + "," + gridColor.green + "," + gridColor.blue + ")"; - emitUpdate(); - }; var colorPickFunction = function (red, green, blue) { - elColorRed.value = red; - elColorGreen.value = green; - elColorBlue.value = blue; gridColor = { red: red, green: green, blue: blue }; emitUpdate(); }; - elColorRed.addEventListener('change', colorChangeFunction); - elColorGreen.addEventListener('change', colorChangeFunction); - elColorBlue.addEventListener('change', colorChangeFunction); $('#grid-color').colpick({ colorScheme: 'dark', - layout: 'hex', + layout: 'rgbhex', color: { r: gridColor.red, g: gridColor.green, b: gridColor.blue }, + submit: false, onShow: function (colpick) { $('#grid-color').attr('active', 'true'); }, onHide: function (colpick) { $('#grid-color').attr('active', 'false'); }, - onSubmit: function (hsb, hex, rgb, el) { + onChange: function (hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); - $(el).colpickHide(); colorPickFunction(rgb.r, rgb.g, rgb.b); } });