diff --git a/CMakeLists.txt b/CMakeLists.txt index d52a557cb1..6120e27b75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ if (ANDROID) set(GLES_OPTION ON) set(PLATFORM_QT_COMPONENTS AndroidExtras WebView) else () - set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets) + set(PLATFORM_QT_COMPONENTS WebEngine) endif () if (USE_GLES AND (NOT ANDROID)) diff --git a/LICENSE b/LICENSE index 60e86a1cc7..deb80afc19 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2016, High Fidelity, Inc. +Copyright (c) 2013-2018, High Fidelity, Inc. All rights reserved. licensing@highfidelity.io diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index cd1bd9e2c2..d9a399c162 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -216,13 +216,14 @@ void Agent::requestScript() { } // make sure this is not a script request for the file scheme - if (scriptURL.scheme() == URL_SCHEME_FILE) { + if (scriptURL.scheme() == HIFI_URL_SCHEME_FILE) { qWarning() << "Cannot load script for Agent from local filesystem."; scriptRequestFinished(); return; } - auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(this, scriptURL); + auto request = DependencyManager::get<ResourceManager>()->createResourceRequest( + this, scriptURL, true, -1, "Agent::requestScript"); if (!request) { qWarning() << "Could not create ResourceRequest for Agent script at" << scriptURL.toString(); diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 2b5ff51b49..7d47c8e713 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -21,7 +21,6 @@ #include <QtCore/QTimer> #include <QUuid> -#include <ClientTraitsHandler.h> #include <EntityEditPacketSender.h> #include <EntityTree.h> #include <ScriptEngine.h> diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 426f3ce6fc..76ff5ab2ed 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -35,6 +35,7 @@ #include "AssignmentClientLogging.h" #include "AssignmentFactory.h" +#include "ResourceRequestObserver.h" const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client"; const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000; @@ -49,6 +50,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri DependencyManager::set<tracing::Tracer>(); DependencyManager::set<StatTracker>(); DependencyManager::set<AccountManager>(); + DependencyManager::set<ResourceRequestObserver>(); auto addressManager = DependencyManager::set<AddressManager>(); diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 7e1420ef60..7f0838d8a5 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -202,7 +202,7 @@ void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const } } -void AudioMixerClientData::setGainForAvatar(QUuid nodeID, uint8_t gain) { +void AudioMixerClientData::setGainForAvatar(QUuid nodeID, float gain) { auto it = std::find_if(_streams.active.cbegin(), _streams.active.cend(), [nodeID](const MixableStream& mixableStream){ return mixableStream.nodeStreamID.nodeID == nodeID && mixableStream.nodeStreamID.streamID.isNull(); }); diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 610b258789..0a66a8164b 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -172,7 +172,7 @@ private: void optionallyReplicatePacket(ReceivedMessage& packet, const Node& node); - void setGainForAvatar(QUuid nodeID, uint8_t gain); + void setGainForAvatar(QUuid nodeID, float gain); bool containsValidPosition(ReceivedMessage& message) const; diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 00cdabe70e..53fc13e5cf 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -541,7 +541,7 @@ void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMess // ...For those nodes, reset the lastBroadcastTime to 0 // so that the AvatarMixer will send Identity data to us [&](const SharedNodePointer& node) { - nodeData->setLastBroadcastTime(node->getUUID(), 0); + nodeData->setLastBroadcastTime(node->getLocalID(), 0); nodeData->resetSentTraitData(node->getLocalID()); } ); @@ -565,7 +565,8 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes // parse the identity packet and update the change timestamp if appropriate bool identityChanged = false; bool displayNameChanged = false; - avatar.processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged); + QDataStream avatarIdentityStream(message->getMessage()); + avatar.processAvatarIdentity(avatarIdentityStream, identityChanged, displayNameChanged); if (identityChanged) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); @@ -637,7 +638,7 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> // Reset the lastBroadcastTime for the ignored avatar to 0 // so the AvatarMixer knows it'll have to send identity data about the ignored avatar // to the ignorer if the ignorer unignores. - nodeData->setLastBroadcastTime(ignoredUUID, 0); + nodeData->setLastBroadcastTime(ignoredNode->getLocalID(), 0); nodeData->resetSentTraitData(ignoredNode->getLocalID()); } @@ -647,7 +648,7 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> // to the ignored if the ignorer unignores. AvatarMixerClientData* ignoredNodeData = reinterpret_cast<AvatarMixerClientData*>(ignoredNode->getLinkedData()); if (ignoredNodeData) { - ignoredNodeData->setLastBroadcastTime(senderNode->getUUID(), 0); + ignoredNodeData->setLastBroadcastTime(senderNode->getLocalID(), 0); ignoredNodeData->resetSentTraitData(senderNode->getLocalID()); } } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 09bdfbc564..76cdf13986 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -26,20 +26,20 @@ AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID _avatar->setID(nodeID); } -uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar) const { - std::unordered_map<QUuid, uint64_t>::const_iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar); +uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar) const { + const auto itr = _lastOtherAvatarEncodeTime.find(otherAvatar); if (itr != _lastOtherAvatarEncodeTime.end()) { return itr->second; } return 0; } -void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time) { - std::unordered_map<QUuid, uint64_t>::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar); +void AvatarMixerClientData::setLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar, uint64_t time) { + auto itr = _lastOtherAvatarEncodeTime.find(otherAvatar); if (itr != _lastOtherAvatarEncodeTime.end()) { itr->second = time; } else { - _lastOtherAvatarEncodeTime.emplace(std::pair<QUuid, uint64_t>(otherAvatar, time)); + _lastOtherAvatarEncodeTime.emplace(std::pair<NLPacket::LocalID, uint64_t>(otherAvatar, time)); } } @@ -220,7 +220,7 @@ void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedDa } } -uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) const { +uint64_t AvatarMixerClientData::getLastBroadcastTime(NLPacket::LocalID nodeUUID) const { // return the matching PacketSequenceNumber, or the default if we don't have it auto nodeMatch = _lastBroadcastTimes.find(nodeUUID); if (nodeMatch != _lastBroadcastTimes.end()) { @@ -229,9 +229,9 @@ uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) cons return 0; } -uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const { +uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) const { // return the matching PacketSequenceNumber, or the default if we don't have it - auto nodeMatch = _lastBroadcastSequenceNumbers.find(nodeUUID); + auto nodeMatch = _lastBroadcastSequenceNumbers.find(nodeID); if (nodeMatch != _lastBroadcastSequenceNumbers.end()) { return nodeMatch->second; } @@ -252,7 +252,7 @@ void AvatarMixerClientData::ignoreOther(const Node* self, const Node* other) { } else { killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble); } - setLastBroadcastTime(other->getUUID(), 0); + setLastBroadcastTime(other->getLocalID(), 0); resetSentTraitData(other->getLocalID()); @@ -331,9 +331,9 @@ AvatarMixerClientData::TraitsCheckTimestamp AvatarMixerClientData::getLastOtherA } } -void AvatarMixerClientData::cleanupKilledNode(const QUuid& nodeUUID, Node::LocalID nodeLocalID) { - removeLastBroadcastSequenceNumber(nodeUUID); - removeLastBroadcastTime(nodeUUID); +void AvatarMixerClientData::cleanupKilledNode(const QUuid&, Node::LocalID nodeLocalID) { + removeLastBroadcastSequenceNumber(nodeLocalID); + removeLastBroadcastTime(nodeLocalID); _lastSentTraitsTimestamps.erase(nodeLocalID); _sentTraitVersions.erase(nodeLocalID); } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 09d11359c3..afbc68378a 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -49,17 +49,16 @@ public: const AvatarData* getConstAvatarData() const { return _avatar.get(); } AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; } + uint16_t getLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) const; + void setLastBroadcastSequenceNumber(NLPacket::LocalID nodeID, uint16_t sequenceNumber) + { _lastBroadcastSequenceNumbers[nodeID] = sequenceNumber; } + Q_INVOKABLE void removeLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) { _lastBroadcastSequenceNumbers.erase(nodeID); } bool isIgnoreRadiusEnabled() const { return _isIgnoreRadiusEnabled; } void setIsIgnoreRadiusEnabled(bool enabled) { _isIgnoreRadiusEnabled = enabled; } - uint16_t getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const; - void setLastBroadcastSequenceNumber(const QUuid& nodeUUID, uint16_t sequenceNumber) - { _lastBroadcastSequenceNumbers[nodeUUID] = sequenceNumber; } - Q_INVOKABLE void removeLastBroadcastSequenceNumber(const QUuid& nodeUUID) { _lastBroadcastSequenceNumbers.erase(nodeUUID); } - - uint64_t getLastBroadcastTime(const QUuid& nodeUUID) const; - void setLastBroadcastTime(const QUuid& nodeUUID, uint64_t broadcastTime) { _lastBroadcastTimes[nodeUUID] = broadcastTime; } - Q_INVOKABLE void removeLastBroadcastTime(const QUuid& nodeUUID) { _lastBroadcastTimes.erase(nodeUUID); } + uint64_t getLastBroadcastTime(NLPacket::LocalID nodeUUID) const; + void setLastBroadcastTime(NLPacket::LocalID nodeUUID, uint64_t broadcastTime) { _lastBroadcastTimes[nodeUUID] = broadcastTime; } + Q_INVOKABLE void removeLastBroadcastTime(NLPacket::LocalID nodeUUID) { _lastBroadcastTimes.erase(nodeUUID); } Q_INVOKABLE void cleanupKilledNode(const QUuid& nodeUUID, Node::LocalID nodeLocalID); @@ -93,7 +92,7 @@ public: void loadJSONStats(QJsonObject& jsonObject) const; - glm::vec3 getPosition() const { return _avatar ? _avatar->getWorldPosition() : glm::vec3(0); } + glm::vec3 getPosition() const { return _avatar ? _avatar->getClientGlobalPosition() : glm::vec3(0); } bool isRadiusIgnoring(const QUuid& other) const; void addToRadiusIgnoringSet(const QUuid& other); void removeFromRadiusIgnoringSet(const QUuid& other); @@ -114,10 +113,10 @@ public: const ConicalViewFrustums& getViewFrustums() const { return _currentViewFrustums; } - uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const; - void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time); + uint64_t getLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar) const; + void setLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar, uint64_t time); - QVector<JointData>& getLastOtherAvatarSentJoints(QUuid otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; } + QVector<JointData>& getLastOtherAvatarSentJoints(NLPacket::LocalID otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; } void queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node); int processPackets(const SlaveSharedData& slaveSharedData); // returns number of packets processed @@ -150,13 +149,13 @@ private: AvatarSharedPointer _avatar { new AvatarData() }; uint16_t _lastReceivedSequenceNumber { 0 }; - std::unordered_map<QUuid, uint16_t> _lastBroadcastSequenceNumbers; - std::unordered_map<QUuid, uint64_t> _lastBroadcastTimes; + std::unordered_map<NLPacket::LocalID, uint16_t> _lastBroadcastSequenceNumbers; + std::unordered_map<NLPacket::LocalID, uint64_t> _lastBroadcastTimes; // this is a map of the last time we encoded an "other" avatar for // sending to "this" node - std::unordered_map<QUuid, uint64_t> _lastOtherAvatarEncodeTime; - std::unordered_map<QUuid, QVector<JointData>> _lastOtherAvatarSentJoints; + std::unordered_map<NLPacket::LocalID, uint64_t> _lastOtherAvatarEncodeTime; + std::unordered_map<NLPacket::LocalID, QVector<JointData>> _lastOtherAvatarSentJoints; uint64_t _identityChangeTimestamp; bool _avatarSessionDisplayNameMustChange{ true }; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index f3f47dff74..7e0b6a00ad 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -68,13 +68,11 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { _stats.processIncomingPacketsElapsedTime += (end - start); } -int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) { - if (destinationNode->getType() == NodeType::Agent && !destinationNode->isUpstream()) { +int AvatarMixerSlave::sendIdentityPacket(NLPacketList& packetList, const AvatarMixerClientData* nodeData, const Node& destinationNode) { + if (destinationNode.getType() == NodeType::Agent && !destinationNode.isUpstream()) { QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(); individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious - auto identityPackets = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); - identityPackets->write(individualData); - DependencyManager::get<NodeList>()->sendPacketList(std::move(identityPackets), *destinationNode); + packetList.write(individualData); _stats.numIdentityPackets++; return individualData.size(); } else { @@ -247,12 +245,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // reset the internal state for correct random number distribution distribution.reset(); + // Estimate number to sort on number sent last frame (with min. of 20). + const int numToSendEst = std::max(int(nodeData->getNumAvatarsSentLastFrame() * 2.5f), 20); + // reset the number of sent avatars nodeData->resetNumAvatarsSentLastFrame(); - // keep a counter of the number of considered avatars - int numOtherAvatars = 0; - // keep track of outbound data rate specifically for avatar data int numAvatarDataBytes = 0; int identityBytesSent = 0; @@ -261,7 +259,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // max number of avatarBytes per frame int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND); - // keep track of the number of other avatars held back in this frame int numAvatarsHeldBack = 0; @@ -279,10 +276,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int minimumBytesPerAvatar = PALIsOpen ? AvatarDataPacket::AVATAR_HAS_FLAGS_SIZE + NUM_BYTES_RFC4122_UUID + sizeof(AvatarDataPacket::AvatarGlobalPosition) + sizeof(AvatarDataPacket::AudioLoudness) : 0; - // setup a PacketList for the avatarPackets - auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); - static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID; - // compute node bounding box const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR); @@ -350,8 +343,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored if (nodeData->isIgnoreRadiusEnabled() || (avatarClientNodeData->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { // Perform the collision check between the two bounding boxes - const float OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR = 2.4f; // magic number determined empirically - AABox otherNodeBox = computeBubbleBox(avatarClientNodeData->getAvatar(), OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR); + AABox otherNodeBox = avatarClientNodeData->getAvatar().getDefaultBubbleBox(); if (nodeBox.touches(otherNodeBox)) { nodeData->ignoreOther(destinationNode, avatarNode); shouldIgnore = !getsAnyIgnored; @@ -364,7 +356,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } if (!shouldIgnore) { - AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID()); + AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getLocalID()); AvatarDataSequenceNumber lastSeqFromSender = avatarClientNodeData->getLastReceivedSequenceNumber(); // FIXME - This code does appear to be working. But it seems brittle. @@ -396,7 +388,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (!shouldIgnore) { // sort this one for later const AvatarData* avatarNodeData = avatarClientNodeData->getConstAvatarData(); - auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNodeData->getSessionUUID()); + auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNode->getLocalID()); sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime)); } @@ -406,8 +398,13 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int remainingAvatars = (int)sortedAvatars.size(); auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); + auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData); + const int avatarPacketCapacity = avatarPacket->getPayloadCapacity(); + int avatarSpaceAvailable = avatarPacketCapacity; + int numPacketsSent = 0; + auto identityPacketList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); - const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); + const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst); for (const auto& sortedAvatar : sortedAvatarVector) { const Node* otherNode = sortedAvatar.getNode(); auto lastEncodeForOther = sortedAvatar.getTimestamp(); @@ -432,21 +429,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) auto startAvatarDataPacking = chrono::high_resolution_clock::now(); - ++numOtherAvatars; - const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData()); const AvatarData* otherAvatar = otherNodeData->getConstAvatarData(); - // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO - // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. - if (otherAvatar->hasProcessedFirstIdentity() - && nodeData->getLastBroadcastTime(otherNode->getUUID()) <= otherNodeData->getIdentityChangeTimestamp()) { - identityBytesSent += sendIdentityPacket(otherNodeData, node); - - // remember the last time we sent identity details about this other node to the receiver - nodeData->setLastBroadcastTime(otherNode->getUUID(), usecTimestampNow()); - } - // Typically all out-of-view avatars but such avatars' priorities will rise with time: bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; @@ -456,71 +441,56 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } else if (!overBudget) { detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData; nodeData->incrementAvatarInView(); - } - bool includeThisAvatar = true; - QVector<JointData>& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); + // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO + // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. + if (otherAvatar->hasProcessedFirstIdentity() + && nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) { + identityBytesSent += sendIdentityPacket(*identityPacketList, otherNodeData, *destinationNode); - lastSentJointsForOther.resize(otherAvatar->getJointCount()); - - bool distanceAdjust = true; - glm::vec3 viewerPosition = myPosition; - AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray - bool dropFaceTracking = false; - - auto startSerialize = chrono::high_resolution_clock::now(); - QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, - &lastSentJointsForOther); - auto endSerialize = chrono::high_resolution_clock::now(); - _stats.toByteArrayElapsedTime += - (quint64) chrono::duration_cast<chrono::microseconds>(endSerialize - startSerialize).count(); - - if (bytes.size() > maxAvatarDataBytes) { - qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() - << "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data"; - - dropFaceTracking = true; // first try dropping the facial data - bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); - - if (bytes.size() > maxAvatarDataBytes) { - qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() - << "without facial data resulted in very large buffer of" << bytes.size() - << "bytes - reducing to MinimumData"; - bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); - - if (bytes.size() > maxAvatarDataBytes) { - qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() - << "MinimumData resulted in very large buffer of" << bytes.size() - << "bytes - refusing to send avatar"; - includeThisAvatar = false; - } + // remember the last time we sent identity details about this other node to the receiver + nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow()); } } - if (includeThisAvatar) { - // start a new segment in the PacketList for this avatar - avatarPacketList->startSegment(); - numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); - numAvatarDataBytes += avatarPacketList->write(bytes); - avatarPacketList->endSegment(); + QVector<JointData>& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getLocalID()); - if (detail != AvatarData::NoData) { - _stats.numOthersIncluded++; + const bool distanceAdjust = true; + const bool dropFaceTracking = false; + AvatarDataPacket::SendStatus sendStatus; + sendStatus.sendUUID = true; - // increment the number of avatars sent to this reciever - nodeData->incrementNumAvatarsSentLastFrame(); + do { + auto startSerialize = chrono::high_resolution_clock::now(); + QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, + sendStatus, dropFaceTracking, distanceAdjust, myPosition, + &lastSentJointsForOther, avatarSpaceAvailable); + auto endSerialize = chrono::high_resolution_clock::now(); + _stats.toByteArrayElapsedTime += + (quint64)chrono::duration_cast<chrono::microseconds>(endSerialize - startSerialize).count(); - // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), - otherNodeData->getLastReceivedSequenceNumber()); - nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow()); + avatarPacket->write(bytes); + avatarSpaceAvailable -= bytes.size(); + numAvatarDataBytes += bytes.size(); + if (!sendStatus || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) { + // Weren't able to fit everything. + nodeList->sendPacket(std::move(avatarPacket), *destinationNode); + ++numPacketsSent; + avatarPacket = NLPacket::create(PacketType::BulkAvatarData); + avatarSpaceAvailable = avatarPacketCapacity; } - } else { - // TODO? this avatar is not included now, and will probably not be included next frame. - // It would be nice if we could tweak its future sort priority to put it at the back of the list. + } while (!sendStatus); + + if (detail != AvatarData::NoData) { + _stats.numOthersIncluded++; + + // increment the number of avatars sent to this receiver + nodeData->incrementNumAvatarsSentLastFrame(); + + // set the last sent sequence number for this sender on the receiver + nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(), + otherNodeData->getLastReceivedSequenceNumber()); + nodeData->setLastOtherAvatarEncodeTime(otherNode->getLocalID(), usecTimestampNow()); } auto endAvatarDataPacking = chrono::high_resolution_clock::now(); @@ -532,17 +502,21 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) remainingAvatars--; } + if (nodeData->getNumAvatarsSentLastFrame() > numToSendEst) { + qCWarning(avatars) << "More avatars sent than upper estimate" << nodeData->getNumAvatarsSentLastFrame() + << " / " << numToSendEst; + } + quint64 startPacketSending = usecTimestampNow(); - // close the current packet so that we're always sending something - avatarPacketList->closeCurrentPacket(true); + if (avatarPacket->getPayloadSize() != 0) { + nodeList->sendPacket(std::move(avatarPacket), *destinationNode); + ++numPacketsSent; + } - _stats.numPacketsSent += (int)avatarPacketList->getNumPackets(); + _stats.numPacketsSent += numPacketsSent; _stats.numBytesSent += numAvatarDataBytes; - // send the avatar data PacketList - nodeList->sendPacketList(std::move(avatarPacketList), *destinationNode); - // record the bytes sent for other avatar data in the AvatarMixerClientData nodeData->recordSentAvatarData(numAvatarDataBytes); @@ -554,6 +528,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeList->sendPacketList(std::move(traitsPacketList), *destinationNode); } + // Send any AvatarIdentity packets: + identityPacketList->closeCurrentPacket(); + if (identityBytesSent > 0) { + nodeList->sendPacketList(std::move(identityPacketList), *destinationNode); + } + // record the number of avatars held back this frame nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack); nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames); @@ -599,20 +579,20 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin // so we always send a full update for this avatar quint64 start = usecTimestampNow(); - AvatarDataPacket::HasFlags flagsOut; + AvatarDataPacket::SendStatus sendStatus; QVector<JointData> emptyLastJointSendData { otherAvatar->getJointCount() }; QByteArray avatarByteArray = otherAvatar->toByteArray(AvatarData::SendAllData, 0, emptyLastJointSendData, - flagsOut, false, false, glm::vec3(0), nullptr); + sendStatus, false, false, glm::vec3(0), nullptr, 0); quint64 end = usecTimestampNow(); _stats.toByteArrayElapsedTime += (end - start); - auto lastBroadcastTime = nodeData->getLastBroadcastTime(agentNode->getUUID()); + auto lastBroadcastTime = nodeData->getLastBroadcastTime(agentNode->getLocalID()); if (lastBroadcastTime <= agentNodeData->getIdentityChangeTimestamp() || (start - lastBroadcastTime) >= REBROADCAST_IDENTITY_TO_DOWNSTREAM_EVERY_US) { sendReplicatedIdentityPacket(*agentNode, agentNodeData, *node); - nodeData->setLastBroadcastTime(agentNode->getUUID(), start); + nodeData->setLastBroadcastTime(agentNode->getLocalID(), start); } // figure out how large our avatar byte array can be to fit in the packet list @@ -630,14 +610,14 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin << "-" << avatarByteArray.size() << "bytes"; avatarByteArray = otherAvatar->toByteArray(AvatarData::SendAllData, 0, emptyLastJointSendData, - flagsOut, true, false, glm::vec3(0), nullptr); + sendStatus, true, false, glm::vec3(0), nullptr, 0); if (avatarByteArray.size() > maxAvatarByteArraySize) { qCWarning(avatars) << "Replicated avatar data without facial data still too large for" << otherAvatar->getSessionUUID() << "-" << avatarByteArray.size() << "bytes"; avatarByteArray = otherAvatar->toByteArray(AvatarData::MinimumData, 0, emptyLastJointSendData, - flagsOut, true, false, glm::vec3(0), nullptr); + sendStatus, true, false, glm::vec3(0), nullptr, 0); } } @@ -646,7 +626,7 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin nodeData->incrementNumAvatarsSentLastFrame(); // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(agentNode->getUUID(), + nodeData->setLastBroadcastSequenceNumber(agentNode->getLocalID(), agentNodeData->getLastReceivedSequenceNumber()); // increment the number of avatars sent to this reciever diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index bcb70f8743..2ef90af38e 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -101,7 +101,7 @@ public: void harvestStats(AvatarMixerSlaveStats& stats); private: - int sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode); + int sendIdentityPacket(NLPacketList& packet, const AvatarMixerClientData* nodeData, const Node& destinationNode); int sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode); qint64 addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData, diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 7d2b267a05..51038a782f 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -69,10 +69,10 @@ void ScriptableAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { AvatarData::setSkeletonModelURL(skeletonModelURL); } -static AnimPose composeAnimPose(const FBXJoint& fbxJoint, const glm::quat rotation, const glm::vec3 translation) { +static AnimPose composeAnimPose(const HFMJoint& joint, const glm::quat rotation, const glm::vec3 translation) { glm::mat4 translationMat = glm::translate(translation); - glm::mat4 rotationMat = glm::mat4_cast(fbxJoint.preRotation * rotation * fbxJoint.postRotation); - glm::mat4 finalMat = translationMat * fbxJoint.preTransform * rotationMat * fbxJoint.postTransform; + glm::mat4 rotationMat = glm::mat4_cast(joint.preRotation * rotation * joint.postRotation); + glm::mat4 finalMat = translationMat * joint.preTransform * rotationMat * joint.postTransform; return AnimPose(finalMat); } @@ -84,7 +84,7 @@ void ScriptableAvatar::update(float deltatime) { // Run animation if (_animation && _animation->isLoaded() && _animation->getFrames().size() > 0 && !_bind.isNull() && _bind->isLoaded()) { if (!_animSkeleton) { - _animSkeleton = std::make_shared<AnimSkeleton>(_bind->getGeometry()); + _animSkeleton = std::make_shared<AnimSkeleton>(_bind->getHFMModel()); } float currentFrame = _animationDetails.currentFrame + deltatime * _animationDetails.fps; if (_animationDetails.loop || currentFrame < _animationDetails.lastFrame) { @@ -93,7 +93,7 @@ void ScriptableAvatar::update(float deltatime) { } _animationDetails.currentFrame = currentFrame; - const QVector<FBXJoint>& modelJoints = _bind->getGeometry().joints; + const QVector<HFMJoint>& modelJoints = _bind->getHFMModel().joints; QStringList animationJointNames = _animation->getJointNames(); const int nJoints = modelJoints.size(); @@ -102,8 +102,8 @@ void ScriptableAvatar::update(float deltatime) { } const int frameCount = _animation->getFrames().size(); - const FBXAnimationFrame& floorFrame = _animation->getFrames().at((int)glm::floor(currentFrame) % frameCount); - const FBXAnimationFrame& ceilFrame = _animation->getFrames().at((int)glm::ceil(currentFrame) % frameCount); + const HFMAnimationFrame& floorFrame = _animation->getFrames().at((int)glm::floor(currentFrame) % frameCount); + const HFMAnimationFrame& ceilFrame = _animation->getFrames().at((int)glm::ceil(currentFrame) % frameCount); const float frameFraction = glm::fract(currentFrame); std::vector<AnimPose> poses = _animSkeleton->getRelativeDefaultPoses(); @@ -113,7 +113,7 @@ void ScriptableAvatar::update(float deltatime) { const QString& name = animationJointNames[i]; // As long as we need the model preRotations anyway, let's get the jointIndex from the bind skeleton rather than // trusting the .fst (which is sometimes not updated to match changes to .fbx). - int mapping = _bind->getGeometry().getJointIndex(name); + int mapping = _bind->getHFMModel().getJointIndex(name); if (mapping != -1 && !_maskedJoints.contains(name)) { AnimPose floorPose = composeAnimPose(modelJoints[mapping], floorFrame.rotations[i], floorFrame.translations[i] * UNIT_SCALE); diff --git a/cmake/externals/serverless-content/CMakeLists.txt b/cmake/externals/serverless-content/CMakeLists.txt index 12e2b7a4c6..8bb49f0973 100644 --- a/cmake/externals/serverless-content/CMakeLists.txt +++ b/cmake/externals/serverless-content/CMakeLists.txt @@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC73.zip - URL_MD5 0c5edfb63cafb042311d3cf25261fbf2 + URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC75.zip + URL_MD5 b4225d058952e17976ac228330ce8d51 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index fd48a792dc..9ce11ca032 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -28,6 +28,78 @@ !include "WinVer.nsh" +;-------------------------------- +;Include Installer Logging +; taken from http://nsis.sourceforge.net/Logging:_Simple_Text_File_Logging_Functions_and_Macros +; TextLog.nsh v1.1 - 2005-12-26 +; Written by Mike Schinkel [http://www.mikeschinkel.com/blog/] + + Var /GLOBAL __TextLog_FileHandle + Var /GLOBAL __TextLog_FileName + Var /GLOBAL __TextLog_State + + !define LogMsg '!insertmacro LogMsgCall' + !macro LogMsgCall _text + Call LogSetOn + Push "${_text}" + Call LogText + Call LogSetOff + !macroend + + + !define LogText '!insertmacro LogTextCall' + !macro LogTextCall _text + Push "${_text}" + Call LogText + !macroend + + Function LogText + Exch $0 ; pABC -> 0ABC + FileWrite $__TextLog_FileHandle "$0$\r$\n" + Pop $0 ; 0ABC -> ABC + FunctionEnd + + !define LogSetFileName '!insertmacro LogSetFileNameCall' + !macro LogSetFileNameCall _filename + Push "${_filename}" + Call LogSetFileName + !macroend + + Function LogSetFileName + Exch $0 ; pABC -> 0ABC + StrCpy $__TextLog_FileName "$0" + StrCmp $__TextLog_State "open" +1 +3 + Call LogSetOff + Call LogSetOn + Pop $0 ; 0ABC -> ABC + FunctionEnd + + !define LogSetOn '!insertmacro LogSetOnCall' + !macro LogSetOnCall + Call LogSetOn + !macroend + + Function LogSetOn + StrCmp $__TextLog_FileName "" +1 AlreadySet + StrCpy $__TextLog_FileName "$INSTDIR\install.log" + AlreadySet: + StrCmp $__TextLog_State "open" +2 + FileOpen $__TextLog_FileHandle "$__TextLog_FileName" a + FileSeek $__TextLog_FileHandle 0 END + StrCpy $__TextLog_State "open" + FunctionEnd + + !define LogSetOff '!insertmacro LogSetOffCall' + !macro LogSetOffCall + Call LogSetOff + !macroend + + Function LogSetOff + StrCmp $__TextLog_State "open" +1 +2 + FileClose $__TextLog_FileHandle + StrCpy $__TextLog_State "" + FunctionEnd + ;-------------------------------- ; Utilities and Functions ;-------------------------------- @@ -375,6 +447,10 @@ Var GAClientID !insertmacro CreateGUID $GAClientID !macroend +!macro LogStep Category Action Label Value + ${LogText} "Step: ${Category} ${Action} ${Label} ${Value}" +!macroend + !macro GoogleAnalytics Category Action Label Value ${If} "@GA_TRACKING_ID@" != "" Push $0 @@ -557,11 +633,13 @@ Var Express !macro MaybeSkipPage ; Check if Express is set, if so, abort the post install options page ${If} $Express == "1" + ${LogText} "Express Install: Skipping Post Install Options Page" Abort ${EndIf} !macroend !macro DownloadSlideshowImages + ${LogText} "Download Slideshow Images" InitPluginsDir Push $0 @@ -583,32 +661,40 @@ Var Express !macroend Function OnUserAbort + !insertmacro LogStep "Installer" "Abort" "User Abort" "" !insertmacro GoogleAnalytics "Installer" "Abort" "User Abort" "" FunctionEnd Function PageWelcomePre + !insertmacro LogStep "Installer" "Welcome" "" "" !insertmacro GoogleAnalytics "Installer" "Welcome" "" "" !insertmacro DownloadSlideshowImages FunctionEnd Function PageLicensePre + !insertmacro LogStep "Installer" "License" "" "" !insertmacro GoogleAnalytics "Installer" "License" "" "" FunctionEnd Function PageDirectoryPre !insertmacro MaybeSkipPage + !insertmacro LogStep "Installer" "Directory" "" "" !insertmacro GoogleAnalytics "Installer" "Directory" "" "" FunctionEnd Function PageStartMenuPre !insertmacro MaybeSkipPage + !insertmacro LogStep "Installer" "StartMenu" "" "" !insertmacro GoogleAnalytics "Installer" "StartMenu" "" "" FunctionEnd Function PageComponentsPre !insertmacro MaybeSkipPage + !insertmacro LogStep "Installer" "Components" "" "" !insertmacro GoogleAnalytics "Installer" "Components" "" "" FunctionEnd Function PageInstallFilesPre + !insertmacro LogStep "Installer" "Install" "" "" !insertmacro GoogleAnalytics "Installer" "Install" "" "" FunctionEnd !macro SetInstallOption Checkbox OptionName Default + ${LogText} "SetInstallOption ${OptionName} ${Default}" ; reads the value for the given install option to the registry ReadRegStr $0 HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}" @@ -625,6 +711,7 @@ FunctionEnd !macroend Function InstallTypesPage + !insertmacro LogStep "Installer" "Install Types" "" "" !insertmacro GoogleAnalytics "Installer" "Install Types" "" "" !insertmacro MUI_HEADER_TEXT "Choose Installation Type" "Express or Custom Install" @@ -688,6 +775,7 @@ FunctionEnd Function StartInstallSlideshow ; create a slideshow file based on what files we have available + ${LogText} "Start Installs Slideshow" ; stash $0 and $1 Push $0 @@ -730,7 +818,11 @@ Function StartInstallSlideshow FunctionEnd Function PostInstallOptionsPage + + ${LogText} "Install Directory: $INSTDIR" + !insertmacro MaybeSkipPage + !insertmacro LogStep "Installer" "Post Install Options" "" "" !insertmacro GoogleAnalytics "Installer" "Post Install Options" "" "" !insertmacro MUI_HEADER_TEXT "Setup Options" "" @@ -876,30 +968,43 @@ Function ReadPostInstallOptions ; check if the user asked for a desktop shortcut to console ${NSD_GetState} $DesktopConsoleCheckbox $DesktopConsoleState - + ${LogText} "Option: Start Desktop Console: $DesktopConsoleState" + ; check if the user asked to have console launched every startup ${NSD_GetState} $ConsoleStartupCheckbox $ConsoleStartupState + ${LogText} "Option: Start Desktop Console On Startup: $ConsoleStartupState" + ${If} @SERVER_COMPONENT_CONDITIONAL@ + ${LogText} "Option: Install Server" + ${EndIf} + ${If} @CLIENT_COMPONENT_CONDITIONAL@ + ${LogText} "Option: Install Client" ; check if the user asked for a desktop shortcut to High Fidelity ${NSD_GetState} $DesktopClientCheckbox $DesktopClientState + ${LogText} "Option: Create Client Desktop Shortcut: $DesktopClientState" ${EndIf} ${If} @PR_BUILD@ == 1 + ${LogText} "Option: PR Build" ; check if we need to copy settings/content from production for this PR build ${NSD_GetState} $CopyFromProductionCheckbox $CopyFromProductionState + ${LogText} "Option: Copy Settings From Production: $CopyFromProductionState" ${EndIf} ; check if we need to launch the console post-install ${NSD_GetState} $LaunchConsoleNowCheckbox $LaunchConsoleNowState + ${LogText} "Option: Launch Console Now: $LaunchConsoleNowState" ${If} @CLIENT_COMPONENT_CONDITIONAL@ ; check if we need to launch the client post-install ${NSD_GetState} $LaunchClientNowCheckbox $LaunchClientNowState + ${LogText} "Option: Launch Client Now: $LaunchClientNowState" ${EndIf} ; check if the user asked for a clean install ${NSD_GetState} $CleanInstallCheckbox $CleanInstallState + ${LogText} "Option: Clean Install: $CleanInstallState" FunctionEnd Function HandlePostInstallOptions @@ -1225,6 +1330,7 @@ Section "-Core installation" ; Handle whichever post install options were set Call HandlePostInstallOptions + !insertmacro LogStep "Installer" "Done" "" "" !insertmacro GoogleAnalytics "Installer" "Done" "" "" SectionEnd @@ -1232,7 +1338,6 @@ SectionEnd !macro PromptForRunningApplication applicationName displayName action prompter !define UniqueID ${__LINE__} - Prompt_${UniqueID}: ${nsProcess::FindProcess} ${applicationName} $R0 @@ -1478,6 +1583,11 @@ InstallDirRegKey HKLM "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_RE Function .onInit + Delete "$TEMP\hifi_install.log" + ${LogSetFileName} "$TEMP\hifi_install.log" + ${LogSetOn} + ${LogText} "In .onInit" + !ifdef INNER ; If INNER is defined, then we aren't supposed to do anything except write out ; the installer. This is better than processing a command line option as it means @@ -1495,6 +1605,7 @@ Function .onInit !insertmacro GoogleAnalytics "Installer" "Start" "$CampaignName" "" ; make sure none of the installed applications are still running + ${LogText} "Checking For Running Applications" !insertmacro CheckForRunningApplications "installed" "Installer" ${nsProcess::Unload} diff --git a/interface/resources/QtWebEngine/UIDelegates/Menu.qml b/interface/resources/QtWebEngine/UIDelegates/Menu.qml index 46c00e758e..adfd29df9e 100644 --- a/interface/resources/QtWebEngine/UIDelegates/Menu.qml +++ b/interface/resources/QtWebEngine/UIDelegates/Menu.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 -import "../../qml/controls-uit" -import "../../qml/styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: menu diff --git a/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml b/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml index 6014b6834b..b4d3ca4bb2 100644 --- a/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml +++ b/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 -import "../../qml/controls-uit" -import "../../qml/styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: root diff --git a/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml b/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml index e4ab3037ef..089c745571 100644 --- a/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml +++ b/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 -import "../../qml/controls-uit" -import "../../qml/styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../../qml/dialogs" QtObject { diff --git a/interface/resources/avatar/network-animation.json b/interface/resources/avatar/network-animation.json new file mode 100644 index 0000000000..0ba4ea465c --- /dev/null +++ b/interface/resources/avatar/network-animation.json @@ -0,0 +1,140 @@ +{ + "version": "1.1", + "root": { + "id": "userAnimStateMachine", + "type": "stateMachine", + "data": { + "currentState": "idleAnim", + "states": [ + { + "id": "idleAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "postTransitAnim", "state": "postTransitAnim" }, + { "var": "preTransitAnim", "state": "preTransitAnim" } + ] + }, + { + "id": "preTransitAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "idleAnim", "state": "idleAnim" }, + { "var": "transitAnim", "state": "transitAnim" } + ] + }, + { + "id": "transitAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "preTransitAnim", "state": "preTransitAnim" }, + { "var": "postTransitAnim", "state": "postTransitAnim" } + ] + }, + { + "id": "postTransitAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "transitAnim", "state": "transitAnim" }, + { "var": "idleAnim", "state": "idleAnim" } + ] + }, + { + "id": "userAnimA", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "idleAnim", "state": "idleAnim" }, + { "var": "userAnimB", "state": "userAnimB" } + ] + }, + { + "id": "userAnimB", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "idleAnim", "state": "idleAnim" }, + { "var": "userAnimA", "state": "userAnimA" } + ] + } + ] + }, + "children": [ + { + "id": "idleAnim", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "preTransitAnim", + "type": "clip", + "data": { + "url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx", + "startFrame": 0.0, + "endFrame": 10.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "transitAnim", + "type": "clip", + "data": { + "url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx", + "startFrame": 11.0, + "endFrame": 11.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "postTransitAnim", + "type": "clip", + "data": { + "url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx", + "startFrame": 22.0, + "endFrame": 49.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "userAnimA", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "userAnimB", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } +} diff --git a/interface/resources/config/keyboard.json b/interface/resources/config/keyboard.json new file mode 100644 index 0000000000..186a9c1084 --- /dev/null +++ b/interface/resources/config/keyboard.json @@ -0,0 +1,2476 @@ +{ + "anchor": { + "dimensions": { + "x": 0.023600000888109207, + "y": 0.022600000724196434, + "z": 0.1274999976158142 + }, + "position": { + "x": 0.006292800903320312, + "y": 0.004300000742077827, + "z": 0.005427663803100586 + }, + "rotation": { + "w": 1.000, + "x": 0.000, + "y": 0.000, + "z": 0.000 + } + }, + "textDisplay": { + "dimensions": { + "x": 0.15, + "y": 0.045, + "z": 0.1 + }, + "localPosition": { + "x": -0.3032040786743164, + "y": 0.059300000742077827, + "z": 0.06454843521118164 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.906, + "z": 0.423 + }, + "leftMargin": 0.0, + "rightMargin": 0.0, + "topMargin": 0.0, + "bottomMargin": 0.0, + "lineHeight": 0.05 + }, + "useResourcesPath": true, + "layers": [ + [ + { + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.5332040786743164, + "y": 0.019300000742077827, + "z": 0.03745675086975098 + }, + "key": "p", + "texture": { + "file9": "meshes/keyboard/key_p.png", + "file10": "meshes/keyboard/key_p.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.42067813873291016, + "y": 0.019300000742077827, + "z": 0.03744244575500488 + }, + "key": "i", + "texture": { + "file9": "meshes/keyboard/key_i.png", + "file10": "meshes/keyboard/key_i.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "o", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.47769832611083984, + "y": 0.019300000742077827, + "z": 0.03745675086975098 + }, + "texture": { + "file9": "meshes/keyboard/key_o.png", + "file10": "meshes/keyboard/key_o.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.49904823303222656, + "y": 0.019300000742077827, + "z": -0.01745915412902832 + }, + "key": "l", + "texture": { + "file9": "meshes/keyboard/key_l.png", + "file10": "meshes/keyboard/key_l.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.4439973831176758, + "y": 0.019300000742077827, + "z": -0.01745915412902832 + }, + "key": "k", + "texture": { + "file9": "meshes/keyboard/key_k.png", + "file10": "meshes/keyboard/key_k.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "y", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_y.png", + "file10": "meshes/keyboard/key_y.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.30902957916259766, + "y": 0.019300000742077827, + "z": 0.0374448299407959 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "r", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_r.png", + "file10": "meshes/keyboard/key_r.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.19474029541015625, + "y": 0.019300000742077827, + "z": 0.03745102882385254 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "d", + "texture": { + "file9": "meshes/keyboard/key_d.png", + "file10": "meshes/keyboard/key_d.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.16272640228271484, + "y": 0.019300000742077827, + "z": -0.01747274398803711 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "t", + "texture": { + "file9": "meshes/keyboard/key_t.png", + "file10": "meshes/keyboard/key_t.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.2517843246459961, + "y": 0.019300000742077827, + "z": 0.03744622692465782 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "f", + "texture": { + "file9": "meshes/keyboard/key_f.png", + "file10": "meshes/keyboard/key_f.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.2200756072998047, + "y": 0.019300000742077827, + "z": -0.01746511459350586 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "g", + "texture": { + "file9": "meshes/keyboard/key_g.png", + "file10": "meshes/keyboard/key_g.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.27622222900390625, + "y": 0.019300000742077827, + "z": -0.017457084730267525 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "w", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_w.png", + "file10": "meshes/keyboard/key_w.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.08203601837158203, + "y": 0.019300000742077827, + "z": 0.03743100166320801 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "q", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_q.png", + "file10": "meshes/keyboard/key_q.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.026292800903320312, + "y": 0.019300000742077827, + "z": 0.037427663803100586 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "a", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_a.png", + "file10": "meshes/keyboard/key_a.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.050909996032714844, + "y": 0.019300000742077827, + "z": -0.017487764358520508 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "e", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_e.png", + "file10": "meshes/keyboard/key_e.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.13773441314697266, + "y": 0.019300000742077827, + "z": 0.03745269775390625 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "s", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_s.png", + "file10": "meshes/keyboard/key_s.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.10659980773925781, + "y": 0.019300000742077827, + "z": -0.017481565475463867 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "u", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_u.png", + "file10": "meshes/keyboard/key_u.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.36423587799072266, + "y": 0.019300000742077827, + "z": 0.03743577003479004 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "h", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_h.png", + "file10": "meshes/keyboard/key_h.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.3321561813354492, + "y": 0.019300000742077827, + "z": -0.017461776733398438 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "j", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_j.png", + "file10": "meshes/keyboard/key_j.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.38753604888916016, + "y": 0.019300000742077827, + "z": -0.01746058464050293 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "c", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_c.png", + "file10": "meshes/keyboard/key_c.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.22023773193359375, + "y": 0.019300000742077827, + "z": -0.07282757759094238 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "v", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_v.png", + "file10": "meshes/keyboard/key_v.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.2767200469970703, + "y": 0.019300000742077827, + "z": -0.07291850447654724 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "b", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_b.png", + "file10": "meshes/keyboard/key_b.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.33254528045654297, + "y": 0.019300000742077827, + "z": -0.07283258438110352 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": " ", + "name": "space", + "dimensions": { + "x": 0.14597539603710175, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.27660465240478516, + "y": 0.019300000742077827, + "z": -0.12705934047698975 + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/white.png", + "file10": "meshes/keyboard/white.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "z", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_z.png", + "file10": "meshes/keyboard/key_z.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.10669422149658203, + "y": 0.019300000742077827, + "z": -0.07285571098327637 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "n", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_n.png", + "file10": "meshes/keyboard/key_n.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020292000845074654, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.38794422149658203, + "y": 0.019300000742077827, + "z": -0.0728309154510498 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "m", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_m.png", + "file10": "meshes/keyboard/key_m.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.01846799999475479, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.44464683532714844, + "y": 0.019300000742077827, + "z": -0.07282185554504395 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "x", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_x.png", + "file10": "meshes/keyboard/key_x.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.1630725860595703, + "y": 0.019300000742077827, + "z": -0.07284045219421387 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "DEL", + "type": "backspace", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.53203323516845703, + "y": 0.019300000742077827, + "z": -0.07286686894893646 + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_backspace.png", + "file10": "meshes/keyboard/key_backspace.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "Caps", + "type": "caps", + "switchToLayer": 1, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.02603323516845703, + "y": 0.019300000742077827, + "z": -0.07285571098327637 + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_cap.png", + "file10": "meshes/keyboard/key_cap.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "Close", + "type": "close", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.59333323516845703, + "y": 0.019300000742077827, + "z": 0.037454843521118164 + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_exit.png", + "file10": "meshes/keyboard/key_exit.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "Enter", + "type": "enter", + "dimensions": { + "x": 0.08787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.5103323516845703, + "y": 0.019300000742077827, + "z": -0.127054843521118164 + }, + "modelURL": "meshes/keyboard/SM_enter.fbx", + "texture": { + "file10": "meshes/keyboard/key_enter.png", + "file11": "meshes/keyboard/key_enter.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "numbers", + "type": "layer", + "switchToLayer": 2, + "dimensions": { + "x": 0.07787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.026323516845703, + "y": 0.019300000742077827, + "z": -0.127054843521118164 + }, + "modelURL": "meshes/keyboard/SM_enter.fbx", + "texture": { + "file10": "meshes/keyboard/key_123.png", + "file11": "meshes/keyboard/key_123.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + } + ], + [ + { + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.5332040786743164, + "y": 0.019300000742077827, + "z": 0.037454843521118164 + }, + "key": "p", + "texture": { + "file9": "meshes/keyboard/keyCap_p.png", + "file10": "meshes/keyboard/keyCap_p.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.42067813873291016, + "y": 0.019300000742077827, + "z": 0.03744244575500488 + }, + "key": "i", + "texture": { + "file9": "meshes/keyboard/keyCap_i.png", + "file10": "meshes/keyboard/keyCap_i.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "o", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.47769832611083984, + "y": 0.019300000742077827, + "z": 0.03745675086975098 + }, + "texture": { + "file9": "meshes/keyboard/keyCap_o.png", + "file10": "meshes/keyboard/keyCap_o.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.49904823303222656, + "y": 0.019300000742077827, + "z": -0.01745915412902832 + }, + "key": "l", + "texture": { + "file9": "meshes/keyboard/keyCap_l.png", + "file10": "meshes/keyboard/keyCap_l.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.4439973831176758, + "y": 0.019300000742077827, + "z": -0.01745915412902832 + }, + "key": "k", + "texture": { + "file9": "meshes/keyboard/keyCap_k.png", + "file10": "meshes/keyboard/keyCap_k.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "y", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_y.png", + "file10": "meshes/keyboard/keyCap_y.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.30902957916259766, + "y": 0.019300000742077827, + "z": 0.0374448299407959 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "r", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_r.png", + "file10": "meshes/keyboard/keyCap_r.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.19474029541015625, + "y": 0.019300000742077827, + "z": 0.03745102882385254 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "d", + "texture": { + "file9": "meshes/keyboard/keyCap_d.png", + "file10": "meshes/keyboard/keyCap_d.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.16272640228271484, + "y": 0.019300000742077827, + "z": -0.01747274398803711 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "t", + "texture": { + "file9": "meshes/keyboard/keyCap_t.png", + "file10": "meshes/keyboard/keyCap_t.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.2517843246459961, + "y": 0.019300000742077827, + "z": 0.03744622692465782 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "f", + "texture": { + "file9": "meshes/keyboard/keyCap_F.png", + "file10": "meshes/keyboard/keyCap_F.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.2200756072998047, + "y": 0.019300000742077827, + "z": -0.01746511459350586 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "g", + "texture": { + "file9": "meshes/keyboard/keyCap_g.png", + "file10": "meshes/keyboard/keyCap_g.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.27622222900390625, + "y": 0.019300000742077827, + "z": -0.017457084730267525 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "w", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_w.png", + "file10": "meshes/keyboard/keyCap_w.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.08203601837158203, + "y": 0.019300000742077827, + "z": 0.03743100166320801 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "q", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_q.png", + "file10": "meshes/keyboard/keyCap_q.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.026292800903320312, + "y": 0.019300000742077827, + "z": 0.037427663803100586 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "a", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_a.png", + "file10": "meshes/keyboard/keyCap_a.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.050909996032714844, + "y": 0.019300000742077827, + "z": -0.017487764358520508 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "e", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_e.png", + "file10": "meshes/keyboard/keyCap_e.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.13773441314697266, + "y": 0.019300000742077827, + "z": 0.03745269775390625 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "s", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_s.png", + "file10": "meshes/keyboard/keyCap_s.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.10659980773925781, + "y": 0.019300000742077827, + "z": -0.017481565475463867 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "u", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_u.png", + "file10": "meshes/keyboard/keyCap_u.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.36423587799072266, + "y": 0.019300000742077827, + "z": 0.03743577003479004 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "h", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_h.png", + "file10": "meshes/keyboard/keyCap_h.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.3321561813354492, + "y": 0.019300000742077827, + "z": -0.017461776733398438 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "j", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_j.png", + "file10": "meshes/keyboard/keyCap_j.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.38753604888916016, + "y": 0.019300000742077827, + "z": -0.01746058464050293 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "c", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_c.png", + "file10": "meshes/keyboard/keyCap_c.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.22023773193359375, + "y": 0.019300000742077827, + "z": -0.07282757759094238 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "v", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_v.png", + "file10": "meshes/keyboard/keyCap_v.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.2767200469970703, + "y": 0.019300000742077827, + "z": -0.07291850447654724 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "b", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_b.png", + "file10": "meshes/keyboard/keyCap_b.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.33254528045654297, + "y": 0.019300000742077827, + "z": -0.07283258438110352 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": " ", + "name": "space", + "dimensions": { + "x": 0.14597539603710175, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.27660465240478516, + "y": 0.019300000742077827, + "z": -0.12705934047698975 + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/white.png", + "file10": "meshes/keyboard/white.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "z", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_z.png", + "file10": "meshes/keyboard/keyCap_z.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.10669422149658203, + "y": 0.019300000742077827, + "z": -0.07285571098327637 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "n", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_n.png", + "file10": "meshes/keyboard/keyCap_n.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020292000845074654, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.38794422149658203, + "y": 0.019300000742077827, + "z": -0.0728309154510498 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "m", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_m.png", + "file10": "meshes/keyboard/keyCap_m.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.01846799999475479, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.44464683532714844, + "y": 0.019300000742077827, + "z": -0.07282185554504395 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "x", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/keyCap_x.png", + "file10": "meshes/keyboard/keyCap_x.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.1630725860595703, + "y": 0.019300000742077827, + "z": -0.07284045219421387 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "DEL", + "type": "backspace", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.53203323516845703, + "y": 0.019300000742077827, + "z": -0.07286686894893646 + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_backspace.png", + "file10": "meshes/keyboard/key_backspace.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "Caps", + "type": "caps", + "switchToLayer": 0, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.02603323516845703, + "y": 0.019300000742077827, + "z": -0.07285571098327637 + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_cap.png", + "file10": "meshes/keyboard/key_cap.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "Close", + "type": "close", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.59333323516845703, + "y": 0.019300000742077827, + "z": 0.037454843521118164 + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_exit.png", + "file10": "meshes/keyboard/key_exit.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "Enter", + "type": "enter", + "dimensions": { + "x": 0.08787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.5103323516845703, + "y": 0.019300000742077827, + "z": -0.127054843521118164 + }, + "modelURL": "meshes/keyboard/SM_enter.fbx", + "texture": { + "file10": "meshes/keyboard/key_enter.png", + "file11": "meshes/keyboard/key_enter.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "numbers", + "type": "layer", + "switchToLayer": 2, + "dimensions": { + "x": 0.07787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.026323516845703, + "y": 0.019300000742077827, + "z": -0.127054843521118164 + }, + "modelURL": "meshes/keyboard/SM_enter.fbx", + "texture": { + "file10": "meshes/keyboard/key_123.png", + "file11": "meshes/keyboard/key_123.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + } + ], + [ + { + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.5332040786743164, + "y": 0.019300000742077827, + "z": 0.037454843521118164 + }, + "key": "0", + "texture": { + "file9": "meshes/keyboard/key_0.png", + "file10": "meshes/keyboard/key_0.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.42067813873291016, + "y": 0.019300000742077827, + "z": 0.03744244575500488 + }, + "key": "8", + "texture": { + "file9": "meshes/keyboard/key_8.png", + "file10": "meshes/keyboard/key_8.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "9", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.47769832611083984, + "y": 0.019300000742077827, + "z": 0.03745675086975098 + }, + "texture": { + "file9": "meshes/keyboard/key_9.png", + "file10": "meshes/keyboard/key_9.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.47764823303222656, + "y": 0.019300000742077827, + "z": -0.01745915412902832 + }, + "key": "(", + "texture": { + "file9": "meshes/keyboard/key_open_paren.png", + "file10": "meshes/keyboard/key_open_paren.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.53364823303222656, + "y": 0.019300000742077827, + "z": -0.01745915412902832 + }, + "key": ")", + "texture": { + "file9": "meshes/keyboard/key_close_paren.png", + "file10": "meshes/keyboard/key_close_paren.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.59634823303222656, + "y": 0.019300000742077827, + "z": -0.01745915412902832 + }, + "key": "/", + "texture": { + "file9": "meshes/keyboard/key_slash.png", + "file10": "meshes/keyboard/key_slash.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.4206973831176758, + "y": 0.019300000742077827, + "z": -0.01745915412902832 + }, + "key": "+", + "texture": { + "file9": "meshes/keyboard/key_plus.png", + "file10": "meshes/keyboard/key_plus.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "6", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_6.png", + "file10": "meshes/keyboard/key_6.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.30902957916259766, + "y": 0.019300000742077827, + "z": 0.0374448299407959 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "4", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_4.png", + "file10": "meshes/keyboard/key_4.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.19474029541015625, + "y": 0.019300000742077827, + "z": 0.03745102882385254 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "$", + "texture": { + "file9": "meshes/keyboard/key_dollar.png", + "file10": "meshes/keyboard/key_dollar.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.13772640228271484, + "y": 0.019300000742077827, + "z": -0.01747274398803711 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "5", + "texture": { + "file9": "meshes/keyboard/key_5.png", + "file10": "meshes/keyboard/key_5.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.2517843246459961, + "y": 0.019300000742077827, + "z": 0.03744622692465782 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "%", + "texture": { + "file9": "meshes/keyboard/key_percentage.png", + "file10": "meshes/keyboard/key_percentage.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.1947756072998047, + "y": 0.019300000742077827, + "z": -0.01746511459350586 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "_", + "texture": { + "file9": "meshes/keyboard/key_under.png", + "file10": "meshes/keyboard/key_under.png" + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.25172222900390625, + "y": 0.019300000742077827, + "z": -0.017457084730267525 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "2", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_2.png", + "file10": "meshes/keyboard/key_2.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.08203601837158203, + "y": 0.019300000742077827, + "z": 0.03743100166320801 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "1", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_1.png", + "file10": "meshes/keyboard/key_1.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.026292800903320312, + "y": 0.019300000742077827, + "z": 0.037427663803100586 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "@", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_at.png", + "file10": "meshes/keyboard/key_at.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.026209996032714844, + "y": 0.019300000742077827, + "z": -0.017487764358520508 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "3", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_3.png", + "file10": "meshes/keyboard/key_3.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.13773441314697266, + "y": 0.019300000742077827, + "z": 0.03745269775390625 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "#", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_hashtag.png", + "file10": "meshes/keyboard/key_hashtag.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.08209980773925781, + "y": 0.019300000742077827, + "z": -0.017481565475463867 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "7", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_7.png", + "file10": "meshes/keyboard/key_7.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.36423587799072266, + "y": 0.019300000742077827, + "z": 0.03743577003479004 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "&", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_ampersand.png", + "file10": "meshes/keyboard/key_ampersand.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.3090561813354492, + "y": 0.019300000742077827, + "z": -0.017461776733398438 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "-", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_min.png", + "file10": "meshes/keyboard/key_min.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.36433604888916016, + "y": 0.019300000742077827, + "z": -0.01746058464050293 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "'", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_squote.png", + "file10": "meshes/keyboard/key_squote.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.22023773193359375, + "y": 0.019300000742077827, + "z": -0.07282757759094238 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": ":", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_colon.png", + "file10": "meshes/keyboard/key_colon.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.2767200469970703, + "y": 0.019300000742077827, + "z": -0.07291850447654724 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": ";", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_semi.png", + "file10": "meshes/keyboard/key_semi.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.33254528045654297, + "y": 0.019300000742077827, + "z": -0.07283258438110352 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": " ", + "name": "space", + "dimensions": { + "x": 0.14597539603710175, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.27660465240478516, + "y": 0.019300000742077827, + "z": -0.12705934047698975 + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/white.png", + "file10": "meshes/keyboard/white.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "*", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_ast.png", + "file10": "meshes/keyboard/key_ast.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.10669422149658203, + "y": 0.019300000742077827, + "z": -0.07285571098327637 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "!", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_exclam.png", + "file10": "meshes/keyboard/key_exclam.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020292000845074654, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.38794422149658203, + "y": 0.019300000742077827, + "z": -0.0728309154510498 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "?", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_question.png", + "file10": "meshes/keyboard/key_question.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.01846799999475479, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.44464683532714844, + "y": 0.019300000742077827, + "z": -0.07282185554504395 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "\"", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_dquote.png", + "file10": "meshes/keyboard/key_dquote.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.1630725860595703, + "y": 0.019300000742077827, + "z": -0.07284045219421387 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "DEL", + "type": "backspace", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.53203323516845703, + "y": 0.019300000742077827, + "z": -0.07286686894893646 + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_backspace.png", + "file10": "meshes/keyboard/key_backspace.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "Caps", + "type": "caps", + "switchToLayer": 1, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.02603323516845703, + "y": 0.019300000742077827, + "z": -0.07285571098327637 + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_cap.png", + "file10": "meshes/keyboard/key_cap.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "Close", + "type": "close", + "dimensions": { + "x": 0.04787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.59333323516845703, + "y": 0.019300000742077827, + "z": 0.037454843521118164 + }, + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_exit.png", + "file10": "meshes/keyboard/key_exit.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "Enter", + "type": "enter", + "dimensions": { + "x": 0.08787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.5103323516845703, + "y": 0.019300000742077827, + "z": -0.127054843521118164 + }, + "modelURL": "meshes/keyboard/SM_enter.fbx", + "texture": { + "file10": "meshes/keyboard/key_enter.png", + "file11": "meshes/keyboard/key_enter.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": "numbers", + "type": "layer", + "switchToLayer": 0, + "dimensions": { + "x": 0.07787999764084816, + "z": 0.020519999787211418, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.026323516845703, + "y": 0.019300000742077827, + "z": -0.127054843521118164 + }, + "modelURL": "meshes/keyboard/SM_enter.fbx", + "texture": { + "file10": "meshes/keyboard/key_abc.png", + "file11": "meshes/keyboard/key_abc.png" + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": ".", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_period.png", + "file10": "meshes/keyboard/key_period.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.38794422149658203, + "y": 0.019300000742077827, + "z": -0.12705934047698975 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + }, + { + "key": ",", + "modelURL": "meshes/keyboard/SM_key.fbx", + "texture": { + "file9": "meshes/keyboard/key_comma.png", + "file10": "meshes/keyboard/key_comma.png" + }, + "dimensions": { + "x": 0.04787999764084816, + "z": 0.02051999792456627, + "y": 0.04787999764084816 + }, + "position": { + "x": -0.1630725860595703, + "y": 0.019300000742077827, + "z": -0.12705934047698975 + }, + "localOrientation": { + "w": 0.000, + "x": 0.000, + "y": 0.707, + "z": 0.707 + } + } + ] + ] +} diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 8a7744efb3..24b1587691 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -51,32 +51,34 @@ { "from": "Vive.RSCenter", "to": "Standard.RightPrimaryThumb" }, { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, - { "from": "Vive.LeftHand", "to": "Standard.LeftHand"}, - { "from": "Vive.RightHand", "to": "Standard.RightHand"}, + { "from": "Vive.LeftHand", "to": "Standard.LeftHand" }, + { "from": "Vive.RightHand", "to": "Standard.RightHand" }, + { "from": "Vive.Head", "to" : "Standard.Head" }, { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", - "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] }, - { "from": "Vive.RightFoot", "to" : "Standard.RightFoot", - "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] }, - { "from": "Vive.Hips", "to" : "Standard.Hips", - "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] }, - { "from": "Vive.Spine2", "to" : "Standard.Spine2", - "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] + }, + { + "from": "Vive.RightArm", "to" : "Standard.RightArm", + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] + }, + { + "from": "Vive.LeftArm", "to" : "Standard.LeftArm", + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] }, - - { "from": "Vive.Head", "to" : "Standard.Head"}, - { "from": "Vive.RightArm", "to" : "Standard.RightArm" }, - { "from": "Vive.LeftArm", "to" : "Standard.LeftArm" }, { "from": "Vive.TrackedObject00", "to" : "Standard.TrackedObject00" }, { "from": "Vive.TrackedObject01", "to" : "Standard.TrackedObject01" }, diff --git a/interface/resources/icons/tablet-icons/inventory-a-msg.svg b/interface/resources/icons/tablet-icons/inventory-a-msg.svg new file mode 100644 index 0000000000..794bd1e414 --- /dev/null +++ b/interface/resources/icons/tablet-icons/inventory-a-msg.svg @@ -0,0 +1,4 @@ +<svg width="22" height="26" fill="none" version="1.1" viewBox="0 0 22 26" xmlns="http://www.w3.org/2000/svg"> + <path d="M1 7L11 1L21 7M1 7L11 13M1 7V19L11 25M11 13L21 7M11 13V25M21 7V19L11 25" stroke="#000" stroke-linejoin="round" stroke-width="2"/> + <circle class="st1" cx="19.407" cy="2.5881" r="2.5846" fill="#ef3b4e" stroke-width=".24043"/> +</svg> diff --git a/interface/resources/icons/tablet-icons/inventory-a.svg b/interface/resources/icons/tablet-icons/inventory-a.svg new file mode 100644 index 0000000000..8b6f34eaa3 --- /dev/null +++ b/interface/resources/icons/tablet-icons/inventory-a.svg @@ -0,0 +1,3 @@ +<svg width="22" height="26" viewBox="0 0 22 26" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M1 7L11 1L21 7M1 7L11 13M1 7V19L11 25M11 13L21 7M11 13V25M21 7V19L11 25" stroke="black" stroke-width="2" stroke-linejoin="round"/> +</svg> diff --git a/interface/resources/icons/tablet-icons/inventory-i-msg.svg b/interface/resources/icons/tablet-icons/inventory-i-msg.svg new file mode 100644 index 0000000000..35d4fb54ae --- /dev/null +++ b/interface/resources/icons/tablet-icons/inventory-i-msg.svg @@ -0,0 +1,4 @@ +<svg width="22" height="26" fill="none" version="1.1" viewBox="0 0 22 26" xmlns="http://www.w3.org/2000/svg"> + <path d="M1 7L11 1L21 7M1 7L11 13M1 7V19L11 25M11 13L21 7M11 13V25M21 7V19L11 25" stroke="#fff" stroke-linejoin="round" stroke-width="2"/> + <circle class="st1" cx="19.41" cy="2.5828" r="2.5846" fill="#ef3b4e" stroke-width=".24043"/> +</svg> diff --git a/interface/resources/icons/tablet-icons/inventory-i.svg b/interface/resources/icons/tablet-icons/inventory-i.svg new file mode 100644 index 0000000000..071fabce88 --- /dev/null +++ b/interface/resources/icons/tablet-icons/inventory-i.svg @@ -0,0 +1,3 @@ +<svg width="22" height="26" viewBox="0 0 22 26" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M1 7L11 1L21 7M1 7L11 13M1 7V19L11 25M11 13L21 7M11 13V25M21 7V19L11 25" stroke="white" stroke-width="2" stroke-linejoin="round"/> +</svg> diff --git a/interface/resources/icons/tablet-icons/wallet-a-msg.svg b/interface/resources/icons/tablet-icons/wallet-a-msg.svg deleted file mode 100644 index d51c3e99a2..0000000000 --- a/interface/resources/icons/tablet-icons/wallet-a-msg.svg +++ /dev/null @@ -1,6 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve"> -<style type="text/css"> - .st1{fill:#EF3B4E;} -</style> -<circle class="st1" cx="84.6" cy="11.5" r="10.75"/> -<g><path d="M2.4,70.5c0,6.1,4.9,11,11,11H76c6.1,0,11-4.9,11-11V59.6c3.7-0.7,6.6-3.9,6.6-7.9v-7.5c0-3.9-2.8-7.2-6.6-7.9V25.5 c0-6.1-4.9-11-11-11H13.4c-6.1,0-11,4.9-11,11V70.5z M87.6,51.8c0,1.1-0.9,2-2,2H72.2c-2.8,0-5-2.2-5-5v-1.5c0-2.8,2.2-5,5-5h13.3 c1.1,0,2,0.9,2,2V51.8z M8.4,25.5c0-2.8,2.2-5,5-5H76c2.8,0,5,2.2,5,5v10.7h-8.7c-6.1,0-11,4.9-11,11v1.5c0,6.1,4.9,11,11,11H81 v10.7c0,2.8-2.2,5-5,5H13.4c-2.8,0-5-2.2-5-5V25.5z"></path></g></svg> \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/wallet-a.svg b/interface/resources/icons/tablet-icons/wallet-a.svg deleted file mode 100644 index 50ea64848f..0000000000 --- a/interface/resources/icons/tablet-icons/wallet-a.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve"><g><path d="M2.4,70.5c0,6.1,4.9,11,11,11H76c6.1,0,11-4.9,11-11V59.6c3.7-0.7,6.6-3.9,6.6-7.9v-7.5c0-3.9-2.8-7.2-6.6-7.9V25.5 c0-6.1-4.9-11-11-11H13.4c-6.1,0-11,4.9-11,11V70.5z M87.6,51.8c0,1.1-0.9,2-2,2H72.2c-2.8,0-5-2.2-5-5v-1.5c0-2.8,2.2-5,5-5h13.3 c1.1,0,2,0.9,2,2V51.8z M8.4,25.5c0-2.8,2.2-5,5-5H76c2.8,0,5,2.2,5,5v10.7h-8.7c-6.1,0-11,4.9-11,11v1.5c0,6.1,4.9,11,11,11H81 v10.7c0,2.8-2.2,5-5,5H13.4c-2.8,0-5-2.2-5-5V25.5z"></path></g></svg> \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/wallet-i-msg.svg b/interface/resources/icons/tablet-icons/wallet-i-msg.svg deleted file mode 100644 index 676f97a966..0000000000 --- a/interface/resources/icons/tablet-icons/wallet-i-msg.svg +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve"> -<style type="text/css"> - .st0{fill:#FFFFFF;} - .st1{fill:#EF3B4E;} -</style> -<circle class="st1" cx="84.6" cy="11.5" r="10.75"/> -<g> - <path class="st0" d="M2.4,70.5c0,6.1,4.9,11,11,11H76c6.1,0,11-4.9,11-11V59.6c3.7-0.7,6.6-3.9,6.6-7.9v-7.5c0-3.9-2.8-7.2-6.6-7.9 - V25.5c0-6.1-4.9-11-11-11H13.4c-6.1,0-11,4.9-11,11C2.4,25.5,2.4,70.5,2.4,70.5z M87.6,51.8c0,1.1-0.9,2-2,2H72.2c-2.8,0-5-2.2-5-5 - v-1.5c0-2.8,2.2-5,5-5h13.3c1.1,0,2,0.9,2,2L87.6,51.8L87.6,51.8z M8.4,25.5c0-2.8,2.2-5,5-5H76c2.8,0,5,2.2,5,5v10.7h-8.7 - c-6.1,0-11,4.9-11,11v1.5c0,6.1,4.9,11,11,11H81v10.7c0,2.8-2.2,5-5,5H13.4c-2.8,0-5-2.2-5-5V25.5z"/> -</g> -</svg> diff --git a/interface/resources/icons/tablet-icons/wallet-i.svg b/interface/resources/icons/tablet-icons/wallet-i.svg deleted file mode 100644 index 4e27e41b44..0000000000 --- a/interface/resources/icons/tablet-icons/wallet-i.svg +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve"> -<style type="text/css"> - .st0{fill:#FFFFFF;} -</style> -<g> - <path class="st0" d="M2.4,70.5c0,6.1,4.9,11,11,11H76c6.1,0,11-4.9,11-11V59.6c3.7-0.7,6.6-3.9,6.6-7.9v-7.5c0-3.9-2.8-7.2-6.6-7.9 - V25.5c0-6.1-4.9-11-11-11H13.4c-6.1,0-11,4.9-11,11C2.4,25.5,2.4,70.5,2.4,70.5z M87.6,51.8c0,1.1-0.9,2-2,2H72.2c-2.8,0-5-2.2-5-5 - v-1.5c0-2.8,2.2-5,5-5h13.3c1.1,0,2,0.9,2,2L87.6,51.8L87.6,51.8z M8.4,25.5c0-2.8,2.2-5,5-5H76c2.8,0,5,2.2,5,5v10.7h-8.7 - c-6.1,0-11,4.9-11,11v1.5c0,6.1,4.9,11,11,11H81v10.7c0,2.8-2.2,5-5,5H13.4c-2.8,0-5-2.2-5-5V25.5z"/> -</g> -</svg> diff --git a/interface/resources/meshes/drumstick.fbx b/interface/resources/meshes/drumstick.fbx new file mode 100644 index 0000000000..0243d9fd1b Binary files /dev/null and b/interface/resources/meshes/drumstick.fbx differ diff --git a/interface/resources/meshes/keyboard/SM_enter.fbx b/interface/resources/meshes/keyboard/SM_enter.fbx new file mode 100644 index 0000000000..119e3fc535 Binary files /dev/null and b/interface/resources/meshes/keyboard/SM_enter.fbx differ diff --git a/interface/resources/meshes/keyboard/SM_key.fbx b/interface/resources/meshes/keyboard/SM_key.fbx new file mode 100644 index 0000000000..02684a42d8 Binary files /dev/null and b/interface/resources/meshes/keyboard/SM_key.fbx differ diff --git a/interface/resources/meshes/keyboard/SM_space.fbx b/interface/resources/meshes/keyboard/SM_space.fbx new file mode 100644 index 0000000000..77632eb795 Binary files /dev/null and b/interface/resources/meshes/keyboard/SM_space.fbx differ diff --git a/interface/resources/meshes/keyboard/keyCap_F.png b/interface/resources/meshes/keyboard/keyCap_F.png new file mode 100644 index 0000000000..fba21a7d77 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_F.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_a.png b/interface/resources/meshes/keyboard/keyCap_a.png new file mode 100644 index 0000000000..f409254292 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_a.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_b.png b/interface/resources/meshes/keyboard/keyCap_b.png new file mode 100644 index 0000000000..5ab85290d1 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_b.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_c.png b/interface/resources/meshes/keyboard/keyCap_c.png new file mode 100644 index 0000000000..9a020163ab Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_c.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_d.png b/interface/resources/meshes/keyboard/keyCap_d.png new file mode 100644 index 0000000000..4eccee2dd5 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_d.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_e.png b/interface/resources/meshes/keyboard/keyCap_e.png new file mode 100644 index 0000000000..09bd5bc289 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_e.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_g.png b/interface/resources/meshes/keyboard/keyCap_g.png new file mode 100644 index 0000000000..9c52605428 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_g.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_h.png b/interface/resources/meshes/keyboard/keyCap_h.png new file mode 100644 index 0000000000..f29323da11 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_h.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_i.png b/interface/resources/meshes/keyboard/keyCap_i.png new file mode 100644 index 0000000000..721e6b84b1 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_i.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_j.png b/interface/resources/meshes/keyboard/keyCap_j.png new file mode 100644 index 0000000000..7186df71c1 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_j.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_k.png b/interface/resources/meshes/keyboard/keyCap_k.png new file mode 100644 index 0000000000..69667f42e0 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_k.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_l.png b/interface/resources/meshes/keyboard/keyCap_l.png new file mode 100644 index 0000000000..d59c24f7e2 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_l.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_m.png b/interface/resources/meshes/keyboard/keyCap_m.png new file mode 100644 index 0000000000..a6bc6729a8 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_m.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_n.png b/interface/resources/meshes/keyboard/keyCap_n.png new file mode 100644 index 0000000000..950fb88e41 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_n.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_o.png b/interface/resources/meshes/keyboard/keyCap_o.png new file mode 100644 index 0000000000..2c1755c887 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_o.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_p.png b/interface/resources/meshes/keyboard/keyCap_p.png new file mode 100644 index 0000000000..366a9ef3ff Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_p.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_q.png b/interface/resources/meshes/keyboard/keyCap_q.png new file mode 100644 index 0000000000..2d58bc2b46 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_q.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_r.png b/interface/resources/meshes/keyboard/keyCap_r.png new file mode 100644 index 0000000000..053151aada Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_r.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_s.png b/interface/resources/meshes/keyboard/keyCap_s.png new file mode 100644 index 0000000000..365a833af2 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_s.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_t.png b/interface/resources/meshes/keyboard/keyCap_t.png new file mode 100644 index 0000000000..6ee42e3ae4 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_t.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_u.png b/interface/resources/meshes/keyboard/keyCap_u.png new file mode 100644 index 0000000000..731467227a Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_u.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_v.png b/interface/resources/meshes/keyboard/keyCap_v.png new file mode 100644 index 0000000000..1dbe395005 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_v.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_w.png b/interface/resources/meshes/keyboard/keyCap_w.png new file mode 100644 index 0000000000..a71de5124d Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_w.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_x.png b/interface/resources/meshes/keyboard/keyCap_x.png new file mode 100644 index 0000000000..232725dd6c Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_x.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_y.png b/interface/resources/meshes/keyboard/keyCap_y.png new file mode 100644 index 0000000000..ed68e21384 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_y.png differ diff --git a/interface/resources/meshes/keyboard/keyCap_z.png b/interface/resources/meshes/keyboard/keyCap_z.png new file mode 100644 index 0000000000..a1fe8d4181 Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_z.png differ diff --git a/interface/resources/meshes/keyboard/key_0.png b/interface/resources/meshes/keyboard/key_0.png new file mode 100644 index 0000000000..ff852cda9d Binary files /dev/null and b/interface/resources/meshes/keyboard/key_0.png differ diff --git a/interface/resources/meshes/keyboard/key_1.png b/interface/resources/meshes/keyboard/key_1.png new file mode 100644 index 0000000000..7115e92be8 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_1.png differ diff --git a/interface/resources/meshes/keyboard/key_123.png b/interface/resources/meshes/keyboard/key_123.png new file mode 100644 index 0000000000..07a3187a70 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_123.png differ diff --git a/interface/resources/meshes/keyboard/key_2.png b/interface/resources/meshes/keyboard/key_2.png new file mode 100644 index 0000000000..99a7e650f1 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_2.png differ diff --git a/interface/resources/meshes/keyboard/key_3.png b/interface/resources/meshes/keyboard/key_3.png new file mode 100644 index 0000000000..5ec80db3e0 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_3.png differ diff --git a/interface/resources/meshes/keyboard/key_4.png b/interface/resources/meshes/keyboard/key_4.png new file mode 100644 index 0000000000..e97261f2cd Binary files /dev/null and b/interface/resources/meshes/keyboard/key_4.png differ diff --git a/interface/resources/meshes/keyboard/key_5.png b/interface/resources/meshes/keyboard/key_5.png new file mode 100644 index 0000000000..59e059fbf4 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_5.png differ diff --git a/interface/resources/meshes/keyboard/key_6.png b/interface/resources/meshes/keyboard/key_6.png new file mode 100644 index 0000000000..bf4e81a7a1 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_6.png differ diff --git a/interface/resources/meshes/keyboard/key_7.png b/interface/resources/meshes/keyboard/key_7.png new file mode 100644 index 0000000000..5d9765b37e Binary files /dev/null and b/interface/resources/meshes/keyboard/key_7.png differ diff --git a/interface/resources/meshes/keyboard/key_8.png b/interface/resources/meshes/keyboard/key_8.png new file mode 100644 index 0000000000..f905e2220c Binary files /dev/null and b/interface/resources/meshes/keyboard/key_8.png differ diff --git a/interface/resources/meshes/keyboard/key_9.png b/interface/resources/meshes/keyboard/key_9.png new file mode 100644 index 0000000000..89a6397c82 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_9.png differ diff --git a/interface/resources/meshes/keyboard/key_a.png b/interface/resources/meshes/keyboard/key_a.png new file mode 100644 index 0000000000..74d57d5bd4 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_a.png differ diff --git a/interface/resources/meshes/keyboard/key_abc.png b/interface/resources/meshes/keyboard/key_abc.png new file mode 100644 index 0000000000..5b7f1bcb0f Binary files /dev/null and b/interface/resources/meshes/keyboard/key_abc.png differ diff --git a/interface/resources/meshes/keyboard/key_ampersand.png b/interface/resources/meshes/keyboard/key_ampersand.png new file mode 100644 index 0000000000..e8e06892f8 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_ampersand.png differ diff --git a/interface/resources/meshes/keyboard/key_ast.png b/interface/resources/meshes/keyboard/key_ast.png new file mode 100644 index 0000000000..1c6f03ed4a Binary files /dev/null and b/interface/resources/meshes/keyboard/key_ast.png differ diff --git a/interface/resources/meshes/keyboard/key_at.png b/interface/resources/meshes/keyboard/key_at.png new file mode 100644 index 0000000000..0d0e9019a8 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_at.png differ diff --git a/interface/resources/meshes/keyboard/key_b.png b/interface/resources/meshes/keyboard/key_b.png new file mode 100644 index 0000000000..50b607cbd3 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_b.png differ diff --git a/interface/resources/meshes/keyboard/key_backspace.png b/interface/resources/meshes/keyboard/key_backspace.png new file mode 100644 index 0000000000..db56841a4a Binary files /dev/null and b/interface/resources/meshes/keyboard/key_backspace.png differ diff --git a/interface/resources/meshes/keyboard/key_c.png b/interface/resources/meshes/keyboard/key_c.png new file mode 100644 index 0000000000..da8a4a4f2d Binary files /dev/null and b/interface/resources/meshes/keyboard/key_c.png differ diff --git a/interface/resources/meshes/keyboard/key_cap.png b/interface/resources/meshes/keyboard/key_cap.png new file mode 100644 index 0000000000..1e3c4c1b9f Binary files /dev/null and b/interface/resources/meshes/keyboard/key_cap.png differ diff --git a/interface/resources/meshes/keyboard/key_caret.png b/interface/resources/meshes/keyboard/key_caret.png new file mode 100644 index 0000000000..05011b905a Binary files /dev/null and b/interface/resources/meshes/keyboard/key_caret.png differ diff --git a/interface/resources/meshes/keyboard/key_close_paren.png b/interface/resources/meshes/keyboard/key_close_paren.png new file mode 100644 index 0000000000..6a93b0fe05 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_close_paren.png differ diff --git a/interface/resources/meshes/keyboard/key_colon.png b/interface/resources/meshes/keyboard/key_colon.png new file mode 100644 index 0000000000..a4f025349c Binary files /dev/null and b/interface/resources/meshes/keyboard/key_colon.png differ diff --git a/interface/resources/meshes/keyboard/key_comma.png b/interface/resources/meshes/keyboard/key_comma.png new file mode 100644 index 0000000000..69da507ec6 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_comma.png differ diff --git a/interface/resources/meshes/keyboard/key_d.png b/interface/resources/meshes/keyboard/key_d.png new file mode 100644 index 0000000000..557f3816f0 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_d.png differ diff --git a/interface/resources/meshes/keyboard/key_dollar.png b/interface/resources/meshes/keyboard/key_dollar.png new file mode 100644 index 0000000000..0105debb68 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_dollar.png differ diff --git a/interface/resources/meshes/keyboard/key_dquote.png b/interface/resources/meshes/keyboard/key_dquote.png new file mode 100644 index 0000000000..393ab9b748 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_dquote.png differ diff --git a/interface/resources/meshes/keyboard/key_e.png b/interface/resources/meshes/keyboard/key_e.png new file mode 100644 index 0000000000..1b356d9d5b Binary files /dev/null and b/interface/resources/meshes/keyboard/key_e.png differ diff --git a/interface/resources/meshes/keyboard/key_enter.png b/interface/resources/meshes/keyboard/key_enter.png new file mode 100644 index 0000000000..cf935fe07e Binary files /dev/null and b/interface/resources/meshes/keyboard/key_enter.png differ diff --git a/interface/resources/meshes/keyboard/key_exclam.png b/interface/resources/meshes/keyboard/key_exclam.png new file mode 100644 index 0000000000..e9f68ced1a Binary files /dev/null and b/interface/resources/meshes/keyboard/key_exclam.png differ diff --git a/interface/resources/meshes/keyboard/key_exit.png b/interface/resources/meshes/keyboard/key_exit.png new file mode 100644 index 0000000000..4c06660d56 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_exit.png differ diff --git a/interface/resources/meshes/keyboard/key_f.png b/interface/resources/meshes/keyboard/key_f.png new file mode 100644 index 0000000000..93306c6035 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_f.png differ diff --git a/interface/resources/meshes/keyboard/key_g.png b/interface/resources/meshes/keyboard/key_g.png new file mode 100644 index 0000000000..9fda692c04 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_g.png differ diff --git a/interface/resources/meshes/keyboard/key_h.png b/interface/resources/meshes/keyboard/key_h.png new file mode 100644 index 0000000000..c73f37c271 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_h.png differ diff --git a/interface/resources/meshes/keyboard/key_hashtag.png b/interface/resources/meshes/keyboard/key_hashtag.png new file mode 100644 index 0000000000..df673653b0 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_hashtag.png differ diff --git a/interface/resources/meshes/keyboard/key_i.png b/interface/resources/meshes/keyboard/key_i.png new file mode 100644 index 0000000000..6277d8085c Binary files /dev/null and b/interface/resources/meshes/keyboard/key_i.png differ diff --git a/interface/resources/meshes/keyboard/key_j.png b/interface/resources/meshes/keyboard/key_j.png new file mode 100644 index 0000000000..f723de1f55 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_j.png differ diff --git a/interface/resources/meshes/keyboard/key_k.png b/interface/resources/meshes/keyboard/key_k.png new file mode 100644 index 0000000000..aa3e806a8d Binary files /dev/null and b/interface/resources/meshes/keyboard/key_k.png differ diff --git a/interface/resources/meshes/keyboard/key_l.png b/interface/resources/meshes/keyboard/key_l.png new file mode 100644 index 0000000000..4b31a84f32 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_l.png differ diff --git a/interface/resources/meshes/keyboard/key_m.png b/interface/resources/meshes/keyboard/key_m.png new file mode 100644 index 0000000000..2e83b7b214 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_m.png differ diff --git a/interface/resources/meshes/keyboard/key_min.png b/interface/resources/meshes/keyboard/key_min.png new file mode 100644 index 0000000000..e45beb4e7c Binary files /dev/null and b/interface/resources/meshes/keyboard/key_min.png differ diff --git a/interface/resources/meshes/keyboard/key_n.png b/interface/resources/meshes/keyboard/key_n.png new file mode 100644 index 0000000000..be1a6a9e7a Binary files /dev/null and b/interface/resources/meshes/keyboard/key_n.png differ diff --git a/interface/resources/meshes/keyboard/key_o.png b/interface/resources/meshes/keyboard/key_o.png new file mode 100644 index 0000000000..883feec871 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_o.png differ diff --git a/interface/resources/meshes/keyboard/key_open_paren.png b/interface/resources/meshes/keyboard/key_open_paren.png new file mode 100644 index 0000000000..9dcee0b9a0 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_open_paren.png differ diff --git a/interface/resources/meshes/keyboard/key_p.png b/interface/resources/meshes/keyboard/key_p.png new file mode 100644 index 0000000000..8d55d351ce Binary files /dev/null and b/interface/resources/meshes/keyboard/key_p.png differ diff --git a/interface/resources/meshes/keyboard/key_percentage.png b/interface/resources/meshes/keyboard/key_percentage.png new file mode 100644 index 0000000000..fbe1fa9599 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_percentage.png differ diff --git a/interface/resources/meshes/keyboard/key_period.png b/interface/resources/meshes/keyboard/key_period.png new file mode 100644 index 0000000000..ff726df39b Binary files /dev/null and b/interface/resources/meshes/keyboard/key_period.png differ diff --git a/interface/resources/meshes/keyboard/key_plus.png b/interface/resources/meshes/keyboard/key_plus.png new file mode 100644 index 0000000000..3eab84c34d Binary files /dev/null and b/interface/resources/meshes/keyboard/key_plus.png differ diff --git a/interface/resources/meshes/keyboard/key_q.png b/interface/resources/meshes/keyboard/key_q.png new file mode 100644 index 0000000000..fc733fd7fd Binary files /dev/null and b/interface/resources/meshes/keyboard/key_q.png differ diff --git a/interface/resources/meshes/keyboard/key_question.png b/interface/resources/meshes/keyboard/key_question.png new file mode 100644 index 0000000000..57f5187213 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_question.png differ diff --git a/interface/resources/meshes/keyboard/key_r.png b/interface/resources/meshes/keyboard/key_r.png new file mode 100644 index 0000000000..6277c64097 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_r.png differ diff --git a/interface/resources/meshes/keyboard/key_s.png b/interface/resources/meshes/keyboard/key_s.png new file mode 100644 index 0000000000..1fc1085391 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_s.png differ diff --git a/interface/resources/meshes/keyboard/key_semi.png b/interface/resources/meshes/keyboard/key_semi.png new file mode 100644 index 0000000000..5cb1b495a4 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_semi.png differ diff --git a/interface/resources/meshes/keyboard/key_slash.png b/interface/resources/meshes/keyboard/key_slash.png new file mode 100644 index 0000000000..be7b0fecb4 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_slash.png differ diff --git a/interface/resources/meshes/keyboard/key_squote.png b/interface/resources/meshes/keyboard/key_squote.png new file mode 100644 index 0000000000..3239c19ceb Binary files /dev/null and b/interface/resources/meshes/keyboard/key_squote.png differ diff --git a/interface/resources/meshes/keyboard/key_t.png b/interface/resources/meshes/keyboard/key_t.png new file mode 100644 index 0000000000..c2082b8f51 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_t.png differ diff --git a/interface/resources/meshes/keyboard/key_u.png b/interface/resources/meshes/keyboard/key_u.png new file mode 100644 index 0000000000..657527f6c0 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_u.png differ diff --git a/interface/resources/meshes/keyboard/key_under.png b/interface/resources/meshes/keyboard/key_under.png new file mode 100644 index 0000000000..3694dd3109 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_under.png differ diff --git a/interface/resources/meshes/keyboard/key_v.png b/interface/resources/meshes/keyboard/key_v.png new file mode 100644 index 0000000000..c061ca6fa0 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_v.png differ diff --git a/interface/resources/meshes/keyboard/key_w.png b/interface/resources/meshes/keyboard/key_w.png new file mode 100644 index 0000000000..15de9b25a8 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_w.png differ diff --git a/interface/resources/meshes/keyboard/key_x.png b/interface/resources/meshes/keyboard/key_x.png new file mode 100644 index 0000000000..d81a423f3b Binary files /dev/null and b/interface/resources/meshes/keyboard/key_x.png differ diff --git a/interface/resources/meshes/keyboard/key_y.png b/interface/resources/meshes/keyboard/key_y.png new file mode 100644 index 0000000000..cb85af5b32 Binary files /dev/null and b/interface/resources/meshes/keyboard/key_y.png differ diff --git a/interface/resources/meshes/keyboard/key_z.png b/interface/resources/meshes/keyboard/key_z.png new file mode 100644 index 0000000000..462531351d Binary files /dev/null and b/interface/resources/meshes/keyboard/key_z.png differ diff --git a/interface/resources/meshes/keyboard/text_placard.png b/interface/resources/meshes/keyboard/text_placard.png new file mode 100644 index 0000000000..b0a5953a30 Binary files /dev/null and b/interface/resources/meshes/keyboard/text_placard.png differ diff --git a/interface/resources/meshes/keyboard/white.png b/interface/resources/meshes/keyboard/white.png new file mode 100644 index 0000000000..9673f508fc Binary files /dev/null and b/interface/resources/meshes/keyboard/white.png differ diff --git a/interface/resources/qml/+android/Stats.qml b/interface/resources/qml/+android/Stats.qml index 0dcb07e730..e9a2aa47eb 100644 --- a/interface/resources/qml/+android/Stats.qml +++ b/interface/resources/qml/+android/Stats.qml @@ -10,6 +10,7 @@ Item { property int modality: Qt.NonModal implicitHeight: row.height implicitWidth: row.width + visible: false Component.onCompleted: { stats.parentChanged.connect(fill); diff --git a/interface/resources/qml/AudioScopeUI.qml b/interface/resources/qml/AudioScopeUI.qml index aa181dbf8d..91908807e2 100644 --- a/interface/resources/qml/AudioScopeUI.qml +++ b/interface/resources/qml/AudioScopeUI.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import "styles-uit" -import "controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit Item { id: root diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 4474cfb2cd..01de7a36f9 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -2,9 +2,9 @@ import QtQuick 2.5 import QtWebChannel 1.0 import QtWebEngine 1.5 -import "controls-uit" +import controlsUit 1.0 import "styles" as HifiStyles -import "styles-uit" +import stylesUit 1.0 import "windows" ScrollingWindow { diff --git a/interface/resources/qml/CurrentAPI.qml b/interface/resources/qml/CurrentAPI.qml index 96bfb5c36b..4ea45041c3 100644 --- a/interface/resources/qml/CurrentAPI.qml +++ b/interface/resources/qml/CurrentAPI.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import "styles-uit" -import "controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Item { id: root diff --git a/interface/resources/qml/InfoView.qml b/interface/resources/qml/InfoView.qml index f18969fb2f..8c5900b4c3 100644 --- a/interface/resources/qml/InfoView.qml +++ b/interface/resources/qml/InfoView.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import Hifi 1.0 as Hifi -import "controls-uit" +import controlsUit 1.0 import "windows" as Windows Windows.ScrollingWindow { diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml index e8ddbf823d..c217238e93 100644 --- a/interface/resources/qml/InteractiveWindow.qml +++ b/interface/resources/qml/InteractiveWindow.qml @@ -12,9 +12,9 @@ import QtQuick 2.3 import "windows" as Windows import "controls" -import "controls-uit" as Controls +import controlsUit 1.0 as Controls import "styles" -import "styles-uit" +import stylesUit 1.0 Windows.Window { id: root; diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index 336858502d..12117aaba4 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -11,8 +11,8 @@ import Hifi 1.0 import QtQuick 2.4 -import "controls-uit" -import "styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "windows" import "LoginDialog" diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml index 96b638c911..a40110b1e9 100644 --- a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml @@ -13,8 +13,8 @@ import QtQuick 2.4 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 as OriginalStyles -import "../../controls-uit" -import "../../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: linkAccountBody diff --git a/interface/resources/qml/LoginDialog/+android/SignUpBody.qml b/interface/resources/qml/LoginDialog/+android/SignUpBody.qml index 3a44a8d741..10909e4c85 100644 --- a/interface/resources/qml/LoginDialog/+android/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/+android/SignUpBody.qml @@ -13,8 +13,8 @@ import QtQuick 2.4 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 as OriginalStyles -import "../../controls-uit" -import "../../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: signupBody diff --git a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml index fe4c511f1d..3a57061de4 100644 --- a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml +++ b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml @@ -12,8 +12,8 @@ import Hifi 1.0 import QtQuick 2.4 import QtQuick.Controls.Styles 1.4 as OriginalStyles -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: completeProfileBody diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 48cf124127..103761236d 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -13,8 +13,9 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 as OriginalStyles -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 + Item { id: linkAccountBody clip: true @@ -96,7 +97,7 @@ Item { topMargin: hifi.dimensions.contentSpacing.y } - text: qsTr("Sign in to High Fidelity to make friends, get HFC, and buy interesting things on the Marketplace!") + text: qsTr("Sign in to High Fidelity to make friends, get HFC, and get interesting things on the Marketplace!") width: parent.width wrapMode: Text.WordWrap lineHeight: 1 @@ -136,7 +137,7 @@ Item { TextField { id: usernameField - text: Settings.getValue("wallet/savedUsername", ""); + text: Settings.getValue("keepMeLoggedIn/savedUsername", ""); width: parent.width focus: true placeholderText: "Username or Email" @@ -165,7 +166,7 @@ Item { root.text = ""; } Component.onCompleted: { - var savedUsername = Settings.getValue("wallet/savedUsername", ""); + var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", ""); usernameField.text = savedUsername === "Unknown user" ? "" : savedUsername; } } @@ -239,7 +240,10 @@ Item { } - Keys.onReturnPressed: linkAccountBody.login() + Keys.onReturnPressed: { + Settings.setValue("keepMeLoggedIn/savedUsername", usernameField.text); + linkAccountBody.login(); + } } InfoItem { @@ -263,21 +267,21 @@ Item { CheckBox { id: autoLogoutCheckbox - checked: !Settings.getValue("wallet/autoLogout", true) - text: "Keep me signed in" + checked: Settings.getValue("keepMeLoggedIn", false) + text: "Keep me logged in" boxSize: 20; labelFontSize: 15 color: hifi.colors.black onCheckedChanged: { - Settings.setValue("wallet/autoLogout", !checked); + Settings.setValue("keepMeLoggedIn", checked); if (checked) { - Settings.setValue("wallet/savedUsername", Account.username); + Settings.setValue("keepMeLoggedIn/savedUsername", usernameField.text); } else { - Settings.setValue("wallet/savedUsername", ""); + Settings.setValue("keepMeLoggedIn/savedUsername", ""); } } Component.onDestruction: { - Settings.setValue("wallet/autoLogout", !checked); + Settings.setValue("keepMeLoggedIn", checked); } } @@ -289,7 +293,10 @@ Item { text: qsTr(loginDialog.isSteamRunning() ? "Link Account" : "Log in") color: hifi.buttons.blue - onClicked: linkAccountBody.login() + onClicked: { + Settings.setValue("keepMeLoggedIn/savedUsername", usernameField.text); + linkAccountBody.login(); + } } } @@ -403,6 +410,7 @@ Item { case Qt.Key_Enter: case Qt.Key_Return: event.accepted = true + Settings.setValue("keepMeLoggedIn/savedUsername", usernameField.text); linkAccountBody.login() break } diff --git a/interface/resources/qml/LoginDialog/SignInBody.qml b/interface/resources/qml/LoginDialog/SignInBody.qml index 9cb1add704..7fe29e13f6 100644 --- a/interface/resources/qml/LoginDialog/SignInBody.qml +++ b/interface/resources/qml/LoginDialog/SignInBody.qml @@ -12,8 +12,8 @@ import Hifi 1.0 import QtQuick 2.7 import QtQuick.Controls.Styles 1.4 as OriginalStyles -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: signInBody diff --git a/interface/resources/qml/LoginDialog/SignUpBody.qml b/interface/resources/qml/LoginDialog/SignUpBody.qml index bb30696e4c..d3c898d76f 100644 --- a/interface/resources/qml/LoginDialog/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/SignUpBody.qml @@ -12,8 +12,8 @@ import Hifi 1.0 import QtQuick 2.7 import QtQuick.Controls 1.4 -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: signupBody diff --git a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml index bf05a36ce1..2a41353534 100644 --- a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml +++ b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml @@ -12,8 +12,8 @@ import Hifi 1.0 import QtQuick 2.4 import QtQuick.Controls 1.4 -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: usernameCollisionBody diff --git a/interface/resources/qml/LoginDialog/WelcomeBody.qml b/interface/resources/qml/LoginDialog/WelcomeBody.qml index 551ec263b7..020e6db002 100644 --- a/interface/resources/qml/LoginDialog/WelcomeBody.qml +++ b/interface/resources/qml/LoginDialog/WelcomeBody.qml @@ -11,8 +11,8 @@ import Hifi 1.0 import QtQuick 2.4 -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: welcomeBody diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index 8c4d6145ec..322535641d 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -13,8 +13,8 @@ import QtWebEngine 1.1 import QtWebChannel 1.0 import "windows" as Windows -import "controls-uit" as Controls -import "styles-uit" +import controlsUit 1.0 as Controls +import stylesUit 1.0 Windows.ScrollingWindow { id: root diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index bef6423e25..53e6bcc37d 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -2,9 +2,9 @@ import QtQuick 2.3 import "windows" as Windows import "controls" -import "controls-uit" as Controls +import controlsUit 1.0 as Controls import "styles" -import "styles-uit" +import stylesUit 1.0 Windows.Window { id: root diff --git a/interface/resources/qml/TabletBrowser.qml b/interface/resources/qml/TabletBrowser.qml index 141c1f25a7..720a904231 100644 --- a/interface/resources/qml/TabletBrowser.qml +++ b/interface/resources/qml/TabletBrowser.qml @@ -3,9 +3,9 @@ import QtWebChannel 1.0 import QtWebEngine 1.5 import "controls" -import "controls-uit" as HifiControls +import controlsUit 1.0 as HifiControls import "styles" as HifiStyles -import "styles-uit" +import stylesUit 1.0 import "windows" Item { diff --git a/interface/resources/qml/UpdateDialog.qml b/interface/resources/qml/UpdateDialog.qml index 5e05601ce4..9c22d0b65b 100644 --- a/interface/resources/qml/UpdateDialog.qml +++ b/interface/resources/qml/UpdateDialog.qml @@ -4,9 +4,9 @@ import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 import QtGraphicalEffects 1.0 -import "controls-uit" +import controlsUit 1.0 import "styles" as HifiStyles -import "styles-uit" +import stylesUit 1.0 import "windows" ScrollingWindow { diff --git a/interface/resources/qml/controls-uit/AttachmentsTable.qml b/interface/resources/qml/controls-uit/AttachmentsTable.qml index 8ee9909ab8..e7fe874610 100644 --- a/interface/resources/qml/controls-uit/AttachmentsTable.qml +++ b/interface/resources/qml/controls-uit/AttachmentsTable.qml @@ -1,170 +1,4 @@ -// -// AttachmentsTable.qml -// -// Created by David Rowe on 18 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtQuick.XmlListModel 2.0 - -import "../styles-uit" -import "../controls-uit" as HifiControls -import "../windows" -import "../hifi/models" - -TableView { - id: tableView - - property int colorScheme: hifi.colorSchemes.light - readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light - - model: S3Model{} - - Rectangle { - anchors.fill: parent - visible: tableView.model.status !== XmlListModel.Ready - color: hifi.colors.darkGray0 - BusyIndicator { - anchors.centerIn: parent - width: 48; height: 48 - running: true - } - } - - headerDelegate: Rectangle { - height: hifi.dimensions.tableHeaderHeight - color: hifi.colors.darkGray - border.width: 0.5 - border.color: hifi.colors.baseGrayHighlight - - RalewayRegular { - id: textHeader - size: hifi.fontSizes.tableHeading - color: hifi.colors.lightGrayText - text: styleData.value - anchors { - horizontalCenter: parent.horizontalCenter - verticalCenter: parent.verticalCenter - } - } - } - - // Use rectangle to draw border with rounded corners. - Rectangle { - color: "#00000000" - anchors { fill: parent; margins: -2 } - radius: hifi.dimensions.borderRadius - border.color: hifi.colors.baseGrayHighlight - border.width: 3 - } - anchors.margins: 2 // Shrink TableView to lie within border. - backgroundVisible: true - - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - verticalScrollBarPolicy: Qt.ScrollBarAsNeeded - - style: TableViewStyle { - // Needed in order for rows to keep displaying rows after end of table entries. - backgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightEven : hifi.colors.tableRowDarkEven - alternateBackgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd - - handle: Item { - id: scrollbarHandle - implicitWidth: 6 - Rectangle { - anchors { - fill: parent - leftMargin: 2 // Move it right - rightMargin: -2 // "" - topMargin: 3 // Shrink vertically - bottomMargin: 3 // "" - } - radius: 3 - color: hifi.colors.tableScrollHandleDark - } - } - - scrollBarBackground: Item { - implicitWidth: 10 - Rectangle { - anchors { - fill: parent - margins: -1 // Expand - } - color: hifi.colors.baseGrayHighlight - } - - Rectangle { - anchors { - fill: parent - margins: 1 // Shrink - } - radius: 4 - color: hifi.colors.tableScrollBackgroundDark - } - } - - incrementControl: Item { - visible: false - } - - decrementControl: Item { - visible: false - } - } - - rowDelegate: Rectangle { - height: hifi.dimensions.tableRowHeight - color: styleData.selected - ? hifi.colors.primaryHighlight - : tableView.isLightColorScheme - ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) - : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd) - } - - itemDelegate: Item { - anchors { - left: parent ? parent.left : undefined - leftMargin: hifi.dimensions.tablePadding - right: parent ? parent.right : undefined - rightMargin: hifi.dimensions.tablePadding - } - FiraSansSemiBold { - id: textItem - text: styleData.value - size: hifi.fontSizes.tableText - color: colorScheme == hifi.colorSchemes.light - ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) - : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) - anchors { - horizontalCenter: parent.horizontalCenter - verticalCenter: parent.verticalCenter - } - } - } - - TableViewColumn { - role: "name" - title: "NAME" - width: parent.width *0.3 - horizontalAlignment: Text.AlignHCenter - } - TableViewColumn { - role: "size" - title: "SIZE" - width: parent.width *0.2 - horizontalAlignment: Text.AlignHCenter - } - TableViewColumn { - role: "modified" - title: "LAST MODIFIED" - width: parent.width *0.5 - horizontalAlignment: Text.AlignHCenter - } -} +AttachmentsTable { +} \ No newline at end of file diff --git a/interface/resources/qml/controls-uit/BaseWebView.qml b/interface/resources/qml/controls-uit/BaseWebView.qml index fdd9c12220..61f428e9f7 100644 --- a/interface/resources/qml/controls-uit/BaseWebView.qml +++ b/interface/resources/qml/controls-uit/BaseWebView.qml @@ -1,38 +1,4 @@ -// -// WebView.qml -// -// Created by Bradley Austin Davis on 12 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.7 -import QtWebEngine 1.5 - -WebEngineView { - id: root - - Component.onCompleted: { - console.log("Connecting JS messaging to Hifi Logging") - // Ensure the JS from the web-engine makes it to our logging - root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { - console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); - }); - } - - onLoadingChanged: { - // Required to support clicking on "hifi://" links - if (WebEngineView.LoadStartedStatus == loadRequest.status) { - var url = loadRequest.url.toString(); - if (urlHandler.canHandleUrl(url)) { - if (urlHandler.handleUrl(url)) { - root.stop(); - } - } - } - } - - WebSpinner { } -} +BaseWebView { +} \ No newline at end of file diff --git a/interface/resources/qml/controls-uit/Button.qml b/interface/resources/qml/controls-uit/Button.qml index f1a6e4bb4a..1d31f02777 100644 --- a/interface/resources/qml/controls-uit/Button.qml +++ b/interface/resources/qml/controls-uit/Button.qml @@ -1,122 +1,4 @@ -// -// Button.qml -// -// Created by David Rowe on 16 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.7 -import QtQuick.Controls 2.3 as Original -import TabletScriptingInterface 1.0 - -import "../styles-uit" - -Original.Button { - id: control; - - property int color: 0 - property int colorScheme: hifi.colorSchemes.light - property int fontSize: hifi.fontSizes.buttonLabel - property int radius: hifi.buttons.radius - property alias implicitTextWidth: buttonText.implicitWidth - property string buttonGlyph: ""; - property int fontCapitalization: Font.AllUppercase - - width: hifi.dimensions.buttonWidth - height: hifi.dimensions.controlLineHeight - - HifiConstants { id: hifi } - - onHoveredChanged: { - if (hovered) { - Tablet.playSound(TabletEnums.ButtonHover); - } - } - - onFocusChanged: { - if (focus) { - Tablet.playSound(TabletEnums.ButtonHover); - } - } - - onClicked: { - Tablet.playSound(TabletEnums.ButtonClick); - } - - background: Rectangle { - radius: control.radius - - border.width: (control.color === hifi.buttons.none || - (control.color === hifi.buttons.noneBorderless && control.hovered) || - (control.color === hifi.buttons.noneBorderlessWhite && control.hovered) || - (control.color === hifi.buttons.noneBorderlessGray && control.hovered)) ? 1 : 0; - border.color: control.color === hifi.buttons.noneBorderless ? hifi.colors.blueHighlight : - (control.color === hifi.buttons.noneBorderlessGray ? hifi.colors.baseGray : hifi.colors.white); - - gradient: Gradient { - GradientStop { - position: 0.2 - color: { - if (!control.enabled) { - hifi.buttons.disabledColorStart[control.colorScheme] - } else if (control.pressed) { - hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { - hifi.buttons.hoveredColor[control.color] - } else { - hifi.buttons.colorStart[control.color] - } - } - } - GradientStop { - position: 1.0 - color: { - if (!control.enabled) { - hifi.buttons.disabledColorFinish[control.colorScheme] - } else if (control.pressed) { - hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { - hifi.buttons.hoveredColor[control.color] - } else { - hifi.buttons.colorFinish[control.color] - } - } - } - } - } - - contentItem: Item { - HiFiGlyphs { - id: buttonGlyph; - visible: control.buttonGlyph !== ""; - text: control.buttonGlyph === "" ? hifi.glyphs.question : control.buttonGlyph; - // Size - size: 34; - // Anchors - anchors.right: buttonText.left; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - // Style - color: enabled ? hifi.buttons.textColor[control.color] - : hifi.buttons.disabledTextColor[control.colorScheme]; - // Alignment - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - } - RalewayBold { - id: buttonText; - anchors.centerIn: parent; - font.capitalization: control.fontCapitalization - color: enabled ? hifi.buttons.textColor[control.color] - : hifi.buttons.disabledTextColor[control.colorScheme] - size: control.fontSize - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: control.text - } - } +Button { } - diff --git a/interface/resources/qml/controls-uit/CheckBox.qml b/interface/resources/qml/controls-uit/CheckBox.qml index 6e4a3df010..c10bea08c4 100644 --- a/interface/resources/qml/controls-uit/CheckBox.qml +++ b/interface/resources/qml/controls-uit/CheckBox.qml @@ -1,121 +1,4 @@ -// -// CheckBox.qml -// -// Created by David Rowe on 26 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.2 -import QtQuick.Controls 2.2 as Original - -import "../styles-uit" - -import TabletScriptingInterface 1.0 - -Original.CheckBox { - id: checkBox - - property int colorScheme: hifi.colorSchemes.light - property string color: hifi.colors.lightGrayText - readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light - property bool isRedCheck: false - property int boxSize: 14 - property int boxRadius: 3 - property bool wrap: true; - readonly property int checkSize: Math.max(boxSize - 8, 10) - readonly property int checkRadius: 2 - property string labelFontFamily: "Raleway" - property int labelFontSize: 14; - property int labelFontWeight: Font.DemiBold; - focusPolicy: Qt.ClickFocus - hoverEnabled: true - - onClicked: { - Tablet.playSound(TabletEnums.ButtonClick); - } - - onHoveredChanged: { - if (hovered) { - Tablet.playSound(TabletEnums.ButtonHover); - } - } - - - indicator: Rectangle { - id: box - implicitWidth: boxSize - implicitHeight: boxSize - radius: boxRadius - y: parent.height / 2 - height / 2 - border.width: 1 - border.color: pressed || hovered - ? hifi.colors.checkboxCheckedBorder - : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) - - gradient: Gradient { - GradientStop { - position: 0.2 - color: pressed || hovered - ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightStart) - : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightStart : hifi.colors.checkboxDarkStart) - } - GradientStop { - position: 1.0 - color: pressed || hovered - ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightFinish) - : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) - } - } - - Rectangle { - visible: pressed || hovered - anchors.centerIn: parent - id: innerBox - width: checkSize - 4 - height: width - radius: checkRadius - color: hifi.colors.checkboxCheckedBorder - } - - Rectangle { - id: check - width: checkSize - height: checkSize - radius: checkRadius - anchors.centerIn: parent - color: isRedCheck ? hifi.colors.checkboxCheckedRed : hifi.colors.checkboxChecked - border.width: 2 - border.color: isRedCheck? hifi.colors.checkboxCheckedBorderRed : hifi.colors.checkboxCheckedBorder - visible: checked && !pressed || !checked && pressed - } - - Rectangle { - id: disabledOverlay - visible: !enabled - width: boxSize - height: boxSize - radius: boxRadius - border.width: 1 - border.color: hifi.colors.baseGrayHighlight - color: hifi.colors.baseGrayHighlight - opacity: 0.5 - } - } - - contentItem: Label { - text: checkBox.text - color: checkBox.color - font.family: checkBox.labelFontFamily; - font.pixelSize: checkBox.labelFontSize; - font.weight: checkBox.labelFontWeight; - x: 2 - verticalAlignment: Text.AlignVCenter - wrapMode: checkBox.wrap ? Text.Wrap : Text.NoWrap - elide: checkBox.wrap ? Text.ElideNone : Text.ElideRight - enabled: checkBox.enabled - leftPadding: checkBox.indicator.width + checkBox.spacing - } +CheckBox { } diff --git a/interface/resources/qml/controls-uit/CheckBoxQQC2.qml b/interface/resources/qml/controls-uit/CheckBoxQQC2.qml index 8a9686ff5e..af758ee707 100644 --- a/interface/resources/qml/controls-uit/CheckBoxQQC2.qml +++ b/interface/resources/qml/controls-uit/CheckBoxQQC2.qml @@ -1,125 +1,4 @@ -// -// CheckBox2.qml -// -// Created by Vlad Stelmahovsky on 10 Aug 2017 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.7 -import QtQuick.Controls 2.2 - -import "../styles-uit" -import "../controls-uit" as HiFiControls -import TabletScriptingInterface 1.0 - -CheckBox { - id: checkBox - - HifiConstants { id: hifi; } - - padding: 0 - leftPadding: 0 - property int colorScheme: hifi.colorSchemes.light - property string color: hifi.colors.lightGrayText - readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light - property bool isRedCheck: false - property bool isRound: false - property int boxSize: 14 - property int boxRadius: isRound ? boxSize : 3 - property bool wrap: true; - readonly property int checkSize: Math.max(boxSize - 8, 10) - readonly property int checkRadius: isRound ? checkSize / 2 : 2 - focusPolicy: Qt.ClickFocus - hoverEnabled: true - - onClicked: { - Tablet.playSound(TabletEnums.ButtonClick); - } - - onHoveredChanged: { - if (hovered) { - Tablet.playSound(TabletEnums.ButtonHover); - } - } - - indicator: Rectangle { - id: box - implicitWidth: boxSize - implicitHeight: boxSize - radius: boxRadius - x: checkBox.leftPadding - y: parent.height / 2 - height / 2 - border.width: 1 - border.color: pressed || hovered - ? hifi.colors.checkboxCheckedBorder - : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) - - gradient: Gradient { - GradientStop { - position: 0.2 - color: pressed || hovered - ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightStart) - : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightStart : hifi.colors.checkboxDarkStart) - } - GradientStop { - position: 1.0 - color: pressed || hovered - ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightFinish) - : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) - } - } - - Rectangle { - visible: pressed || hovered - anchors.centerIn: parent - id: innerBox - width: checkSize - 4 - height: width - radius: checkRadius - color: hifi.colors.checkboxCheckedBorder - } - - Rectangle { - id: check - width: checkSize - height: checkSize - radius: checkRadius - anchors.centerIn: parent - color: isRedCheck ? hifi.colors.checkboxCheckedRed : hifi.colors.checkboxChecked - border.width: 2 - border.color: isRedCheck? hifi.colors.checkboxCheckedBorderRed : hifi.colors.checkboxCheckedBorder - visible: checked && !pressed || !checked && pressed - } - - Rectangle { - id: disabledOverlay - visible: !enabled - width: boxSize - height: boxSize - radius: boxRadius - border.width: 1 - border.color: hifi.colors.baseGrayHighlight - color: hifi.colors.baseGrayHighlight - opacity: 0.5 - } - } - - contentItem: Text { - id: root - font.pixelSize: hifi.fontSizes.inputLabel - font.family: "Raleway" - font.weight: Font.DemiBold - text: checkBox.text - color: checkBox.color - x: 2 - wrapMode: checkBox.wrap ? Text.Wrap : Text.NoWrap - elide: checkBox.wrap ? Text.ElideNone : Text.ElideRight - enabled: checkBox.enabled - verticalAlignment: Text.AlignVCenter - leftPadding: checkBox.indicator.width + checkBox.spacing - } -} +import controlsUit 1.0 +CheckBoxQQC2 { +} \ No newline at end of file diff --git a/interface/resources/qml/controls-uit/ComboBox.qml b/interface/resources/qml/controls-uit/ComboBox.qml index 245b565a62..8ac92909b6 100644 --- a/interface/resources/qml/controls-uit/ComboBox.qml +++ b/interface/resources/qml/controls-uit/ComboBox.qml @@ -1,191 +1,4 @@ -// -// ComboBox.qml -// -// Created by Bradley Austin David on 27 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.7 -import QtQuick.Controls 2.2 - -import "../styles-uit" -import "../controls-uit" as HifiControls - -FocusScope { - id: root - HifiConstants { id: hifi } - - property alias model: comboBox.model; - property alias editable: comboBox.editable - property alias comboBox: comboBox - readonly property alias currentText: comboBox.currentText; - property alias currentIndex: comboBox.currentIndex; - property int currentHighLightedIndex: comboBox.currentIndex; - - property int dropdownHeight: 480 - property int colorScheme: hifi.colorSchemes.light - readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light - property string label: "" - property real controlHeight: height + (comboBoxLabel.visible ? comboBoxLabel.height + comboBoxLabel.anchors.bottomMargin : 0) - - readonly property ComboBox control: comboBox - - property bool isDesktop: true - - signal accepted(); - - implicitHeight: comboBox.height; - focus: true - - ComboBox { - id: comboBox - anchors.fill: parent - hoverEnabled: true - visible: true - height: hifi.fontSizes.textFieldInput + 13 // Match height of TextField control. - - function previousItem() { root.currentHighLightedIndex = (root.currentHighLightedIndex + comboBox.count - 1) % comboBox.count; } - function nextItem() { root.currentHighLightedIndex = (root.currentHighLightedIndex + comboBox.count + 1) % comboBox.count; } - function selectCurrentItem() { root.currentIndex = root.currentHighLightedIndex; close(); /*hideList();*/ } - function selectSpecificItem(index) { root.currentIndex = index; close();/*hideList();*/ } - - Keys.onUpPressed: previousItem(); - Keys.onDownPressed: nextItem(); - Keys.onSpacePressed: selectCurrentItem(); - Keys.onRightPressed: selectCurrentItem(); - Keys.onReturnPressed: selectCurrentItem(); - - background: Rectangle { - gradient: Gradient { - GradientStop { - position: 0.2 - color: comboBox.popup.visible - ? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark) - : (isLightColorScheme ? hifi.colors.dropDownLightStart : hifi.colors.dropDownDarkStart) - } - GradientStop { - position: 1.0 - color: comboBox.popup.visible - ? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark) - : (isLightColorScheme ? hifi.colors.dropDownLightFinish : hifi.colors.dropDownDarkFinish) - } - } - } - - indicator: Item { - id: dropIcon - anchors { right: parent.right; verticalCenter: parent.verticalCenter } - height: root.height - width: height - Rectangle { - width: 1 - height: parent.height - anchors.top: parent.top - anchors.left: parent.left - color: isLightColorScheme ? hifi.colors.faintGray : hifi.colors.baseGray - } - HiFiGlyphs { - anchors { top: parent.top; topMargin: -11; horizontalCenter: parent.horizontalCenter } - size: hifi.dimensions.spinnerSize - text: hifi.glyphs.caratDn - color: comboBox.hovered || comboBox.popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText) - } - } - - contentItem: FiraSansSemiBold { - id: textField - anchors { - left: parent.left - leftMargin: hifi.dimensions.textPadding - verticalCenter: parent.verticalCenter - } - size: hifi.fontSizes.textFieldInput - text: comboBox.displayText ? comboBox.displayText : comboBox.currentText - elide: Text.ElideRight - color: comboBox.hovered || comboBox.popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText ) - } - - delegate: ItemDelegate { - id: itemDelegate - hoverEnabled: true - width: root.width + 4 - height: popupText.implicitHeight * 1.4 - highlighted: root.currentHighLightedIndex == index - - onHoveredChanged: { - if (hovered) { - root.currentHighLightedIndex = index - } - } - - background: Rectangle { - color: itemDelegate.highlighted ? hifi.colors.primaryHighlight - : (isLightColorScheme ? hifi.colors.dropDownPressedLight - : hifi.colors.dropDownPressedDark) - } - - contentItem: FiraSansSemiBold { - id: popupText - anchors.left: parent.left - anchors.leftMargin: hifi.dimensions.textPadding - anchors.verticalCenter: parent.verticalCenter - text: comboBox.model[index] ? comboBox.model[index] - : (comboBox.model.get && comboBox.model.get(index).text ? - comboBox.model.get(index).text : "") - size: hifi.fontSizes.textFieldInput - color: hifi.colors.baseGray - } - } - popup: Popup { - y: comboBox.height - 1 - width: comboBox.width - implicitHeight: listView.contentHeight > dropdownHeight ? dropdownHeight - : listView.contentHeight - padding: 0 - topPadding: 1 - - onClosed: { - root.accepted() - } - - contentItem: ListView { - id: listView - clip: true - model: comboBox.popup.visible ? comboBox.delegateModel : null - currentIndex: root.currentHighLightedIndex - delegate: comboBox.delegate - ScrollBar.vertical: HifiControls.ScrollBar { - id: scrollbar - parent: listView - policy: ScrollBar.AsNeeded - visible: size < 1.0 - } - } - - background: Rectangle { - color: hifi.colors.baseGray - } - } - } - - function textAt(index) { - return comboBox.textAt(index); - } - - HifiControls.Label { - id: comboBoxLabel - text: root.label - colorScheme: root.colorScheme - anchors.left: parent.left - anchors.bottom: parent.top - anchors.bottomMargin: 4 - visible: label != "" - } - - Component.onCompleted: { - isDesktop = (typeof desktop !== "undefined"); - } +ComboBox { } diff --git a/interface/resources/qml/controls-uit/ContentSection.qml b/interface/resources/qml/controls-uit/ContentSection.qml index 47a13e9262..d41b5ad8e6 100644 --- a/interface/resources/qml/controls-uit/ContentSection.qml +++ b/interface/resources/qml/controls-uit/ContentSection.qml @@ -1,138 +1,4 @@ -// -// ContentSection.qml -// -// Created by David Rowe on 16 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 -import QtGraphicalEffects 1.0 - -import "../styles-uit" - -Column { - property string name: "Content Section" - property bool isFirst: false - property bool isCollapsible: false // Set at creation. - property bool isCollapsed: false - - spacing: 0 // Defer spacing decisions to individual controls. - - anchors { - left: parent.left - leftMargin: hifi.dimensions.contentMargin.x - right: parent.right - rightMargin: hifi.dimensions.contentMargin.x - } - - function toggleCollapsed() { - if (isCollapsible) { - isCollapsed = !isCollapsed; - for (var i = 1; i < children.length; i++) { - children[i].visible = !isCollapsed; - } - } - } - - Item { - id: sectionName - anchors.left: parent.left - anchors.right: parent.right - height: leadingSpace.height + topBar.height + heading.height + bottomBar.height - - Item { - id: leadingSpace - width: 1 - height: isFirst ? 7 : 0 - anchors.top: parent.top - } - - Item { - id: topBar - visible: !isFirst - height: visible ? 2 : 0 - anchors.top: leadingSpace.bottom - - Rectangle { - id: shadow - width: frame.width - height: 1 - color: hifi.colors.baseGrayShadow - x: -hifi.dimensions.contentMargin.x - } - - Rectangle { - width: frame.width - height: 1 - color: hifi.colors.baseGrayHighlight - x: -hifi.dimensions.contentMargin.x - anchors.top: shadow.bottom - } - } - - Item { - id: heading - anchors { - left: parent.left - right: parent.right - top: topBar.bottom - } - height: isCollapsible ? 36 : 28 - - RalewayRegular { - id: title - anchors { - left: parent.left - top: parent.top - topMargin: 12 - } - size: hifi.fontSizes.sectionName - font.capitalization: Font.AllUppercase - text: name - color: hifi.colors.lightGrayText - } - - HiFiGlyphs { - anchors { - top: title.top - topMargin: -9 - right: parent.right - rightMargin: -4 - } - size: hifi.fontSizes.disclosureButton - text: isCollapsed ? hifi.glyphs.disclosureButtonExpand : hifi.glyphs.disclosureButtonCollapse - color: hifi.colors.lightGrayText - visible: isCollapsible - } - - MouseArea { - // Events are propogated so that any active control is defocused. - anchors.fill: parent - propagateComposedEvents: true - onPressed: { - toggleCollapsed(); - mouse.accepted = false; - } - } - } - - LinearGradient { - id: bottomBar - visible: desktop.gradientsSupported && isCollapsible - width: frame.width - height: visible ? 4 : 0 - x: -hifi.dimensions.contentMargin.x - anchors.top: heading.bottom - start: Qt.point(0, 0) - end: Qt.point(0, 4) - gradient: Gradient { - GradientStop { position: 0.0; color: hifi.colors.darkGray } - GradientStop { position: 1.0; color: hifi.colors.baseGray } // Equivalent of darkGray0 over baseGray background. - } - cached: true - } - } +ContentSection { } diff --git a/interface/resources/qml/controls-uit/FilterBar.qml b/interface/resources/qml/controls-uit/FilterBar.qml index ecae790b22..dede6d2ded 100644 --- a/interface/resources/qml/controls-uit/FilterBar.qml +++ b/interface/resources/qml/controls-uit/FilterBar.qml @@ -1,321 +1,4 @@ -// -// FilterBar.qml -// -// Created by Zach Fox on 17 Feb 2018-03-12 -// Copyright 2018 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.9 -import QtQuick.Controls 2.2 -import QtGraphicalEffects 1.0 - -import "../styles-uit" -import "../controls-uit" as HifiControls - -Item { - id: root; - - property int colorScheme: hifi.colorSchemes.light - readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light - readonly property bool isFaintGrayColorScheme: colorScheme == hifi.colorSchemes.faintGray - property bool error: false; - property alias textFieldHeight: textField.height; - property string placeholderText; - property alias dropdownHeight: dropdownContainer.height; - property alias text: textField.text; - property alias primaryFilterChoices: filterBarModel; - property int primaryFilter_index: -1; - property string primaryFilter_filterName: ""; - property string primaryFilter_displayName: ""; - signal accepted; - - onPrimaryFilter_indexChanged: { - if (primaryFilter_index === -1) { - primaryFilter_filterName = ""; - primaryFilter_displayName = ""; - } else { - primaryFilter_filterName = filterBarModel.get(primaryFilter_index).filterName; - primaryFilter_displayName = filterBarModel.get(primaryFilter_index).displayName; - } - } - - TextField { - id: textField; - - anchors.top: parent.top; - anchors.right: parent.right; - anchors.left: parent.left; - - font.family: "Fira Sans" - font.pixelSize: hifi.fontSizes.textFieldInput; - - placeholderText: root.primaryFilter_index === -1 ? root.placeholderText : ""; - - TextMetrics { - id: primaryFilterTextMetrics; - font.family: "FiraSans Regular"; - font.pixelSize: hifi.fontSizes.textFieldInput; - font.capitalization: Font.AllUppercase; - text: root.primaryFilter_displayName; - } - - // workaround for https://bugreports.qt.io/browse/QTBUG-49297 - Keys.onPressed: { - switch (event.key) { - case Qt.Key_Return: - case Qt.Key_Enter: - event.accepted = true; - - // emit accepted signal manually - if (acceptableInput) { - root.accepted(); - root.forceActiveFocus(); - } - break; - case Qt.Key_Backspace: - if (textField.text === "") { - primaryFilter_index = -1; - } - break; - } - } - - onAccepted: { - root.forceActiveFocus(); - } - - onActiveFocusChanged: { - if (!activeFocus) { - dropdownContainer.visible = false; - } - } - - color: { - if (isLightColorScheme) { - if (textField.activeFocus) { - hifi.colors.black - } else { - hifi.colors.lightGray - } - } else if (isFaintGrayColorScheme) { - if (textField.activeFocus) { - hifi.colors.black - } else { - hifi.colors.lightGray - } - } else { - if (textField.activeFocus) { - hifi.colors.white - } else { - hifi.colors.lightGrayText - } - } - } - - background: Rectangle { - id: mainFilterBarRectangle; - - color: { - if (isLightColorScheme) { - if (textField.activeFocus) { - hifi.colors.white - } else { - hifi.colors.textFieldLightBackground - } - } else if (isFaintGrayColorScheme) { - if (textField.activeFocus) { - hifi.colors.white - } else { - hifi.colors.faintGray50 - } - } else { - if (textField.activeFocus) { - hifi.colors.black - } else { - hifi.colors.baseGrayShadow - } - } - } - - border.color: textField.error ? hifi.colors.redHighlight : - (textField.activeFocus ? hifi.colors.primaryHighlight : (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray)) - border.width: 1 - radius: 4 - - Item { - id: searchButtonContainer; - anchors.left: parent.left; - anchors.verticalCenter: parent.verticalCenter; - height: parent.height; - width: 42; - - // Search icon - HiFiGlyphs { - id: searchIcon; - text: hifi.glyphs.search - color: textField.color - size: 40; - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - width: paintedWidth; - } - - // Carat - HiFiGlyphs { - text: hifi.glyphs.caratDn; - color: textField.color; - size: 40; - anchors.left: parent.left; - anchors.leftMargin: 15; - width: paintedWidth; - } - - MouseArea { - anchors.fill: parent; - onClicked: { - textField.forceActiveFocus(); - dropdownContainer.visible = !dropdownContainer.visible; - } - } - } - - Rectangle { - z: 999; - id: primaryFilterContainer; - color: textField.activeFocus ? hifi.colors.faintGray : hifi.colors.white; - width: primaryFilterTextMetrics.tightBoundingRect.width + 14; - height: parent.height - 8; - anchors.verticalCenter: parent.verticalCenter; - anchors.left: searchButtonContainer.right; - anchors.leftMargin: 4; - visible: primaryFilterText.text !== ""; - radius: height/2; - - FiraSansRegular { - id: primaryFilterText; - text: root.primaryFilter_displayName; - anchors.fill: parent; - color: textField.activeFocus ? hifi.colors.black : hifi.colors.lightGray; - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - size: hifi.fontSizes.textFieldInput; - font.capitalization: Font.AllUppercase; - } - - MouseArea { - anchors.fill: parent; - onClicked: { - textField.forceActiveFocus(); - } - } - } - - // "Clear" button - HiFiGlyphs { - text: hifi.glyphs.error - color: textField.color - size: 40 - anchors.right: parent.right - anchors.rightMargin: hifi.dimensions.textPadding - 2 - anchors.verticalCenter: parent.verticalCenter - visible: root.text !== "" || root.primaryFilter_index !== -1; - - MouseArea { - anchors.fill: parent; - onClicked: { - root.text = ""; - root.primaryFilter_index = -1; - dropdownContainer.visible = false; - textField.forceActiveFocus(); - } - } - } - } - - selectedTextColor: hifi.colors.black - selectionColor: hifi.colors.primaryHighlight - leftPadding: 44 + (root.primaryFilter_index === -1 ? 0 : primaryFilterTextMetrics.tightBoundingRect.width + 20); - rightPadding: 44; - } - - Rectangle { - id: dropdownContainer; - visible: false; - height: 50 * filterBarModel.count; - width: parent.width; - anchors.top: textField.bottom; - anchors.left: parent.left; - anchors.right: parent.right; - color: hifi.colors.white; - - ListModel { - id: filterBarModel; - } - - ListView { - id: dropdownListView; - interactive: false; - anchors.fill: parent; - model: filterBarModel; - delegate: Rectangle { - id: dropDownButton; - color: hifi.colors.white; - width: parent.width; - height: 50; - - RalewaySemiBold { - id: dropDownButtonText; - text: model.displayName; - anchors.fill: parent; - anchors.leftMargin: 12; - color: hifi.colors.baseGray; - horizontalAlignment: Text.AlignLeft; - verticalAlignment: Text.AlignVCenter; - size: 18; - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: true; - propagateComposedEvents: false; - onEntered: { - dropDownButton.color = hifi.colors.blueHighlight; - } - onExited: { - dropDownButton.color = hifi.colors.white; - } - onClicked: { - textField.forceActiveFocus(); - root.primaryFilter_index = index; - dropdownContainer.visible = false; - } - } - } - } - } - - DropShadow { - anchors.fill: dropdownContainer; - horizontalOffset: 0; - verticalOffset: 4; - radius: 4.0; - samples: 9 - color: Qt.rgba(0, 0, 0, 0.25); - source: dropdownContainer; - visible: dropdownContainer.visible; - } - - function changeFilterByDisplayName(name) { - for (var i = 0; i < filterBarModel.count; i++) { - if (filterBarModel.get(i).displayName === name) { - root.primaryFilter_index = i; - return; - } - } - - console.log("Passed displayName not found in filterBarModel! primaryFilter unchanged."); - } +FilterBar { } diff --git a/interface/resources/qml/controls-uit/GlyphButton.qml b/interface/resources/qml/controls-uit/GlyphButton.qml index 9129486720..3a0f8631a8 100644 --- a/interface/resources/qml/controls-uit/GlyphButton.qml +++ b/interface/resources/qml/controls-uit/GlyphButton.qml @@ -1,91 +1,4 @@ -// -// GlyphButton.qml -// -// Created by Clement on 3/7/16 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.7 -import QtQuick.Controls 2.2 as Original -import TabletScriptingInterface 1.0 - -import "../styles-uit" - -Original.Button { - id: control - property int color: 0 - property int colorScheme: hifi.colorSchemes.light - property string glyph: "" - property int size: 32 - - width: 120 - height: 28 - - onHoveredChanged: { - if (hovered) { - Tablet.playSound(TabletEnums.ButtonHover); - } - } - - onFocusChanged: { - if (focus) { - Tablet.playSound(TabletEnums.ButtonHover); - } - } - - onClicked: { - Tablet.playSound(TabletEnums.ButtonClick); - } - - background: Rectangle { - radius: hifi.buttons.radius - - gradient: Gradient { - GradientStop { - position: 0.2 - color: { - if (!control.enabled) { - hifi.buttons.disabledColorStart[control.colorScheme] - } else if (control.pressed) { - hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { - hifi.buttons.hoveredColor[control.color] - } else if (!control.hovered && control.focus) { - hifi.buttons.focusedColor[control.color] - } else { - hifi.buttons.colorStart[control.color] - } - } - } - GradientStop { - position: 1.0 - color: { - if (!control.enabled) { - hifi.buttons.disabledColorFinish[control.colorScheme] - } else if (control.pressed) { - hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { - hifi.buttons.hoveredColor[control.color] - } else if (!control.hovered && control.focus) { - hifi.buttons.focusedColor[control.color] - } else { - hifi.buttons.colorFinish[control.color] - } - } - } - } - } - - contentItem: HiFiGlyphs { - color: enabled ? hifi.buttons.textColor[control.color] - : hifi.buttons.disabledTextColor[control.colorScheme] - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: control.glyph - size: control.size - } +GlyphButton { } - diff --git a/interface/resources/qml/controls-uit/HorizontalRule.qml b/interface/resources/qml/controls-uit/HorizontalRule.qml index 0609cc451d..7fc2269649 100644 --- a/interface/resources/qml/controls-uit/HorizontalRule.qml +++ b/interface/resources/qml/controls-uit/HorizontalRule.qml @@ -1,18 +1,4 @@ -// -// HorizontalRule.qml -// -// Created by Clement on 7/18/16 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 - -Rectangle { - anchors.left: parent.left - anchors.right: parent.right - height: 1 - color: hifi.colors.lightGray +HorizontalRule { } diff --git a/interface/resources/qml/controls-uit/HorizontalSpacer.qml b/interface/resources/qml/controls-uit/HorizontalSpacer.qml index 545154ab44..b4f372545c 100644 --- a/interface/resources/qml/controls-uit/HorizontalSpacer.qml +++ b/interface/resources/qml/controls-uit/HorizontalSpacer.qml @@ -1,21 +1,4 @@ -// -// HorizontalSpacer.qml -// -// Created by Clement on 7/18/16 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 - -import "../styles-uit" - -Item { - id: root - property alias size: root.width - - width: hifi.dimensions.controlInterlineHeight - height: 1 // Must be non-zero +HorizontalSpacer { } diff --git a/interface/resources/qml/controls-uit/ImageMessageBox.qml b/interface/resources/qml/controls-uit/ImageMessageBox.qml index 74313f7ffe..484a17dd7c 100644 --- a/interface/resources/qml/controls-uit/ImageMessageBox.qml +++ b/interface/resources/qml/controls-uit/ImageMessageBox.qml @@ -1,63 +1,4 @@ -// -// ImageMessageBox.qml -// -// Created by Dante Ruiz on 7/5/2017 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import "../styles-uit" - -Item { - id: imageBox - visible: false - anchors.fill: parent - property alias source: image.source - property alias imageWidth: image.width - property alias imageHeight: image.height - - Rectangle { - anchors.fill: parent - color: "black" - opacity: 0.3 - } - - Image { - id: image - anchors.centerIn: parent - - HiFiGlyphs { - id: closeGlyphButton - text: hifi.glyphs.close - size: 25 - - anchors { - top: parent.top - topMargin: 15 - right: parent.right - rightMargin: 15 - } - - MouseArea { - anchors.fill: parent - hoverEnabled: true - - onEntered: { - parent.text = hifi.glyphs.closeInverted; - } - - onExited: { - parent.text = hifi.glyphs.close; - } - - onClicked: { - imageBox.visible = false; - } - } - } - } +import controlsUit 1.0 +ImageMessageBox { } diff --git a/interface/resources/qml/controls-uit/Key.qml b/interface/resources/qml/controls-uit/Key.qml index dd77fc92dc..c031c2f660 100644 --- a/interface/resources/qml/controls-uit/Key.qml +++ b/interface/resources/qml/controls-uit/Key.qml @@ -1,185 +1,4 @@ -import QtQuick 2.0 -import TabletScriptingInterface 1.0 +import controlsUit 1.0 -Item { - id: keyItem - width: 45 - height: 50 - - property int contentPadding: 4 - property string glyph: "a" - property bool toggle: false // does this button have the toggle behaivor? - property bool toggled: false // is this button currently toggled? - property alias mouseArea: mouseArea1 - property alias fontFamily: letter.font.family; - property alias fontPixelSize: letter.font.pixelSize - property alias verticalAlignment: letter.verticalAlignment - property alias letterAnchors: letter.anchors - - function resetToggledMode(mode) { - toggled = mode; - if (toggled) { - state = "mouseDepressed"; - } else { - state = ""; - } - } - - MouseArea { - id: mouseArea1 - width: 36 - anchors.fill: parent - hoverEnabled: true - - onCanceled: { - if (toggled) { - keyItem.state = "mouseDepressed"; - } else { - keyItem.state = ""; - } - } - - onContainsMouseChanged: { - if (containsMouse) { - Tablet.playSound(TabletEnums.ButtonHover); - } - } - - onDoubleClicked: { - mouse.accepted = true; - } - - property var _HAPTIC_STRENGTH: 0.1; - property var _HAPTIC_DURATION: 3.0; - property var leftHand: 0; - property var rightHand: 1; - - onEntered: { - keyItem.state = "mouseOver"; - - var globalPosition = keyItem.mapToGlobal(mouseArea1.mouseX, mouseArea1.mouseY); - var pointerID = Web3DOverlay.deviceIdByTouchPoint(globalPosition.x, globalPosition.y); - - if (Pointers.isLeftHand(pointerID)) { - Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, leftHand); - } else if (Pointers.isRightHand(pointerID)) { - Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, rightHand); - } - } - - onExited: { - if (toggled) { - keyItem.state = "mouseDepressed"; - } else { - keyItem.state = ""; - } - } - - onPressed: { - keyItem.state = "mouseClicked"; - mouse.accepted = true; - } - - onReleased: { - if (containsMouse) { - Tablet.playSound(TabletEnums.ButtonClick); - - webEntity.synthesizeKeyPress(glyph); - webEntity.synthesizeKeyPress(glyph, mirrorText); - - if (toggle) { - toggled = !toggled; - } - keyItem.state = "mouseOver"; - } else { - if (toggled) { - keyItem.state = "mouseDepressed"; - } else { - keyItem.state = ""; - } - } - mouse.accepted = true; - } - } - - Rectangle { - id: roundedRect - width: 30 - color: "#121212" - radius: 2 - border.color: "#00000000" - anchors.fill: parent - anchors.margins: contentPadding - } - - Text { - id: letter - y: 6 - width: 50 - color: "#ffffff" - text: glyph - style: Text.Normal - font.family: "Tahoma" - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.bottom: parent.bottom - anchors.bottomMargin: 0 - anchors.top: parent.top - anchors.topMargin: 8 - horizontalAlignment: Text.AlignHCenter - font.pixelSize: 28 - } - - states: [ - State { - name: "mouseOver" - - PropertyChanges { - target: roundedRect - color: "#121212" - radius: 3 - border.width: 2 - border.color: "#00b4ef" - } - - PropertyChanges { - target: letter - color: "#00b4ef" - style: Text.Normal - } - }, - State { - name: "mouseClicked" - PropertyChanges { - target: roundedRect - color: "#1080b8" - border.width: 2 - border.color: "#00b4ef" - } - - PropertyChanges { - target: letter - color: "#121212" - styleColor: "#00000000" - style: Text.Normal - } - }, - State { - name: "mouseDepressed" - PropertyChanges { - target: roundedRect - color: "#0578b1" - border.width: 0 - } - - PropertyChanges { - target: letter - color: "#121212" - styleColor: "#00000000" - style: Text.Normal - } - } - ] +Key { } diff --git a/interface/resources/qml/controls-uit/Keyboard.qml b/interface/resources/qml/controls-uit/Keyboard.qml index 9d4fd33022..2bdf682b9a 100644 --- a/interface/resources/qml/controls-uit/Keyboard.qml +++ b/interface/resources/qml/controls-uit/Keyboard.qml @@ -1,354 +1,4 @@ -// -// FileDialog.qml -// -// Created by Anthony Thibault on 31 Oct 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.7 -import QtGraphicalEffects 1.0 -import "." - -Rectangle { - id: keyboardBase - objectName: "keyboard" - - anchors.left: parent.left - anchors.right: parent.right - - color: "#252525" - - property bool raised: false - property bool numeric: false - - readonly property int keyboardRowHeight: 50 - readonly property int keyboardWidth: 480 - readonly property int keyboardHeight: 200 - - readonly property int mirrorTextHeight: keyboardRowHeight - - property bool password: false - property alias mirroredText: mirrorText.text - property bool showMirrorText: true - - readonly property int raisedHeight: keyboardHeight + (showMirrorText ? keyboardRowHeight : 0) - - height: enabled && raised ? raisedHeight : 0 - visible: enabled && raised - - property bool shiftMode: false - property bool numericShiftMode: false - - onRaisedChanged: { - mirroredText = ""; - } - - function resetShiftMode(mode) { - shiftMode = mode; - shiftKey.resetToggledMode(mode); - } - - function toUpper(str) { - if (str === ",") { - return "<"; - } else if (str === ".") { - return ">"; - } else if (str === "/") { - return "?"; - } else if (str === "-") { - return "_"; - } else { - return str.toUpperCase(str); - } - } - - function toLower(str) { - if (str === "<") { - return ","; - } else if (str === ">") { - return "."; - } else if (str === "?") { - return "/"; - } else if (str === "_") { - return "-"; - } else { - return str.toLowerCase(str); - } - } - - function forEachKey(func) { - var i, j; - for (i = 0; i < columnAlpha.children.length; i++) { - var row = columnAlpha.children[i]; - for (j = 0; j < row.children.length; j++) { - var key = row.children[j]; - func(key); - } - } - } - - onShiftModeChanged: { - forEachKey(function (key) { - if (/[a-z-_]/i.test(key.glyph)) { - if (shiftMode) { - key.glyph = keyboardBase.toUpper(key.glyph); - } else { - key.glyph = keyboardBase.toLower(key.glyph); - } - } - }); - } - - function alphaKeyClickedHandler(mouseArea) { - // reset shift mode to false after first keypress - if (shiftMode) { - resetShiftMode(false); - } - } - - Component.onCompleted: { - // hook up callbacks to every ascii key - forEachKey(function (key) { - if (/^[a-z]+$/i.test(key.glyph)) { - key.mouseArea.onClicked.connect(alphaKeyClickedHandler); - } - }); - } - - Rectangle { - height: showMirrorText ? mirrorTextHeight : 0 - width: keyboardWidth - color: "#252525" - anchors.horizontalCenter: parent.horizontalCenter - - TextInput { - id: mirrorText - visible: showMirrorText - font.family: "Fira Sans" - font.pixelSize: 20 - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - color: "#00B4EF"; - anchors.left: parent.left - anchors.leftMargin: 10 - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - - wrapMode: Text.WordWrap - readOnly: false // we need this to allow control to accept QKeyEvent - selectByMouse: false - echoMode: password ? TextInput.Password : TextInput.Normal - - Keys.onPressed: { - if (event.key == Qt.Key_Return || event.key == Qt.Key_Space) { - mirrorText.text = ""; - event.accepted = true; - } - } - - MouseArea { // ... and we need this mouse area to prevent mirrorText from getting mouse events to ensure it will never get focus - anchors.fill: parent - } - } - } - - Rectangle { - id: keyboardRect - y: showMirrorText ? mirrorTextHeight : 0 - width: keyboardWidth - height: keyboardHeight - color: "#252525" - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: 0 - - Column { - id: columnAlpha - width: keyboardWidth - height: keyboardHeight - visible: !numeric - - Row { - width: keyboardWidth - height: keyboardRowHeight - anchors.left: parent.left - anchors.leftMargin: 4 - - Key { width: 43; glyph: "q"; } - Key { width: 43; glyph: "w"; } - Key { width: 43; glyph: "e"; } - Key { width: 43; glyph: "r"; } - Key { width: 43; glyph: "t"; } - Key { width: 43; glyph: "y"; } - Key { width: 43; glyph: "u"; } - Key { width: 43; glyph: "i"; } - Key { width: 43; glyph: "o"; } - Key { width: 43; glyph: "p"; } - Key { width: 43; glyph: "←"; } - } - - Row { - width: keyboardWidth - height: keyboardRowHeight - anchors.left: parent.left - anchors.leftMargin: 20 - - Key { width: 43; glyph: "a"; } - Key { width: 43; glyph: "s"; } - Key { width: 43; glyph: "d"; } - Key { width: 43; glyph: "f"; } - Key { width: 43; glyph: "g"; } - Key { width: 43; glyph: "h"; } - Key { width: 43; glyph: "j"; } - Key { width: 43; glyph: "k"; } - Key { width: 43; glyph: "l"; } - Key { width: 70; glyph: "⏎"; } - } - - Row { - width: keyboardWidth - height: keyboardRowHeight - anchors.left: parent.left - anchors.leftMargin: 4 - - Key { - id: shiftKey - width: 43 - glyph: "⇪" - toggle: true - onToggledChanged: shiftMode = toggled - } - Key { width: 43; glyph: "z"; } - Key { width: 43; glyph: "x"; } - Key { width: 43; glyph: "c"; } - Key { width: 43; glyph: "v"; } - Key { width: 43; glyph: "b"; } - Key { width: 43; glyph: "n"; } - Key { width: 43; glyph: "m"; } - Key { width: 43; glyph: "-"; } - Key { width: 43; glyph: "/"; } - Key { width: 43; glyph: "?"; } - } - - Row { - width: keyboardWidth - height: keyboardRowHeight - anchors.left: parent.left - anchors.leftMargin: 4 - - Key { - width: 70 - glyph: "123" - mouseArea.onClicked: keyboardBase.parent.punctuationMode = true - } - Key { width: 231; glyph: " "; } - Key { width: 43; glyph: ","; } - Key { width: 43; glyph: "."; } - Key { - fontFamily: "hifi-glyphs"; - fontPixelSize: 48; - letterAnchors.topMargin: -4; - verticalAlignment: Text.AlignVCenter; - width: 86; glyph: "\ue02b"; - } - } - } - - Column { - id: columnNumeric - width: keyboardWidth - height: keyboardHeight - visible: numeric - - Row { - width: keyboardWidth - height: keyboardRowHeight - anchors.left: parent.left - anchors.leftMargin: 4 - - Key { width: 43; glyph: "1"; } - Key { width: 43; glyph: "2"; } - Key { width: 43; glyph: "3"; } - Key { width: 43; glyph: "4"; } - Key { width: 43; glyph: "5"; } - Key { width: 43; glyph: "6"; } - Key { width: 43; glyph: "7"; } - Key { width: 43; glyph: "8"; } - Key { width: 43; glyph: "9"; } - Key { width: 43; glyph: "0"; } - Key { width: 43; glyph: "←"; } - } - - Row { - width: keyboardWidth - height: keyboardRowHeight - anchors.left: parent.left - anchors.leftMargin: 4 - - Key { width: 43; glyph: "!"; } - Key { width: 43; glyph: "@"; } - Key { width: 43; glyph: "#"; } - Key { width: 43; glyph: "$"; } - Key { width: 43; glyph: "%"; } - Key { width: 43; glyph: "^"; } - Key { width: 43; glyph: "&"; } - Key { width: 43; glyph: "*"; } - Key { width: 43; glyph: "("; } - Key { width: 43; glyph: ")"; } - Key { width: 43; glyph: "⏎"; } - } - - Row { - width: keyboardWidth - height: keyboardRowHeight - anchors.left: parent.left - anchors.leftMargin: 4 - - Key { - id: numericShiftKey - width: 43 - glyph: "\u21E8" - toggle: true - onToggledChanged: numericShiftMode = toggled - } - Key { width: 43; glyph: numericShiftMode ? "`" : "+"; } - Key { width: 43; glyph: numericShiftMode ? "~" : "-"; } - Key { width: 43; glyph: numericShiftMode ? "\u00A3" : "="; } - Key { width: 43; glyph: numericShiftMode ? "\u20AC" : ";"; } - Key { width: 43; glyph: numericShiftMode ? "\u00A5" : ":"; } - Key { width: 43; glyph: numericShiftMode ? "<" : "'"; } - Key { width: 43; glyph: numericShiftMode ? ">" : "\""; } - Key { width: 43; glyph: numericShiftMode ? "[" : "{"; } - Key { width: 43; glyph: numericShiftMode ? "]" : "}"; } - Key { width: 43; glyph: numericShiftMode ? "\\" : "|"; } - } - - Row { - width: keyboardWidth - height: keyboardRowHeight - anchors.left: parent.left - anchors.leftMargin: 4 - - Key { - width: 70 - glyph: "abc" - mouseArea.onClicked: keyboardBase.parent.punctuationMode = false - } - Key { width: 231; glyph: " "; } - Key { width: 43; glyph: ","; } - Key { width: 43; glyph: "."; } - Key { - fontFamily: "hifi-glyphs"; - fontPixelSize: 48; - letterAnchors.topMargin: -4; - verticalAlignment: Text.AlignVCenter; - width: 86; glyph: "\ue02b"; - } - } - } - } +Keyboard { } diff --git a/interface/resources/qml/controls-uit/Label.qml b/interface/resources/qml/controls-uit/Label.qml index 4c7051b495..032367208b 100644 --- a/interface/resources/qml/controls-uit/Label.qml +++ b/interface/resources/qml/controls-uit/Label.qml @@ -1,35 +1,4 @@ -// -// Label.qml -// -// Created by David Rowe on 26 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.7 - -import "../styles-uit" - -RalewaySemiBold { - HifiConstants { id: hifi } - property int colorScheme: hifi.colorSchemes.light - - size: hifi.fontSizes.inputLabel - color: { - if (colorScheme === hifi.colorSchemes.dark) { - if (enabled) { - hifi.colors.lightGrayText - } else { - hifi.colors.baseGrayHighlight - } - } else { - if (enabled) { - hifi.colors.lightGray - } else { - hifi.colors.lightGrayText - } - } - } +Label { } diff --git a/interface/resources/qml/controls-uit/QueuedButton.qml b/interface/resources/qml/controls-uit/QueuedButton.qml index 6612d582df..54d366a663 100644 --- a/interface/resources/qml/controls-uit/QueuedButton.qml +++ b/interface/resources/qml/controls-uit/QueuedButton.qml @@ -1,41 +1,4 @@ -// -// QueuedButton.qml -// -- original Button.qml + signal timer workaround --ht -// Created by David Rowe on 16 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 - -import "../styles-uit" -import "." as HifiControls - -HifiControls.Button { - // FIXME: THIS WORKAROUND MIGRATED/CONSOLIDATED FROM RUNNINGSCRIPTS.QML - - // For some reason trigginer an API that enters - // an internal event loop directly from the button clicked - // trigger below causes the appliction to behave oddly. - // Most likely because the button onClicked handling is never - // completed until the function returns. - // FIXME find a better way of handling the input dialogs that - // doesn't trigger this. - - // NOTE: dialogs that need to use this workaround can connect via - // onQueuedClicked: ... - // instead of: - // onClicked: ... - - signal clickedQueued() - Timer { - id: fromTimer - interval: 5 - repeat: false - running: false - onTriggered: clickedQueued() - } - onClicked: fromTimer.running = true +QueuedButton { } diff --git a/interface/resources/qml/controls-uit/RadioButton.qml b/interface/resources/qml/controls-uit/RadioButton.qml index 56324c55d7..46eb76e7ce 100644 --- a/interface/resources/qml/controls-uit/RadioButton.qml +++ b/interface/resources/qml/controls-uit/RadioButton.qml @@ -1,93 +1,4 @@ -// -// RadioButton.qml -// -// Created by Cain Kilgore on 20th July 2017 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 -import QtQuick.Controls 2.2 as Original - -import "../styles-uit" -import "../controls-uit" as HifiControls - -import TabletScriptingInterface 1.0 - -Original.RadioButton { - id: radioButton - HifiConstants { id: hifi } - - hoverEnabled: true - - property int colorScheme: hifi.colorSchemes.light - readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light - - property real letterSpacing: 1 - property int fontSize: hifi.fontSizes.inputLabel - property int boxSize: defaultBoxSize - property real scaleFactor: boxSize / defaultBoxSize - - readonly property int defaultBoxSize: 14 - readonly property int boxRadius: 3 * scaleFactor - readonly property int checkSize: 10 * scaleFactor - readonly property int checkRadius: 2 * scaleFactor - readonly property int indicatorRadius: 7 * scaleFactor - - onClicked: { - Tablet.playSound(TabletEnums.ButtonClick); - } - - onHoveredChanged: { - if (hovered) { - Tablet.playSound(TabletEnums.ButtonHover); - } - } - - indicator: Rectangle { - id: box - width: boxSize - height: boxSize - radius: indicatorRadius - x: radioButton.leftPadding - y: parent.height / 2 - height / 2 - gradient: Gradient { - GradientStop { - position: 0.2 - color: pressed || hovered - ? (radioButton.isLightColorScheme ? hifi.colors.checkboxDarkStart : hifi.colors.checkboxLightStart) - : (radioButton.isLightColorScheme ? hifi.colors.checkboxLightStart : hifi.colors.checkboxDarkStart) - } - GradientStop { - position: 1.0 - color: pressed || hovered - ? (radioButton.isLightColorScheme ? hifi.colors.checkboxDarkFinish : hifi.colors.checkboxLightFinish) - : (radioButton.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) - } - } - - Rectangle { - id: check - width: checkSize - height: checkSize - radius: indicatorRadius - anchors.centerIn: parent - color: "#00B4EF" - border.width: 1 - border.color: "#36CDFF" - visible: checked && !pressed || !checked && pressed - } - } - - contentItem: RalewaySemiBold { - text: radioButton.text - size: radioButton.fontSize - font.letterSpacing: letterSpacing - color: isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - leftPadding: radioButton.indicator.width + radioButton.spacing - } +RadioButton { } diff --git a/interface/resources/qml/controls-uit/ScrollBar.qml b/interface/resources/qml/controls-uit/ScrollBar.qml index 125e84e585..1b21509819 100644 --- a/interface/resources/qml/controls-uit/ScrollBar.qml +++ b/interface/resources/qml/controls-uit/ScrollBar.qml @@ -1,41 +1,4 @@ -// -// ScrollBar.qml -// -// Created by Vlad Stelmahovsky on 27 Nov 2017 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.7 -import QtQuick.Controls 2.2 - -import "../styles-uit" +import controlsUit 1.0 ScrollBar { - visible: size < 1.0 - - HifiConstants { id: hifi } - property int colorScheme: hifi.colorSchemes.light - readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light - - background: Item { - implicitWidth: hifi.dimensions.scrollbarBackgroundWidth - Rectangle { - anchors { fill: parent; topMargin: 3; bottomMargin: 3 } - radius: hifi.dimensions.scrollbarHandleWidth/2 - color: isLightColorScheme ? hifi.colors.tableScrollBackgroundLight - : hifi.colors.tableScrollBackgroundDark - } - } - - contentItem: Item { - implicitWidth: hifi.dimensions.scrollbarHandleWidth - Rectangle { - anchors { fill: parent; topMargin: 1; bottomMargin: 1 } - radius: hifi.dimensions.scrollbarHandleWidth/2 - color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark - } - } } diff --git a/interface/resources/qml/controls-uit/Separator.qml b/interface/resources/qml/controls-uit/Separator.qml index 3350764ae9..baeefc7895 100644 --- a/interface/resources/qml/controls-uit/Separator.qml +++ b/interface/resources/qml/controls-uit/Separator.qml @@ -1,44 +1,4 @@ -// -// Separator.qml -// -// Created by Zach Fox on 2017-06-06 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 -import "../styles-uit" - -Item { - property int colorScheme: 0; - - readonly property var topColor: [ hifi.colors.baseGrayShadow, hifi.colors.faintGray, "#89858C" ]; - readonly property var bottomColor: [ hifi.colors.baseGrayHighlight, hifi.colors.faintGray, "#89858C" ]; - - // Size - height: colorScheme === 0 ? 2 : 1; - Rectangle { - // Size - width: parent.width; - height: 1; - // Anchors - anchors.left: parent.left; - anchors.bottom: parent.bottom; - // Style - color: topColor[colorScheme]; - } - Rectangle { - visible: colorScheme === 0; - // Size - width: parent.width; - height: 1; - // Anchors - anchors.left: parent.left; - anchors.bottom: parent.bottom; - anchors.bottomMargin: -height; - // Style - color: bottomColor[colorScheme]; - } +Separator { } diff --git a/interface/resources/qml/controls-uit/Slider.qml b/interface/resources/qml/controls-uit/Slider.qml index 2a5d4c137d..1bcdbc39b7 100644 --- a/interface/resources/qml/controls-uit/Slider.qml +++ b/interface/resources/qml/controls-uit/Slider.qml @@ -1,98 +1,4 @@ -// -// Slider.qml -// -// Created by David Rowe on 27 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.7 -import QtQuick.Controls 2.2 - -import "../styles-uit" -import "../controls-uit" as HifiControls +import controlsUit 1.0 Slider { - id: slider - - property int colorScheme: hifi.colorSchemes.light - readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light - property string label: "" - property real controlHeight: height + (sliderLabel.visible ? sliderLabel.height + sliderLabel.anchors.bottomMargin : 0) - - property alias minimumValue: slider.from - property alias maximumValue: slider.to - property alias step: slider.stepSize - property bool tickmarksEnabled: false - - height: hifi.fontSizes.textFieldInput + 14 // Match height of TextField control. - y: sliderLabel.visible ? sliderLabel.height + sliderLabel.anchors.bottomMargin : 0 - - background: Rectangle { - x: slider.leftPadding - y: slider.topPadding + slider.availableHeight / 2 - height / 2 - - implicitWidth: 50 - implicitHeight: hifi.dimensions.sliderGrooveHeight - width: slider.availableWidth - height: implicitHeight - radius: height / 2 - color: isLightColorScheme ? hifi.colors.sliderGutterLight : hifi.colors.sliderGutterDark - - Rectangle { - width: slider.visualPosition * parent.width - height: parent.height - radius: height / 2 - gradient: Gradient { - GradientStop { position: 0.0; color: hifi.colors.blueAccent } - GradientStop { position: 1.0; color: hifi.colors.primaryHighlight } - } - } - } - - handle: Rectangle { - x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width) - y: slider.topPadding + slider.availableHeight / 2 - height / 2 - implicitWidth: hifi.dimensions.sliderHandleSize - implicitHeight: hifi.dimensions.sliderHandleSize - radius: height / 2 - border.width: 1 - border.color: isLightColorScheme ? hifi.colors.sliderBorderLight : hifi.colors.sliderBorderDark - gradient: Gradient { - GradientStop { - position: 0.0 - color: pressed || hovered - ? (isLightColorScheme ? hifi.colors.sliderDarkStart : hifi.colors.sliderLightStart ) - : (isLightColorScheme ? hifi.colors.sliderLightStart : hifi.colors.sliderDarkStart ) - } - GradientStop { - position: 1.0 - color: pressed || hovered - ? (isLightColorScheme ? hifi.colors.sliderDarkFinish : hifi.colors.sliderLightFinish ) - : (isLightColorScheme ? hifi.colors.sliderLightFinish : hifi.colors.sliderDarkFinish ) - } - } - - Rectangle { - height: parent.height - 2 - width: height - radius: height / 2 - anchors.centerIn: parent - color: hifi.colors.transparent - border.width: 1 - border.color: hifi.colors.black - } - } - - HifiControls.Label { - id: sliderLabel - text: slider.label - colorScheme: slider.colorScheme - anchors.left: parent.left - anchors.bottom: parent.top - anchors.bottomMargin: 2 - visible: label != "" - } -} +} \ No newline at end of file diff --git a/interface/resources/qml/controls-uit/SpinBox.qml b/interface/resources/qml/controls-uit/SpinBox.qml index 3d3ea7a75e..c202a3997c 100644 --- a/interface/resources/qml/controls-uit/SpinBox.qml +++ b/interface/resources/qml/controls-uit/SpinBox.qml @@ -1,185 +1,4 @@ -// -// SpinBox.qml -// -// Created by David Rowe on 26 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.7 -import QtQuick.Controls 2.2 - -import "../styles-uit" -import "../controls-uit" as HifiControls +import controlsUit 1.0 SpinBox { - id: spinBox - - HifiConstants { - id: hifi - } - - inputMethodHints: Qt.ImhFormattedNumbersOnly - property int colorScheme: hifi.colorSchemes.light - readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light - property string label: "" - property string suffix: "" - property string labelInside: "" - property color colorLabelInside: hifi.colors.white - property color backgroundColor: isLightColorScheme - ? (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGray) - : (spinBox.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow) - property real controlHeight: height + (spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0) - property int decimals: 2; - property real factor: Math.pow(10, decimals) - - property real minimumValue: 0.0 - property real maximumValue: 0.0 - - property real realValue: 0.0 - property real realFrom: minimumValue - property real realTo: maximumValue - property real realStepSize: 1.0 - - signal editingFinished() - - implicitHeight: height - implicitWidth: width - editable: true - - padding: 0 - leftPadding: 0 - rightPadding: padding + (up.indicator ? up.indicator.width : 0) - topPadding: 0 - bottomPadding: 0 - - locale: Qt.locale("en_US") - - onValueModified: realValue = value/factor - onValueChanged: realValue = value/factor - onRealValueChanged: { - var newValue = Math.round(realValue*factor); - if(value != newValue) { - value = newValue; - } - } - - stepSize: realStepSize*factor - to : realTo*factor - from : realFrom*factor - - font.family: "Fira Sans SemiBold" - font.pixelSize: hifi.fontSizes.textFieldInput - height: hifi.fontSizes.textFieldInput + 13 // Match height of TextField control. - - y: spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0 - - background: Rectangle { - color: backgroundColor - border.color: spinBoxLabelInside.visible ? spinBoxLabelInside.color : hifi.colors.primaryHighlight - border.width: spinBox.activeFocus ? spinBoxLabelInside.visible ? 2 : 1 : 0 - } - - validator: DoubleValidator { - bottom: Math.min(spinBox.from, spinBox.to) - top: Math.max(spinBox.from, spinBox.to) - } - - textFromValue: function(value, locale) { - return parseFloat(value/factor).toFixed(decimals); - } - - valueFromText: function(text, locale) { - return Number.fromLocaleString(locale, text)*factor; - } - - - contentItem: TextInput { - z: 2 - color: isLightColorScheme - ? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray) - : (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText) - selectedTextColor: hifi.colors.black - selectionColor: hifi.colors.primaryHighlight - text: spinBox.textFromValue(spinBox.value, spinBox.locale) + suffix - inputMethodHints: spinBox.inputMethodHints - validator: spinBox.validator - verticalAlignment: Qt.AlignVCenter - leftPadding: spinBoxLabelInside.visible ? 30 : hifi.dimensions.textPadding - //rightPadding: hifi.dimensions.spinnerSize - width: spinBox.width - hifi.dimensions.spinnerSize - onEditingFinished: spinBox.editingFinished() - } - - up.indicator: Item { - x: spinBox.width - implicitWidth - 5 - y: 1 - clip: true - implicitHeight: spinBox.implicitHeight/2 - implicitWidth: spinBox.implicitHeight/2 - HiFiGlyphs { - anchors.centerIn: parent - text: hifi.glyphs.caratUp - size: hifi.dimensions.spinnerSize - color: spinBox.up.pressed || spinBox.up.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray - } - } - up.onPressedChanged: { - if(value) { - spinBox.forceActiveFocus(); - } - } - - down.indicator: Item { - x: spinBox.width - implicitWidth - 5 - y: spinBox.implicitHeight/2 - clip: true - implicitHeight: spinBox.implicitHeight/2 - implicitWidth: spinBox.implicitHeight/2 - HiFiGlyphs { - anchors.centerIn: parent - text: hifi.glyphs.caratDn - size: hifi.dimensions.spinnerSize - color: spinBox.down.pressed || spinBox.down.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray - } - } - down.onPressedChanged: { - if(value) { - spinBox.forceActiveFocus(); - } - } - - HifiControls.Label { - id: spinBoxLabel - text: spinBox.label - colorScheme: spinBox.colorScheme - anchors.left: parent.left - anchors.bottom: parent.top - anchors.bottomMargin: 4 - visible: label != "" - } - - HifiControls.Label { - id: spinBoxLabelInside - text: spinBox.labelInside - anchors.left: parent.left - anchors.leftMargin: 10 - font.bold: true - anchors.verticalCenter: parent.verticalCenter - color: spinBox.colorLabelInside - visible: spinBox.labelInside != "" - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton - onWheel: { - if (wheel.angleDelta.y > 0) - value += stepSize - else - value -= stepSize - } - } } diff --git a/interface/resources/qml/controls-uit/Switch.qml b/interface/resources/qml/controls-uit/Switch.qml index bfe86b1420..96ff7f8898 100644 --- a/interface/resources/qml/controls-uit/Switch.qml +++ b/interface/resources/qml/controls-uit/Switch.qml @@ -1,160 +1,4 @@ -// -// Switch.qml -// -// Created by Zach Fox on 2017-06-06 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.7 -import QtQuick.Controls 2.2 as Original - -import "../styles-uit" - -Item { - id: rootSwitch; - - property int colorScheme: hifi.colorSchemes.light; - readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light; - property int switchWidth: 70; - readonly property int switchRadius: height/2; - property string labelTextOff: ""; - property string labelGlyphOffText: ""; - property int labelGlyphOffSize: 32; - property string labelTextOn: ""; - property string labelGlyphOnText: ""; - property int labelGlyphOnSize: 32; - property alias checked: originalSwitch.checked; - signal onCheckedChanged; - signal clicked; - - Original.Switch { - id: originalSwitch; - focusPolicy: Qt.ClickFocus - anchors.top: rootSwitch.top; - anchors.left: rootSwitch.left; - anchors.leftMargin: rootSwitch.width/2 - rootSwitch.switchWidth/2; - onCheckedChanged: rootSwitch.onCheckedChanged(); - onClicked: rootSwitch.clicked(); - hoverEnabled: true - - topPadding: 3; - leftPadding: 3; - rightPadding: 3; - bottomPadding: 3; - - onHoveredChanged: { - if (hovered) { - switchHandle.color = hifi.colors.blueHighlight; - } else { - switchHandle.color = hifi.colors.lightGray; - } - } - - background: Rectangle { - color: "#252525"; - implicitWidth: rootSwitch.switchWidth; - implicitHeight: rootSwitch.height; - radius: rootSwitch.switchRadius; - } - - indicator: Rectangle { - id: switchHandle; - implicitWidth: rootSwitch.height - originalSwitch.topPadding - originalSwitch.bottomPadding; - implicitHeight: implicitWidth; - radius: implicitWidth/2; - border.color: hifi.colors.lightGrayText; - color: hifi.colors.lightGray; - //x: originalSwitch.leftPadding - x: Math.max(0, Math.min(parent.width - width, originalSwitch.visualPosition * parent.width - (width / 2))) - y: parent.height / 2 - height / 2 - Behavior on x { - enabled: !originalSwitch.down - SmoothedAnimation { velocity: 200 } - } - - } - } - - // OFF Label - Item { - anchors.right: originalSwitch.left; - anchors.rightMargin: 10; - anchors.top: rootSwitch.top; - height: rootSwitch.height; - - RalewaySemiBold { - id: labelOff; - text: labelTextOff; - size: hifi.fontSizes.inputLabel; - color: originalSwitch.checked ? hifi.colors.lightGrayText : "#FFFFFF"; - anchors.top: parent.top; - anchors.right: parent.right; - width: paintedWidth; - height: parent.height; - verticalAlignment: Text.AlignVCenter; - } - - HiFiGlyphs { - id: labelGlyphOff; - text: labelGlyphOffText; - size: labelGlyphOffSize; - color: labelOff.color; - anchors.top: parent.top; - anchors.topMargin: 2; - anchors.right: labelOff.left; - anchors.rightMargin: 4; - } - - MouseArea { - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: labelGlyphOff.left; - anchors.right: labelOff.right; - onClicked: { - originalSwitch.checked = false; - } - } - } - - // ON Label - Item { - anchors.left: originalSwitch.right; - anchors.leftMargin: 10; - anchors.top: rootSwitch.top; - height: rootSwitch.height; - - RalewaySemiBold { - id: labelOn; - text: labelTextOn; - size: hifi.fontSizes.inputLabel; - color: originalSwitch.checked ? "#FFFFFF" : hifi.colors.lightGrayText; - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - height: parent.height; - verticalAlignment: Text.AlignVCenter; - } - - HiFiGlyphs { - id: labelGlyphOn; - text: labelGlyphOnText; - size: labelGlyphOnSize; - color: labelOn.color; - anchors.top: parent.top; - anchors.left: labelOn.right; - } - - MouseArea { - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: labelOn.left; - anchors.right: labelGlyphOn.right; - onClicked: { - originalSwitch.checked = true; - } - } - } +Switch { } diff --git a/interface/resources/qml/controls-uit/Table.qml b/interface/resources/qml/controls-uit/Table.qml index ce4e1c376a..3a4932302f 100644 --- a/interface/resources/qml/controls-uit/Table.qml +++ b/interface/resources/qml/controls-uit/Table.qml @@ -1,165 +1,4 @@ -// -// Table.qml -// -// Created by David Rowe on 18 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Controls 2.3 as QQC2 - -import "../styles-uit" - -TableView { - id: tableView - - property int colorScheme: hifi.colorSchemes.light - readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light - property bool expandSelectedRow: false - property bool centerHeaderText: false - readonly property real headerSpacing: 3 //spacing between sort indicator and table header title - property var titlePaintedPos: [] // storing extra data position behind painted - // title text and sort indicatorin table's header - signal titlePaintedPosSignal(int column) //signal that extradata position gets changed - - model: ListModel { } - - Component.onCompleted: { - if (flickableItem !== null && flickableItem !== undefined) { - tableView.flickableItem.QQC2.ScrollBar.vertical = scrollbar - } - } - - QQC2.ScrollBar { - id: scrollbar - parent: tableView.flickableItem - policy: QQC2.ScrollBar.AsNeeded - orientation: Qt.Vertical - visible: size < 1.0 - topPadding: tableView.headerVisible ? hifi.dimensions.tableHeaderHeight + 1 : 1 - anchors.top: tableView.top - anchors.left: tableView.right - anchors.bottom: tableView.bottom - - background: Item { - implicitWidth: hifi.dimensions.scrollbarBackgroundWidth - Rectangle { - anchors { - fill: parent; - topMargin: tableView.headerVisible ? hifi.dimensions.tableHeaderHeight : 0 - } - color: isLightColorScheme ? hifi.colors.tableScrollBackgroundLight - : hifi.colors.tableScrollBackgroundDark - } - } - - contentItem: Item { - implicitWidth: hifi.dimensions.scrollbarHandleWidth - Rectangle { - anchors.fill: parent - radius: (width - 4)/2 - color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark - } - } - } - - headerVisible: false - headerDelegate: Rectangle { - height: hifi.dimensions.tableHeaderHeight - color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark - - - RalewayRegular { - id: titleText - x: centerHeaderText ? (parent.width - paintedWidth - - ((sortIndicatorVisible && - sortIndicatorColumn === styleData.column) ? - (titleSort.paintedWidth / 5 + tableView.headerSpacing) : 0)) / 2 : - hifi.dimensions.tablePadding - text: styleData.value - size: hifi.fontSizes.tableHeading - font.capitalization: Font.AllUppercase - color: hifi.colors.baseGrayHighlight - horizontalAlignment: (centerHeaderText ? Text.AlignHCenter : Text.AlignLeft) - anchors.verticalCenter: parent.verticalCenter - } - - //actual image of sort indicator in glyph font only 20% of real font size - //i.e. if the charachter size set to 60 pixels, actual image is 12 pixels - HiFiGlyphs { - id: titleSort - text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn - color: hifi.colors.darkGray - opacity: 0.6; - size: hifi.fontSizes.tableHeadingIcon - anchors.verticalCenter: titleText.verticalCenter - anchors.left: titleText.right - anchors.leftMargin: -(hifi.fontSizes.tableHeadingIcon / 2.5) + tableView.headerSpacing - visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column - onXChanged: { - titlePaintedPos[styleData.column] = titleText.x + titleText.paintedWidth + - paintedWidth / 5 + tableView.headerSpacing*2 - titlePaintedPosSignal(styleData.column) - } - } - - Rectangle { - width: 1 - anchors { - left: parent.left - top: parent.top - topMargin: 1 - bottom: parent.bottom - bottomMargin: 2 - } - color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight - visible: styleData.column > 0 - } - - Rectangle { - height: 1 - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - } - color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight - } - } - - // Use rectangle to draw border with rounded corners. - frameVisible: false - Rectangle { - color: "#00000000" - anchors { fill: parent; margins: -2 } - border.color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight - border.width: 2 - } - anchors.margins: 2 // Shrink TableView to lie within border. - - backgroundVisible: true - - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff - - style: TableViewStyle { - // Needed in order for rows to keep displaying rows after end of table entries. - backgroundColor: tableView.isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark - alternateBackgroundColor: tableView.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd - padding.top: headerVisible ? hifi.dimensions.tableHeaderHeight: 0 - } - - rowDelegate: Rectangle { - height: (styleData.selected && expandSelectedRow ? 1.8 : 1) * hifi.dimensions.tableRowHeight - color: styleData.selected - ? hifi.colors.primaryHighlight - : tableView.isLightColorScheme - ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) - : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd) - } +Table { } diff --git a/interface/resources/qml/controls-uit/TabletContentSection.qml b/interface/resources/qml/controls-uit/TabletContentSection.qml index c34f4afdd6..70075d3858 100644 --- a/interface/resources/qml/controls-uit/TabletContentSection.qml +++ b/interface/resources/qml/controls-uit/TabletContentSection.qml @@ -1,138 +1,4 @@ -// -// ContentSection.qml -// -// Created by Dante Ruiz on 13 Feb 2017 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 -import QtGraphicalEffects 1.0 - -import "../styles-uit" - -Column { - property string name: "Content Section" - property bool isFirst: false - property bool isCollapsible: false // Set at creation. - property bool isCollapsed: false - - spacing: 0 // Defer spacing decisions to individual controls. - - anchors { - left: parent.left - leftMargin: hifi.dimensions.contentMargin.x - right: parent.right - rightMargin: hifi.dimensions.contentMargin.x - } - - function toggleCollapsed() { - if (isCollapsible) { - isCollapsed = !isCollapsed; - for (var i = 1; i < children.length; i++) { - children[i].visible = !isCollapsed; - } - } - } - - Item { - id: sectionName - anchors.left: parent.left - anchors.right: parent.right - height: leadingSpace.height + topBar.height + heading.height + bottomBar.height - - Item { - id: leadingSpace - width: 1 - height: isFirst ? 7 : 0 - anchors.top: parent.top - } - - Item { - id: topBar - visible: !isFirst - height: visible ? 2 : 0 - anchors.top: leadingSpace.bottom - - Rectangle { - id: shadow - width: 480 - height: 1 - color: hifi.colors.baseGrayShadow - x: -hifi.dimensions.contentMargin.x - } - - Rectangle { - width: 480 - height: 1 - color: hifi.colors.baseGrayHighlight - x: -hifi.dimensions.contentMargin.x - anchors.top: shadow.bottom - } - } - - Item { - id: heading - anchors { - left: parent.left - right: parent.right - top: topBar.bottom - } - height: isCollapsible ? 36 : 28 - - RalewayRegular { - id: title - anchors { - left: parent.left - top: parent.top - topMargin: 12 - } - size: hifi.fontSizes.sectionName - font.capitalization: Font.AllUppercase - text: name - color: hifi.colors.lightGrayText - } - - HiFiGlyphs { - anchors { - top: title.top - topMargin: -9 - right: parent.right - rightMargin: -4 - } - size: hifi.fontSizes.disclosureButton - text: isCollapsed ? hifi.glyphs.disclosureButtonExpand : hifi.glyphs.disclosureButtonCollapse - color: hifi.colors.lightGrayText - visible: isCollapsible - } - - MouseArea { - // Events are propogated so that any active control is defocused. - anchors.fill: parent - propagateComposedEvents: true - onPressed: { - toggleCollapsed(); - mouse.accepted = false; - } - } - } - - LinearGradient { - id: bottomBar - visible: false - width: 480 - height: visible ? 4 : 0 - x: -hifi.dimensions.contentMargin.x - anchors.top: heading.bottom - start: Qt.point(0, 0) - end: Qt.point(0, 4) - gradient: Gradient { - GradientStop { position: 0.0; color: hifi.colors.darkGray } - GradientStop { position: 1.0; color: hifi.colors.baseGray } // Equivalent of darkGray0 over baseGray background. - } - cached: true - } - } +TabletContentSection { } diff --git a/interface/resources/qml/controls-uit/TabletHeader.qml b/interface/resources/qml/controls-uit/TabletHeader.qml index 56203de286..53e660d708 100644 --- a/interface/resources/qml/controls-uit/TabletHeader.qml +++ b/interface/resources/qml/controls-uit/TabletHeader.qml @@ -1,34 +1,4 @@ -// -// TabletHeader.qml -// -// Created by David Rowe on 11 Mar 2017. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 - -import "../styles-uit" - -Rectangle { - - property string title: "" - - HifiConstants { id: hifi } - - height: hifi.dimensions.tabletMenuHeader - z: 100 - - color: hifi.colors.darkGray - - RalewayBold { - text: title - size: 26 - color: hifi.colors.white - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: hifi.dimensions.contentMargin.x - } +TabletHeader { } diff --git a/interface/resources/qml/controls-uit/TextAction.qml b/interface/resources/qml/controls-uit/TextAction.qml index 1745a6c273..ce5a973776 100644 --- a/interface/resources/qml/controls-uit/TextAction.qml +++ b/interface/resources/qml/controls-uit/TextAction.qml @@ -1,65 +1,4 @@ -// -// TextField.qml -// -// Created by David Rowe on 21 Apr 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 - -import "../styles-uit" -import "../controls-uit" as HifiControls - -Item { - property string icon: "" - property int iconSize: 30 - property string text: "" - - property int colorScheme: hifi.colorSchemes.light - readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light - - signal clicked() - - height: Math.max(glyph.visible ? glyph.height - 4 : 0, string.visible ? string.height : 0) - width: glyph.width + string.anchors.leftMargin + string.width - - HiFiGlyphs { - id: glyph - anchors.left: parent.left - anchors.top: parent.top - anchors.topMargin: -2 - text: parent.icon - size: parent.iconSize - color: isLightColorScheme - ? (mouseArea.containsMouse ? hifi.colors.baseGrayHighlight : hifi.colors.lightGray) - : (mouseArea.containsMouse ? hifi.colors.faintGray : hifi.colors.lightGrayText) - visible: text !== "" - width: visible ? implicitWidth : 0 - } - - RalewaySemiBold { - id: string - anchors { - left: glyph.visible ? glyph.right : parent.left - leftMargin: visible && glyph.visible ? hifi.dimensions.contentSpacing.x : 0 - verticalCenter: glyph.visible ? glyph.verticalCenter : undefined - } - text: parent.text - size: hifi.fontSizes.inputLabel - color: isLightColorScheme - ? (mouseArea.containsMouse ? hifi.colors.baseGrayHighlight : hifi.colors.lightGray) - : (mouseArea.containsMouse ? hifi.colors.faintGray : hifi.colors.lightGrayText) - font.underline: true; - visible: text !== "" - } - - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - onClicked: parent.clicked() - } +TextAction { } diff --git a/interface/resources/qml/controls-uit/TextEdit.qml b/interface/resources/qml/controls-uit/TextEdit.qml index a72a3b13d8..783950bd1e 100644 --- a/interface/resources/qml/controls-uit/TextEdit.qml +++ b/interface/resources/qml/controls-uit/TextEdit.qml @@ -1,23 +1,4 @@ -// -// TextEdit.qml -// -// Created by Bradley Austin Davis on 24 Apr 2015 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import "../styles-uit" +import controlsUit 1.0 TextEdit { - - property real size: 32 - - font.family: "Raleway" - font.weight: Font.DemiBold - font.pointSize: size - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft } diff --git a/interface/resources/qml/controls-uit/TextField.qml b/interface/resources/qml/controls-uit/TextField.qml index 917068ac01..261a4162ef 100644 --- a/interface/resources/qml/controls-uit/TextField.qml +++ b/interface/resources/qml/controls-uit/TextField.qml @@ -1,180 +1,4 @@ -// -// TextField.qml -// -// Created by David Rowe on 17 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 - -import "../styles-uit" -import "../controls-uit" as HifiControls +import controlsUit 1.0 TextField { - id: textField - - property int colorScheme: hifi.colorSchemes.light - readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light - readonly property bool isFaintGrayColorScheme: colorScheme == hifi.colorSchemes.faintGray - property bool isSearchField: false - property string label: "" - property real controlHeight: height + (textFieldLabel.visible ? textFieldLabel.height + 1 : 0) - property bool hasDefocusedBorder: true; - property bool hasRoundedBorder: false - property int roundedBorderRadius: 4 - property bool error: false; - property bool hasClearButton: false; - property string leftPermanentGlyph: ""; - property string centerPlaceholderGlyph: ""; - - placeholderText: textField.placeholderText - - font.family: "Fira Sans" - font.pixelSize: hifi.fontSizes.textFieldInput - height: implicitHeight + 3 // Make surrounding box higher so that highlight is vertically centered. - property alias textFieldLabel: textFieldLabel - - y: textFieldLabel.visible ? textFieldLabel.height + textFieldLabel.anchors.bottomMargin : 0 - - // workaround for https://bugreports.qt.io/browse/QTBUG-49297 - Keys.onPressed: { - switch (event.key) { - case Qt.Key_Return: - case Qt.Key_Enter: - event.accepted = true; - - // emit accepted signal manually - if (acceptableInput) { - accepted(); - } - } - } - - style: TextFieldStyle { - id: style; - textColor: { - if (isLightColorScheme) { - if (textField.activeFocus) { - hifi.colors.black - } else { - hifi.colors.lightGray - } - } else if (isFaintGrayColorScheme) { - if (textField.activeFocus) { - hifi.colors.black - } else { - hifi.colors.lightGray - } - } else { - if (textField.activeFocus) { - hifi.colors.white - } else { - hifi.colors.lightGrayText - } - } - } - background: Rectangle { - color: { - if (isLightColorScheme) { - if (textField.activeFocus) { - hifi.colors.white - } else { - hifi.colors.textFieldLightBackground - } - } else if (isFaintGrayColorScheme) { - if (textField.activeFocus) { - hifi.colors.white - } else { - hifi.colors.faintGray50 - } - } else { - if (textField.activeFocus) { - hifi.colors.black - } else { - hifi.colors.baseGrayShadow - } - } - } - border.color: textField.error ? hifi.colors.redHighlight : - (textField.activeFocus ? hifi.colors.primaryHighlight : (hasDefocusedBorder ? (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray) : color)) - border.width: textField.activeFocus || hasRoundedBorder || textField.error ? 1 : 0 - radius: isSearchField ? textField.height / 2 : (hasRoundedBorder ? roundedBorderRadius : 0) - - HiFiGlyphs { - text: textField.leftPermanentGlyph; - color: textColor; - size: hifi.fontSizes.textFieldSearchIcon; - anchors.left: parent.left; - anchors.verticalCenter: parent.verticalCenter; - anchors.leftMargin: hifi.dimensions.textPadding - 2; - visible: text; - } - - HiFiGlyphs { - text: textField.centerPlaceholderGlyph; - color: textColor; - size: parent.height; - anchors.horizontalCenter: parent.horizontalCenter; - anchors.verticalCenter: parent.verticalCenter; - visible: text && !textField.focus && textField.text === ""; - } - - HiFiGlyphs { - text: hifi.glyphs.search - color: textColor - size: hifi.fontSizes.textFieldSearchIcon - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: hifi.dimensions.textPadding - 2 - visible: isSearchField - } - - HiFiGlyphs { - text: hifi.glyphs.error - color: textColor - size: 40 - anchors.right: parent.right - anchors.rightMargin: hifi.dimensions.textPadding - 2 - anchors.verticalCenter: parent.verticalCenter - visible: hasClearButton && textField.text !== ""; - - MouseArea { - anchors.fill: parent; - onClicked: { - textField.text = ""; - } - } - } - } - placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray - selectedTextColor: hifi.colors.black - selectionColor: hifi.colors.primaryHighlight - padding.left: hasRoundedBorder ? textField.height / 2 : ((isSearchField || textField.leftPermanentGlyph !== "") ? textField.height - 2 : 0) + hifi.dimensions.textPadding - padding.right: (hasClearButton ? textField.height - 2 : 0) + hifi.dimensions.textPadding - } - - HifiControls.Label { - id: textFieldLabel - text: textField.label - colorScheme: textField.colorScheme - anchors.left: parent.left - - Binding on anchors.right { - when: textField.right - value: textField.right - } - Binding on wrapMode { - when: textField.right - value: Text.WordWrap - } - - anchors.bottom: parent.top - anchors.bottomMargin: 3 - visible: label != "" - } } diff --git a/interface/resources/qml/controls-uit/ToolTip.qml b/interface/resources/qml/controls-uit/ToolTip.qml index 4fe36adcd5..a5f24c7974 100644 --- a/interface/resources/qml/controls-uit/ToolTip.qml +++ b/interface/resources/qml/controls-uit/ToolTip.qml @@ -1,51 +1,4 @@ -// -// ToolTip.qml -// -// Created by Clement on 9/12/17 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 - -Item { - property string toolTip - property bool showToolTip: false - - Rectangle { - id: toolTipRectangle - anchors.right: parent.right - - width: toolTipText.width + 4 - height: toolTipText.height + 4 - opacity: (toolTip != "" && showToolTip) ? 1 : 0 - color: "#ffffaa" - border.color: "#0a0a0a" - Text { - id: toolTipText - text: toolTip - color: "black" - anchors.centerIn: parent - } - Behavior on opacity { - PropertyAnimation { - easing.type: Easing.InOutQuad - duration: 250 - } - } - } - MouseArea { - id: mouseArea - anchors.fill: parent - onEntered: showTimer.start() - onExited: { showToolTip = false; showTimer.stop(); } - hoverEnabled: true - } - Timer { - id: showTimer - interval: 250 - onTriggered: { showToolTip = true; } - } -} \ No newline at end of file +ToolTip { +} diff --git a/interface/resources/qml/controls-uit/Tree.qml b/interface/resources/qml/controls-uit/Tree.qml index 5199a10a27..a21cdc6d47 100644 --- a/interface/resources/qml/controls-uit/Tree.qml +++ b/interface/resources/qml/controls-uit/Tree.qml @@ -1,205 +1,4 @@ -// -// Tree.qml -// -// Created by David Rowe on 17 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQml.Models 2.2 -import QtQuick 2.7 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Controls 2.2 as QQC2 - - -import "../styles-uit" - -TreeView { - id: treeView - - property var treeModel: ListModel { } - property bool centerHeaderText: false - property int colorScheme: hifi.colorSchemes.light - readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light - - property var modifyEl: function(index, data) { return false; } - - model: treeModel - selection: ItemSelectionModel { - id: selectionModel - model: treeModel - } - - anchors { left: parent.left; right: parent.right } - - headerVisible: false - - Component.onCompleted: { - if (flickableItem !== null && flickableItem !== undefined) { - treeView.flickableItem.QQC2.ScrollBar.vertical = scrollbar - } - } - - QQC2.ScrollBar { - id: scrollbar - parent: treeView.flickableItem - policy: QQC2.ScrollBar.AsNeeded - orientation: Qt.Vertical - visible: size < 1.0 - topPadding: treeView.headerVisible ? hifi.dimensions.tableHeaderHeight + 1 : 1 - anchors.top: treeView.top - anchors.left: treeView.right - anchors.bottom: treeView.bottom - - background: Item { - implicitWidth: hifi.dimensions.scrollbarBackgroundWidth - Rectangle { - anchors { - fill: parent; - topMargin: treeView.headerVisible ? hifi.dimensions.tableHeaderHeight: 0 - } - color: isLightColorScheme ? hifi.colors.tableScrollBackgroundLight - : hifi.colors.tableScrollBackgroundDark - } - } - - contentItem: Item { - implicitWidth: hifi.dimensions.scrollbarHandleWidth - Rectangle { - anchors.fill: parent - radius: (width - 4)/2 - color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark - } - } - } - - // Use rectangle to draw border with rounded corners. - frameVisible: false - Rectangle { - color: "#00000000" - anchors.fill: parent - radius: hifi.dimensions.borderRadius - border.color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight - border.width: 2 - anchors.margins: -2 - } - anchors.margins: 2 // Shrink TreeView to lie within border. - - backgroundVisible: true - - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff - - style: TreeViewStyle { - // Needed in order for rows to keep displaying rows after end of table entries. - backgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightEven : hifi.colors.tableRowDarkEven - alternateBackgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd - - headerDelegate: Rectangle { - height: hifi.dimensions.tableHeaderHeight - color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark - - RalewayRegular { - id: titleText - text: styleData.value - size: hifi.fontSizes.tableHeading - font.capitalization: Font.AllUppercase - color: hifi.colors.baseGrayHighlight - horizontalAlignment: (centerHeaderText ? Text.AlignHCenter : Text.AlignLeft) - elide: Text.ElideRight - anchors { - left: parent.left - leftMargin: hifi.dimensions.tablePadding - right: sortIndicatorVisible && sortIndicatorColumn === styleData.column ? titleSort.left : parent.right - rightMargin: hifi.dimensions.tablePadding - verticalCenter: parent.verticalCenter - } - } - - HiFiGlyphs { - id: titleSort - text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn - color: isLightColorScheme ? hifi.colors.darkGray : hifi.colors.baseGrayHighlight - opacity: 0.6; - size: hifi.fontSizes.tableHeadingIcon - anchors { - right: parent.right - verticalCenter: titleText.verticalCenter - } - visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column - } - - Rectangle { - width: 1 - anchors { - left: parent.left - top: parent.top - topMargin: 1 - bottom: parent.bottom - bottomMargin: 2 - } - color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight - visible: styleData.column > 0 - } - - Rectangle { - height: 1 - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - } - color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight - } - } - - branchDelegate: HiFiGlyphs { - text: styleData.isExpanded ? hifi.glyphs.caratDn : hifi.glyphs.caratR - size: hifi.fontSizes.carat - color: colorScheme == hifi.colorSchemes.light - ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) - : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) - anchors { - left: parent ? parent.left : undefined - leftMargin: hifi.dimensions.tablePadding / 2 - } - } - } - - rowDelegate: Rectangle { - height: hifi.dimensions.tableRowHeight - color: styleData.selected - ? hifi.colors.primaryHighlight - : treeView.isLightColorScheme - ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) - : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd) - } - - itemDelegate: FiraSansSemiBold { - anchors { - left: parent ? parent.left : undefined - leftMargin: (2 + styleData.depth) * hifi.dimensions.tablePadding - right: parent ? parent.right : undefined - rightMargin: hifi.dimensions.tablePadding - verticalCenter: parent ? parent.verticalCenter : undefined - } - - text: styleData.value - size: hifi.fontSizes.tableText - color: colorScheme == hifi.colorSchemes.light - ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) - : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) - - elide: Text.ElideRight - } - - Item { - id: unfocusHelper - visible: false - } - - onDoubleClicked: isExpanded(index) ? collapse(index) : expand(index) +Tree { } diff --git a/interface/resources/qml/controls-uit/VerticalSpacer.qml b/interface/resources/qml/controls-uit/VerticalSpacer.qml index 2df65f1002..42b48b003e 100644 --- a/interface/resources/qml/controls-uit/VerticalSpacer.qml +++ b/interface/resources/qml/controls-uit/VerticalSpacer.qml @@ -1,21 +1,4 @@ -// -// VerticalSpacer.qml -// -// Created by David Rowe on 16 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 - -import "../styles-uit" - -Item { - id: root - property alias size: root.height - - width: 1 // Must be non-zero - height: hifi.dimensions.controlInterlineHeight +VerticalSpacer { } diff --git a/interface/resources/qml/controls-uit/WebGlyphButton.qml b/interface/resources/qml/controls-uit/WebGlyphButton.qml index fd7cd001b2..1377b937a2 100644 --- a/interface/resources/qml/controls-uit/WebGlyphButton.qml +++ b/interface/resources/qml/controls-uit/WebGlyphButton.qml @@ -1,40 +1,4 @@ -// -// GlyphButton.qml -// -// Created by Vlad Stelmahovsky on 2017-06-21 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 -import QtQuick.Controls 2.2 as Original - -import "../styles-uit" - -Original.Button { - id: control - - property int colorScheme: hifi.colorSchemes.light - property string glyph: "" - property int size: 32 - //colors - readonly property color normalColor: "#AFAFAF" - readonly property color hoverColor: "#00B4EF" - readonly property color clickedColor: "#FFFFFF" - readonly property color disabledColor: "#575757" - - background: Item {} - - contentItem: HiFiGlyphs { - color: control.enabled ? (control.pressed ? control.clickedColor : - (control.hovered ? control.hoverColor : control.normalColor)) : - control.disabledColor - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: control.glyph - size: control.size - } +WebGlyphButton { } - diff --git a/interface/resources/qml/controls-uit/WebSpinner.qml b/interface/resources/qml/controls-uit/WebSpinner.qml index e8e01c4865..a20597d8dc 100644 --- a/interface/resources/qml/controls-uit/WebSpinner.qml +++ b/interface/resources/qml/controls-uit/WebSpinner.qml @@ -1,24 +1,4 @@ -// -// WebSpinner.qml -// -// Created by David Rowe on 23 May 2017 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 -import QtWebEngine 1.5 - -AnimatedImage { - property WebEngineView webview: parent - source: "../../icons/loader-snake-64-w.gif" - visible: webview.loading && /^(http.*|)$/i.test(webview.url.toString()) - playing: visible - z: 10000 - anchors { - horizontalCenter: parent.horizontalCenter - verticalCenter: parent.verticalCenter - } +WebSpinner { } diff --git a/interface/resources/qml/controls-uit/WebView.qml b/interface/resources/qml/controls-uit/WebView.qml index 2895f36944..b11dee7f3b 100644 --- a/interface/resources/qml/controls-uit/WebView.qml +++ b/interface/resources/qml/controls-uit/WebView.qml @@ -1,21 +1,4 @@ -// -// WebView.qml -// -// Created by Bradley Austin Davis on 12 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import controlsUit 1.0 -import QtQuick 2.5 -import "." - -BaseWebView { - onNewViewRequested: { - // Load dialog via OffscreenUi so that JavaScript EventBridge is available. - var browser = OffscreenUi.load("Browser.qml"); - request.openIn(browser.webView); - browser.webView.forceActiveFocus(); - } +WebView { } diff --git a/interface/resources/qml/controls-uit/readme.txt b/interface/resources/qml/controls-uit/readme.txt new file mode 100644 index 0000000000..8aa3714ff9 --- /dev/null +++ b/interface/resources/qml/controls-uit/readme.txt @@ -0,0 +1 @@ +this folder exists purely for compatibility reasons and might be deleted in future! please consider using 'import controlsUit 1.0' instead of including this folder \ No newline at end of file diff --git a/interface/resources/qml/controls/Button.qml b/interface/resources/qml/controls/Button.qml index 6cbdec5644..b677822c0e 100644 --- a/interface/resources/qml/controls/Button.qml +++ b/interface/resources/qml/controls/Button.qml @@ -3,7 +3,6 @@ import QtQuick.Controls 2.2 as Original import "." import "../styles" -import "../controls-uit" Original.Button { id: control diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index 943f15e1de..cce32c137a 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -4,7 +4,7 @@ import QtWebChannel 1.0 import QtQuick.Controls 2.2 -import "../styles-uit" as StylesUIt +import stylesUit 1.0 as StylesUIt Item { id: flick diff --git a/interface/resources/qml/controls/TabletWebButton.qml b/interface/resources/qml/controls/TabletWebButton.qml index d016f71f2d..140461d817 100644 --- a/interface/resources/qml/controls/TabletWebButton.qml +++ b/interface/resources/qml/controls/TabletWebButton.qml @@ -10,7 +10,7 @@ import Hifi 1.0 import QtQuick 2.4 -import "../styles-uit" +import stylesUit 1.0 Rectangle { property alias text: label.text diff --git a/interface/resources/qml/controls/TabletWebScreen.qml b/interface/resources/qml/controls/TabletWebScreen.qml index bb037ad478..be11f16498 100644 --- a/interface/resources/qml/controls/TabletWebScreen.qml +++ b/interface/resources/qml/controls/TabletWebScreen.qml @@ -1,5 +1,5 @@ import QtQuick 2.7 -import "../controls-uit" as HiFiControls +import controlsUit 1.0 as HiFiControls Item { id: root diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index db695dbfb2..94f4c7978c 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -1,8 +1,8 @@ import QtQuick 2.7 import QtWebEngine 1.5 -import "../controls-uit" as HiFiControls +import controlsUit 1.0 as HiFiControls import "../styles" as HifiStyles -import "../styles-uit" +import stylesUit 1.0 Item { id: root @@ -195,6 +195,10 @@ Item { keyboardEnabled = HMD.active; } + Component.onDestruction: { + keyboardRaised = false; + } + Keys.onPressed: { switch(event.key) { case Qt.Key_L: diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 71bf69fdc8..375bcd50e0 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -1,5 +1,5 @@ import QtQuick 2.7 -import "../controls-uit" as HiFiControls +import controlsUit 1.0 as HiFiControls Item { width: parent !== null ? parent.width : undefined diff --git a/interface/resources/qml/controls-uit/+android/ImageButton.qml b/interface/resources/qml/controlsUit/+android/ImageButton.qml similarity index 96% rename from interface/resources/qml/controls-uit/+android/ImageButton.qml rename to interface/resources/qml/controlsUit/+android/ImageButton.qml index 5ebf7cd3e9..88eaf95d76 100644 --- a/interface/resources/qml/controls-uit/+android/ImageButton.qml +++ b/interface/resources/qml/controlsUit/+android/ImageButton.qml @@ -1,6 +1,6 @@ // // ImageButton.qml -// interface/resources/qml/controls-uit +// interface/resources/qml/controlsUit // // Created by Gabriel Calero & Cristian Duarte on 12 Oct 2017 // Copyright 2017 High Fidelity, Inc. @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtQuick.Layouts 1.3 -import "../styles-uit" as HifiStyles +import "../stylesUit" as HifiStyles Item { id: button @@ -79,4 +79,4 @@ Item { } } ] -} \ No newline at end of file +} diff --git a/interface/resources/qml/controlsUit/AttachmentsTable.qml b/interface/resources/qml/controlsUit/AttachmentsTable.qml new file mode 100644 index 0000000000..a2677962da --- /dev/null +++ b/interface/resources/qml/controlsUit/AttachmentsTable.qml @@ -0,0 +1,170 @@ +// +// AttachmentsTable.qml +// +// Created by David Rowe on 18 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.XmlListModel 2.0 + +import "../stylesUit" +import "." as HifiControls +import "../windows" +import "../hifi/models" + +TableView { + id: tableView + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + + model: S3Model{} + + Rectangle { + anchors.fill: parent + visible: tableView.model.status !== XmlListModel.Ready + color: hifi.colors.darkGray0 + BusyIndicator { + anchors.centerIn: parent + width: 48; height: 48 + running: true + } + } + + headerDelegate: Rectangle { + height: hifi.dimensions.tableHeaderHeight + color: hifi.colors.darkGray + border.width: 0.5 + border.color: hifi.colors.baseGrayHighlight + + RalewayRegular { + id: textHeader + size: hifi.fontSizes.tableHeading + color: hifi.colors.lightGrayText + text: styleData.value + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + } + } + + // Use rectangle to draw border with rounded corners. + Rectangle { + color: "#00000000" + anchors { fill: parent; margins: -2 } + radius: hifi.dimensions.borderRadius + border.color: hifi.colors.baseGrayHighlight + border.width: 3 + } + anchors.margins: 2 // Shrink TableView to lie within border. + backgroundVisible: true + + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + verticalScrollBarPolicy: Qt.ScrollBarAsNeeded + + style: TableViewStyle { + // Needed in order for rows to keep displaying rows after end of table entries. + backgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightEven : hifi.colors.tableRowDarkEven + alternateBackgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd + + handle: Item { + id: scrollbarHandle + implicitWidth: 6 + Rectangle { + anchors { + fill: parent + leftMargin: 2 // Move it right + rightMargin: -2 // "" + topMargin: 3 // Shrink vertically + bottomMargin: 3 // "" + } + radius: 3 + color: hifi.colors.tableScrollHandleDark + } + } + + scrollBarBackground: Item { + implicitWidth: 10 + Rectangle { + anchors { + fill: parent + margins: -1 // Expand + } + color: hifi.colors.baseGrayHighlight + } + + Rectangle { + anchors { + fill: parent + margins: 1 // Shrink + } + radius: 4 + color: hifi.colors.tableScrollBackgroundDark + } + } + + incrementControl: Item { + visible: false + } + + decrementControl: Item { + visible: false + } + } + + rowDelegate: Rectangle { + height: hifi.dimensions.tableRowHeight + color: styleData.selected + ? hifi.colors.primaryHighlight + : tableView.isLightColorScheme + ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) + : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd) + } + + itemDelegate: Item { + anchors { + left: parent ? parent.left : undefined + leftMargin: hifi.dimensions.tablePadding + right: parent ? parent.right : undefined + rightMargin: hifi.dimensions.tablePadding + } + FiraSansSemiBold { + id: textItem + text: styleData.value + size: hifi.fontSizes.tableText + color: colorScheme == hifi.colorSchemes.light + ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) + : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + } + } + + TableViewColumn { + role: "name" + title: "NAME" + width: parent.width *0.3 + horizontalAlignment: Text.AlignHCenter + } + TableViewColumn { + role: "size" + title: "SIZE" + width: parent.width *0.2 + horizontalAlignment: Text.AlignHCenter + } + TableViewColumn { + role: "modified" + title: "LAST MODIFIED" + width: parent.width *0.5 + horizontalAlignment: Text.AlignHCenter + } +} diff --git a/interface/resources/qml/controlsUit/BaseWebView.qml b/interface/resources/qml/controlsUit/BaseWebView.qml new file mode 100644 index 0000000000..fdd9c12220 --- /dev/null +++ b/interface/resources/qml/controlsUit/BaseWebView.qml @@ -0,0 +1,38 @@ +// +// WebView.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtWebEngine 1.5 + +WebEngineView { + id: root + + Component.onCompleted: { + console.log("Connecting JS messaging to Hifi Logging") + // Ensure the JS from the web-engine makes it to our logging + root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { + console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); + }); + } + + onLoadingChanged: { + // Required to support clicking on "hifi://" links + if (WebEngineView.LoadStartedStatus == loadRequest.status) { + var url = loadRequest.url.toString(); + if (urlHandler.canHandleUrl(url)) { + if (urlHandler.handleUrl(url)) { + root.stop(); + } + } + } + } + + WebSpinner { } +} diff --git a/interface/resources/qml/controlsUit/Button.qml b/interface/resources/qml/controlsUit/Button.qml new file mode 100644 index 0000000000..6ea7ce4b4c --- /dev/null +++ b/interface/resources/qml/controlsUit/Button.qml @@ -0,0 +1,122 @@ +// +// Button.qml +// +// Created by David Rowe on 16 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 2.3 as Original +import TabletScriptingInterface 1.0 + +import "../stylesUit" + +Original.Button { + id: control; + + property int color: 0 + property int colorScheme: hifi.colorSchemes.light + property int fontSize: hifi.fontSizes.buttonLabel + property int radius: hifi.buttons.radius + property alias implicitTextWidth: buttonText.implicitWidth + property string buttonGlyph: ""; + property int fontCapitalization: Font.AllUppercase + + width: hifi.dimensions.buttonWidth + height: hifi.dimensions.controlLineHeight + + HifiConstants { id: hifi } + + onHoveredChanged: { + if (hovered) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + onFocusChanged: { + if (focus) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + } + + background: Rectangle { + radius: control.radius + + border.width: (control.color === hifi.buttons.none || + (control.color === hifi.buttons.noneBorderless && control.hovered) || + (control.color === hifi.buttons.noneBorderlessWhite && control.hovered) || + (control.color === hifi.buttons.noneBorderlessGray && control.hovered)) ? 1 : 0; + border.color: control.color === hifi.buttons.noneBorderless ? hifi.colors.blueHighlight : + (control.color === hifi.buttons.noneBorderlessGray ? hifi.colors.baseGray : hifi.colors.white); + + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorStart[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] + } else { + hifi.buttons.colorStart[control.color] + } + } + } + GradientStop { + position: 1.0 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorFinish[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] + } else { + hifi.buttons.colorFinish[control.color] + } + } + } + } + } + + contentItem: Item { + HiFiGlyphs { + id: buttonGlyph; + visible: control.buttonGlyph !== ""; + text: control.buttonGlyph === "" ? hifi.glyphs.question : control.buttonGlyph; + // Size + size: 34; + // Anchors + anchors.right: buttonText.left; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + // Style + color: enabled ? hifi.buttons.textColor[control.color] + : hifi.buttons.disabledTextColor[control.colorScheme]; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + RalewayBold { + id: buttonText; + anchors.centerIn: parent; + font.capitalization: control.fontCapitalization + color: enabled ? hifi.buttons.textColor[control.color] + : hifi.buttons.disabledTextColor[control.colorScheme] + size: control.fontSize + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: control.text + } + } +} + diff --git a/interface/resources/qml/controlsUit/CheckBox.qml b/interface/resources/qml/controlsUit/CheckBox.qml new file mode 100644 index 0000000000..abf08908fb --- /dev/null +++ b/interface/resources/qml/controlsUit/CheckBox.qml @@ -0,0 +1,121 @@ +// +// CheckBox.qml +// +// Created by David Rowe on 26 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.2 +import QtQuick.Controls 2.2 as Original + +import "../stylesUit" + +import TabletScriptingInterface 1.0 + +Original.CheckBox { + id: checkBox + + property int colorScheme: hifi.colorSchemes.light + property string color: hifi.colors.lightGrayText + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + property bool isRedCheck: false + property int boxSize: 14 + property int boxRadius: 3 + property bool wrap: true; + readonly property int checkSize: Math.max(boxSize - 8, 10) + readonly property int checkRadius: 2 + property string labelFontFamily: "Raleway" + property int labelFontSize: 14; + property int labelFontWeight: Font.DemiBold; + focusPolicy: Qt.ClickFocus + hoverEnabled: true + + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + } + + onHoveredChanged: { + if (hovered) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + + indicator: Rectangle { + id: box + implicitWidth: boxSize + implicitHeight: boxSize + radius: boxRadius + y: parent.height / 2 - height / 2 + border.width: 1 + border.color: pressed || hovered + ? hifi.colors.checkboxCheckedBorder + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) + + gradient: Gradient { + GradientStop { + position: 0.2 + color: pressed || hovered + ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightStart) + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightStart : hifi.colors.checkboxDarkStart) + } + GradientStop { + position: 1.0 + color: pressed || hovered + ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightFinish) + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) + } + } + + Rectangle { + visible: pressed || hovered + anchors.centerIn: parent + id: innerBox + width: checkSize - 4 + height: width + radius: checkRadius + color: hifi.colors.checkboxCheckedBorder + } + + Rectangle { + id: check + width: checkSize + height: checkSize + radius: checkRadius + anchors.centerIn: parent + color: isRedCheck ? hifi.colors.checkboxCheckedRed : hifi.colors.checkboxChecked + border.width: 2 + border.color: isRedCheck? hifi.colors.checkboxCheckedBorderRed : hifi.colors.checkboxCheckedBorder + visible: checked && !pressed || !checked && pressed + } + + Rectangle { + id: disabledOverlay + visible: !enabled + width: boxSize + height: boxSize + radius: boxRadius + border.width: 1 + border.color: hifi.colors.baseGrayHighlight + color: hifi.colors.baseGrayHighlight + opacity: 0.5 + } + } + + contentItem: Label { + text: checkBox.text + color: checkBox.color + font.family: checkBox.labelFontFamily; + font.pixelSize: checkBox.labelFontSize; + font.weight: checkBox.labelFontWeight; + x: 2 + verticalAlignment: Text.AlignVCenter + wrapMode: checkBox.wrap ? Text.Wrap : Text.NoWrap + elide: checkBox.wrap ? Text.ElideNone : Text.ElideRight + enabled: checkBox.enabled + leftPadding: checkBox.indicator.width + checkBox.spacing + } +} diff --git a/interface/resources/qml/controlsUit/CheckBoxQQC2.qml b/interface/resources/qml/controlsUit/CheckBoxQQC2.qml new file mode 100644 index 0000000000..91d35ecd58 --- /dev/null +++ b/interface/resources/qml/controlsUit/CheckBoxQQC2.qml @@ -0,0 +1,125 @@ +// +// CheckBox2.qml +// +// Created by Vlad Stelmahovsky on 10 Aug 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 + +import "../stylesUit" +import "." as HiFiControls +import TabletScriptingInterface 1.0 + +CheckBox { + id: checkBox + + HifiConstants { id: hifi; } + + padding: 0 + leftPadding: 0 + property int colorScheme: hifi.colorSchemes.light + property string color: hifi.colors.lightGrayText + readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light + property bool isRedCheck: false + property bool isRound: false + property int boxSize: 14 + property int boxRadius: isRound ? boxSize : 3 + property bool wrap: true; + readonly property int checkSize: Math.max(boxSize - 8, 10) + readonly property int checkRadius: isRound ? checkSize / 2 : 2 + focusPolicy: Qt.ClickFocus + hoverEnabled: true + + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + } + + onHoveredChanged: { + if (hovered) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + indicator: Rectangle { + id: box + implicitWidth: boxSize + implicitHeight: boxSize + radius: boxRadius + x: checkBox.leftPadding + y: parent.height / 2 - height / 2 + border.width: 1 + border.color: pressed || hovered + ? hifi.colors.checkboxCheckedBorder + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) + + gradient: Gradient { + GradientStop { + position: 0.2 + color: pressed || hovered + ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightStart) + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightStart : hifi.colors.checkboxDarkStart) + } + GradientStop { + position: 1.0 + color: pressed || hovered + ? (checkBox.isLightColorScheme ? hifi.colors.checkboxChecked : hifi.colors.checkboxLightFinish) + : (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) + } + } + + Rectangle { + visible: pressed || hovered + anchors.centerIn: parent + id: innerBox + width: checkSize - 4 + height: width + radius: checkRadius + color: hifi.colors.checkboxCheckedBorder + } + + Rectangle { + id: check + width: checkSize + height: checkSize + radius: checkRadius + anchors.centerIn: parent + color: isRedCheck ? hifi.colors.checkboxCheckedRed : hifi.colors.checkboxChecked + border.width: 2 + border.color: isRedCheck? hifi.colors.checkboxCheckedBorderRed : hifi.colors.checkboxCheckedBorder + visible: checked && !pressed || !checked && pressed + } + + Rectangle { + id: disabledOverlay + visible: !enabled + width: boxSize + height: boxSize + radius: boxRadius + border.width: 1 + border.color: hifi.colors.baseGrayHighlight + color: hifi.colors.baseGrayHighlight + opacity: 0.5 + } + } + + contentItem: Text { + id: root + font.pixelSize: hifi.fontSizes.inputLabel + font.family: "Raleway" + font.weight: Font.DemiBold + text: checkBox.text + color: checkBox.color + x: 2 + wrapMode: checkBox.wrap ? Text.Wrap : Text.NoWrap + elide: checkBox.wrap ? Text.ElideNone : Text.ElideRight + enabled: checkBox.enabled + verticalAlignment: Text.AlignVCenter + leftPadding: checkBox.indicator.width + checkBox.spacing + } +} + diff --git a/interface/resources/qml/controlsUit/ComboBox.qml b/interface/resources/qml/controlsUit/ComboBox.qml new file mode 100644 index 0000000000..8d1d7a5262 --- /dev/null +++ b/interface/resources/qml/controlsUit/ComboBox.qml @@ -0,0 +1,191 @@ +// +// ComboBox.qml +// +// Created by Bradley Austin David on 27 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 + +import "../stylesUit" +import "." as HifiControls + +FocusScope { + id: root + HifiConstants { id: hifi } + + property alias model: comboBox.model; + property alias editable: comboBox.editable + property alias comboBox: comboBox + readonly property alias currentText: comboBox.currentText; + property alias currentIndex: comboBox.currentIndex; + property int currentHighLightedIndex: comboBox.currentIndex; + + property int dropdownHeight: 480 + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + property string label: "" + property real controlHeight: height + (comboBoxLabel.visible ? comboBoxLabel.height + comboBoxLabel.anchors.bottomMargin : 0) + + readonly property ComboBox control: comboBox + + property bool isDesktop: true + + signal accepted(); + + implicitHeight: comboBox.height; + focus: true + + ComboBox { + id: comboBox + anchors.fill: parent + hoverEnabled: true + visible: true + height: hifi.fontSizes.textFieldInput + 13 // Match height of TextField control. + + function previousItem() { root.currentHighLightedIndex = (root.currentHighLightedIndex + comboBox.count - 1) % comboBox.count; } + function nextItem() { root.currentHighLightedIndex = (root.currentHighLightedIndex + comboBox.count + 1) % comboBox.count; } + function selectCurrentItem() { root.currentIndex = root.currentHighLightedIndex; close(); /*hideList();*/ } + function selectSpecificItem(index) { root.currentIndex = index; close();/*hideList();*/ } + + Keys.onUpPressed: previousItem(); + Keys.onDownPressed: nextItem(); + Keys.onSpacePressed: selectCurrentItem(); + Keys.onRightPressed: selectCurrentItem(); + Keys.onReturnPressed: selectCurrentItem(); + + background: Rectangle { + gradient: Gradient { + GradientStop { + position: 0.2 + color: comboBox.popup.visible + ? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark) + : (isLightColorScheme ? hifi.colors.dropDownLightStart : hifi.colors.dropDownDarkStart) + } + GradientStop { + position: 1.0 + color: comboBox.popup.visible + ? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark) + : (isLightColorScheme ? hifi.colors.dropDownLightFinish : hifi.colors.dropDownDarkFinish) + } + } + } + + indicator: Item { + id: dropIcon + anchors { right: parent.right; verticalCenter: parent.verticalCenter } + height: root.height + width: height + Rectangle { + width: 1 + height: parent.height + anchors.top: parent.top + anchors.left: parent.left + color: isLightColorScheme ? hifi.colors.faintGray : hifi.colors.baseGray + } + HiFiGlyphs { + anchors { top: parent.top; topMargin: -11; horizontalCenter: parent.horizontalCenter } + size: hifi.dimensions.spinnerSize + text: hifi.glyphs.caratDn + color: comboBox.hovered || comboBox.popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText) + } + } + + contentItem: FiraSansSemiBold { + id: textField + anchors { + left: parent.left + leftMargin: hifi.dimensions.textPadding + verticalCenter: parent.verticalCenter + } + size: hifi.fontSizes.textFieldInput + text: comboBox.displayText ? comboBox.displayText : comboBox.currentText + elide: Text.ElideRight + color: comboBox.hovered || comboBox.popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText ) + } + + delegate: ItemDelegate { + id: itemDelegate + hoverEnabled: true + width: root.width + 4 + height: popupText.implicitHeight * 1.4 + highlighted: root.currentHighLightedIndex == index + + onHoveredChanged: { + if (hovered) { + root.currentHighLightedIndex = index + } + } + + background: Rectangle { + color: itemDelegate.highlighted ? hifi.colors.primaryHighlight + : (isLightColorScheme ? hifi.colors.dropDownPressedLight + : hifi.colors.dropDownPressedDark) + } + + contentItem: FiraSansSemiBold { + id: popupText + anchors.left: parent.left + anchors.leftMargin: hifi.dimensions.textPadding + anchors.verticalCenter: parent.verticalCenter + text: comboBox.model[index] ? comboBox.model[index] + : (comboBox.model.get && comboBox.model.get(index).text ? + comboBox.model.get(index).text : "") + size: hifi.fontSizes.textFieldInput + color: hifi.colors.baseGray + } + } + popup: Popup { + y: comboBox.height - 1 + width: comboBox.width + implicitHeight: listView.contentHeight > dropdownHeight ? dropdownHeight + : listView.contentHeight + padding: 0 + topPadding: 1 + + onClosed: { + root.accepted() + } + + contentItem: ListView { + id: listView + clip: true + model: comboBox.popup.visible ? comboBox.delegateModel : null + currentIndex: root.currentHighLightedIndex + delegate: comboBox.delegate + ScrollBar.vertical: HifiControls.ScrollBar { + id: scrollbar + parent: listView + policy: ScrollBar.AsNeeded + visible: size < 1.0 + } + } + + background: Rectangle { + color: hifi.colors.baseGray + } + } + } + + function textAt(index) { + return comboBox.textAt(index); + } + + HifiControls.Label { + id: comboBoxLabel + text: root.label + colorScheme: root.colorScheme + anchors.left: parent.left + anchors.bottom: parent.top + anchors.bottomMargin: 4 + visible: label != "" + } + + Component.onCompleted: { + isDesktop = (typeof desktop !== "undefined"); + } +} diff --git a/interface/resources/qml/controlsUit/ContentSection.qml b/interface/resources/qml/controlsUit/ContentSection.qml new file mode 100644 index 0000000000..262c29220f --- /dev/null +++ b/interface/resources/qml/controlsUit/ContentSection.qml @@ -0,0 +1,138 @@ +// +// ContentSection.qml +// +// Created by David Rowe on 16 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "../stylesUit" + +Column { + property string name: "Content Section" + property bool isFirst: false + property bool isCollapsible: false // Set at creation. + property bool isCollapsed: false + + spacing: 0 // Defer spacing decisions to individual controls. + + anchors { + left: parent.left + leftMargin: hifi.dimensions.contentMargin.x + right: parent.right + rightMargin: hifi.dimensions.contentMargin.x + } + + function toggleCollapsed() { + if (isCollapsible) { + isCollapsed = !isCollapsed; + for (var i = 1; i < children.length; i++) { + children[i].visible = !isCollapsed; + } + } + } + + Item { + id: sectionName + anchors.left: parent.left + anchors.right: parent.right + height: leadingSpace.height + topBar.height + heading.height + bottomBar.height + + Item { + id: leadingSpace + width: 1 + height: isFirst ? 7 : 0 + anchors.top: parent.top + } + + Item { + id: topBar + visible: !isFirst + height: visible ? 2 : 0 + anchors.top: leadingSpace.bottom + + Rectangle { + id: shadow + width: frame.width + height: 1 + color: hifi.colors.baseGrayShadow + x: -hifi.dimensions.contentMargin.x + } + + Rectangle { + width: frame.width + height: 1 + color: hifi.colors.baseGrayHighlight + x: -hifi.dimensions.contentMargin.x + anchors.top: shadow.bottom + } + } + + Item { + id: heading + anchors { + left: parent.left + right: parent.right + top: topBar.bottom + } + height: isCollapsible ? 36 : 28 + + RalewayRegular { + id: title + anchors { + left: parent.left + top: parent.top + topMargin: 12 + } + size: hifi.fontSizes.sectionName + font.capitalization: Font.AllUppercase + text: name + color: hifi.colors.lightGrayText + } + + HiFiGlyphs { + anchors { + top: title.top + topMargin: -9 + right: parent.right + rightMargin: -4 + } + size: hifi.fontSizes.disclosureButton + text: isCollapsed ? hifi.glyphs.disclosureButtonExpand : hifi.glyphs.disclosureButtonCollapse + color: hifi.colors.lightGrayText + visible: isCollapsible + } + + MouseArea { + // Events are propogated so that any active control is defocused. + anchors.fill: parent + propagateComposedEvents: true + onPressed: { + toggleCollapsed(); + mouse.accepted = false; + } + } + } + + LinearGradient { + id: bottomBar + visible: desktop.gradientsSupported && isCollapsible + width: frame.width + height: visible ? 4 : 0 + x: -hifi.dimensions.contentMargin.x + anchors.top: heading.bottom + start: Qt.point(0, 0) + end: Qt.point(0, 4) + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.darkGray } + GradientStop { position: 1.0; color: hifi.colors.baseGray } // Equivalent of darkGray0 over baseGray background. + } + cached: true + } + } +} diff --git a/interface/resources/qml/controlsUit/FilterBar.qml b/interface/resources/qml/controlsUit/FilterBar.qml new file mode 100644 index 0000000000..0892018913 --- /dev/null +++ b/interface/resources/qml/controlsUit/FilterBar.qml @@ -0,0 +1,333 @@ +// +// FilterBar.qml +// +// Created by Zach Fox on 17 Feb 2018-03-12 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 + +import "../stylesUit" +import "." as HifiControls + +Item { + id: root; + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + readonly property bool isFaintGrayColorScheme: colorScheme == hifi.colorSchemes.faintGray + property bool error: false; + property alias textFieldHeight: textField.height; + property string placeholderText; + property alias dropdownHeight: dropdownContainer.height; + property alias text: textField.text; + property alias primaryFilterChoices: filterBarModel; + property int primaryFilter_index: -1; + property string primaryFilter_filterName: ""; + property string primaryFilter_displayName: ""; + signal accepted; + + onPrimaryFilter_indexChanged: { + if (primaryFilter_index === -1) { + primaryFilter_filterName = ""; + primaryFilter_displayName = ""; + } else { + primaryFilter_filterName = filterBarModel.get(primaryFilter_index).filterName; + primaryFilter_displayName = filterBarModel.get(primaryFilter_index).displayName; + } + } + + TextField { + id: textField; + + anchors.top: parent.top; + anchors.right: parent.right; + anchors.left: parent.left; + + font.family: "Fira Sans" + font.pixelSize: hifi.fontSizes.textFieldInput; + + placeholderText: root.primaryFilter_index === -1 ? root.placeholderText : ""; + + TextMetrics { + id: primaryFilterTextMetrics; + font.family: "FiraSans Regular"; + font.pixelSize: hifi.fontSizes.textFieldInput; + font.capitalization: Font.AllUppercase; + text: root.primaryFilter_displayName; + } + + // workaround for https://bugreports.qt.io/browse/QTBUG-49297 + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Return: + case Qt.Key_Enter: + event.accepted = true; + + // emit accepted signal manually + if (acceptableInput) { + root.accepted(); + root.forceActiveFocus(); + } + break; + case Qt.Key_Backspace: + if (textField.text === "") { + primaryFilter_index = -1; + } + break; + } + } + + onAccepted: { + root.forceActiveFocus(); + } + + onActiveFocusChanged: { + if (!activeFocus) { + dropdownContainer.visible = false; + } + } + + color: { + if (isLightColorScheme) { + if (textField.activeFocus) { + hifi.colors.black + } else { + hifi.colors.lightGray + } + } else if (isFaintGrayColorScheme) { + if (textField.activeFocus) { + hifi.colors.black + } else { + hifi.colors.lightGray + } + } else { + if (textField.activeFocus) { + hifi.colors.white + } else { + hifi.colors.lightGrayText + } + } + } + + background: Rectangle { + id: mainFilterBarRectangle; + + color: { + if (isLightColorScheme) { + if (textField.activeFocus) { + hifi.colors.white + } else { + hifi.colors.textFieldLightBackground + } + } else if (isFaintGrayColorScheme) { + if (textField.activeFocus) { + hifi.colors.white + } else { + hifi.colors.faintGray50 + } + } else { + if (textField.activeFocus) { + hifi.colors.black + } else { + hifi.colors.baseGrayShadow + } + } + } + + border.color: textField.error ? hifi.colors.redHighlight : + (textField.activeFocus ? hifi.colors.primaryHighlight : (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray)) + border.width: 1 + radius: 4 + + Item { + id: searchButtonContainer; + anchors.left: parent.left; + anchors.verticalCenter: parent.verticalCenter; + height: parent.height; + width: 42; + + // Search icon + HiFiGlyphs { + id: searchIcon; + text: hifi.glyphs.search + color: textField.color + size: 40; + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + width: paintedWidth; + } + + // Carat + HiFiGlyphs { + text: hifi.glyphs.caratDn; + color: textField.color; + size: 40; + anchors.left: parent.left; + anchors.leftMargin: 15; + width: paintedWidth; + } + + MouseArea { + anchors.fill: parent; + onClicked: { + textField.forceActiveFocus(); + dropdownContainer.visible = !dropdownContainer.visible; + } + } + } + + Rectangle { + z: 999; + id: primaryFilterContainer; + color: textField.activeFocus ? hifi.colors.faintGray : hifi.colors.white; + width: primaryFilterTextMetrics.tightBoundingRect.width + 14; + height: parent.height - 8; + anchors.verticalCenter: parent.verticalCenter; + anchors.left: searchButtonContainer.right; + anchors.leftMargin: 4; + visible: primaryFilterText.text !== ""; + radius: height/2; + + FiraSansRegular { + id: primaryFilterText; + text: root.primaryFilter_displayName; + anchors.fill: parent; + color: textField.activeFocus ? hifi.colors.black : hifi.colors.lightGray; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + size: hifi.fontSizes.textFieldInput; + font.capitalization: Font.AllUppercase; + } + + MouseArea { + anchors.fill: parent; + onClicked: { + textField.forceActiveFocus(); + } + } + } + + // "Clear" button + HiFiGlyphs { + text: hifi.glyphs.error + color: textField.color + size: 40 + anchors.right: parent.right + anchors.rightMargin: hifi.dimensions.textPadding - 2 + anchors.verticalCenter: parent.verticalCenter + visible: root.text !== "" || root.primaryFilter_index !== -1; + + MouseArea { + anchors.fill: parent; + onClicked: { + root.text = ""; + root.primaryFilter_index = -1; + dropdownContainer.visible = false; + textField.forceActiveFocus(); + } + } + } + } + + selectedTextColor: hifi.colors.black + selectionColor: hifi.colors.primaryHighlight + leftPadding: 44 + (root.primaryFilter_index === -1 ? 0 : primaryFilterTextMetrics.tightBoundingRect.width + 20); + rightPadding: 44; + } + + Rectangle { + id: dropdownContainer; + visible: false; + height: 50 * filterBarModel.count; + width: parent.width; + anchors.top: textField.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + color: hifi.colors.white; + + ListModel { + id: filterBarModel; + } + + ListView { + id: dropdownListView; + interactive: false; + anchors.fill: parent; + model: filterBarModel; + delegate: Item { + width: parent.width; + height: 50; + Rectangle { + id: dropDownButton; + color: hifi.colors.white; + width: parent.width; + height: 50; + visible: true; + + RalewaySemiBold { + id: dropDownButtonText; + text: model.displayName; + anchors.fill: parent; + anchors.topMargin: 2; + anchors.leftMargin: 12; + color: hifi.colors.baseGray; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + size: 18; + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + propagateComposedEvents: false; + onEntered: { + dropDownButton.color = hifi.colors.blueHighlight; + } + onExited: { + dropDownButton.color = hifi.colors.white; + } + onClicked: { + textField.forceActiveFocus(); + root.primaryFilter_index = index; + dropdownContainer.visible = false; + } + } + } + Rectangle { + height: 2; + width: parent.width; + color: hifi.colors.lightGray; + visible: model.separator + } + } + } + } + + DropShadow { + anchors.fill: dropdownContainer; + horizontalOffset: 0; + verticalOffset: 4; + radius: 4.0; + samples: 9 + color: Qt.rgba(0, 0, 0, 0.25); + source: dropdownContainer; + visible: dropdownContainer.visible; + } + + function changeFilterByDisplayName(name) { + for (var i = 0; i < filterBarModel.count; i++) { + if (filterBarModel.get(i).displayName === name) { + root.primaryFilter_index = i; + return; + } + } + + console.log("Passed displayName not found in filterBarModel! primaryFilter unchanged."); + } +} diff --git a/interface/resources/qml/controlsUit/GlyphButton.qml b/interface/resources/qml/controlsUit/GlyphButton.qml new file mode 100644 index 0000000000..17f7fba2d6 --- /dev/null +++ b/interface/resources/qml/controlsUit/GlyphButton.qml @@ -0,0 +1,91 @@ +// +// GlyphButton.qml +// +// Created by Clement on 3/7/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 as Original +import TabletScriptingInterface 1.0 + +import "../stylesUit" + +Original.Button { + id: control + property int color: 0 + property int colorScheme: hifi.colorSchemes.light + property string glyph: "" + property int size: 32 + + width: 120 + height: 28 + + onHoveredChanged: { + if (hovered) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + onFocusChanged: { + if (focus) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + } + + background: Rectangle { + radius: hifi.buttons.radius + + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorStart[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] + } else if (!control.hovered && control.focus) { + hifi.buttons.focusedColor[control.color] + } else { + hifi.buttons.colorStart[control.color] + } + } + } + GradientStop { + position: 1.0 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorFinish[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] + } else if (!control.hovered && control.focus) { + hifi.buttons.focusedColor[control.color] + } else { + hifi.buttons.colorFinish[control.color] + } + } + } + } + } + + contentItem: HiFiGlyphs { + color: enabled ? hifi.buttons.textColor[control.color] + : hifi.buttons.disabledTextColor[control.colorScheme] + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: control.glyph + size: control.size + } +} + diff --git a/interface/resources/qml/controlsUit/HorizontalRule.qml b/interface/resources/qml/controlsUit/HorizontalRule.qml new file mode 100644 index 0000000000..0609cc451d --- /dev/null +++ b/interface/resources/qml/controlsUit/HorizontalRule.qml @@ -0,0 +1,18 @@ +// +// HorizontalRule.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: hifi.colors.lightGray +} diff --git a/interface/resources/qml/controlsUit/HorizontalSpacer.qml b/interface/resources/qml/controlsUit/HorizontalSpacer.qml new file mode 100644 index 0000000000..efcabf2699 --- /dev/null +++ b/interface/resources/qml/controlsUit/HorizontalSpacer.qml @@ -0,0 +1,21 @@ +// +// HorizontalSpacer.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +import "../stylesUit" + +Item { + id: root + property alias size: root.width + + width: hifi.dimensions.controlInterlineHeight + height: 1 // Must be non-zero +} diff --git a/interface/resources/qml/controlsUit/ImageMessageBox.qml b/interface/resources/qml/controlsUit/ImageMessageBox.qml new file mode 100644 index 0000000000..46d93383a4 --- /dev/null +++ b/interface/resources/qml/controlsUit/ImageMessageBox.qml @@ -0,0 +1,63 @@ +// +// ImageMessageBox.qml +// +// Created by Dante Ruiz on 7/5/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "../stylesUit" + +Item { + id: imageBox + visible: false + anchors.fill: parent + property alias source: image.source + property alias imageWidth: image.width + property alias imageHeight: image.height + + Rectangle { + anchors.fill: parent + color: "black" + opacity: 0.3 + } + + Image { + id: image + anchors.centerIn: parent + + HiFiGlyphs { + id: closeGlyphButton + text: hifi.glyphs.close + size: 25 + + anchors { + top: parent.top + topMargin: 15 + right: parent.right + rightMargin: 15 + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + + onEntered: { + parent.text = hifi.glyphs.closeInverted; + } + + onExited: { + parent.text = hifi.glyphs.close; + } + + onClicked: { + imageBox.visible = false; + } + } + } + } + +} diff --git a/interface/resources/qml/controlsUit/Key.qml b/interface/resources/qml/controlsUit/Key.qml new file mode 100644 index 0000000000..dd77fc92dc --- /dev/null +++ b/interface/resources/qml/controlsUit/Key.qml @@ -0,0 +1,185 @@ +import QtQuick 2.0 +import TabletScriptingInterface 1.0 + +Item { + id: keyItem + width: 45 + height: 50 + + property int contentPadding: 4 + property string glyph: "a" + property bool toggle: false // does this button have the toggle behaivor? + property bool toggled: false // is this button currently toggled? + property alias mouseArea: mouseArea1 + property alias fontFamily: letter.font.family; + property alias fontPixelSize: letter.font.pixelSize + property alias verticalAlignment: letter.verticalAlignment + property alias letterAnchors: letter.anchors + + function resetToggledMode(mode) { + toggled = mode; + if (toggled) { + state = "mouseDepressed"; + } else { + state = ""; + } + } + + MouseArea { + id: mouseArea1 + width: 36 + anchors.fill: parent + hoverEnabled: true + + onCanceled: { + if (toggled) { + keyItem.state = "mouseDepressed"; + } else { + keyItem.state = ""; + } + } + + onContainsMouseChanged: { + if (containsMouse) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + onDoubleClicked: { + mouse.accepted = true; + } + + property var _HAPTIC_STRENGTH: 0.1; + property var _HAPTIC_DURATION: 3.0; + property var leftHand: 0; + property var rightHand: 1; + + onEntered: { + keyItem.state = "mouseOver"; + + var globalPosition = keyItem.mapToGlobal(mouseArea1.mouseX, mouseArea1.mouseY); + var pointerID = Web3DOverlay.deviceIdByTouchPoint(globalPosition.x, globalPosition.y); + + if (Pointers.isLeftHand(pointerID)) { + Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, leftHand); + } else if (Pointers.isRightHand(pointerID)) { + Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, rightHand); + } + } + + onExited: { + if (toggled) { + keyItem.state = "mouseDepressed"; + } else { + keyItem.state = ""; + } + } + + onPressed: { + keyItem.state = "mouseClicked"; + mouse.accepted = true; + } + + onReleased: { + if (containsMouse) { + Tablet.playSound(TabletEnums.ButtonClick); + + webEntity.synthesizeKeyPress(glyph); + webEntity.synthesizeKeyPress(glyph, mirrorText); + + if (toggle) { + toggled = !toggled; + } + keyItem.state = "mouseOver"; + } else { + if (toggled) { + keyItem.state = "mouseDepressed"; + } else { + keyItem.state = ""; + } + } + mouse.accepted = true; + } + } + + Rectangle { + id: roundedRect + width: 30 + color: "#121212" + radius: 2 + border.color: "#00000000" + anchors.fill: parent + anchors.margins: contentPadding + } + + Text { + id: letter + y: 6 + width: 50 + color: "#ffffff" + text: glyph + style: Text.Normal + font.family: "Tahoma" + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.top: parent.top + anchors.topMargin: 8 + horizontalAlignment: Text.AlignHCenter + font.pixelSize: 28 + } + + states: [ + State { + name: "mouseOver" + + PropertyChanges { + target: roundedRect + color: "#121212" + radius: 3 + border.width: 2 + border.color: "#00b4ef" + } + + PropertyChanges { + target: letter + color: "#00b4ef" + style: Text.Normal + } + }, + State { + name: "mouseClicked" + PropertyChanges { + target: roundedRect + color: "#1080b8" + border.width: 2 + border.color: "#00b4ef" + } + + PropertyChanges { + target: letter + color: "#121212" + styleColor: "#00000000" + style: Text.Normal + } + }, + State { + name: "mouseDepressed" + PropertyChanges { + target: roundedRect + color: "#0578b1" + border.width: 0 + } + + PropertyChanges { + target: letter + color: "#121212" + styleColor: "#00000000" + style: Text.Normal + } + } + ] +} diff --git a/interface/resources/qml/controlsUit/Keyboard.qml b/interface/resources/qml/controlsUit/Keyboard.qml new file mode 100644 index 0000000000..c38631ff79 --- /dev/null +++ b/interface/resources/qml/controlsUit/Keyboard.qml @@ -0,0 +1,370 @@ +// +// FileDialog.qml +// +// Created by Anthony Thibault on 31 Oct 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtGraphicalEffects 1.0 +import "." + +Rectangle { + id: keyboardBase + objectName: "keyboard" + + anchors.left: parent.left + anchors.right: parent.right + + color: "#252525" + + property bool raised: false + property bool numeric: false + + readonly property int keyboardRowHeight: 50 + readonly property int keyboardWidth: 480 + readonly property int keyboardHeight: 200 + + readonly property int mirrorTextHeight: keyboardRowHeight + + property bool password: false + property alias mirroredText: mirrorText.text + property bool showMirrorText: true + + readonly property int raisedHeight: keyboardHeight + (showMirrorText ? keyboardRowHeight : 0) + + height: 0 + visible: false + + property bool shiftMode: false + property bool numericShiftMode: false + + + onPasswordChanged: { + var use3DKeyboard = (typeof MenuInterface === "undefined") ? false : MenuInterface.isOptionChecked("Use 3D Keyboard"); + if (use3DKeyboard) { + KeyboardScriptingInterface.password = password; + } + } + + onRaisedChanged: { + var use3DKeyboard = (typeof MenuInterface === "undefined") ? false : MenuInterface.isOptionChecked("Use 3D Keyboard"); + if (!use3DKeyboard) { + keyboardBase.height = raised ? raisedHeight : 0; + keyboardBase.visible = raised; + } else { + KeyboardScriptingInterface.raised = raised; + KeyboardScriptingInterface.password = raised ? password : false; + } + mirroredText = ""; + } + + function resetShiftMode(mode) { + shiftMode = mode; + shiftKey.resetToggledMode(mode); + } + + function toUpper(str) { + if (str === ",") { + return "<"; + } else if (str === ".") { + return ">"; + } else if (str === "/") { + return "?"; + } else if (str === "-") { + return "_"; + } else { + return str.toUpperCase(str); + } + } + + function toLower(str) { + if (str === "<") { + return ","; + } else if (str === ">") { + return "."; + } else if (str === "?") { + return "/"; + } else if (str === "_") { + return "-"; + } else { + return str.toLowerCase(str); + } + } + + function forEachKey(func) { + var i, j; + for (i = 0; i < columnAlpha.children.length; i++) { + var row = columnAlpha.children[i]; + for (j = 0; j < row.children.length; j++) { + var key = row.children[j]; + func(key); + } + } + } + + onShiftModeChanged: { + forEachKey(function (key) { + if (/[a-z-_]/i.test(key.glyph)) { + if (shiftMode) { + key.glyph = keyboardBase.toUpper(key.glyph); + } else { + key.glyph = keyboardBase.toLower(key.glyph); + } + } + }); + } + + function alphaKeyClickedHandler(mouseArea) { + // reset shift mode to false after first keypress + if (shiftMode) { + resetShiftMode(false); + } + } + + Component.onCompleted: { + // hook up callbacks to every ascii key + forEachKey(function (key) { + if (/^[a-z]+$/i.test(key.glyph)) { + key.mouseArea.onClicked.connect(alphaKeyClickedHandler); + } + }); + } + + Rectangle { + height: showMirrorText ? mirrorTextHeight : 0 + width: keyboardWidth + color: "#252525" + anchors.horizontalCenter: parent.horizontalCenter + + TextInput { + id: mirrorText + visible: showMirrorText + font.family: "Fira Sans" + font.pixelSize: 20 + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + color: "#00B4EF"; + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + + wrapMode: Text.WordWrap + readOnly: false // we need this to allow control to accept QKeyEvent + selectByMouse: false + echoMode: password ? TextInput.Password : TextInput.Normal + + Keys.onPressed: { + if (event.key == Qt.Key_Return || event.key == Qt.Key_Space) { + mirrorText.text = ""; + event.accepted = true; + } + } + + MouseArea { // ... and we need this mouse area to prevent mirrorText from getting mouse events to ensure it will never get focus + anchors.fill: parent + } + } + } + + Rectangle { + id: keyboardRect + y: showMirrorText ? mirrorTextHeight : 0 + width: keyboardWidth + height: keyboardHeight + color: "#252525" + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + + Column { + id: columnAlpha + width: keyboardWidth + height: keyboardHeight + visible: !numeric + + Row { + width: keyboardWidth + height: keyboardRowHeight + anchors.left: parent.left + anchors.leftMargin: 4 + + Key { width: 43; glyph: "q"; } + Key { width: 43; glyph: "w"; } + Key { width: 43; glyph: "e"; } + Key { width: 43; glyph: "r"; } + Key { width: 43; glyph: "t"; } + Key { width: 43; glyph: "y"; } + Key { width: 43; glyph: "u"; } + Key { width: 43; glyph: "i"; } + Key { width: 43; glyph: "o"; } + Key { width: 43; glyph: "p"; } + Key { width: 43; glyph: "←"; } + } + + Row { + width: keyboardWidth + height: keyboardRowHeight + anchors.left: parent.left + anchors.leftMargin: 20 + + Key { width: 43; glyph: "a"; } + Key { width: 43; glyph: "s"; } + Key { width: 43; glyph: "d"; } + Key { width: 43; glyph: "f"; } + Key { width: 43; glyph: "g"; } + Key { width: 43; glyph: "h"; } + Key { width: 43; glyph: "j"; } + Key { width: 43; glyph: "k"; } + Key { width: 43; glyph: "l"; } + Key { width: 70; glyph: "⏎"; } + } + + Row { + width: keyboardWidth + height: keyboardRowHeight + anchors.left: parent.left + anchors.leftMargin: 4 + + Key { + id: shiftKey + width: 43 + glyph: "⇪" + toggle: true + onToggledChanged: shiftMode = toggled + } + Key { width: 43; glyph: "z"; } + Key { width: 43; glyph: "x"; } + Key { width: 43; glyph: "c"; } + Key { width: 43; glyph: "v"; } + Key { width: 43; glyph: "b"; } + Key { width: 43; glyph: "n"; } + Key { width: 43; glyph: "m"; } + Key { width: 43; glyph: "-"; } + Key { width: 43; glyph: "/"; } + Key { width: 43; glyph: "?"; } + } + + Row { + width: keyboardWidth + height: keyboardRowHeight + anchors.left: parent.left + anchors.leftMargin: 4 + + Key { + width: 70 + glyph: "123" + mouseArea.onClicked: keyboardBase.parent.punctuationMode = true + } + Key { width: 231; glyph: " "; } + Key { width: 43; glyph: ","; } + Key { width: 43; glyph: "."; } + Key { + fontFamily: "hifi-glyphs"; + fontPixelSize: 48; + letterAnchors.topMargin: -4; + verticalAlignment: Text.AlignVCenter; + width: 86; glyph: "\ue02b"; + } + } + } + + Column { + id: columnNumeric + width: keyboardWidth + height: keyboardHeight + visible: numeric + + Row { + width: keyboardWidth + height: keyboardRowHeight + anchors.left: parent.left + anchors.leftMargin: 4 + + Key { width: 43; glyph: "1"; } + Key { width: 43; glyph: "2"; } + Key { width: 43; glyph: "3"; } + Key { width: 43; glyph: "4"; } + Key { width: 43; glyph: "5"; } + Key { width: 43; glyph: "6"; } + Key { width: 43; glyph: "7"; } + Key { width: 43; glyph: "8"; } + Key { width: 43; glyph: "9"; } + Key { width: 43; glyph: "0"; } + Key { width: 43; glyph: "←"; } + } + + Row { + width: keyboardWidth + height: keyboardRowHeight + anchors.left: parent.left + anchors.leftMargin: 4 + + Key { width: 43; glyph: "!"; } + Key { width: 43; glyph: "@"; } + Key { width: 43; glyph: "#"; } + Key { width: 43; glyph: "$"; } + Key { width: 43; glyph: "%"; } + Key { width: 43; glyph: "^"; } + Key { width: 43; glyph: "&"; } + Key { width: 43; glyph: "*"; } + Key { width: 43; glyph: "("; } + Key { width: 43; glyph: ")"; } + Key { width: 43; glyph: "⏎"; } + } + + Row { + width: keyboardWidth + height: keyboardRowHeight + anchors.left: parent.left + anchors.leftMargin: 4 + + Key { + id: numericShiftKey + width: 43 + glyph: "\u21E8" + toggle: true + onToggledChanged: numericShiftMode = toggled + } + Key { width: 43; glyph: numericShiftMode ? "`" : "+"; } + Key { width: 43; glyph: numericShiftMode ? "~" : "-"; } + Key { width: 43; glyph: numericShiftMode ? "\u00A3" : "="; } + Key { width: 43; glyph: numericShiftMode ? "\u20AC" : ";"; } + Key { width: 43; glyph: numericShiftMode ? "\u00A5" : ":"; } + Key { width: 43; glyph: numericShiftMode ? "<" : "'"; } + Key { width: 43; glyph: numericShiftMode ? ">" : "\""; } + Key { width: 43; glyph: numericShiftMode ? "[" : "{"; } + Key { width: 43; glyph: numericShiftMode ? "]" : "}"; } + Key { width: 43; glyph: numericShiftMode ? "\\" : "|"; } + } + + Row { + width: keyboardWidth + height: keyboardRowHeight + anchors.left: parent.left + anchors.leftMargin: 4 + + Key { + width: 70 + glyph: "abc" + mouseArea.onClicked: keyboardBase.parent.punctuationMode = false + } + Key { width: 231; glyph: " "; } + Key { width: 43; glyph: ","; } + Key { width: 43; glyph: "."; } + Key { + fontFamily: "hifi-glyphs"; + fontPixelSize: 48; + letterAnchors.topMargin: -4; + verticalAlignment: Text.AlignVCenter; + width: 86; glyph: "\ue02b"; + } + } + } + } +} diff --git a/interface/resources/qml/controlsUit/Label.qml b/interface/resources/qml/controlsUit/Label.qml new file mode 100644 index 0000000000..7f208cde88 --- /dev/null +++ b/interface/resources/qml/controlsUit/Label.qml @@ -0,0 +1,35 @@ +// +// Label.qml +// +// Created by David Rowe on 26 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 + +import "../stylesUit" + +RalewaySemiBold { + HifiConstants { id: hifi } + property int colorScheme: hifi.colorSchemes.light + + size: hifi.fontSizes.inputLabel + color: { + if (colorScheme === hifi.colorSchemes.dark) { + if (enabled) { + hifi.colors.lightGrayText + } else { + hifi.colors.baseGrayHighlight + } + } else { + if (enabled) { + hifi.colors.lightGray + } else { + hifi.colors.lightGrayText + } + } + } +} diff --git a/interface/resources/qml/controlsUit/QueuedButton.qml b/interface/resources/qml/controlsUit/QueuedButton.qml new file mode 100644 index 0000000000..70ad9eb112 --- /dev/null +++ b/interface/resources/qml/controlsUit/QueuedButton.qml @@ -0,0 +1,41 @@ +// +// QueuedButton.qml +// -- original Button.qml + signal timer workaround --ht +// Created by David Rowe on 16 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +import "../stylesUit" +import "." as HifiControls + +HifiControls.Button { + // FIXME: THIS WORKAROUND MIGRATED/CONSOLIDATED FROM RUNNINGSCRIPTS.QML + + // For some reason trigginer an API that enters + // an internal event loop directly from the button clicked + // trigger below causes the appliction to behave oddly. + // Most likely because the button onClicked handling is never + // completed until the function returns. + // FIXME find a better way of handling the input dialogs that + // doesn't trigger this. + + // NOTE: dialogs that need to use this workaround can connect via + // onQueuedClicked: ... + // instead of: + // onClicked: ... + + signal clickedQueued() + Timer { + id: fromTimer + interval: 5 + repeat: false + running: false + onTriggered: clickedQueued() + } + onClicked: fromTimer.running = true +} diff --git a/interface/resources/qml/controlsUit/RadioButton.qml b/interface/resources/qml/controlsUit/RadioButton.qml new file mode 100644 index 0000000000..ad62a77aa7 --- /dev/null +++ b/interface/resources/qml/controlsUit/RadioButton.qml @@ -0,0 +1,93 @@ +// +// RadioButton.qml +// +// Created by Cain Kilgore on 20th July 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 2.2 as Original + +import "../stylesUit" +import "." as HifiControls + +import TabletScriptingInterface 1.0 + +Original.RadioButton { + id: radioButton + HifiConstants { id: hifi } + + hoverEnabled: true + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + + property real letterSpacing: 1 + property int fontSize: hifi.fontSizes.inputLabel + property int boxSize: defaultBoxSize + property real scaleFactor: boxSize / defaultBoxSize + + readonly property int defaultBoxSize: 14 + readonly property int boxRadius: 3 * scaleFactor + readonly property int checkSize: 10 * scaleFactor + readonly property int checkRadius: 2 * scaleFactor + readonly property int indicatorRadius: 7 * scaleFactor + + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + } + + onHoveredChanged: { + if (hovered) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + indicator: Rectangle { + id: box + width: boxSize + height: boxSize + radius: indicatorRadius + x: radioButton.leftPadding + y: parent.height / 2 - height / 2 + gradient: Gradient { + GradientStop { + position: 0.2 + color: pressed || hovered + ? (radioButton.isLightColorScheme ? hifi.colors.checkboxDarkStart : hifi.colors.checkboxLightStart) + : (radioButton.isLightColorScheme ? hifi.colors.checkboxLightStart : hifi.colors.checkboxDarkStart) + } + GradientStop { + position: 1.0 + color: pressed || hovered + ? (radioButton.isLightColorScheme ? hifi.colors.checkboxDarkFinish : hifi.colors.checkboxLightFinish) + : (radioButton.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish) + } + } + + Rectangle { + id: check + width: checkSize + height: checkSize + radius: indicatorRadius + anchors.centerIn: parent + color: "#00B4EF" + border.width: 1 + border.color: "#36CDFF" + visible: checked && !pressed || !checked && pressed + } + } + + contentItem: RalewaySemiBold { + text: radioButton.text + size: radioButton.fontSize + font.letterSpacing: letterSpacing + color: isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + leftPadding: radioButton.indicator.width + radioButton.spacing + } +} diff --git a/interface/resources/qml/controlsUit/ScrollBar.qml b/interface/resources/qml/controlsUit/ScrollBar.qml new file mode 100644 index 0000000000..bcb1f62429 --- /dev/null +++ b/interface/resources/qml/controlsUit/ScrollBar.qml @@ -0,0 +1,41 @@ +// +// ScrollBar.qml +// +// Created by Vlad Stelmahovsky on 27 Nov 2017 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 + +import "../stylesUit" + +ScrollBar { + visible: size < 1.0 + + HifiConstants { id: hifi } + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + + background: Item { + implicitWidth: hifi.dimensions.scrollbarBackgroundWidth + Rectangle { + anchors { fill: parent; topMargin: 3; bottomMargin: 3 } + radius: hifi.dimensions.scrollbarHandleWidth/2 + color: isLightColorScheme ? hifi.colors.tableScrollBackgroundLight + : hifi.colors.tableScrollBackgroundDark + } + } + + contentItem: Item { + implicitWidth: hifi.dimensions.scrollbarHandleWidth + Rectangle { + anchors { fill: parent; topMargin: 1; bottomMargin: 1 } + radius: hifi.dimensions.scrollbarHandleWidth/2 + color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark + } + } +} diff --git a/interface/resources/qml/controlsUit/Separator.qml b/interface/resources/qml/controlsUit/Separator.qml new file mode 100644 index 0000000000..da6b9adf57 --- /dev/null +++ b/interface/resources/qml/controlsUit/Separator.qml @@ -0,0 +1,44 @@ +// +// Separator.qml +// +// Created by Zach Fox on 2017-06-06 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "../stylesUit" + +Item { + property int colorScheme: 0; + + readonly property var topColor: [ hifi.colors.baseGrayShadow, hifi.colors.faintGray, "#89858C" ]; + readonly property var bottomColor: [ hifi.colors.baseGrayHighlight, hifi.colors.faintGray, "#89858C" ]; + + // Size + height: colorScheme === 0 ? 2 : 1; + Rectangle { + // Size + width: parent.width; + height: 1; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + // Style + color: topColor[colorScheme]; + } + Rectangle { + visible: colorScheme === 0; + // Size + width: parent.width; + height: 1; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: -height; + // Style + color: bottomColor[colorScheme]; + } +} diff --git a/interface/resources/qml/controlsUit/Slider.qml b/interface/resources/qml/controlsUit/Slider.qml new file mode 100644 index 0000000000..8cb08b69e2 --- /dev/null +++ b/interface/resources/qml/controlsUit/Slider.qml @@ -0,0 +1,98 @@ +// +// Slider.qml +// +// Created by David Rowe on 27 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 + +import "../stylesUit" +import "." as HifiControls + +Slider { + id: slider + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + property string label: "" + property real controlHeight: height + (sliderLabel.visible ? sliderLabel.height + sliderLabel.anchors.bottomMargin : 0) + + property alias minimumValue: slider.from + property alias maximumValue: slider.to + property alias step: slider.stepSize + property bool tickmarksEnabled: false + + height: hifi.fontSizes.textFieldInput + 14 // Match height of TextField control. + y: sliderLabel.visible ? sliderLabel.height + sliderLabel.anchors.bottomMargin : 0 + + background: Rectangle { + x: slider.leftPadding + y: slider.topPadding + slider.availableHeight / 2 - height / 2 + + implicitWidth: 50 + implicitHeight: hifi.dimensions.sliderGrooveHeight + width: slider.availableWidth + height: implicitHeight + radius: height / 2 + color: isLightColorScheme ? hifi.colors.sliderGutterLight : hifi.colors.sliderGutterDark + + Rectangle { + width: slider.visualPosition * parent.width + height: parent.height + radius: height / 2 + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.blueAccent } + GradientStop { position: 1.0; color: hifi.colors.primaryHighlight } + } + } + } + + handle: Rectangle { + x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width) + y: slider.topPadding + slider.availableHeight / 2 - height / 2 + implicitWidth: hifi.dimensions.sliderHandleSize + implicitHeight: hifi.dimensions.sliderHandleSize + radius: height / 2 + border.width: 1 + border.color: isLightColorScheme ? hifi.colors.sliderBorderLight : hifi.colors.sliderBorderDark + gradient: Gradient { + GradientStop { + position: 0.0 + color: pressed || hovered + ? (isLightColorScheme ? hifi.colors.sliderDarkStart : hifi.colors.sliderLightStart ) + : (isLightColorScheme ? hifi.colors.sliderLightStart : hifi.colors.sliderDarkStart ) + } + GradientStop { + position: 1.0 + color: pressed || hovered + ? (isLightColorScheme ? hifi.colors.sliderDarkFinish : hifi.colors.sliderLightFinish ) + : (isLightColorScheme ? hifi.colors.sliderLightFinish : hifi.colors.sliderDarkFinish ) + } + } + + Rectangle { + height: parent.height - 2 + width: height + radius: height / 2 + anchors.centerIn: parent + color: hifi.colors.transparent + border.width: 1 + border.color: hifi.colors.black + } + } + + HifiControls.Label { + id: sliderLabel + text: slider.label + colorScheme: slider.colorScheme + anchors.left: parent.left + anchors.bottom: parent.top + anchors.bottomMargin: 2 + visible: label != "" + } +} diff --git a/interface/resources/qml/controlsUit/SpinBox.qml b/interface/resources/qml/controlsUit/SpinBox.qml new file mode 100644 index 0000000000..d24c7c5e8c --- /dev/null +++ b/interface/resources/qml/controlsUit/SpinBox.qml @@ -0,0 +1,185 @@ +// +// SpinBox.qml +// +// Created by David Rowe on 26 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 + +import "../stylesUit" +import "." as HifiControls + +SpinBox { + id: spinBox + + HifiConstants { + id: hifi + } + + inputMethodHints: Qt.ImhFormattedNumbersOnly + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light + property string label: "" + property string suffix: "" + property string labelInside: "" + property color colorLabelInside: hifi.colors.white + property color backgroundColor: isLightColorScheme + ? (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGray) + : (spinBox.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow) + property real controlHeight: height + (spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0) + property int decimals: 2; + property real factor: Math.pow(10, decimals) + + property real minimumValue: 0.0 + property real maximumValue: 0.0 + + property real realValue: 0.0 + property real realFrom: minimumValue + property real realTo: maximumValue + property real realStepSize: 1.0 + + signal editingFinished() + + implicitHeight: height + implicitWidth: width + editable: true + + padding: 0 + leftPadding: 0 + rightPadding: padding + (up.indicator ? up.indicator.width : 0) + topPadding: 0 + bottomPadding: 0 + + locale: Qt.locale("en_US") + + onValueModified: realValue = value/factor + onValueChanged: realValue = value/factor + onRealValueChanged: { + var newValue = Math.round(realValue*factor); + if(value != newValue) { + value = newValue; + } + } + + stepSize: realStepSize*factor + to : realTo*factor + from : realFrom*factor + + font.family: "Fira Sans SemiBold" + font.pixelSize: hifi.fontSizes.textFieldInput + height: hifi.fontSizes.textFieldInput + 13 // Match height of TextField control. + + y: spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0 + + background: Rectangle { + color: backgroundColor + border.color: spinBoxLabelInside.visible ? spinBoxLabelInside.color : hifi.colors.primaryHighlight + border.width: spinBox.activeFocus ? spinBoxLabelInside.visible ? 2 : 1 : 0 + } + + validator: DoubleValidator { + bottom: Math.min(spinBox.from, spinBox.to) + top: Math.max(spinBox.from, spinBox.to) + } + + textFromValue: function(value, locale) { + return parseFloat(value/factor).toFixed(decimals); + } + + valueFromText: function(text, locale) { + return Number.fromLocaleString(locale, text)*factor; + } + + + contentItem: TextInput { + z: 2 + color: isLightColorScheme + ? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray) + : (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText) + selectedTextColor: hifi.colors.black + selectionColor: hifi.colors.primaryHighlight + text: spinBox.textFromValue(spinBox.value, spinBox.locale) + suffix + inputMethodHints: spinBox.inputMethodHints + validator: spinBox.validator + verticalAlignment: Qt.AlignVCenter + leftPadding: spinBoxLabelInside.visible ? 30 : hifi.dimensions.textPadding + //rightPadding: hifi.dimensions.spinnerSize + width: spinBox.width - hifi.dimensions.spinnerSize + onEditingFinished: spinBox.editingFinished() + } + + up.indicator: Item { + x: spinBox.width - implicitWidth - 5 + y: 1 + clip: true + implicitHeight: spinBox.implicitHeight/2 + implicitWidth: spinBox.implicitHeight/2 + HiFiGlyphs { + anchors.centerIn: parent + text: hifi.glyphs.caratUp + size: hifi.dimensions.spinnerSize + color: spinBox.up.pressed || spinBox.up.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray + } + } + up.onPressedChanged: { + if(value) { + spinBox.forceActiveFocus(); + } + } + + down.indicator: Item { + x: spinBox.width - implicitWidth - 5 + y: spinBox.implicitHeight/2 + clip: true + implicitHeight: spinBox.implicitHeight/2 + implicitWidth: spinBox.implicitHeight/2 + HiFiGlyphs { + anchors.centerIn: parent + text: hifi.glyphs.caratDn + size: hifi.dimensions.spinnerSize + color: spinBox.down.pressed || spinBox.down.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray + } + } + down.onPressedChanged: { + if(value) { + spinBox.forceActiveFocus(); + } + } + + HifiControls.Label { + id: spinBoxLabel + text: spinBox.label + colorScheme: spinBox.colorScheme + anchors.left: parent.left + anchors.bottom: parent.top + anchors.bottomMargin: 4 + visible: label != "" + } + + HifiControls.Label { + id: spinBoxLabelInside + text: spinBox.labelInside + anchors.left: parent.left + anchors.leftMargin: 10 + font.bold: true + anchors.verticalCenter: parent.verticalCenter + color: spinBox.colorLabelInside + visible: spinBox.labelInside != "" + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + onWheel: { + if (wheel.angleDelta.y > 0) + value += stepSize + else + value -= stepSize + } + } +} diff --git a/interface/resources/qml/controlsUit/Switch.qml b/interface/resources/qml/controlsUit/Switch.qml new file mode 100644 index 0000000000..0961ef2500 --- /dev/null +++ b/interface/resources/qml/controlsUit/Switch.qml @@ -0,0 +1,160 @@ +// +// Switch.qml +// +// Created by Zach Fox on 2017-06-06 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 as Original + +import "../stylesUit" + +Item { + id: rootSwitch; + + property int colorScheme: hifi.colorSchemes.light; + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light; + property int switchWidth: 70; + readonly property int switchRadius: height/2; + property string labelTextOff: ""; + property string labelGlyphOffText: ""; + property int labelGlyphOffSize: 32; + property string labelTextOn: ""; + property string labelGlyphOnText: ""; + property int labelGlyphOnSize: 32; + property alias checked: originalSwitch.checked; + signal onCheckedChanged; + signal clicked; + + Original.Switch { + id: originalSwitch; + focusPolicy: Qt.ClickFocus + anchors.top: rootSwitch.top; + anchors.left: rootSwitch.left; + anchors.leftMargin: rootSwitch.width/2 - rootSwitch.switchWidth/2; + onCheckedChanged: rootSwitch.onCheckedChanged(); + onClicked: rootSwitch.clicked(); + hoverEnabled: true + + topPadding: 3; + leftPadding: 3; + rightPadding: 3; + bottomPadding: 3; + + onHoveredChanged: { + if (hovered) { + switchHandle.color = hifi.colors.blueHighlight; + } else { + switchHandle.color = hifi.colors.lightGray; + } + } + + background: Rectangle { + color: "#252525"; + implicitWidth: rootSwitch.switchWidth; + implicitHeight: rootSwitch.height; + radius: rootSwitch.switchRadius; + } + + indicator: Rectangle { + id: switchHandle; + implicitWidth: rootSwitch.height - originalSwitch.topPadding - originalSwitch.bottomPadding; + implicitHeight: implicitWidth; + radius: implicitWidth/2; + border.color: hifi.colors.lightGrayText; + color: hifi.colors.lightGray; + //x: originalSwitch.leftPadding + x: Math.max(0, Math.min(parent.width - width, originalSwitch.visualPosition * parent.width - (width / 2))) + y: parent.height / 2 - height / 2 + Behavior on x { + enabled: !originalSwitch.down + SmoothedAnimation { velocity: 200 } + } + + } + } + + // OFF Label + Item { + anchors.right: originalSwitch.left; + anchors.rightMargin: 10; + anchors.top: rootSwitch.top; + height: rootSwitch.height; + + RalewaySemiBold { + id: labelOff; + text: labelTextOff; + size: hifi.fontSizes.inputLabel; + color: originalSwitch.checked ? hifi.colors.lightGrayText : "#FFFFFF"; + anchors.top: parent.top; + anchors.right: parent.right; + width: paintedWidth; + height: parent.height; + verticalAlignment: Text.AlignVCenter; + } + + HiFiGlyphs { + id: labelGlyphOff; + text: labelGlyphOffText; + size: labelGlyphOffSize; + color: labelOff.color; + anchors.top: parent.top; + anchors.topMargin: 2; + anchors.right: labelOff.left; + anchors.rightMargin: 4; + } + + MouseArea { + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.left: labelGlyphOff.left; + anchors.right: labelOff.right; + onClicked: { + originalSwitch.checked = false; + } + } + } + + // ON Label + Item { + anchors.left: originalSwitch.right; + anchors.leftMargin: 10; + anchors.top: rootSwitch.top; + height: rootSwitch.height; + + RalewaySemiBold { + id: labelOn; + text: labelTextOn; + size: hifi.fontSizes.inputLabel; + color: originalSwitch.checked ? "#FFFFFF" : hifi.colors.lightGrayText; + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + height: parent.height; + verticalAlignment: Text.AlignVCenter; + } + + HiFiGlyphs { + id: labelGlyphOn; + text: labelGlyphOnText; + size: labelGlyphOnSize; + color: labelOn.color; + anchors.top: parent.top; + anchors.left: labelOn.right; + } + + MouseArea { + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.left: labelOn.left; + anchors.right: labelGlyphOn.right; + onClicked: { + originalSwitch.checked = true; + } + } + } +} diff --git a/interface/resources/qml/controlsUit/Table.qml b/interface/resources/qml/controlsUit/Table.qml new file mode 100644 index 0000000000..ab74361046 --- /dev/null +++ b/interface/resources/qml/controlsUit/Table.qml @@ -0,0 +1,165 @@ +// +// Table.qml +// +// Created by David Rowe on 18 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls 2.3 as QQC2 + +import "../stylesUit" + +TableView { + id: tableView + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + property bool expandSelectedRow: false + property bool centerHeaderText: false + readonly property real headerSpacing: 3 //spacing between sort indicator and table header title + property var titlePaintedPos: [] // storing extra data position behind painted + // title text and sort indicatorin table's header + signal titlePaintedPosSignal(int column) //signal that extradata position gets changed + + model: ListModel { } + + Component.onCompleted: { + if (flickableItem !== null && flickableItem !== undefined) { + tableView.flickableItem.QQC2.ScrollBar.vertical = scrollbar + } + } + + QQC2.ScrollBar { + id: scrollbar + parent: tableView.flickableItem + policy: QQC2.ScrollBar.AsNeeded + orientation: Qt.Vertical + visible: size < 1.0 + topPadding: tableView.headerVisible ? hifi.dimensions.tableHeaderHeight + 1 : 1 + anchors.top: tableView.top + anchors.left: tableView.right + anchors.bottom: tableView.bottom + + background: Item { + implicitWidth: hifi.dimensions.scrollbarBackgroundWidth + Rectangle { + anchors { + fill: parent; + topMargin: tableView.headerVisible ? hifi.dimensions.tableHeaderHeight : 0 + } + color: isLightColorScheme ? hifi.colors.tableScrollBackgroundLight + : hifi.colors.tableScrollBackgroundDark + } + } + + contentItem: Item { + implicitWidth: hifi.dimensions.scrollbarHandleWidth + Rectangle { + anchors.fill: parent + radius: (width - 4)/2 + color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark + } + } + } + + headerVisible: false + headerDelegate: Rectangle { + height: hifi.dimensions.tableHeaderHeight + color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark + + + RalewayRegular { + id: titleText + x: centerHeaderText ? (parent.width - paintedWidth - + ((sortIndicatorVisible && + sortIndicatorColumn === styleData.column) ? + (titleSort.paintedWidth / 5 + tableView.headerSpacing) : 0)) / 2 : + hifi.dimensions.tablePadding + text: styleData.value + size: hifi.fontSizes.tableHeading + font.capitalization: Font.AllUppercase + color: hifi.colors.baseGrayHighlight + horizontalAlignment: (centerHeaderText ? Text.AlignHCenter : Text.AlignLeft) + anchors.verticalCenter: parent.verticalCenter + } + + //actual image of sort indicator in glyph font only 20% of real font size + //i.e. if the charachter size set to 60 pixels, actual image is 12 pixels + HiFiGlyphs { + id: titleSort + text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn + color: hifi.colors.darkGray + opacity: 0.6; + size: hifi.fontSizes.tableHeadingIcon + anchors.verticalCenter: titleText.verticalCenter + anchors.left: titleText.right + anchors.leftMargin: -(hifi.fontSizes.tableHeadingIcon / 2.5) + tableView.headerSpacing + visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column + onXChanged: { + titlePaintedPos[styleData.column] = titleText.x + titleText.paintedWidth + + paintedWidth / 5 + tableView.headerSpacing*2 + titlePaintedPosSignal(styleData.column) + } + } + + Rectangle { + width: 1 + anchors { + left: parent.left + top: parent.top + topMargin: 1 + bottom: parent.bottom + bottomMargin: 2 + } + color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + visible: styleData.column > 0 + } + + Rectangle { + height: 1 + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + } + } + + // Use rectangle to draw border with rounded corners. + frameVisible: false + Rectangle { + color: "#00000000" + anchors { fill: parent; margins: -2 } + border.color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + border.width: 2 + } + anchors.margins: 2 // Shrink TableView to lie within border. + + backgroundVisible: true + + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff + + style: TableViewStyle { + // Needed in order for rows to keep displaying rows after end of table entries. + backgroundColor: tableView.isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark + alternateBackgroundColor: tableView.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd + padding.top: headerVisible ? hifi.dimensions.tableHeaderHeight: 0 + } + + rowDelegate: Rectangle { + height: (styleData.selected && expandSelectedRow ? 1.8 : 1) * hifi.dimensions.tableRowHeight + color: styleData.selected + ? hifi.colors.primaryHighlight + : tableView.isLightColorScheme + ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) + : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd) + } +} diff --git a/interface/resources/qml/controlsUit/TabletContentSection.qml b/interface/resources/qml/controlsUit/TabletContentSection.qml new file mode 100644 index 0000000000..dccaf31bbe --- /dev/null +++ b/interface/resources/qml/controlsUit/TabletContentSection.qml @@ -0,0 +1,138 @@ +// +// ContentSection.qml +// +// Created by Dante Ruiz on 13 Feb 2017 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "../stylesUit" + +Column { + property string name: "Content Section" + property bool isFirst: false + property bool isCollapsible: false // Set at creation. + property bool isCollapsed: false + + spacing: 0 // Defer spacing decisions to individual controls. + + anchors { + left: parent.left + leftMargin: hifi.dimensions.contentMargin.x + right: parent.right + rightMargin: hifi.dimensions.contentMargin.x + } + + function toggleCollapsed() { + if (isCollapsible) { + isCollapsed = !isCollapsed; + for (var i = 1; i < children.length; i++) { + children[i].visible = !isCollapsed; + } + } + } + + Item { + id: sectionName + anchors.left: parent.left + anchors.right: parent.right + height: leadingSpace.height + topBar.height + heading.height + bottomBar.height + + Item { + id: leadingSpace + width: 1 + height: isFirst ? 7 : 0 + anchors.top: parent.top + } + + Item { + id: topBar + visible: !isFirst + height: visible ? 2 : 0 + anchors.top: leadingSpace.bottom + + Rectangle { + id: shadow + width: 480 + height: 1 + color: hifi.colors.baseGrayShadow + x: -hifi.dimensions.contentMargin.x + } + + Rectangle { + width: 480 + height: 1 + color: hifi.colors.baseGrayHighlight + x: -hifi.dimensions.contentMargin.x + anchors.top: shadow.bottom + } + } + + Item { + id: heading + anchors { + left: parent.left + right: parent.right + top: topBar.bottom + } + height: isCollapsible ? 36 : 28 + + RalewayRegular { + id: title + anchors { + left: parent.left + top: parent.top + topMargin: 12 + } + size: hifi.fontSizes.sectionName + font.capitalization: Font.AllUppercase + text: name + color: hifi.colors.lightGrayText + } + + HiFiGlyphs { + anchors { + top: title.top + topMargin: -9 + right: parent.right + rightMargin: -4 + } + size: hifi.fontSizes.disclosureButton + text: isCollapsed ? hifi.glyphs.disclosureButtonExpand : hifi.glyphs.disclosureButtonCollapse + color: hifi.colors.lightGrayText + visible: isCollapsible + } + + MouseArea { + // Events are propogated so that any active control is defocused. + anchors.fill: parent + propagateComposedEvents: true + onPressed: { + toggleCollapsed(); + mouse.accepted = false; + } + } + } + + LinearGradient { + id: bottomBar + visible: false + width: 480 + height: visible ? 4 : 0 + x: -hifi.dimensions.contentMargin.x + anchors.top: heading.bottom + start: Qt.point(0, 0) + end: Qt.point(0, 4) + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.darkGray } + GradientStop { position: 1.0; color: hifi.colors.baseGray } // Equivalent of darkGray0 over baseGray background. + } + cached: true + } + } +} diff --git a/interface/resources/qml/controlsUit/TabletHeader.qml b/interface/resources/qml/controlsUit/TabletHeader.qml new file mode 100644 index 0000000000..f626700742 --- /dev/null +++ b/interface/resources/qml/controlsUit/TabletHeader.qml @@ -0,0 +1,34 @@ +// +// TabletHeader.qml +// +// Created by David Rowe on 11 Mar 2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +import "../stylesUit" + +Rectangle { + + property string title: "" + + HifiConstants { id: hifi } + + height: hifi.dimensions.tabletMenuHeader + z: 100 + + color: hifi.colors.darkGray + + RalewayBold { + text: title + size: 26 + color: hifi.colors.white + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: hifi.dimensions.contentMargin.x + } +} diff --git a/interface/resources/qml/controlsUit/TextAction.qml b/interface/resources/qml/controlsUit/TextAction.qml new file mode 100644 index 0000000000..a0a1bb7d07 --- /dev/null +++ b/interface/resources/qml/controlsUit/TextAction.qml @@ -0,0 +1,65 @@ +// +// TextField.qml +// +// Created by David Rowe on 21 Apr 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +import "../stylesUit" +import "." as HifiControls + +Item { + property string icon: "" + property int iconSize: 30 + property string text: "" + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + + signal clicked() + + height: Math.max(glyph.visible ? glyph.height - 4 : 0, string.visible ? string.height : 0) + width: glyph.width + string.anchors.leftMargin + string.width + + HiFiGlyphs { + id: glyph + anchors.left: parent.left + anchors.top: parent.top + anchors.topMargin: -2 + text: parent.icon + size: parent.iconSize + color: isLightColorScheme + ? (mouseArea.containsMouse ? hifi.colors.baseGrayHighlight : hifi.colors.lightGray) + : (mouseArea.containsMouse ? hifi.colors.faintGray : hifi.colors.lightGrayText) + visible: text !== "" + width: visible ? implicitWidth : 0 + } + + RalewaySemiBold { + id: string + anchors { + left: glyph.visible ? glyph.right : parent.left + leftMargin: visible && glyph.visible ? hifi.dimensions.contentSpacing.x : 0 + verticalCenter: glyph.visible ? glyph.verticalCenter : undefined + } + text: parent.text + size: hifi.fontSizes.inputLabel + color: isLightColorScheme + ? (mouseArea.containsMouse ? hifi.colors.baseGrayHighlight : hifi.colors.lightGray) + : (mouseArea.containsMouse ? hifi.colors.faintGray : hifi.colors.lightGrayText) + font.underline: true; + visible: text !== "" + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: parent.clicked() + } +} diff --git a/interface/resources/qml/controlsUit/TextEdit.qml b/interface/resources/qml/controlsUit/TextEdit.qml new file mode 100644 index 0000000000..7446c5040f --- /dev/null +++ b/interface/resources/qml/controlsUit/TextEdit.qml @@ -0,0 +1,23 @@ +// +// TextEdit.qml +// +// Created by Bradley Austin Davis on 24 Apr 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "../stylesUit" + +TextEdit { + + property real size: 32 + + font.family: "Raleway" + font.weight: Font.DemiBold + font.pointSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft +} diff --git a/interface/resources/qml/controlsUit/TextField.qml b/interface/resources/qml/controlsUit/TextField.qml new file mode 100644 index 0000000000..d78f3a1340 --- /dev/null +++ b/interface/resources/qml/controlsUit/TextField.qml @@ -0,0 +1,180 @@ +// +// TextField.qml +// +// Created by David Rowe on 17 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +import "../stylesUit" +import "." as HifiControls + +TextField { + id: textField + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + readonly property bool isFaintGrayColorScheme: colorScheme == hifi.colorSchemes.faintGray + property bool isSearchField: false + property string label: "" + property real controlHeight: height + (textFieldLabel.visible ? textFieldLabel.height + 1 : 0) + property bool hasDefocusedBorder: true; + property bool hasRoundedBorder: false + property int roundedBorderRadius: 4 + property bool error: false; + property bool hasClearButton: false; + property string leftPermanentGlyph: ""; + property string centerPlaceholderGlyph: ""; + + placeholderText: textField.placeholderText + + font.family: "Fira Sans" + font.pixelSize: hifi.fontSizes.textFieldInput + height: implicitHeight + 3 // Make surrounding box higher so that highlight is vertically centered. + property alias textFieldLabel: textFieldLabel + + y: textFieldLabel.visible ? textFieldLabel.height + textFieldLabel.anchors.bottomMargin : 0 + + // workaround for https://bugreports.qt.io/browse/QTBUG-49297 + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Return: + case Qt.Key_Enter: + event.accepted = true; + + // emit accepted signal manually + if (acceptableInput) { + accepted(); + } + } + } + + style: TextFieldStyle { + id: style; + textColor: { + if (isLightColorScheme) { + if (textField.activeFocus) { + hifi.colors.black + } else { + hifi.colors.lightGray + } + } else if (isFaintGrayColorScheme) { + if (textField.activeFocus) { + hifi.colors.black + } else { + hifi.colors.lightGray + } + } else { + if (textField.activeFocus) { + hifi.colors.white + } else { + hifi.colors.lightGrayText + } + } + } + background: Rectangle { + color: { + if (isLightColorScheme) { + if (textField.activeFocus) { + hifi.colors.white + } else { + hifi.colors.textFieldLightBackground + } + } else if (isFaintGrayColorScheme) { + if (textField.activeFocus) { + hifi.colors.white + } else { + hifi.colors.faintGray50 + } + } else { + if (textField.activeFocus) { + hifi.colors.black + } else { + hifi.colors.baseGrayShadow + } + } + } + border.color: textField.error ? hifi.colors.redHighlight : + (textField.activeFocus ? hifi.colors.primaryHighlight : (hasDefocusedBorder ? (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray) : color)) + border.width: textField.activeFocus || hasRoundedBorder || textField.error ? 1 : 0 + radius: isSearchField ? textField.height / 2 : (hasRoundedBorder ? roundedBorderRadius : 0) + + HiFiGlyphs { + text: textField.leftPermanentGlyph; + color: textColor; + size: hifi.fontSizes.textFieldSearchIcon; + anchors.left: parent.left; + anchors.verticalCenter: parent.verticalCenter; + anchors.leftMargin: hifi.dimensions.textPadding - 2; + visible: text; + } + + HiFiGlyphs { + text: textField.centerPlaceholderGlyph; + color: textColor; + size: parent.height; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.verticalCenter: parent.verticalCenter; + visible: text && !textField.focus && textField.text === ""; + } + + HiFiGlyphs { + text: hifi.glyphs.search + color: textColor + size: hifi.fontSizes.textFieldSearchIcon + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: hifi.dimensions.textPadding - 2 + visible: isSearchField + } + + HiFiGlyphs { + text: hifi.glyphs.error + color: textColor + size: 40 + anchors.right: parent.right + anchors.rightMargin: hifi.dimensions.textPadding - 2 + anchors.verticalCenter: parent.verticalCenter + visible: hasClearButton && textField.text !== ""; + + MouseArea { + anchors.fill: parent; + onClicked: { + textField.text = ""; + } + } + } + } + placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray + selectedTextColor: hifi.colors.black + selectionColor: hifi.colors.primaryHighlight + padding.left: hasRoundedBorder ? textField.height / 2 : ((isSearchField || textField.leftPermanentGlyph !== "") ? textField.height - 2 : 0) + hifi.dimensions.textPadding + padding.right: (hasClearButton ? textField.height - 2 : 0) + hifi.dimensions.textPadding + } + + HifiControls.Label { + id: textFieldLabel + text: textField.label + colorScheme: textField.colorScheme + anchors.left: parent.left + + Binding on anchors.right { + when: textField.right + value: textField.right + } + Binding on wrapMode { + when: textField.right + value: Text.WordWrap + } + + anchors.bottom: parent.top + anchors.bottomMargin: 3 + visible: label != "" + } +} diff --git a/interface/resources/qml/controlsUit/ToolTip.qml b/interface/resources/qml/controlsUit/ToolTip.qml new file mode 100644 index 0000000000..4fe36adcd5 --- /dev/null +++ b/interface/resources/qml/controlsUit/ToolTip.qml @@ -0,0 +1,51 @@ +// +// ToolTip.qml +// +// Created by Clement on 9/12/17 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +Item { + property string toolTip + property bool showToolTip: false + + Rectangle { + id: toolTipRectangle + anchors.right: parent.right + + width: toolTipText.width + 4 + height: toolTipText.height + 4 + opacity: (toolTip != "" && showToolTip) ? 1 : 0 + color: "#ffffaa" + border.color: "#0a0a0a" + Text { + id: toolTipText + text: toolTip + color: "black" + anchors.centerIn: parent + } + Behavior on opacity { + PropertyAnimation { + easing.type: Easing.InOutQuad + duration: 250 + } + } + } + MouseArea { + id: mouseArea + anchors.fill: parent + onEntered: showTimer.start() + onExited: { showToolTip = false; showTimer.stop(); } + hoverEnabled: true + } + Timer { + id: showTimer + interval: 250 + onTriggered: { showToolTip = true; } + } +} \ No newline at end of file diff --git a/interface/resources/qml/controlsUit/Tree.qml b/interface/resources/qml/controlsUit/Tree.qml new file mode 100644 index 0000000000..f2c49095b1 --- /dev/null +++ b/interface/resources/qml/controlsUit/Tree.qml @@ -0,0 +1,205 @@ +// +// Tree.qml +// +// Created by David Rowe on 17 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQml.Models 2.2 +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls 2.2 as QQC2 + + +import "../stylesUit" + +TreeView { + id: treeView + + property var treeModel: ListModel { } + property bool centerHeaderText: false + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + + property var modifyEl: function(index, data) { return false; } + + model: treeModel + selection: ItemSelectionModel { + id: selectionModel + model: treeModel + } + + anchors { left: parent.left; right: parent.right } + + headerVisible: false + + Component.onCompleted: { + if (flickableItem !== null && flickableItem !== undefined) { + treeView.flickableItem.QQC2.ScrollBar.vertical = scrollbar + } + } + + QQC2.ScrollBar { + id: scrollbar + parent: treeView.flickableItem + policy: QQC2.ScrollBar.AsNeeded + orientation: Qt.Vertical + visible: size < 1.0 + topPadding: treeView.headerVisible ? hifi.dimensions.tableHeaderHeight + 1 : 1 + anchors.top: treeView.top + anchors.left: treeView.right + anchors.bottom: treeView.bottom + + background: Item { + implicitWidth: hifi.dimensions.scrollbarBackgroundWidth + Rectangle { + anchors { + fill: parent; + topMargin: treeView.headerVisible ? hifi.dimensions.tableHeaderHeight: 0 + } + color: isLightColorScheme ? hifi.colors.tableScrollBackgroundLight + : hifi.colors.tableScrollBackgroundDark + } + } + + contentItem: Item { + implicitWidth: hifi.dimensions.scrollbarHandleWidth + Rectangle { + anchors.fill: parent + radius: (width - 4)/2 + color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark + } + } + } + + // Use rectangle to draw border with rounded corners. + frameVisible: false + Rectangle { + color: "#00000000" + anchors.fill: parent + radius: hifi.dimensions.borderRadius + border.color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + border.width: 2 + anchors.margins: -2 + } + anchors.margins: 2 // Shrink TreeView to lie within border. + + backgroundVisible: true + + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff + + style: TreeViewStyle { + // Needed in order for rows to keep displaying rows after end of table entries. + backgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightEven : hifi.colors.tableRowDarkEven + alternateBackgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd + + headerDelegate: Rectangle { + height: hifi.dimensions.tableHeaderHeight + color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark + + RalewayRegular { + id: titleText + text: styleData.value + size: hifi.fontSizes.tableHeading + font.capitalization: Font.AllUppercase + color: hifi.colors.baseGrayHighlight + horizontalAlignment: (centerHeaderText ? Text.AlignHCenter : Text.AlignLeft) + elide: Text.ElideRight + anchors { + left: parent.left + leftMargin: hifi.dimensions.tablePadding + right: sortIndicatorVisible && sortIndicatorColumn === styleData.column ? titleSort.left : parent.right + rightMargin: hifi.dimensions.tablePadding + verticalCenter: parent.verticalCenter + } + } + + HiFiGlyphs { + id: titleSort + text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn + color: isLightColorScheme ? hifi.colors.darkGray : hifi.colors.baseGrayHighlight + opacity: 0.6; + size: hifi.fontSizes.tableHeadingIcon + anchors { + right: parent.right + verticalCenter: titleText.verticalCenter + } + visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column + } + + Rectangle { + width: 1 + anchors { + left: parent.left + top: parent.top + topMargin: 1 + bottom: parent.bottom + bottomMargin: 2 + } + color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + visible: styleData.column > 0 + } + + Rectangle { + height: 1 + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + } + } + + branchDelegate: HiFiGlyphs { + text: styleData.isExpanded ? hifi.glyphs.caratDn : hifi.glyphs.caratR + size: hifi.fontSizes.carat + color: colorScheme == hifi.colorSchemes.light + ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) + : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) + anchors { + left: parent ? parent.left : undefined + leftMargin: hifi.dimensions.tablePadding / 2 + } + } + } + + rowDelegate: Rectangle { + height: hifi.dimensions.tableRowHeight + color: styleData.selected + ? hifi.colors.primaryHighlight + : treeView.isLightColorScheme + ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) + : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd) + } + + itemDelegate: FiraSansSemiBold { + anchors { + left: parent ? parent.left : undefined + leftMargin: (2 + styleData.depth) * hifi.dimensions.tablePadding + right: parent ? parent.right : undefined + rightMargin: hifi.dimensions.tablePadding + verticalCenter: parent ? parent.verticalCenter : undefined + } + + text: styleData.value + size: hifi.fontSizes.tableText + color: colorScheme == hifi.colorSchemes.light + ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) + : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) + + elide: Text.ElideRight + } + + Item { + id: unfocusHelper + visible: false + } + + onDoubleClicked: isExpanded(index) ? collapse(index) : expand(index) +} diff --git a/interface/resources/qml/controlsUit/VerticalSpacer.qml b/interface/resources/qml/controlsUit/VerticalSpacer.qml new file mode 100644 index 0000000000..4c93aa1801 --- /dev/null +++ b/interface/resources/qml/controlsUit/VerticalSpacer.qml @@ -0,0 +1,21 @@ +// +// VerticalSpacer.qml +// +// Created by David Rowe on 16 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +import "../stylesUit" + +Item { + id: root + property alias size: root.height + + width: 1 // Must be non-zero + height: hifi.dimensions.controlInterlineHeight +} diff --git a/interface/resources/qml/controlsUit/WebGlyphButton.qml b/interface/resources/qml/controlsUit/WebGlyphButton.qml new file mode 100644 index 0000000000..7739ecd5e7 --- /dev/null +++ b/interface/resources/qml/controlsUit/WebGlyphButton.qml @@ -0,0 +1,40 @@ +// +// GlyphButton.qml +// +// Created by Vlad Stelmahovsky on 2017-06-21 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 2.2 as Original + +import "../stylesUit" + +Original.Button { + id: control + + property int colorScheme: hifi.colorSchemes.light + property string glyph: "" + property int size: 32 + //colors + readonly property color normalColor: "#AFAFAF" + readonly property color hoverColor: "#00B4EF" + readonly property color clickedColor: "#FFFFFF" + readonly property color disabledColor: "#575757" + + background: Item {} + + contentItem: HiFiGlyphs { + color: control.enabled ? (control.pressed ? control.clickedColor : + (control.hovered ? control.hoverColor : control.normalColor)) : + control.disabledColor + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: control.glyph + size: control.size + } +} + diff --git a/interface/resources/qml/controlsUit/WebSpinner.qml b/interface/resources/qml/controlsUit/WebSpinner.qml new file mode 100644 index 0000000000..e8e01c4865 --- /dev/null +++ b/interface/resources/qml/controlsUit/WebSpinner.qml @@ -0,0 +1,24 @@ +// +// WebSpinner.qml +// +// Created by David Rowe on 23 May 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtWebEngine 1.5 + +AnimatedImage { + property WebEngineView webview: parent + source: "../../icons/loader-snake-64-w.gif" + visible: webview.loading && /^(http.*|)$/i.test(webview.url.toString()) + playing: visible + z: 10000 + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } +} diff --git a/interface/resources/qml/controlsUit/WebView.qml b/interface/resources/qml/controlsUit/WebView.qml new file mode 100644 index 0000000000..2895f36944 --- /dev/null +++ b/interface/resources/qml/controlsUit/WebView.qml @@ -0,0 +1,21 @@ +// +// WebView.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "." + +BaseWebView { + onNewViewRequested: { + // Load dialog via OffscreenUi so that JavaScript EventBridge is available. + var browser = OffscreenUi.load("Browser.qml"); + request.openIn(browser.webView); + browser.webView.forceActiveFocus(); + } +} diff --git a/interface/resources/qml/controls-uit/qmldir b/interface/resources/qml/controlsUit/qmldir similarity index 94% rename from interface/resources/qml/controls-uit/qmldir rename to interface/resources/qml/controlsUit/qmldir index 989115b8d2..d0577f5575 100644 --- a/interface/resources/qml/controls-uit/qmldir +++ b/interface/resources/qml/controlsUit/qmldir @@ -6,6 +6,7 @@ CheckBox 1.0 CheckBox.qml CheckBoxQQC2 1.0 CheckBoxQQC2.qml ComboBox 1.0 ComboBox.qml ContentSection 1.0 ContentSection.qml +FilterBar 1.0 FilterBar.qml GlyphButton 1.0 GlyphButton.qml HorizontalRule 1.0 HorizontalRule.qml HorizontalSpacer 1.0 HorizontalSpacer.qml @@ -15,6 +16,7 @@ Keyboard 1.0 Keyboard.qml Label 1.0 Label.qml QueuedButton 1.0 QueuedButton.qml RadioButton 1.0 RadioButton.qml +ScrollBar 1.0 ScrollBar.qml Separator 1.0 Separator.qml Slider 1.0 Slider.qml SpinBox 1.0 SpinBox.qml diff --git a/interface/resources/qml/dialogs/AssetDialog.qml b/interface/resources/qml/dialogs/AssetDialog.qml index e8d28e9b37..b8eaab0b8d 100644 --- a/interface/resources/qml/dialogs/AssetDialog.qml +++ b/interface/resources/qml/dialogs/AssetDialog.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import Qt.labs.settings 1.0 -import "../styles-uit" +import stylesUit 1.0 import "../windows" import "assetDialog" diff --git a/interface/resources/qml/dialogs/CustomQueryDialog.qml b/interface/resources/qml/dialogs/CustomQueryDialog.qml index 0c86b93c4b..026068eee1 100644 --- a/interface/resources/qml/dialogs/CustomQueryDialog.qml +++ b/interface/resources/qml/dialogs/CustomQueryDialog.qml @@ -12,8 +12,8 @@ import QtQuick 2.7; import QtQuick.Dialogs 1.2 as OriginalDialogs; import QtQuick.Controls 2.3 -import "../controls-uit"; -import "../styles-uit"; +import controlsUit 1.0 +import stylesUit 1.0 import "../windows"; ModalWindow { diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 6651af0db3..b7340575dd 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -16,8 +16,8 @@ import QtQuick.Controls 1.4 as QQC1 import QtQuick.Controls 2.3 import ".." -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" import "fileDialog" diff --git a/interface/resources/qml/dialogs/MessageDialog.qml b/interface/resources/qml/dialogs/MessageDialog.qml index b5ac6cab72..9428e3ab6e 100644 --- a/interface/resources/qml/dialogs/MessageDialog.qml +++ b/interface/resources/qml/dialogs/MessageDialog.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import QtQuick.Dialogs 1.2 as OriginalDialogs -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" import "messageDialog" diff --git a/interface/resources/qml/dialogs/PreferencesDialog.qml b/interface/resources/qml/dialogs/PreferencesDialog.qml index fffd0e2ed9..9df1d0b963 100644 --- a/interface/resources/qml/dialogs/PreferencesDialog.qml +++ b/interface/resources/qml/dialogs/PreferencesDialog.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import "../controls-uit" as HifiControls -import "../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 import "../windows" import "preferences" diff --git a/interface/resources/qml/dialogs/QueryDialog.qml b/interface/resources/qml/dialogs/QueryDialog.qml index 41ee30e6d5..9cfb3011bd 100644 --- a/interface/resources/qml/dialogs/QueryDialog.qml +++ b/interface/resources/qml/dialogs/QueryDialog.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" ModalWindow { diff --git a/interface/resources/qml/dialogs/TabletAssetDialog.qml b/interface/resources/qml/dialogs/TabletAssetDialog.qml index 897378e40c..b3bd45f972 100644 --- a/interface/resources/qml/dialogs/TabletAssetDialog.qml +++ b/interface/resources/qml/dialogs/TabletAssetDialog.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../styles-uit" +import stylesUit 1.0 import "../windows" import "assetDialog" diff --git a/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml index 81a2c5c1e0..c7772984ab 100644 --- a/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml +++ b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Dialogs 1.2 as OriginalDialogs import QtQuick.Controls 2.3 -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" TabletModalWindow { diff --git a/interface/resources/qml/dialogs/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml index 6848c230e3..3be6e30dd0 100644 --- a/interface/resources/qml/dialogs/TabletFileDialog.qml +++ b/interface/resources/qml/dialogs/TabletFileDialog.qml @@ -16,8 +16,8 @@ import QtQuick.Controls 1.4 as QQC1 import QtQuick.Controls 2.3 import ".." -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" import "fileDialog" diff --git a/interface/resources/qml/dialogs/TabletLoginDialog.qml b/interface/resources/qml/dialogs/TabletLoginDialog.qml index c85b2b2ba0..dad2bb91aa 100644 --- a/interface/resources/qml/dialogs/TabletLoginDialog.qml +++ b/interface/resources/qml/dialogs/TabletLoginDialog.qml @@ -11,8 +11,8 @@ import Hifi 1.0 import QtQuick 2.5 -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" import "../LoginDialog" @@ -95,6 +95,18 @@ TabletModalWindow { } } + Timer { + id: keyboardTimer + repeat: false + interval: 200 + + onTriggered: { + if (MenuInterface.isOptionChecked("Use 3D Keyboard")) { + KeyboardScriptingInterface.raised = true; + } + } + } + TabletModalFrame { id: mfRoot @@ -127,6 +139,14 @@ TabletModalWindow { } } + Component.onDestruction: { + loginKeyboard.raised = false; + } + + Component.onCompleted: { + keyboardTimer.start(); + } + Keyboard { id: loginKeyboard raised: root.keyboardEnabled && root.keyboardRaised diff --git a/interface/resources/qml/dialogs/TabletMessageBox.qml b/interface/resources/qml/dialogs/TabletMessageBox.qml index fabe0dd247..1e6f0734ad 100644 --- a/interface/resources/qml/dialogs/TabletMessageBox.qml +++ b/interface/resources/qml/dialogs/TabletMessageBox.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import QtQuick.Dialogs 1.2 as OriginalDialogs -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" import "messageDialog" diff --git a/interface/resources/qml/dialogs/TabletQueryDialog.qml b/interface/resources/qml/dialogs/TabletQueryDialog.qml index 5746a3d67c..8f63730b8e 100644 --- a/interface/resources/qml/dialogs/TabletQueryDialog.qml +++ b/interface/resources/qml/dialogs/TabletQueryDialog.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Dialogs 1.2 as OriginalDialogs import QtQuick.Controls 2.3 -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" TabletModalWindow { diff --git a/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml b/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml index c3e842bc2f..da976ef3e1 100644 --- a/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml +++ b/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 import QtQuick.Controls 1.5 as QQC1 -import "../../controls-uit" -import "../../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../fileDialog" diff --git a/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml b/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml index 50a10974b5..6c042b5598 100644 --- a/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml +++ b/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../../controls-uit" +import controlsUit 1.0 ComboBox { id: root diff --git a/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml b/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml index 8411980db7..f5715fa2c2 100644 --- a/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml +++ b/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtQuick.Dialogs 1.2 -import "../../controls-uit" +import controlsUit 1.0 Button { property var dialog; diff --git a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml index 0efc3776b3..9505e70530 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../../controls-uit" +import controlsUit 1.0 import "../../hifi/tablet/tabletWindows/preferences" Preference { diff --git a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml index 2cf50891c9..6059f8ff1c 100644 --- a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml +++ b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "../../dialogs" -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/ButtonPreference.qml b/interface/resources/qml/dialogs/preferences/ButtonPreference.qml index 454a9124ae..09c5b4329d 100644 --- a/interface/resources/qml/dialogs/preferences/ButtonPreference.qml +++ b/interface/resources/qml/dialogs/preferences/ButtonPreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import TabletScriptingInterface 1.0 -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml index e2172d8eda..f6f840bbe8 100644 --- a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import TabletScriptingInterface 1.0 -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml b/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml index 3b3efaf520..98cb397976 100644 --- a/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import "../../controls-uit" as HiFiControls -import "../../styles-uit" +import controlsUit 1.0 as HiFiControls +import stylesUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/EditablePreference.qml b/interface/resources/qml/dialogs/preferences/EditablePreference.qml index 8acf8e1f76..e0c79ebba0 100644 --- a/interface/resources/qml/dialogs/preferences/EditablePreference.qml +++ b/interface/resources/qml/dialogs/preferences/EditablePreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "../../dialogs" -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/PrimaryHandPreference.qml b/interface/resources/qml/dialogs/preferences/PrimaryHandPreference.qml index cfc2e94ed9..f963003c59 100644 --- a/interface/resources/qml/dialogs/preferences/PrimaryHandPreference.qml +++ b/interface/resources/qml/dialogs/preferences/PrimaryHandPreference.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml b/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml index 103904a666..0a09d8d609 100644 --- a/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml +++ b/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import "../../controls-uit" -import "../../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index c2c6583b7e..a9b755ad83 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -12,8 +12,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import Hifi 1.0 -import "../../controls-uit" as HiFiControls -import "../../styles-uit" +import controlsUit 1.0 as HiFiControls +import stylesUit 1.0 import "." Preference { diff --git a/interface/resources/qml/dialogs/preferences/SliderPreference.qml b/interface/resources/qml/dialogs/preferences/SliderPreference.qml index 2bdda09fc3..c8a2aae158 100644 --- a/interface/resources/qml/dialogs/preferences/SliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SliderPreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "../../dialogs" -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml index b2c334b674..1b080c2759 100644 --- a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index 126e62fc30..cbc804d9d7 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "../../dialogs" -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/hifi/+android/ActionBar.qml b/interface/resources/qml/hifi/+android/ActionBar.qml index d487901d6f..3c58156f30 100644 --- a/interface/resources/qml/hifi/+android/ActionBar.qml +++ b/interface/resources/qml/hifi/+android/ActionBar.qml @@ -3,8 +3,8 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls import ".." diff --git a/interface/resources/qml/hifi/+android/AudioBar.qml b/interface/resources/qml/hifi/+android/AudioBar.qml index 6cc17fccf7..912572fdf8 100644 --- a/interface/resources/qml/hifi/+android/AudioBar.qml +++ b/interface/resources/qml/hifi/+android/AudioBar.qml @@ -3,8 +3,8 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls import ".." diff --git a/interface/resources/qml/hifi/+android/AvatarOption.qml b/interface/resources/qml/hifi/+android/AvatarOption.qml index 85d7e52eb2..7eba3c2a67 100644 --- a/interface/resources/qml/hifi/+android/AvatarOption.qml +++ b/interface/resources/qml/hifi/+android/AvatarOption.qml @@ -11,7 +11,7 @@ import QtQuick.Layouts 1.3 import QtQuick 2.5 -import "../controls-uit" as HifiControlsUit +import controlsUit 1.0 as HifiControlsUit ColumnLayout { id: itemRoot diff --git a/interface/resources/qml/hifi/+android/StatsBar.qml b/interface/resources/qml/hifi/+android/StatsBar.qml index aee438b44f..64e93b4a08 100644 --- a/interface/resources/qml/hifi/+android/StatsBar.qml +++ b/interface/resources/qml/hifi/+android/StatsBar.qml @@ -3,8 +3,8 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls import ".." diff --git a/interface/resources/qml/hifi/+android/WindowHeader.qml b/interface/resources/qml/hifi/+android/WindowHeader.qml index 4ec0a0c6e6..5316fc4786 100644 --- a/interface/resources/qml/hifi/+android/WindowHeader.qml +++ b/interface/resources/qml/hifi/+android/WindowHeader.qml @@ -16,8 +16,8 @@ import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 import "." import "../styles" as HifiStyles -import "../styles-uit" -import "../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../controls" as HifiControls import ".." diff --git a/interface/resources/qml/hifi/+android/bottomHudOptions.qml b/interface/resources/qml/hifi/+android/bottomHudOptions.qml index 22beccf531..6b830d94c2 100644 --- a/interface/resources/qml/hifi/+android/bottomHudOptions.qml +++ b/interface/resources/qml/hifi/+android/bottomHudOptions.qml @@ -16,8 +16,8 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 import "../../styles" as HifiStyles -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls import ".." import "." diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android/modesbar.qml index 994bf1efe4..1bf04fb8d9 100644 --- a/interface/resources/qml/hifi/+android/modesbar.qml +++ b/interface/resources/qml/hifi/+android/modesbar.qml @@ -3,8 +3,8 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls import ".." diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 1a7f5bac40..ad337a6361 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -14,8 +14,8 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 -import "../styles-uit" -import "../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../windows" as Windows import "../dialogs" diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index aea5931627..9635681c34 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -3,8 +3,8 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import QtQml.Models 2.1 import QtGraphicalEffects 1.0 -import "../controls-uit" as HifiControls -import "../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 import "avatarapp" Rectangle { @@ -19,7 +19,7 @@ Rectangle { HifiControls.Keyboard { id: keyboard z: 1000 - raised: parent.keyboardEnabled && parent.keyboardRaised + raised: parent.keyboardEnabled && parent.keyboardRaised && HMD.active numeric: parent.punctuationMode anchors { left: parent.left @@ -204,7 +204,8 @@ Rectangle { property bool isInManageState: false - Component.onCompleted: { + Component.onDestruction: { + keyboard.raised = false; } AvatarAppStyle { @@ -235,6 +236,8 @@ Rectangle { avatarIconVisible: mainPageVisible settingsButtonVisible: mainPageVisible onSettingsClicked: { + displayNameInput.focus = false; + root.keyboardRaised = false; settings.open(currentAvatarSettings, currentAvatar.avatarScale); } } @@ -344,6 +347,10 @@ Rectangle { emitSendToScript({'method' : 'changeDisplayName', 'displayName' : text}) focus = false; } + + onFocusChanged: { + root.keyboardRaised = focus; + } } ShadowImage { diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 83bf1e2c54..7f29324416 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -17,7 +17,7 @@ import QtGraphicalEffects 1.0 import TabletScriptingInterface 1.0 import "toolbars" -import "../styles-uit" +import stylesUit 1.0 Item { id: root; diff --git a/interface/resources/qml/hifi/ComboDialog.qml b/interface/resources/qml/hifi/ComboDialog.qml index e5dc8a9c1a..74d9c1019b 100644 --- a/interface/resources/qml/hifi/ComboDialog.qml +++ b/interface/resources/qml/hifi/ComboDialog.qml @@ -10,8 +10,8 @@ // import QtQuick 2.5 -import "../styles-uit" -import "../controls-uit" +import stylesUit 1.0 +import controlsUit 1.0 Item { property var dialogTitleText : ""; diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 4d342fe775..511d9377e5 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -8,7 +8,7 @@ import "../desktop" as OriginalDesktop import ".." import "." import "./toolbars" -import "../controls-uit" +import controlsUit 1.0 OriginalDesktop.Desktop { id: desktop diff --git a/interface/resources/qml/hifi/DesktopLetterboxMessage.qml b/interface/resources/qml/hifi/DesktopLetterboxMessage.qml index 9e9dcc75b2..048add24e5 100644 --- a/interface/resources/qml/hifi/DesktopLetterboxMessage.qml +++ b/interface/resources/qml/hifi/DesktopLetterboxMessage.qml @@ -10,7 +10,7 @@ // import QtQuick 2.5 -import "../styles-uit" +import stylesUit 1.0 Item { property alias text: popupText.text diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 346481fe1f..4cfd4804b3 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -15,7 +15,7 @@ import Hifi 1.0 import QtQuick 2.5 import QtGraphicalEffects 1.0 import "toolbars" -import "../styles-uit" +import stylesUit 1.0 import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. Column { diff --git a/interface/resources/qml/hifi/LetterboxMessage.qml b/interface/resources/qml/hifi/LetterboxMessage.qml index 8a18d88842..68bebdd041 100644 --- a/interface/resources/qml/hifi/LetterboxMessage.qml +++ b/interface/resources/qml/hifi/LetterboxMessage.qml @@ -10,7 +10,7 @@ // import QtQuick 2.5 -import "../styles-uit" +import stylesUit 1.0 Item { property alias text: popupText.text diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index dfa6555150..242ca5ab57 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -13,8 +13,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtGraphicalEffects 1.0 -import "../styles-uit" -import "../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "toolbars" // references Users, UserActivityLogger, MyAvatar, Vec3, Quat, AddressManager, Account from root context diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 1384cb8711..368beaab47 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -15,8 +15,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtGraphicalEffects 1.0 import Qt.labs.settings 1.0 -import "../styles-uit" -import "../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../controls" as HifiControls import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. diff --git a/interface/resources/qml/hifi/SkyboxChanger.qml b/interface/resources/qml/hifi/SkyboxChanger.qml index f0c97a11a3..a66fc38415 100644 --- a/interface/resources/qml/hifi/SkyboxChanger.qml +++ b/interface/resources/qml/hifi/SkyboxChanger.qml @@ -10,8 +10,8 @@ // import QtQuick 2.5 -import "../styles-uit" -import "../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import QtQuick.Controls 2.2 Item { diff --git a/interface/resources/qml/hifi/SpectatorCamera.qml b/interface/resources/qml/hifi/SpectatorCamera.qml index 4bf80e410b..09b722b906 100644 --- a/interface/resources/qml/hifi/SpectatorCamera.qml +++ b/interface/resources/qml/hifi/SpectatorCamera.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.7 -import "../styles-uit" -import "../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../controls" as HifiControls // references HMD, XXX from root context diff --git a/interface/resources/qml/hifi/TabletTextButton.qml b/interface/resources/qml/hifi/TabletTextButton.qml index e5ff1d381d..6c9e0331df 100644 --- a/interface/resources/qml/hifi/TabletTextButton.qml +++ b/interface/resources/qml/hifi/TabletTextButton.qml @@ -10,7 +10,7 @@ import Hifi 1.0 import QtQuick 2.4 -import "../styles-uit" +import stylesUit 1.0 Rectangle { property alias text: label.text diff --git a/interface/resources/qml/hifi/TextButton.qml b/interface/resources/qml/hifi/TextButton.qml index 02e49d86e4..61588a9603 100644 --- a/interface/resources/qml/hifi/TextButton.qml +++ b/interface/resources/qml/hifi/TextButton.qml @@ -9,7 +9,7 @@ // import Hifi 1.0 import QtQuick 2.4 -import "../styles-uit" +import stylesUit 1.0 Rectangle { property alias text: label.text; diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index ab93752d92..c05de26471 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -18,8 +18,8 @@ import QtGraphicalEffects 1.0 import QtWebEngine 1.5 import QtWebChannel 1.0 -import "../styles-uit" -import "../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../windows" import "../controls" diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index f4a708567a..c8dd83cd62 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -15,8 +15,8 @@ import QtQuick 2.5 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" import "./" as AudioControls diff --git a/interface/resources/qml/hifi/audio/AudioTabButton.qml b/interface/resources/qml/hifi/audio/AudioTabButton.qml index 3a3ed90f5e..32331ccb6e 100644 --- a/interface/resources/qml/hifi/audio/AudioTabButton.qml +++ b/interface/resources/qml/hifi/audio/AudioTabButton.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../../controls-uit" as HifiControls -import "../../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 TabButton { id: control diff --git a/interface/resources/qml/hifi/audio/CheckBox.qml b/interface/resources/qml/hifi/audio/CheckBox.qml index 3a954d4004..5ab62a5091 100644 --- a/interface/resources/qml/hifi/audio/CheckBox.qml +++ b/interface/resources/qml/hifi/audio/CheckBox.qml @@ -11,7 +11,7 @@ import QtQuick 2.7 -import "../../controls-uit" as HifiControls +import controlsUit 1.0 as HifiControls HifiControls.CheckBoxQQC2 { color: "white" diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml index 2b9599a3cc..cfe55af9c4 100644 --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml @@ -13,8 +13,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls RowLayout { property var sound: null; diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index 5fff14e4a1..0740914440 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -1,9 +1,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtQuick.Layouts 1.3 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit -import "../../controls" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit Rectangle { id: root; diff --git a/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml b/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml index 9d9db010fb..d3c9cd1d5f 100644 --- a/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml +++ b/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml @@ -1,6 +1,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../styles-uit" +import stylesUit 1.0 ShadowRectangle { id: header diff --git a/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml b/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml index f66c7121cb..36cb4b1080 100644 --- a/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml +++ b/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 import QtQuick.Window 2.2 -import "../../styles-uit" +import stylesUit 1.0 QtObject { readonly property QtObject colors: QtObject { diff --git a/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml b/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml index cb73e9fe71..8b28d4c66b 100644 --- a/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml +++ b/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml @@ -1,6 +1,6 @@ import QtQuick 2.9 -import "../../controls-uit" -import "../../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 ShadowRectangle { property int wearablesCount: 0 diff --git a/interface/resources/qml/hifi/avatarapp/BlueButton.qml b/interface/resources/qml/hifi/avatarapp/BlueButton.qml index e668951517..0cc84d5ba0 100644 --- a/interface/resources/qml/hifi/avatarapp/BlueButton.qml +++ b/interface/resources/qml/hifi/avatarapp/BlueButton.qml @@ -1,6 +1,6 @@ import QtQuick 2.5 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit HifiControlsUit.Button { HifiConstants { diff --git a/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml b/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml index 1387c0791a..780981a5a3 100644 --- a/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml +++ b/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml @@ -1,7 +1,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls Rectangle { diff --git a/interface/resources/qml/hifi/avatarapp/InputField.qml b/interface/resources/qml/hifi/avatarapp/InputField.qml index 905518ef0f..2020d56c96 100644 --- a/interface/resources/qml/hifi/avatarapp/InputField.qml +++ b/interface/resources/qml/hifi/avatarapp/InputField.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 import QtQuick.Controls 2.2 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit TextField { id: textField diff --git a/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml b/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml index 4b868b47ce..6c2101498c 100644 --- a/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml +++ b/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml @@ -1,5 +1,5 @@ -import "../../controls-uit" as HifiControlsUit -import "../../styles-uit" +import controlsUit 1.0 as HifiControlsUit +import stylesUit 1.0 import QtQuick 2.0 import QtQuick.Controls 2.2 diff --git a/interface/resources/qml/hifi/avatarapp/MessageBox.qml b/interface/resources/qml/hifi/avatarapp/MessageBox.qml index f111303214..eb28745b1a 100644 --- a/interface/resources/qml/hifi/avatarapp/MessageBox.qml +++ b/interface/resources/qml/hifi/avatarapp/MessageBox.qml @@ -1,7 +1,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls Rectangle { diff --git a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml index b7782c697d..89a8eff025 100644 --- a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml +++ b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml @@ -63,8 +63,8 @@ MessageBox { popup.dialogButtons.yesButton.fontCapitalization = Font.MixedCase; popup.button1text = 'CANCEL' popup.titleText = 'Get Wearables' - popup.bodyText = 'Buy wearables from <b><a href="app://marketplace">Marketplace.</a></b>' + '<br/>' + - 'Wear wearable from <b><a href="app://purchases">My Purchases.</a></b>' + '<br/>' + '<br/>' + + popup.bodyText = 'Get wearables from <b><a href="app://marketplace">Marketplace.</a></b>' + '<br/>' + + 'Wear wearable from <b><a href="app://purchases">Inventory.</a></b>' + '<br/>' + '<br/>' + 'Visit “AvatarIsland” to get wearables' popup.imageSource = getWearablesUrl; @@ -89,7 +89,7 @@ MessageBox { function showDeleteFavorite(favoriteName, callback) { popup.titleText = 'Delete Favorite: {AvatarName}'.replace('{AvatarName}', favoriteName) - popup.bodyText = 'This will delete your favorite. You will retain access to the wearables and avatar that made up the favorite from My Purchases.' + popup.bodyText = 'This will delete your favorite. You will retain access to the wearables and avatar that made up the favorite from Inventory.' popup.imageSource = null; popup.button1text = 'CANCEL' popup.button2text = 'DELETE' @@ -128,8 +128,8 @@ MessageBox { popup.button1text = 'CANCEL' popup.titleText = 'Get Avatars' - popup.bodyText = 'Buy avatars from <b><a href="app://marketplace">Marketplace.</a></b>' + '<br/>' + - 'Wear avatars in <b><a href="app://purchases">My Purchases.</a></b>' + '<br/>' + '<br/>' + + popup.bodyText = 'Get avatars from <b><a href="app://marketplace">Marketplace.</a></b>' + '<br/>' + + 'Wear avatars in <b><a href="app://purchases">Inventory.</a></b>' + '<br/>' + '<br/>' + 'Visit “BodyMart” to get free avatars.' popup.imageSource = getAvatarsUrl; diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index 71bfbb084d..cd892c17b1 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -2,8 +2,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls Rectangle { @@ -14,6 +14,22 @@ Rectangle { signal scaleChanged(real scale); + property bool keyboardEnabled: true + property bool keyboardRaised: false + property bool punctuationMode: false + + HifiControlsUit.Keyboard { + id: keyboard + z: 1000 + raised: parent.keyboardEnabled && parent.keyboardRaised + numeric: parent.punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + } + property alias onSaveClicked: dialogButtons.onYesClicked property alias onCancelClicked: dialogButtons.onNoClicked @@ -31,7 +47,7 @@ Rectangle { scaleSlider.notify = false; scaleSlider.value = Math.round(avatarScale * 10); - scaleSlider.notify = true;; + scaleSlider.notify = true; if (settings.dominantHand === 'left') { leftHandRadioButton.checked = true; @@ -314,6 +330,10 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right placeholderText: 'user\\file\\dir' + + onFocusChanged: { + keyboardRaised = (avatarAnimationUrlInputText.focus || avatarCollisionSoundUrlInputText.focus); + } } } @@ -340,6 +360,10 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right placeholderText: 'https://hifi-public.s3.amazonaws.com/sounds/Collisions-' + + onFocusChanged: { + keyboardRaised = (avatarAnimationUrlInputText.focus || avatarCollisionSoundUrlInputText.focus); + } } } diff --git a/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml b/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml index c2d84bb371..a2c84fad47 100644 --- a/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml +++ b/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml @@ -1,4 +1,4 @@ -import "../../styles-uit" +import stylesUit 1.0 import QtQuick 2.9 import QtGraphicalEffects 1.0 diff --git a/interface/resources/qml/hifi/avatarapp/ShadowImage.qml b/interface/resources/qml/hifi/avatarapp/ShadowImage.qml index 3995446e49..51e1043702 100644 --- a/interface/resources/qml/hifi/avatarapp/ShadowImage.qml +++ b/interface/resources/qml/hifi/avatarapp/ShadowImage.qml @@ -1,4 +1,4 @@ -import "../../styles-uit" +import stylesUit 1.0 import QtQuick 2.9 import QtGraphicalEffects 1.0 diff --git a/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml b/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml index 741fce3d8d..3968fcb1ff 100644 --- a/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml +++ b/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml @@ -1,4 +1,4 @@ -import "../../styles-uit" +import stylesUit 1.0 import QtQuick 2.9 import QtGraphicalEffects 1.0 diff --git a/interface/resources/qml/hifi/avatarapp/SquareLabel.qml b/interface/resources/qml/hifi/avatarapp/SquareLabel.qml index e2c456ec04..69aff47373 100644 --- a/interface/resources/qml/hifi/avatarapp/SquareLabel.qml +++ b/interface/resources/qml/hifi/avatarapp/SquareLabel.qml @@ -1,5 +1,5 @@ -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import QtQuick 2.9 import QtGraphicalEffects 1.0 diff --git a/interface/resources/qml/hifi/avatarapp/Vector3.qml b/interface/resources/qml/hifi/avatarapp/Vector3.qml index d77665f992..698123104f 100644 --- a/interface/resources/qml/hifi/avatarapp/Vector3.qml +++ b/interface/resources/qml/hifi/avatarapp/Vector3.qml @@ -1,7 +1,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls Row { diff --git a/interface/resources/qml/hifi/avatarapp/WhiteButton.qml b/interface/resources/qml/hifi/avatarapp/WhiteButton.qml index dc729ae097..d0a4a152db 100644 --- a/interface/resources/qml/hifi/avatarapp/WhiteButton.qml +++ b/interface/resources/qml/hifi/avatarapp/WhiteButton.qml @@ -1,6 +1,6 @@ import QtQuick 2.5 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit HifiControlsUit.Button { HifiConstants { diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index b13f23f17d..62c3675ba8 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtQuick.Controls 1.4 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "../wallet" as HifiWallet import "../common" as HifiCommerceCommon @@ -55,6 +55,7 @@ Rectangle { property bool isInstalled; property bool isUpdating; property string baseAppURL; + property int currentUpdatesPage: 1; // Style color: hifi.colors.white; Connections { @@ -156,8 +157,14 @@ Rectangle { break; } } - root.availableUpdatesReceived = true; - refreshBuyUI(); + + if (result.data.updates.length === 0 || root.isUpdating) { + root.availableUpdatesReceived = true; + refreshBuyUI(); + } else { + root.currentUpdatesPage++; + Commerce.getAvailableUpdates(root.itemId, currentUpdatesPage) + } } } @@ -176,6 +183,7 @@ Rectangle { root.ownershipStatusReceived = false; Commerce.alreadyOwned(root.itemId); root.availableUpdatesReceived = false; + root.currentUpdatesPage = 1; Commerce.getAvailableUpdates(root.itemId); itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg"; } @@ -240,11 +248,6 @@ Rectangle { lightboxPopup.button1method = function() { lightboxPopup.visible = false; } - lightboxPopup.button2text = "GO TO WALLET"; - lightboxPopup.button2method = function() { - lightboxPopup.visible = false; - sendToScript({method: 'checkout_openWallet'}); - }; lightboxPopup.visible = true; } else { sendToScript(msg); @@ -383,7 +386,7 @@ Rectangle { anchors.leftMargin: 16; width: paintedWidth; height: paintedHeight; - text: "Review Purchase:"; + text: "Review:"; color: hifi.colors.black; size: 28; } @@ -448,7 +451,7 @@ Rectangle { // "HFC" balance label HiFiGlyphs { id: itemPriceTextLabel; - visible: !(root.isUpdating && root.itemEdition > 0); + visible: !(root.isUpdating && root.itemEdition > 0) && (root.itemPrice > 0); text: hifi.glyphs.hfc; // Size size: 30; @@ -464,7 +467,7 @@ Rectangle { } FiraSansSemiBold { id: itemPriceText; - text: (root.isUpdating && root.itemEdition > 0) ? "FREE\nUPDATE" : ((root.itemPrice === -1) ? "--" : root.itemPrice); + text: (root.isUpdating && root.itemEdition > 0) ? "FREE\nUPDATE" : ((root.itemPrice === -1) ? "--" : ((root.itemPrice > 0) ? root.itemPrice : "FREE")); // Text size size: (root.isUpdating && root.itemEdition > 0) ? 20 : 26; // Anchors @@ -559,7 +562,7 @@ Rectangle { } } - // "View in My Purchases" button + // "View in Inventory" button HifiControlsUit.Button { id: viewInMyPurchasesButton; visible: false; @@ -570,7 +573,7 @@ Rectangle { height: 50; anchors.left: parent.left; anchors.right: parent.right; - text: root.isUpdating ? "UPDATE TO THIS ITEM FOR FREE" : "VIEW THIS ITEM IN MY PURCHASES"; + text: root.isUpdating ? "UPDATE TO THIS ITEM FOR FREE" : "VIEW THIS ITEM IN YOUR INVENTORY"; onClicked: { if (root.isUpdating) { sendToScript({method: 'checkout_goToPurchases', filterText: root.baseItemName}); @@ -594,7 +597,7 @@ Rectangle { anchors.left: parent.left; anchors.right: parent.right; text: (root.isUpdating && root.itemEdition > 0) ? "CONFIRM UPDATE" : (((root.isCertified) ? ((ownershipStatusReceived && balanceReceived && availableUpdatesReceived) ? - ((viewInMyPurchasesButton.visible && !root.isUpdating) ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item")); + ((viewInMyPurchasesButton.visible && !root.isUpdating) ? "Get It Again" : "Confirm") : "--") : "Get Item")); onClicked: { if (root.isUpdating && root.itemEdition > 0) { // If we're updating an app, the existing app needs to be uninstalled. @@ -608,9 +611,9 @@ Rectangle { } else if (root.isCertified) { if (!root.shouldBuyWithControlledFailure) { if (root.itemType === "contentSet" && !Entities.canReplaceContent()) { - lightboxPopup.titleText = "Purchase Content Set"; + lightboxPopup.titleText = "Get Content Set"; lightboxPopup.bodyText = "You will not be able to replace this domain's content with <b>" + root.itemName + - " </b>until the server owner gives you 'Replace Content' permissions.<br><br>Are you sure you want to purchase this content set?"; + " </b>until the server owner gives you 'Replace Content' permissions.<br><br>Are you sure you want to get this content set?"; lightboxPopup.button1text = "CANCEL"; lightboxPopup.button1method = function() { lightboxPopup.visible = false; @@ -694,7 +697,7 @@ Rectangle { id: completeText2; text: "The " + (root.itemTypesText)[itemTypesArray.indexOf(root.itemType)] + ' <font color="' + hifi.colors.blueAccent + '"><a href="#">' + root.itemName + '</a></font>' + - " has been added to your Purchases and a receipt will appear in your Wallet's transaction history."; + " has been added to your Inventory."; // Text size size: 18; // Anchors @@ -833,7 +836,7 @@ Rectangle { } lightboxPopup.button2text = "OPEN GOTO"; lightboxPopup.button2method = function() { - sendToScript({method: 'purchases_openGoTo'}); + sendToScript({method: 'checkout_openGoTo'}); lightboxPopup.visible = false; }; lightboxPopup.visible = true; @@ -864,7 +867,7 @@ Rectangle { RalewaySemiBold { id: myPurchasesLink; - text: '<font color="' + hifi.colors.primaryHighlight + '"><a href="#">View this item in My Purchases</a></font>'; + text: '<font color="' + hifi.colors.primaryHighlight + '"><a href="#">View this item in your Inventory</a></font>'; // Text size size: 18; // Anchors @@ -886,7 +889,8 @@ Rectangle { RalewaySemiBold { id: walletLink; - text: '<font color="' + hifi.colors.primaryHighlight + '"><a href="#">View receipt in Wallet</a></font>'; + visible: !WalletScriptingInterface.limitedCommerce; + text: '<font color="' + hifi.colors.primaryHighlight + '"><a href="#">View receipt in Recent Activity</a></font>'; // Text size size: 18; // Anchors @@ -902,18 +906,18 @@ Rectangle { horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignVCenter; onLinkActivated: { - sendToScript({method: 'purchases_openWallet'}); + sendToScript({method: 'checkout_openRecentActivity'}); } } RalewayRegular { id: pendingText; - text: 'Your item is marked "pending" while your purchase is being confirmed. ' + + text: 'Your item is marked "pending" while the transfer is being confirmed. ' + '<b><font color="' + hifi.colors.primaryHighlight + '"><a href="#">Learn More</a></font></b>'; // Text size size: 18; // Anchors - anchors.top: walletLink.bottom; + anchors.top: walletLink.visible ? walletLink.bottom : myPurchasesLink.bottom; anchors.topMargin: 32; height: paintedHeight; anchors.left: parent.left; @@ -925,8 +929,8 @@ Rectangle { horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignVCenter; onLinkActivated: { - lightboxPopup.titleText = "Purchase Confirmations"; - lightboxPopup.bodyText = 'Your item is marked "pending" while your purchase is being confirmed.<br><br>' + + lightboxPopup.titleText = "Confirmations"; + lightboxPopup.bodyText = 'Your item is marked "pending" while the transfer is being confirmed.<br><br>' + 'Confirmations usually take about 90 seconds.'; lightboxPopup.button1text = "CLOSE"; lightboxPopup.button1method = function() { @@ -936,9 +940,9 @@ Rectangle { } } - // "Continue Shopping" button + // "Continue" button HifiControlsUit.Button { - id: continueShoppingButton; + id: continueButton; color: hifi.buttons.noneBorderlessGray; colorScheme: hifi.colorSchemes.light; anchors.bottom: parent.bottom; @@ -946,9 +950,9 @@ Rectangle { anchors.right: parent.right; width: 193; height: 44; - text: "Continue Shopping"; + text: "Continue"; onClicked: { - sendToScript({method: 'checkout_continueShopping', itemId: itemId}); + sendToScript({method: 'checkout_continue', itemId: itemId}); } } } @@ -971,7 +975,7 @@ Rectangle { RalewayRegular { id: failureHeaderText; - text: "<b>Purchase Failed.</b><br>Your Purchases and HFC balance haven't changed."; + text: "<b>Purchase Failed.</b><br>Your Inventory and HFC balance haven't changed."; // Text size size: 24; // Anchors @@ -1037,7 +1041,7 @@ Rectangle { width: parent.width/2 - anchors.leftMargin*2; text: "Back to Marketplace"; onClicked: { - sendToScript({method: 'checkout_continueShopping', itemId: itemId}); + sendToScript({method: 'checkout_continue', itemId: itemId}); } } } @@ -1122,10 +1126,10 @@ Rectangle { if (root.balanceAfterPurchase < 0) { // If you already own the item... if (!root.alreadyOwned) { - buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item.</b>"; + buyText.text = "<b>You do not have sufficient funds to purchase this item.</b>"; // Else if you don't already own the item... } else if (canBuyAgain()) { - buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item again.</b>"; + buyText.text = "<b>You do not have sufficient funds to purchase this item again.</b>"; } else { buyText.text = "<b>While you do not have sufficient funds to buy this, you already have this item.</b>" } @@ -1171,7 +1175,7 @@ Rectangle { buyText.text = ""; } } else { - buyText.text = '<i>This type of item cannot currently be certified, so it will not show up in "My Purchases". You can access it again for free from the Marketplace.</i>'; + buyText.text = '<i>This type of item cannot currently be certified, so it will not show up in "Inventory". You can access it again for free from the Marketplace.</i>'; buyTextContainer.color = hifi.colors.white; buyTextContainer.border.color = hifi.colors.white; buyGlyph.text = ""; @@ -1185,6 +1189,7 @@ Rectangle { root.ownershipStatusReceived = false; Commerce.alreadyOwned(root.itemId); root.availableUpdatesReceived = false; + root.currentUpdatesPage = 1; Commerce.getAvailableUpdates(root.itemId); root.balanceReceived = false; Commerce.balance(); diff --git a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml index 9d9216c461..b7215500d2 100644 --- a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml +++ b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml @@ -14,9 +14,9 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit -import "../../../controls" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "qrc:////qml//controls" as HifiControls // references XXX from root context @@ -33,13 +33,15 @@ Rectangle { property string buttonLayout: "leftright"; readonly property string securityPicBodyText: "When you see your Security Pic, your actions and data are securely making use of your " + - "Wallet's private keys.<br><br>You can change your Security Pic in your Wallet."; + "private keys.<br><br>You can change your Security Pic via Settings > Security..."; id: root; visible: false; anchors.fill: parent; color: Qt.rgba(0, 0, 0, 0.5); z: 999; + + HifiConstants { id: hifi; } onVisibleChanged: { if (!visible) { diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml index 1b77dcd3e9..0d0af875d1 100644 --- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml +++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.7 import QtGraphicalEffects 1.0 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context @@ -27,7 +27,6 @@ Item { property string referrerURL: (Account.metaverseServerURL + "/marketplace?"); readonly property int additionalDropdownHeight: usernameDropdown.height - myUsernameButton.anchors.bottomMargin; property alias usernameDropdownVisible: usernameDropdown.visible; - property bool messagesWaiting: false; height: mainContainer.height + additionalDropdownHeight; @@ -38,7 +37,6 @@ Item { if (walletStatus === 0) { sendToParent({method: "needsLogIn"}); } else if (walletStatus === 5) { - Commerce.getAvailableUpdates(); Commerce.getSecurityImage(); } else if (walletStatus > 5) { console.log("ERROR in EmulatedMarketplaceHeader.qml: Unknown wallet status: " + walletStatus); @@ -59,14 +57,6 @@ Item { securityImage.source = "image://security/securityImage"; } } - - onAvailableUpdatesResult: { - if (result.status !== 'success') { - console.log("Failed to get Available Updates", result.data.message); - } else { - root.messagesWaiting = result.data.updates.length > 0; - } - } } Component.onCompleted: { @@ -118,50 +108,6 @@ Item { anchors.right: securityImage.left; anchors.rightMargin: 6; - Rectangle { - id: myPurchasesLink; - anchors.right: myUsernameButton.left; - anchors.rightMargin: 8; - anchors.verticalCenter: parent.verticalCenter; - height: 40; - width: myPurchasesText.paintedWidth + 10; - - RalewaySemiBold { - id: myPurchasesText; - text: "My Purchases"; - // Text size - size: 18; - // Style - color: hifi.colors.blueAccent; - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - // Anchors - anchors.centerIn: parent; - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: enabled; - onClicked: { - sendToParent({ method: 'header_goToPurchases', hasUpdates: root.messagesWaiting }); - } - onEntered: myPurchasesText.color = hifi.colors.blueHighlight; - onExited: myPurchasesText.color = hifi.colors.blueAccent; - } - } - - Rectangle { - id: messagesWaitingLight; - visible: root.messagesWaiting; - anchors.right: myPurchasesLink.left; - anchors.rightMargin: -2; - anchors.verticalCenter: parent.verticalCenter; - height: 10; - width: height; - radius: height/2; - color: "red"; - } - TextMetrics { id: textMetrics; font.family: "Raleway" @@ -267,7 +213,7 @@ Item { anchors.topMargin: -buttonAndUsernameContainer.anchors.bottomMargin; anchors.right: buttonAndUsernameContainer.right; height: childrenRect.height; - width: 100; + width: 150; Rectangle { id: myItemsButton; @@ -279,7 +225,7 @@ Item { RalewaySemiBold { anchors.fill: parent; - text: "My Items" + text: "My Submissions" color: hifi.colors.baseGray; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; diff --git a/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml b/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml index 5f874d3f04..c2d85b68b4 100644 --- a/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml +++ b/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context @@ -87,7 +87,7 @@ Rectangle { } RalewayRegular { id: introText2; - text: "My Purchases"; + text: "Inventory"; // Text size size: 22; // Anchors @@ -116,7 +116,7 @@ Rectangle { RalewayRegular { id: step1text; - text: "The <b>'REZ IT'</b> button makes your purchase appear in front of you."; + text: "The <b>'REZ IT'</b> button makes your item appear in front of you."; // Text size size: 20; // Anchors diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml index 41eacd68d5..1eb8af31e6 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml @@ -16,8 +16,8 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 -import "../../../../styles-uit" -import "../../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../../controls" as HifiControls import "../../wallet" as HifiWallet diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml index 9293dc83ab..9e1a967d50 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml @@ -15,8 +15,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.6 import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 -import "../../../../styles-uit" -import "../../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../../controls" as HifiControls import "../" as HifiCommerceCommon diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 0a5c3e8053..ec7146f33e 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -15,8 +15,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.6 import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 -import "../../../../styles-uit" -import "../../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../../controls" as HifiControls import "../" as HifiCommerceCommon import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. @@ -73,6 +73,10 @@ Item { } onTransferAssetToNodeResult: { + if (!root.visible) { + return; + } + root.isCurrentlySendingAsset = false; if (result.status === 'success') { @@ -92,6 +96,10 @@ Item { } onTransferAssetToUsernameResult: { + if (!root.visible) { + return; + } + root.isCurrentlySendingAsset = false; if (result.status === 'success') { @@ -1309,13 +1317,13 @@ Item { Rectangle { anchors.top: parent.top; - anchors.topMargin: root.assetName === "" ? 15 : 150; + anchors.topMargin: root.assetName === "" ? 15 : 125; anchors.left: parent.left; anchors.leftMargin: root.assetName === "" ? 15 : 50; anchors.right: parent.right; anchors.rightMargin: root.assetName === "" ? 15 : 50; anchors.bottom: parent.bottom; - anchors.bottomMargin: root.assetName === "" ? 15 : 240; + anchors.bottomMargin: root.assetName === "" ? 15 : 125; color: "#FFFFFF"; RalewaySemiBold { diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index d24344b40a..8d0b93d11a 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "../wallet" as HifiWallet @@ -28,7 +28,7 @@ Rectangle { property string itemName: "--"; property string itemOwner: "--"; property string itemEdition: "--"; - property string dateOfPurchase: "--"; + property string dateAcquired: "--"; property string itemCost: "--"; property string certTitleTextColor: hifi.colors.darkGray; property string certTextColor: hifi.colors.white; @@ -64,7 +64,7 @@ Rectangle { root.itemName = ""; root.itemEdition = ""; root.itemOwner = ""; - root.dateOfPurchase = ""; + root.dateAcquired = ""; root.itemCost = ""; errorText.text = "Information about this certificate is currently unavailable. Please try again later."; } @@ -77,8 +77,9 @@ Rectangle { // "\u2022" is the Unicode character 'BULLET' - it's what's used in password fields on the web, etc root.itemOwner = root.isMyCert ? Account.username : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; - root.dateOfPurchase = root.isMyCert ? getFormattedDate(result.data.transfer_created_at * 1000) : "Undisclosed"; - root.itemCost = (root.isMyCert && result.data.cost !== undefined) ? result.data.cost : "Undisclosed"; + root.dateAcquired = root.isMyCert ? getFormattedDate(result.data.transfer_created_at * 1000) : "Undisclosed"; + root.itemCost = (root.isMyCert && result.data.cost !== undefined) ? + (parseInt(result.data.cost) > 0 ? result.data.cost : "Free") : "Undisclosed"; } if (root.certInfoReplaceMode > 4) { root.itemEdition = result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run); @@ -86,7 +87,7 @@ Rectangle { if (root.certificateStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED if (root.isMyCert) { - errorText.text = "This item is an uncertified copy of an item you purchased."; + errorText.text = "This item is an uncertified copy of an item you acquired."; } else { errorText.text = "The person who placed this item doesn't own it."; } @@ -102,8 +103,8 @@ Rectangle { showInMarketplaceButton.visible = false; // "Edition" text previously set above in this function // "Owner" text previously set above in this function - // "Purchase Date" text previously set above in this function - // "Purchase Price" text previously set above in this function + // "Acquisition Date" text previously set above in this function + // "Acquisition Price" text previously set above in this function if (result.data.invalid_reason) { errorText.text = result.data.invalid_reason; } @@ -117,8 +118,8 @@ Rectangle { showInMarketplaceButton.visible = true; // "Edition" text previously set above in this function // "Owner" text previously set above in this function - // "Purchase Date" text previously set above in this function - // "Purchase Price" text previously set above in this function + // "Acquisition Date" text previously set above in this function + // "Acquisition Price" text previously set above in this function errorText.text = "The status of this item is still pending confirmation. If the purchase is not confirmed, " + "this entity will be cleaned up by the domain."; } @@ -145,8 +146,8 @@ Rectangle { // "Item Name" text will be set in "onCertificateInfoResult()" // "Edition" text will be set in "onCertificateInfoResult()" // "Owner" text will be set in "onCertificateInfoResult()" - // "Purchase Date" text will be set in "onCertificateInfoResult()" - // "Purchase Price" text will be set in "onCertificateInfoResult()" + // "Acquisition Date" text will be set in "onCertificateInfoResult()" + // "Acquisition Price" text will be set in "onCertificateInfoResult()" errorText.text = ""; } else if (root.certificateStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT root.useGoldCert = false; @@ -160,7 +161,7 @@ Rectangle { root.itemName = ""; root.itemEdition = ""; root.itemOwner = ""; - root.dateOfPurchase = ""; + root.dateAcquired = ""; root.itemCost = ""; errorText.text = "Your request to inspect this item timed out. Please try again later."; } else if (root.certificateStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED @@ -175,8 +176,8 @@ Rectangle { // "Item Name" text will be set in "onCertificateInfoResult()" // "Edition" text will be set in "onCertificateInfoResult()" // "Owner" text will be set in "onCertificateInfoResult()" - // "Purchase Date" text will be set in "onCertificateInfoResult()" - // "Purchase Price" text will be set in "onCertificateInfoResult()" + // "Acquisition Date" text will be set in "onCertificateInfoResult()" + // "Acquisition Price" text will be set in "onCertificateInfoResult()" errorText.text = "The information associated with this item has been modified and it no longer matches the original certified item."; } else if (root.certificateStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED root.useGoldCert = false; @@ -190,8 +191,8 @@ Rectangle { // "Item Name" text will be set in "onCertificateInfoResult()" root.itemEdition = "Uncertified Copy" // "Owner" text will be set in "onCertificateInfoResult()" - // "Purchase Date" text will be set in "onCertificateInfoResult()" - // "Purchase Price" text will be set in "onCertificateInfoResult()" + // "Acquisition Date" text will be set in "onCertificateInfoResult()" + // "Acquisition Price" text will be set in "onCertificateInfoResult()" // "Error Text" text will be set in "onCertificateInfoResult()" } else { console.log("Unknown certificate status received from ledger signal!"); @@ -485,8 +486,8 @@ Rectangle { } RalewayRegular { - id: dateOfPurchaseHeader; - text: "PURCHASE DATE"; + id: dateAcquiredHeader; + text: "ACQUISITION DATE"; // Text size size: 16; // Anchors @@ -500,15 +501,15 @@ Rectangle { color: hifi.colors.darkGray; } AnonymousProRegular { - id: dateOfPurchase; - text: root.dateOfPurchase; + id: dateAcquired; + text: root.dateAcquired; // Text size size: 18; // Anchors - anchors.top: dateOfPurchaseHeader.bottom; + anchors.top: dateAcquiredHeader.bottom; anchors.topMargin: 8; - anchors.left: dateOfPurchaseHeader.left; - anchors.right: dateOfPurchaseHeader.right; + anchors.left: dateAcquiredHeader.left; + anchors.right: dateAcquiredHeader.right; height: paintedHeight; // Style color: root.infoTextColor; @@ -516,7 +517,7 @@ Rectangle { RalewayRegular { id: priceHeader; - text: "PURCHASE PRICE"; + text: "ACQUISITION PRICE"; // Text size size: 16; // Anchors @@ -530,7 +531,7 @@ Rectangle { } HiFiGlyphs { id: hfcGlyph; - visible: priceText.text !== "Undisclosed" && priceText.text !== ""; + visible: priceText.text !== "Undisclosed" && priceText.text !== "" && priceText.text !== "Free"; text: hifi.glyphs.hfc; // Size size: 24; @@ -618,7 +619,7 @@ Rectangle { root.itemName = "--"; root.itemOwner = "--"; root.itemEdition = "--"; - root.dateOfPurchase = "--"; + root.dateAcquired = "--"; root.marketplaceUrl = ""; root.itemCost = "--"; root.isMyCert = false; diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml new file mode 100644 index 0000000000..966f359e92 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml @@ -0,0 +1,364 @@ +// +// ItemUnderTest +// qml/hifi/commerce/marketplaceItemTester +// +// Load items not in the marketplace for testing purposes +// +// Created by Kerry Ivan Kurian on 2018-10-18 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import Hifi 1.0 as Hifi +import stylesUit 1.0 as HifiStylesUit +import controlsUit 1.0 as HifiControlsUit + +Rectangle { + id: root; + color: hifi.colors.baseGray + width: parent.width - 16 + height: childrenRect.height + itemHeaderContainer.anchors.topMargin + detailsContainer.anchors.topMargin + + property var detailsExpanded: false + + property var actions: { + "forward": function(resource, assetType, resourceObjectId){ + switch(assetType) { + case "application": + Commerce.installApp(resource, true); + break; + case "avatar": + MyAvatar.useFullAvatarURL(resource); + break; + case "content set": + urlHandler.handleUrl("hifi://localhost/0,0,0"); + Commerce.replaceContentSet(toUrl(resource), ""); + break; + case "entity": + case "wearable": + rezEntity(resource, assetType, resourceObjectId); + break; + default: + print("Marketplace item tester unsupported assetType " + assetType); + } + }, + "trash": function(resource, assetType){ + if ("application" === assetType) { + Commerce.uninstallApp(resource); + } + sendToScript({ + method: "tester_deleteResourceObject", + objectId: resourceListModel.get(index).resourceObjectId}); + resourceListModel.remove(index); + } + } + + Item { + id: itemHeaderContainer + anchors.top: parent.top + anchors.topMargin: 8 + anchors.left: parent.left + anchors.leftMargin: 8 + width: parent.width - 16 + height: childrenRect.height + + Item { + id: itemNameContainer + width: parent.width * 0.5 + height: childrenRect.height + + HifiStylesUit.RalewaySemiBold { + id: resourceName + height: paintedHeight + width: parent.width + text: { + var match = resource.match(/\/([^/]*)$/); + return match ? match[1] : resource; + } + size: 14 + color: hifi.colors.white + wrapMode: Text.WrapAnywhere + } + + HifiStylesUit.RalewayRegular { + id: resourceUrl + anchors.top: resourceName.bottom; + anchors.topMargin: 4; + height: paintedHeight + width: parent.width + text: resource + size: 12 + color: hifi.colors.faintGray; + wrapMode: Text.WrapAnywhere + } + } + + HifiControlsUit.ComboBox { + id: comboBox + anchors.left: itemNameContainer.right + anchors.leftMargin: 4 + anchors.verticalCenter: itemNameContainer.verticalCenter + height: 30 + width: parent.width * 0.3 - anchors.leftMargin + + model: [ + "application", + "avatar", + "content set", + "entity", + "wearable", + "unknown" + ] + + currentIndex: (("entity or wearable" === assetType) ? + model.indexOf("unknown") : model.indexOf(assetType)) + + Component.onCompleted: { + onCurrentIndexChanged.connect(function() { + assetType = model[currentIndex]; + sendToScript({ + method: "tester_updateResourceObjectAssetType", + objectId: resourceListModel.get(index)["resourceObjectId"], + assetType: assetType }); + }); + } + } + + Button { + id: actionButton + property var glyphs: { + "application": hifi.glyphs.install, + "avatar": hifi.glyphs.avatar, + "content set": hifi.glyphs.globe, + "entity": hifi.glyphs.wand, + "trash": hifi.glyphs.trash, + "unknown": hifi.glyphs.circleSlash, + "wearable": hifi.glyphs.hat + } + property int color: hifi.buttons.blue; + property int colorScheme: hifi.colorSchemes.dark; + anchors.left: comboBox.right + anchors.leftMargin: 4 + anchors.verticalCenter: itemNameContainer.verticalCenter + width: parent.width * 0.10 - anchors.leftMargin + height: width + enabled: comboBox.model[comboBox.currentIndex] !== "unknown" + + onClicked: { + if (model.currentlyRecordingResources) { + model.currentlyRecordingResources = false; + } else { + model.resourceAccessEventText = ""; + model.currentlyRecordingResources = true; + root.actions["forward"](resource, comboBox.currentText, resourceObjectId); + } + sendToScript({ + method: "tester_updateResourceRecordingStatus", + objectId: resourceListModel.get(index).resourceObjectId, + status: model.currentlyRecordingResources + }); + } + + background: Rectangle { + radius: 4; + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!actionButton.enabled) { + hifi.buttons.disabledColorStart[actionButton.colorScheme] + } else if (actionButton.pressed) { + hifi.buttons.pressedColor[actionButton.color] + } else if (actionButton.hovered) { + hifi.buttons.hoveredColor[actionButton.color] + } else { + hifi.buttons.colorStart[actionButton.color] + } + } + } + GradientStop { + position: 1.0 + color: { + if (!actionButton.enabled) { + hifi.buttons.disabledColorFinish[actionButton.colorScheme] + } else if (actionButton.pressed) { + hifi.buttons.pressedColor[actionButton.color] + } else if (actionButton.hovered) { + hifi.buttons.hoveredColor[actionButton.color] + } else { + hifi.buttons.colorFinish[actionButton.color] + } + } + } + } + } + + contentItem: Item { + HifiStylesUit.HiFiGlyphs { + id: rezIcon; + text: model.currentlyRecordingResources ? hifi.glyphs.scriptStop : actionButton.glyphs[comboBox.model[comboBox.currentIndex]]; + anchors.fill: parent + size: 30; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + color: enabled ? hifi.buttons.textColor[actionButton.color] + : hifi.buttons.disabledTextColor[actionButton.colorScheme] + } + } + } + + Button { + id: trashButton + property int color: hifi.buttons.red; + property int colorScheme: hifi.colorSchemes.dark; + anchors.left: actionButton.right + anchors.verticalCenter: itemNameContainer.verticalCenter + anchors.leftMargin: 4 + width: parent.width * 0.10 - anchors.leftMargin + height: width + + onClicked: { + root.actions["trash"](resource, comboBox.currentText, resourceObjectId); + } + + background: Rectangle { + radius: 4; + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!trashButton.enabled) { + hifi.buttons.disabledColorStart[trashButton.colorScheme] + } else if (trashButton.pressed) { + hifi.buttons.pressedColor[trashButton.color] + } else if (trashButton.hovered) { + hifi.buttons.hoveredColor[trashButton.color] + } else { + hifi.buttons.colorStart[trashButton.color] + } + } + } + GradientStop { + position: 1.0 + color: { + if (!trashButton.enabled) { + hifi.buttons.disabledColorFinish[trashButton.colorScheme] + } else if (trashButton.pressed) { + hifi.buttons.pressedColor[trashButton.color] + } else if (trashButton.hovered) { + hifi.buttons.hoveredColor[trashButton.color] + } else { + hifi.buttons.colorFinish[trashButton.color] + } + } + } + } + } + + contentItem: Item { + HifiStylesUit.HiFiGlyphs { + id: trashIcon; + text: hifi.glyphs.trash + anchors.fill: parent + size: 22; + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: enabled ? hifi.buttons.textColor[trashButton.color] + : hifi.buttons.disabledTextColor[trashButton.colorScheme] + } + } + } + } + + Item { + id: detailsContainer + + width: parent.width - 16 + height: root.detailsExpanded ? 300 : 26 + anchors.top: itemHeaderContainer.bottom + anchors.topMargin: 12 + anchors.left: parent.left + anchors.leftMargin: 8 + + HifiStylesUit.HiFiGlyphs { + id: detailsToggle + anchors.left: parent.left + anchors.leftMargin: -4 + anchors.top: parent.top + anchors.topMargin: -2 + width: 22 + text: root.detailsExpanded ? hifi.glyphs.minimize : hifi.glyphs.maximize + color: hifi.colors.white + size: 22 + MouseArea { + anchors.fill: parent + onClicked: root.detailsExpanded = !root.detailsExpanded + } + } + + ScrollView { + id: detailsTextContainer + anchors.top: parent.top + anchors.left: detailsToggle.right + anchors.leftMargin: 4 + anchors.right: parent.right + height: detailsContainer.height - (root.detailsExpanded ? (copyToClipboardButton.height + copyToClipboardButton.anchors.topMargin) : 0) + clip: true + + TextArea { + id: detailsText + readOnly: true + color: hifi.colors.white + text: { + var numUniqueResources = (model.resourceAccessEventText.split("\n").length - 1); + if (root.detailsExpanded && numUniqueResources > 0) { + return model.resourceAccessEventText + } else { + return numUniqueResources.toString() + " unique source/resource url pair" + (numUniqueResources === 1 ? "" : "s") + " recorded" + } + } + font: Qt.font({ family: "Courier", pointSize: 8, weight: Font.Normal }) + wrapMode: TextEdit.NoWrap + + background: Rectangle { + anchors.fill: parent; + color: hifi.colors.baseGrayShadow; + border.width: 0; + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + if (root.detailsExpanded) { + detailsText.selectAll(); + } else { + root.detailsExpanded = true; + } + } + } + } + + HifiControlsUit.Button { + id: copyToClipboardButton; + visible: root.detailsExpanded + color: hifi.buttons.noneBorderlessWhite + colorScheme: hifi.colorSchemes.dark + + anchors.top: detailsTextContainer.bottom + anchors.topMargin: 8 + anchors.right: parent.right + width: 160 + height: 30 + text: "Copy to Clipboard" + + onClicked: { + Window.copyToClipboard(detailsText.text); + } + } + } +} diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index c3d87ca2f5..a37a0ac756 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -4,21 +4,20 @@ // // Load items not in the marketplace for testing purposes // -// Created by Zach Fox on 2018-09-05 +// Created by Kerry Ivan Kurian on 2018-09-05 // Copyright 2018 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Dialogs 1.0 +import QtQuick 2.10 import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.3 import Hifi 1.0 as Hifi -import "../../../styles-uit" as HifiStylesUit -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 as HifiStylesUit +import controlsUit 1.0 as HifiControlsUit + @@ -27,33 +26,223 @@ Rectangle { property string installedApps property var nextResourceObjectId: 0 - signal sendToScript(var message) HifiStylesUit.HifiConstants { id: hifi } ListModel { id: resourceListModel } - color: hifi.colors.white + color: hifi.colors.darkGray - AnimatedImage { - id: spinner; - source: "spinner.gif" - width: 74; - height: width; - anchors.verticalCenter: parent.verticalCenter; - anchors.horizontalCenter: parent.horizontalCenter; + // + // TITLE BAR START + // + Item { + id: titleBarContainer + // Size + width: root.width + height: 50 + // Anchors + anchors.left: parent.left + anchors.top: parent.top + + // Title bar text + HifiStylesUit.RalewaySemiBold { + id: titleBarText + text: "Marketplace Item Tester" + // Text size + size: 24 + // Anchors + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.leftMargin: 16 + width: paintedWidth + // Style + color: hifi.colors.lightGrayText + // Alignment + horizontalAlignment: Text.AlignHLeft + verticalAlignment: Text.AlignVCenter + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: 1 + } + } + // + // TITLE BAR END + // + + Rectangle { + id: spinner + z: 999 + anchors.top: titleBarContainer.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: buttonContainer.top + color: hifi.colors.darkGray + + AnimatedImage { + source: "spinner.gif" + width: 74 + height: width + anchors.centerIn: parent + } + } + + Rectangle { + id: instructionsContainer + z: 998 + color: hifi.colors.darkGray + visible: resourceListModel.count === 0 && !spinner.visible + anchors.top: titleBarContainer.bottom + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 20 + anchors.right: parent.right + anchors.rightMargin: 20 + anchors.bottom: buttonContainer.top + anchors.bottomMargin: 20 + + HifiStylesUit.RalewayRegular { + text: "Use Marketplace Item Tester to test out your items before submitting them to the Marketplace." + + "\n\nUse one of the buttons below to load your item." + // Text size + size: 20 + // Anchors + anchors.fill: parent + // Style + color: hifi.colors.lightGrayText + wrapMode: Text.Wrap + // Alignment + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + ListView { + id: itemList + visible: !instructionsContainer.visible + anchors.top: titleBarContainer.bottom + anchors.topMargin: 20 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: buttonContainer.top + anchors.bottomMargin: 20 + ScrollBar.vertical: ScrollBar { + visible: !instructionsContainer.visible + policy: ScrollBar.AlwaysOn + parent: itemList.parent + anchors.top: itemList.top + anchors.right: itemList.right + anchors.bottom: itemList.bottom + width: 16 + } + clip: true + model: resourceListModel + spacing: 8 + + delegate: ItemUnderTest { } + } + + Item { + id: buttonContainer + + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.right: parent.right + anchors.rightMargin: 12 + anchors.bottom: parent.bottom + anchors.bottomMargin: 12 + height: 40 + + property string currentAction + property var actions: { + "Load File": function() { + buttonContainer.currentAction = "load file"; + Window.browseChanged.connect(onResourceSelected); + Window.browseAsync("Please select a file (*.app.json *.json *.fst *.json.gz)", "", "Assets (*.app.json *.json *.fst *.json.gz)"); + }, + "Load URL": function() { + buttonContainer.currentAction = "load url"; + Window.promptTextChanged.connect(onResourceSelected); + Window.promptAsync("Please enter a URL", ""); + } + } + + function onResourceSelected(resource) { + // It is possible that we received the present signal + // from something other than our browserAsync window. + // Alas, there is nothing we can do about that so charge + // ahead as though we are sure the present signal is one + // we expect. + print("!!!! resource selected"); + switch(currentAction) { + case "load file": + Window.browseChanged.disconnect(onResourceSelected); + break + case "load url": + Window.promptTextChanged.disconnect(onResourceSelected); + break; + } + if (resource) { + print("!!!! building resource object"); + var resourceObj = buildResourceObj(resource); + print("!!!! notifying script of resource object"); + sendToScript({ + method: 'tester_newResourceObject', + resourceObject: resourceObj + }); + } + } + + HifiControlsUit.Button { + enabled: !spinner.visible + anchors.right: parent.horizontalCenter + anchors.rightMargin: width/4 + anchors.verticalCenter: parent.verticalCenter + color: hifi.buttons.blue + fontSize: 20 + text: "Load File" + width: parent.width / 3 + height: parent.height + onClicked: buttonContainer.actions[text]() + } + + HifiControlsUit.Button { + enabled: !spinner.visible + anchors.left: parent.horizontalCenter + anchors.leftMargin: width/4 + anchors.verticalCenter: parent.verticalCenter + color: hifi.buttons.blue + fontSize: 20 + text: "Load URL" + width: parent.width / 3 + height: parent.height + onClicked: buttonContainer.actions[text]() + } } function fromScript(message) { switch (message.method) { case "newResourceObjectInTest": var resourceObject = message.resourceObject; + resourceListModel.clear(); // REMOVE THIS once we support specific referrers resourceListModel.append(resourceObject); spinner.visible = false; break; case "nextObjectIdInTest": + print("!!!! message from script! " + JSON.stringify(message)); nextResourceObjectId = message.id; spinner.visible = false; break; + case "resourceRequestEvent": + // When we support multiple items under test simultaneously, + // we'll have to replace "0" with the correct index. + resourceListModel.setProperty(0, "resourceAccessEventText", message.resourceAccessEventText); + break; } } @@ -64,227 +253,26 @@ Rectangle { resource.match(/\.json\.gz$/) ? "content set" : resource.match(/\.json$/) ? "entity or wearable" : "unknown"); - return { "id": nextResourceObjectId++, + // Uncomment this once we support more than one item in test at the same time + //nextResourceObjectId++; + return { "resourceObjectId": nextResourceObjectId, "resource": resource, "assetType": assetType }; } - function installResourceObj(resourceObj) { - if ("application" === resourceObj.assetType) { - Commerce.installApp(resourceObj.resource); - } - } - - function addAllInstalledAppsToList() { - var i, apps = Commerce.getInstalledApps().split(","), len = apps.length; - for(i = 0; i < len - 1; ++i) { - if (i in apps) { - resourceListModel.append(buildResourceObj(apps[i])); - } - } - } - function toUrl(resource) { var httpPattern = /^http/i; return httpPattern.test(resource) ? resource : "file:///" + resource; } - function rezEntity(resource, entityType) { + function rezEntity(resource, entityType, resourceObjectId) { + print("!!!! tester_rezClicked"); sendToScript({ method: 'tester_rezClicked', itemHref: toUrl(resource), - itemType: entityType}); + itemType: entityType, + itemId: resourceObjectId }); } - ListView { - anchors.fill: parent - anchors.leftMargin: 12 - anchors.bottomMargin: 40 - anchors.rightMargin: 12 - model: resourceListModel - spacing: 5 - interactive: false - - delegate: RowLayout { - anchors.left: parent.left - width: parent.width - spacing: 5 - - property var actions: { - "forward": function(resource, assetType){ - switch(assetType) { - case "application": - Commerce.openApp(resource); - break; - case "avatar": - MyAvatar.useFullAvatarURL(resource); - break; - case "content set": - urlHandler.handleUrl("hifi://localhost/0,0,0"); - Commerce.replaceContentSet(toUrl(resource), ""); - break; - case "entity": - case "wearable": - rezEntity(resource, assetType); - break; - default: - print("Marketplace item tester unsupported assetType " + assetType); - } - }, - "trash": function(resource, assetType){ - if ("application" === assetType) { - Commerce.uninstallApp(resource); - } - sendToScript({ - method: "tester_deleteResourceObject", - objectId: resourceListModel.get(index).id}); - resourceListModel.remove(index); - } - } - - Column { - Layout.preferredWidth: root.width * .6 - spacing: 5 - Text { - text: { - var match = resource.match(/\/([^/]*)$/); - return match ? match[1] : resource; - } - font.pointSize: 12 - horizontalAlignment: Text.AlignBottom - } - Text { - text: resource - font.pointSize: 8 - width: root.width * .6 - horizontalAlignment: Text.AlignBottom - wrapMode: Text.WrapAnywhere - } - } - - ComboBox { - id: comboBox - - Layout.preferredWidth: root.width * .2 - - model: [ - "application", - "avatar", - "content set", - "entity", - "wearable", - "unknown" - ] - - currentIndex: (("entity or wearable" === assetType) ? - model.indexOf("unknown") : model.indexOf(assetType)) - - Component.onCompleted: { - onCurrentIndexChanged.connect(function() { - assetType = model[currentIndex]; - sendToScript({ - method: "tester_updateResourceObjectAssetType", - objectId: resourceListModel.get(index)["id"], - assetType: assetType }); - }); - } - } - - Repeater { - model: [ "forward", "trash" ] - - HifiStylesUit.HiFiGlyphs { - property var glyphs: { - "application": hifi.glyphs.install, - "avatar": hifi.glyphs.avatar, - "content set": hifi.glyphs.globe, - "entity": hifi.glyphs.wand, - "trash": hifi.glyphs.trash, - "unknown": hifi.glyphs.circleSlash, - "wearable": hifi.glyphs.hat, - } - text: (("trash" === modelData) ? - glyphs.trash : - glyphs[comboBox.model[comboBox.currentIndex]]) - size: ("trash" === modelData) ? 22 : 30 - color: hifi.colors.black - horizontalAlignment: Text.AlignHCenter - MouseArea { - anchors.fill: parent - onClicked: { - actions[modelData](resource, comboBox.currentText); - } - } - } - } - } - - headerPositioning: ListView.OverlayHeader - header: HifiStylesUit.RalewayRegular { - id: rootHeader - text: "Marketplace Item Tester" - height: 80 - width: paintedWidth - size: 22 - color: hifi.colors.black - anchors.left: parent.left - anchors.leftMargin: 12 - } - - footerPositioning: ListView.OverlayFooter - footer: Row { - id: rootActions - spacing: 20 - anchors.horizontalCenter: parent.horizontalCenter - - property string currentAction - property var actions: { - "Load File": function(){ - rootActions.currentAction = "load file"; - Window.browseChanged.connect(onResourceSelected); - Window.browseAsync("Please select a file (*.app.json *.json *.fst *.json.gz)", "", "Assets (*.app.json *.json *.fst *.json.gz)"); - }, - "Load URL": function(){ - rootActions.currentAction = "load url"; - Window.promptTextChanged.connect(onResourceSelected); - Window.promptAsync("Please enter a URL", ""); - } - } - - function onResourceSelected(resource) { - // It is possible that we received the present signal - // from something other than our browserAsync window. - // Alas, there is nothing we can do about that so charge - // ahead as though we are sure the present signal is one - // we expect. - switch(currentAction) { - case "load file": - Window.browseChanged.disconnect(onResourceSelected); - break - case "load url": - Window.promptTextChanged.disconnect(onResourceSelected); - break; - } - if (resource) { - var resourceObj = buildResourceObj(resource); - installResourceObj(resourceObj); - sendToScript({ - method: 'tester_newResourceObject', - resourceObject: resourceObj }); - } - } - - Repeater { - model: [ "Load File", "Load URL" ] - HifiControlsUit.Button { - color: hifi.buttons.blue - fontSize: 20 - text: modelData - width: root.width / 3 - height: 40 - onClicked: actions[text]() - } - } - } - } + signal sendToScript(var message) } diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif b/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif index 00f75ae62f..0536bd1884 100644 Binary files a/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif and b/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif differ diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index eeb9ac3c54..c8ec7238d6 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -15,8 +15,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "../wallet" as HifiWallet import TabletScriptingInterface 1.0 @@ -47,8 +47,7 @@ Item { property string wornEntityID; property string upgradeUrl; property string upgradeTitle; - property bool updateAvailable: root.upgradeUrl !== "" && !root.isShowingMyItems; - property bool isShowingMyItems; + property bool updateAvailable: root.upgradeUrl !== ""; property bool valid; property string originalStatusText; @@ -231,7 +230,7 @@ Item { Loader { id: giftButton; - visible: !root.isShowingMyItems; + visible: root.itemEdition > 0; sourceComponent: contextCardButton; anchors.right: parent.right; anchors.top: parent.top; @@ -345,6 +344,7 @@ Item { Rectangle { id: permissionExplanationCard; + visible: false; anchors.left: parent.left; anchors.leftMargin: 30; anchors.top: parent.top; diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 015ec3a172..932a3ab6c4 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -13,13 +13,12 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. import "../wallet" as HifiWallet import "../common" as HifiCommerceCommon -import "../inspectionCertificate" as HifiInspectionCertificate import "../common/sendAsset" as HifiSendAsset import "../.." as HifiCommon @@ -34,7 +33,6 @@ Rectangle { property bool securityImageResultReceived: false; property bool purchasesReceived: false; property bool punctuationMode: false; - property bool isShowingMyItems: false; property bool isDebuggingFirstUseTutorial: false; property string installedApps; property bool keyboardRaised: false; @@ -92,7 +90,6 @@ Rectangle { if (result.status !== 'success') { console.log("Failed to get Available Updates", result.data.message); } else { - sendToScript({method: 'purchases_availableUpdatesReceived', numUpdates: result.data.updates.length }); root.numUpdatesAvailable = result.total_entries; } } @@ -106,10 +103,6 @@ Rectangle { } } - onIsShowingMyItemsChanged: { - getPurchases(); - } - Timer { id: notSetUpTimer; interval: 200; @@ -118,19 +111,6 @@ Rectangle { } } - HifiInspectionCertificate.InspectionCertificate { - id: inspectionCertificate; - z: 998; - visible: false; - anchors.fill: parent; - - Connections { - onSendToScript: { - sendToScript(message); - } - } - } - HifiCommerceCommon.CommerceLightbox { id: lightboxPopup; z: 999; @@ -180,7 +160,8 @@ Rectangle { HifiCommerceCommon.EmulatedMarketplaceHeader { id: titleBarContainer; z: 997; - visible: !needsLogIn.visible; + visible: false; + height: 100; // Size width: parent.width; // Anchors @@ -199,11 +180,6 @@ Rectangle { lightboxPopup.button1method = function() { lightboxPopup.visible = false; } - lightboxPopup.button2text = "GO TO WALLET"; - lightboxPopup.button2method = function() { - sendToScript({method: 'purchases_openWallet'}); - lightboxPopup.visible = false; - }; lightboxPopup.visible = true; } else { sendToScript(msg); @@ -475,7 +451,7 @@ Rectangle { anchors.left: parent.left; anchors.leftMargin: 16; width: paintedWidth; - text: isShowingMyItems ? "My Items" : "My Purchases"; + text: "Items"; color: hifi.colors.black; size: 22; } @@ -517,8 +493,13 @@ Rectangle { "filterName": "wearable" }, { + "separator" : true, "displayName": "Updatable", "filterName": "updated" + }, + { + "displayName": "My Submissions", + "filterName": "proofs" } ] filterBar.primaryFilterChoices.clear(); @@ -533,6 +514,7 @@ Rectangle { onTextChanged: { purchasesModel.searchFilter = filterBar.text; filterBar.previousText = filterBar.text; + } } } @@ -556,10 +538,18 @@ Rectangle { listModelName: 'purchases'; listView: purchasesContentsList; getPage: function () { - console.debug('getPage', purchasesModel.listModelName, root.isShowingMyItems, filterBar.primaryFilter_filterName, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage); + console.debug('getPage', purchasesModel.listModelName, filterBar.primaryFilter_filterName, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage); + var editionFilter = ""; + var primaryFilter = ""; + + if (filterBar.primaryFilter_filterName === "proofs") { + editionFilter = "proofs"; + } else { + primaryFilter = filterBar.primaryFilter_filterName; + } Commerce.inventory( - root.isShowingMyItems ? "proofs" : "purchased", - filterBar.primaryFilter_filterName, + editionFilter, + primaryFilter, filterBar.text, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage @@ -609,7 +599,6 @@ Rectangle { upgradeUrl: model.upgrade_url; upgradeTitle: model.upgrade_title; itemType: model.item_type; - isShowingMyItems: root.isShowingMyItems; valid: model.valid; anchors.topMargin: 10; anchors.bottomMargin: 10; @@ -626,8 +615,6 @@ Rectangle { sendToScript({ method: 'purchases_updateWearables' }); } } else if (msg.method === 'purchases_itemCertificateClicked') { - inspectionCertificate.visible = true; - inspectionCertificate.isLightbox = true; sendToScript(msg); } else if (msg.method === "showInvalidatedLightbox") { lightboxPopup.titleText = "Item Invalidated"; @@ -640,7 +627,7 @@ Rectangle { lightboxPopup.visible = true; } else if (msg.method === "showPendingLightbox") { lightboxPopup.titleText = "Item Pending"; - lightboxPopup.bodyText = 'Your item is marked "pending" while your purchase is being confirmed. ' + + lightboxPopup.bodyText = 'Your item is marked "pending" while the transfer is being confirmed. ' + "Usually, purchases take about 90 seconds to confirm."; lightboxPopup.button1text = "CLOSE"; lightboxPopup.button1method = function() { @@ -822,7 +809,8 @@ Rectangle { Rectangle { id: updatesAvailableBanner; - visible: root.numUpdatesAvailable > 0 && !root.isShowingMyItems; + visible: root.numUpdatesAvailable > 0 && + filterBar.primaryFilter_filterName !== "proofs"; anchors.bottom: parent.bottom; anchors.left: parent.left; anchors.right: parent.right; @@ -883,9 +871,8 @@ Rectangle { id: noItemsAlertContainer; visible: !purchasesContentsList.visible && root.purchasesReceived && - root.isShowingMyItems && filterBar.text === "" && - filterBar.primaryFilter_displayName === ""; + filterBar.primaryFilter_filterName === "proofs"; anchors.top: filterBarContainer.bottom; anchors.topMargin: 12; anchors.left: parent.left; @@ -895,7 +882,7 @@ Rectangle { // Explanitory text RalewayRegular { id: noItemsYet; - text: "<b>You haven't submitted anything to the Marketplace yet!</b><br><br>Submit an item to the Marketplace to add it to My Items."; + text: "<b>You haven't submitted anything to the Marketplace yet!</b><br><br>Submit an item to the Marketplace to add it to My Submissions."; // Text size size: 22; // Anchors @@ -933,7 +920,6 @@ Rectangle { id: noPurchasesAlertContainer; visible: !purchasesContentsList.visible && root.purchasesReceived && - !root.isShowingMyItems && filterBar.text === "" && filterBar.primaryFilter_displayName === ""; anchors.top: filterBarContainer.bottom; @@ -945,7 +931,7 @@ Rectangle { // Explanitory text RalewayRegular { id: haventPurchasedYet; - text: "<b>You haven't purchased anything yet!</b><br><br>Get an item from <b>Marketplace</b> to add it to My Purchases."; + text: "<b>You haven't gotten anything yet!</b><br><br>Get an item from <b>Marketplace</b> to add it to your Inventory."; // Text size size: 22; // Anchors @@ -1065,11 +1051,10 @@ Rectangle { titleBarContainer.referrerURL = message.referrerURL || ""; filterBar.text = message.filterText ? message.filterText : ""; break; - case 'inspectionCertificate_setCertificateId': - inspectionCertificate.fromScript(message); - break; case 'purchases_showMyItems': - root.isShowingMyItems = true; + filterBar.primaryFilter_filterName = "proofs"; + filterBar.primaryFilter_displayName = "Proofs"; + filterBar.primaryFilter_index = 6; break; case 'updateConnections': sendAsset.updateConnections(message.connections); diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index 6d8fc3c33f..1598aec41c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context @@ -62,7 +62,7 @@ Item { isExpanded: false; question: "How can I get HFC?"; answer: "High Fidelity commerce is in open beta right now. Want more HFC? \ -Get it by going to <br><br><b><font color='#0093C5'><a href='#bank'>BankOfHighFidelity.</a></font></b> and meeting with the banker!"; +Get it by going to <b><font color='#0093C5'><a href='#bank'>BankOfHighFidelity</a></font></b> and meeting with the banker!"; } ListElement { isExpanded: false; @@ -74,8 +74,7 @@ In your Wallet's Send Money tab, choose from your list of connections, or choose isExpanded: false; question: "What is a Security Pic?" answer: "Your Security Pic acts as an extra layer of Wallet security. \ -When you see your Security Pic, you know that your actions and data are securely making use of your account. \ -<br><br><b><font color='#0093C5'><a href='#securitypic'>Tap here to change your Security Pic.</a></font></b>"; +When you see your Security Pic, you know that your actions and data are securely making use of your account."; } ListElement { isExpanded: false; @@ -137,7 +136,7 @@ At the moment, there is currently no way to convert HFC to other currencies. Sta anchors.left: parent.left; width: parent.width; height: questionText.paintedHeight + 50; - + RalewaySemiBold { id: plusMinusButton; text: model.isExpanded ? "-" : "+"; @@ -217,8 +216,6 @@ At the moment, there is currently no way to convert HFC to other currencies. Sta } } else if (link === "#support") { Qt.openUrlExternally("mailto:support@highfidelity.com"); - } else if (link === "#securitypic") { - sendSignalToWallet({method: 'walletSecurity_changeSecurityImage'}); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml index eadf1ca8a2..ab05f55deb 100644 --- a/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml +++ b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context @@ -93,7 +93,7 @@ Item { // Text below helper text RalewayRegular { id: loginDetailText; - text: "To buy/sell items on the <b>Marketplace</b>, or to use your <b>Wallet</b>, you must first log in to High Fidelity."; + text: "To get items on the <b>Marketplace</b>, or to use your <b>Assets</b>, you must first log in to High Fidelity."; // Text size size: 18; // Anchors diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml index 8451c90836..6ddfe0da1c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml index c4abd40d2a..86d50e87ec 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "../common" as HifiCommerceCommon diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml index e052b78876..179ffcf707 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml deleted file mode 100644 index 14ac696ef7..0000000000 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ /dev/null @@ -1,246 +0,0 @@ -// -// Security.qml -// qml/hifi/commerce/wallet -// -// Security -// -// Created by Zach Fox on 2017-08-18 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import Hifi 1.0 as Hifi -import QtQuick 2.5 -import QtGraphicalEffects 1.0 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit -import "../../../controls" as HifiControls - -// references XXX from root context - -Item { - HifiConstants { id: hifi; } - - id: root; - property string keyFilePath; - - Connections { - target: Commerce; - - onKeyFilePathIfExistsResult: { - root.keyFilePath = path; - } - } - - // Username Text - RalewayRegular { - id: usernameText; - text: Account.username; - // Text size - size: 24; - // Style - color: hifi.colors.white; - elide: Text.ElideRight; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.leftMargin: 20; - width: parent.width/2; - height: 80; - } - - Item { - id: securityContainer; - anchors.top: usernameText.bottom; - anchors.topMargin: 20; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.bottom: parent.bottom; - - RalewaySemiBold { - id: securityText; - text: "Security"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.leftMargin: 20; - anchors.right: parent.right; - height: 30; - // Text size - size: 18; - // Style - color: hifi.colors.blueHighlight; - } - - Rectangle { - id: securityTextSeparator; - // Size - width: parent.width; - height: 1; - // Anchors - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: securityText.bottom; - anchors.topMargin: 8; - // Style - color: hifi.colors.faintGray; - } - - Item { - id: changeSecurityImageContainer; - anchors.top: securityTextSeparator.bottom; - anchors.topMargin: 8; - anchors.left: parent.left; - anchors.leftMargin: 40; - anchors.right: parent.right; - anchors.rightMargin: 55; - height: 75; - - HiFiGlyphs { - id: changeSecurityImageImage; - text: hifi.glyphs.securityImage; - // Size - size: 80; - // Anchors - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: parent.left; - // Style - color: hifi.colors.white; - } - - RalewaySemiBold { - text: "Security Pic"; - // Anchors - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: changeSecurityImageImage.right; - anchors.leftMargin: 30; - width: 50; - // Text size - size: 18; - // Style - color: hifi.colors.white; - } - - // "Change Security Pic" button - HifiControlsUit.Button { - id: changeSecurityImageButton; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.dark; - anchors.right: parent.right; - anchors.verticalCenter: parent.verticalCenter; - width: 140; - height: 40; - text: "Change"; - onClicked: { - sendSignalToWallet({method: 'walletSecurity_changeSecurityImage'}); - } - } - } - - Item { - id: autoLogoutContainer; - anchors.top: changeSecurityImageContainer.bottom; - anchors.topMargin: 8; - anchors.left: parent.left; - anchors.leftMargin: 40; - anchors.right: parent.right; - anchors.rightMargin: 55; - height: 75; - - HiFiGlyphs { - id: autoLogoutImage; - text: hifi.glyphs.walletKey; - // Size - size: 80; - // Anchors - anchors.top: parent.top; - anchors.topMargin: 20; - anchors.left: parent.left; - // Style - color: hifi.colors.white; - } - - HifiControlsUit.CheckBox { - id: autoLogoutCheckbox; - checked: Settings.getValue("wallet/autoLogout", false); - text: "Automatically Log Out when Exiting Interface" - // Anchors - anchors.verticalCenter: autoLogoutImage.verticalCenter; - anchors.left: autoLogoutImage.right; - anchors.leftMargin: 20; - anchors.right: autoLogoutHelp.left; - anchors.rightMargin: 12; - boxSize: 28; - labelFontSize: 18; - color: hifi.colors.white; - onCheckedChanged: { - Settings.setValue("wallet/autoLogout", checked); - if (checked) { - Settings.setValue("wallet/savedUsername", Account.username); - } else { - Settings.setValue("wallet/savedUsername", ""); - } - } - } - - RalewaySemiBold { - id: autoLogoutHelp; - text: '[?]'; - // Anchors - anchors.verticalCenter: autoLogoutImage.verticalCenter; - anchors.right: parent.right; - width: 30; - height: 30; - // Text size - size: 18; - // Style - color: hifi.colors.blueHighlight; - - MouseArea { - anchors.fill: parent; - hoverEnabled: true; - onEntered: { - parent.color = hifi.colors.blueAccent; - } - onExited: { - parent.color = hifi.colors.blueHighlight; - } - onClicked: { - sendSignalToWallet({method: 'walletSecurity_autoLogoutHelp'}); - } - } - } - } - } - - // - // FUNCTION DEFINITIONS START - // - // - // Function Name: fromScript() - // - // Relevant Variables: - // None - // - // Arguments: - // message: The message sent from the JavaScript. - // Messages are in format "{method, params}", like json-rpc. - // - // Description: - // Called when a message is received from a script. - // - function fromScript(message) { - switch (message.method) { - default: - console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); - } - } - signal sendSignalToWallet(var msg); - // - // FUNCTION DEFINITIONS END - // -} diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index cbb77883df..81fec4ace3 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -14,12 +14,14 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "../common" as HifiCommerceCommon import "../common/sendAsset" import "../.." as HifiCommon +import "../purchases" as HifiPurchases +import "../inspectionCertificate" as HifiInspectionCertificate Rectangle { HifiConstants { id: hifi; } @@ -27,6 +29,7 @@ Rectangle { id: root; property string activeView: "initialize"; + property string initialActiveViewAfterStatus5: "walletInventory"; property bool keyboardRaised: false; property bool isPassword: false; @@ -37,6 +40,10 @@ Rectangle { source: "images/wallet-bg.jpg"; } + Component.onDestruction: { + KeyboardScriptingInterface.raised = false; + } + Connections { target: Commerce; @@ -64,7 +71,8 @@ Rectangle { } } else if (walletStatus === 5) { if (root.activeView !== "walletSetup") { - root.activeView = "walletHome"; + root.activeView = root.initialActiveViewAfterStatus5; + Commerce.getAvailableUpdates(); Commerce.getSecurityImage(); } } else { @@ -86,6 +94,21 @@ Rectangle { titleBarSecurityImage.source = "image://security/securityImage"; } } + + onAvailableUpdatesResult: { + if (result.status !== 'success') { + console.log("Failed to get Available Updates", result.data.message); + } else { + exchangeMoneyButtonContainer.messagesWaiting = result.data.updates.length > 0; + } + } + } + + onActiveViewChanged: { + if (activeView === "walletHome") { + walletHomeButtonContainer.messagesWaiting = false; + sendToScript({method: 'clearShouldShowDotHistory'}); + } } HifiCommerceCommon.CommerceLightbox { @@ -108,29 +131,32 @@ Rectangle { anchors.top: parent.top; // Wallet icon - HiFiGlyphs { + Image { id: walletIcon; - text: hifi.glyphs.wallet; - // Size - size: parent.height * 0.8; - // Anchors + source: "../../../../icons/tablet-icons/inventory-a.svg"; + height: parent.height * 0.5; + width: walletIcon.height; anchors.left: parent.left; anchors.leftMargin: 8; anchors.verticalCenter: parent.verticalCenter; - // Style + visible: false; // When we use a white .svg instead of a glyph with color property, we set to invisible and use the following ColorOverlay. + } + ColorOverlay { + anchors.fill: walletIcon; + source: walletIcon; color: hifi.colors.blueHighlight; } // Title Bar text RalewaySemiBold { id: titleBarText; - text: "WALLET"; + text: "INVENTORY"; // Text size size: hifi.fontSizes.overlayTitle; // Anchors anchors.top: parent.top; anchors.left: walletIcon.right; - anchors.leftMargin: 4; + anchors.leftMargin: 6; anchors.bottom: parent.bottom; width: paintedWidth; // Style @@ -142,7 +168,7 @@ Rectangle { Image { id: titleBarSecurityImage; source: ""; - visible: titleBarSecurityImage.source !== "" && !securityImageChange.visible; + visible: titleBarSecurityImage.source !== ""; anchors.right: parent.right; anchors.rightMargin: 6; anchors.top: parent.top; @@ -232,10 +258,6 @@ Rectangle { root.isPassword = msg.isPasswordField; } else if (msg.method === 'walletSetup_lowerKeyboard') { root.keyboardRaised = false; - } else if (msg.method === 'walletSecurity_changePassphraseCancelled') { - root.activeView = "security"; - } else if (msg.method === 'walletSecurity_changePassphraseSuccess') { - root.activeView = "security"; } else { sendToScript(msg); } @@ -245,27 +267,6 @@ Rectangle { } } } - SecurityImageChange { - id: securityImageChange; - visible: root.activeView === "securityImageChange"; - z: 997; - anchors.top: titleBarContainer.bottom; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.bottom: parent.bottom; - - Connections { - onSendSignalToWallet: { - if (msg.method === 'walletSecurity_changeSecurityImageCancelled') { - root.activeView = "security"; - } else if (msg.method === 'walletSecurity_changeSecurityImageSuccess') { - root.activeView = "security"; - } else { - sendToScript(msg); - } - } - } - } // // TAB CONTENTS START @@ -344,6 +345,39 @@ Rectangle { } } + HifiInspectionCertificate.InspectionCertificate { + id: inspectionCertificate; + z: 998; + visible: false; + anchors.fill: parent; + + Connections { + onSendToScript: { + sendToScript(message); + } + } + } + + HifiPurchases.Purchases { + id: walletInventory; + visible: root.activeView === "walletInventory"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: !WalletScriptingInterface.limitedCommerce ? tabButtonsContainer.top : parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + Connections { + onSendToScript: { + if (message.method === 'purchases_itemCertificateClicked') { + inspectionCertificate.visible = true; + inspectionCertificate.isLightbox = true; + sendToScript(message); + } else { + sendToScript(message); + } + } + } + } + HifiCommon.RootHttpRequest { id: http; } @@ -366,39 +400,6 @@ Rectangle { } } - Security { - id: security; - visible: root.activeView === "security"; - anchors.top: titleBarContainer.bottom; - anchors.bottom: tabButtonsContainer.top; - anchors.left: parent.left; - anchors.right: parent.right; - - Connections { - onSendSignalToWallet: { - if (msg.method === 'walletSecurity_changePassphrase') { - root.activeView = "passphraseChange"; - passphraseChange.clearPassphraseFields(); - passphraseChange.resetSubmitButton(); - } else if (msg.method === 'walletSecurity_changeSecurityImage') { - securityImageChange.initModel(); - root.activeView = "securityImageChange"; - } else if (msg.method === 'walletSecurity_autoLogoutHelp') { - lightboxPopup.titleText = "Automatically Log Out"; - lightboxPopup.bodyText = "By default, after you log in to High Fidelity, you will stay logged in to your High Fidelity " + - "account even after you close and re-open Interface. This means anyone who opens Interface on your computer " + - "could make purchases with your Wallet.\n\n" + - "If you do not want to stay logged in across Interface sessions, check this box."; - lightboxPopup.button1text = "CLOSE"; - lightboxPopup.button1method = function() { - lightboxPopup.visible = false; - } - lightboxPopup.visible = true; - } - } - } - } - Help { id: help; visible: root.activeView === "help"; @@ -407,14 +408,6 @@ Rectangle { anchors.left: parent.left; anchors.right: parent.right; - Connections { - onSendSignalToWallet: { - if (msg.method === 'walletSecurity_changeSecurityImage') { - securityImageChange.initModel(); - root.activeView = "securityImageChange"; - } - } - } } @@ -427,8 +420,8 @@ Rectangle { // Item { id: tabButtonsContainer; - visible: !needsLogIn.visible && root.activeView !== "passphraseChange" && root.activeView !== "securityImageChange" && sendMoney.currentActiveView !== "sendAssetStep"; - property int numTabs: 5; + visible: !needsLogIn.visible && root.activeView !== "passphraseChange" && sendMoney.currentActiveView !== "sendAssetStep" && !WalletScriptingInterface.limitedCommerce; + property int numTabs: 4; // Size width: root.width; height: 90; @@ -446,16 +439,17 @@ Rectangle { // "WALLET HOME" tab button Rectangle { id: walletHomeButtonContainer; + property bool messagesWaiting: false; visible: !walletSetup.visible; color: root.activeView === "walletHome" ? hifi.colors.blueAccent : hifi.colors.black; anchors.top: parent.top; - anchors.left: parent.left; + anchors.left: exchangeMoneyButtonContainer.right; anchors.bottom: parent.bottom; width: parent.width / tabButtonsContainer.numTabs; HiFiGlyphs { id: homeTabIcon; - text: hifi.glyphs.home2; + text: hifi.glyphs.leftRightArrows; // Size size: 50; // Anchors @@ -463,11 +457,24 @@ Rectangle { anchors.top: parent.top; anchors.topMargin: -2; // Style - color: root.activeView === "walletHome" || walletHomeTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight; + color: WalletScriptingInterface.limitedCommerce ? hifi.colors.lightGray50 : ((root.activeView === "walletHome" || walletHomeTabMouseArea.containsMouse) ? hifi.colors.white : hifi.colors.blueHighlight); + } + + Rectangle { + id: recentActivityMessagesWaitingLight; + visible: parent.messagesWaiting; + anchors.right: homeTabIcon.left; + anchors.rightMargin: -4; + anchors.top: homeTabIcon.top; + anchors.topMargin: 16; + height: 10; + width: height; + radius: height/2; + color: "red"; } RalewaySemiBold { - text: "WALLET HOME"; + text: "RECENT ACTIVITY"; // Text size size: 16; // Anchors @@ -478,7 +485,7 @@ Rectangle { anchors.right: parent.right; anchors.rightMargin: 4; // Style - color: root.activeView === "walletHome" || walletHomeTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight; + color: WalletScriptingInterface.limitedCommerce ? hifi.colors.lightGray50 : ((root.activeView === "walletHome" || walletHomeTabMouseArea.containsMouse) ? hifi.colors.white : hifi.colors.blueHighlight); wrapMode: Text.WordWrap; // Alignment horizontalAlignment: Text.AlignHCenter; @@ -487,6 +494,7 @@ Rectangle { MouseArea { id: walletHomeTabMouseArea; anchors.fill: parent; + enabled: !WalletScriptingInterface.limitedCommerce; hoverEnabled: enabled; onClicked: { root.activeView = "walletHome"; @@ -500,28 +508,46 @@ Rectangle { // "EXCHANGE MONEY" tab button Rectangle { id: exchangeMoneyButtonContainer; + property bool messagesWaiting: false; + visible: !walletSetup.visible; - color: hifi.colors.black; + color: root.activeView === "walletInventory" ? hifi.colors.blueAccent : hifi.colors.black; anchors.top: parent.top; - anchors.left: walletHomeButtonContainer.right; + anchors.left: parent.left; anchors.bottom: parent.bottom; width: parent.width / tabButtonsContainer.numTabs; - HiFiGlyphs { + Image { id: exchangeMoneyTabIcon; - text: hifi.glyphs.leftRightArrows; - // Size - size: 50; - // Anchors + source: "images/items-tab-a.svg"; + height: 25; + width: exchangeMoneyTabIcon.height; anchors.horizontalCenter: parent.horizontalCenter; anchors.top: parent.top; - anchors.topMargin: -2; - // Style - color: hifi.colors.lightGray50; + anchors.topMargin: 10; + visible: false; // When we use a white .svg instead of a glyph with color property, we set to invisible and use the following ColorOverlay. + } + ColorOverlay { + anchors.fill: exchangeMoneyTabIcon; + source: exchangeMoneyTabIcon; + color: root.activeView === "walletInventory" || inventoryTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight; + } + + Rectangle { + id: exchangeMoneyMessagesWaitingLight; + visible: parent.messagesWaiting; + anchors.right: exchangeMoneyTabIcon.left; + anchors.rightMargin: 9; + anchors.top: exchangeMoneyTabIcon.top; + anchors.topMargin: 4; + height: 10; + width: height; + radius: height/2; + color: "red"; } RalewaySemiBold { - text: "EXCHANGE MONEY"; + text: "ITEMS"; // Text size size: 16; // Anchors @@ -532,12 +558,24 @@ Rectangle { anchors.right: parent.right; anchors.rightMargin: 4; // Style - color: hifi.colors.lightGray50; + color: root.activeView === "walletInventory" || inventoryTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight; wrapMode: Text.WordWrap; // Alignment horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignTop; } + + MouseArea { + id: inventoryTabMouseArea; + anchors.fill: parent; + hoverEnabled: enabled; + onClicked: { + root.activeView = "walletInventory"; + tabButtonsContainer.resetTabButtonColors(); + } + onEntered: parent.color = hifi.colors.blueHighlight; + onExited: parent.color = root.activeView === "walletInventory" ? hifi.colors.blueAccent : hifi.colors.black; + } } @@ -547,7 +585,7 @@ Rectangle { visible: !walletSetup.visible; color: root.activeView === "sendMoney" ? hifi.colors.blueAccent : hifi.colors.black; anchors.top: parent.top; - anchors.left: exchangeMoneyButtonContainer.right; + anchors.left: walletHomeButtonContainer.right; anchors.bottom: parent.bottom; width: parent.width / tabButtonsContainer.numTabs; @@ -561,7 +599,7 @@ Rectangle { anchors.top: parent.top; anchors.topMargin: -2; // Style - color: root.activeView === "sendMoney" || sendMoneyTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight; + color: WalletScriptingInterface.limitedCommerce ? hifi.colors.lightGray50 : ((root.activeView === "sendMoney" || sendMoneyTabMouseArea.containsMouse) ? hifi.colors.white : hifi.colors.blueHighlight); } RalewaySemiBold { @@ -576,7 +614,7 @@ Rectangle { anchors.right: parent.right; anchors.rightMargin: 4; // Style - color: root.activeView === "sendMoney" || sendMoneyTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight; + color: WalletScriptingInterface.limitedCommerce ? hifi.colors.lightGray50 : ((root.activeView === "sendMoney" || sendMoneyTabMouseArea.containsMouse) ? hifi.colors.white : hifi.colors.blueHighlight); wrapMode: Text.WordWrap; // Alignment horizontalAlignment: Text.AlignHCenter; @@ -586,6 +624,7 @@ Rectangle { MouseArea { id: sendMoneyTabMouseArea; anchors.fill: parent; + enabled: !WalletScriptingInterface.limitedCommerce; hoverEnabled: enabled; onClicked: { root.activeView = "sendMoney"; @@ -596,67 +635,13 @@ Rectangle { } } - // "SECURITY" tab button - Rectangle { - id: securityButtonContainer; - visible: !walletSetup.visible; - color: root.activeView === "security" ? hifi.colors.blueAccent : hifi.colors.black; - anchors.top: parent.top; - anchors.left: sendMoneyButtonContainer.right; - anchors.bottom: parent.bottom; - width: parent.width / tabButtonsContainer.numTabs; - - HiFiGlyphs { - id: securityTabIcon; - text: hifi.glyphs.lock; - // Size - size: 38; - // Anchors - anchors.horizontalCenter: parent.horizontalCenter; - anchors.top: parent.top; - anchors.topMargin: 2; - // Style - color: root.activeView === "security" || securityTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight; - } - - RalewaySemiBold { - text: "SECURITY"; - // Text size - size: 16; - // Anchors - anchors.bottom: parent.bottom; - height: parent.height/2; - anchors.left: parent.left; - anchors.leftMargin: 4; - anchors.right: parent.right; - anchors.rightMargin: 4; - // Style - color: root.activeView === "security" || securityTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight; - wrapMode: Text.WordWrap; - // Alignment - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignTop; - } - MouseArea { - id: securityTabMouseArea; - anchors.fill: parent; - hoverEnabled: enabled; - onClicked: { - root.activeView = "security"; - tabButtonsContainer.resetTabButtonColors(); - } - onEntered: parent.color = hifi.colors.blueHighlight; - onExited: parent.color = root.activeView === "security" ? hifi.colors.blueAccent : hifi.colors.black; - } - } - // "HELP" tab button Rectangle { id: helpButtonContainer; visible: !walletSetup.visible; color: root.activeView === "help" ? hifi.colors.blueAccent : hifi.colors.black; anchors.top: parent.top; - anchors.left: securityButtonContainer.right; + anchors.left: sendMoneyButtonContainer.right; anchors.bottom: parent.bottom; width: parent.width / tabButtonsContainer.numTabs; @@ -708,16 +693,16 @@ Rectangle { function resetTabButtonColors() { walletHomeButtonContainer.color = hifi.colors.black; sendMoneyButtonContainer.color = hifi.colors.black; - securityButtonContainer.color = hifi.colors.black; helpButtonContainer.color = hifi.colors.black; + exchangeMoneyButtonContainer.color = hifi.colors.black; if (root.activeView === "walletHome") { walletHomeButtonContainer.color = hifi.colors.blueAccent; } else if (root.activeView === "sendMoney") { sendMoneyButtonContainer.color = hifi.colors.blueAccent; - } else if (root.activeView === "security") { - securityButtonContainer.color = hifi.colors.blueAccent; } else if (root.activeView === "help") { helpButtonContainer.color = hifi.colors.blueAccent; + } else if (root.activeView == "walletInventory") { + exchangeMoneyButtonContainer.color = hifi.colors.blueAccent; } } } @@ -783,18 +768,40 @@ Rectangle { break; case 'updateConnections': sendMoney.updateConnections(message.connections); + walletInventory.fromScript(message); break; case 'selectRecipient': case 'updateSelectedRecipientUsername': sendMoney.fromScript(message); + walletInventory.fromScript(message); break; case 'http.response': http.handleHttpResponse(message); + // Duplicate handler is required because we don't track referrer for `http` + walletInventory.fromScript(message); break; case 'palIsStale': case 'avatarDisconnected': // Because we don't have "channels" for sending messages to a specific QML object, the messages are broadcast to all QML Items. If an Item of yours happens to be visible when some script sends a message with a method you don't expect, you'll get "Unrecognized message..." logs. break; + case 'inspectionCertificate_setCertificateId': + inspectionCertificate.fromScript(message); + break; + case 'updatePurchases': + case 'purchases_showMyItems': + case 'updateWearables': + walletInventory.fromScript(message); + break; + case 'updateRecentActivityMessageLight': + walletHomeButtonContainer.messagesWaiting = message.messagesWaiting; + break; + case 'checkout_openRecentActivity': + if (root.activeView === "initialize") { + root.initialActiveViewAfterStatus5 = "walletHome"; + } else { + root.activeView = "walletHome"; + } + break; default: console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); } @@ -842,7 +849,8 @@ Rectangle { root.activeView = "initialize"; Commerce.getWalletStatus(); } else if (msg.referrer === 'purchases') { - sendToScript({method: 'goToPurchases'}); + root.activeView = "walletInventory"; + tabButtonsContainer.resetTabButtonColors(); } else if (msg.referrer === 'marketplace cta' || msg.referrer === 'mainPage') { sendToScript({method: 'goToMarketplaceMainPage', itemId: msg.referrer}); } else { diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletChoice.qml b/interface/resources/qml/hifi/commerce/wallet/WalletChoice.qml index 19065ee542..e7163a3641 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletChoice.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletChoice.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import "../common" as HifiCommerceCommon -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit Item { diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 627da1d43f..4c8e1e6ca5 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -15,8 +15,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 import QtQuick.Controls 2.2 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. @@ -29,7 +29,6 @@ Item { if (visible) { Commerce.balance(); transactionHistoryModel.getFirstPage(); - Commerce.getAvailableUpdates(); } else { refreshTimer.stop(); } @@ -179,28 +178,6 @@ Item { color: hifi.colors.baseGrayHighlight; } - RalewaySemiBold { - id: myPurchasesLink; - text: '<font color="#0093C5"><a href="#myPurchases">My Purchases</a></font>'; - // Anchors - anchors.top: parent.top; - anchors.topMargin: 26; - anchors.right: parent.right; - anchors.rightMargin: 20; - width: paintedWidth; - height: 30; - y: 4; - // Text size - size: 18; - // Style - color: hifi.colors.baseGrayHighlight; - horizontalAlignment: Text.AlignRight; - - onLinkActivated: { - sendSignalToWallet({method: 'goToPurchases_fromWalletHome'}); - } - } - HifiModels.PSFListModel { id: transactionHistoryModel; property int lastPendingCount: 0; diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index dc6ce45a74..1cecebc41b 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "../common" as HifiCommerceCommon @@ -243,7 +243,6 @@ Item { height: 50; text: "Set Up Wallet"; onClicked: { - securityImageSelection.initModel(); root.activeView = "step_2"; } } @@ -267,124 +266,6 @@ Item { // FIRST PAGE END // - // - // SECURITY IMAGE SELECTION START - // - Item { - id: securityImageContainer; - visible: root.activeView === "step_2"; - // Anchors - anchors.top: titleBarContainer.bottom; - anchors.topMargin: 30; - anchors.bottom: parent.bottom; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - - // Text below title bar - RalewayRegular { - id: securityImageTitleHelper; - text: "Choose a Security Pic:"; - // Text size - size: 24; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - height: 50; - width: paintedWidth; - // Style - color: hifi.colors.white; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - - SecurityImageSelection { - id: securityImageSelection; - // Anchors - anchors.top: securityImageTitleHelper.bottom; - anchors.left: parent.left; - anchors.right: parent.right; - height: 300; - - Connections { - onSendSignalToWallet: { - sendSignalToWallet(msg); - } - } - } - - // Text below security images - RalewayRegular { - text: "<b>Your security picture shows you that the service asking for your passphrase is authorized.</b> You can change your secure picture at any time."; - // Text size - size: 18; - // Anchors - anchors.top: securityImageSelection.bottom; - anchors.topMargin: 40; - anchors.left: parent.left; - anchors.right: parent.right; - height: paintedHeight; - // Style - color: hifi.colors.white; - wrapMode: Text.WordWrap; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - - // Navigation Bar - Item { - // Size - width: parent.width; - height: 50; - // Anchors: - anchors.left: parent.left; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 50; - - // "Back" button - HifiControlsUit.Button { - color: hifi.buttons.noneBorderlessWhite; - colorScheme: hifi.colorSchemes.dark; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: parent.left; - anchors.leftMargin: 20; - width: 200; - text: "Back" - onClicked: { - securityImageSelection.resetSelection(); - root.activeView = "step_1"; - } - } - - // "Next" button - HifiControlsUit.Button { - enabled: securityImageSelection.currentIndex !== -1; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.dark; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.right: parent.right; - anchors.rightMargin: 20; - width: 200; - text: "Next"; - onClicked: { - root.lastPage = "step_2"; - var securityImagePath = securityImageSelection.getImagePathFromImageID(securityImageSelection.getSelectedImageIndex()) - Commerce.chooseSecurityImage(securityImagePath); - root.activeView = "step_3"; - passphraseSelection.clearPassphraseFields(); - } - } - } - } - // - // SECURITY IMAGE SELECTION END - // - // // SECURE PASSPHRASE SELECTION START // @@ -525,7 +406,6 @@ Item { width: 200; text: "Back" onClicked: { - securityImageSelection.resetSelection(); root.lastPage = "step_3"; root.activeView = "step_2"; } diff --git a/interface/resources/qml/hifi/commerce/wallet/images/items-tab-a.svg b/interface/resources/qml/hifi/commerce/wallet/images/items-tab-a.svg new file mode 100644 index 0000000000..7474f4ed57 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/images/items-tab-a.svg @@ -0,0 +1,8 @@ +<svg width="26" height="24" viewBox="0 0 26 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<line x1="6" y1="2" x2="26" y2="2" stroke="white" stroke-width="4"/> +<line x1="1.74846e-07" y1="2" x2="4" y2="2" stroke="white" stroke-width="4"/> +<line x1="6" y1="12" x2="26" y2="12" stroke="white" stroke-width="4"/> +<line x1="1.74846e-07" y1="12" x2="4" y2="12" stroke="white" stroke-width="4"/> +<line x1="6" y1="22" x2="26" y2="22" stroke="white" stroke-width="4"/> +<line x1="1.74846e-07" y1="22" x2="4" y2="22" stroke="white" stroke-width="4"/> +</svg> diff --git a/interface/resources/qml/hifi/dialogs/AboutDialog.qml b/interface/resources/qml/hifi/dialogs/AboutDialog.qml index b8e6e89aec..3d5d1a94a3 100644 --- a/interface/resources/qml/hifi/dialogs/AboutDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AboutDialog.qml @@ -10,7 +10,7 @@ import QtQuick 2.8 -import "../../styles-uit" +import stylesUit 1.0 import "../../windows" ScrollingWindow { diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index 9a180a66f6..be17e65ab3 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -13,8 +13,8 @@ import QtQuick.Controls 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" import "../" diff --git a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml index 579aa1cb1e..d26bf81e57 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import "../../styles-uit" +import stylesUit 1.0 Rectangle { width: 480 diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index 0eeb252049..f665032b01 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -14,8 +14,8 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" import ".." diff --git a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml index afe06897df..763f56b92b 100644 --- a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" Rectangle { diff --git a/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml b/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml index 50df4dedbc..213dca8b48 100644 --- a/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml +++ b/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import Hifi 1.0 as Hifi -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Rectangle { id: root diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml index 24798af21a..4cfc99e0eb 100644 --- a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" Rectangle { diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml index d5c5a5ee02..e86dfd7554 100644 --- a/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Column { id: root diff --git a/interface/resources/qml/hifi/dialogs/TabletLODTools.qml b/interface/resources/qml/hifi/dialogs/TabletLODTools.qml index ab53f03477..bb3d668850 100644 --- a/interface/resources/qml/hifi/dialogs/TabletLODTools.qml +++ b/interface/resources/qml/hifi/dialogs/TabletLODTools.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" Rectangle { diff --git a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml index 018c8f5737..b0f17ff841 100644 --- a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml @@ -13,8 +13,8 @@ import QtQuick.Controls 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" import "../" @@ -88,6 +88,10 @@ Rectangle { checkMenu.restart(); } + Component.onDestruction: { + keyboard.raised = false; + } + function updateRunningScripts() { function simplify(path) { // trim URI querystring/fragment diff --git a/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml b/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml index ce1abc6154..b1aa8e5c45 100644 --- a/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml +++ b/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml @@ -1,7 +1,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 -import "../../../controls-uit" as HifiControls +import controlsUit 1.0 as HifiControls Column { width: pane.contentWidth diff --git a/interface/resources/qml/hifi/dialogs/security/Security.qml b/interface/resources/qml/hifi/dialogs/security/Security.qml new file mode 100644 index 0000000000..08605033a5 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/security/Security.qml @@ -0,0 +1,344 @@ +// +// Security.qml +// qml\hifi\dialogs\security +// +// Security +// +// Created by Zach Fox on 2018-10-31 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtGraphicalEffects 1.0 +import stylesUit 1.0 as HifiStylesUit +import controlsUit 1.0 as as HifiControlsUit +import "qrc:////qml//controls" as HifiControls +import "qrc:////qml//hifi//commerce//common" as HifiCommerceCommon + +Rectangle { + HifiStylesUit.HifiConstants { id: hifi; } + + id: root; + color: hifi.colors.baseGray; + + property string title: "Security Settings"; + property bool walletSetUp; + + QtObject { + id: margins + property real paddings: root.width / 20.25 + + property real sizeCheckBox: root.width / 13.5 + property real sizeText: root.width / 2.5 + property real sizeLevel: root.width / 5.8 + property real sizeDesktop: root.width / 5.8 + property real sizeVR: root.width / 13.5 + } + + Connections { + target: Commerce; + + onWalletStatusResult: { + if (walletStatus === 5) { + Commerce.getSecurityImage(); + root.walletSetUp = true; + } else { + root.walletSetUp = false; + } + } + + onSecurityImageResult: { + if (exists) { + currentSecurityPicture.source = ""; + currentSecurityPicture.source = "image://security/securityImage"; + } + } + } + + Component.onCompleted: { + Commerce.getWalletStatus(); + } + + HifiCommerceCommon.CommerceLightbox { + z: 996; + id: lightboxPopup; + visible: false; + anchors.fill: parent; + } + + SecurityImageChange { + id: securityImageChange; + visible: false; + z: 997; + anchors.top: usernameText.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + + Connections { + onSendSignalToParent: { + securityImageChange.visible = false; + } + } + } + + // Username Text + HifiStylesUit.RalewayRegular { + id: usernameText; + text: Account.username === "Unknown user" ? "Please Log In" : Account.username; + // Text size + size: 24; + // Style + color: hifi.colors.white; + elide: Text.ElideRight; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 60; + } + + Item { + id: pleaseLogInContainer; + visible: Account.username === "Unknown user"; + anchors.top: usernameText.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + + HifiStylesUit.RalewayRegular { + text: "Please log in for security settings." + // Text size + size: 24; + // Style + color: hifi.colors.white; + // Anchors + anchors.bottom: openLoginButton.top; + anchors.left: parent.left; + anchors.right: parent.right; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + height: 60; + } + + HifiControlsUit.Button { + id: openLoginButton; + color: hifi.buttons.white; + colorScheme: hifi.colorSchemes.dark; + anchors.centerIn: parent; + width: 140; + height: 40; + text: "Log In"; + onClicked: { + DialogsManager.showLoginDialog(); + } + } + } + + Item { + id: securitySettingsContainer; + visible: !pleaseLogInContainer.visible; + anchors.top: usernameText.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + + Item { + id: accountContainer; + anchors.top: securitySettingsContainer.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: childrenRect.height; + + Rectangle { + id: accountHeaderContainer; + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: 55; + color: hifi.colors.baseGrayHighlight; + + HifiStylesUit.RalewaySemiBold { + text: "Account"; + anchors.fill: parent; + anchors.leftMargin: 20; + color: hifi.colors.white; + size: 18; + } + } + + Item { + id: keepMeLoggedInContainer; + anchors.top: accountHeaderContainer.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: 80; + + HifiControlsUit.CheckBox { + id: autoLogoutCheckbox; + checked: Settings.getValue("keepMeLoggedIn", false); + text: "Keep Me Logged In" + // Anchors + anchors.verticalCenter: parent.verticalCenter; + anchors.left: parent.left; + anchors.leftMargin: 20; + boxSize: 24; + labelFontSize: 18; + colorScheme: hifi.colorSchemes.dark + color: hifi.colors.white; + width: 240; + onCheckedChanged: { + Settings.setValue("keepMeLoggedIn", checked); + if (checked) { + Settings.setValue("keepMeLoggedIn/savedUsername", Account.username); + } else { + Settings.setValue("keepMeLoggedIn/savedUsername", ""); + } + } + } + + HifiStylesUit.RalewaySemiBold { + id: autoLogoutHelp; + text: '[?]'; + // Anchors + anchors.verticalCenter: parent.verticalCenter; + anchors.right: autoLogoutCheckbox.right; + width: 30; + height: 30; + // Text size + size: 18; + // Style + color: hifi.colors.blueHighlight; + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + parent.color = hifi.colors.blueAccent; + } + onExited: { + parent.color = hifi.colors.blueHighlight; + } + onClicked: { + lightboxPopup.titleText = "Keep Me Logged In"; + lightboxPopup.bodyText = "If you choose to stay logged in, ensure that this is a trusted device.\n\n" + + "Also, remember that logging out may not disconnect you from a domain."; + lightboxPopup.button1text = "OK"; + lightboxPopup.button1method = function() { + lightboxPopup.visible = false; + } + lightboxPopup.visible = true; + } + } + } + } + } + + Item { + id: walletContainer; + anchors.top: accountContainer.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: childrenRect.height; + + Rectangle { + id: walletHeaderContainer; + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: 55; + color: hifi.colors.baseGrayHighlight; + + HifiStylesUit.RalewaySemiBold { + text: "Wallet"; + anchors.fill: parent; + anchors.leftMargin: 20; + color: hifi.colors.white; + size: 18; + } + } + + Item { + id: walletSecurityPictureContainer; + visible: root.walletSetUp; + anchors.top: walletHeaderContainer.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: 80; + + Image { + id: currentSecurityPicture; + source: ""; + visible: true; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.verticalCenter: parent.verticalCenter; + height: 40; + width: height; + mipmap: true; + cache: false; + } + + HifiStylesUit.RalewaySemiBold { + id: securityPictureText; + text: "Wallet Security Picture"; + // Anchors + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.left: currentSecurityPicture.right; + anchors.leftMargin: 12; + width: paintedWidth; + // Text size + size: 18; + // Style + color: hifi.colors.white; + } + + // "Change Security Pic" button + HifiControlsUit.Button { + id: changeSecurityImageButton; + color: hifi.buttons.white; + colorScheme: hifi.colorSchemes.dark; + anchors.left: securityPictureText.right; + anchors.leftMargin: 12; + anchors.verticalCenter: parent.verticalCenter; + width: 140; + height: 40; + text: "Change"; + onClicked: { + securityImageChange.visible = true; + securityImageChange.initModel(); + } + } + } + + Item { + id: walletNotSetUpContainer; + visible: !root.walletSetUp; + anchors.top: walletHeaderContainer.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: 60; + + HifiStylesUit.RalewayRegular { + text: "Your wallet is not set up.\n" + + "Open the ASSETS app to get started."; + // Anchors + anchors.fill: parent; + // Text size + size: 18; + // Style + color: hifi.colors.white; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + } + } + } +} diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml b/interface/resources/qml/hifi/dialogs/security/SecurityImageChange.qml similarity index 88% rename from interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml rename to interface/resources/qml/hifi/dialogs/security/SecurityImageChange.qml index 01df18352b..82d094144d 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml +++ b/interface/resources/qml/hifi/dialogs/security/SecurityImageChange.qml @@ -1,11 +1,11 @@ // // SecurityImageChange.qml -// qml/hifi/commerce/wallet +// qml\hifi\dialogs\security // -// SecurityImageChange +// Security // -// Created by Zach Fox on 2017-08-18 -// Copyright 2017 High Fidelity, Inc. +// Created by Zach Fox on 2018-10-31 +// Copyright 2018 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -13,16 +13,17 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit -import "../../../controls" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "qrc:////qml//controls" as HifiControls // references XXX from root context -Item { - HifiConstants { id: hifi; } +Rectangle { + HifiStylesUit.HifiConstants { id: hifi; } id: root; + color: hifi.colors.baseGray; property bool justSubmitted: false; Connections { @@ -33,7 +34,7 @@ Item { securityImageChangePageSecurityImage.source = "image://security/securityImage"; if (exists) { // Success submitting new security image if (root.justSubmitted) { - sendSignalToWallet({method: "walletSecurity_changeSecurityImageSuccess"}); + sendSignalToParent({method: "walletSecurity_changeSecurityImageSuccess"}); root.justSubmitted = false; } } else if (root.justSubmitted) { @@ -72,7 +73,7 @@ Item { anchors.bottom: parent.bottom; height: 22; // Lock icon - HiFiGlyphs { + HifiStylesUit.HiFiGlyphs { id: lockIcon; text: hifi.glyphs.lock; anchors.bottom: parent.bottom; @@ -83,8 +84,8 @@ Item { verticalAlignment: Text.AlignBottom; color: hifi.colors.white; } - // "Security image" text below pic - RalewayRegular { + // "Security image" text below image + HifiStylesUit.RalewayRegular { id: securityImageText; text: "SECURITY PIC"; // Text size @@ -116,9 +117,9 @@ Item { anchors.bottom: parent.bottom; // "Change Security Image" text - RalewaySemiBold { + HifiStylesUit.RalewaySemiBold { id: securityImageTitle; - text: "Change Security Pic:"; + text: "Change Security Image:"; // Text size size: 18; anchors.top: parent.top; @@ -139,12 +140,6 @@ Item { anchors.right: parent.right; anchors.rightMargin: 16; height: 300; - - Connections { - onSendSignalToWallet: { - sendSignalToWallet(msg); - } - } } // Navigation Bar @@ -169,7 +164,7 @@ Item { width: 150; text: "Cancel" onClicked: { - sendSignalToWallet({method: "walletSecurity_changeSecurityImageCancelled"}); + sendSignalToParent({method: "walletSecurity_changeSecurityImageCancelled"}); } } @@ -197,7 +192,7 @@ Item { // SECURITY IMAGE SELECTION END // - signal sendSignalToWallet(var msg); + signal sendSignalToParent(var msg); function initModel() { securityImageSelection.initModel(); diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml b/interface/resources/qml/hifi/dialogs/security/SecurityImageModel.qml similarity index 89% rename from interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml rename to interface/resources/qml/hifi/dialogs/security/SecurityImageModel.qml index b8e9db09ab..946f979c1a 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml +++ b/interface/resources/qml/hifi/dialogs/security/SecurityImageModel.qml @@ -1,11 +1,11 @@ // // SecurityImageModel.qml -// qml/hifi/commerce +// qml\hifi\dialogs\security // -// SecurityImageModel +// Security // -// Created by Zach Fox on 2017-08-17 -// Copyright 2017 High Fidelity, Inc. +// Created by Zach Fox on 2018-10-31 +// Copyright 2018 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml b/interface/resources/qml/hifi/dialogs/security/SecurityImageSelection.qml similarity index 81% rename from interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml rename to interface/resources/qml/hifi/dialogs/security/SecurityImageSelection.qml index 599c6a1851..366372622c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml +++ b/interface/resources/qml/hifi/dialogs/security/SecurityImageSelection.qml @@ -1,11 +1,11 @@ // // SecurityImageSelection.qml -// qml/hifi/commerce/wallet +// qml\hifi\dialogs\security // -// SecurityImageSelection +// Security // -// Created by Zach Fox on 2017-08-17 -// Copyright 2017 High Fidelity, Inc. +// Created by Zach Fox on 2018-10-31 +// Copyright 2018 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -13,14 +13,14 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit -import "../../../controls" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "qrc:////qml//controls" as HifiControls // references XXX from root context Item { - HifiConstants { id: hifi; } + HifiStylesUit.HifiConstants { id: hifi; } id: root; property alias currentIndex: securityImageGrid.currentIndex; @@ -64,17 +64,15 @@ Item { } } highlight: Rectangle { - width: securityImageGrid.cellWidth; - height: securityImageGrid.cellHeight; - color: hifi.colors.blueHighlight; - } + width: securityImageGrid.cellWidth; + height: securityImageGrid.cellHeight; + color: hifi.colors.blueHighlight; + } } // // FUNCTION DEFINITIONS START // - signal sendSignalToWallet(var msg); - function getImagePathFromImageID(imageID) { return (imageID ? gridModel.getImagePathFromImageID(imageID) : ""); } diff --git a/interface/resources/qml/hifi/commerce/wallet/images/01.jpg b/interface/resources/qml/hifi/dialogs/security/images/01.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/01.jpg rename to interface/resources/qml/hifi/dialogs/security/images/01.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/02.jpg b/interface/resources/qml/hifi/dialogs/security/images/02.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/02.jpg rename to interface/resources/qml/hifi/dialogs/security/images/02.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/03.jpg b/interface/resources/qml/hifi/dialogs/security/images/03.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/03.jpg rename to interface/resources/qml/hifi/dialogs/security/images/03.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/04.jpg b/interface/resources/qml/hifi/dialogs/security/images/04.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/04.jpg rename to interface/resources/qml/hifi/dialogs/security/images/04.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/05.jpg b/interface/resources/qml/hifi/dialogs/security/images/05.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/05.jpg rename to interface/resources/qml/hifi/dialogs/security/images/05.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/06.jpg b/interface/resources/qml/hifi/dialogs/security/images/06.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/06.jpg rename to interface/resources/qml/hifi/dialogs/security/images/06.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/07.jpg b/interface/resources/qml/hifi/dialogs/security/images/07.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/07.jpg rename to interface/resources/qml/hifi/dialogs/security/images/07.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/08.jpg b/interface/resources/qml/hifi/dialogs/security/images/08.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/08.jpg rename to interface/resources/qml/hifi/dialogs/security/images/08.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/09.jpg b/interface/resources/qml/hifi/dialogs/security/images/09.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/09.jpg rename to interface/resources/qml/hifi/dialogs/security/images/09.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/10.jpg b/interface/resources/qml/hifi/dialogs/security/images/10.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/10.jpg rename to interface/resources/qml/hifi/dialogs/security/images/10.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/11.jpg b/interface/resources/qml/hifi/dialogs/security/images/11.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/11.jpg rename to interface/resources/qml/hifi/dialogs/security/images/11.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/12.jpg b/interface/resources/qml/hifi/dialogs/security/images/12.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/12.jpg rename to interface/resources/qml/hifi/dialogs/security/images/12.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/13.jpg b/interface/resources/qml/hifi/dialogs/security/images/13.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/13.jpg rename to interface/resources/qml/hifi/dialogs/security/images/13.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/14.jpg b/interface/resources/qml/hifi/dialogs/security/images/14.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/14.jpg rename to interface/resources/qml/hifi/dialogs/security/images/14.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/15.jpg b/interface/resources/qml/hifi/dialogs/security/images/15.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/15.jpg rename to interface/resources/qml/hifi/dialogs/security/images/15.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/16.jpg b/interface/resources/qml/hifi/dialogs/security/images/16.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/16.jpg rename to interface/resources/qml/hifi/dialogs/security/images/16.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/17.jpg b/interface/resources/qml/hifi/dialogs/security/images/17.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/17.jpg rename to interface/resources/qml/hifi/dialogs/security/images/17.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/18.jpg b/interface/resources/qml/hifi/dialogs/security/images/18.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/18.jpg rename to interface/resources/qml/hifi/dialogs/security/images/18.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/19.jpg b/interface/resources/qml/hifi/dialogs/security/images/19.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/19.jpg rename to interface/resources/qml/hifi/dialogs/security/images/19.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/20.jpg b/interface/resources/qml/hifi/dialogs/security/images/20.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/20.jpg rename to interface/resources/qml/hifi/dialogs/security/images/20.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/21.jpg b/interface/resources/qml/hifi/dialogs/security/images/21.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/21.jpg rename to interface/resources/qml/hifi/dialogs/security/images/21.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/22.jpg b/interface/resources/qml/hifi/dialogs/security/images/22.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/22.jpg rename to interface/resources/qml/hifi/dialogs/security/images/22.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/23.jpg b/interface/resources/qml/hifi/dialogs/security/images/23.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/23.jpg rename to interface/resources/qml/hifi/dialogs/security/images/23.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/24.jpg b/interface/resources/qml/hifi/dialogs/security/images/24.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/24.jpg rename to interface/resources/qml/hifi/dialogs/security/images/24.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/25.jpg b/interface/resources/qml/hifi/dialogs/security/images/25.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/25.jpg rename to interface/resources/qml/hifi/dialogs/security/images/25.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/26.jpg b/interface/resources/qml/hifi/dialogs/security/images/26.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/26.jpg rename to interface/resources/qml/hifi/dialogs/security/images/26.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/27.jpg b/interface/resources/qml/hifi/dialogs/security/images/27.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/27.jpg rename to interface/resources/qml/hifi/dialogs/security/images/27.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/28.jpg b/interface/resources/qml/hifi/dialogs/security/images/28.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/28.jpg rename to interface/resources/qml/hifi/dialogs/security/images/28.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/29.jpg b/interface/resources/qml/hifi/dialogs/security/images/29.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/29.jpg rename to interface/resources/qml/hifi/dialogs/security/images/29.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/30.jpg b/interface/resources/qml/hifi/dialogs/security/images/30.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/30.jpg rename to interface/resources/qml/hifi/dialogs/security/images/30.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/31.jpg b/interface/resources/qml/hifi/dialogs/security/images/31.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/31.jpg rename to interface/resources/qml/hifi/dialogs/security/images/31.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/32.jpg b/interface/resources/qml/hifi/dialogs/security/images/32.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/32.jpg rename to interface/resources/qml/hifi/dialogs/security/images/32.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/33.jpg b/interface/resources/qml/hifi/dialogs/security/images/33.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/33.jpg rename to interface/resources/qml/hifi/dialogs/security/images/33.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/34.jpg b/interface/resources/qml/hifi/dialogs/security/images/34.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/34.jpg rename to interface/resources/qml/hifi/dialogs/security/images/34.jpg diff --git a/interface/resources/qml/hifi/tablet/CalibratingScreen.qml b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml index e3115a5738..6b2aa331e8 100644 --- a/interface/resources/qml/hifi/tablet/CalibratingScreen.qml +++ b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml @@ -10,9 +10,9 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../../styles-uit" +import stylesUit 1.0 import "../../controls" -import "../../controls-uit" as HifiControls +import controlsUit 1.0 as HifiControls Rectangle { diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index 6706830537..b8bbd71f33 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -11,9 +11,9 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 import Qt.labs.settings 1.0 -import "../../styles-uit" +import stylesUit 1.0 import "../../controls" -import "../../controls-uit" as HifiControls +import controlsUit 1.0 as HifiControls import "../../dialogs" import "../../dialogs/preferences" import "tabletWindows" diff --git a/interface/resources/qml/hifi/tablet/Edit.qml b/interface/resources/qml/hifi/tablet/Edit.qml index 4acced86ce..099c53cda2 100644 --- a/interface/resources/qml/hifi/tablet/Edit.qml +++ b/interface/resources/qml/hifi/tablet/Edit.qml @@ -49,5 +49,11 @@ StackView { if (currentItem && currentItem.fromScript) currentItem.fromScript(message); } + + Component.onDestruction: { + if (KeyboardScriptingInterface.raised) { + KeyboardScriptingInterface.raised = false; + } + } } diff --git a/interface/resources/qml/hifi/tablet/EditEntityList.qml b/interface/resources/qml/hifi/tablet/EditEntityList.qml index d484885103..d2fb99ea0a 100644 --- a/interface/resources/qml/hifi/tablet/EditEntityList.qml +++ b/interface/resources/qml/hifi/tablet/EditEntityList.qml @@ -4,8 +4,8 @@ import QtWebChannel 1.0 import "../../controls" import "../toolbars" import QtGraphicalEffects 1.0 -import "../../controls-uit" as HifiControls -import "../../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 WebView { diff --git a/interface/resources/qml/hifi/tablet/EditTabButton.qml b/interface/resources/qml/hifi/tablet/EditTabButton.qml index 13894f4d15..5fc4341eb8 100644 --- a/interface/resources/qml/hifi/tablet/EditTabButton.qml +++ b/interface/resources/qml/hifi/tablet/EditTabButton.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../../controls-uit" as HifiControls -import "../../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 TabButton { id: control diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index 4ac8755570..d9549feeb0 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -4,8 +4,8 @@ import QtWebChannel 1.0 import "../../controls" import "../toolbars" import QtGraphicalEffects 1.0 -import "../../controls-uit" as HifiControls -import "../../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 TabBar { id: editTabView @@ -175,7 +175,7 @@ TabBar { method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" } }); - editTabView.currentIndex = 4 + editTabView.currentIndex = 2 } } @@ -279,21 +279,6 @@ TabBar { } } - EditTabButton { - title: "P" - active: true - enabled: true - property string originalUrl: "" - - property Component visualItem: Component { - WebView { - id: particleExplorerWebView - url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html" - enabled: true - } - } - } - function fromScript(message) { switch (message.method) { case 'selectTab': @@ -326,9 +311,6 @@ TabBar { case 'grid': editTabView.currentIndex = 3; break; - case 'particle': - editTabView.currentIndex = 4; - break; default: console.warn('Attempt to switch to invalid tab:', id); } diff --git a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml index 00084b8ca9..dc7ad683e3 100644 --- a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml @@ -4,8 +4,8 @@ import QtWebChannel 1.0 import "../../controls" import "../toolbars" import QtGraphicalEffects 1.0 -import "../../controls-uit" as HifiControls -import "../../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 TabBar { id: editTabView @@ -18,7 +18,6 @@ TabBar { readonly property int create: 0 readonly property int properties: 1 readonly property int grid: 2 - readonly property int particle: 3 } readonly property HifiConstants hifi: HifiConstants {} @@ -182,7 +181,7 @@ TabBar { method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" } }); - editTabView.currentIndex = tabIndex.particle + editTabView.currentIndex = tabIndex.properties } } @@ -271,21 +270,6 @@ TabBar { } } - EditTabButton { - title: "P" - active: true - enabled: true - property string originalUrl: "" - - property Component visualItem: Component { - WebView { - id: particleExplorerWebView - url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html" - enabled: true - } - } - } - function fromScript(message) { switch (message.method) { case 'selectTab': @@ -299,7 +283,7 @@ TabBar { // Changes the current tab based on tab index or title as input function selectTab(id) { if (typeof id === 'number') { - if (id >= tabIndex.create && id <= tabIndex.particle) { + if (id >= tabIndex.create && id <= tabIndex.grid) { editTabView.currentIndex = id; } else { console.warn('Attempt to switch to invalid tab:', id); @@ -315,9 +299,6 @@ TabBar { case 'grid': editTabView.currentIndex = tabIndex.grid; break; - case 'particle': - editTabView.currentIndex = tabIndex.particle; - break; default: console.warn('Attempt to switch to invalid tab:', id); } diff --git a/interface/resources/qml/hifi/tablet/InputRecorder.qml b/interface/resources/qml/hifi/tablet/InputRecorder.qml index 527a6cacb4..9b63a612a8 100644 --- a/interface/resources/qml/hifi/tablet/InputRecorder.qml +++ b/interface/resources/qml/hifi/tablet/InputRecorder.qml @@ -9,8 +9,8 @@ import QtQuick 2.5 import Hifi 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" import "../../dialogs" diff --git a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml index 526a42f8e2..dde372648b 100644 --- a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml @@ -13,8 +13,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Dialogs 1.2 as OriginalDialogs -import "../../styles-uit" -import "../../controls-uit" +import stylesUit 1.0 +import controlsUit 1.0 import "../dialogs" Rectangle { diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml index 553a4fd59f..9540979479 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml @@ -12,8 +12,8 @@ import QtQuick 2.5 import QtQuick.Dialogs 1.2 as OriginalDialogs -import "../../styles-uit" -import "../../controls-uit" +import stylesUit 1.0 +import controlsUit 1.0 import "../dialogs" Rectangle { diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index c2aff08e35..2fc5cc4196 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -9,9 +9,9 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../../styles-uit" +import stylesUit 1.0 import "../../controls" -import "../../controls-uit" as HifiControls +import controlsUit 1.0 as HifiControls import "." @@ -822,11 +822,44 @@ Flickable { } } + Row { + id: outOfRangeDataStrategyRow + anchors.top: viveInDesktop.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 15 + + RalewayRegular { + id: outOfRangeDataStrategyLabel + size: 12 + text: "Out Of Range Data Strategy:" + color: hifi.colors.lightGrayText + topPadding: 5 + } + + HifiControls.ComboBox { + id: outOfRangeDataStrategyComboBox + + height: 25 + width: 100 + + editable: true + colorScheme: hifi.colorSchemes.dark + model: ["None", "Freeze", "Drop"] + label: "" + + onCurrentIndexChanged: { + sendConfigurationSettings(); + } + } + } + RalewayBold { id: viveDesktopText - size: 10 + size: 12 text: "Use " + stack.selectedPlugin + " devices in desktop mode" - color: hifi.colors.white + color: hifi.colors.lightGrayText anchors { left: viveInDesktop.right @@ -946,6 +979,7 @@ Flickable { viveInDesktop.checked = desktopMode; hmdInDesktop.checked = hmdDesktopPosition; + outOfRangeDataStrategyComboBox.currentIndex = outOfRangeDataStrategyComboBox.model.indexOf(settings.outOfRangeDataStrategy); initializeButtonState(); updateCalibrationText(); @@ -1107,7 +1141,8 @@ Flickable { "armCircumference": armCircumference.realValue, "shoulderWidth": shoulderWidth.realValue, "desktopMode": viveInDesktop.checked, - "hmdDesktopTracking": hmdInDesktop.checked + "hmdDesktopTracking": hmdInDesktop.checked, + "outOfRangeDataStrategy": outOfRangeDataStrategyComboBox.model[outOfRangeDataStrategyComboBox.currentIndex] } return settingsObject; diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 3d518289fb..b8972378ad 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -18,8 +18,8 @@ import "../../styles" import "../../windows" import "../" import "../toolbars" -import "../../styles-uit" as HifiStyles -import "../../controls-uit" as HifiControls +import stylesUit 1.0 as HifiStyles +import controlsUit 1.0 as HifiControls import QtQuick.Controls 2.2 as QQC2 import QtQuick.Templates 2.2 as T @@ -56,10 +56,13 @@ StackView { Qt.callLater(function() { addressBarDialog.keyboardEnabled = HMD.active; addressLine.forceActiveFocus(); + addressBarDialog.raised = true; }) } + Component.onDestruction: { root.parentChanged.disconnect(center); + keyboard.raised = false; } function center() { @@ -218,6 +221,11 @@ StackView { leftMargin: 8; verticalCenter: addressLineContainer.verticalCenter; } + + onFocusChanged: { + addressBarDialog.raised = focus; + } + onTextChanged: { updateLocationText(text.length > 0); } diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml index 1922b02f93..934ed91995 100644 --- a/interface/resources/qml/hifi/tablet/TabletHome.qml +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -6,7 +6,7 @@ import QtQuick.Layouts 1.3 import TabletScriptingInterface 1.0 import "." -import "../../styles-uit" +import stylesUit 1.0 import "../audio" as HifiAudio Item { diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index 6540d53fca..267fb9f0cf 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -7,7 +7,7 @@ import QtWebEngine 1.1 import "." -import "../../styles-uit" +import stylesUit 1.0 import "../../controls" FocusScope { diff --git a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml index 74f175e049..25db90c771 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import "../../controls-uit" -import "../../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: root diff --git a/interface/resources/qml/hifi/tablet/TabletMenuView.qml b/interface/resources/qml/hifi/tablet/TabletMenuView.qml index b632a17e57..73b0405984 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuView.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuView.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import TabletScriptingInterface 1.0 -import "../../styles-uit" +import stylesUit 1.0 import "." FocusScope { diff --git a/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml b/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml index d69d760b95..ce4e641476 100644 --- a/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import "../../controls-uit" as HifiControls -import "../../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 import "../dialogs/content" Item { diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml index 9c027308b8..d55ec363f0 100644 --- a/interface/resources/qml/hifi/tablet/WindowRoot.qml +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -129,8 +129,21 @@ Windows.ScrollingWindow { height: pane.scrollHeight width: pane.contentWidth - anchors.left: parent.left - anchors.top: parent.top + + // this might be looking not clear from the first look + // but loader.parent is not tabletRoot and it can be null! + // unfortunately we can't use conditional bindings here due to https://bugreports.qt.io/browse/QTBUG-22005 + + onParentChanged: { + if (parent) { + anchors.left = Qt.binding(function() { return parent.left }) + anchors.top = Qt.binding(function() { return parent.top }) + } else { + anchors.left = undefined + anchors.top = undefined + } + } + signal loaded; onWidthChanged: { diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml index 871d1c92a9..8e91655dda 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml @@ -16,8 +16,8 @@ import QtQuick.Controls 1.4 as QQC1 import QtQuick.Controls 2.3 import ".." -import "../../../controls-uit" -import "../../../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../../../windows" import "../../../dialogs/fileDialog" diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml index 3708f75114..a5d7b23df6 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml @@ -12,8 +12,8 @@ import QtQuick 2.5 import "." import "./preferences" -import "../../../styles-uit" -import "../../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Item { id: dialog @@ -242,6 +242,10 @@ Item { keyboardEnabled = HMD.active; } + Component.onDestruction: { + keyboard.raised = false; + } + onKeyboardRaisedChanged: { if (keyboardEnabled && keyboardRaised) { var delta = mouseArea.mouseY - (dialog.height - footer.height - keyboard.raisedHeight -hifi.dimensions.controlLineHeight); diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml index 6ac3f706e4..57fdeb482b 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml @@ -12,8 +12,8 @@ import QtQuick 2.5 import Hifi 1.0 import "../../../../dialogs/preferences" -import "../../../../controls-uit" as HiFiControls -import "../../../../styles-uit" +import controlsUit 1.0 as HiFiControls +import stylesUit 1.0 import "." Preference { diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml index 8c0e934971..36b927f5f9 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "../../../../dialogs" -import "../../../../controls-uit" +import controlsUit 1.0 import "../" Preference { diff --git a/interface/resources/qml/hifi/tts/TTS.qml b/interface/resources/qml/hifi/tts/TTS.qml new file mode 100644 index 0000000000..d9507f6084 --- /dev/null +++ b/interface/resources/qml/hifi/tts/TTS.qml @@ -0,0 +1,314 @@ +// +// TTS.qml +// +// TTS App +// +// Created by Zach Fox on 2018-10-10 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 as Hifi +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import "qrc:////qml//styles-uit" as HifiStylesUit +import "qrc:////qml//controls-uit" as HifiControlsUit +import "qrc:////qml//controls" as HifiControls + +Rectangle { + HifiStylesUit.HifiConstants { id: hifi; } + + id: root; + // Style + color: hifi.colors.darkGray; + property bool keyboardRaised: false; + + // + // TITLE BAR START + // + Item { + id: titleBarContainer; + // Size + width: root.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Title bar text + HifiStylesUit.RalewaySemiBold { + id: titleBarText; + text: "Text-to-Speech"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.leftMargin: 16; + width: paintedWidth; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + } + } + // + // TITLE BAR END + // + + + Item { + id: tagButtonContainer; + anchors.top: titleBarContainer.bottom; + anchors.topMargin: 2; + anchors.left: parent.left; + anchors.right: parent.right; + height: 70; + + HifiStylesUit.RalewaySemiBold { + id: tagButtonTitle; + text: "Insert Tag:"; + // Text size + size: 18; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: 35; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + HifiControlsUit.Button { + id: pitch10Button; + focusPolicy: Qt.NoFocus; + color: hifi.buttons.none; + colorScheme: hifi.colorSchemes.dark; + anchors.top: tagButtonTitle.bottom; + anchors.left: parent.left; + anchors.leftMargin: 3; + width: parent.width/6 - 6; + height: 30; + text: "Pitch 10"; + onClicked: { + messageToSpeak.insert(messageToSpeak.cursorPosition, "<pitch absmiddle='10'/>"); + } + } + + HifiControlsUit.Button { + id: pitch0Button; + focusPolicy: Qt.NoFocus; + color: hifi.buttons.none; + colorScheme: hifi.colorSchemes.dark; + anchors.top: tagButtonTitle.bottom; + anchors.left: pitch10Button.right; + anchors.leftMargin: 6; + width: parent.width/6 - anchors.leftMargin; + height: 30; + text: "Pitch 0"; + onClicked: { + messageToSpeak.insert(messageToSpeak.cursorPosition, "<pitch absmiddle='0'/>"); + } + } + + HifiControlsUit.Button { + id: pitchNeg10Button; + focusPolicy: Qt.NoFocus; + color: hifi.buttons.none; + colorScheme: hifi.colorSchemes.dark; + anchors.top: tagButtonTitle.bottom; + anchors.left: pitch0Button.right; + anchors.leftMargin: 6; + width: parent.width/6 - anchors.leftMargin; + height: 30; + text: "Pitch -10"; + onClicked: { + messageToSpeak.insert(messageToSpeak.cursorPosition, "<pitch absmiddle='-10'/>"); + } + } + + HifiControlsUit.Button { + id: speed5Button; + focusPolicy: Qt.NoFocus; + color: hifi.buttons.none; + colorScheme: hifi.colorSchemes.dark; + anchors.top: tagButtonTitle.bottom; + anchors.left: pitchNeg10Button.right; + anchors.leftMargin: 6; + width: parent.width/6 - anchors.leftMargin; + height: 30; + text: "Speed 5"; + onClicked: { + messageToSpeak.insert(messageToSpeak.cursorPosition, "<rate absspeed='5'/>"); + } + } + + HifiControlsUit.Button { + id: speed0Button; + focusPolicy: Qt.NoFocus; + color: hifi.buttons.none; + colorScheme: hifi.colorSchemes.dark; + anchors.top: tagButtonTitle.bottom; + anchors.left: speed5Button.right; + anchors.leftMargin: 6; + width: parent.width/6 - anchors.leftMargin; + height: 30; + text: "Speed 0"; + onClicked: { + messageToSpeak.insert(messageToSpeak.cursorPosition, "<rate absspeed='0'/>"); + } + } + + HifiControlsUit.Button { + id: speedNeg10Button; + focusPolicy: Qt.NoFocus; + color: hifi.buttons.none; + colorScheme: hifi.colorSchemes.dark; + anchors.top: tagButtonTitle.bottom; + anchors.left: speed0Button.right; + anchors.leftMargin: 6; + width: parent.width/6 - anchors.leftMargin; + height: 30; + text: "Speed -10"; + onClicked: { + messageToSpeak.insert(messageToSpeak.cursorPosition, "<rate absspeed='-10'/>"); + } + } + } + + Item { + anchors.top: tagButtonContainer.bottom; + anchors.topMargin: 8; + anchors.bottom: keyboardContainer.top; + anchors.bottomMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + + TextArea { + id: messageToSpeak; + font.family: "Fira Sans SemiBold"; + font.pixelSize: 20; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: speakButton.top; + anchors.bottomMargin: 8; + // Style + background: Rectangle { + anchors.fill: parent; + color: parent.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow; + border.width: parent.activeFocus ? 1 : 0; + border.color: parent.activeFocus ? hifi.colors.primaryHighlight : hifi.colors.textFieldLightBackground; + } + color: hifi.colors.white; + textFormat: TextEdit.PlainText; + wrapMode: TextEdit.Wrap; + activeFocusOnPress: true; + activeFocusOnTab: true; + Keys.onPressed: { + if (event.key == Qt.Key_Return || event.key == Qt.Key_Enter) { + TextToSpeech.speakText(messageToSpeak.text, 480, 10, 24000, 16, true); + event.accepted = true; + } + } + + HifiStylesUit.FiraSansRegular { + text: "<i>Input Text to Speak...</i>"; + size: 20; + anchors.fill: parent; + anchors.topMargin: 4; + anchors.leftMargin: 4; + color: hifi.colors.lightGrayText; + visible: !parent.activeFocus && messageToSpeak.text === ""; + verticalAlignment: Text.AlignTop; + } + } + + HifiControlsUit.Button { + id: speakButton; + focusPolicy: Qt.NoFocus; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + width: 215; + height: 40; + text: "Speak"; + onClicked: { + TextToSpeech.speakText(messageToSpeak.text, 480, 10, 24000, 16, true); + } + } + + HifiControlsUit.Button { + id: clearButton; + focusPolicy: Qt.NoFocus; + color: hifi.buttons.white; + colorScheme: hifi.colorSchemes.dark; + anchors.right: speakButton.left; + anchors.rightMargin: 16; + anchors.bottom: parent.bottom; + width: 100; + height: 40; + text: "Clear"; + onClicked: { + messageToSpeak.text = ""; + } + } + + HifiControlsUit.Button { + id: stopButton; + focusPolicy: Qt.NoFocus; + color: hifi.buttons.red; + colorScheme: hifi.colorSchemes.dark; + anchors.right: clearButton.left; + anchors.rightMargin: 16; + anchors.bottom: parent.bottom; + width: 100; + height: 40; + text: "Stop Last"; + onClicked: { + TextToSpeech.stopLastSpeech(); + } + } + } + + Item { + id: keyboardContainer; + z: 998; + visible: keyboard.raised; + property bool punctuationMode: false; + anchors { + bottom: parent.bottom; + left: parent.left; + right: parent.right; + } + + HifiControlsUit.Keyboard { + id: keyboard; + raised: HMD.mounted && root.keyboardRaised; + numeric: parent.punctuationMode; + anchors { + bottom: parent.bottom; + left: parent.left; + right: parent.right; + } + } + } +} diff --git a/interface/resources/qml/styles-uit/AnonymousProRegular.qml b/interface/resources/qml/styles-uit/AnonymousProRegular.qml index 431ecd0f38..87a91f183a 100644 --- a/interface/resources/qml/styles-uit/AnonymousProRegular.qml +++ b/interface/resources/qml/styles-uit/AnonymousProRegular.qml @@ -1,20 +1,4 @@ -// -// AnonymousProRegular.qml -// -// Created by David Rowe on 12 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 - -Text { - id: root - property real size: 32 - font.pixelSize: size - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - font.family: "Anonymous Pro" +AnonymousProRegular { } diff --git a/interface/resources/qml/styles-uit/ButtonLabel.qml b/interface/resources/qml/styles-uit/ButtonLabel.qml index d227cb4869..d677cd06ca 100644 --- a/interface/resources/qml/styles-uit/ButtonLabel.qml +++ b/interface/resources/qml/styles-uit/ButtonLabel.qml @@ -1,16 +1,4 @@ -// -// ButtonLabel.qml -// -// Created by Clement on 7/18/16 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 -import "." - -RalewayBold { - font.pixelSize: hifi.fontSizes.buttonLabel +ButtonLabel { } diff --git a/interface/resources/qml/styles-uit/FiraSansRegular.qml b/interface/resources/qml/styles-uit/FiraSansRegular.qml index 05f6ecf74b..65ee891443 100644 --- a/interface/resources/qml/styles-uit/FiraSansRegular.qml +++ b/interface/resources/qml/styles-uit/FiraSansRegular.qml @@ -1,20 +1,4 @@ -// -// FiraSansRegular.qml -// -// Created by David Rowe on 12 May 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 - -Text { - id: root - property real size: 32 - font.pixelSize: size - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - font.family: "Fira Sans" +FiraSansRegular { } diff --git a/interface/resources/qml/styles-uit/FiraSansSemiBold.qml b/interface/resources/qml/styles-uit/FiraSansSemiBold.qml index 32554c2f25..635523feb9 100644 --- a/interface/resources/qml/styles-uit/FiraSansSemiBold.qml +++ b/interface/resources/qml/styles-uit/FiraSansSemiBold.qml @@ -1,20 +1,4 @@ -// -// FiraSansSemiBold.qml -// -// Created by David Rowe on 12 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 - -Text { - id: root - property real size: 32 - font.pixelSize: size - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - font.family: "Fira Sans SemiBold" +FiraSansSemiBold { } diff --git a/interface/resources/qml/styles-uit/HiFiGlyphs.qml b/interface/resources/qml/styles-uit/HiFiGlyphs.qml index 07f0212f0c..646cfc2988 100644 --- a/interface/resources/qml/styles-uit/HiFiGlyphs.qml +++ b/interface/resources/qml/styles-uit/HiFiGlyphs.qml @@ -1,22 +1,4 @@ -// -// HiFiGlyphs.qml -// -// Created by David Rowe on 12 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 - -Text { - id: root - property int size: 32 - font.pixelSize: size - width: size - height: size - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - font.family: "hifi-glyphs" +HiFiGlyphs { } diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index 595c393de9..24b24cf019 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -1,353 +1,4 @@ -// -// HiFiConstants.qml -// -// Created by Bradley Austin Davis on 28 Apr 2015 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 -import QtQuick.Window 2.2 - -QtObject { - - function glyphForIcon(icon) { - // Translates icon enum to glyph char. - var glyph; - switch (icon) { - case icons.information: - glyph = glyphs.info; - break; - case icons.question: - glyph = glyphs.question; - break; - case icons.warning: - glyph = glyphs.alert; - break; - case icons.critical: - glyph = glyphs.error; - break; - case icons.placemark: - glyph = glyphs.placemark; - break; - default: - glyph = glyphs.noIcon; - } - return glyph; - } - - readonly property QtObject colors: QtObject { - // Base colors - readonly property color baseGray: "#393939" - readonly property color darkGray: "#121212" - readonly property color baseGrayShadow: "#252525" - readonly property color baseGrayHighlight: "#575757" - readonly property color lightGray: "#6a6a6a" - readonly property color lightGrayText: "#afafaf" - readonly property color faintGray: "#e3e3e3" - readonly property color primaryHighlight: "#00b4ef" - readonly property color blueHighlight: "#00b4ef" - readonly property color blueAccent: "#0093C5" - readonly property color redHighlight: "#EA4C5F" - readonly property color redAccent: "#C62147" - readonly property color greenHighlight: "#1ac567" - readonly property color greenShadow: "#359D85" - readonly property color orangeHighlight: "#FFC49C" - readonly property color orangeAccent: "#FF6309" - readonly property color indigoHighlight: "#C0D2FF" - readonly property color indigoAccent: "#9495FF" - readonly property color magentaHighlight: "#EF93D1" - readonly property color magentaAccent: "#A2277C" - readonly property color checkboxCheckedRed: "#FF0000" - readonly property color checkboxCheckedBorderRed: "#D00000" - readonly property color lightBlueHighlight: "#d6f6ff" - - // Semitransparent - readonly property color darkGray30: "#4d121212" - readonly property color darkGray0: "#00121212" - readonly property color baseGrayShadow60: "#99252525" - readonly property color baseGrayShadow50: "#80252525" - readonly property color baseGrayShadow25: "#40252525" - readonly property color baseGrayHighlight40: "#66575757" - readonly property color baseGrayHighlight15: "#26575757" - readonly property color lightGray50: "#806a6a6a" - readonly property color lightGrayText80: "#ccafafaf" - readonly property color faintGray80: "#cce3e3e3" - readonly property color faintGray50: "#80e3e3e3" - - // Other colors - readonly property color white: "#ffffff" - readonly property color gray: "#808080" - readonly property color black: "#000000" - readonly property color locked: "#252525" - // Semitransparent - readonly property color white50: "#80ffffff" - readonly property color white30: "#4dffffff" - readonly property color white25: "#40ffffff" - readonly property color transparent: "#00ffffff" - - // Control specific colors - readonly property color tableRowLightOdd: "#fafafa" - readonly property color tableRowLightEven: "#eeeeee" // Equivavlent to "#1a575757" over #e3e3e3 background - readonly property color tableRowDarkOdd: "#2e2e2e" // Equivalent to "#80393939" over #404040 background - readonly property color tableRowDarkEven: "#1c1c1c" // Equivalent to "#a6181818" over #404040 background - readonly property color tableBackgroundLight: tableRowLightEven - readonly property color tableBackgroundDark: tableRowDarkEven - readonly property color tableScrollHandleLight: "#DDDDDD" - readonly property color tableScrollHandleDark: "#707070" - readonly property color tableScrollBackgroundLight: tableRowLightOdd - readonly property color tableScrollBackgroundDark: "#323232" - readonly property color checkboxLightStart: "#ffffff" - readonly property color checkboxLightFinish: "#afafaf" - readonly property color checkboxDarkStart: "#7d7d7d" - readonly property color checkboxDarkFinish: "#6b6a6b" - readonly property color checkboxChecked: primaryHighlight - readonly property color checkboxCheckedBorder: "#36cdff" - readonly property color sliderGutterLight: "#d4d4d4" - readonly property color sliderGutterDark: "#252525" - readonly property color sliderBorderLight: "#afafaf" - readonly property color sliderBorderDark: "#7d7d7d" - readonly property color sliderLightStart: "#ffffff" - readonly property color sliderLightFinish: "#afafaf" - readonly property color sliderDarkStart: "#7d7d7d" - readonly property color sliderDarkFinish: "#6b6a6b" - readonly property color dropDownPressedLight: "#d4d4d4" - readonly property color dropDownPressedDark: "#afafaf" - readonly property color dropDownLightStart: "#ffffff" - readonly property color dropDownLightFinish: "#afafaf" - readonly property color dropDownDarkStart: "#7d7d7d" - readonly property color dropDownDarkFinish: "#6b6a6b" - readonly property color textFieldLightBackground: "#d4d4d4" - readonly property color tabBackgroundDark: "#252525" - readonly property color tabBackgroundLight: "#d4d4d4" - } - - readonly property QtObject colorSchemes: QtObject { - readonly property int light: 0 - readonly property int dark: 1 - readonly property int faintGray: 2 - } - - readonly property QtObject dimensions: QtObject { - readonly property bool largeScreen: Screen.width >= 1920 && Screen.height >= 1080 - readonly property real borderRadius: largeScreen ? 7.5 : 5.0 - readonly property real borderWidth: largeScreen ? 2 : 1 - readonly property vector2d contentMargin: Qt.vector2d(21, 21) - readonly property vector2d contentSpacing: Qt.vector2d(11, 14) - readonly property real labelPadding: 40 - readonly property real textPadding: 8 - readonly property real sliderHandleSize: 18 - readonly property real sliderGrooveHeight: 8 - readonly property real frameIconSize: 22 - readonly property real spinnerSize: 50 - readonly property real tablePadding: 12 - readonly property real tableRowHeight: largeScreen ? 26 : 23 - readonly property real tableHeaderHeight: 29 - readonly property vector2d modalDialogMargin: Qt.vector2d(50, 30) - readonly property real modalDialogTitleHeight: 40 - readonly property real controlLineHeight: 28 // Height of spinbox control on 1920 x 1080 monitor - readonly property real controlInterlineHeight: 21 // 75% of controlLineHeight - readonly property vector2d menuPadding: Qt.vector2d(14, 102) - readonly property real scrollbarBackgroundWidth: 20 - readonly property real scrollbarHandleWidth: scrollbarBackgroundWidth - 2 - readonly property real tabletMenuHeader: 90 - readonly property real buttonWidth: 120 - } - - readonly property QtObject fontSizes: QtObject { - // In pixels - readonly property real overlayTitle: dimensions.largeScreen ? 18 : 14 - readonly property real tabName: dimensions.largeScreen ? 12 : 10 - readonly property real sectionName: dimensions.largeScreen ? 12 : 10 - readonly property real inputLabel: dimensions.largeScreen ? 14 : 10 - readonly property real textFieldInput: dimensions.largeScreen ? 15 : 12 - readonly property real textFieldInputLabel: dimensions.largeScreen ? 13 : 9 - readonly property real textFieldSearchIcon: dimensions.largeScreen ? 30 : 24 - readonly property real tableHeading: dimensions.largeScreen ? 12 : 10 - readonly property real tableHeadingIcon: dimensions.largeScreen ? 60 : 33 - readonly property real tableText: dimensions.largeScreen ? 15 : 12 - readonly property real buttonLabel: dimensions.largeScreen ? 14 : 9 - readonly property real iconButton: dimensions.largeScreen ? 13 : 9 - readonly property real listItem: dimensions.largeScreen ? 15 : 11 - readonly property real tabularData: dimensions.largeScreen ? 15 : 11 - readonly property real logs: dimensions.largeScreen ? 16 : 12 - readonly property real code: dimensions.largeScreen ? 16 : 12 - readonly property real rootMenu: dimensions.largeScreen ? 15 : 11 - readonly property real rootMenuDisclosure: dimensions.largeScreen ? 20 : 16 - readonly property real menuItem: dimensions.largeScreen ? 15 : 11 - readonly property real shortcutText: dimensions.largeScreen ? 13 : 9 - readonly property real carat: dimensions.largeScreen ? 38 : 30 - readonly property real disclosureButton: dimensions.largeScreen ? 30 : 22 - } - - readonly property QtObject icons: QtObject { - // Values per OffscreenUi::Icon - readonly property int none: 0 - readonly property int question: 1 - readonly property int information: 2 - readonly property int warning: 3 - readonly property int critical: 4 - readonly property int placemark: 5 - } - - readonly property QtObject buttons: QtObject { - readonly property int white: 0 - readonly property int blue: 1 - readonly property int red: 2 - readonly property int black: 3 - readonly property int none: 4 - readonly property int noneBorderless: 5 - readonly property int noneBorderlessWhite: 6 - readonly property int noneBorderlessGray: 7 - readonly property var textColor: [ colors.darkGray, colors.white, colors.white, colors.white, colors.white, colors.blueAccent, colors.white, colors.darkGray ] - readonly property var colorStart: [ colors.white, colors.primaryHighlight, "#d42043", "#343434", Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ] - readonly property var colorFinish: [ colors.lightGrayText, colors.blueAccent, "#94132e", colors.black, Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ] - readonly property var hoveredColor: [ colorStart[white], colorStart[blue], colorStart[red], colorFinish[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] - readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colors.lightGrayText ] - readonly property var focusedColor: [ colors.lightGray50, colors.blueAccent, colors.redAccent, colors.darkGray, colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] - readonly property var disabledColorStart: [ colorStart[white], colors.baseGrayHighlight] - readonly property var disabledColorFinish: [ colorFinish[white], colors.baseGrayShadow] - readonly property var disabledTextColor: [ colors.lightGrayText, colors.baseGrayShadow] - readonly property int radius: 5 - } - - readonly property QtObject effects: QtObject { - readonly property int fadeInDuration: 300 - } - - readonly property QtObject glyphs: QtObject { - readonly property string noIcon: "" - readonly property string hmd: "b" - readonly property string screen: "c" - readonly property string keyboard: "d" - readonly property string handControllers: "e" - readonly property string headphonesMic: "f" - readonly property string gamepad: "g" - readonly property string headphones: "h" - readonly property string mic: "i" - readonly property string upload: "j" - readonly property string script: "k" - readonly property string text: "l" - readonly property string cube: "m" - readonly property string sphere: "n" - readonly property string zone: "o" - readonly property string light: "p" - readonly property string web: "q" - readonly property string web2: "r" - readonly property string edit: "s" - readonly property string market: "t" - readonly property string directory: "u" - readonly property string menu: "v" - readonly property string close: "w" - readonly property string closeInverted: "x" - readonly property string pin: "y" - readonly property string pinInverted: "z" - readonly property string resizeHandle: "A" - readonly property string disclosureExpand: "B" - readonly property string reloadSmall: "a" - readonly property string closeSmall: "C" - readonly property string forward: "D" - readonly property string backward: "E" - readonly property string reload: "F" - readonly property string unmuted: "G" - readonly property string muted: "H" - readonly property string minimize: "I" - readonly property string maximize: "J" - readonly property string maximizeInverted: "K" - readonly property string disclosureButtonExpand: "L" - readonly property string disclosureButtonCollapse: "M" - readonly property string scriptStop: "N" - readonly property string scriptReload: "O" - readonly property string scriptRun: "P" - readonly property string scriptNew: "Q" - readonly property string hifiForum: "2" - readonly property string hifiLogoSmall: "S" - readonly property string avatar1: "T" - readonly property string placemark: "U" - readonly property string box: "V" - readonly property string community: "0" - readonly property string grabHandle: "X" - readonly property string search: "Y" - readonly property string disclosureCollapse: "Z" - readonly property string scriptUpload: "R" - readonly property string code: "W" - readonly property string avatar: "<" - readonly property string arrowsH: ":" - readonly property string arrowsV: ";" - readonly property string arrows: "`" - readonly property string compress: "!" - readonly property string expand: "\"" - readonly property string placemark1: "#" - readonly property string circle: "$" - readonly property string handPointer: "9" - readonly property string plusSquareO: "%" - readonly property string sliders: "&" - readonly property string square: "'" - readonly property string alignCenter: "8" - readonly property string alignJustify: ")" - readonly property string alignLeft: "*" - readonly property string alignRight: "^" - readonly property string bars: "7" - readonly property string circleSlash: "," - readonly property string sync: "()" - readonly property string key: "-" - readonly property string link: "." - readonly property string location: "/" - readonly property string caratR: "3" - readonly property string caratL: "4" - readonly property string caratDn: "5" - readonly property string caratUp: "6" - readonly property string folderLg: ">" - readonly property string folderSm: "?" - readonly property string levelUp: "1" - readonly property string info: "[" - readonly property string question: "]" - readonly property string alert: "+" - readonly property string home: "_" - readonly property string error: "=" - readonly property string settings: "@" - readonly property string trash: "{" - readonly property string objectGroup: "\ue000" - readonly property string cm: "}" - readonly property string msvg79: "~" - readonly property string deg: "\\" - readonly property string px: "|" - readonly property string editPencil: "\ue00d" - readonly property string vol_0: "\ue00e" - readonly property string vol_1: "\ue00f" - readonly property string vol_2: "\ue010" - readonly property string vol_3: "\ue011" - readonly property string vol_4: "\ue012" - readonly property string vol_x_0: "\ue013" - readonly property string vol_x_1: "\ue014" - readonly property string vol_x_2: "\ue015" - readonly property string vol_x_3: "\ue016" - readonly property string vol_x_4: "\ue017" - readonly property string source: "\ue01c" - readonly property string playback_play: "\ue01d" - readonly property string stop_square: "\ue01e" - readonly property string avatarTPose: "\ue01f" - readonly property string lock: "\ue006" - readonly property string checkmark: "\ue020" - readonly property string leftRightArrows: "\ue021" - readonly property string hfc: "\ue022" - readonly property string home2: "\ue023" - readonly property string walletKey: "\ue024" - readonly property string lightning: "\ue025" - readonly property string securityImage: "\ue026" - readonly property string wallet: "\ue027" - readonly property string paperPlane: "\ue028" - readonly property string passphrase: "\ue029" - readonly property string globe: "\ue02c" - readonly property string wand: "\ue02d" - readonly property string hat: "\ue02e" - readonly property string install: "\ue02f" - readonly property string certificate: "\ue030" - readonly property string gift: "\ue031" - readonly property string update: "\ue032" - readonly property string uninstall: "\ue033" - readonly property string verticalEllipsis: "\ue034" - } +HifiConstants { } diff --git a/interface/resources/qml/styles-uit/IconButton.qml b/interface/resources/qml/styles-uit/IconButton.qml index e5a18e2ae7..e60ed9eb10 100644 --- a/interface/resources/qml/styles-uit/IconButton.qml +++ b/interface/resources/qml/styles-uit/IconButton.qml @@ -1,18 +1,4 @@ -// -// IconButton.qml -// -// Created by Clement on 7/18/16 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 -import "." - -RalewayRegular { - font.pixelSize: hifi.fontSizes.iconButton - font.capitalization: Font.AllUppercase - font.letterSpacing: 1.5 +IconButton { } diff --git a/interface/resources/qml/styles-uit/InfoItem.qml b/interface/resources/qml/styles-uit/InfoItem.qml index fa7684e8e7..d09f26649d 100644 --- a/interface/resources/qml/styles-uit/InfoItem.qml +++ b/interface/resources/qml/styles-uit/InfoItem.qml @@ -1,17 +1,4 @@ -// -// InfoItem.qml -// -// Created by Clement on 7/18/16 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 -import "." - -RalewaySemiBold { - lineHeight: 2 - font.pixelSize: hifi.fontSizes.menuItem +InfoItem { } diff --git a/interface/resources/qml/styles-uit/InputLabel.qml b/interface/resources/qml/styles-uit/InputLabel.qml index 3853dd5b19..89d74afc8f 100644 --- a/interface/resources/qml/styles-uit/InputLabel.qml +++ b/interface/resources/qml/styles-uit/InputLabel.qml @@ -1,16 +1,4 @@ -// -// InputLabel.qml -// -// Created by Clement on 7/18/16 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 -import "." - -RalewaySemiBold { - font.pixelSize: hifi.fontSizes.inputLabel +InputLabel { } diff --git a/interface/resources/qml/styles-uit/ListItem.qml b/interface/resources/qml/styles-uit/ListItem.qml index a69c4b48c2..8d72762644 100644 --- a/interface/resources/qml/styles-uit/ListItem.qml +++ b/interface/resources/qml/styles-uit/ListItem.qml @@ -1,16 +1,4 @@ -// -// ListItem.qml -// -// Created by Clement on 7/18/16 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 -import "." - -RalewayRegular { - font.pixelSize: hifi.fontSizes.listItem +ListItem { } diff --git a/interface/resources/qml/styles-uit/Logs.qml b/interface/resources/qml/styles-uit/Logs.qml index 45d4436fbf..d9453afade 100644 --- a/interface/resources/qml/styles-uit/Logs.qml +++ b/interface/resources/qml/styles-uit/Logs.qml @@ -1,16 +1,4 @@ -// -// Logs.qml -// -// Created by Clement on 7/18/16 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 -import "." - -AnonymousProRegular { - font.pixelSize: hifi.fontSizes.logs +Logs { } diff --git a/interface/resources/qml/styles-uit/OverlayTitle.qml b/interface/resources/qml/styles-uit/OverlayTitle.qml index 0fb423baab..51fc11a77d 100644 --- a/interface/resources/qml/styles-uit/OverlayTitle.qml +++ b/interface/resources/qml/styles-uit/OverlayTitle.qml @@ -1,16 +1,4 @@ -// -// OverlayTitle.qml -// -// Created by Clement on 7/18/16 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 -import "." - -RalewayRegular { - font.pixelSize: hifi.fontSizes.overlayTitle +OverlayTitle { } diff --git a/interface/resources/qml/styles-uit/RalewayBold.qml b/interface/resources/qml/styles-uit/RalewayBold.qml index 7edde91271..1373859b32 100644 --- a/interface/resources/qml/styles-uit/RalewayBold.qml +++ b/interface/resources/qml/styles-uit/RalewayBold.qml @@ -1,21 +1,4 @@ -// -// RalewayBold.qml -// -// Created by David Rowe on 12 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 - -Text { - id: root - property real size: 32 - font.pixelSize: size - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - font.family: "Raleway" - font.bold: true +RalewayBold { } diff --git a/interface/resources/qml/styles-uit/RalewayLight.qml b/interface/resources/qml/styles-uit/RalewayLight.qml index 666ebc2ea9..9573eb6649 100644 --- a/interface/resources/qml/styles-uit/RalewayLight.qml +++ b/interface/resources/qml/styles-uit/RalewayLight.qml @@ -1,20 +1,4 @@ -// -// RalewayLight.qml -// -// Created by David Rowe on 12 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 - -Text { - id: root - property real size: 32 - font.pixelSize: size - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - font.family: "Raleway Light" +RalewayLight { } diff --git a/interface/resources/qml/styles-uit/RalewayRegular.qml b/interface/resources/qml/styles-uit/RalewayRegular.qml index e263922095..d5a66ff696 100644 --- a/interface/resources/qml/styles-uit/RalewayRegular.qml +++ b/interface/resources/qml/styles-uit/RalewayRegular.qml @@ -1,20 +1,4 @@ -// -// RalewayRegular.qml -// -// Created by David Rowe on 12 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.7 - -Text { - id: root - property real size: 32 - font.pixelSize: size - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - font.family: "Raleway" +RalewayRegular { } diff --git a/interface/resources/qml/styles-uit/RalewaySemiBold.qml b/interface/resources/qml/styles-uit/RalewaySemiBold.qml index 19d8b6b8c9..874a48555b 100644 --- a/interface/resources/qml/styles-uit/RalewaySemiBold.qml +++ b/interface/resources/qml/styles-uit/RalewaySemiBold.qml @@ -1,21 +1,4 @@ -// -// RalewaySemiBold.qml -// -// Created by David Rowe on 12 Feb 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.7 - -Text { - id: root - property real size: 32 - font.pixelSize: size - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - font.family: "Raleway" - font.weight: Font.DemiBold +RalewaySemiBold { } diff --git a/interface/resources/qml/styles-uit/SectionName.qml b/interface/resources/qml/styles-uit/SectionName.qml index 20f8e1e116..819ed87d8b 100644 --- a/interface/resources/qml/styles-uit/SectionName.qml +++ b/interface/resources/qml/styles-uit/SectionName.qml @@ -1,17 +1,4 @@ -// -// SectionName.qml -// -// Created by Clement on 7/18/16 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 -import "." - -RalewayRegular { - font.pixelSize: hifi.fontSizes.sectionName - font.capitalization: Font.AllUppercase +SectionName { } diff --git a/interface/resources/qml/styles-uit/Separator.qml b/interface/resources/qml/styles-uit/Separator.qml index 4134b928a7..9708ec4c26 100644 --- a/interface/resources/qml/styles-uit/Separator.qml +++ b/interface/resources/qml/styles-uit/Separator.qml @@ -1,41 +1,4 @@ -// -// Separator.qml -// -// Created by Zach Fox on 2017-06-06 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 -import "../styles-uit" - -Item { - // Size - height: 2; - width: parent.width; - - Rectangle { - // Size - width: parent.width; - height: 1; - // Anchors - anchors.left: parent.left; - anchors.bottom: parent.bottom; - anchors.bottomMargin: height; - // Style - color: hifi.colors.baseGrayShadow; - } - - Rectangle { - // Size - width: parent.width; - height: 1; - // Anchors - anchors.left: parent.left; - anchors.bottom: parent.bottom; - // Style - color: hifi.colors.baseGrayHighlight; - } +Separator { } diff --git a/interface/resources/qml/styles-uit/ShortcutText.qml b/interface/resources/qml/styles-uit/ShortcutText.qml index 8504ffa2b8..16e1b2f1c1 100644 --- a/interface/resources/qml/styles-uit/ShortcutText.qml +++ b/interface/resources/qml/styles-uit/ShortcutText.qml @@ -1,16 +1,4 @@ -// -// ShortcutText.qml -// -// Created by Clement on 7/18/16 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 -import "." - -RalewayLight { - font.pixelSize: hifi.fontSizes.shortcutText +ShortcutText { } diff --git a/interface/resources/qml/styles-uit/TabName.qml b/interface/resources/qml/styles-uit/TabName.qml index 0f620fe8c2..b66192e582 100644 --- a/interface/resources/qml/styles-uit/TabName.qml +++ b/interface/resources/qml/styles-uit/TabName.qml @@ -1,17 +1,4 @@ -// -// TabName.qml -// -// Created by Clement on 7/18/16 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 -import "." - -RalewayRegular { - font.pixelSize: hifi.fontSizes.tabName - font.capitalization: Font.AllUppercase +TabName { } diff --git a/interface/resources/qml/styles-uit/TextFieldInput.qml b/interface/resources/qml/styles-uit/TextFieldInput.qml index f2a57e57fc..1498dde821 100644 --- a/interface/resources/qml/styles-uit/TextFieldInput.qml +++ b/interface/resources/qml/styles-uit/TextFieldInput.qml @@ -1,16 +1,4 @@ -// -// TextFieldInput.qml -// -// Created by Clement on 7/18/16 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// +import stylesUit 1.0 -import QtQuick 2.5 -import "." - -FiraSansSemiBold { - font.pixelSize: hifi.fontSizes.textFieldInput +TextFieldInput { } diff --git a/interface/resources/qml/styles-uit/readme.txt b/interface/resources/qml/styles-uit/readme.txt new file mode 100644 index 0000000000..105eda3c81 --- /dev/null +++ b/interface/resources/qml/styles-uit/readme.txt @@ -0,0 +1 @@ +this folder exists purely for compatibility reasons and might be deleted in future! please consider using 'import stylesUit 1.0' instead of including this folder \ No newline at end of file diff --git a/interface/resources/qml/styles-uit/+android/HifiConstants.qml b/interface/resources/qml/stylesUit/+android/HifiConstants.qml similarity index 100% rename from interface/resources/qml/styles-uit/+android/HifiConstants.qml rename to interface/resources/qml/stylesUit/+android/HifiConstants.qml diff --git a/interface/resources/qml/stylesUit/AnonymousProRegular.qml b/interface/resources/qml/stylesUit/AnonymousProRegular.qml new file mode 100644 index 0000000000..431ecd0f38 --- /dev/null +++ b/interface/resources/qml/stylesUit/AnonymousProRegular.qml @@ -0,0 +1,20 @@ +// +// AnonymousProRegular.qml +// +// Created by David Rowe on 12 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +Text { + id: root + property real size: 32 + font.pixelSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: "Anonymous Pro" +} diff --git a/interface/resources/qml/stylesUit/ButtonLabel.qml b/interface/resources/qml/stylesUit/ButtonLabel.qml new file mode 100644 index 0000000000..d227cb4869 --- /dev/null +++ b/interface/resources/qml/stylesUit/ButtonLabel.qml @@ -0,0 +1,16 @@ +// +// ButtonLabel.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "." + +RalewayBold { + font.pixelSize: hifi.fontSizes.buttonLabel +} diff --git a/interface/resources/qml/stylesUit/FiraSansRegular.qml b/interface/resources/qml/stylesUit/FiraSansRegular.qml new file mode 100644 index 0000000000..05f6ecf74b --- /dev/null +++ b/interface/resources/qml/stylesUit/FiraSansRegular.qml @@ -0,0 +1,20 @@ +// +// FiraSansRegular.qml +// +// Created by David Rowe on 12 May 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +Text { + id: root + property real size: 32 + font.pixelSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: "Fira Sans" +} diff --git a/interface/resources/qml/stylesUit/FiraSansSemiBold.qml b/interface/resources/qml/stylesUit/FiraSansSemiBold.qml new file mode 100644 index 0000000000..32554c2f25 --- /dev/null +++ b/interface/resources/qml/stylesUit/FiraSansSemiBold.qml @@ -0,0 +1,20 @@ +// +// FiraSansSemiBold.qml +// +// Created by David Rowe on 12 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +Text { + id: root + property real size: 32 + font.pixelSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: "Fira Sans SemiBold" +} diff --git a/interface/resources/qml/stylesUit/HiFiGlyphs.qml b/interface/resources/qml/stylesUit/HiFiGlyphs.qml new file mode 100644 index 0000000000..07f0212f0c --- /dev/null +++ b/interface/resources/qml/stylesUit/HiFiGlyphs.qml @@ -0,0 +1,22 @@ +// +// HiFiGlyphs.qml +// +// Created by David Rowe on 12 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +Text { + id: root + property int size: 32 + font.pixelSize: size + width: size + height: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: "hifi-glyphs" +} diff --git a/interface/resources/qml/stylesUit/HifiConstants.qml b/interface/resources/qml/stylesUit/HifiConstants.qml new file mode 100644 index 0000000000..595c393de9 --- /dev/null +++ b/interface/resources/qml/stylesUit/HifiConstants.qml @@ -0,0 +1,353 @@ +// +// HiFiConstants.qml +// +// Created by Bradley Austin Davis on 28 Apr 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Window 2.2 + +QtObject { + + function glyphForIcon(icon) { + // Translates icon enum to glyph char. + var glyph; + switch (icon) { + case icons.information: + glyph = glyphs.info; + break; + case icons.question: + glyph = glyphs.question; + break; + case icons.warning: + glyph = glyphs.alert; + break; + case icons.critical: + glyph = glyphs.error; + break; + case icons.placemark: + glyph = glyphs.placemark; + break; + default: + glyph = glyphs.noIcon; + } + return glyph; + } + + readonly property QtObject colors: QtObject { + // Base colors + readonly property color baseGray: "#393939" + readonly property color darkGray: "#121212" + readonly property color baseGrayShadow: "#252525" + readonly property color baseGrayHighlight: "#575757" + readonly property color lightGray: "#6a6a6a" + readonly property color lightGrayText: "#afafaf" + readonly property color faintGray: "#e3e3e3" + readonly property color primaryHighlight: "#00b4ef" + readonly property color blueHighlight: "#00b4ef" + readonly property color blueAccent: "#0093C5" + readonly property color redHighlight: "#EA4C5F" + readonly property color redAccent: "#C62147" + readonly property color greenHighlight: "#1ac567" + readonly property color greenShadow: "#359D85" + readonly property color orangeHighlight: "#FFC49C" + readonly property color orangeAccent: "#FF6309" + readonly property color indigoHighlight: "#C0D2FF" + readonly property color indigoAccent: "#9495FF" + readonly property color magentaHighlight: "#EF93D1" + readonly property color magentaAccent: "#A2277C" + readonly property color checkboxCheckedRed: "#FF0000" + readonly property color checkboxCheckedBorderRed: "#D00000" + readonly property color lightBlueHighlight: "#d6f6ff" + + // Semitransparent + readonly property color darkGray30: "#4d121212" + readonly property color darkGray0: "#00121212" + readonly property color baseGrayShadow60: "#99252525" + readonly property color baseGrayShadow50: "#80252525" + readonly property color baseGrayShadow25: "#40252525" + readonly property color baseGrayHighlight40: "#66575757" + readonly property color baseGrayHighlight15: "#26575757" + readonly property color lightGray50: "#806a6a6a" + readonly property color lightGrayText80: "#ccafafaf" + readonly property color faintGray80: "#cce3e3e3" + readonly property color faintGray50: "#80e3e3e3" + + // Other colors + readonly property color white: "#ffffff" + readonly property color gray: "#808080" + readonly property color black: "#000000" + readonly property color locked: "#252525" + // Semitransparent + readonly property color white50: "#80ffffff" + readonly property color white30: "#4dffffff" + readonly property color white25: "#40ffffff" + readonly property color transparent: "#00ffffff" + + // Control specific colors + readonly property color tableRowLightOdd: "#fafafa" + readonly property color tableRowLightEven: "#eeeeee" // Equivavlent to "#1a575757" over #e3e3e3 background + readonly property color tableRowDarkOdd: "#2e2e2e" // Equivalent to "#80393939" over #404040 background + readonly property color tableRowDarkEven: "#1c1c1c" // Equivalent to "#a6181818" over #404040 background + readonly property color tableBackgroundLight: tableRowLightEven + readonly property color tableBackgroundDark: tableRowDarkEven + readonly property color tableScrollHandleLight: "#DDDDDD" + readonly property color tableScrollHandleDark: "#707070" + readonly property color tableScrollBackgroundLight: tableRowLightOdd + readonly property color tableScrollBackgroundDark: "#323232" + readonly property color checkboxLightStart: "#ffffff" + readonly property color checkboxLightFinish: "#afafaf" + readonly property color checkboxDarkStart: "#7d7d7d" + readonly property color checkboxDarkFinish: "#6b6a6b" + readonly property color checkboxChecked: primaryHighlight + readonly property color checkboxCheckedBorder: "#36cdff" + readonly property color sliderGutterLight: "#d4d4d4" + readonly property color sliderGutterDark: "#252525" + readonly property color sliderBorderLight: "#afafaf" + readonly property color sliderBorderDark: "#7d7d7d" + readonly property color sliderLightStart: "#ffffff" + readonly property color sliderLightFinish: "#afafaf" + readonly property color sliderDarkStart: "#7d7d7d" + readonly property color sliderDarkFinish: "#6b6a6b" + readonly property color dropDownPressedLight: "#d4d4d4" + readonly property color dropDownPressedDark: "#afafaf" + readonly property color dropDownLightStart: "#ffffff" + readonly property color dropDownLightFinish: "#afafaf" + readonly property color dropDownDarkStart: "#7d7d7d" + readonly property color dropDownDarkFinish: "#6b6a6b" + readonly property color textFieldLightBackground: "#d4d4d4" + readonly property color tabBackgroundDark: "#252525" + readonly property color tabBackgroundLight: "#d4d4d4" + } + + readonly property QtObject colorSchemes: QtObject { + readonly property int light: 0 + readonly property int dark: 1 + readonly property int faintGray: 2 + } + + readonly property QtObject dimensions: QtObject { + readonly property bool largeScreen: Screen.width >= 1920 && Screen.height >= 1080 + readonly property real borderRadius: largeScreen ? 7.5 : 5.0 + readonly property real borderWidth: largeScreen ? 2 : 1 + readonly property vector2d contentMargin: Qt.vector2d(21, 21) + readonly property vector2d contentSpacing: Qt.vector2d(11, 14) + readonly property real labelPadding: 40 + readonly property real textPadding: 8 + readonly property real sliderHandleSize: 18 + readonly property real sliderGrooveHeight: 8 + readonly property real frameIconSize: 22 + readonly property real spinnerSize: 50 + readonly property real tablePadding: 12 + readonly property real tableRowHeight: largeScreen ? 26 : 23 + readonly property real tableHeaderHeight: 29 + readonly property vector2d modalDialogMargin: Qt.vector2d(50, 30) + readonly property real modalDialogTitleHeight: 40 + readonly property real controlLineHeight: 28 // Height of spinbox control on 1920 x 1080 monitor + readonly property real controlInterlineHeight: 21 // 75% of controlLineHeight + readonly property vector2d menuPadding: Qt.vector2d(14, 102) + readonly property real scrollbarBackgroundWidth: 20 + readonly property real scrollbarHandleWidth: scrollbarBackgroundWidth - 2 + readonly property real tabletMenuHeader: 90 + readonly property real buttonWidth: 120 + } + + readonly property QtObject fontSizes: QtObject { + // In pixels + readonly property real overlayTitle: dimensions.largeScreen ? 18 : 14 + readonly property real tabName: dimensions.largeScreen ? 12 : 10 + readonly property real sectionName: dimensions.largeScreen ? 12 : 10 + readonly property real inputLabel: dimensions.largeScreen ? 14 : 10 + readonly property real textFieldInput: dimensions.largeScreen ? 15 : 12 + readonly property real textFieldInputLabel: dimensions.largeScreen ? 13 : 9 + readonly property real textFieldSearchIcon: dimensions.largeScreen ? 30 : 24 + readonly property real tableHeading: dimensions.largeScreen ? 12 : 10 + readonly property real tableHeadingIcon: dimensions.largeScreen ? 60 : 33 + readonly property real tableText: dimensions.largeScreen ? 15 : 12 + readonly property real buttonLabel: dimensions.largeScreen ? 14 : 9 + readonly property real iconButton: dimensions.largeScreen ? 13 : 9 + readonly property real listItem: dimensions.largeScreen ? 15 : 11 + readonly property real tabularData: dimensions.largeScreen ? 15 : 11 + readonly property real logs: dimensions.largeScreen ? 16 : 12 + readonly property real code: dimensions.largeScreen ? 16 : 12 + readonly property real rootMenu: dimensions.largeScreen ? 15 : 11 + readonly property real rootMenuDisclosure: dimensions.largeScreen ? 20 : 16 + readonly property real menuItem: dimensions.largeScreen ? 15 : 11 + readonly property real shortcutText: dimensions.largeScreen ? 13 : 9 + readonly property real carat: dimensions.largeScreen ? 38 : 30 + readonly property real disclosureButton: dimensions.largeScreen ? 30 : 22 + } + + readonly property QtObject icons: QtObject { + // Values per OffscreenUi::Icon + readonly property int none: 0 + readonly property int question: 1 + readonly property int information: 2 + readonly property int warning: 3 + readonly property int critical: 4 + readonly property int placemark: 5 + } + + readonly property QtObject buttons: QtObject { + readonly property int white: 0 + readonly property int blue: 1 + readonly property int red: 2 + readonly property int black: 3 + readonly property int none: 4 + readonly property int noneBorderless: 5 + readonly property int noneBorderlessWhite: 6 + readonly property int noneBorderlessGray: 7 + readonly property var textColor: [ colors.darkGray, colors.white, colors.white, colors.white, colors.white, colors.blueAccent, colors.white, colors.darkGray ] + readonly property var colorStart: [ colors.white, colors.primaryHighlight, "#d42043", "#343434", Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ] + readonly property var colorFinish: [ colors.lightGrayText, colors.blueAccent, "#94132e", colors.black, Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ] + readonly property var hoveredColor: [ colorStart[white], colorStart[blue], colorStart[red], colorFinish[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] + readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colors.lightGrayText ] + readonly property var focusedColor: [ colors.lightGray50, colors.blueAccent, colors.redAccent, colors.darkGray, colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] + readonly property var disabledColorStart: [ colorStart[white], colors.baseGrayHighlight] + readonly property var disabledColorFinish: [ colorFinish[white], colors.baseGrayShadow] + readonly property var disabledTextColor: [ colors.lightGrayText, colors.baseGrayShadow] + readonly property int radius: 5 + } + + readonly property QtObject effects: QtObject { + readonly property int fadeInDuration: 300 + } + + readonly property QtObject glyphs: QtObject { + readonly property string noIcon: "" + readonly property string hmd: "b" + readonly property string screen: "c" + readonly property string keyboard: "d" + readonly property string handControllers: "e" + readonly property string headphonesMic: "f" + readonly property string gamepad: "g" + readonly property string headphones: "h" + readonly property string mic: "i" + readonly property string upload: "j" + readonly property string script: "k" + readonly property string text: "l" + readonly property string cube: "m" + readonly property string sphere: "n" + readonly property string zone: "o" + readonly property string light: "p" + readonly property string web: "q" + readonly property string web2: "r" + readonly property string edit: "s" + readonly property string market: "t" + readonly property string directory: "u" + readonly property string menu: "v" + readonly property string close: "w" + readonly property string closeInverted: "x" + readonly property string pin: "y" + readonly property string pinInverted: "z" + readonly property string resizeHandle: "A" + readonly property string disclosureExpand: "B" + readonly property string reloadSmall: "a" + readonly property string closeSmall: "C" + readonly property string forward: "D" + readonly property string backward: "E" + readonly property string reload: "F" + readonly property string unmuted: "G" + readonly property string muted: "H" + readonly property string minimize: "I" + readonly property string maximize: "J" + readonly property string maximizeInverted: "K" + readonly property string disclosureButtonExpand: "L" + readonly property string disclosureButtonCollapse: "M" + readonly property string scriptStop: "N" + readonly property string scriptReload: "O" + readonly property string scriptRun: "P" + readonly property string scriptNew: "Q" + readonly property string hifiForum: "2" + readonly property string hifiLogoSmall: "S" + readonly property string avatar1: "T" + readonly property string placemark: "U" + readonly property string box: "V" + readonly property string community: "0" + readonly property string grabHandle: "X" + readonly property string search: "Y" + readonly property string disclosureCollapse: "Z" + readonly property string scriptUpload: "R" + readonly property string code: "W" + readonly property string avatar: "<" + readonly property string arrowsH: ":" + readonly property string arrowsV: ";" + readonly property string arrows: "`" + readonly property string compress: "!" + readonly property string expand: "\"" + readonly property string placemark1: "#" + readonly property string circle: "$" + readonly property string handPointer: "9" + readonly property string plusSquareO: "%" + readonly property string sliders: "&" + readonly property string square: "'" + readonly property string alignCenter: "8" + readonly property string alignJustify: ")" + readonly property string alignLeft: "*" + readonly property string alignRight: "^" + readonly property string bars: "7" + readonly property string circleSlash: "," + readonly property string sync: "()" + readonly property string key: "-" + readonly property string link: "." + readonly property string location: "/" + readonly property string caratR: "3" + readonly property string caratL: "4" + readonly property string caratDn: "5" + readonly property string caratUp: "6" + readonly property string folderLg: ">" + readonly property string folderSm: "?" + readonly property string levelUp: "1" + readonly property string info: "[" + readonly property string question: "]" + readonly property string alert: "+" + readonly property string home: "_" + readonly property string error: "=" + readonly property string settings: "@" + readonly property string trash: "{" + readonly property string objectGroup: "\ue000" + readonly property string cm: "}" + readonly property string msvg79: "~" + readonly property string deg: "\\" + readonly property string px: "|" + readonly property string editPencil: "\ue00d" + readonly property string vol_0: "\ue00e" + readonly property string vol_1: "\ue00f" + readonly property string vol_2: "\ue010" + readonly property string vol_3: "\ue011" + readonly property string vol_4: "\ue012" + readonly property string vol_x_0: "\ue013" + readonly property string vol_x_1: "\ue014" + readonly property string vol_x_2: "\ue015" + readonly property string vol_x_3: "\ue016" + readonly property string vol_x_4: "\ue017" + readonly property string source: "\ue01c" + readonly property string playback_play: "\ue01d" + readonly property string stop_square: "\ue01e" + readonly property string avatarTPose: "\ue01f" + readonly property string lock: "\ue006" + readonly property string checkmark: "\ue020" + readonly property string leftRightArrows: "\ue021" + readonly property string hfc: "\ue022" + readonly property string home2: "\ue023" + readonly property string walletKey: "\ue024" + readonly property string lightning: "\ue025" + readonly property string securityImage: "\ue026" + readonly property string wallet: "\ue027" + readonly property string paperPlane: "\ue028" + readonly property string passphrase: "\ue029" + readonly property string globe: "\ue02c" + readonly property string wand: "\ue02d" + readonly property string hat: "\ue02e" + readonly property string install: "\ue02f" + readonly property string certificate: "\ue030" + readonly property string gift: "\ue031" + readonly property string update: "\ue032" + readonly property string uninstall: "\ue033" + readonly property string verticalEllipsis: "\ue034" + } +} diff --git a/interface/resources/qml/stylesUit/IconButton.qml b/interface/resources/qml/stylesUit/IconButton.qml new file mode 100644 index 0000000000..e5a18e2ae7 --- /dev/null +++ b/interface/resources/qml/stylesUit/IconButton.qml @@ -0,0 +1,18 @@ +// +// IconButton.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "." + +RalewayRegular { + font.pixelSize: hifi.fontSizes.iconButton + font.capitalization: Font.AllUppercase + font.letterSpacing: 1.5 +} diff --git a/interface/resources/qml/stylesUit/InfoItem.qml b/interface/resources/qml/stylesUit/InfoItem.qml new file mode 100644 index 0000000000..fa7684e8e7 --- /dev/null +++ b/interface/resources/qml/stylesUit/InfoItem.qml @@ -0,0 +1,17 @@ +// +// InfoItem.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "." + +RalewaySemiBold { + lineHeight: 2 + font.pixelSize: hifi.fontSizes.menuItem +} diff --git a/interface/resources/qml/stylesUit/InputLabel.qml b/interface/resources/qml/stylesUit/InputLabel.qml new file mode 100644 index 0000000000..3853dd5b19 --- /dev/null +++ b/interface/resources/qml/stylesUit/InputLabel.qml @@ -0,0 +1,16 @@ +// +// InputLabel.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "." + +RalewaySemiBold { + font.pixelSize: hifi.fontSizes.inputLabel +} diff --git a/interface/resources/qml/stylesUit/ListItem.qml b/interface/resources/qml/stylesUit/ListItem.qml new file mode 100644 index 0000000000..a69c4b48c2 --- /dev/null +++ b/interface/resources/qml/stylesUit/ListItem.qml @@ -0,0 +1,16 @@ +// +// ListItem.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "." + +RalewayRegular { + font.pixelSize: hifi.fontSizes.listItem +} diff --git a/interface/resources/qml/stylesUit/Logs.qml b/interface/resources/qml/stylesUit/Logs.qml new file mode 100644 index 0000000000..45d4436fbf --- /dev/null +++ b/interface/resources/qml/stylesUit/Logs.qml @@ -0,0 +1,16 @@ +// +// Logs.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "." + +AnonymousProRegular { + font.pixelSize: hifi.fontSizes.logs +} diff --git a/interface/resources/qml/stylesUit/OverlayTitle.qml b/interface/resources/qml/stylesUit/OverlayTitle.qml new file mode 100644 index 0000000000..0fb423baab --- /dev/null +++ b/interface/resources/qml/stylesUit/OverlayTitle.qml @@ -0,0 +1,16 @@ +// +// OverlayTitle.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "." + +RalewayRegular { + font.pixelSize: hifi.fontSizes.overlayTitle +} diff --git a/interface/resources/qml/stylesUit/RalewayBold.qml b/interface/resources/qml/stylesUit/RalewayBold.qml new file mode 100644 index 0000000000..7edde91271 --- /dev/null +++ b/interface/resources/qml/stylesUit/RalewayBold.qml @@ -0,0 +1,21 @@ +// +// RalewayBold.qml +// +// Created by David Rowe on 12 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +Text { + id: root + property real size: 32 + font.pixelSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: "Raleway" + font.bold: true +} diff --git a/interface/resources/qml/stylesUit/RalewayLight.qml b/interface/resources/qml/stylesUit/RalewayLight.qml new file mode 100644 index 0000000000..666ebc2ea9 --- /dev/null +++ b/interface/resources/qml/stylesUit/RalewayLight.qml @@ -0,0 +1,20 @@ +// +// RalewayLight.qml +// +// Created by David Rowe on 12 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +Text { + id: root + property real size: 32 + font.pixelSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: "Raleway Light" +} diff --git a/interface/resources/qml/stylesUit/RalewayRegular.qml b/interface/resources/qml/stylesUit/RalewayRegular.qml new file mode 100644 index 0000000000..e263922095 --- /dev/null +++ b/interface/resources/qml/stylesUit/RalewayRegular.qml @@ -0,0 +1,20 @@ +// +// RalewayRegular.qml +// +// Created by David Rowe on 12 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 + +Text { + id: root + property real size: 32 + font.pixelSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: "Raleway" +} diff --git a/interface/resources/qml/stylesUit/RalewaySemiBold.qml b/interface/resources/qml/stylesUit/RalewaySemiBold.qml new file mode 100644 index 0000000000..19d8b6b8c9 --- /dev/null +++ b/interface/resources/qml/stylesUit/RalewaySemiBold.qml @@ -0,0 +1,21 @@ +// +// RalewaySemiBold.qml +// +// Created by David Rowe on 12 Feb 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 + +Text { + id: root + property real size: 32 + font.pixelSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: "Raleway" + font.weight: Font.DemiBold +} diff --git a/interface/resources/qml/stylesUit/SectionName.qml b/interface/resources/qml/stylesUit/SectionName.qml new file mode 100644 index 0000000000..20f8e1e116 --- /dev/null +++ b/interface/resources/qml/stylesUit/SectionName.qml @@ -0,0 +1,17 @@ +// +// SectionName.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "." + +RalewayRegular { + font.pixelSize: hifi.fontSizes.sectionName + font.capitalization: Font.AllUppercase +} diff --git a/interface/resources/qml/stylesUit/Separator.qml b/interface/resources/qml/stylesUit/Separator.qml new file mode 100644 index 0000000000..d9f11e192c --- /dev/null +++ b/interface/resources/qml/stylesUit/Separator.qml @@ -0,0 +1,41 @@ +// +// Separator.qml +// +// Created by Zach Fox on 2017-06-06 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "." + +Item { + // Size + height: 2; + width: parent.width; + + Rectangle { + // Size + width: parent.width; + height: 1; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: height; + // Style + color: hifi.colors.baseGrayShadow; + } + + Rectangle { + // Size + width: parent.width; + height: 1; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + // Style + color: hifi.colors.baseGrayHighlight; + } +} diff --git a/interface/resources/qml/stylesUit/ShortcutText.qml b/interface/resources/qml/stylesUit/ShortcutText.qml new file mode 100644 index 0000000000..8504ffa2b8 --- /dev/null +++ b/interface/resources/qml/stylesUit/ShortcutText.qml @@ -0,0 +1,16 @@ +// +// ShortcutText.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "." + +RalewayLight { + font.pixelSize: hifi.fontSizes.shortcutText +} diff --git a/interface/resources/qml/stylesUit/TabName.qml b/interface/resources/qml/stylesUit/TabName.qml new file mode 100644 index 0000000000..0f620fe8c2 --- /dev/null +++ b/interface/resources/qml/stylesUit/TabName.qml @@ -0,0 +1,17 @@ +// +// TabName.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "." + +RalewayRegular { + font.pixelSize: hifi.fontSizes.tabName + font.capitalization: Font.AllUppercase +} diff --git a/interface/resources/qml/stylesUit/TextFieldInput.qml b/interface/resources/qml/stylesUit/TextFieldInput.qml new file mode 100644 index 0000000000..f2a57e57fc --- /dev/null +++ b/interface/resources/qml/stylesUit/TextFieldInput.qml @@ -0,0 +1,16 @@ +// +// TextFieldInput.qml +// +// Created by Clement on 7/18/16 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import "." + +FiraSansSemiBold { + font.pixelSize: hifi.fontSizes.textFieldInput +} diff --git a/interface/resources/qml/styles-uit/qmldir b/interface/resources/qml/stylesUit/qmldir similarity index 100% rename from interface/resources/qml/styles-uit/qmldir rename to interface/resources/qml/stylesUit/qmldir diff --git a/interface/resources/qml/windows/Decoration.qml b/interface/resources/qml/windows/Decoration.qml index f8fd9f4e6c..efaea6be8a 100644 --- a/interface/resources/qml/windows/Decoration.qml +++ b/interface/resources/qml/windows/Decoration.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "." -import "../styles-uit" +import stylesUit 1.0 Rectangle { HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows/DefaultFrame.qml b/interface/resources/qml/windows/DefaultFrame.qml index 60e744bec3..5a366e367b 100644 --- a/interface/resources/qml/windows/DefaultFrame.qml +++ b/interface/resources/qml/windows/DefaultFrame.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "." -import "../styles-uit" +import stylesUit 1.0 Frame { HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows/DefaultFrameDecoration.qml b/interface/resources/qml/windows/DefaultFrameDecoration.qml index 1ddd83976e..fb0dd55985 100644 --- a/interface/resources/qml/windows/DefaultFrameDecoration.qml +++ b/interface/resources/qml/windows/DefaultFrameDecoration.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "." -import "../styles-uit" +import stylesUit 1.0 Decoration { HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows/Fadable.qml b/interface/resources/qml/windows/Fadable.qml index 406c6be556..6d88fb067a 100644 --- a/interface/resources/qml/windows/Fadable.qml +++ b/interface/resources/qml/windows/Fadable.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../styles-uit" +import stylesUit 1.0 // Enable window visibility transitions FocusScope { diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index 271d4f2e07..7b0fbf8d8c 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../styles-uit" +import stylesUit 1.0 import "../js/Utils.js" as Utils Item { diff --git a/interface/resources/qml/windows/ModalFrame.qml b/interface/resources/qml/windows/ModalFrame.qml index cb23ccd5ad..ae149224e3 100644 --- a/interface/resources/qml/windows/ModalFrame.qml +++ b/interface/resources/qml/windows/ModalFrame.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import "." -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Frame { HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows/ScrollingWindow.qml b/interface/resources/qml/windows/ScrollingWindow.qml index c156b80388..4cab96701e 100644 --- a/interface/resources/qml/windows/ScrollingWindow.qml +++ b/interface/resources/qml/windows/ScrollingWindow.qml @@ -14,8 +14,8 @@ import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 import "." -import "../styles-uit" -import "../controls-uit" as HiFiControls +import stylesUit 1.0 +import controlsUit 1.0 as HiFiControls // FIXME how do I set the initial position of a window without // overriding places where the a individual client of the window diff --git a/interface/resources/qml/windows/TabletModalFrame.qml b/interface/resources/qml/windows/TabletModalFrame.qml index 550eec8357..1e9310eb5a 100644 --- a/interface/resources/qml/windows/TabletModalFrame.qml +++ b/interface/resources/qml/windows/TabletModalFrame.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import "." -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Rectangle { diff --git a/interface/resources/qml/windows/ToolFrame.qml b/interface/resources/qml/windows/ToolFrame.qml index 20c86afb5e..bb2bada498 100644 --- a/interface/resources/qml/windows/ToolFrame.qml +++ b/interface/resources/qml/windows/ToolFrame.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "." -import "../styles-uit" +import stylesUit 1.0 Frame { HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows/ToolFrameDecoration.qml b/interface/resources/qml/windows/ToolFrameDecoration.qml index ba36a2a38c..4f149037b3 100644 --- a/interface/resources/qml/windows/ToolFrameDecoration.qml +++ b/interface/resources/qml/windows/ToolFrameDecoration.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "." -import "../styles-uit" +import stylesUit 1.0 Decoration { id: root diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index 835967c628..9f180af55d 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "." -import "../styles-uit" +import stylesUit 1.0 // FIXME how do I set the initial position of a window without // overriding places where the a individual client of the window diff --git a/interface/resources/sounds/keyboard_key.mp3 b/interface/resources/sounds/keyboard_key.mp3 new file mode 100644 index 0000000000..e2cec81032 Binary files /dev/null and b/interface/resources/sounds/keyboard_key.mp3 differ diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e2c4df22db..368f4d4d9a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -52,6 +52,8 @@ #include <QTemporaryDir> #include <gl/QOpenGLContextWrapper.h> +#include <gl/GLWindow.h> +#include <gl/GLHelpers.h> #include <shared/FileUtils.h> #include <shared/QtHelpers.h> @@ -184,6 +186,8 @@ #include "scripting/RatesScriptingInterface.h" #include "scripting/SelectionScriptingInterface.h" #include "scripting/WalletScriptingInterface.h" +#include "scripting/TTSScriptingInterface.h" +#include "scripting/KeyboardScriptingInterface.h" #if defined(Q_OS_MAC) || defined(Q_OS_WIN) #include "SpeechRecognizer.h" #endif @@ -202,6 +206,7 @@ #include "ui/UpdateDialog.h" #include "ui/overlays/Overlays.h" #include "ui/DomainConnectionModel.h" +#include "ui/Keyboard.h" #include "Util.h" #include "InterfaceParentFinder.h" #include "ui/OctreeStatsProvider.h" @@ -226,6 +231,7 @@ #include "commerce/Ledger.h" #include "commerce/Wallet.h" #include "commerce/QmlCommerce.h" +#include "ResourceRequestObserver.h" #include "webbrowser/WebBrowserSuggestionsEngine.h" #include <DesktopPreviewProvider.h> @@ -379,7 +385,7 @@ static const int INTERVAL_TO_CHECK_HMD_WORN_STATUS = 500; // milliseconds static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop"; static const QString ACTIVE_DISPLAY_PLUGIN_SETTING_NAME = "activeDisplayPlugin"; static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; -static const QString AUTO_LOGOUT_SETTING_NAME = "wallet/autoLogout"; +static const QString KEEP_ME_LOGGED_IN_SETTING_NAME = "keepMeLoggedIn"; const std::vector<std::pair<QString, Application::AcceptURLMethod>> Application::_acceptedExtensions { { SVO_EXTENSION, &Application::importSVOFromURL }, @@ -530,11 +536,11 @@ bool isDomainURL(QUrl url) { if (url.scheme() == URL_SCHEME_HIFI) { return true; } - if (url.scheme() != URL_SCHEME_FILE) { + if (url.scheme() != HIFI_URL_SCHEME_FILE) { // TODO -- once Octree::readFromURL no-longer takes over the main event-loop, serverless-domain urls can // be loaded over http(s) - // && url.scheme() != URL_SCHEME_HTTP && - // url.scheme() != URL_SCHEME_HTTPS + // && url.scheme() != HIFI_URL_SCHEME_HTTP && + // url.scheme() != HIFI_URL_SCHEME_HTTPS return false; } if (url.path().endsWith(".json", Qt::CaseInsensitive) || @@ -945,8 +951,12 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set<Ledger>(); DependencyManager::set<Wallet>(); DependencyManager::set<WalletScriptingInterface>(); + DependencyManager::set<TTSScriptingInterface>(); DependencyManager::set<FadeEffect>(); + DependencyManager::set<ResourceRequestObserver>(); + DependencyManager::set<Keyboard>(); + DependencyManager::set<KeyboardScriptingInterface>(); return previousSessionCrashed; } @@ -969,9 +979,11 @@ OffscreenGLCanvas* _qmlShareContext { nullptr }; // and manually set THAT to be the shared context for the Chromium helper #if !defined(DISABLE_QML) OffscreenGLCanvas* _chromiumShareContext { nullptr }; -Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); #endif +Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); +Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); + Setting::Handle<int> sessionRunTime{ "sessionRunTime", 0 }; const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 60.0f; @@ -1026,8 +1038,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // If the URL scheme is http(s) or ftp, then use as is, else - treat it as a local file // This is done so as not break previous command line scripts - if (testScriptPath.left(URL_SCHEME_HTTP.length()) == URL_SCHEME_HTTP || - testScriptPath.left(URL_SCHEME_FTP.length()) == URL_SCHEME_FTP) { + if (testScriptPath.left(HIFI_URL_SCHEME_HTTP.length()) == HIFI_URL_SCHEME_HTTP || + testScriptPath.left(HIFI_URL_SCHEME_FTP.length()) == HIFI_URL_SCHEME_FTP) { setProperty(hifi::properties::TEST, QUrl::fromUserInput(testScriptPath)); } else if (QFileInfo(testScriptPath).exists()) { @@ -1142,7 +1154,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // set the OCULUS_STORE property so the oculus plugin can know if we ran from the Oculus Store static const QString OCULUS_STORE_ARG = "--oculus-store"; - setProperty(hifi::properties::OCULUS_STORE, arguments().indexOf(OCULUS_STORE_ARG) != -1); + bool isStore = arguments().indexOf(OCULUS_STORE_ARG) != -1; + setProperty(hifi::properties::OCULUS_STORE, isStore); + DependencyManager::get<WalletScriptingInterface>()->setLimitedCommerce(isStore); // Or we could make it a separate arg, or if either arg is set, etc. And should this instead by a hifi::properties? updateHeartbeat(); @@ -1368,7 +1382,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _glWidget->setMouseTracking(true); // Make sure the window is set to the correct size by processing the pending events QCoreApplication::processEvents(); - _glWidget->createContext(); // Create the main thread context, the GPU backend initializeGL(); @@ -2318,6 +2331,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Preload Tablet sounds DependencyManager::get<TabletScriptingInterface>()->preloadSounds(); + DependencyManager::get<Keyboard>()->createKeyboard(); _pendingIdleEvent = false; _pendingRenderEvent = false; @@ -2330,23 +2344,29 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground); AndroidHelper::instance().notifyLoadComplete(); #else - static int CHECK_LOGIN_TIMER = 3000; - QTimer* checkLoginTimer = new QTimer(this); - checkLoginTimer->setInterval(CHECK_LOGIN_TIMER); - checkLoginTimer->setSingleShot(true); - connect(checkLoginTimer, &QTimer::timeout, this, []() { - auto accountManager = DependencyManager::get<AccountManager>(); - auto dialogsManager = DependencyManager::get<DialogsManager>(); - if (!accountManager->isLoggedIn()) { - Setting::Handle<bool>{"loginDialogPoppedUp", false}.set(true); - dialogsManager->showLoginDialog(); - QJsonObject loginData = {}; - loginData["action"] = "login dialog shown"; - UserActivityLogger::getInstance().logAction("encourageLoginDialog", loginData); - } - }); - Setting::Handle<bool>{"loginDialogPoppedUp", false}.set(false); - checkLoginTimer->start(); + // Do not show login dialog if requested not to on the command line + const QString HIFI_NO_LOGIN_COMMAND_LINE_KEY = "--no-login-suggestion"; + int index = arguments().indexOf(HIFI_NO_LOGIN_COMMAND_LINE_KEY); + if (index == -1) { + // request not found + static int CHECK_LOGIN_TIMER = 3000; + QTimer* checkLoginTimer = new QTimer(this); + checkLoginTimer->setInterval(CHECK_LOGIN_TIMER); + checkLoginTimer->setSingleShot(true); + connect(checkLoginTimer, &QTimer::timeout, this, []() { + auto accountManager = DependencyManager::get<AccountManager>(); + auto dialogsManager = DependencyManager::get<DialogsManager>(); + if (!accountManager->isLoggedIn()) { + Setting::Handle<bool>{ "loginDialogPoppedUp", false }.set(true); + dialogsManager->showLoginDialog(); + QJsonObject loginData = {}; + loginData["action"] = "login dialog shown"; + UserActivityLogger::getInstance().logAction("encourageLoginDialog", loginData); + } + }); + Setting::Handle<bool>{ "loginDialogPoppedUp", false }.set(false); + checkLoginTimer->start(); + } #endif } @@ -2427,11 +2447,17 @@ QString Application::getUserAgent() { } void Application::toggleTabletUI(bool shouldOpen) const { - auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); auto hmd = DependencyManager::get<HMDScriptingInterface>(); if (!(shouldOpen && hmd->getShouldShowTablet())) { auto HMD = DependencyManager::get<HMDScriptingInterface>(); HMD->toggleShouldShowTablet(); + + if (!HMD->getShouldShowTablet()) { + DependencyManager::get<Keyboard>()->setRaised(false); + _window->activateWindow(); + auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet(SYSTEM_TABLET); + tablet->unfocus(); + } } } @@ -2562,8 +2588,8 @@ void Application::cleanupBeforeQuit() { } DependencyManager::destroy<ScriptEngines>(); - bool autoLogout = Setting::Handle<bool>(AUTO_LOGOUT_SETTING_NAME, false).get(); - if (autoLogout) { + bool keepMeLoggedIn = Setting::Handle<bool>(KEEP_ME_LOGGED_IN_SETTING_NAME, false).get(); + if (!keepMeLoggedIn) { DependencyManager::get<AccountManager>()->removeAccountFromFile(); } @@ -2624,6 +2650,8 @@ void Application::cleanupBeforeQuit() { // it accesses the PickManager to delete its associated Pick DependencyManager::destroy<PointerManager>(); DependencyManager::destroy<PickManager>(); + DependencyManager::destroy<KeyboardScriptingInterface>(); + DependencyManager::destroy<Keyboard>(); qCDebug(interfaceapp) << "Application::cleanupBeforeQuit() complete"; } @@ -2725,46 +2753,66 @@ void Application::initializeGL() { _isGLInitialized = true; } - if (!_glWidget->makeCurrent()) { - qCWarning(interfaceapp, "Unable to make window context current"); - } + _glWidget->windowHandle()->setFormat(getDefaultOpenGLSurfaceFormat()); + // When loading QtWebEngineWidgets, it creates a global share context on startup. + // We have to account for this possibility by checking here for an existing + // global share context + auto globalShareContext = qt_gl_global_share_context(); + #if !defined(DISABLE_QML) // Build a shared canvas / context for the Chromium processes - { - // Disable signed distance field font rendering on ATI/AMD GPUs, due to - // https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app - std::string vendor{ (const char*)glGetString(GL_VENDOR) }; - if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) { - qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text")); - } - + if (!globalShareContext) { // Chromium rendering uses some GL functions that prevent nSight from capturing // frames, so we only create the shared context if nsight is NOT active. if (!nsightActive()) { _chromiumShareContext = new OffscreenGLCanvas(); _chromiumShareContext->setObjectName("ChromiumShareContext"); - _chromiumShareContext->create(_glWidget->qglContext()); + auto format =QSurfaceFormat::defaultFormat(); +#ifdef Q_OS_MAC + // On mac, the primary shared OpenGL context must be a 3.2 core context, + // or chromium flips out and spews error spam (but renders fine) + format.setMajorVersion(3); + format.setMinorVersion(2); +#endif + _chromiumShareContext->setFormat(format); + _chromiumShareContext->create(); if (!_chromiumShareContext->makeCurrent()) { qCWarning(interfaceapp, "Unable to make chromium shared context current"); } - qt_gl_set_global_share_context(_chromiumShareContext->getContext()); + globalShareContext = _chromiumShareContext->getContext(); + qt_gl_set_global_share_context(globalShareContext); _chromiumShareContext->doneCurrent(); - // Restore the GL widget context - if (!_glWidget->makeCurrent()) { - qCWarning(interfaceapp, "Unable to make window context current"); - } - } else { - qCWarning(interfaceapp) << "nSight detected, disabling chrome rendering"; } } #endif + + _glWidget->createContext(globalShareContext); + + if (!_glWidget->makeCurrent()) { + qCWarning(interfaceapp, "Unable to make window context current"); + } + +#if !defined(DISABLE_QML) + // Disable signed distance field font rendering on ATI/AMD GPUs, due to + // https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app + std::string vendor{ (const char*)glGetString(GL_VENDOR) }; + if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) { + qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text")); + } +#endif + + if (!globalShareContext) { + globalShareContext = _glWidget->qglContext(); + qt_gl_set_global_share_context(globalShareContext); + } + // Build a shared canvas / context for the QML rendering { _qmlShareContext = new OffscreenGLCanvas(); _qmlShareContext->setObjectName("QmlShareContext"); - _qmlShareContext->create(_glWidget->qglContext()); + _qmlShareContext->create(globalShareContext); if (!_qmlShareContext->makeCurrent()) { qCWarning(interfaceapp, "Unable to make QML shared context current"); } @@ -2888,6 +2936,7 @@ void Application::initializeRenderEngine() { // Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success. DependencyManager::get<GeometryCache>()->initializeShapePipelines(); + DependencyManager::get<Keyboard>()->registerKeyboardHighlighting(); }); } @@ -2900,7 +2949,7 @@ void Application::initializeUi() { LoginDialog::registerType(); Tooltip::registerType(); UpdateDialog::registerType(); - QmlContextCallback callback = [](QQmlContext* context) { + QmlContextCallback commerceCallback = [](QQmlContext* context) { context->setContextProperty("Commerce", new QmlCommerce()); }; OffscreenQmlSurface::addWhitelistContextHandler({ @@ -2919,14 +2968,20 @@ void Application::initializeUi() { QUrl{ "hifi/commerce/wallet/PassphraseChange.qml" }, QUrl{ "hifi/commerce/wallet/PassphraseModal.qml" }, QUrl{ "hifi/commerce/wallet/PassphraseSelection.qml" }, - QUrl{ "hifi/commerce/wallet/Security.qml" }, - QUrl{ "hifi/commerce/wallet/SecurityImageChange.qml" }, - QUrl{ "hifi/commerce/wallet/SecurityImageModel.qml" }, - QUrl{ "hifi/commerce/wallet/SecurityImageSelection.qml" }, QUrl{ "hifi/commerce/wallet/Wallet.qml" }, QUrl{ "hifi/commerce/wallet/WalletHome.qml" }, QUrl{ "hifi/commerce/wallet/WalletSetup.qml" }, - }, callback); + QUrl{ "hifi/dialogs/security/Security.qml" }, + QUrl{ "hifi/dialogs/security/SecurityImageChange.qml" }, + QUrl{ "hifi/dialogs/security/SecurityImageModel.qml" }, + QUrl{ "hifi/dialogs/security/SecurityImageSelection.qml" }, + }, commerceCallback); + QmlContextCallback ttsCallback = [](QQmlContext* context) { + context->setContextProperty("TextToSpeech", DependencyManager::get<TTSScriptingInterface>().data()); + }; + OffscreenQmlSurface::addWhitelistContextHandler({ + QUrl{ "hifi/tts/TTS.qml" } + }, ttsCallback); qmlRegisterType<ResourceImageItem>("Hifi", 1, 0, "ResourceImageItem"); qmlRegisterType<Preference>("Hifi", 1, 0, "Preference"); qmlRegisterType<WebBrowserSuggestionsEngine>("HifiWeb", 1, 0, "WebBrowserSuggestionsEngine"); @@ -3078,6 +3133,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { surfaceContext->setContextProperty("Vec3", new Vec3()); surfaceContext->setContextProperty("Uuid", new ScriptUUID()); surfaceContext->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data()); + surfaceContext->setContextProperty("Keyboard", DependencyManager::get<KeyboardScriptingInterface>().data()); surfaceContext->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data()); surfaceContext->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data()); @@ -3127,8 +3183,9 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance()); surfaceContext->setContextProperty("Selection", DependencyManager::get<SelectionScriptingInterface>().data()); surfaceContext->setContextProperty("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data()); - surfaceContext->setContextProperty("Wallet", DependencyManager::get<WalletScriptingInterface>().data()); + surfaceContext->setContextProperty("WalletScriptingInterface", DependencyManager::get<WalletScriptingInterface>().data()); surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance()); + surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().data()); if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { surfaceContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get())); @@ -5022,12 +5079,12 @@ void Application::saveSettings() const { PluginManager::getInstance()->saveSettings(); } -bool Application::importEntities(const QString& urlOrFilename) { +bool Application::importEntities(const QString& urlOrFilename, const bool isObservable, const qint64 callerId) { bool success = false; _entityClipboard->withWriteLock([&] { _entityClipboard->eraseAllOctreeElements(); - success = _entityClipboard->readFromURL(urlOrFilename); + success = _entityClipboard->readFromURL(urlOrFilename, isObservable, callerId); if (success) { _entityClipboard->reaverageOctreeElements(); } @@ -5812,6 +5869,42 @@ void Application::update(float deltaTime) { controller::Pose pose = userInputMapper->getPoseState(action); myAvatar->setControllerPoseInSensorFrame(action, pose.transform(avatarToSensorMatrix)); } + + static const std::vector<QString> trackedObjectStringLiterals = { + QStringLiteral("_TrackedObject00"), QStringLiteral("_TrackedObject01"), QStringLiteral("_TrackedObject02"), QStringLiteral("_TrackedObject03"), + QStringLiteral("_TrackedObject04"), QStringLiteral("_TrackedObject05"), QStringLiteral("_TrackedObject06"), QStringLiteral("_TrackedObject07"), + QStringLiteral("_TrackedObject08"), QStringLiteral("_TrackedObject09"), QStringLiteral("_TrackedObject10"), QStringLiteral("_TrackedObject11"), + QStringLiteral("_TrackedObject12"), QStringLiteral("_TrackedObject13"), QStringLiteral("_TrackedObject14"), QStringLiteral("_TrackedObject15") + }; + + // Controlled by the Developer > Avatar > Show Tracked Objects menu. + if (_showTrackedObjects) { + static const std::vector<controller::Action> trackedObjectActions = { + controller::Action::TRACKED_OBJECT_00, controller::Action::TRACKED_OBJECT_01, controller::Action::TRACKED_OBJECT_02, controller::Action::TRACKED_OBJECT_03, + controller::Action::TRACKED_OBJECT_04, controller::Action::TRACKED_OBJECT_05, controller::Action::TRACKED_OBJECT_06, controller::Action::TRACKED_OBJECT_07, + controller::Action::TRACKED_OBJECT_08, controller::Action::TRACKED_OBJECT_09, controller::Action::TRACKED_OBJECT_10, controller::Action::TRACKED_OBJECT_11, + controller::Action::TRACKED_OBJECT_12, controller::Action::TRACKED_OBJECT_13, controller::Action::TRACKED_OBJECT_14, controller::Action::TRACKED_OBJECT_15 + }; + + int i = 0; + glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); + for (auto& action : trackedObjectActions) { + controller::Pose pose = userInputMapper->getPoseState(action); + if (pose.valid) { + glm::vec3 pos = transformPoint(myAvatarMatrix, pose.translation); + glm::quat rot = glmExtractRotation(myAvatarMatrix) * pose.rotation; + DebugDraw::getInstance().addMarker(trackedObjectStringLiterals[i], rot, pos, BLUE); + } else { + DebugDraw::getInstance().removeMarker(trackedObjectStringLiterals[i]); + } + i++; + } + } else if (_prevShowTrackedObjects) { + for (auto& key : trackedObjectStringLiterals) { + DebugDraw::getInstance().removeMarker(key); + } + } + _prevShowTrackedObjects = _showTrackedObjects; } updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... @@ -5978,7 +6071,9 @@ void Application::update(float deltaTime) { { PROFILE_RANGE_EX(app, "Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("overlays"); - _overlays.update(deltaTime); + if (qApp->shouldPaint()) { + _overlays.update(deltaTime); + } } // Update _viewFrustum with latest camera and view frustum data... @@ -6063,8 +6158,10 @@ void Application::update(float deltaTime) { PROFILE_RANGE_EX(app, "PostUpdateLambdas", 0xffff0000, (uint64_t)0); PerformanceTimer perfTimer("postUpdateLambdas"); std::unique_lock<std::mutex> guard(_postUpdateLambdasLock); - for (auto& iter : _postUpdateLambdas) { - iter.second(); + if (qApp->shouldPaint()) { + for (auto& iter : _postUpdateLambdas) { + iter.second(); + } } _postUpdateLambdas.clear(); } @@ -6772,6 +6869,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("LODManager", DependencyManager::get<LODManager>().data()); + scriptEngine->registerGlobalObject("Keyboard", DependencyManager::get<KeyboardScriptingInterface>().data()); + scriptEngine->registerGlobalObject("Paths", DependencyManager::get<PathUtils>().data()); scriptEngine->registerGlobalObject("HMD", DependencyManager::get<HMDScriptingInterface>().data()); @@ -6808,9 +6907,10 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("AvatarInputs", AvatarInputs::getInstance()); scriptEngine->registerGlobalObject("Selection", DependencyManager::get<SelectionScriptingInterface>().data()); scriptEngine->registerGlobalObject("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data()); - scriptEngine->registerGlobalObject("Wallet", DependencyManager::get<WalletScriptingInterface>().data()); + scriptEngine->registerGlobalObject("WalletScriptingInterface", DependencyManager::get<WalletScriptingInterface>().data()); scriptEngine->registerGlobalObject("AddressManager", DependencyManager::get<AddressManager>().data()); scriptEngine->registerGlobalObject("HifiAbout", AboutUtil::getInstance()); + scriptEngine->registerGlobalObject("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().data()); qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue); @@ -7197,7 +7297,8 @@ void Application::addAssetToWorldFromURL(QString url) { addAssetToWorldInfo(filename, "Downloading model file " + filename + "."); - auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(nullptr, QUrl(url)); + auto request = DependencyManager::get<ResourceManager>()->createResourceRequest( + nullptr, QUrl(url), true, -1, "Application::addAssetToWorldFromURL"); connect(request, &ResourceRequest::finished, this, &Application::addAssetToWorldFromURLRequestFinished); request->send(); } @@ -7811,6 +7912,7 @@ void Application::loadAvatarBrowser() const { auto tablet = dynamic_cast<TabletProxy*>(DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system")); // construct the url to the marketplace item QString url = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace?category=avatars"; + QString MARKETPLACES_INJECT_SCRIPT_PATH = "file:///" + qApp->applicationDirPath() + "/scripts/system/html/js/marketplacesInject.js"; tablet->gotoWebScreen(url, MARKETPLACES_INJECT_SCRIPT_PATH); DependencyManager::get<HMDScriptingInterface>()->openTablet(); @@ -8306,6 +8408,10 @@ void Application::setShowBulletConstraintLimits(bool value) { _physicsEngine->setShowBulletConstraintLimits(value); } +void Application::setShowTrackedObjects(bool value) { + _showTrackedObjects = value; +} + void Application::startHMDStandBySession() { _autoSwitchDisplayModeSupportedHMDPlugin->startStandBySession(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index ed51e085b2..14e30b8006 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -342,7 +342,7 @@ public slots: QVector<EntityItemID> pasteEntities(float x, float y, float z); bool exportEntities(const QString& filename, const QVector<EntityItemID>& entityIDs, const glm::vec3* givenOffset = nullptr); bool exportEntities(const QString& filename, float x, float y, float z, float scale); - bool importEntities(const QString& url); + bool importEntities(const QString& url, const bool isObservable = true, const qint64 callerId = -1); void updateThreadPoolCount() const; void updateSystemTabletMode(); void goToErrorDomainURL(QUrl errorDomainURL); @@ -498,6 +498,8 @@ private slots: void setShowBulletConstraints(bool value); void setShowBulletConstraintLimits(bool value); + void setShowTrackedObjects(bool value); + private: void init(); bool handleKeyEventForFocusedEntityOrOverlay(QEvent* event); @@ -779,5 +781,8 @@ private: std::atomic<bool> _pendingRenderEvent { true }; bool quitWhenFinished { false }; + + bool _showTrackedObjects { false }; + bool _prevShowTrackedObjects { false }; }; #endif // hifi_Application_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index eef14c873e..1fc1e0c033 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -265,6 +265,18 @@ Menu::Menu() { QString("hifi/tablet/TabletGraphicsPreferences.qml"), "GraphicsPreferencesDialog"); }); + // Settings > Security... + action = addActionToQMenuAndActionHash(settingsMenu, "Security..."); + connect(action, &QAction::triggered, [] { + auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system"); + auto hmd = DependencyManager::get<HMDScriptingInterface>(); + tablet->pushOntoStack("hifi/dialogs/security/Security.qml"); + + if (!hmd->getShouldShowTablet()) { + hmd->toggleShouldShowTablet(); + } + }); + // Settings > Developer Menu addCheckableActionToQMenuAndActionHash(settingsMenu, "Developer Menu", 0, false, this, SLOT(toggleDeveloperMenus())); @@ -273,21 +285,79 @@ Menu::Menu() { // Developer menu ---------------------------------- MenuWrapper* developerMenu = addMenu("Developer", "Developer"); + + // Developer > Scripting >>> + MenuWrapper* scriptingOptionsMenu = developerMenu->addMenu("Scripting"); + + // Developer > Scripting > Console... + addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J, + DependencyManager::get<StandAloneJSConsole>().data(), + SLOT(toggleConsole()), + QAction::NoRole, + UNSPECIFIED_POSITION); + // Developer > Scripting > API Debugger + action = addActionToQMenuAndActionHash(scriptingOptionsMenu, "API Debugger"); + connect(action, &QAction::triggered, [] { + QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); + defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js"); + DependencyManager::get<ScriptEngines>()->loadScript(defaultScriptsLoc.toString()); + }); + + // Developer > Scripting > Entity Script Server Log + auto essLogAction = addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::EntityScriptServerLog, 0, + qApp, SLOT(toggleEntityScriptServerLogDialog())); + { + auto nodeList = DependencyManager::get<NodeList>(); + QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] { + auto nodeList = DependencyManager::get<NodeList>(); + essLogAction->setEnabled(nodeList->getThisNodeCanRez()); + }); + essLogAction->setEnabled(nodeList->getThisNodeCanRez()); + } + + // Developer > Scripting > Script Log (HMD friendly)... + addActionToQMenuAndActionHash(scriptingOptionsMenu, "Script Log (HMD friendly)...", Qt::NoButton, + qApp, SLOT(showScriptLogs())); + + // Developer > Scripting > Verbose Logging + addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::VerboseLogging, 0, false, + qApp, SLOT(updateVerboseLogging())); + + // Developer > Scripting > Enable Speech Control API +#if defined(Q_OS_MAC) || defined(Q_OS_WIN) + auto speechRecognizer = DependencyManager::get<SpeechRecognizer>(); + QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::ControlWithSpeech, + Qt::CTRL | Qt::SHIFT | Qt::Key_C, + speechRecognizer->getEnabled(), + speechRecognizer.data(), + SLOT(setEnabled(bool)), + UNSPECIFIED_POSITION); + connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); +#endif + // Developer > UI >>> MenuWrapper* uiOptionsMenu = developerMenu->addMenu("UI"); action = addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::DesktopTabletToToolbar, 0, qApp->getDesktopTabletBecomesToolbarSetting()); + + // Developer > UI > Show Overlays + addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::Overlays, 0, true); + + // Developer > UI > Desktop Tablet Becomes Toolbar connect(action, &QAction::triggered, [action] { qApp->setDesktopTabletBecomesToolbarSetting(action->isChecked()); }); - + + // Developer > UI > HMD Tablet Becomes Toolbar action = addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::HMDTabletToToolbar, 0, qApp->getHmdTabletBecomesToolbarSetting()); connect(action, &QAction::triggered, [action] { qApp->setHmdTabletBecomesToolbarSetting(action->isChecked()); }); + addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::Use3DKeyboard, 0, true); + // Developer > Render >>> MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render"); @@ -570,6 +640,8 @@ Menu::Menu() { avatar.get(), SLOT(updateMotionBehaviorFromMenu()), UNSPECIFIED_POSITION, "Developer"); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowTrackedObjects, 0, false, qApp, SLOT(setShowTrackedObjects(bool))); + // Developer > Hands >>> MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands"); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false, @@ -684,10 +756,11 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(pickingOptionsMenu, MenuOption::ForceCoarsePicking, 0, false, DependencyManager::get<PickManager>().data(), SLOT(setForceCoarsePicking(bool))); - // Developer > Display Crash Options - addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true); // Developer > Crash >>> MenuWrapper* crashMenu = developerMenu->addMenu("Crash"); + + // Developer > Crash > Display Crash Options + addCheckableActionToQMenuAndActionHash(crashMenu, MenuOption::DisplayCrashOptions, 0, true); addActionToQMenuAndActionHash(crashMenu, MenuOption::DeadlockInterface, 0, qApp, SLOT(deadlockApplication())); addActionToQMenuAndActionHash(crashMenu, MenuOption::UnresponsiveInterface, 0, qApp, SLOT(unresponsiveApplication())); @@ -722,59 +795,15 @@ Menu::Menu() { action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFaultThreaded); connect(action, &QAction::triggered, qApp, []() { std::thread(crash::newFault).join(); }); - // Developer > Stats + // Developer > Show Statistics addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats); + + // Developer > Show Animation Statistics addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::AnimStats); - // Settings > Enable Speech Control API -#if defined(Q_OS_MAC) || defined(Q_OS_WIN) - auto speechRecognizer = DependencyManager::get<SpeechRecognizer>(); - QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::ControlWithSpeech, - Qt::CTRL | Qt::SHIFT | Qt::Key_C, - speechRecognizer->getEnabled(), - speechRecognizer.data(), - SLOT(setEnabled(bool)), - UNSPECIFIED_POSITION); - connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); -#endif - - // console - addActionToQMenuAndActionHash(developerMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J, - DependencyManager::get<StandAloneJSConsole>().data(), - SLOT(toggleConsole()), - QAction::NoRole, - UNSPECIFIED_POSITION); - - // Developer > API Debugger - action = addActionToQMenuAndActionHash(developerMenu, "API Debugger"); - connect(action, &QAction::triggered, [] { - QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); - defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js"); - DependencyManager::get<ScriptEngines>()->loadScript(defaultScriptsLoc.toString()); - }); - - // Developer > Log... + // Developer > Log addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, qApp, SLOT(toggleLogDialog())); - auto essLogAction = addActionToQMenuAndActionHash(developerMenu, MenuOption::EntityScriptServerLog, 0, - qApp, SLOT(toggleEntityScriptServerLogDialog())); - { - auto nodeList = DependencyManager::get<NodeList>(); - QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] { - auto nodeList = DependencyManager::get<NodeList>(); - essLogAction->setEnabled(nodeList->getThisNodeCanRez()); - }); - essLogAction->setEnabled(nodeList->getThisNodeCanRez()); - } - - addActionToQMenuAndActionHash(developerMenu, "Script Log (HMD friendly)...", Qt::NoButton, - qApp, SLOT(showScriptLogs())); - - addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::VerboseLogging, 0, false, - qApp, SLOT(updateVerboseLogging())); - - // Developer > Show Overlays - addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Overlays, 0, true); #if 0 /// -------------- REMOVED FOR NOW -------------- addDisabledActionAndSeparator(navigateMenu, "History"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 031ee2561c..f1d56825b5 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -183,6 +183,7 @@ namespace MenuOption { const QString RunClientScriptTests = "Run Client Script Tests"; const QString RunTimingTests = "Run Timing Tests"; const QString ScriptedMotorControl = "Enable Scripted Motor Control"; + const QString ShowTrackedObjects = "Show Tracked Objects"; const QString SendWrongDSConnectVersion = "Send wrong DS connect version"; const QString SendWrongProtocolVersion = "Send wrong protocol version"; const QString SetHomeLocation = "Set Home Location"; @@ -209,6 +210,7 @@ namespace MenuOption { const QString TurnWithHead = "Turn using Head"; const QString UseAudioForMouth = "Use Audio for Mouth"; const QString UseCamera = "Use Camera"; + const QString Use3DKeyboard = "Use 3D Keyboard"; const QString VelocityFilter = "Velocity Filter"; const QString VisibleToEveryone = "Everyone"; const QString VisibleToFriends = "Friends"; diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp index 3a5d92eb8c..21d3477d7e 100644 --- a/interface/src/ModelPackager.cpp +++ b/interface/src/ModelPackager.cpp @@ -109,10 +109,10 @@ bool ModelPackager::loadModel() { qCDebug(interfaceapp) << "Reading FBX file : " << _fbxInfo.filePath(); QByteArray fbxContents = fbx.readAll(); - _geometry.reset(readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath())); + _hfmModel.reset(readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath())); // make sure we have some basic mappings - populateBasicMapping(_mapping, _fbxInfo.filePath(), *_geometry); + populateBasicMapping(_mapping, _fbxInfo.filePath(), *_hfmModel); } catch (const QString& error) { qCDebug(interfaceapp) << "Error reading " << _fbxInfo.filePath() << ": " << error; return false; @@ -122,7 +122,7 @@ bool ModelPackager::loadModel() { bool ModelPackager::editProperties() { // open the dialog to configure the rest - ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), *_geometry); + ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), *_hfmModel); if (properties.exec() == QDialog::Rejected) { return false; } @@ -235,18 +235,18 @@ bool ModelPackager::zipModel() { return true; } -void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry) { +void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename, const HFMModel& hfmModel) { bool isBodyType = _modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL; // mixamo files - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will // be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file - bool likelyMixamoFile = geometry.applicationName == "mixamo.com" || - (geometry.blendshapeChannelNames.contains("BrowsDown_Right") && - geometry.blendshapeChannelNames.contains("MouthOpen") && - geometry.blendshapeChannelNames.contains("Blink_Left") && - geometry.blendshapeChannelNames.contains("Blink_Right") && - geometry.blendshapeChannelNames.contains("Squint_Right")); + bool likelyMixamoFile = hfmModel.applicationName == "mixamo.com" || + (hfmModel.blendshapeChannelNames.contains("BrowsDown_Right") && + hfmModel.blendshapeChannelNames.contains("MouthOpen") && + hfmModel.blendshapeChannelNames.contains("Blink_Left") && + hfmModel.blendshapeChannelNames.contains("Blink_Right") && + hfmModel.blendshapeChannelNames.contains("Squint_Right")); if (!mapping.contains(NAME_FIELD)) { mapping.insert(NAME_FIELD, QFileInfo(filename).baseName()); @@ -268,15 +268,15 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename } QVariantHash joints = mapping.value(JOINT_FIELD).toHash(); if (!joints.contains("jointEyeLeft")) { - joints.insert("jointEyeLeft", geometry.jointIndices.contains("jointEyeLeft") ? "jointEyeLeft" : - (geometry.jointIndices.contains("EyeLeft") ? "EyeLeft" : "LeftEye")); + joints.insert("jointEyeLeft", hfmModel.jointIndices.contains("jointEyeLeft") ? "jointEyeLeft" : + (hfmModel.jointIndices.contains("EyeLeft") ? "EyeLeft" : "LeftEye")); } if (!joints.contains("jointEyeRight")) { - joints.insert("jointEyeRight", geometry.jointIndices.contains("jointEyeRight") ? "jointEyeRight" : - geometry.jointIndices.contains("EyeRight") ? "EyeRight" : "RightEye"); + joints.insert("jointEyeRight", hfmModel.jointIndices.contains("jointEyeRight") ? "jointEyeRight" : + hfmModel.jointIndices.contains("EyeRight") ? "EyeRight" : "RightEye"); } if (!joints.contains("jointNeck")) { - joints.insert("jointNeck", geometry.jointIndices.contains("jointNeck") ? "jointNeck" : "Neck"); + joints.insert("jointNeck", hfmModel.jointIndices.contains("jointNeck") ? "jointNeck" : "Neck"); } if (isBodyType) { @@ -296,7 +296,7 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename if (!joints.contains("jointHead")) { const char* topName = likelyMixamoFile ? "HeadTop_End" : "HeadEnd"; - joints.insert("jointHead", geometry.jointIndices.contains(topName) ? topName : "Head"); + joints.insert("jointHead", hfmModel.jointIndices.contains(topName) ? topName : "Head"); } mapping.insert(JOINT_FIELD, joints); @@ -370,7 +370,7 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename void ModelPackager::listTextures() { _textures.clear(); - foreach (const FBXMaterial mat, _geometry->materials) { + foreach (const HFMMaterial mat, _hfmModel->materials) { if (!mat.albedoTexture.filename.isEmpty() && mat.albedoTexture.content.isEmpty() && !_textures.contains(mat.albedoTexture.filename)) { _textures << mat.albedoTexture.filename; diff --git a/interface/src/ModelPackager.h b/interface/src/ModelPackager.h index 76295e5a85..ed86f15008 100644 --- a/interface/src/ModelPackager.h +++ b/interface/src/ModelPackager.h @@ -19,7 +19,7 @@ #include "ui/ModelsBrowser.h" -class FBXGeometry; +class HFMModel; class ModelPackager : public QObject { public: @@ -32,7 +32,7 @@ private: bool editProperties(); bool zipModel(); - void populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry); + void populateBasicMapping(QVariantHash& mapping, QString filename, const HFMModel& hfmModel); void listTextures(); bool copyTextures(const QString& oldDir, const QDir& newDir); @@ -44,7 +44,7 @@ private: QString _scriptDir; QVariantHash _mapping; - std::unique_ptr<FBXGeometry> _geometry; + std::unique_ptr<HFMModel> _hfmModel; QStringList _textures; QStringList _scripts; }; diff --git a/interface/src/ModelPropertiesDialog.cpp b/interface/src/ModelPropertiesDialog.cpp index 8984f89d07..49c57744a9 100644 --- a/interface/src/ModelPropertiesDialog.cpp +++ b/interface/src/ModelPropertiesDialog.cpp @@ -27,11 +27,11 @@ ModelPropertiesDialog::ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping, - const QString& basePath, const FBXGeometry& geometry) : + const QString& basePath, const HFMModel& hfmModel) : _modelType(modelType), _originalMapping(originalMapping), _basePath(basePath), -_geometry(geometry) +_hfmModel(hfmModel) { setWindowTitle("Set Model Properties"); @@ -108,8 +108,8 @@ QVariantHash ModelPropertiesDialog::getMapping() const { // update the joint indices QVariantHash jointIndices; - for (int i = 0; i < _geometry.joints.size(); i++) { - jointIndices.insert(_geometry.joints.at(i).name, QString::number(i)); + for (int i = 0; i < _hfmModel.joints.size(); i++) { + jointIndices.insert(_hfmModel.joints.at(i).name, QString::number(i)); } mapping.insert(JOINT_INDEX_FIELD, jointIndices); @@ -118,10 +118,10 @@ QVariantHash ModelPropertiesDialog::getMapping() const { if (_modelType == FSTReader::ATTACHMENT_MODEL) { glm::vec3 pivot; if (_pivotAboutCenter->isChecked()) { - pivot = (_geometry.meshExtents.minimum + _geometry.meshExtents.maximum) * 0.5f; + pivot = (_hfmModel.meshExtents.minimum + _hfmModel.meshExtents.maximum) * 0.5f; } else if (_pivotJoint->currentIndex() != 0) { - pivot = extractTranslation(_geometry.joints.at(_pivotJoint->currentIndex() - 1).transform); + pivot = extractTranslation(_hfmModel.joints.at(_pivotJoint->currentIndex() - 1).transform); } mapping.insert(TRANSLATION_X_FIELD, -pivot.x * (float)_scale->value() + (float)_translationX->value()); mapping.insert(TRANSLATION_Y_FIELD, -pivot.y * (float)_scale->value() + (float)_translationY->value()); @@ -191,7 +191,7 @@ void ModelPropertiesDialog::reset() { } foreach (const QVariant& joint, _originalMapping.values(FREE_JOINT_FIELD)) { QString jointName = joint.toString(); - if (_geometry.jointIndices.contains(jointName)) { + if (_hfmModel.jointIndices.contains(jointName)) { createNewFreeJoint(jointName); } } @@ -249,8 +249,8 @@ QComboBox* ModelPropertiesDialog::createJointBox(bool withNone) const { if (withNone) { box->addItem("(none)"); } - foreach (const FBXJoint& joint, _geometry.joints) { - if (joint.isSkeletonJoint || !_geometry.hasSkeletonJoints) { + foreach (const HFMJoint& joint, _hfmModel.joints) { + if (joint.isSkeletonJoint || !_hfmModel.hasSkeletonJoints) { box->addItem(joint.name); } } @@ -266,7 +266,7 @@ QDoubleSpinBox* ModelPropertiesDialog::createTranslationBox() const { } void ModelPropertiesDialog::insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const { - if (_geometry.jointIndices.contains(name)) { + if (_hfmModel.jointIndices.contains(name)) { joints.insert(joint, name); } else { joints.remove(joint); diff --git a/interface/src/ModelPropertiesDialog.h b/interface/src/ModelPropertiesDialog.h index e3c2d8ed6a..0bf8075197 100644 --- a/interface/src/ModelPropertiesDialog.h +++ b/interface/src/ModelPropertiesDialog.h @@ -30,7 +30,7 @@ class ModelPropertiesDialog : public QDialog { public: ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping, - const QString& basePath, const FBXGeometry& geometry); + const QString& basePath, const HFMModel& hfmModel); QVariantHash getMapping() const; @@ -50,7 +50,7 @@ private: FSTReader::ModelType _modelType; QVariantHash _originalMapping; QString _basePath; - FBXGeometry _geometry; + HFMModel _hfmModel; QLineEdit* _name = nullptr; QPushButton* _textureDirectory = nullptr; QPushButton* _scriptDirectory = nullptr; diff --git a/interface/src/assets/ATPAssetMigrator.cpp b/interface/src/assets/ATPAssetMigrator.cpp index 45ac80b054..d1f882b232 100644 --- a/interface/src/assets/ATPAssetMigrator.cpp +++ b/interface/src/assets/ATPAssetMigrator.cpp @@ -53,7 +53,8 @@ void ATPAssetMigrator::loadEntityServerFile() { auto migrateResources = [=](QUrl migrationURL, QJsonValueRef jsonValue, bool isModelURL) { auto request = - DependencyManager::get<ResourceManager>()->createResourceRequest(this, migrationURL); + DependencyManager::get<ResourceManager>()->createResourceRequest( + this, migrationURL, true, -1, "ATPAssetMigrator::loadEntityServerFile"); if (request) { qCDebug(asset_migrator) << "Requesting" << migrationURL << "for ATP asset migration"; @@ -121,8 +122,8 @@ void ATPAssetMigrator::loadEntityServerFile() { QUrl migrationURL = QUrl(migrationURLString); if (!_ignoredUrls.contains(migrationURL) - && (migrationURL.scheme() == URL_SCHEME_HTTP || migrationURL.scheme() == URL_SCHEME_HTTPS - || migrationURL.scheme() == URL_SCHEME_FILE || migrationURL.scheme() == URL_SCHEME_FTP)) { + && (migrationURL.scheme() == HIFI_URL_SCHEME_HTTP || migrationURL.scheme() == HIFI_URL_SCHEME_HTTPS + || migrationURL.scheme() == HIFI_URL_SCHEME_FILE || migrationURL.scheme() == HIFI_URL_SCHEME_FTP)) { if (_pendingReplacements.contains(migrationURL)) { // we already have a request out for this asset, just store the QJsonValueRef diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 230f8aa64b..53074ac4ba 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -135,7 +135,7 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm:: glm::vec3 palmPosition; glm::quat palmRotation; - bool isTransitingWithAvatar = holdingAvatar->getTransit()->isTransiting(); + bool isTransitingWithAvatar = holdingAvatar->getTransit()->isActive(); if (isTransitingWithAvatar != _isTransitingWithAvatar) { _isTransitingWithAvatar = isTransitingWithAvatar; auto ownerEntity = _ownerEntity.lock(); @@ -424,7 +424,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { if (ownerEntity) { ownerEntity->setDynamicDataDirty(true); ownerEntity->setDynamicDataNeedsTransmit(true); - ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isTransiting()); + ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isActive()); } }); } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 4abd561dc5..6688dc26ee 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -71,14 +71,12 @@ AvatarManager::AvatarManager(QObject* parent) : } }); - const float AVATAR_TRANSIT_TRIGGER_DISTANCE = 1.0f; - const int AVATAR_TRANSIT_FRAME_COUNT = 11; // Based on testing - const int AVATAR_TRANSIT_FRAMES_PER_METER = 1; // Based on testing - _transitConfig._totalFrames = AVATAR_TRANSIT_FRAME_COUNT; - _transitConfig._triggerDistance = AVATAR_TRANSIT_TRIGGER_DISTANCE; + _transitConfig._minTriggerDistance = AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE; + _transitConfig._maxTriggerDistance = AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE; _transitConfig._framesPerMeter = AVATAR_TRANSIT_FRAMES_PER_METER; - _transitConfig._isDistanceBased = true; + _transitConfig._isDistanceBased = AVATAR_TRANSIT_DISTANCE_BASED; + _transitConfig._abortDistance = AVATAR_TRANSIT_ABORT_DISTANCE; } AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) { @@ -126,13 +124,39 @@ void AvatarManager::setSpace(workload::SpacePointer& space ) { _space = space; } +void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) { + switch (status) { + case AvatarTransit::Status::STARTED: + _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("preTransitAnim"); + break; + case AvatarTransit::Status::START_TRANSIT: + _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("transitAnim"); + break; + case AvatarTransit::Status::END_TRANSIT: + _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("postTransitAnim"); + break; + case AvatarTransit::Status::ENDED: + _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("idleAnim"); + break; + case AvatarTransit::Status::PRE_TRANSIT: + break; + case AvatarTransit::Status::POST_TRANSIT: + break; + case AvatarTransit::Status::IDLE: + break; + case AvatarTransit::Status::TRANSITING: + break; + case AvatarTransit::Status::ABORT_TRANSIT: + break; + } +} + void AvatarManager::updateMyAvatar(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); - AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _transitConfig); - bool sendFirstTransitPackage = (status == AvatarTransit::Status::START_TRANSIT); - bool blockTransitData = (status == AvatarTransit::Status::TRANSITING); + AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _myAvatar->getSensorToWorldScale(), _transitConfig); + handleTransitAnimations(status); _myAvatar->update(deltaTime); render::Transaction transaction; @@ -142,18 +166,13 @@ void AvatarManager::updateMyAvatar(float deltaTime) { quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; - - if (sendFirstTransitPackage || (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused && !blockTransitData)) { + if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) { // send head/hand data to the avatar mixer and voxel server - PerformanceTimer perfTimer("send"); - if (sendFirstTransitPackage) { - _myAvatar->overrideNextPackagePositionData(_myAvatar->getTransit()->getEndPosition()); - } + PerformanceTimer perfTimer("send"); _myAvatar->sendAvatarDataPacket(); _lastSendAvatarDataTime = now; _myAvatarSendRate.increment(); } - } @@ -242,7 +261,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { for (auto it = sortedAvatarVector.begin(); it != sortedAvatarVector.end(); ++it) { const SortableAvatar& sortData = *it; const auto avatar = std::static_pointer_cast<OtherAvatar>(sortData.getAvatar()); - + if (!avatar->_isClientAvatar) { + avatar->setIsClientAvatar(true); + } // TODO: to help us scale to more avatars it would be nice to not have to poll this stuff every update if (avatar->getSkeletonModel()->isLoaded()) { // remove the orb if it is there @@ -267,7 +288,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (inView && avatar->hasNewJointData()) { numAvatarsUpdated++; } - auto transitStatus = avatar->_transit.update(deltaTime, avatar->_globalPosition, _transitConfig); + auto transitStatus = avatar->_transit.update(deltaTime, avatar->_serverPosition, _transitConfig); if (avatar->getIsNewAvatar() && (transitStatus == AvatarTransit::Status::START_TRANSIT || transitStatus == AvatarTransit::Status::ABORT_TRANSIT)) { avatar->_transit.reset(); avatar->setIsNewAvatar(false); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 9d5b02d748..c8901065af 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -221,6 +221,7 @@ private: // frequently grabs a read lock on the hash to get a given avatar by ID void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; + void handleTransitAnimations(AvatarTransit::Status status); QVector<AvatarSharedPointer> _avatarsToFadeOut; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 38df7b9b06..36da27fff4 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -26,6 +26,7 @@ #include <AccountManager.h> #include <AddressManager.h> #include <AudioClient.h> +#include <ClientTraitsHandler.h> #include <display-plugins/DisplayPlugin.h> #include <FSTReader.h> #include <GeometryUtil.h> @@ -154,8 +155,8 @@ MyAvatar::MyAvatar(QThread* thread) : }); connect(_skeletonModel.get(), &Model::rigReady, this, [this]() { if (_shouldLoadScripts) { - auto geometry = getSkeletonModel()->getFBXGeometry(); - qApp->loadAvatarScripts(geometry.scripts); + auto hfmModel = getSkeletonModel()->getHFMModel(); + qApp->loadAvatarScripts(hfmModel.scripts); _shouldLoadScripts = false; } // Load and convert old attachments to avatar entities @@ -463,10 +464,74 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { } } +void MyAvatar::updateSitStandState(float newHeightReading, float dt) { + const float STANDING_HEIGHT_MULTIPLE = 1.2f; + const float SITTING_HEIGHT_MULTIPLE = 0.833f; + const float SITTING_TIMEOUT = 4.0f; // 4 seconds + const float STANDING_TIMEOUT = 0.3333f; // 1/3 second + const float SITTING_UPPER_BOUND = 1.52f; + if (!getIsSitStandStateLocked()) { + if (!getIsAway() && qApp->isHMDMode()) { + if (getIsInSittingState()) { + if (newHeightReading > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { + // if we recenter upwards then no longer in sitting state + _sitStandStateTimer += dt; + if (_sitStandStateTimer > STANDING_TIMEOUT) { + _averageUserHeightSensorSpace = newHeightReading; + _tippingPoint = newHeightReading; + setIsInSittingState(false); + } + } else if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { + // if we are mis labelled as sitting but we are standing in the real world this will + // make sure that a real sit is still recognized so we won't be stuck in sitting unable to change state + _sitStandStateTimer += dt; + if (_sitStandStateTimer > SITTING_TIMEOUT) { + _averageUserHeightSensorSpace = newHeightReading; + _tippingPoint = newHeightReading; + // here we stay in sit state but reset the average height + setIsInSittingState(true); + } + } else { + // sanity check if average height greater than 5ft they are not sitting(or get off your dangerous barstool please) + if (_averageUserHeightSensorSpace > SITTING_UPPER_BOUND) { + setIsInSittingState(false); + } else { + // tipping point is average height when sitting. + _tippingPoint = _averageUserHeightSensorSpace; + _sitStandStateTimer = 0.0f; + } + } + } else { + // in the standing state + if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { + _sitStandStateTimer += dt; + if (_sitStandStateTimer > SITTING_TIMEOUT) { + _averageUserHeightSensorSpace = newHeightReading; + _tippingPoint = newHeightReading; + setIsInSittingState(true); + } + } else { + // use the mode height for the tipping point when we are standing. + _tippingPoint = getCurrentStandingHeight(); + _sitStandStateTimer = 0.0f; + } + } + } else { + //if you are away then reset the average and set state to standing. + _averageUserHeightSensorSpace = _userHeight.get(); + _tippingPoint = _userHeight.get(); + setIsInSittingState(false); + } + } +} + void MyAvatar::update(float deltaTime) { // update moving average of HMD facing in xz plane. const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength(); const float PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH = 0.0f; // 100 percent shoulders + const float COSINE_THIRTY_DEGREES = 0.866f; + const float SQUATTY_TIMEOUT = 30.0f; // 30 seconds + const float HEIGHT_FILTER_COEFFICIENT = 0.01f; float tau = deltaTime / HMD_FACING_TIMESCALE; setHipToHandController(computeHandAzimuth()); @@ -493,11 +558,36 @@ void MyAvatar::update(float deltaTime) { _smoothOrientationTimer += deltaTime; } - float newHeightReading = getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y; - int newHeightReadingInCentimeters = glm::floor(newHeightReading * CENTIMETERS_PER_METER); - _recentModeReadings.insert(newHeightReadingInCentimeters); - setCurrentStandingHeight(computeStandingHeightMode(getControllerPoseInAvatarFrame(controller::Action::HEAD))); - setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); + controller::Pose newHeightReading = getControllerPoseInSensorFrame(controller::Action::HEAD); + if (newHeightReading.isValid()) { + int newHeightReadingInCentimeters = glm::floor(newHeightReading.getTranslation().y * CENTIMETERS_PER_METER); + _averageUserHeightSensorSpace = lerp(_averageUserHeightSensorSpace, newHeightReading.getTranslation().y, HEIGHT_FILTER_COEFFICIENT); + _recentModeReadings.insert(newHeightReadingInCentimeters); + setCurrentStandingHeight(computeStandingHeightMode(newHeightReading)); + setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); + } + + // if the spine is straight and the head is below the default position by 5 cm then increment squatty count. + const float SQUAT_THRESHOLD = 0.05f; + glm::vec3 headDefaultPositionAvatarSpace = getAbsoluteDefaultJointTranslationInObjectFrame(getJointIndex("Head")); + glm::quat spine2OrientationAvatarSpace = getAbsoluteJointRotationInObjectFrame(getJointIndex("Spine2")); + glm::vec3 upSpine2 = spine2OrientationAvatarSpace * glm::vec3(0.0f, 1.0f, 0.0f); + if (glm::length(upSpine2) > 0.0f) { + upSpine2 = glm::normalize(upSpine2); + } + float angleSpine2 = glm::dot(upSpine2, glm::vec3(0.0f, 1.0f, 0.0f)); + if (getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPositionAvatarSpace.y - SQUAT_THRESHOLD) && (angleSpine2 > COSINE_THIRTY_DEGREES)) { + _squatTimer += deltaTime; + if (_squatTimer > SQUATTY_TIMEOUT) { + _squatTimer = 0.0f; + _follow._squatDetected = true; + } + } else { + _squatTimer = 0.0f; + } + + // put update sit stand state counts here + updateSitStandState(newHeightReading.getTranslation().y, deltaTime); if (_drawAverageFacingEnabled) { auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD); @@ -970,7 +1060,6 @@ void MyAvatar::updateSensorToWorldMatrix() { updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache); if (hasSensorToWorldScaleChanged) { - setTransitScale(sensorToWorldScale); emit sensorToWorldScaleChanged(sensorToWorldScale); } @@ -2342,10 +2431,10 @@ void MyAvatar::attachmentDataToEntityProperties(const AttachmentData& data, Enti void MyAvatar::initHeadBones() { int neckJointIndex = -1; if (_skeletonModel->isLoaded()) { - neckJointIndex = _skeletonModel->getFBXGeometry().neckJointIndex; + neckJointIndex = _skeletonModel->getHFMModel().neckJointIndex; } if (neckJointIndex == -1) { - neckJointIndex = (_skeletonModel->getFBXGeometry().headJointIndex - 1); + neckJointIndex = (_skeletonModel->getHFMModel().headJointIndex - 1); if (neckJointIndex < 0) { // return if the head is not even there. can't cauterize!! return; @@ -2356,7 +2445,7 @@ void MyAvatar::initHeadBones() { q.push(neckJointIndex); _headBoneSet.insert(neckJointIndex); - // fbxJoints only hold links to parents not children, so we have to do a bit of extra work here. + // hfmJoints only hold links to parents not children, so we have to do a bit of extra work here. while (q.size() > 0) { int jointIndex = q.front(); for (int i = 0; i < _skeletonModel->getJointStateCount(); i++) { @@ -2505,11 +2594,11 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { if (_skeletonModel && _skeletonModel->isLoaded()) { const Rig& rig = _skeletonModel->getRig(); - const FBXGeometry& geometry = _skeletonModel->getFBXGeometry(); + const HFMModel& hfmModel = _skeletonModel->getHFMModel(); for (int i = 0; i < rig.getJointStateCount(); i++) { AnimPose jointPose; rig.getAbsoluteJointPoseInRigFrame(i, jointPose); - const FBXJointShapeInfo& shapeInfo = geometry.joints[i].shapeInfo; + const HFMJointShapeInfo& shapeInfo = hfmModel.joints[i].shapeInfo; const AnimPose pose = rigToWorldPose * jointPose; for (size_t j = 0; j < shapeInfo.debugLines.size() / 2; j++) { glm::vec3 pointA = pose.xformPoint(shapeInfo.debugLines[2 * j]); @@ -3559,12 +3648,9 @@ glm::vec3 MyAvatar::computeCounterBalance() { glm::vec3 counterBalancedCg = (1.0f / DEFAULT_AVATAR_HIPS_MASS) * counterBalancedForHead; // find the height of the hips - const float UPPER_LEG_FRACTION = 0.3333f; glm::vec3 xzDiff((cgHeadMass.position.x - counterBalancedCg.x), 0.0f, (cgHeadMass.position.z - counterBalancedCg.z)); float headMinusHipXz = glm::length(xzDiff); float headHipDefault = glm::length(tposeHead - tposeHips); - float hipFootDefault = tposeHips.y - tposeRightFoot.y; - float sitSquatThreshold = tposeHips.y - (UPPER_LEG_FRACTION * hipFootDefault); float hipHeight = 0.0f; if (headHipDefault > headMinusHipXz) { hipHeight = sqrtf((headHipDefault * headHipDefault) - (headMinusHipXz * headMinusHipXz)); @@ -3576,10 +3662,6 @@ glm::vec3 MyAvatar::computeCounterBalance() { if (counterBalancedCg.y > (tposeHips.y + 0.05f)) { // if the height is higher than default hips, clamp to default hips counterBalancedCg.y = tposeHips.y + 0.05f; - } else if (counterBalancedCg.y < sitSquatThreshold) { - //do a height reset - setResetMode(true); - _follow.activate(FollowHelper::Vertical); } return counterBalancedCg; } @@ -3820,6 +3902,18 @@ bool MyAvatar::getIsInWalkingState() const { return _isInWalkingState; } +bool MyAvatar::getIsInSittingState() const { + return _isInSittingState.get(); +} + +MyAvatar::SitStandModelType MyAvatar::getUserRecenterModel() const { + return _userRecenterModel.get(); +} + +bool MyAvatar::getIsSitStandStateLocked() const { + return _lockSitStandState.get(); +} + float MyAvatar::getWalkSpeed() const { return _walkSpeed.get() * _walkSpeedScalar; } @@ -3840,6 +3934,61 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { _isInWalkingState = isWalking; } +void MyAvatar::setIsInSittingState(bool isSitting) { + _sitStandStateTimer = 0.0f; + _squatTimer = 0.0f; + // on reset height we need the count to be more than one in case the user sits and stands up quickly. + _isInSittingState.set(isSitting); + setResetMode(true); + if (isSitting) { + setCenterOfGravityModelEnabled(false); + } else { + setCenterOfGravityModelEnabled(true); + } + setSitStandStateChange(true); +} + +void MyAvatar::setUserRecenterModel(MyAvatar::SitStandModelType modelName) { + + _userRecenterModel.set(modelName); + + switch (modelName) { + case MyAvatar::SitStandModelType::ForceSit: + setHMDLeanRecenterEnabled(true); + setIsInSittingState(true); + setIsSitStandStateLocked(true); + break; + case MyAvatar::SitStandModelType::ForceStand: + setHMDLeanRecenterEnabled(true); + setIsInSittingState(false); + setIsSitStandStateLocked(true); + break; + case MyAvatar::SitStandModelType::Auto: + default: + setHMDLeanRecenterEnabled(true); + setIsInSittingState(false); + setIsSitStandStateLocked(false); + break; + case MyAvatar::SitStandModelType::DisableHMDLean: + setHMDLeanRecenterEnabled(false); + setIsInSittingState(false); + setIsSitStandStateLocked(false); + break; + } +} + +void MyAvatar::setIsSitStandStateLocked(bool isLocked) { + _lockSitStandState.set(isLocked); + _sitStandStateTimer = 0.0f; + _squatTimer = 0.0f; + _averageUserHeightSensorSpace = _userHeight.get(); + _tippingPoint = _userHeight.get(); + if (!isLocked) { + // always start the auto transition mode in standing state. + setIsInSittingState(false); + } +} + void MyAvatar::setWalkSpeed(float value) { _walkSpeed.set(value); } @@ -3856,8 +4005,16 @@ float MyAvatar::getSprintSpeed() const { return _sprintSpeed.get(); } +void MyAvatar::setSitStandStateChange(bool stateChanged) { + _sitStandStateChange = stateChanged; +} + +float MyAvatar::getSitStandStateChange() const { + return _sitStandStateChange; +} + QVector<QString> MyAvatar::getScriptUrls() { - QVector<QString> scripts = _skeletonModel->isLoaded() ? _skeletonModel->getFBXGeometry().scripts : QVector<QString>(); + QVector<QString> scripts = _skeletonModel->isLoaded() ? _skeletonModel->getHFMModel().scripts : QVector<QString>(); return scripts; } @@ -3999,6 +4156,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, // x axis of currentBodyMatrix in world space. glm::vec3 right = glm::normalize(glm::vec3(currentBodyMatrix[0][0], currentBodyMatrix[1][0], currentBodyMatrix[2][0])); glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); + controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); float forwardLeanAmount = glm::dot(forward, offset); float lateralLeanAmount = glm::dot(right, offset); @@ -4007,14 +4165,19 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const float MAX_FORWARD_LEAN = 0.15f; const float MAX_BACKWARD_LEAN = 0.1f; - - if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) { - return true; + bool stepDetected = false; + if (myAvatar.getIsInSittingState()) { + if (!withinBaseOfSupport(currentHeadPose)) { + stepDetected = true; + } + } else if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) { + stepDetected = true; } else if (forwardLeanAmount < 0 && forwardLeanAmount < -MAX_BACKWARD_LEAN) { - return true; + stepDetected = true; + } else { + stepDetected = fabs(lateralLeanAmount) > MAX_LATERAL_LEAN; } - - return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN; + return stepDetected; } bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const { @@ -4023,6 +4186,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND); controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); + controller::Pose currentHeadSensorPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD); bool stepDetected = false; float myScale = myAvatar.getAvatarScale(); @@ -4032,7 +4196,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons } else { if (!withinBaseOfSupport(currentHeadPose) && headAngularVelocityBelowThreshold(currentHeadPose) && - isWithinThresholdHeightMode(currentHeadPose, myAvatar.getCurrentStandingHeight(), myScale) && + isWithinThresholdHeightMode(currentHeadSensorPose, myAvatar.getCurrentStandingHeight(), myScale) && handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) && handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) && headVelocityGreaterThanThreshold(currentHeadPose) && @@ -4048,6 +4212,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); if (!isActive(Horizontal) && + (!isActive(Vertical)) && (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { myAvatar.setResetMode(true); stepDetected = true; @@ -4063,10 +4228,32 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { const float CYLINDER_TOP = 0.1f; const float CYLINDER_BOTTOM = -1.5f; + const float SITTING_BOTTOM = -0.02f; glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); + bool returnValue = false; - return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); + if (myAvatar.getSitStandStateChange()) { + returnValue = true; + } else { + if (myAvatar.getIsInSittingState()) { + if (myAvatar.getIsSitStandStateLocked()) { + returnValue = (offset.y > CYLINDER_TOP); + } + if (offset.y < SITTING_BOTTOM) { + // we recenter more easily when in sitting state. + returnValue = true; + } + } else { + // in the standing state + returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); + // finally check for squats in standing + if (_squatDetected) { + returnValue = true; + } + } + } + return returnValue; } void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, @@ -4087,9 +4274,10 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } } } else { + // center of gravity model is not enabled if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Horizontal); - if (myAvatar.getEnableStepResetRotation()) { + if (myAvatar.getEnableStepResetRotation() && !myAvatar.getIsInSittingState()) { activate(Rotation); myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); } @@ -4097,6 +4285,9 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Vertical); + if (_squatDetected) { + _squatDetected = false; + } } } else { if (!isActive(Rotation) && getForceActivateRotation()) { @@ -4146,7 +4337,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat myAvatar.getCharacterController()->setFollowParameters(followWorldPose, getMaxTimeRemaining()); } -glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix) { +glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix) { if (isActive()) { float dt = myAvatar.getCharacterController()->getFollowTime(); decrementTimeRemaining(dt); @@ -4163,6 +4354,11 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, co glm::mat4 newBodyMat = createMatFromQuatAndPos(sensorAngularDisplacement * glmExtractRotation(currentBodyMatrix), sensorLinearDisplacement + extractTranslation(currentBodyMatrix)); + if (myAvatar.getSitStandStateChange()) { + myAvatar.setSitStandStateChange(false); + deactivate(Vertical); + setTranslation(newBodyMat, extractTranslation(myAvatar.deriveBodyFromHMDSensor())); + } return newBodyMat; } else { return currentBodyMatrix; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 08a7c09fa4..799427530a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -21,7 +21,6 @@ #include <AvatarConstants.h> #include <avatars-renderer/Avatar.h> #include <avatars-renderer/ScriptAvatar.h> -#include <ClientTraitsHandler.h> #include <controllers/Pose.h> #include <controllers/Actions.h> #include <EntityItem.h> @@ -142,6 +141,8 @@ class MyAvatar : public Avatar { * @property {number} walkSpeed * @property {number} walkBackwardSpeed * @property {number} sprintSpeed + * @property {number} isInSittingState + * @property {number} userRecenterModel * * @property {Vec3} skeletonOffset - Can be used to apply a translation offset between the avatar's position and the * registration point of the 3D model. @@ -242,6 +243,9 @@ class MyAvatar : public Avatar { Q_PROPERTY(float walkSpeed READ getWalkSpeed WRITE setWalkSpeed); Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed); Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed); + Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState); + Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel); + Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked); const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_RIGHT_HAND = "right"; @@ -262,6 +266,15 @@ public: }; Q_ENUM(DriveKeys) + enum SitStandModelType { + ForceSit = 0, + ForceStand, + Auto, + DisableHMDLean, + NumSitStandTypes + }; + Q_ENUM(SitStandModelType) + explicit MyAvatar(QThread* thread); virtual ~MyAvatar(); @@ -1121,12 +1134,21 @@ public: void setIsInWalkingState(bool isWalking); bool getIsInWalkingState() const; + void setIsInSittingState(bool isSitting); + bool getIsInSittingState() const; + void setUserRecenterModel(MyAvatar::SitStandModelType modelName); + MyAvatar::SitStandModelType getUserRecenterModel() const; + void setIsSitStandStateLocked(bool isLocked); + bool getIsSitStandStateLocked() const; void setWalkSpeed(float value); float getWalkSpeed() const; void setWalkBackwardSpeed(float value); float getWalkBackwardSpeed() const; void setSprintSpeed(float value); float getSprintSpeed() const; + void setSitStandStateChange(bool stateChanged); + float getSitStandStateChange() const; + void updateSitStandState(float newHeightReading, float dt); QVector<QString> getScriptUrls(); @@ -1532,6 +1554,7 @@ signals: */ void disableHandTouchForIDChanged(const QUuid& entityID, bool disable); + private slots: void leaveDomain(); void updateCollisionCapsuleCache(); @@ -1742,7 +1765,7 @@ private: bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const; void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput); - glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix); + glm::mat4 postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix); bool getForceActivateRotation() const; void setForceActivateRotation(bool val); bool getForceActivateVertical() const; @@ -1751,6 +1774,7 @@ private: void setForceActivateHorizontal(bool val); bool getToggleHipsFollowing() const; void setToggleHipsFollowing(bool followHead); + bool _squatDetected { false }; std::atomic<bool> _forceActivateRotation { false }; std::atomic<bool> _forceActivateVertical { false }; std::atomic<bool> _forceActivateHorizontal { false }; @@ -1820,10 +1844,13 @@ private: std::mutex _pinnedJointsMutex; std::vector<int> _pinnedJoints; + void updateChildCauterization(SpatiallyNestablePointer object, bool cauterize); + // height of user in sensor space, when standing erect. ThreadSafeValueCache<float> _userHeight { DEFAULT_AVATAR_HEIGHT }; - - void updateChildCauterization(SpatiallyNestablePointer object, bool cauterize); + float _averageUserHeightSensorSpace { _userHeight.get() }; + bool _sitStandStateChange { false }; + ThreadSafeValueCache<bool> _lockSitStandState { false }; // max unscaled forward movement speed ThreadSafeValueCache<float> _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; @@ -1831,6 +1858,11 @@ private: ThreadSafeValueCache<float> _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; bool _isInWalkingState { false }; + ThreadSafeValueCache<bool> _isInSittingState { false }; + ThreadSafeValueCache<MyAvatar::SitStandModelType> _userRecenterModel { MyAvatar::SitStandModelType::Auto }; + float _sitStandStateTimer { 0.0f }; + float _squatTimer { 0.0f }; + float _tippingPoint { _userHeight.get() }; // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index c1a49d7a10..2a21f78b21 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -36,6 +36,7 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix()); + // check for pinned hips. auto hipsIndex = myAvatar->getJointIndex("Hips"); if (myAvatar->isJointPinned(hipsIndex)) { @@ -46,7 +47,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { } glm::mat4 hipsMat; - if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState()) && myAvatar->getHMDLeanRecenterEnabled()) { + if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState()) && !(myAvatar->getIsInSittingState()) && myAvatar->getHMDLeanRecenterEnabled()) { // then we use center of gravity model hipsMat = myAvatar->deriveBodyUsingCgModel(); } else { @@ -89,7 +90,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { // Called within Model::simulate call, below. void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { - const FBXGeometry& geometry = getFBXGeometry(); + const HFMModel& hfmModel = getHFMModel(); Head* head = _owningAvatar->getHead(); @@ -199,49 +200,38 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { if (avatarHeadPose.isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] & (uint8_t)Rig::ControllerFlags::Enabled)) { bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS); - if (!_prevHipsValid) { - AnimPose hips = computeHipsInSensorFrame(myAvatar, isFlying); - _prevHips = hips; - } - - AnimPose hips = computeHipsInSensorFrame(myAvatar, isFlying); - // timescale in seconds const float TRANS_HORIZ_TIMESCALE = 0.15f; const float TRANS_VERT_TIMESCALE = 0.01f; // We want the vertical component of the hips to follow quickly to prevent spine squash/stretch. const float ROT_TIMESCALE = 0.15f; const float FLY_IDLE_TRANSITION_TIMESCALE = 0.25f; - float transHorizAlpha, transVertAlpha, rotAlpha; if (_flyIdleTimer < 0.0f) { - transHorizAlpha = glm::min(deltaTime / TRANS_HORIZ_TIMESCALE, 1.0f); - transVertAlpha = glm::min(deltaTime / TRANS_VERT_TIMESCALE, 1.0f); - rotAlpha = glm::min(deltaTime / ROT_TIMESCALE, 1.0f); + _smoothHipsHelper.setHorizontalTranslationTimescale(TRANS_HORIZ_TIMESCALE); + _smoothHipsHelper.setVerticalTranslationTimescale(TRANS_VERT_TIMESCALE); + _smoothHipsHelper.setRotationTimescale(ROT_TIMESCALE); } else { - transHorizAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f); - transVertAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f); - rotAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f); + _smoothHipsHelper.setHorizontalTranslationTimescale(FLY_IDLE_TRANSITION_TIMESCALE); + _smoothHipsHelper.setVerticalTranslationTimescale(FLY_IDLE_TRANSITION_TIMESCALE); + _smoothHipsHelper.setRotationTimescale(FLY_IDLE_TRANSITION_TIMESCALE); } - // smootly lerp hips, in sensorframe, with different coeff for horiz and vertical translation. - float hipsY = hips.trans().y; - hips.trans() = lerp(_prevHips.trans(), hips.trans(), transHorizAlpha); - hips.trans().y = lerp(_prevHips.trans().y, hipsY, transVertAlpha); - hips.rot() = safeLerp(_prevHips.rot(), hips.rot(), rotAlpha); - - _prevHips = hips; - _prevHipsValid = true; + AnimPose sensorHips = computeHipsInSensorFrame(myAvatar, isFlying); + if (!_prevIsEstimatingHips) { + _smoothHipsHelper.teleport(sensorHips); + } + sensorHips = _smoothHipsHelper.update(sensorHips, deltaTime); glm::mat4 invRigMat = glm::inverse(myAvatar->getTransform().getMatrix() * Matrices::Y_180); AnimPose sensorToRigPose(invRigMat * myAvatar->getSensorToWorldMatrix()); - params.primaryControllerPoses[Rig::PrimaryControllerType_Hips] = sensorToRigPose * hips; + params.primaryControllerPoses[Rig::PrimaryControllerType_Hips] = sensorToRigPose * sensorHips; params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; // set spine2 if we have hand controllers if (myAvatar->getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid() && - myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && - !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { + myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && + !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { AnimPose currentSpine2Pose; AnimPose currentHeadPose; @@ -250,6 +240,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { bool headExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Head"), currentHeadPose); bool hipsExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Hips"), currentHipsPose); if (spine2Exists && headExists && hipsExists) { + AnimPose rigSpaceYaw(myAvatar->getSpine2RotationRigSpace()); glm::vec3 u, v, w; glm::vec3 fwd = rigSpaceYaw.rot() * glm::vec3(0.0f, 0.0f, 1.0f); @@ -267,8 +258,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } } + _prevIsEstimatingHips = true; } else { - _prevHipsValid = false; + _prevIsEstimatingHips = false; } params.isTalking = head->getTimeWithoutTalking() <= 1.5f; @@ -276,19 +268,19 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { // pass detailed torso k-dops to rig. int hipsJoint = _rig.indexOfJoint("Hips"); if (hipsJoint >= 0) { - params.hipsShapeInfo = geometry.joints[hipsJoint].shapeInfo; + params.hipsShapeInfo = hfmModel.joints[hipsJoint].shapeInfo; } int spineJoint = _rig.indexOfJoint("Spine"); if (spineJoint >= 0) { - params.spineShapeInfo = geometry.joints[spineJoint].shapeInfo; + params.spineShapeInfo = hfmModel.joints[spineJoint].shapeInfo; } int spine1Joint = _rig.indexOfJoint("Spine1"); if (spine1Joint >= 0) { - params.spine1ShapeInfo = geometry.joints[spine1Joint].shapeInfo; + params.spine1ShapeInfo = hfmModel.joints[spine1Joint].shapeInfo; } int spine2Joint = _rig.indexOfJoint("Spine2"); if (spine2Joint >= 0) { - params.spine2ShapeInfo = geometry.joints[spine2Joint].shapeInfo; + params.spine2ShapeInfo = hfmModel.joints[spine2Joint].shapeInfo; } _rig.updateFromControllerParameters(params, deltaTime); @@ -298,7 +290,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { auto velocity = myAvatar->getLocalVelocity() / myAvatar->getSensorToWorldScale(); auto position = myAvatar->getLocalPosition(); auto orientation = myAvatar->getLocalOrientation(); - _rig.computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState); + _rig.computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState, myAvatar->getSensorToWorldScale()); // evaluate AnimGraph animation and update jointStates. Model::updateRig(deltaTime, parentTransform); @@ -308,8 +300,8 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { eyeParams.eyeSaccade = head->getSaccade(); eyeParams.modelRotation = getRotation(); eyeParams.modelTranslation = getTranslation(); - eyeParams.leftEyeJointIndex = geometry.leftEyeJointIndex; - eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex; + eyeParams.leftEyeJointIndex = hfmModel.leftEyeJointIndex; + eyeParams.rightEyeJointIndex = hfmModel.rightEyeJointIndex; _rig.updateFromEyeParameters(eyeParams); diff --git a/interface/src/avatar/MySkeletonModel.h b/interface/src/avatar/MySkeletonModel.h index ebef9796a4..9a3559ddf7 100644 --- a/interface/src/avatar/MySkeletonModel.h +++ b/interface/src/avatar/MySkeletonModel.h @@ -10,6 +10,7 @@ #define hifi_MySkeletonModel_h #include <avatars-renderer/SkeletonModel.h> +#include <AnimUtil.h> #include "MyAvatar.h" /// A skeleton loaded from a model. @@ -26,11 +27,12 @@ public: private: void updateFingers(); - AnimPose _prevHips; // sensor frame - bool _prevHipsValid { false }; + CriticallyDampedSpringPoseHelper _smoothHipsHelper; // sensor frame bool _prevIsFlying { false }; float _flyIdleTimer { 0.0f }; + float _prevIsEstimatingHips { false }; + std::map<int, int> _jointRotationFrameOffsetMap; }; diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 67303f2a9b..3512677650 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -219,7 +219,11 @@ QString transactionString(const QJsonObject& valueObject) { if (!message.isEmpty()) { result += QString("<br>with memo: <i>\"%1\"</i>").arg(message); } - } else if (sentMoney <= 0 && receivedMoney <= 0 && (sentCerts > 0 || receivedCerts > 0) && !KNOWN_USERS.contains(valueObject["sender_name"].toString())) { + } else if (sentMoney <= 0 && receivedMoney <= 0 && + (sentCerts > 0 || receivedCerts > 0) && + !KNOWN_USERS.contains(valueObject["sender_name"].toString()) && + !KNOWN_USERS.contains(valueObject["recipient_name"].toString()) + ) { // this is a non-HFC asset transfer. if (sentCerts > 0) { QString recipient = userLink(valueObject["recipient_name"].toString(), valueObject["place_name"].toString()); @@ -240,7 +244,6 @@ QString transactionString(const QJsonObject& valueObject) { return result; } -static const QString MARKETPLACE_ITEMS_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace/items/"; void Ledger::historySuccess(QNetworkReply* reply) { // here we send a historyResult with some extra stuff in it // Namely, the styled text we'd like to show. The issue is the @@ -451,7 +454,7 @@ void Ledger::alreadyOwned(const QString& marketplaceId) { } } -void Ledger::getAvailableUpdates(const QString& itemId) { +void Ledger::getAvailableUpdates(const QString& itemId, const int& pageNumber, const int& itemsPerPage) { auto wallet = DependencyManager::get<Wallet>(); QString endpoint = "available_updates"; QJsonObject request; @@ -459,6 +462,8 @@ void Ledger::getAvailableUpdates(const QString& itemId) { if (!itemId.isEmpty()) { request["marketplace_item_id"] = itemId; } + request["per_page"] = itemsPerPage; + request["page"] = pageNumber; send(endpoint, "availableUpdatesSuccess", "availableUpdatesFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); } diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 427395ee11..715d6337ad 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -37,7 +37,7 @@ public: void transferAssetToNode(const QString& hfc_key, const QString& nodeID, const QString& certificateID, const int& amount, const QString& optionalMessage); void transferAssetToUsername(const QString& hfc_key, const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage); void alreadyOwned(const QString& marketplaceId); - void getAvailableUpdates(const QString& itemId = ""); + void getAvailableUpdates(const QString& itemId = "", const int& pageNumber = 1, const int& itemsPerPage = 10); void updateItem(const QString& hfc_key, const QString& certificate_id); enum CertificateStatus { diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index aa39fdc1b9..0ef26a62b3 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -315,7 +315,7 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) { return installedAppsFromMarketplace; } -bool QmlCommerce::installApp(const QString& itemHref) { +bool QmlCommerce::installApp(const QString& itemHref, const bool& alsoOpenImmediately) { if (!QDir(_appsPath).exists()) { if (!QDir().mkdir(_appsPath)) { qCDebug(commerce) << "Couldn't make _appsPath directory."; @@ -325,7 +325,8 @@ bool QmlCommerce::installApp(const QString& itemHref) { QUrl appHref(itemHref); - auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(this, appHref); + auto request = + DependencyManager::get<ResourceManager>()->createResourceRequest(this, appHref, true, -1, "QmlCommerce::installApp"); if (!request) { qCDebug(commerce) << "Couldn't create resource request for app."; @@ -357,13 +358,22 @@ bool QmlCommerce::installApp(const QString& itemHref) { QJsonObject appFileJsonObject = appFileJsonDocument.object(); QString scriptUrl = appFileJsonObject["scriptURL"].toString(); - if ((DependencyManager::get<ScriptEngines>()->loadScript(scriptUrl.trimmed())).isNull()) { - qCDebug(commerce) << "Couldn't load script."; - return false; + // Don't try to re-load (install) a script if it's already running + QStringList runningScripts = DependencyManager::get<ScriptEngines>()->getRunningScripts(); + if (!runningScripts.contains(scriptUrl)) { + if ((DependencyManager::get<ScriptEngines>()->loadScript(scriptUrl.trimmed())).isNull()) { + qCDebug(commerce) << "Couldn't load script."; + return false; + } + + QFileInfo appFileInfo(appFile); + emit appInstalled(appFileInfo.baseName()); + } + + if (alsoOpenImmediately) { + QmlCommerce::openApp(itemHref); } - QFileInfo appFileInfo(appFile); - emit appInstalled(appFileInfo.baseName()); return true; }); request->send(); @@ -431,9 +441,9 @@ bool QmlCommerce::openApp(const QString& itemHref) { return true; } -void QmlCommerce::getAvailableUpdates(const QString& itemId) { +void QmlCommerce::getAvailableUpdates(const QString& itemId, const int& pageNumber, const int& itemsPerPage) { auto ledger = DependencyManager::get<Ledger>(); - ledger->getAvailableUpdates(itemId); + ledger->getAvailableUpdates(itemId, pageNumber, itemsPerPage); } void QmlCommerce::updateItem(const QString& certificateId) { diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index bee30e1b62..c5fbdaf4a4 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -88,11 +88,11 @@ protected: Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID); Q_INVOKABLE QString getInstalledApps(const QString& justInstalledAppID = ""); - Q_INVOKABLE bool installApp(const QString& appHref); + Q_INVOKABLE bool installApp(const QString& appHref, const bool& alsoOpenImmediately = false); Q_INVOKABLE bool uninstallApp(const QString& appHref); Q_INVOKABLE bool openApp(const QString& appHref); - Q_INVOKABLE void getAvailableUpdates(const QString& itemId = ""); + Q_INVOKABLE void getAvailableUpdates(const QString& itemId = "", const int& pageNumber = 1, const int& itemsPerPage = 10); Q_INVOKABLE void updateItem(const QString& certificateId); private: diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 5b8417be7c..0e9ad7d79a 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -687,7 +687,7 @@ void Wallet::chooseSecurityImage(const QString& filename) { delete _securityImage; } QString path = PathUtils::resourcesPath(); - path.append("/qml/hifi/commerce/wallet/"); + path.append("/qml/hifi/dialogs/security/"); path.append(filename); // now create a new security image pixmap diff --git a/interface/src/main.cpp b/interface/src/main.cpp index d9396ae4d1..5af0a9371d 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -23,6 +23,7 @@ #include <SandboxUtils.h> #include <SharedUtil.h> #include <NetworkAccessManager.h> +#include <gl/GLHelpers.h> #include "AddressManager.h" #include "Application.h" @@ -40,6 +41,18 @@ extern "C" { #endif int main(int argc, const char* argv[]) { +#ifdef Q_OS_MAC + auto format = getDefaultOpenGLSurfaceFormat(); + // Deal with some weirdness in the chromium context sharing on Mac. + // The primary share context needs to be 3.2, so that the Chromium will + // succeed in it's creation of it's command stub contexts. + format.setVersion(3, 2); + // This appears to resolve the issues with corrupted fonts on OSX. No + // idea why. + qputenv("QT_ENABLE_GLYPH_CACHE_WORKAROUND", "true"); + // https://i.kym-cdn.com/entries/icons/original/000/008/342/ihave.jpg + QSurfaceFormat::setDefaultFormat(format); +#endif setupHifiApplication(BuildInfo::INTERFACE_NAME); QStringList arguments; diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 25927c5b68..2b75946e28 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -131,7 +131,7 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha // should never fall in here when collision model not fully loaded // TODO: assert that all geometries exist and are loaded //assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded()); - const FBXGeometry& collisionGeometry = resource->getFBXGeometry(); + const HFMModel& collisionModel = resource->getHFMModel(); ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); pointCollection.clear(); @@ -139,15 +139,15 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. - foreach (const FBXMesh& mesh, collisionGeometry.meshes) { + foreach (const HFMMesh& mesh, collisionModel.meshes) { // each meshPart is a convex hull - foreach (const FBXMeshPart &meshPart, mesh.parts) { + foreach (const HFMMeshPart &meshPart, mesh.parts) { pointCollection.push_back(QVector<glm::vec3>()); ShapeInfo::PointList& pointsInPart = pointCollection[i]; // run through all the triangles and (uniquely) add each point to the hull uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size(); - // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices % TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader @@ -168,7 +168,7 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha // run through all the quads and (uniquely) add each point to the hull numIndices = (uint32_t)meshPart.quadIndices.size(); - // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices % QUAD_STRIDE == 0); numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXReader @@ -206,7 +206,7 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha // to the visual model and apply them to the collision model (without regard for the // collision model's extents). - glm::vec3 scaleToFit = dimensions / resource->getFBXGeometry().getUnscaledMeshExtents().size(); + glm::vec3 scaleToFit = dimensions / resource->getHFMModel().getUnscaledMeshExtents().size(); // multiply each point by scale for (int32_t i = 0; i < pointCollection.size(); i++) { for (int32_t j = 0; j < pointCollection[i].size(); j++) { @@ -216,11 +216,11 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha } shapeInfo.setParams(type, dimensions, resource->getURL().toString()); } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { - const FBXGeometry& fbxGeometry = resource->getFBXGeometry(); - int numFbxMeshes = fbxGeometry.meshes.size(); + const HFMModel& hfmModel = resource->getHFMModel(); + int numHFMMeshes = hfmModel.meshes.size(); int totalNumVertices = 0; - for (int i = 0; i < numFbxMeshes; i++) { - const FBXMesh& mesh = fbxGeometry.meshes.at(i); + for (int i = 0; i < numHFMMeshes; i++) { + const HFMMesh& mesh = hfmModel.meshes.at(i); totalNumVertices += mesh.vertices.size(); } const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6; @@ -230,7 +230,7 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha return; } - auto& meshes = resource->getFBXGeometry().meshes; + auto& meshes = resource->getHFMModel().meshes; int32_t numMeshes = (int32_t)(meshes.size()); const int MAX_ALLOWED_MESH_COUNT = 1000; @@ -285,12 +285,12 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha if (type == SHAPE_TYPE_STATIC_MESH) { // copy into triangleIndices size_t triangleIndicesCount = 0; - for (const FBXMeshPart& meshPart : mesh.parts) { + for (const HFMMeshPart& meshPart : mesh.parts) { triangleIndicesCount += meshPart.triangleIndices.count(); } triangleIndices.reserve((int)triangleIndicesCount); - for (const FBXMeshPart& meshPart : mesh.parts) { + for (const HFMMeshPart& meshPart : mesh.parts) { const int* indexItr = meshPart.triangleIndices.cbegin(); while (indexItr != meshPart.triangleIndices.cend()) { triangleIndices.push_back(*indexItr); @@ -299,11 +299,11 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha } } else if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { // for each mesh copy unique part indices, separated by special bogus (flag) index values - for (const FBXMeshPart& meshPart : mesh.parts) { + for (const HFMMeshPart& meshPart : mesh.parts) { // collect unique list of indices for this part std::set<int32_t> uniqueIndices; auto numIndices = meshPart.triangleIndices.count(); - // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices% TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 26b5aacac5..6e979d2d91 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -31,6 +31,9 @@ #include <ScriptEngine.h> +static const float WEB_TOUCH_Y_OFFSET = 0.105f; // how far forward (or back with a negative number) to slide stylus in hand +static const glm::vec3 TIP_OFFSET = glm::vec3(0.0f, StylusPick::WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, 0.0f); + unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type, const QVariant& properties) { switch (type) { case PickQuery::PickType::Ray: @@ -137,7 +140,12 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties maxDistance = propMap["maxDistance"].toFloat(); } - return DependencyManager::get<PickManager>()->addPick(PickQuery::Stylus, std::make_shared<StylusPick>(side, filter, maxDistance, enabled)); + glm::vec3 tipOffset = TIP_OFFSET; + if (propMap["tipOffset"].isValid()) { + tipOffset = vec3FromVariant(propMap["tipOffset"]); + } + + return DependencyManager::get<PickManager>()->addPick(PickQuery::Stylus, std::make_shared<StylusPick>(side, filter, maxDistance, enabled, tipOffset)); } // NOTE: Laser pointer still uses scaleWithAvatar. Until scaleWithAvatar is also deprecated for pointers, scaleWithAvatar should not be removed from the pick API. @@ -416,4 +424,4 @@ void PickScriptingInterface::setParentTransform(std::shared_ptr<PickQuery> pick, pick->parentTransform = std::make_shared<PickTransformNode>(pickID); } } -} \ No newline at end of file +} diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index a44d14b4a6..0009536479 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -16,6 +16,11 @@ #include "LaserPointer.h" #include "StylusPointer.h" #include "ParabolaPointer.h" +#include "StylusPick.h" + +static const glm::quat X_ROT_NEG_90{ 0.70710678f, -0.70710678f, 0.0f, 0.0f }; +static const glm::vec3 DEFAULT_POSITION_OFFSET{0.0f, 0.0f, -StylusPick::WEB_STYLUS_LENGTH / 2.0f}; +static const glm::vec3 DEFAULT_MODEL_DIMENSIONS{0.01f, 0.01f, StylusPick::WEB_STYLUS_LENGTH}; void PointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const { DependencyManager::get<PointerManager>()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems)); @@ -50,7 +55,17 @@ unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType& * @typedef {object} Pointers.StylusPointerProperties * @property {boolean} [hover=false] If this pointer should generate hover events. * @property {boolean} [enabled=false] + * @property {Vec3} [tipOffset] The specified offset of the from the joint index. + * @property {Pointers.StylusPointerProperties.model} [model] Data to replace the default model url, positionOffset and rotationOffset. */ + /**jsdoc + * properties defining stylus pick model that can be included to {@link Pointers.StylusPointerProperties} + * @typedef {object} Pointers.StylusPointerProperties.model + * @property {string} [url] url to the model + * @property {Vec3} [dimensions] the dimensions of the model + * @property {Vec3} [positionOffset] the position offset of the model from the stylus tip. + * @property {Vec3} [rotationOffset] the rotation offset of the model from the parent joint index + */ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) const { QVariantMap propertyMap = properties.toMap(); @@ -64,7 +79,28 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) enabled = propertyMap["enabled"].toBool(); } - return DependencyManager::get<PointerManager>()->addPointer(std::make_shared<StylusPointer>(properties, StylusPointer::buildStylusOverlay(propertyMap), hover, enabled)); + glm::vec3 modelPositionOffset = DEFAULT_POSITION_OFFSET; + glm::quat modelRotationOffset = X_ROT_NEG_90; + glm::vec3 modelDimensions = DEFAULT_MODEL_DIMENSIONS; + + if (propertyMap["model"].isValid()) { + QVariantMap modelData = propertyMap["model"].toMap(); + + if (modelData["positionOffset"].isValid()) { + modelPositionOffset = vec3FromVariant(modelData["positionOffset"]); + } + + if (modelData["rotationOffset"].isValid()) { + modelRotationOffset = quatFromVariant(modelData["rotationOffset"]); + } + + if (modelData["dimensions"].isValid()) { + modelDimensions = vec3FromVariant(modelData["dimensions"]); + } + } + + return DependencyManager::get<PointerManager>()->addPointer(std::make_shared<StylusPointer>(properties, StylusPointer::buildStylusOverlay(propertyMap), hover, enabled, modelPositionOffset, + modelRotationOffset, modelDimensions)); } /**jsdoc diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index ad12db4df2..a48d858504 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -118,4 +118,4 @@ glm::vec2 RayPick::projectOntoOverlayXYPlane(const QUuid& overlayID, const glm:: glm::vec2 RayPick::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized) { auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID); return projectOntoXYPlane(worldPos, props.getPosition(), props.getRotation(), props.getDimensions(), props.getRegistrationPoint(), unNormalized); -} \ No newline at end of file +} diff --git a/interface/src/raypick/StylusPick.cpp b/interface/src/raypick/StylusPick.cpp index c495ddd194..0a76180be8 100644 --- a/interface/src/raypick/StylusPick.cpp +++ b/interface/src/raypick/StylusPick.cpp @@ -21,11 +21,7 @@ #include <controllers/UserInputMapper.h> using namespace bilateral; - -// TODO: make these configurable per pick -static const float WEB_STYLUS_LENGTH = 0.2f; -static const float WEB_TOUCH_Y_OFFSET = 0.105f; // how far forward (or back with a negative number) to slide stylus in hand -static const glm::vec3 TIP_OFFSET = glm::vec3(0.0f, WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, 0.0f); +float StylusPick::WEB_STYLUS_LENGTH = 0.2f; struct SideData { QString avatarJoint; @@ -64,8 +60,8 @@ bool StylusPickResult::checkOrFilterAgainstMaxDistance(float maxDistance) { return distance < maxDistance; } -StylusPick::StylusPick(Side side, const PickFilter& filter, float maxDistance, bool enabled) : - Pick(StylusTip(side), filter, maxDistance, enabled) +StylusPick::StylusPick(Side side, const PickFilter& filter, float maxDistance, bool enabled, const glm::vec3& tipOffset) : + Pick(StylusTip(side, tipOffset), filter, maxDistance, enabled) { } @@ -90,7 +86,7 @@ static StylusTip getFingerWorldLocation(Side side) { } // controllerWorldLocation is where the controller would be, in-world, with an added offset -static StylusTip getControllerWorldLocation(Side side) { +static StylusTip getControllerWorldLocation(Side side, const glm::vec3& tipOffset) { static const std::array<controller::Input, 2> INPUTS{ { UserInputMapper::makeStandardInput(SIDES[0].channel), UserInputMapper::makeStandardInput(SIDES[1].channel) } }; const auto sideIndex = index(side); @@ -114,7 +110,7 @@ static StylusTip getControllerWorldLocation(Side side) { // add to the real position so the grab-point is out in front of the hand, a bit result.position += result.orientation * (sideData.grabPointSphereOffset * sensorScaleFactor); // move the stylus forward a bit - result.position += result.orientation * (TIP_OFFSET * sensorScaleFactor); + result.position += result.orientation * (tipOffset * sensorScaleFactor); auto worldControllerPos = avatarPosition + avatarOrientation * pose.translation; // compute tip velocity from hand controller motion, it is more accurate than computing it from previous positions. @@ -131,7 +127,7 @@ StylusTip StylusPick::getMathematicalPick() const { if (qApp->getPreferAvatarFingerOverStylus()) { result = getFingerWorldLocation(_mathPick.side); } else { - result = getControllerWorldLocation(_mathPick.side); + result = getControllerWorldLocation(_mathPick.side, _mathPick.tipOffset); } return result; } @@ -236,4 +232,4 @@ Transform StylusPick::getResultTransform() const { Transform transform; transform.setTranslation(stylusResult->intersection); return transform; -} \ No newline at end of file +} diff --git a/interface/src/raypick/StylusPick.h b/interface/src/raypick/StylusPick.h index cd01df20e9..14821c0ce5 100644 --- a/interface/src/raypick/StylusPick.h +++ b/interface/src/raypick/StylusPick.h @@ -58,7 +58,7 @@ public: class StylusPick : public Pick<StylusTip> { using Side = bilateral::Side; public: - StylusPick(Side side, const PickFilter& filter, float maxDistance, bool enabled); + StylusPick(Side side, const PickFilter& filter, float maxDistance, bool enabled, const glm::vec3& tipOffset); StylusTip getMathematicalPick() const override; PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override; @@ -71,6 +71,8 @@ public: bool isLeftHand() const override { return _mathPick.side == Side::Left; } bool isRightHand() const override { return _mathPick.side == Side::Right; } bool isMouse() const override { return false; } + + static float WEB_STYLUS_LENGTH; }; -#endif // hifi_StylusPick_h \ No newline at end of file +#endif // hifi_StylusPick_h diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 4ba3813c4a..5595c54b71 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -17,9 +17,6 @@ #include "PickScriptingInterface.h" #include <PickManager.h> -// TODO: make these configurable per pointer -static const float WEB_STYLUS_LENGTH = 0.2f; - static const float TABLET_MIN_HOVER_DISTANCE = -0.1f; static const float TABLET_MAX_HOVER_DISTANCE = 0.1f; static const float TABLET_MIN_TOUCH_DISTANCE = -0.1f; @@ -28,9 +25,15 @@ static const float TABLET_MAX_TOUCH_DISTANCE = 0.005f; static const float HOVER_HYSTERESIS = 0.01f; static const float TOUCH_HYSTERESIS = 0.001f; -StylusPointer::StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled) : +static const QString DEFAULT_STYLUS_MODEL_URL = PathUtils::resourcesUrl() + "/meshes/tablet-stylus-fat.fbx"; + +StylusPointer::StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled, + const glm::vec3& modelPositionOffset, const glm::quat& modelRotationOffset, const glm::vec3& modelDimensions) : Pointer(DependencyManager::get<PickScriptingInterface>()->createStylusPick(props), enabled, hover), - _stylusOverlay(stylusOverlay) + _stylusOverlay(stylusOverlay), + _modelPositionOffset(modelPositionOffset), + _modelDimensions(modelDimensions), + _modelRotationOffset(modelRotationOffset) { } @@ -42,9 +45,19 @@ StylusPointer::~StylusPointer() { OverlayID StylusPointer::buildStylusOverlay(const QVariantMap& properties) { QVariantMap overlayProperties; + + QString modelUrl = DEFAULT_STYLUS_MODEL_URL; + + if (properties["model"].isValid()) { + QVariantMap modelData = properties["model"].toMap(); + + if (modelData["url"].isValid()) { + modelUrl = modelData["url"].toString(); + } + } // TODO: make these configurable per pointer overlayProperties["name"] = "stylus"; - overlayProperties["url"] = PathUtils::resourcesUrl() + "/meshes/tablet-stylus-fat.fbx"; + overlayProperties["url"] = modelUrl; overlayProperties["loadPriority"] = 10.0f; overlayProperties["solid"] = true; overlayProperties["visible"] = false; @@ -72,13 +85,12 @@ void StylusPointer::updateVisuals(const PickResultPointer& pickResult) { void StylusPointer::show(const StylusTip& tip) { if (!_stylusOverlay.isNull()) { QVariantMap props; - static const glm::quat X_ROT_NEG_90{ 0.70710678f, -0.70710678f, 0.0f, 0.0f }; - auto modelOrientation = tip.orientation * X_ROT_NEG_90; + auto modelOrientation = tip.orientation * _modelRotationOffset; auto sensorToWorldScale = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale(); - auto modelPositionOffset = modelOrientation * (vec3(0.0f, 0.0f, -WEB_STYLUS_LENGTH / 2.0f) * sensorToWorldScale); + auto modelPositionOffset = modelOrientation * (_modelPositionOffset * sensorToWorldScale); props["position"] = vec3toVariant(tip.position + modelPositionOffset); props["rotation"] = quatToVariant(modelOrientation); - props["dimensions"] = vec3toVariant(sensorToWorldScale * vec3(0.01f, 0.01f, WEB_STYLUS_LENGTH)); + props["dimensions"] = vec3toVariant(sensorToWorldScale * _modelDimensions); props["visible"] = true; qApp->getOverlays().editOverlay(_stylusOverlay, props); } diff --git a/interface/src/raypick/StylusPointer.h b/interface/src/raypick/StylusPointer.h index ff60fd78e5..64e2a38bed 100644 --- a/interface/src/raypick/StylusPointer.h +++ b/interface/src/raypick/StylusPointer.h @@ -21,7 +21,8 @@ class StylusPointer : public Pointer { using Ptr = std::shared_ptr<StylusPointer>; public: - StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled); + StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled, + const glm::vec3& modelPositionOffset, const glm::quat& modelRotationOffset, const glm::vec3& modelDimensions); ~StylusPointer(); void updateVisuals(const PickResultPointer& pickResult) override; @@ -81,6 +82,10 @@ private: bool _showing { true }; + glm::vec3 _modelPositionOffset; + glm::vec3 _modelDimensions; + glm::quat _modelRotationOffset; + }; #endif // hifi_StylusPointer_h diff --git a/interface/src/scripting/ClipboardScriptingInterface.cpp b/interface/src/scripting/ClipboardScriptingInterface.cpp index c2d2b69883..c14f4ea895 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.cpp +++ b/interface/src/scripting/ClipboardScriptingInterface.cpp @@ -46,11 +46,17 @@ bool ClipboardScriptingInterface::exportEntities(const QString& filename, float return retVal; } -bool ClipboardScriptingInterface::importEntities(const QString& filename) { +bool ClipboardScriptingInterface::importEntities( + const QString& filename, + const bool isObservable, + const qint64 callerId +) { bool retVal; BLOCKING_INVOKE_METHOD(qApp, "importEntities", Q_RETURN_ARG(bool, retVal), - Q_ARG(const QString&, filename)); + Q_ARG(const QString&, filename), + Q_ARG(const bool, isObservable), + Q_ARG(const qint64, callerId)); return retVal; } diff --git a/interface/src/scripting/ClipboardScriptingInterface.h b/interface/src/scripting/ClipboardScriptingInterface.h index 32b8c64a7d..60b6ca2e03 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.h +++ b/interface/src/scripting/ClipboardScriptingInterface.h @@ -50,9 +50,11 @@ public: * You can generate a JSON file using {@link Clipboard.exportEntities}. * @function Clipboard.importEntities * @param {string} filename Path and name of file to import. + * @param {boolean} does the ResourceRequestObserver observe this request? + * @param {number} optional internal id of object causing this import. * @returns {boolean} <code>true</code> if the import was successful, otherwise <code>false</code>. */ - Q_INVOKABLE bool importEntities(const QString& filename); + Q_INVOKABLE bool importEntities(const QString& filename, const bool isObservable = true, const qint64 callerId = -1); /**jsdoc * Export the entities specified to a JSON file. diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index ea24d6c793..f2f8d3b8d4 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -119,8 +119,11 @@ void HMDScriptingInterface::toggleShouldShowTablet() { } void HMDScriptingInterface::setShouldShowTablet(bool value) { - _showTablet = value; - _tabletContextualMode = false; + if (_showTablet != value) { + _showTablet = value; + _tabletContextualMode = false; + emit showTabletChanged(value); + } } QScriptValue HMDScriptingInterface::getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine) { diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 2c0a3fe45f..6cc695762b 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -355,6 +355,8 @@ signals: */ bool shouldShowHandControllersChanged(); + void showTabletChanged(bool showTablet); + public: HMDScriptingInterface(); static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine); diff --git a/interface/src/scripting/KeyboardScriptingInterface.cpp b/interface/src/scripting/KeyboardScriptingInterface.cpp new file mode 100644 index 0000000000..b26e1ec378 --- /dev/null +++ b/interface/src/scripting/KeyboardScriptingInterface.cpp @@ -0,0 +1,34 @@ +// +// KeyboardScriptingInterface.cpp +// interface/src/scripting +// +// Created by Dante Ruiz on 2018-08-27. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "KeyboardScriptingInterface.h" +#include "ui/Keyboard.h" + +bool KeyboardScriptingInterface::isRaised() { + return DependencyManager::get<Keyboard>()->isRaised(); +} + +void KeyboardScriptingInterface::setRaised(bool raised) { + DependencyManager::get<Keyboard>()->setRaised(raised); +} + + +bool KeyboardScriptingInterface::isPassword() { + return DependencyManager::get<Keyboard>()->isPassword(); +} + +void KeyboardScriptingInterface::setPassword(bool password) { + DependencyManager::get<Keyboard>()->setPassword(password); +} + +void KeyboardScriptingInterface::loadKeyboardFile(const QString& keyboardFile) { + DependencyManager::get<Keyboard>()->loadKeyboardFile(keyboardFile); +} diff --git a/interface/src/scripting/KeyboardScriptingInterface.h b/interface/src/scripting/KeyboardScriptingInterface.h new file mode 100644 index 0000000000..1ab91ea7c3 --- /dev/null +++ b/interface/src/scripting/KeyboardScriptingInterface.h @@ -0,0 +1,43 @@ +// +// KeyboardScriptingInterface.h +// interface/src/scripting +// +// Created by Dante Ruiz on 2018-08-27. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_KeyboardScriptingInterface_h +#define hifi_KeyboardScriptingInterface_h + +#include <QtCore/QObject> + +#include "DependencyManager.h" + +/**jsdoc + * The Keyboard API provides facilities to use 3D Physical keyboard. + * @namespace Keyboard + * + * @hifi-interface + * @hifi-client-entity + * + * @property {bool} raised - <code>true</code> If the keyboard is visible <code>false</code> otherwise + * @property {bool} password - <code>true</code> Will show * instead of characters in the text display <code>false</code> otherwise + */ +class KeyboardScriptingInterface : public QObject, public Dependency { + Q_OBJECT + Q_PROPERTY(bool raised READ isRaised WRITE setRaised) + Q_PROPERTY(bool password READ isPassword WRITE setPassword) + +public: + Q_INVOKABLE void loadKeyboardFile(const QString& string); +private: + bool isRaised(); + void setRaised(bool raised); + + bool isPassword(); + void setPassword(bool password); +}; +#endif diff --git a/interface/src/scripting/TTSScriptingInterface.cpp b/interface/src/scripting/TTSScriptingInterface.cpp new file mode 100644 index 0000000000..6b1677aecb --- /dev/null +++ b/interface/src/scripting/TTSScriptingInterface.cpp @@ -0,0 +1,163 @@ +// +// TTSScriptingInterface.cpp +// libraries/audio-client/src/scripting +// +// Created by Zach Fox on 2018-10-10. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "TTSScriptingInterface.h" +#include "avatar/AvatarManager.h" + +TTSScriptingInterface::TTSScriptingInterface() { +#ifdef WIN32 + // + // Create text to speech engine + // + HRESULT hr = m_tts.CoCreateInstance(CLSID_SpVoice); + if (FAILED(hr)) { + qDebug() << "Text-to-speech engine creation failed."; + } + + // + // Get token corresponding to default voice + // + hr = SpGetDefaultTokenFromCategoryId(SPCAT_VOICES, &m_voiceToken, FALSE); + if (FAILED(hr)) { + qDebug() << "Can't get default voice token."; + } + + // + // Set default voice + // + hr = m_tts->SetVoice(m_voiceToken); + if (FAILED(hr)) { + qDebug() << "Can't set default voice."; + } + + _lastSoundAudioInjectorUpdateTimer.setSingleShot(true); + connect(&_lastSoundAudioInjectorUpdateTimer, &QTimer::timeout, this, &TTSScriptingInterface::updateLastSoundAudioInjector); +#endif +} + +TTSScriptingInterface::~TTSScriptingInterface() { +} + +#ifdef WIN32 +class ReleaseOnExit { +public: + ReleaseOnExit(IUnknown* p) : m_p(p) {} + ~ReleaseOnExit() { + if (m_p) { + m_p->Release(); + } + } + +private: + IUnknown* m_p; +}; +#endif + +const int INJECTOR_INTERVAL_MS = 100; +void TTSScriptingInterface::updateLastSoundAudioInjector() { + if (_lastSoundAudioInjector) { + AudioInjectorOptions options; + options.position = DependencyManager::get<AvatarManager>()->getMyAvatarPosition(); + _lastSoundAudioInjector->setOptions(options); + _lastSoundAudioInjectorUpdateTimer.start(INJECTOR_INTERVAL_MS); + } +} + +void TTSScriptingInterface::speakText(const QString& textToSpeak) { +#ifdef WIN32 + WAVEFORMATEX fmt; + fmt.wFormatTag = WAVE_FORMAT_PCM; + fmt.nSamplesPerSec = AudioConstants::SAMPLE_RATE; + fmt.wBitsPerSample = 16; + fmt.nChannels = 1; + fmt.nBlockAlign = fmt.nChannels * fmt.wBitsPerSample / 8; + fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign; + fmt.cbSize = 0; + + IStream* pStream = NULL; + + ISpStream* pSpStream = nullptr; + HRESULT hr = CoCreateInstance(CLSID_SpStream, nullptr, CLSCTX_ALL, __uuidof(ISpStream), (void**)&pSpStream); + if (FAILED(hr)) { + qDebug() << "CoCreateInstance failed."; + } + ReleaseOnExit rSpStream(pSpStream); + + pStream = SHCreateMemStream(NULL, 0); + if (nullptr == pStream) { + qDebug() << "SHCreateMemStream failed."; + } + + hr = pSpStream->SetBaseStream(pStream, SPDFID_WaveFormatEx, &fmt); + if (FAILED(hr)) { + qDebug() << "Can't set base stream."; + } + + hr = m_tts->SetOutput(pSpStream, true); + if (FAILED(hr)) { + qDebug() << "Can't set output stream."; + } + + ReleaseOnExit rStream(pStream); + + ULONG streamNumber; + hr = m_tts->Speak(reinterpret_cast<LPCWSTR>(textToSpeak.utf16()), SPF_IS_XML | SPF_ASYNC | SPF_PURGEBEFORESPEAK, + &streamNumber); + if (FAILED(hr)) { + qDebug() << "Speak failed."; + } + + m_tts->WaitUntilDone(-1); + + hr = pSpStream->GetBaseStream(&pStream); + if (FAILED(hr)) { + qDebug() << "Couldn't get base stream."; + } + + hr = IStream_Reset(pStream); + if (FAILED(hr)) { + qDebug() << "Couldn't reset stream."; + } + + ULARGE_INTEGER StreamSize; + StreamSize.LowPart = 0; + hr = IStream_Size(pStream, &StreamSize); + + DWORD dwSize = StreamSize.QuadPart; + _lastSoundByteArray.resize(dwSize); + + hr = IStream_Read(pStream, _lastSoundByteArray.data(), dwSize); + if (FAILED(hr)) { + qDebug() << "Couldn't read from stream."; + } + + AudioInjectorOptions options; + options.position = DependencyManager::get<AvatarManager>()->getMyAvatarPosition(); + + if (_lastSoundAudioInjector) { + _lastSoundAudioInjector->stop(); + _lastSoundAudioInjectorUpdateTimer.stop(); + } + + _lastSoundAudioInjector = AudioInjector::playSoundAndDelete(_lastSoundByteArray, options); + + _lastSoundAudioInjectorUpdateTimer.start(INJECTOR_INTERVAL_MS); +#else + qDebug() << "Text-to-Speech isn't currently supported on non-Windows platforms."; +#endif +} + +void TTSScriptingInterface::stopLastSpeech() { + if (_lastSoundAudioInjector) { + _lastSoundAudioInjector->stop(); + _lastSoundAudioInjector = NULL; + } +} diff --git a/interface/src/scripting/TTSScriptingInterface.h b/interface/src/scripting/TTSScriptingInterface.h new file mode 100644 index 0000000000..0f1e723885 --- /dev/null +++ b/interface/src/scripting/TTSScriptingInterface.h @@ -0,0 +1,88 @@ +// TTSScriptingInterface.h +// libraries/audio-client/src/scripting +// +// Created by Zach Fox on 2018-10-10. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_SpeechScriptingInterface_h +#define hifi_SpeechScriptingInterface_h + +#include <QtCore/QObject> +#include <QTimer> +#include <DependencyManager.h> +#ifdef WIN32 +#pragma warning(disable : 4996) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <sapi.h> // SAPI +#include <sphelper.h> // SAPI Helper +#endif +#include <AudioInjector.h> +#include <AudioConstants.h> + +class TTSScriptingInterface : public QObject, public Dependency { + Q_OBJECT + +public: + TTSScriptingInterface(); + ~TTSScriptingInterface(); + + Q_INVOKABLE void speakText(const QString& textToSpeak); + Q_INVOKABLE void stopLastSpeech(); + +private: +#ifdef WIN32 + class CComAutoInit { + public: + // Initializes COM using CoInitialize. + // On failure, signals error using AtlThrow. + CComAutoInit() { + HRESULT hr = ::CoInitialize(NULL); + if (FAILED(hr)) { + ATLTRACE(TEXT("CoInitialize() failed in CComAutoInit constructor (hr=0x%08X).\n"), hr); + AtlThrow(hr); + } + } + + // Initializes COM using CoInitializeEx. + // On failure, signals error using AtlThrow. + explicit CComAutoInit(__in DWORD dwCoInit) { + HRESULT hr = ::CoInitializeEx(NULL, dwCoInit); + if (FAILED(hr)) { + ATLTRACE(TEXT("CoInitializeEx() failed in CComAutoInit constructor (hr=0x%08X).\n"), hr); + AtlThrow(hr); + } + } + + // Uninitializes COM using CoUninitialize. + ~CComAutoInit() { ::CoUninitialize(); } + + // + // Ban copy + // + private: + CComAutoInit(const CComAutoInit&); + }; + + // COM initialization and cleanup (must precede other COM related data members) + CComAutoInit m_comInit; + + // Text to speech engine + CComPtr<ISpVoice> m_tts; + + // Default voice token + CComPtr<ISpObjectToken> m_voiceToken; +#endif + + QByteArray _lastSoundByteArray; + AudioInjectorPointer _lastSoundAudioInjector; + QTimer _lastSoundAudioInjectorUpdateTimer; + void updateLastSoundAudioInjector(); +}; + +#endif // hifi_SpeechScriptingInterface_h diff --git a/interface/src/scripting/WalletScriptingInterface.cpp b/interface/src/scripting/WalletScriptingInterface.cpp index e2158b9fd7..93ee60ba5b 100644 --- a/interface/src/scripting/WalletScriptingInterface.cpp +++ b/interface/src/scripting/WalletScriptingInterface.cpp @@ -10,13 +10,15 @@ // #include "WalletScriptingInterface.h" +#include <SettingHandle.h> CheckoutProxy::CheckoutProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(qmlObject, parent) { Q_ASSERT(QThread::currentThread() == qApp->thread()); } WalletScriptingInterface::WalletScriptingInterface() { - + connect(DependencyManager::get<AccountManager>().data(), + &AccountManager::limitedCommerceChanged, this, &WalletScriptingInterface::limitedCommerceChanged); } void WalletScriptingInterface::refreshWalletStatus() { @@ -42,4 +44,4 @@ void WalletScriptingInterface::proveAvatarEntityOwnershipVerification(const QUui } else { qCDebug(entities) << "Failed to prove ownership of:" << entityID << "is not an avatar entity"; } -} \ No newline at end of file +} diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index 25955ca7a3..36ee021b29 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -15,13 +15,13 @@ #include <QtCore/QObject> #include <DependencyManager.h> -#include "scripting/HMDScriptingInterface.h" #include <ui/TabletScriptingInterface.h> #include <ui/QmlWrapper.h> #include <OffscreenUi.h> #include "Application.h" #include "commerce/Wallet.h" #include "ui/overlays/ContextOverlayInterface.h" +#include <AccountManager.h> class CheckoutProxy : public QmlWrapper { Q_OBJECT @@ -29,7 +29,6 @@ public: CheckoutProxy(QObject* qmlObject, QObject* parent = nullptr); }; - /**jsdoc * @namespace Wallet * @@ -37,28 +36,31 @@ public: * @hifi-client-entity * * @property {number} walletStatus + * @property {bool} limitedCommerce */ class WalletScriptingInterface : public QObject, public Dependency { Q_OBJECT - + SINGLETON_DEPENDENCY Q_PROPERTY(uint walletStatus READ getWalletStatus WRITE setWalletStatus NOTIFY walletStatusChanged) + Q_PROPERTY(bool limitedCommerce READ getLimitedCommerce WRITE setLimitedCommerce NOTIFY limitedCommerceChanged) public: + WalletScriptingInterface(); /**jsdoc - * @function Wallet.refreshWalletStatus + * @function WalletScriptingInterface.refreshWalletStatus */ Q_INVOKABLE void refreshWalletStatus(); /**jsdoc - * @function Wallet.getWalletStatus + * @function WalletScriptingInterface.getWalletStatus * @returns {number} */ Q_INVOKABLE uint getWalletStatus() { return _walletStatus; } /**jsdoc - * @function Wallet.proveAvatarEntityOwnershipVerification + * @function WalletScriptingInterface.proveAvatarEntityOwnershipVerification * @param {Uuid} entityID */ Q_INVOKABLE void proveAvatarEntityOwnershipVerification(const QUuid& entityID); @@ -67,29 +69,38 @@ public: // scripts could cause the Wallet to incorrectly report its status. void setWalletStatus(const uint& status); + bool getLimitedCommerce() { return DependencyManager::get<AccountManager>()->getLimitedCommerce(); } + void setLimitedCommerce(bool isLimited) { DependencyManager::get<AccountManager>()->setLimitedCommerce(isLimited); }; + signals: /**jsdoc - * @function Wallet.walletStatusChanged + * @function WalletScriptingInterface.walletStatusChanged * @returns {Signal} */ void walletStatusChanged(); /**jsdoc - * @function Wallet.walletNotSetup + * @function WalletScriptingInterface.limitedCommerceChanged + * @returns {Signal} + */ + void limitedCommerceChanged(); + + /**jsdoc + * @function WalletScriptingInterface.walletNotSetup * @returns {Signal} */ void walletNotSetup(); /**jsdoc - * @function Wallet.ownershipVerificationSuccess + * @function WalletScriptingInterface.ownershipVerificationSuccess * @param {Uuid} entityID * @returns {Signal} */ void ownershipVerificationSuccess(const QUuid& entityID); /**jsdoc - * @function Wallet.ownershipVerificationFailed + * @function WalletScriptingInterface.ownershipVerificationFailed * @param {Uuid} entityID * @returns {Signal} */ diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 108f20b2dd..2fb40c8c30 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -115,6 +115,7 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) { batch.resetViewTransform(); batch.setResourceTexture(0, _uiTexture); geometryCache->renderUnitQuad(batch, glm::vec4(1), _qmlGeometryId); + batch.setResourceTexture(0, nullptr); } void ApplicationOverlay::renderOverlays(RenderArgs* renderArgs) { diff --git a/interface/src/ui/Keyboard.cpp b/interface/src/ui/Keyboard.cpp new file mode 100644 index 0000000000..773253f85c --- /dev/null +++ b/interface/src/ui/Keyboard.cpp @@ -0,0 +1,882 @@ +// +// Keyboard.cpp +// interface/src/scripting +// +// Created by Dante Ruiz on 2018-08-27. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Keyboard.h" + +#include <utility> + +#include <glm/gtc/quaternion.hpp> +#include <glm/gtx/quaternion.hpp> +#include <QtGui/qevent.h> +#include <QFile> +#include <QTextStream> +#include <QJsonArray> +#include <QJsonDocument> +#include <QJsonParseError> + +#include <PointerEvent.h> +#include <PointerManager.h> +#include <Pick.h> +#include <controllers/UserInputMapper.h> +#include <PathUtils.h> +#include <ResourceManager.h> +#include <SoundCache.h> +#include <AudioInjector.h> +#include <RegisteredMetaTypes.h> +#include <ui/TabletScriptingInterface.h> + +#include "ui/overlays/Overlays.h" +#include "ui/overlays/Overlay.h" +#include "ui/overlays/ModelOverlay.h" +#include "ui/overlays/Cube3DOverlay.h" +#include "ui/overlays/Text3DOverlay.h" +#include "avatar/AvatarManager.h" +#include "avatar/MyAvatar.h" +#include "avatar/AvatarManager.h" +#include "raypick/PickScriptingInterface.h" +#include "scripting/HMDScriptingInterface.h" +#include "scripting/WindowScriptingInterface.h" +#include "scripting/SelectionScriptingInterface.h" +#include "DependencyManager.h" + +#include "raypick/StylusPointer.h" +#include "GLMHelpers.h" +#include "Application.h" + +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 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}; +static const glm::vec3 MALLET_POSITION_OFFSET{0.0f, -MALLET_Y_OFFSET / 2.0f, 0.0f}; +static const glm::vec3 MALLET_TIP_OFFSET{0.0f, MALLET_LENGTH - MALLET_TOUCH_Y_OFFSET, 0.0f}; + + +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_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 MALLET_MODEL_URL = PathUtils::resourcesUrl() + "meshes/drumstick.fbx"; + +static const float PULSE_STRENGTH = 0.6f; +static const float PULSE_DURATION = 3.0f; + +static const int KEY_PRESS_TIMEOUT_MS = 100; +static const int LAYER_SWITCH_TIMEOUT_MS = 200; + +static const QString CHARACTER_STRING = "character"; +static const QString CAPS_STRING = "caps"; +static const QString CLOSE_STRING = "close"; +static const QString LAYER_STRING = "layer"; +static const QString BACKSPACE_STRING = "backspace"; +static const QString SPACE_STRING = "space"; +static const QString ENTER_STRING = "enter"; + +static const QString KEY_HOVER_HIGHLIGHT = "keyHoverHiglight"; +static const QString KEY_PRESSED_HIGHLIGHT = "keyPressesHighlight"; +static const QVariantMap KEY_HOVERING_STYLE { + { "isOutlineSmooth", true }, + { "outlineWidth", 3 }, + { "outlineUnoccludedColor", QVariantMap {{"red", 13}, {"green", 152}, {"blue", 186}}}, + { "outlineUnoccludedAlpha", 1.0 }, + { "outlineOccludedAlpha", 0.0 }, + { "fillUnoccludedAlpha", 0.0 }, + { "fillOccludedAlpha", 0.0 } +}; + +static const QVariantMap KEY_PRESSING_STYLE { + { "isOutlineSmooth", true }, + { "outlineWidth", 3 }, + { "fillUnoccludedColor", QVariantMap {{"red", 50}, {"green", 50}, {"blue", 50}}}, + { "outlineUnoccludedAlpha", 0.0 }, + { "outlineOccludedAlpha", 0.0 }, + { "fillUnoccludedAlpha", 0.6 }, + { "fillOccludedAlpha", 0.0 } +}; + +std::pair<glm::vec3, glm::quat> calculateKeyboardPositionAndOrientation() { + auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); + auto hmd = DependencyManager::get<HMDScriptingInterface>(); + + std::pair<glm::vec3, glm::quat> keyboardLocation = std::make_pair(glm::vec3(), glm::quat()); + float sensorToWorldScale = myAvatar->getSensorToWorldScale(); + QUuid tabletID = hmd->getCurrentTabletFrameID(); + if (!tabletID.isNull() && hmd->getShouldShowTablet()) { + Overlays& overlays = qApp->getOverlays(); + auto tabletOverlay = std::dynamic_pointer_cast<Base3DOverlay>(overlays.getOverlay(tabletID)); + if (tabletOverlay) { + auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system"); + bool landscapeMode = tablet->getLandscape(); + glm::vec3 keyboardOffset = landscapeMode ? KEYBOARD_TABLET_LANDSCAPE_OFFSET : KEYBOARD_TABLET_OFFSET; + glm::vec3 keyboardDegreesOffset = landscapeMode ? KEYBOARD_TABLET_LANDSCAPE_DEGREES_OFFSET : KEYBOARD_TABLET_DEGREES_OFFSET; + glm::vec3 tabletWorldPosition = tabletOverlay->getWorldPosition(); + glm::quat tabletWorldOrientation = tabletOverlay->getWorldOrientation(); + glm::vec3 scaledKeyboardTabletOffset = keyboardOffset * sensorToWorldScale; + + keyboardLocation.first = tabletWorldPosition + (tabletWorldOrientation * scaledKeyboardTabletOffset); + keyboardLocation.second = tabletWorldOrientation * glm::quat(glm::radians(keyboardDegreesOffset)); + } + + } else { + glm::vec3 avatarWorldPosition = myAvatar->getWorldPosition(); + glm::quat avatarWorldOrientation = myAvatar->getWorldOrientation(); + glm::vec3 scaledKeyboardAvatarOffset = KEYBOARD_AVATAR_OFFSET * sensorToWorldScale; + + keyboardLocation.first = avatarWorldPosition + (avatarWorldOrientation * scaledKeyboardAvatarOffset); + keyboardLocation.second = avatarWorldOrientation * glm::quat(glm::radians(KEYBOARD_AVATAR_DEGREES_OFFSET)); + } + + return keyboardLocation; +} + +void Key::saveDimensionsAndLocalPosition() { + Overlays& overlays = qApp->getOverlays(); + auto model3DOverlay = std::dynamic_pointer_cast<ModelOverlay>(overlays.getOverlay(_keyID)); + + if (model3DOverlay) { + _originalLocalPosition = model3DOverlay->getLocalPosition(); + _originalDimensions = model3DOverlay->getDimensions(); + _currentLocalPosition = _originalLocalPosition; + } +} + +void Key::scaleKey(float sensorToWorldScale) { + Overlays& overlays = qApp->getOverlays(); + auto model3DOverlay = std::dynamic_pointer_cast<ModelOverlay>(overlays.getOverlay(_keyID)); + + if (model3DOverlay) { + glm::vec3 scaledLocalPosition = _originalLocalPosition * sensorToWorldScale; + glm::vec3 scaledDimensions = _originalDimensions * sensorToWorldScale; + _currentLocalPosition = scaledLocalPosition; + + QVariantMap properties { + { "dimensions", vec3toVariant(scaledDimensions) }, + { "localPosition", vec3toVariant(scaledLocalPosition) } + }; + + overlays.editOverlay(_keyID, properties); + } +} + +void Key::startTimer(int time) { + if (_timer) { + _timer->start(time); + _timer->setSingleShot(true); + } +} + +bool Key::timerFinished() { + if (_timer) { + return (_timer->remainingTime() <= 0); + } + return false; +} + +QString Key::getKeyString(bool toUpper) const { + return toUpper ? _keyString.toUpper() : _keyString; +} + +int Key::getScanCode(bool toUpper) const { + QString character = toUpper ? _keyString.toUpper() : _keyString; + auto utf8Key = character.toUtf8(); + return (int)utf8Key[0]; +} + +Key::Type Key::getKeyTypeFromString(const QString& keyTypeString) { + if (keyTypeString == SPACE_STRING) { + return Type::SPACE; + } else if (keyTypeString == BACKSPACE_STRING) { + return Type::BACKSPACE; + } else if (keyTypeString == LAYER_STRING) { + return Type::LAYER; + } else if (keyTypeString == CAPS_STRING) { + return Type::CAPS; + } else if (keyTypeString == CLOSE_STRING) { + return Type::CLOSE; + } else if (keyTypeString == ENTER_STRING) { + return Type::ENTER; + } + + return Type::CHARACTER; +} + +Keyboard::Keyboard() { + auto pointerManager = DependencyManager::get<PointerManager>(); + auto windowScriptingInterface = DependencyManager::get<WindowScriptingInterface>(); + auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); + 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); + connect(pointerManager.data(), &PointerManager::hoverBeginOverlay, this, &Keyboard::handleHoverBegin, Qt::QueuedConnection); + 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); }); +} + +void Keyboard::registerKeyboardHighlighting() { + auto selection = DependencyManager::get<SelectionScriptingInterface>(); + selection->enableListHighlight(KEY_HOVER_HIGHLIGHT, KEY_HOVERING_STYLE); + selection->enableListToScene(KEY_HOVER_HIGHLIGHT); + selection->enableListHighlight(KEY_PRESSED_HIGHLIGHT, KEY_PRESSING_STYLE); + selection->enableListToScene(KEY_PRESSED_HIGHLIGHT); +} + + +void Keyboard::createKeyboard() { + auto pointerManager = DependencyManager::get<PointerManager>(); + + QVariantMap modelProperties { + { "url", MALLET_MODEL_URL } + }; + + QVariantMap leftStylusProperties { + { "hand", LEFT_HAND_CONTROLLER_INDEX }, + { "filter", PickScriptingInterface::PICK_OVERLAYS() }, + { "model", modelProperties }, + { "tipOffset", vec3toVariant(MALLET_TIP_OFFSET) } + }; + + QVariantMap rightStylusProperties { + { "hand", RIGHT_HAND_CONTROLLER_INDEX }, + { "filter", PickScriptingInterface::PICK_OVERLAYS() }, + { "model", modelProperties }, + { "tipOffset", vec3toVariant(MALLET_TIP_OFFSET) } + }; + + _leftHandStylus = pointerManager->addPointer(std::make_shared<StylusPointer>(leftStylusProperties, StylusPointer::buildStylusOverlay(leftStylusProperties), true, true, + MALLET_POSITION_OFFSET, MALLET_ROTATION_OFFSET, MALLET_MODEL_DIMENSIONS)); + _rightHandStylus = pointerManager->addPointer(std::make_shared<StylusPointer>(rightStylusProperties, StylusPointer::buildStylusOverlay(rightStylusProperties), true, true, + MALLET_POSITION_OFFSET, MALLET_ROTATION_OFFSET, MALLET_MODEL_DIMENSIONS)); + + pointerManager->disablePointer(_rightHandStylus); + pointerManager->disablePointer(_leftHandStylus); + + QString keyboardSvg = PathUtils::resourcesUrl() + "config/keyboard.json"; + loadKeyboardFile(keyboardSvg); + + _keySound = DependencyManager::get<SoundCache>()->getSound(SOUND_FILE); +} + +bool Keyboard::isRaised() const { + return resultWithReadLock<bool>([&] { return _raised; }); +} + +void Keyboard::setRaised(bool raised) { + + bool isRaised; + withReadLock([&] { isRaised = _raised; }); + if (isRaised != raised) { + raiseKeyboardAnchor(raised); + raiseKeyboard(raised); + raised ? enableStylus() : disableStylus(); + withWriteLock([&] { + _raised = raised; + _layerIndex = 0; + _capsEnabled = false; + _typedCharacters.clear(); + }); + + updateTextDisplay(); + } +} + +void Keyboard::updateTextDisplay() { + Overlays& overlays = qApp->getOverlays(); + + auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); + float sensorToWorldScale = myAvatar->getSensorToWorldScale(); + float textWidth = (float) overlays.textSize(_textDisplay.overlayID, _typedCharacters).width(); + + glm::vec3 scaledDimensions = _textDisplay.dimensions; + scaledDimensions *= sensorToWorldScale; + float leftMargin = (scaledDimensions.x / 2); + scaledDimensions.x += textWidth; + + + QVariantMap textDisplayProperties { + { "dimensions", vec3toVariant(scaledDimensions) }, + { "leftMargin", leftMargin }, + { "text", _typedCharacters }, + { "lineHeight", (_textDisplay.lineHeight * sensorToWorldScale) } + }; + + overlays.editOverlay(_textDisplay.overlayID, textDisplayProperties); +} + +void Keyboard::raiseKeyboardAnchor(bool raise) const { + Overlays& overlays = qApp->getOverlays(); + OverlayID anchorOverlayID = _anchor.overlayID; + auto anchorOverlay = std::dynamic_pointer_cast<Cube3DOverlay>(overlays.getOverlay(anchorOverlayID)); + if (anchorOverlay) { + std::pair<glm::vec3, glm::quat> keyboardLocation = calculateKeyboardPositionAndOrientation(); + anchorOverlay->setWorldPosition(keyboardLocation.first); + anchorOverlay->setWorldOrientation(keyboardLocation.second); + anchorOverlay->setVisible(raise); + + QVariantMap textDisplayProperties { + { "visible", raise } + }; + + overlays.editOverlay(_textDisplay.overlayID, textDisplayProperties); + } +} + +void Keyboard::scaleKeyboard(float sensorToWorldScale) { + Overlays& overlays = qApp->getOverlays(); + + glm::vec3 scaledDimensions = _anchor.originalDimensions * sensorToWorldScale; + auto volume3DOverlay = std::dynamic_pointer_cast<Volume3DOverlay>(overlays.getOverlay(_anchor.overlayID)); + + if (volume3DOverlay) { + volume3DOverlay->setDimensions(scaledDimensions); + } + + for (auto& keyboardLayer: _keyboardLayers) { + for (auto iter = keyboardLayer.begin(); iter != keyboardLayer.end(); iter++) { + iter.value().scaleKey(sensorToWorldScale); + } + } + + + + glm::vec3 scaledLocalPosition = _textDisplay.localPosition * sensorToWorldScale; + glm::vec3 textDisplayScaledDimensions = _textDisplay.dimensions * sensorToWorldScale; + + QVariantMap textDisplayProperties { + { "localPosition", vec3toVariant(scaledLocalPosition) }, + { "dimensions", vec3toVariant(textDisplayScaledDimensions) }, + { "lineHeight", (_textDisplay.lineHeight * sensorToWorldScale) } + }; + + overlays.editOverlay(_textDisplay.overlayID, textDisplayProperties); +} + +void Keyboard::startLayerSwitchTimer() { + if (_layerSwitchTimer) { + _layerSwitchTimer->start(LAYER_SWITCH_TIMEOUT_MS); + _layerSwitchTimer->setSingleShot(true); + } +} + +bool Keyboard::isLayerSwitchTimerFinished() { + if (_layerSwitchTimer) { + return (_layerSwitchTimer->remainingTime() <= 0); + } + return false; +} + +void Keyboard::raiseKeyboard(bool raise) const { + if (_keyboardLayers.empty()) { + return; + } + Overlays& overlays = qApp->getOverlays(); + const auto& keyboardLayer = _keyboardLayers[_layerIndex]; + for (auto iter = keyboardLayer.begin(); iter != keyboardLayer.end(); iter++) { + auto base3DOverlay = std::dynamic_pointer_cast<Base3DOverlay>(overlays.getOverlay(iter.key())); + if (base3DOverlay) { + base3DOverlay->setVisible(raise); + } + } +} + +bool Keyboard::isPassword() const { + return resultWithReadLock<bool>([&] { return _password; }); +} + +void Keyboard::setPassword(bool password) { + if (_password != password) { + withWriteLock([&] { + _password = password; + _typedCharacters.clear(); + }); + } + + updateTextDisplay(); +} + +void Keyboard::switchToLayer(int layerIndex) { + if (layerIndex >= 0 && layerIndex < (int)_keyboardLayers.size()) { + Overlays& overlays = qApp->getOverlays(); + + OverlayID currentAnchorOverlayID = _anchor.overlayID; + + glm::vec3 currentOverlayPosition; + glm::quat currentOverlayOrientation; + + auto currentAnchorOverlay = std::dynamic_pointer_cast<Cube3DOverlay>(overlays.getOverlay(currentAnchorOverlayID)); + if (currentAnchorOverlay) { + currentOverlayPosition = currentAnchorOverlay->getWorldPosition(); + currentOverlayOrientation = currentAnchorOverlay->getWorldOrientation(); + } + + raiseKeyboardAnchor(false); + raiseKeyboard(false); + + setLayerIndex(layerIndex); + + raiseKeyboardAnchor(true); + raiseKeyboard(true); + + OverlayID newAnchorOverlayID = _anchor.overlayID; + auto newAnchorOverlay = std::dynamic_pointer_cast<Cube3DOverlay>(overlays.getOverlay(newAnchorOverlayID)); + if (newAnchorOverlay) { + newAnchorOverlay->setWorldPosition(currentOverlayPosition); + newAnchorOverlay->setWorldOrientation(currentOverlayOrientation); + } + + startLayerSwitchTimer(); + } +} + +void Keyboard::handleTriggerBegin(const OverlayID& overlayID, const PointerEvent& event) { + if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished()) { + return; + } + + auto pointerID = event.getID(); + auto buttonType = event.getButton(); + + if ((pointerID != _leftHandStylus && pointerID != _rightHandStylus) || buttonType != PointerEvent::PrimaryButton) { + return; + } + + auto& keyboardLayer = _keyboardLayers[_layerIndex]; + auto search = keyboardLayer.find(overlayID); + + if (search == keyboardLayer.end()) { + return; + } + + Key& key = search.value(); + + if (key.timerFinished()) { + + auto handIndex = (pointerID == _leftHandStylus) ? controller::Hand::LEFT : controller::Hand::RIGHT; + auto userInputMapper = DependencyManager::get<UserInputMapper>(); + userInputMapper->triggerHapticPulse(PULSE_STRENGTH, PULSE_DURATION, handIndex); + + Overlays& overlays = qApp->getOverlays(); + auto base3DOverlay = std::dynamic_pointer_cast<Base3DOverlay>(overlays.getOverlay(overlayID)); + + glm::vec3 keyWorldPosition; + if (base3DOverlay) { + keyWorldPosition = base3DOverlay->getWorldPosition(); + } + + AudioInjectorOptions audioOptions; + audioOptions.localOnly = true; + audioOptions.position = keyWorldPosition; + audioOptions.volume = 0.1f; + + AudioInjector::playSound(_keySound->getByteArray(), audioOptions); + + int scanCode = key.getScanCode(_capsEnabled); + QString keyString = key.getKeyString(_capsEnabled); + + auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system"); + + switch (key.getKeyType()) { + case Key::Type::CLOSE: + setRaised(false); + tablet->unfocus(); + return; + + case Key::Type::CAPS: + _capsEnabled = !_capsEnabled; + switchToLayer(key.getSwitchToLayerIndex()); + return; + case Key::Type::LAYER: + _capsEnabled = false; + switchToLayer(key.getSwitchToLayerIndex()); + return; + case Key::Type::BACKSPACE: + scanCode = Qt::Key_Backspace; + keyString = "\x08"; + _typedCharacters = _typedCharacters.left(_typedCharacters.length() -1); + updateTextDisplay(); + break; + case Key::Type::ENTER: + scanCode = Qt::Key_Return; + keyString = "\x0d"; + _typedCharacters.clear(); + updateTextDisplay(); + break; + case Key::Type::CHARACTER: + if (keyString != " ") { + _typedCharacters.push_back((_password ? "*" : keyString)); + } else { + _typedCharacters.clear(); + } + updateTextDisplay(); + break; + + default: + break; + } + + QKeyEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, scanCode, Qt::NoModifier, keyString); + QKeyEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, scanCode, Qt::NoModifier, keyString); + QCoreApplication::postEvent(QCoreApplication::instance(), pressEvent); + QCoreApplication::postEvent(QCoreApplication::instance(), releaseEvent); + + key.startTimer(KEY_PRESS_TIMEOUT_MS); + auto selection = DependencyManager::get<SelectionScriptingInterface>(); + selection->addToSelectedItemsList(KEY_PRESSED_HIGHLIGHT, "overlay", overlayID); + } +} + +void Keyboard::handleTriggerEnd(const OverlayID& overlayID, const PointerEvent& event) { + if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished()) { + return; + } + + auto pointerID = event.getID(); + if (pointerID != _leftHandStylus && pointerID != _rightHandStylus) { + return; + } + + auto& keyboardLayer = _keyboardLayers[_layerIndex]; + auto search = keyboardLayer.find(overlayID); + + if (search == keyboardLayer.end()) { + return; + } + + Key& key = search.value();; + Overlays& overlays = qApp->getOverlays(); + auto base3DOverlay = std::dynamic_pointer_cast<Base3DOverlay>(overlays.getOverlay(overlayID)); + + if (base3DOverlay) { + base3DOverlay->setLocalPosition(key.getCurrentLocalPosition()); + } + + key.setIsPressed(false); + if (key.timerFinished()) { + key.startTimer(KEY_PRESS_TIMEOUT_MS); + } + + auto selection = DependencyManager::get<SelectionScriptingInterface>(); + selection->removeFromSelectedItemsList(KEY_PRESSED_HIGHLIGHT, "overlay", overlayID); +} + +void Keyboard::handleTriggerContinue(const OverlayID& overlayID, const PointerEvent& event) { + if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished()) { + return; + } + + auto pointerID = event.getID(); + + if (pointerID != _leftHandStylus && pointerID != _rightHandStylus) { + return; + } + + auto& keyboardLayer = _keyboardLayers[_layerIndex]; + auto search = keyboardLayer.find(overlayID); + + if (search == keyboardLayer.end()) { + return; + } + + Key& key = search.value(); + Overlays& overlays = qApp->getOverlays(); + + if (!key.isPressed()) { + auto base3DOverlay = std::dynamic_pointer_cast<Base3DOverlay>(overlays.getOverlay(overlayID)); + + if (base3DOverlay) { + auto pointerManager = DependencyManager::get<PointerManager>(); + auto pickResult = pointerManager->getPrevPickResult(pointerID); + auto stylusPickResult = std::dynamic_pointer_cast<StylusPickResult>(pickResult); + float distance = stylusPickResult->distance; + + static const float PENATRATION_THRESHOLD = 0.025f; + if (distance < PENATRATION_THRESHOLD) { + static const float Z_OFFSET = 0.002f; + glm::quat overlayOrientation = base3DOverlay->getWorldOrientation(); + glm::vec3 overlayYAxis = overlayOrientation * Z_AXIS; + glm::vec3 overlayYOffset = overlayYAxis * Z_OFFSET; + glm::vec3 localPosition = key.getCurrentLocalPosition() - overlayYOffset; + base3DOverlay->setLocalPosition(localPosition); + key.setIsPressed(true); + } + } + } +} + +void Keyboard::handleHoverBegin(const OverlayID& overlayID, const PointerEvent& event) { + if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished()) { + return; + } + + auto pointerID = event.getID(); + + if (pointerID != _leftHandStylus && pointerID != _rightHandStylus) { + return; + } + + auto& keyboardLayer = _keyboardLayers[_layerIndex]; + auto search = keyboardLayer.find(overlayID); + + if (search == keyboardLayer.end()) { + return; + } + + auto selection = DependencyManager::get<SelectionScriptingInterface>(); + selection->addToSelectedItemsList(KEY_HOVER_HIGHLIGHT, "overlay", overlayID); +} + +void Keyboard::handleHoverEnd(const OverlayID& overlayID, const PointerEvent& event) { + if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished()) { + return; + } + + auto pointerID = event.getID(); + + if (pointerID != _leftHandStylus && pointerID != _rightHandStylus) { + return; + } + + auto& keyboardLayer = _keyboardLayers[_layerIndex]; + auto search = keyboardLayer.find(overlayID); + + if (search == keyboardLayer.end()) { + return; + } + + auto selection = DependencyManager::get<SelectionScriptingInterface>(); + selection->removeFromSelectedItemsList(KEY_HOVER_HIGHLIGHT, "overlay", overlayID); +} + +void Keyboard::disableStylus() { + auto pointerManager = DependencyManager::get<PointerManager>(); + pointerManager->setRenderState(_leftHandStylus, "events off"); + pointerManager->disablePointer(_leftHandStylus); + pointerManager->setRenderState(_rightHandStylus, "events off"); + pointerManager->disablePointer(_rightHandStylus); +} + +void Keyboard::setLayerIndex(int layerIndex) { + if (layerIndex >= 0 && layerIndex < (int)_keyboardLayers.size()) { + _layerIndex = layerIndex; + } else { + _layerIndex = 0; + } +} + +void Keyboard::loadKeyboardFile(const QString& keyboardFile) { + if (keyboardFile.isEmpty()) { + return; + } + + auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(this, keyboardFile); + + if (!request) { + qCWarning(interfaceapp) << "Could not create resource for Keyboard file" << keyboardFile; + } + + + connect(request, &ResourceRequest::finished, this, [=]() { + if (request->getResult() != ResourceRequest::Success) { + qCWarning(interfaceapp) << "Keyboard file failed to download"; + return; + } + + clearKeyboardKeys(); + Overlays& overlays = qApp->getOverlays(); + auto requestData = request->getData(); + + QVector<QUuid> includeItems; + + QJsonParseError parseError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(requestData, &parseError); + + if (parseError.error != QJsonParseError::NoError) { + qCWarning(interfaceapp) << "Failed to parse keyboard json file - Error: " << parseError.errorString(); + return; + } + QJsonObject jsonObject = jsonDoc.object(); + QJsonArray layer = jsonObject["Layer1"].toArray(); + QJsonObject anchorObject = jsonObject["anchor"].toObject(); + bool useResourcePath = jsonObject["useResourcesPath"].toBool(); + QString resourcePath = PathUtils::resourcesUrl(); + + + if (anchorObject.isEmpty()) { + qCWarning(interfaceapp) << "No Anchor specified. Not creating keyboard"; + return; + } + + QVariantMap anchorProperties { + { "name", "KeyboardAnchor"}, + { "isSolid", true }, + { "visible", false }, + { "grabbable", true }, + { "ignoreRayIntersection", false }, + { "dimensions", anchorObject["dimensions"].toVariant() }, + { "position", anchorObject["position"].toVariant() }, + { "orientation", anchorObject["rotation"].toVariant() } + }; + + glm::vec3 dimensions = vec3FromVariant(anchorObject["dimensions"].toVariant()); + + Anchor anchor; + anchor.overlayID = overlays.addOverlay("cube", anchorProperties); + anchor.originalDimensions = dimensions; + _anchor = anchor; + + const QJsonArray& keyboardLayers = jsonObject["layers"].toArray(); + int keyboardLayerCount = keyboardLayers.size(); + _keyboardLayers.reserve(keyboardLayerCount); + + + for (int keyboardLayerIndex = 0; keyboardLayerIndex < keyboardLayerCount; keyboardLayerIndex++) { + const QJsonValue& keyboardLayer = keyboardLayers[keyboardLayerIndex].toArray(); + + QHash<OverlayID, Key> keyboardLayerKeys; + foreach (const QJsonValue& keyboardKeyValue, keyboardLayer.toArray()) { + + QVariantMap textureMap; + if (!keyboardKeyValue["texture"].isNull()) { + textureMap = keyboardKeyValue["texture"].toObject().toVariantMap(); + + if (useResourcePath) { + for (auto iter = textureMap.begin(); iter != textureMap.end(); iter++) { + QString modifiedPath = resourcePath + iter.value().toString(); + textureMap[iter.key()] = modifiedPath; + } + } + } + + QString modelUrl = keyboardKeyValue["modelURL"].toString(); + QString url = (useResourcePath ? (resourcePath + modelUrl) : modelUrl); + + QVariantMap properties { + { "dimensions", keyboardKeyValue["dimensions"].toVariant() }, + { "position", keyboardKeyValue["position"].toVariant() }, + { "visible", false }, + { "isSolid", true }, + { "emissive", true }, + { "parentID", _anchor.overlayID }, + { "url", url }, + { "textures", textureMap }, + { "grabbable", false }, + { "localOrientation", keyboardKeyValue["localOrientation"].toVariant() } + }; + + OverlayID overlayID = overlays.addOverlay("model", properties); + + QString keyType = keyboardKeyValue["type"].toString(); + QString keyString = keyboardKeyValue["key"].toString(); + + Key key; + if (!keyType.isNull()) { + Key::Type type= Key::getKeyTypeFromString(keyType); + key.setKeyType(type); + + if (type == Key::Type::LAYER || type == Key::Type::CAPS) { + int switchToLayer = keyboardKeyValue["switchToLayer"].toInt(); + key.setSwitchToLayerIndex(switchToLayer); + } + } + key.setID(overlayID); + key.setKeyString(keyString); + key.saveDimensionsAndLocalPosition(); + + includeItems.append(key.getID()); + _itemsToIgnore.append(key.getID()); + keyboardLayerKeys.insert(overlayID, key); + } + + _keyboardLayers.push_back(keyboardLayerKeys); + + } + + TextDisplay textDisplay; + QJsonObject displayTextObject = jsonObject["textDisplay"].toObject(); + + QVariantMap displayTextProperties { + { "dimensions", displayTextObject["dimensions"].toVariant() }, + { "localPosition", displayTextObject["localPosition"].toVariant() }, + { "localOrientation", displayTextObject["localOrientation"].toVariant() }, + { "leftMargin", displayTextObject["leftMargin"].toVariant() }, + { "rightMargin", displayTextObject["rightMargin"].toVariant() }, + { "topMargin", displayTextObject["topMargin"].toVariant() }, + { "bottomMargin", displayTextObject["bottomMargin"].toVariant() }, + { "lineHeight", displayTextObject["lineHeight"].toVariant() }, + { "visible", false }, + { "emissive", true }, + { "grabbable", false }, + { "text", ""}, + { "parentID", _anchor.overlayID } + }; + + textDisplay.overlayID = overlays.addOverlay("text3d", displayTextProperties); + textDisplay.localPosition = vec3FromVariant(displayTextObject["localPosition"].toVariant()); + textDisplay.dimensions = vec3FromVariant(displayTextObject["dimensions"].toVariant()); + textDisplay.lineHeight = (float) displayTextObject["lineHeight"].toDouble(); + + _textDisplay = textDisplay; + + _ignoreItemsLock.withWriteLock([&] { + _itemsToIgnore.push_back(_textDisplay.overlayID); + _itemsToIgnore.push_back(_anchor.overlayID); + }); + _layerIndex = 0; + auto pointerManager = DependencyManager::get<PointerManager>(); + pointerManager->setIncludeItems(_leftHandStylus, includeItems); + pointerManager->setIncludeItems(_rightHandStylus, includeItems); + }); + + request->send(); +} + +QVector<OverlayID> Keyboard::getKeysID() { + return _ignoreItemsLock.resultWithReadLock<QVector<OverlayID>>([&] { + return _itemsToIgnore; + }); +} + +void Keyboard::clearKeyboardKeys() { + Overlays& overlays = qApp->getOverlays(); + + for (const auto& keyboardLayer: _keyboardLayers) { + for (auto iter = keyboardLayer.begin(); iter != keyboardLayer.end(); iter++) { + overlays.deleteOverlay(iter.key()); + } + } + + overlays.deleteOverlay(_anchor.overlayID); + overlays.deleteOverlay(_textDisplay.overlayID); + + _keyboardLayers.clear(); + + _ignoreItemsLock.withWriteLock([&] { + _itemsToIgnore.clear(); + }); +} + +void Keyboard::enableStylus() { + auto pointerManager = DependencyManager::get<PointerManager>(); + pointerManager->setRenderState(_leftHandStylus, "events on"); + pointerManager->enablePointer(_leftHandStylus); + pointerManager->setRenderState(_rightHandStylus, "events on"); + pointerManager->enablePointer(_rightHandStylus); + +} diff --git a/interface/src/ui/Keyboard.h b/interface/src/ui/Keyboard.h new file mode 100644 index 0000000000..18db38b2ae --- /dev/null +++ b/interface/src/ui/Keyboard.h @@ -0,0 +1,155 @@ +// +// Keyboard.h +// interface/src/scripting +// +// Created by Dante Ruiz on 2018-08-27. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_Keyboard_h +#define hifi_Keyboard_h + +#include <map> +#include <memory> +#include <vector> + +#include <QtCore/QObject> +#include <QTimer> +#include <QHash> +#include <DependencyManager.h> +#include <Sound.h> +#include <AudioInjector.h> +#include <shared/ReadWriteLockable.h> + +#include "ui/overlays/Overlay.h" + +class PointerEvent; + + +class Key { +public: + Key() = default; + ~Key() = default; + + enum Type { + CHARACTER, + CAPS, + CLOSE, + LAYER, + BACKSPACE, + SPACE, + ENTER + }; + + static Key::Type getKeyTypeFromString(const QString& keyTypeString); + + OverlayID getID() const { return _keyID; } + void setID(OverlayID overlayID) { _keyID = overlayID; } + + void startTimer(int time); + bool timerFinished(); + + void setKeyString(QString keyString) { _keyString = keyString; } + QString getKeyString(bool toUpper) const; + int getScanCode(bool toUpper) const; + + bool isPressed() const { return _pressed; } + void setIsPressed(bool pressed) { _pressed = pressed; } + + void setSwitchToLayerIndex(int layerIndex) { _switchToLayer = layerIndex; } + int getSwitchToLayerIndex() const { return _switchToLayer; } + + Type getKeyType() const { return _type; } + void setKeyType(Type type) { _type = type; } + + glm::vec3 getCurrentLocalPosition() const { return _currentLocalPosition; } + + void saveDimensionsAndLocalPosition(); + + void scaleKey(float sensorToWorldScale); +private: + Type _type { Type::CHARACTER }; + + int _switchToLayer { 0 }; + bool _pressed { false }; + + OverlayID _keyID; + QString _keyString; + + glm::vec3 _originalLocalPosition; + glm::vec3 _originalDimensions; + glm::vec3 _currentLocalPosition; + + std::shared_ptr<QTimer> _timer { std::make_shared<QTimer>() }; +}; + +class Keyboard : public Dependency, public QObject, public ReadWriteLockable { +public: + Keyboard(); + void createKeyboard(); + void registerKeyboardHighlighting(); + bool isRaised() const; + void setRaised(bool raised); + + bool isPassword() const; + void setPassword(bool password); + + void loadKeyboardFile(const QString& keyboardFile); + QVector<OverlayID> getKeysID(); + +public slots: + void handleTriggerBegin(const OverlayID& overlayID, const PointerEvent& event); + void handleTriggerEnd(const OverlayID& overlayID, const PointerEvent& event); + void handleTriggerContinue(const OverlayID& overlayID, const PointerEvent& event); + void handleHoverBegin(const OverlayID& overlayID, const PointerEvent& event); + void handleHoverEnd(const OverlayID& overlayID, const PointerEvent& event); + void scaleKeyboard(float sensorToWorldScale); + +private: + struct Anchor { + OverlayID overlayID; + glm::vec3 originalDimensions; + }; + + struct TextDisplay { + float lineHeight; + OverlayID overlayID; + glm::vec3 localPosition; + glm::vec3 dimensions; + }; + + void raiseKeyboard(bool raise) const; + void raiseKeyboardAnchor(bool raise) const; + + void setLayerIndex(int layerIndex); + void enableStylus(); + void disableStylus(); + void clearKeyboardKeys(); + void switchToLayer(int layerIndex); + void updateTextDisplay(); + + void startLayerSwitchTimer(); + bool isLayerSwitchTimerFinished(); + + bool _raised { false }; + bool _password { false }; + bool _capsEnabled { false }; + int _layerIndex { 0 }; + unsigned int _leftHandStylus { 0 }; + unsigned int _rightHandStylus { 0 }; + SharedSoundPointer _keySound { nullptr }; + std::shared_ptr<QTimer> _layerSwitchTimer { std::make_shared<QTimer>() }; + + QString _typedCharacters; + TextDisplay _textDisplay; + Anchor _anchor; + + mutable ReadWriteLockable _ignoreItemsLock; + QVector<OverlayID> _itemsToIgnore; + std::vector<QHash<OverlayID, Key>> _keyboardLayers; +}; + +#endif diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 5eccef5e9d..d71ad9dc82 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -147,7 +147,6 @@ void setupPreferences() { preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter)); } */ - // Snapshots static const QString SNAPSHOTS { "Snapshots" }; { @@ -259,6 +258,39 @@ void setupPreferences() { auto preference = new CheckPreference(VR_MOVEMENT, "Show room boundaries while teleporting", getter, setter); preferences->addPreference(preference); } + { + auto getter = [myAvatar]()->int { + switch (myAvatar->getUserRecenterModel()) { + case MyAvatar::SitStandModelType::Auto: + default: + return 0; + case MyAvatar::SitStandModelType::ForceSit: + return 1; + case MyAvatar::SitStandModelType::DisableHMDLean: + return 2; + } + }; + auto setter = [myAvatar](int value) { + switch (value) { + case 0: + default: + myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::Auto); + break; + case 1: + myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceSit); + break; + case 2: + myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::DisableHMDLean); + break; + } + }; + auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Auto / Force Sit / Disable Recenter", getter, setter); + QStringList items; + items << "Auto - turns on avatar leaning when standing in real world" << "Seated - disables all avatar leaning while sitting in real world" << "Disabled - allows avatar sitting on the floor [Experimental]"; + preference->setHeading("Avatar leaning behavior"); + preference->setItems(items); + preferences->addPreference(preference); + } { auto getter = [=]()->float { return myAvatar->getUserHeight(); }; auto setter = [=](float value) { myAvatar->setUserHeight(value); }; diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 14c1f1a15a..8599e05332 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -290,6 +290,7 @@ void Base3DOverlay::locationChanged(bool tellPhysics) { notifyRenderVariableChange(); } +// FIXME: Overlays shouldn't be deleted when their parents are void Base3DOverlay::parentDeleted() { qApp->getOverlays().deleteOverlay(getOverlayID()); } diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 6f6092a42e..6cc5182b56 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -59,8 +59,6 @@ public: void setIsGrabbable(bool value) { _isGrabbable = value; } virtual void setIsVisibleInSecondaryCamera(bool value) { _isVisibleInSecondaryCamera = value; } - virtual AABox getBounds() const override = 0; - void update(float deltatime) override; void notifyRenderVariableChange() const; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 9f1018f6c7..11c268dd48 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -223,12 +223,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& bool ContextOverlayInterface::contextOverlayFilterPassed(const EntityItemID& entityItemID) { EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags); - Setting::Handle<bool> _settingSwitch{ "commerce", true }; - if (_settingSwitch.get()) { - return (entityProperties.getCertificateID().length() != 0); - } else { - return (entityProperties.getMarketplaceID().length() != 0); - } + return (entityProperties.getCertificateID().length() != 0); } bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event) { diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index 18da15f019..e24e3b3ed8 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -98,8 +98,8 @@ void Image3DOverlay::render(RenderArgs* args) { } float maxSize = glm::max(fromImage.width(), fromImage.height()); - float x = fromImage.width() / (2.0f * maxSize); - float y = -fromImage.height() / (2.0f * maxSize); + float x = _keepAspectRatio ? fromImage.width() / (2.0f * maxSize) : 0.5f; + float y = _keepAspectRatio ? -fromImage.height() / (2.0f * maxSize) : -0.5f; glm::vec2 topLeft(-x, -y); glm::vec2 bottomRight(x, y); @@ -176,6 +176,11 @@ void Image3DOverlay::setProperties(const QVariantMap& properties) { } } + auto keepAspectRatioValue = properties["keepAspectRatio"]; + if (keepAspectRatioValue.isValid()) { + _keepAspectRatio = keepAspectRatioValue.toBool(); + } + auto emissiveValue = properties["emissive"]; if (emissiveValue.isValid()) { _emissive = emissiveValue.toBool(); @@ -225,6 +230,8 @@ void Image3DOverlay::setProperties(const QVariantMap& properties) { * * @property {Vec2} dimensions=1,1 - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>. * + * @property {bool} keepAspectRatio=true - overlays will maintain the aspect ratio when the subImage is applied. + * * @property {boolean} isFacingAvatar - If <code>true</code>, the overlay is rotated to face the user's camera about an axis * parallel to the user's avatar's "up" direction. * @@ -246,6 +253,9 @@ QVariant Image3DOverlay::getProperty(const QString& property) { if (property == "emissive") { return _emissive; } + if (property == "keepAspectRatio") { + return _keepAspectRatio; + } return Billboard3DOverlay::getProperty(property); } diff --git a/interface/src/ui/overlays/Image3DOverlay.h b/interface/src/ui/overlays/Image3DOverlay.h index 1ffa062d45..1000401abb 100644 --- a/interface/src/ui/overlays/Image3DOverlay.h +++ b/interface/src/ui/overlays/Image3DOverlay.h @@ -58,6 +58,7 @@ private: bool _textureIsLoaded { false }; bool _alphaTexture { false }; bool _emissive { false }; + bool _keepAspectRatio { true }; QRect _fromImage; // where from in the image to sample int _geometryId { 0 }; diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index eee8222051..19bdfce2b3 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -60,6 +60,8 @@ ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) : } void ModelOverlay::update(float deltatime) { + Base3DOverlay::update(deltatime); + if (_updateModel) { _updateModel = false; _model->setSnapModelToCenter(true); @@ -446,7 +448,7 @@ QVariant ModelOverlay::getProperty(const QString& property) { if (property == "jointNames") { if (_model && _model->isActive()) { - // note: going through Rig because Model::getJointNames() (which proxies to FBXGeometry) was always empty + // note: going through Rig because Model::getJointNames() (which proxies to HFMModel) was always empty const Rig* rig = &(_model->getRig()); return mapJoints<QStringList, QString>([rig](int jointIndex) -> QString { return rig->nameOfJoint(jointIndex); @@ -574,7 +576,7 @@ void ModelOverlay::animate() { QVector<JointData> jointsData; - const QVector<FBXAnimationFrame>& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy + const QVector<HFMAnimationFrame>& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy int frameCount = frames.size(); if (frameCount <= 0) { return; @@ -605,11 +607,11 @@ void ModelOverlay::animate() { return; } - QStringList animationJointNames = _animation->getGeometry().getJointNames(); - auto& fbxJoints = _animation->getGeometry().joints; + QStringList animationJointNames = _animation->getHFMModel().getJointNames(); + auto& hfmJoints = _animation->getHFMModel().joints; - auto& originalFbxJoints = _model->getFBXGeometry().joints; - auto& originalFbxIndices = _model->getFBXGeometry().jointIndices; + auto& originalHFMJoints = _model->getHFMModel().joints; + auto& originalHFMIndices = _model->getHFMModel().jointIndices; const QVector<glm::quat>& rotations = frames[_lastKnownCurrentFrame].rotations; const QVector<glm::vec3>& translations = frames[_lastKnownCurrentFrame].translations; @@ -626,23 +628,23 @@ void ModelOverlay::animate() { translationMat = glm::translate(translations[index]); } } else if (index < animationJointNames.size()) { - QString jointName = fbxJoints[index].name; + QString jointName = hfmJoints[index].name; - if (originalFbxIndices.contains(jointName)) { + if (originalHFMIndices.contains(jointName)) { // Making sure the joint names exist in the original model the animation is trying to apply onto. If they do, then remap and get its translation. - int remappedIndex = originalFbxIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual. - translationMat = glm::translate(originalFbxJoints[remappedIndex].translation); + int remappedIndex = originalHFMIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual. + translationMat = glm::translate(originalHFMJoints[remappedIndex].translation); } } glm::mat4 rotationMat; if (index < rotations.size()) { - rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * rotations[index] * fbxJoints[index].postRotation); + rotationMat = glm::mat4_cast(hfmJoints[index].preRotation * rotations[index] * hfmJoints[index].postRotation); } else { - rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * fbxJoints[index].postRotation); + rotationMat = glm::mat4_cast(hfmJoints[index].preRotation * hfmJoints[index].postRotation); } - glm::mat4 finalMat = (translationMat * fbxJoints[index].preTransform * - rotationMat * fbxJoints[index].postTransform); + glm::mat4 finalMat = (translationMat * hfmJoints[index].preTransform * + rotationMat * hfmJoints[index].postTransform); auto& jointData = jointsData[j]; jointData.translation = extractTranslation(finalMat); jointData.translationIsDefaultPose = false; @@ -766,4 +768,4 @@ render::ItemKey ModelOverlay::getKey() { builder.withMetaCullGroup(); } return builder.build(); -} \ No newline at end of file +} diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 22cf924727..1bf94adfa0 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -247,7 +247,7 @@ void Overlay::removeMaterial(graphics::MaterialPointer material, const std::stri } render::ItemKey Overlay::getKey() { - auto builder = render::ItemKey::Builder().withTypeShape(); + auto builder = render::ItemKey::Builder().withTypeShape().withTypeMeta(); builder.withViewSpace(); builder.withLayer(render::hifi::LAYER_2D); diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index f4c98bb9e0..7593e12e07 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -35,6 +35,7 @@ #include "RectangleOverlay.h" #include "Text3DOverlay.h" #include "Web3DOverlay.h" +#include "ui/Keyboard.h" #include <QtQuick/QQuickWindow> #include <PointerManager.h> @@ -582,7 +583,7 @@ ParabolaToOverlayIntersectionResult Overlays::findParabolaIntersectionVector(con bool visibleOnly, bool collidableOnly) { float bestDistance = std::numeric_limits<float>::max(); bool bestIsFront = false; - + const QVector<OverlayID> keyboardKeysToDiscard = DependencyManager::get<Keyboard>()->getKeysID(); QMutexLocker locker(&_mutex); ParabolaToOverlayIntersectionResult result; QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysWorld); @@ -592,7 +593,8 @@ ParabolaToOverlayIntersectionResult Overlays::findParabolaIntersectionVector(con auto thisOverlay = std::dynamic_pointer_cast<Base3DOverlay>(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; } @@ -868,8 +870,13 @@ void Overlays::mousePressPointerEvent(const OverlayID& overlayID, const PointerE QMetaObject::invokeMethod(thisOverlay.get(), "handlePointerEvent", Q_ARG(PointerEvent, event)); } - // emit to scripts - emit mousePressOnOverlay(overlayID, event); + + auto keyboard = DependencyManager::get<Keyboard>(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeysID().contains(overlayID)) { + // emit to scripts + emit mousePressOnOverlay(overlayID, event); + } } bool Overlays::mouseDoublePressEvent(QMouseEvent* event) { @@ -902,8 +909,12 @@ void Overlays::hoverEnterPointerEvent(const OverlayID& overlayID, const PointerE QMetaObject::invokeMethod(thisOverlay.get(), "hoverEnterOverlay", Q_ARG(PointerEvent, event)); } - // emit to scripts - emit hoverEnterOverlay(overlayID, event); + auto keyboard = DependencyManager::get<Keyboard>(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeysID().contains(overlayID)) { + // emit to scripts + emit hoverEnterOverlay(overlayID, event); + } } void Overlays::hoverOverPointerEvent(const OverlayID& overlayID, const PointerEvent& event) { @@ -917,8 +928,12 @@ void Overlays::hoverOverPointerEvent(const OverlayID& overlayID, const PointerEv QMetaObject::invokeMethod(thisOverlay.get(), "handlePointerEvent", Q_ARG(PointerEvent, event)); } - // emit to scripts - emit hoverOverOverlay(overlayID, event); + auto keyboard = DependencyManager::get<Keyboard>(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeysID().contains(overlayID)) { + // emit to scripts + emit hoverOverOverlay(overlayID, event); + } } void Overlays::hoverLeavePointerEvent(const OverlayID& overlayID, const PointerEvent& event) { @@ -932,8 +947,12 @@ void Overlays::hoverLeavePointerEvent(const OverlayID& overlayID, const PointerE QMetaObject::invokeMethod(thisOverlay.get(), "hoverLeaveOverlay", Q_ARG(PointerEvent, event)); } - // emit to scripts - emit hoverLeaveOverlay(overlayID, event); + auto keyboard = DependencyManager::get<Keyboard>(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeysID().contains(overlayID)) { + // emit to scripts + emit hoverLeaveOverlay(overlayID, event); + } } bool Overlays::mouseReleaseEvent(QMouseEvent* event) { @@ -962,8 +981,12 @@ void Overlays::mouseReleasePointerEvent(const OverlayID& overlayID, const Pointe QMetaObject::invokeMethod(thisOverlay.get(), "handlePointerEvent", Q_ARG(PointerEvent, event)); } - // emit to scripts - emit mouseReleaseOnOverlay(overlayID, event); + auto keyboard = DependencyManager::get<Keyboard>(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeysID().contains(overlayID)) { + // emit to scripts + emit mouseReleaseOnOverlay(overlayID, event); + } } bool Overlays::mouseMoveEvent(QMouseEvent* event) { @@ -1014,8 +1037,13 @@ void Overlays::mouseMovePointerEvent(const OverlayID& overlayID, const PointerEv QMetaObject::invokeMethod(thisOverlay.get(), "handlePointerEvent", Q_ARG(PointerEvent, event)); } - // emit to scripts - emit mouseMoveOnOverlay(overlayID, event); + auto keyboard = DependencyManager::get<Keyboard>(); + + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeysID().contains(overlayID)) { + // emit to scripts + emit mouseMoveOnOverlay(overlayID, event); + } } QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) { @@ -1034,8 +1062,8 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) { i.next(); OverlayID thisID = i.key(); auto overlay = std::dynamic_pointer_cast<Volume3DOverlay>(i.value()); - // FIXME: this ignores overlays with ignorePickIntersection == true, which seems wrong - if (overlay && overlay->getVisible() && !overlay->getIgnorePickIntersection() && overlay->isLoaded()) { + + if (overlay && overlay->getVisible() && overlay->isLoaded()) { // get AABox in frame of overlay glm::vec3 dimensions = overlay->getDimensions(); glm::vec3 low = dimensions * -0.5f; diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index a307d445c0..0cceb44a36 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -20,11 +20,9 @@ Volume3DOverlay::Volume3DOverlay(const Volume3DOverlay* volume3DOverlay) : } AABox Volume3DOverlay::getBounds() const { - auto extents = Extents{_localBoundingBox}; - extents.rotate(getWorldOrientation()); - extents.shiftBy(getWorldPosition()); - - return AABox(extents); + AABox bounds = _localBoundingBox; + bounds.transform(getTransform()); + return bounds; } void Volume3DOverlay::setDimensions(const glm::vec3& value) { @@ -49,15 +47,7 @@ void Volume3DOverlay::setProperties(const QVariantMap& properties) { glm::vec3 scale = vec3FromVariant(dimensions); // don't allow a zero or negative dimension component to reach the renderTransform const float MIN_DIMENSION = 0.0001f; - if (scale.x < MIN_DIMENSION) { - scale.x = MIN_DIMENSION; - } - if (scale.y < MIN_DIMENSION) { - scale.y = MIN_DIMENSION; - } - if (scale.z < MIN_DIMENSION) { - scale.z = MIN_DIMENSION; - } + scale = glm::max(scale, MIN_DIMENSION); setDimensions(scale); } } diff --git a/interface/src/ui/overlays/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h index e4060ae335..2083f7344a 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.h +++ b/interface/src/ui/overlays/Volume3DOverlay.h @@ -24,7 +24,6 @@ public: virtual AABox getBounds() const override; const glm::vec3& getDimensions() const { return _localBoundingBox.getDimensions(); } - void setDimensions(float value) { setDimensions(glm::vec3(value)); } void setDimensions(const glm::vec3& value); void setProperties(const QVariantMap& properties) override; @@ -37,7 +36,7 @@ public: protected: // Centered local bounding box - AABox _localBoundingBox{ vec3(0.0f), 1.0f }; + AABox _localBoundingBox { vec3(-0.5), 1.0f }; Transform evalRenderTransform() override; }; diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 9d55c91ef3..e7a0c5934e 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -41,6 +41,7 @@ #include "scripting/AssetMappingsScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" +#include "scripting/KeyboardScriptingInterface.h" #include <Preferences.h> #include <AvatarBookmarks.h> #include <ScriptEngines.h> @@ -53,12 +54,14 @@ #include "ui/AvatarInputs.h" #include "avatar/AvatarManager.h" #include "scripting/AccountServicesScriptingInterface.h" +#include "scripting/WalletScriptingInterface.h" #include <plugins/InputConfiguration.h> #include "ui/Snapshot.h" #include "SoundCacheScriptingInterface.h" #include "raypick/PointerScriptingInterface.h" #include <display-plugins/CompositorHelper.h> #include "AboutUtil.h" +#include "ResourceRequestObserver.h" static int MAX_WINDOW_SIZE = 4096; static const float METERS_TO_INCHES = 39.3701f; @@ -269,6 +272,9 @@ void Web3DOverlay::setupQmlSurface(bool isTablet) { _webSurface->getSurfaceContext()->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data()); _webSurface->getSurfaceContext()->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface()); _webSurface->getSurfaceContext()->setContextProperty("HiFiAbout", AboutUtil::getInstance()); + _webSurface->getSurfaceContext()->setContextProperty("WalletScriptingInterface", DependencyManager::get<WalletScriptingInterface>().data()); + _webSurface->getSurfaceContext()->setContextProperty("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().data()); + _webSurface->getSurfaceContext()->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data()); // Override min fps for tablet UI, for silky smooth scrolling setMaxFPS(90); diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index f9195a608b..eeac8fadce 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -101,8 +101,8 @@ void AnimClip::copyFromNetworkAnim() { // build a mapping from animation joint indices to skeleton joint indices. // by matching joints with the same name. - const FBXGeometry& geom = _networkAnim->getGeometry(); - AnimSkeleton animSkeleton(geom); + const HFMModel& hfmModel = _networkAnim->getHFMModel(); + AnimSkeleton animSkeleton(hfmModel); const auto animJointCount = animSkeleton.getNumJoints(); const auto skeletonJointCount = _skeleton->getNumJoints(); std::vector<int> jointMap; @@ -115,12 +115,12 @@ void AnimClip::copyFromNetworkAnim() { jointMap.push_back(skeletonJoint); } - const int frameCount = geom.animationFrames.size(); + const int frameCount = hfmModel.animationFrames.size(); _anim.resize(frameCount); for (int frame = 0; frame < frameCount; frame++) { - const FBXAnimationFrame& fbxAnimFrame = geom.animationFrames[frame]; + const HFMAnimationFrame& hfmAnimFrame = hfmModel.animationFrames[frame]; // init all joints in animation to default pose // this will give us a resonable result for bones in the model skeleton but not in the animation. @@ -132,8 +132,8 @@ void AnimClip::copyFromNetworkAnim() { for (int animJoint = 0; animJoint < animJointCount; animJoint++) { int skeletonJoint = jointMap[animJoint]; - const glm::vec3& fbxAnimTrans = fbxAnimFrame.translations[animJoint]; - const glm::quat& fbxAnimRot = fbxAnimFrame.rotations[animJoint]; + const glm::vec3& hfmAnimTrans = hfmAnimFrame.translations[animJoint]; + const glm::quat& hfmAnimRot = hfmAnimFrame.rotations[animJoint]; // skip joints that are in the animation but not in the skeleton. if (skeletonJoint >= 0 && skeletonJoint < skeletonJointCount) { @@ -146,19 +146,19 @@ void AnimClip::copyFromNetworkAnim() { preRot.scale() = glm::vec3(1.0f); postRot.scale() = glm::vec3(1.0f); - AnimPose rot(glm::vec3(1.0f), fbxAnimRot, glm::vec3()); + AnimPose rot(glm::vec3(1.0f), hfmAnimRot, glm::vec3()); // adjust translation offsets, so large translation animatons on the reference skeleton // will be adjusted when played on a skeleton with short limbs. - const glm::vec3& fbxZeroTrans = geom.animationFrames[0].translations[animJoint]; + const glm::vec3& hfmZeroTrans = hfmModel.animationFrames[0].translations[animJoint]; const AnimPose& relDefaultPose = _skeleton->getRelativeDefaultPose(skeletonJoint); float boneLengthScale = 1.0f; const float EPSILON = 0.0001f; - if (fabsf(glm::length(fbxZeroTrans)) > EPSILON) { - boneLengthScale = glm::length(relDefaultPose.trans()) / glm::length(fbxZeroTrans); + if (fabsf(glm::length(hfmZeroTrans)) > EPSILON) { + boneLengthScale = glm::length(relDefaultPose.trans()) / glm::length(hfmZeroTrans); } - AnimPose trans = AnimPose(glm::vec3(1.0f), glm::quat(), relDefaultPose.trans() + boneLengthScale * (fbxAnimTrans - fbxZeroTrans)); + AnimPose trans = AnimPose(glm::vec3(1.0f), glm::quat(), relDefaultPose.trans() + boneLengthScale * (hfmAnimTrans - hfmZeroTrans)); _anim[frame][skeletonJoint] = trans * preRot * rot * postRot; } diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index eba361fd4c..92f4c01dcc 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -51,6 +51,8 @@ public: bool getMirrorFlag() const { return _mirrorFlag; } void setMirrorFlag(bool mirrorFlag) { _mirrorFlag = mirrorFlag; } + float getFrame() const { return _frame; } + void loadURL(const QString& url); protected: diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 71094cc6e1..a1809f3438 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -24,7 +24,7 @@ #include "AnimUtil.h" static const int MAX_TARGET_MARKERS = 30; -static const float JOINT_CHAIN_INTERP_TIME = 0.25f; +static const float JOINT_CHAIN_INTERP_TIME = 0.5f; static void lookupJointInfo(const AnimInverseKinematics::JointChainInfo& jointChainInfo, int indexA, int indexB, @@ -253,11 +253,25 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< if (numLoops == MAX_IK_LOOPS) { for (size_t i = 0; i < _prevJointChainInfoVec.size(); i++) { if (_prevJointChainInfoVec[i].timer > 0.0f) { + float alpha = (JOINT_CHAIN_INTERP_TIME - _prevJointChainInfoVec[i].timer) / JOINT_CHAIN_INTERP_TIME; + + // ease in expo + alpha = 1.0f - powf(2.0f, -10.0f * alpha); + size_t chainSize = std::min(_prevJointChainInfoVec[i].jointInfoVec.size(), jointChainInfoVec[i].jointInfoVec.size()); - for (size_t j = 0; j < chainSize; j++) { - jointChainInfoVec[i].jointInfoVec[j].rot = safeMix(_prevJointChainInfoVec[i].jointInfoVec[j].rot, jointChainInfoVec[i].jointInfoVec[j].rot, alpha); - jointChainInfoVec[i].jointInfoVec[j].trans = lerp(_prevJointChainInfoVec[i].jointInfoVec[j].trans, jointChainInfoVec[i].jointInfoVec[j].trans, alpha); + + if (jointChainInfoVec[i].target.getType() != IKTarget::Type::Unknown) { + // if we are interping into an enabled target type, i.e. not off, lerp the rot and the trans. + for (size_t j = 0; j < chainSize; j++) { + jointChainInfoVec[i].jointInfoVec[j].rot = safeMix(_prevJointChainInfoVec[i].jointInfoVec[j].rot, jointChainInfoVec[i].jointInfoVec[j].rot, alpha); + jointChainInfoVec[i].jointInfoVec[j].trans = lerp(_prevJointChainInfoVec[i].jointInfoVec[j].trans, jointChainInfoVec[i].jointInfoVec[j].trans, alpha); + } + } else { + // if we are interping into a disabled target type, keep the rot & trans the same, but lerp the weight down to zero. + jointChainInfoVec[i].target.setType((int)_prevJointChainInfoVec[i].target.getType()); + jointChainInfoVec[i].target.setWeight(_prevJointChainInfoVec[i].target.getWeight() * (1.0f - alpha)); + jointChainInfoVec[i].jointInfoVec = _prevJointChainInfoVec[i].jointInfoVec; } } } diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index bed9c590be..73a0891fbe 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -16,17 +16,17 @@ #include "AnimationLogging.h" -AnimSkeleton::AnimSkeleton(const FBXGeometry& fbxGeometry) { +AnimSkeleton::AnimSkeleton(const HFMModel& hfmModel) { // convert to std::vector of joints - std::vector<FBXJoint> joints; - joints.reserve(fbxGeometry.joints.size()); - for (auto& joint : fbxGeometry.joints) { + std::vector<HFMJoint> joints; + joints.reserve(hfmModel.joints.size()); + for (auto& joint : hfmModel.joints) { joints.push_back(joint); } buildSkeletonFromJoints(joints); } -AnimSkeleton::AnimSkeleton(const std::vector<FBXJoint>& joints) { +AnimSkeleton::AnimSkeleton(const std::vector<HFMJoint>& joints) { buildSkeletonFromJoints(joints); } @@ -166,7 +166,7 @@ void AnimSkeleton::mirrorAbsolutePoses(AnimPoseVec& poses) const { } } -void AnimSkeleton::buildSkeletonFromJoints(const std::vector<FBXJoint>& joints) { +void AnimSkeleton::buildSkeletonFromJoints(const std::vector<HFMJoint>& joints) { _joints = joints; _jointsSize = (int)joints.size(); // build a cache of bind poses @@ -177,7 +177,7 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector<FBXJoint>& joints) _relativePreRotationPoses.reserve(_jointsSize); _relativePostRotationPoses.reserve(_jointsSize); - // iterate over FBXJoints and extract the bind pose information. + // iterate over HFMJoints and extract the bind pose information. for (int i = 0; i < _jointsSize; i++) { // build pre and post transforms @@ -240,7 +240,7 @@ void AnimSkeleton::dump(bool verbose) const { qCDebug(animation) << " absDefaultPose =" << getAbsoluteDefaultPose(i); qCDebug(animation) << " relDefaultPose =" << getRelativeDefaultPose(i); if (verbose) { - qCDebug(animation) << " fbxJoint ="; + qCDebug(animation) << " hfmJoint ="; qCDebug(animation) << " isFree =" << _joints[i].isFree; qCDebug(animation) << " freeLineage =" << _joints[i].freeLineage; qCDebug(animation) << " parentIndex =" << _joints[i].parentIndex; diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 2ebf3f4f5d..3a384388d0 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -23,8 +23,8 @@ public: using Pointer = std::shared_ptr<AnimSkeleton>; using ConstPointer = std::shared_ptr<const AnimSkeleton>; - explicit AnimSkeleton(const FBXGeometry& fbxGeometry); - explicit AnimSkeleton(const std::vector<FBXJoint>& joints); + explicit AnimSkeleton(const HFMModel& hfmModel); + explicit AnimSkeleton(const std::vector<HFMJoint>& joints); int nameToJointIndex(const QString& jointName) const; const QString& getJointName(int jointIndex) const; int getNumJoints() const; @@ -64,9 +64,9 @@ public: std::vector<int> lookUpJointIndices(const std::vector<QString>& jointNames) const; protected: - void buildSkeletonFromJoints(const std::vector<FBXJoint>& joints); + void buildSkeletonFromJoints(const std::vector<HFMJoint>& joints); - std::vector<FBXJoint> _joints; + std::vector<HFMJoint> _joints; int _jointsSize { 0 }; AnimPoseVec _relativeDefaultPoses; AnimPoseVec _absoluteDefaultPoses; diff --git a/libraries/animation/src/AnimTwoBoneIK.cpp b/libraries/animation/src/AnimTwoBoneIK.cpp index d68240d176..8960b15940 100644 --- a/libraries/animation/src/AnimTwoBoneIK.cpp +++ b/libraries/animation/src/AnimTwoBoneIK.cpp @@ -201,14 +201,17 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const if (_interpType != InterpType::None) { _interpAlpha += _interpAlphaVel * dt; + // ease in expo + float easeInAlpha = 1.0f - powf(2.0f, -10.0f * _interpAlpha); + if (_interpAlpha < 1.0f) { AnimChain interpChain; if (_interpType == InterpType::SnapshotToUnderPoses) { interpChain = underChain; - interpChain.blend(_snapshotChain, _interpAlpha); + interpChain.blend(_snapshotChain, easeInAlpha); } else if (_interpType == InterpType::SnapshotToSolve) { interpChain = ikChain; - interpChain.blend(_snapshotChain, _interpAlpha); + interpChain.blend(_snapshotChain, easeInAlpha); } // copy interpChain into _poses interpChain.outputRelativePoses(_poses); diff --git a/libraries/animation/src/AnimUtil.h b/libraries/animation/src/AnimUtil.h index 9300f1a7a0..cf190e8dbf 100644 --- a/libraries/animation/src/AnimUtil.h +++ b/libraries/animation/src/AnimUtil.h @@ -38,4 +38,94 @@ AnimPose boneLookAt(const glm::vec3& target, const AnimPose& bone); // and returns a bodyRot that is also z-forward and y-up glm::quat computeBodyFacingFromHead(const glm::quat& headRot, const glm::vec3& up); + +// Uses a approximation of a critically damped spring to smooth full AnimPoses. +// It provides seperate timescales for horizontal, vertical and rotation components. +// The timescale is roughly how much time it will take the spring will reach halfway toward it's target. +class CriticallyDampedSpringPoseHelper { +public: + CriticallyDampedSpringPoseHelper() : _prevPoseValid(false) {} + + void setHorizontalTranslationTimescale(float timescale) { + _horizontalTranslationTimescale = timescale; + } + void setVerticalTranslationTimescale(float timescale) { + _verticalTranslationTimescale = timescale; + } + void setRotationTimescale(float timescale) { + _rotationTimescale = timescale; + } + + AnimPose update(const AnimPose& pose, float deltaTime) { + if (!_prevPoseValid) { + _prevPose = pose; + _prevPoseValid = true; + } + + const float horizontalTranslationAlpha = glm::min(deltaTime / _horizontalTranslationTimescale, 1.0f); + const float verticalTranslationAlpha = glm::min(deltaTime / _verticalTranslationTimescale, 1.0f); + const float rotationAlpha = glm::min(deltaTime / _rotationTimescale, 1.0f); + + const float poseY = pose.trans().y; + AnimPose newPose = _prevPose; + newPose.trans() = lerp(_prevPose.trans(), pose.trans(), horizontalTranslationAlpha); + newPose.trans().y = lerp(_prevPose.trans().y, poseY, verticalTranslationAlpha); + newPose.rot() = safeLerp(_prevPose.rot(), pose.rot(), rotationAlpha); + + _prevPose = newPose; + _prevPoseValid = true; + + return newPose; + } + + void teleport(const AnimPose& pose) { + _prevPoseValid = true; + _prevPose = pose; + } + +protected: + AnimPose _prevPose; + float _horizontalTranslationTimescale { 0.15f }; + float _verticalTranslationTimescale { 0.15f }; + float _rotationTimescale { 0.15f }; + bool _prevPoseValid; +}; + +class SnapshotBlendPoseHelper { +public: + SnapshotBlendPoseHelper() : _snapshotValid(false) {} + + void setBlendDuration(float duration) { + _duration = duration; + } + + void setSnapshot(const AnimPose& pose) { + _snapshotValid = true; + _snapshotPose = pose; + _timer = _duration; + } + + AnimPose update(const AnimPose& targetPose, float deltaTime) { + _timer -= deltaTime; + if (_timer > 0.0f) { + float alpha = (_duration - _timer) / _duration; + + // ease in expo + alpha = 1.0f - powf(2.0f, -10.0f * alpha); + + AnimPose newPose = targetPose; + newPose.blend(_snapshotPose, alpha); + return newPose; + } else { + return targetPose; + } + } + +protected: + AnimPose _snapshotPose; + float _duration { 1.0f }; + float _timer { 0.0f }; + bool _snapshotValid { false }; +}; + #endif diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index 04b7952ddb..06dfc0262a 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -69,14 +69,14 @@ void AnimationReader::run() { if (urlValid) { // Parse the FBX directly from the QNetworkReply - FBXGeometry::Pointer fbxgeo; + HFMModel::Pointer hfmModel; if (_url.path().toLower().endsWith(".fbx")) { - fbxgeo.reset(readFBX(_data, QVariantHash(), _url.path())); + hfmModel.reset(readFBX(_data, QVariantHash(), _url.path())); } else { QString errorStr("usupported format"); emit onError(299, errorStr); } - emit onSuccess(fbxgeo); + emit onSuccess(hfmModel); } else { throw QString("url is invalid"); } @@ -88,7 +88,7 @@ void AnimationReader::run() { } bool Animation::isLoaded() const { - return _loaded && _geometry; + return _loaded && _hfmModel; } QStringList Animation::getJointNames() const { @@ -99,45 +99,45 @@ QStringList Animation::getJointNames() const { return result; } QStringList names; - if (_geometry) { - foreach (const FBXJoint& joint, _geometry->joints) { + if (_hfmModel) { + foreach (const HFMJoint& joint, _hfmModel->joints) { names.append(joint.name); } } return names; } -QVector<FBXAnimationFrame> Animation::getFrames() const { +QVector<HFMAnimationFrame> Animation::getFrames() const { if (QThread::currentThread() != thread()) { - QVector<FBXAnimationFrame> result; + QVector<HFMAnimationFrame> result; BLOCKING_INVOKE_METHOD(const_cast<Animation*>(this), "getFrames", - Q_RETURN_ARG(QVector<FBXAnimationFrame>, result)); + Q_RETURN_ARG(QVector<HFMAnimationFrame>, result)); return result; } - if (_geometry) { - return _geometry->animationFrames; + if (_hfmModel) { + return _hfmModel->animationFrames; } else { - return QVector<FBXAnimationFrame>(); + return QVector<HFMAnimationFrame>(); } } -const QVector<FBXAnimationFrame>& Animation::getFramesReference() const { - return _geometry->animationFrames; +const QVector<HFMAnimationFrame>& Animation::getFramesReference() const { + return _hfmModel->animationFrames; } void Animation::downloadFinished(const QByteArray& data) { // parse the animation/fbx file on a background thread. AnimationReader* animationReader = new AnimationReader(_url, data); - connect(animationReader, SIGNAL(onSuccess(FBXGeometry::Pointer)), SLOT(animationParseSuccess(FBXGeometry::Pointer))); + connect(animationReader, SIGNAL(onSuccess(HFMModel::Pointer)), SLOT(animationParseSuccess(HFMModel::Pointer))); connect(animationReader, SIGNAL(onError(int, QString)), SLOT(animationParseError(int, QString))); QThreadPool::globalInstance()->start(animationReader); } -void Animation::animationParseSuccess(FBXGeometry::Pointer geometry) { +void Animation::animationParseSuccess(HFMModel::Pointer hfmModel) { qCDebug(animation) << "Animation parse success" << _url.toDisplayString(); - _geometry = geometry; + _hfmModel = hfmModel; finishedLoading(true); } diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 483350e2b5..4423e8f18d 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -66,7 +66,7 @@ public: QString getType() const override { return "Animation"; } - const FBXGeometry& getGeometry() const { return *_geometry; } + const HFMModel& getHFMModel() const { return *_hfmModel; } virtual bool isLoaded() const override; @@ -80,20 +80,20 @@ public: * @function AnimationObject.getFrames * @returns {FBXAnimationFrame[]} */ - Q_INVOKABLE QVector<FBXAnimationFrame> getFrames() const; + Q_INVOKABLE QVector<HFMAnimationFrame> getFrames() const; - const QVector<FBXAnimationFrame>& getFramesReference() const; + const QVector<HFMAnimationFrame>& getFramesReference() const; protected: virtual void downloadFinished(const QByteArray& data) override; protected slots: - void animationParseSuccess(FBXGeometry::Pointer geometry); + void animationParseSuccess(HFMModel::Pointer hfmModel); void animationParseError(int error, QString str); private: - FBXGeometry::Pointer _geometry; + HFMModel::Pointer _hfmModel; }; /// Reads geometry in a worker thread. @@ -105,7 +105,7 @@ public: virtual void run() override; signals: - void onSuccess(FBXGeometry::Pointer geometry); + void onSuccess(HFMModel::Pointer hfmModel); void onError(int error, QString str); private: diff --git a/libraries/animation/src/AnimationObject.cpp b/libraries/animation/src/AnimationObject.cpp index 7f0f35b104..bcbf497199 100644 --- a/libraries/animation/src/AnimationObject.cpp +++ b/libraries/animation/src/AnimationObject.cpp @@ -19,17 +19,17 @@ QStringList AnimationObject::getJointNames() const { return qscriptvalue_cast<AnimationPointer>(thisObject())->getJointNames(); } -QVector<FBXAnimationFrame> AnimationObject::getFrames() const { +QVector<HFMAnimationFrame> AnimationObject::getFrames() const { return qscriptvalue_cast<AnimationPointer>(thisObject())->getFrames(); } QVector<glm::quat> AnimationFrameObject::getRotations() const { - return qscriptvalue_cast<FBXAnimationFrame>(thisObject()).rotations; + return qscriptvalue_cast<HFMAnimationFrame>(thisObject()).rotations; } void registerAnimationTypes(QScriptEngine* engine) { - qScriptRegisterSequenceMetaType<QVector<FBXAnimationFrame> >(engine); - engine->setDefaultPrototype(qMetaTypeId<FBXAnimationFrame>(), engine->newQObject( + qScriptRegisterSequenceMetaType<QVector<HFMAnimationFrame> >(engine); + engine->setDefaultPrototype(qMetaTypeId<HFMAnimationFrame>(), engine->newQObject( new AnimationFrameObject(), QScriptEngine::ScriptOwnership)); engine->setDefaultPrototype(qMetaTypeId<AnimationPointer>(), engine->newQObject( new AnimationObject(), QScriptEngine::ScriptOwnership)); diff --git a/libraries/animation/src/AnimationObject.h b/libraries/animation/src/AnimationObject.h index aa69e78ceb..83880ed2ab 100644 --- a/libraries/animation/src/AnimationObject.h +++ b/libraries/animation/src/AnimationObject.h @@ -23,13 +23,13 @@ class QScriptEngine; class AnimationObject : public QObject, protected QScriptable { Q_OBJECT Q_PROPERTY(QStringList jointNames READ getJointNames) - Q_PROPERTY(QVector<FBXAnimationFrame> frames READ getFrames) + Q_PROPERTY(QVector<HFMAnimationFrame> frames READ getFrames) public: Q_INVOKABLE QStringList getJointNames() const; - Q_INVOKABLE QVector<FBXAnimationFrame> getFrames() const; + Q_INVOKABLE QVector<HFMAnimationFrame> getFrames() const; }; /// Scriptable wrapper for animation frames. diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 341b554949..c2f909dd24 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -30,7 +30,9 @@ #include "AnimOverlay.h" #include "AnimSkeleton.h" #include "AnimUtil.h" +#include "AvatarConstants.h" #include "IKTarget.h" +#include "PathUtils.h" static int nextRigId = 1; @@ -133,6 +135,30 @@ void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firs _animVars.set("userAnimB", clipNodeEnum == UserAnimState::B); } +void Rig::triggerNetworkAnimation(const QString& animName) { + _networkVars.set("idleAnim", false); + _networkVars.set("preTransitAnim", false); + _networkVars.set("transitAnim", false); + _networkVars.set("postTransitAnim", false); + _sendNetworkNode = true; + + if (animName == "idleAnim") { + _networkVars.set("idleAnim", true); + _networkAnimState.clipNodeEnum = NetworkAnimState::Idle; + _sendNetworkNode = false; + } else if (animName == "preTransitAnim") { + _networkVars.set("preTransitAnim", true); + _networkAnimState.clipNodeEnum = NetworkAnimState::PreTransit; + } else if (animName == "transitAnim") { + _networkVars.set("transitAnim", true); + _networkAnimState.clipNodeEnum = NetworkAnimState::Transit; + } else if (animName == "postTransitAnim") { + _networkVars.set("postTransitAnim", true); + _networkAnimState.clipNodeEnum = NetworkAnimState::PostTransit; + } + +} + void Rig::restoreAnimation() { if (_userAnimState.clipNodeEnum != UserAnimState::None) { _userAnimState.clipNodeEnum = UserAnimState::None; @@ -144,6 +170,16 @@ void Rig::restoreAnimation() { } } +void Rig::restoreNetworkAnimation() { + if (_networkAnimState.clipNodeEnum != NetworkAnimState::Idle) { + _networkAnimState.clipNodeEnum = NetworkAnimState::Idle; + _networkVars.set("idleAnim", true); + _networkVars.set("preTransitAnim", false); + _networkVars.set("transitAnim", false); + _networkVars.set("postTransitAnim", false); + } +} + QStringList Rig::getAnimationRoles() const { if (_animNode) { QStringList list; @@ -208,57 +244,73 @@ void Rig::restoreRoleAnimation(const QString& role) { void Rig::destroyAnimGraph() { _animSkeleton.reset(); _animLoader.reset(); + _networkLoader.reset(); _animNode.reset(); _internalPoseSet._relativePoses.clear(); _internalPoseSet._absolutePoses.clear(); _internalPoseSet._overridePoses.clear(); _internalPoseSet._overrideFlags.clear(); + _networkNode.reset(); + _networkPoseSet._relativePoses.clear(); + _networkPoseSet._absolutePoses.clear(); + _networkPoseSet._overridePoses.clear(); + _networkPoseSet._overrideFlags.clear(); _numOverrides = 0; _leftEyeJointChildren.clear(); _rightEyeJointChildren.clear(); } -void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) { - _geometryOffset = AnimPose(geometry.offset); +void Rig::initJointStates(const HFMModel& hfmModel, const glm::mat4& modelOffset) { + _geometryOffset = AnimPose(hfmModel.offset); _invGeometryOffset = _geometryOffset.inverse(); - _geometryToRigTransform = modelOffset * geometry.offset; + _geometryToRigTransform = modelOffset * hfmModel.offset; _rigToGeometryTransform = glm::inverse(_geometryToRigTransform); setModelOffset(modelOffset); - _animSkeleton = std::make_shared<AnimSkeleton>(geometry); + _animSkeleton = std::make_shared<AnimSkeleton>(hfmModel); _internalPoseSet._relativePoses.clear(); _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); + _networkPoseSet._relativePoses.clear(); + _networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); + buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses); _internalPoseSet._overridePoses.clear(); _internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); _internalPoseSet._overrideFlags.clear(); _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); + + _networkPoseSet._overridePoses.clear(); + _networkPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); + + _networkPoseSet._overrideFlags.clear(); + _networkPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); + _numOverrides = 0; buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); - _rootJointIndex = geometry.rootJointIndex; - _leftEyeJointIndex = geometry.leftEyeJointIndex; - _rightEyeJointIndex = geometry.rightEyeJointIndex; - _leftHandJointIndex = geometry.leftHandJointIndex; - _leftElbowJointIndex = _leftHandJointIndex >= 0 ? geometry.joints.at(_leftHandJointIndex).parentIndex : -1; - _leftShoulderJointIndex = _leftElbowJointIndex >= 0 ? geometry.joints.at(_leftElbowJointIndex).parentIndex : -1; - _rightHandJointIndex = geometry.rightHandJointIndex; - _rightElbowJointIndex = _rightHandJointIndex >= 0 ? geometry.joints.at(_rightHandJointIndex).parentIndex : -1; - _rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? geometry.joints.at(_rightElbowJointIndex).parentIndex : -1; + _rootJointIndex = hfmModel.rootJointIndex; + _leftEyeJointIndex = hfmModel.leftEyeJointIndex; + _rightEyeJointIndex = hfmModel.rightEyeJointIndex; + _leftHandJointIndex = hfmModel.leftHandJointIndex; + _leftElbowJointIndex = _leftHandJointIndex >= 0 ? hfmModel.joints.at(_leftHandJointIndex).parentIndex : -1; + _leftShoulderJointIndex = _leftElbowJointIndex >= 0 ? hfmModel.joints.at(_leftElbowJointIndex).parentIndex : -1; + _rightHandJointIndex = hfmModel.rightHandJointIndex; + _rightElbowJointIndex = _rightHandJointIndex >= 0 ? hfmModel.joints.at(_rightHandJointIndex).parentIndex : -1; + _rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? hfmModel.joints.at(_rightElbowJointIndex).parentIndex : -1; - _leftEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.leftEyeJointIndex); - _rightEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.rightEyeJointIndex); + _leftEyeJointChildren = _animSkeleton->getChildrenOfJoint(hfmModel.leftEyeJointIndex); + _rightEyeJointChildren = _animSkeleton->getChildrenOfJoint(hfmModel.rightEyeJointIndex); } -void Rig::reset(const FBXGeometry& geometry) { - _geometryOffset = AnimPose(geometry.offset); +void Rig::reset(const HFMModel& hfmModel) { + _geometryOffset = AnimPose(hfmModel.offset); _invGeometryOffset = _geometryOffset.inverse(); - _animSkeleton = std::make_shared<AnimSkeleton>(geometry); + _animSkeleton = std::make_shared<AnimSkeleton>(hfmModel); _internalPoseSet._relativePoses.clear(); _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); @@ -270,22 +322,34 @@ void Rig::reset(const FBXGeometry& geometry) { _internalPoseSet._overrideFlags.clear(); _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); + + _networkPoseSet._relativePoses.clear(); + _networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); + + buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses); + + _networkPoseSet._overridePoses.clear(); + _networkPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); + + _networkPoseSet._overrideFlags.clear(); + _networkPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); + _numOverrides = 0; buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); - _rootJointIndex = geometry.rootJointIndex; - _leftEyeJointIndex = geometry.leftEyeJointIndex; - _rightEyeJointIndex = geometry.rightEyeJointIndex; - _leftHandJointIndex = geometry.leftHandJointIndex; - _leftElbowJointIndex = _leftHandJointIndex >= 0 ? geometry.joints.at(_leftHandJointIndex).parentIndex : -1; - _leftShoulderJointIndex = _leftElbowJointIndex >= 0 ? geometry.joints.at(_leftElbowJointIndex).parentIndex : -1; - _rightHandJointIndex = geometry.rightHandJointIndex; - _rightElbowJointIndex = _rightHandJointIndex >= 0 ? geometry.joints.at(_rightHandJointIndex).parentIndex : -1; - _rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? geometry.joints.at(_rightElbowJointIndex).parentIndex : -1; + _rootJointIndex = hfmModel.rootJointIndex; + _leftEyeJointIndex = hfmModel.leftEyeJointIndex; + _rightEyeJointIndex = hfmModel.rightEyeJointIndex; + _leftHandJointIndex = hfmModel.leftHandJointIndex; + _leftElbowJointIndex = _leftHandJointIndex >= 0 ? hfmModel.joints.at(_leftHandJointIndex).parentIndex : -1; + _leftShoulderJointIndex = _leftElbowJointIndex >= 0 ? hfmModel.joints.at(_leftElbowJointIndex).parentIndex : -1; + _rightHandJointIndex = hfmModel.rightHandJointIndex; + _rightElbowJointIndex = _rightHandJointIndex >= 0 ? hfmModel.joints.at(_rightHandJointIndex).parentIndex : -1; + _rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? hfmModel.joints.at(_rightElbowJointIndex).parentIndex : -1; - _leftEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.leftEyeJointIndex); - _rightEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.rightEyeJointIndex); + _leftEyeJointChildren = _animSkeleton->getChildrenOfJoint(hfmModel.leftEyeJointIndex); + _rightEyeJointChildren = _animSkeleton->getChildrenOfJoint(hfmModel.rightEyeJointIndex); if (!_animGraphURL.isEmpty()) { _animNode.reset(); @@ -629,7 +693,8 @@ bool Rig::getRelativeDefaultJointTranslation(int index, glm::vec3& translationOu } } -void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState) { +void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, + const glm::quat& worldRotation, CharacterControllerState ccState, float sensorToWorldScale) { glm::vec3 forward = worldRotation * IDENTITY_FORWARD; glm::vec3 workingVelocity = worldVelocity; @@ -924,9 +989,15 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos } _animVars.set("isNotInAir", false); - // compute blend based on velocity - const float JUMP_SPEED = 3.5f; - float alpha = glm::clamp(-workingVelocity.y / JUMP_SPEED, -1.0f, 1.0f) + 1.0f; + // We want to preserve the apparent jump height in sensor space. + const float jumpHeight = std::max(sensorToWorldScale * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); + + // convert jump height to a initial jump speed with the given gravity. + const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); + + // compute inAirAlpha blend based on velocity + float alpha = glm::clamp((-workingVelocity.y * sensorToWorldScale) / jumpSpeed, -1.0f, 1.0f) + 1.0f; + _animVars.set("inAirAlpha", alpha); } @@ -1049,26 +1120,56 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons updateAnimationStateHandlers(); _animVars.setRigToGeometryTransform(_rigToGeometryTransform); - + if (_networkNode) { + _networkVars.setRigToGeometryTransform(_rigToGeometryTransform); + } AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, _enableDebugDrawIKChains, getGeometryToRigTransform(), rigToWorldTransform); // evaluate the animation AnimVariantMap triggersOut; - + AnimVariantMap networkTriggersOut; _internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut); + if (_networkNode) { + _networkPoseSet._relativePoses = _networkNode->evaluate(_networkVars, context, deltaTime, networkTriggersOut); + const float NETWORK_ANIMATION_BLEND_FRAMES = 6.0f; + float alpha = 1.0f; + std::shared_ptr<AnimClip> clip; + if (_networkAnimState.clipNodeEnum == NetworkAnimState::PreTransit) { + clip = std::dynamic_pointer_cast<AnimClip>(_networkNode->findByName("preTransitAnim")); + if (clip) { + alpha = (clip->getFrame() - clip->getStartFrame()) / NETWORK_ANIMATION_BLEND_FRAMES; + } + } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::PostTransit) { + clip = std::dynamic_pointer_cast<AnimClip>(_networkNode->findByName("postTransitAnim")); + if (clip) { + alpha = (clip->getEndFrame() - clip->getFrame()) / NETWORK_ANIMATION_BLEND_FRAMES; + } + } + if (_sendNetworkNode) { + for (size_t i = 0; i < _networkPoseSet._relativePoses.size(); i++) { + _networkPoseSet._relativePoses[i].blend(_internalPoseSet._relativePoses[i], (alpha > 1.0f ? 1.0f : alpha)); + } + } + } if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) { // animations haven't fully loaded yet. _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); } + if ((int)_networkPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) { + // animations haven't fully loaded yet. + _networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); + } _lastAnimVars = _animVars; _animVars.clearTriggers(); _animVars = triggersOut; + _networkVars.clearTriggers(); + _networkVars = networkTriggersOut; _lastContext = context; } applyOverridePoses(); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); - + buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses); // copy internal poses to external poses { QWriteLocker writeLock(&_externalPoseSetLock); @@ -1152,7 +1253,7 @@ const glm::vec3 DOP14_NORMALS[DOP14_COUNT] = { // returns true if the given point lies inside of the k-dop, specified by shapeInfo & shapePose. // if the given point does lie within the k-dop, it also returns the amount of displacement necessary to push that point outward // such that it lies on the surface of the kdop. -static bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& shapePose, const FBXJointShapeInfo& shapeInfo, glm::vec3& displacementOut) { +static bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& shapePose, const HFMJointShapeInfo& shapeInfo, glm::vec3& displacementOut) { // transform point into local space of jointShape. glm::vec3 localPoint = shapePose.inverse().xformPoint(point); @@ -1198,8 +1299,8 @@ static bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& sh } } -glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, - const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) const { +glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, + const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const { glm::vec3 position = handPosition; glm::vec3 displacement; int hipsJoint = indexOfJoint("Hips"); @@ -1248,8 +1349,8 @@ glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const FBXJoin void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated, bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, - const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, - const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo, + const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, + const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) { const bool ENABLE_POLE_VECTORS = true; @@ -1574,6 +1675,7 @@ glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, int kneeIndex, int up void Rig::updateFromControllerParameters(const ControllerParameters& params, float dt) { if (!_animSkeleton || !_animNode) { + _previousControllerParameters = params; return; } @@ -1584,7 +1686,9 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo bool leftHandEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftHand] & (uint8_t)ControllerFlags::Enabled; bool rightHandEnabled = params.primaryControllerFlags[PrimaryControllerType_RightHand] & (uint8_t)ControllerFlags::Enabled; bool hipsEnabled = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Enabled; + bool prevHipsEnabled = _previousControllerParameters.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Enabled; bool hipsEstimated = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Estimated; + bool prevHipsEstimated = _previousControllerParameters.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Estimated; bool leftFootEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftFoot] & (uint8_t)ControllerFlags::Enabled; bool rightFootEnabled = params.primaryControllerFlags[PrimaryControllerType_RightFoot] & (uint8_t)ControllerFlags::Enabled; bool spine2Enabled = params.primaryControllerFlags[PrimaryControllerType_Spine2] & (uint8_t)ControllerFlags::Enabled; @@ -1623,9 +1727,26 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo } if (hipsEnabled) { + + // Apply a bit of smoothing when the hips toggle between estimated and non-estimated poses. + // This should help smooth out problems with the vive tracker when the sensor is occluded. + if (prevHipsEnabled && hipsEstimated != prevHipsEstimated) { + // blend from a snapshot of the previous hips. + const float HIPS_BLEND_DURATION = 0.5f; + _hipsBlendHelper.setBlendDuration(HIPS_BLEND_DURATION); + _hipsBlendHelper.setSnapshot(_previousControllerParameters.primaryControllerPoses[PrimaryControllerType_Hips]); + } else if (!prevHipsEnabled) { + // we have no sensible value to blend from. + const float HIPS_BLEND_DURATION = 0.0f; + _hipsBlendHelper.setBlendDuration(HIPS_BLEND_DURATION); + _hipsBlendHelper.setSnapshot(params.primaryControllerPoses[PrimaryControllerType_Hips]); + } + + AnimPose hips = _hipsBlendHelper.update(params.primaryControllerPoses[PrimaryControllerType_Hips], dt); + _animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition); - _animVars.set("hipsPosition", params.primaryControllerPoses[PrimaryControllerType_Hips].trans()); - _animVars.set("hipsRotation", params.primaryControllerPoses[PrimaryControllerType_Hips].rot()); + _animVars.set("hipsPosition", hips.trans()); + _animVars.set("hipsRotation", hips.rot()); } else { _animVars.set("hipsType", (int)IKTarget::Type::Unknown); } @@ -1665,6 +1786,8 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo } } } + + _previousControllerParameters = params; } void Rig::initAnimGraph(const QUrl& url) { @@ -1672,11 +1795,14 @@ void Rig::initAnimGraph(const QUrl& url) { _animGraphURL = url; _animNode.reset(); + _networkNode.reset(); // load the anim graph _animLoader.reset(new AnimNodeLoader(url)); + auto networkUrl = PathUtils::resourcesUrl("avatar/network-animation.json"); + _networkLoader.reset(new AnimNodeLoader(networkUrl)); std::weak_ptr<AnimSkeleton> weakSkeletonPtr = _animSkeleton; - connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) { + connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr, url](AnimNode::Pointer nodeIn) { _animNode = nodeIn; // abort load if the previous skeleton was deleted. @@ -1703,7 +1829,33 @@ void Rig::initAnimGraph(const QUrl& url) { emit onLoadComplete(); }); connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { - qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; + qCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; + }); + + connect(_networkLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr, networkUrl](AnimNode::Pointer nodeIn) { + _networkNode = nodeIn; + // abort load if the previous skeleton was deleted. + auto sharedSkeletonPtr = weakSkeletonPtr.lock(); + if (!sharedSkeletonPtr) { + return; + } + _networkNode->setSkeleton(sharedSkeletonPtr); + if (_networkAnimState.clipNodeEnum != NetworkAnimState::Idle) { + // restore the user animation we had before reset. + NetworkAnimState origState = _networkAnimState; + _networkAnimState = { NetworkAnimState::Idle, "", 30.0f, false, 0.0f, 0.0f }; + if (_networkAnimState.clipNodeEnum == NetworkAnimState::PreTransit) { + triggerNetworkAnimation("preTransitAnim"); + } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::Transit) { + triggerNetworkAnimation("transitAnim"); + } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::PostTransit) { + triggerNetworkAnimation("postTransitAnim"); + } + } + + }); + connect(_networkLoader.get(), &AnimNodeLoader::error, [networkUrl](int error, QString str) { + qCritical(animation) << "Error loading" << networkUrl.toDisplayString() << "code = " << error << "str =" << str; }); } } @@ -1782,13 +1934,13 @@ void Rig::copyJointsIntoJointData(QVector<JointData>& jointDataVec) const { if (isIndexValid(i)) { // rotations are in absolute rig frame. glm::quat defaultAbsRot = geometryToRigPose.rot() * _animSkeleton->getAbsoluteDefaultPose(i).rot(); - data.rotation = _internalPoseSet._absolutePoses[i].rot(); + data.rotation = !_sendNetworkNode ? _internalPoseSet._absolutePoses[i].rot() : _networkPoseSet._absolutePoses[i].rot(); data.rotationIsDefaultPose = isEqual(data.rotation, defaultAbsRot); // translations are in relative frame but scaled so that they are in meters, - // instead of geometry units. + // instead of model units. glm::vec3 defaultRelTrans = _geometryOffset.scale() * _animSkeleton->getRelativeDefaultPose(i).trans(); - data.translation = _geometryOffset.scale() * _internalPoseSet._relativePoses[i].trans(); + data.translation = _geometryOffset.scale() * (!_sendNetworkNode ? _internalPoseSet._relativePoses[i].trans() : _networkPoseSet._relativePoses[i].trans()); data.translationIsDefaultPose = isEqual(data.translation, defaultRelTrans); } else { data.translationIsDefaultPose = true; @@ -1811,7 +1963,7 @@ void Rig::copyJointsFromJointData(const QVector<JointData>& jointDataVec) { return; } - // make a vector of rotations in absolute-geometry-frame + // make a vector of rotations in absolute-model-frame std::vector<glm::quat> rotations; rotations.reserve(numJoints); const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform)); @@ -1820,7 +1972,7 @@ void Rig::copyJointsFromJointData(const QVector<JointData>& jointDataVec) { if (data.rotationIsDefaultPose) { rotations.push_back(absoluteDefaultPoses[i].rot()); } else { - // JointData rotations are in absolute rig-frame so we rotate them to absolute geometry-frame + // JointData rotations are in absolute rig-frame so we rotate them to absolute model-frame rotations.push_back(rigToGeometryRot * data.rotation); } } @@ -1856,7 +2008,7 @@ void Rig::computeExternalPoses(const glm::mat4& modelOffsetMat) { } void Rig::computeAvatarBoundingCapsule( - const FBXGeometry& geometry, + const HFMModel& hfmModel, float& radiusOut, float& heightOut, glm::vec3& localOffsetOut) const { @@ -1889,7 +2041,7 @@ void Rig::computeAvatarBoundingCapsule( // from the head to the hips when computing the rest of the bounding capsule. int index = indexOfJoint("Head"); while (index != -1) { - const FBXJointShapeInfo& shapeInfo = geometry.joints.at(index).shapeInfo; + const HFMJointShapeInfo& shapeInfo = hfmModel.joints.at(index).shapeInfo; AnimPose pose = _animSkeleton->getAbsoluteDefaultPose(index); if (shapeInfo.points.size() > 0) { for (auto& point : shapeInfo.points) { diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index ed0b70d4b6..345f335f88 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -24,6 +24,7 @@ #include "AnimNode.h" #include "AnimNodeLoader.h" #include "SimpleMovingAverage.h" +#include "AnimUtil.h" class Rig; class AnimInverseKinematics; @@ -85,10 +86,10 @@ public: AnimPose secondaryControllerPoses[NumSecondaryControllerTypes]; // rig space uint8_t secondaryControllerFlags[NumSecondaryControllerTypes]; bool isTalking; - FBXJointShapeInfo hipsShapeInfo; - FBXJointShapeInfo spineShapeInfo; - FBXJointShapeInfo spine1ShapeInfo; - FBXJointShapeInfo spine2ShapeInfo; + HFMJointShapeInfo hipsShapeInfo; + HFMJointShapeInfo spineShapeInfo; + HFMJointShapeInfo spine1ShapeInfo; + HFMJointShapeInfo spine2ShapeInfo; }; struct EyeParameters { @@ -113,13 +114,16 @@ public: void destroyAnimGraph(); void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); + void triggerNetworkAnimation(const QString& animName); void restoreAnimation(); + void restoreNetworkAnimation(); + QStringList getAnimationRoles() const; void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame); void restoreRoleAnimation(const QString& role); - void initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset); - void reset(const FBXGeometry& geometry); + void initJointStates(const HFMModel& hfmModel, const glm::mat4& modelOffset); + void reset(const HFMModel& hfmModel); bool jointStatesEmpty(); int getJointStateCount() const; int indexOfJoint(const QString& jointName) const; @@ -172,7 +176,8 @@ public: AnimPose getJointPose(int jointIndex) const; // Start or stop animations as needed. - void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState); + void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, + const glm::quat& worldRotation, CharacterControllerState ccState, float sensorToWorldScale); // Regardless of who started the animations or how many, update the joints. void updateAnimations(float deltaTime, const glm::mat4& rootTransform, const glm::mat4& rigToWorldTransform); @@ -205,7 +210,7 @@ public: void copyJointsFromJointData(const QVector<JointData>& jointDataVec); void computeExternalPoses(const glm::mat4& modelOffsetMat); - void computeAvatarBoundingCapsule(const FBXGeometry& geometry, float& radiusOut, float& heightOut, glm::vec3& offsetOut) const; + void computeAvatarBoundingCapsule(const HFMModel& hfmModel, float& radiusOut, float& heightOut, glm::vec3& offsetOut) const; void setEnableInverseKinematics(bool enable); void setEnableAnimations(bool enable); @@ -240,8 +245,8 @@ protected: void updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated, bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, - const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, - const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo, + const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, + const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix); void updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose, @@ -252,8 +257,8 @@ protected: bool calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const; glm::vec3 calculateKneePoleVector(int footJointIndex, int kneeJoint, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const; - glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, - const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) const; + glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, + const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const; AnimPose _modelOffset; // model to rig space @@ -269,6 +274,7 @@ protected: // Only accessed by the main thread PoseSet _internalPoseSet; + PoseSet _networkPoseSet; // Copy of the _poseSet for external threads. PoseSet _externalPoseSet; @@ -300,9 +306,12 @@ protected: QUrl _animGraphURL; std::shared_ptr<AnimNode> _animNode; + std::shared_ptr<AnimNode> _networkNode; std::shared_ptr<AnimSkeleton> _animSkeleton; std::unique_ptr<AnimNodeLoader> _animLoader; + std::unique_ptr<AnimNodeLoader> _networkLoader; AnimVariantMap _animVars; + AnimVariantMap _networkVars; enum class RigRole { Idle = 0, @@ -315,6 +324,25 @@ protected: RigRole _state { RigRole::Idle }; RigRole _desiredState { RigRole::Idle }; float _desiredStateAge { 0.0f }; + + struct NetworkAnimState { + enum ClipNodeEnum { + Idle = 0, + PreTransit, + Transit, + PostTransit + }; + NetworkAnimState() : clipNodeEnum(NetworkAnimState::Idle) {} + NetworkAnimState(ClipNodeEnum clipNodeEnumIn, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) : + clipNodeEnum(clipNodeEnumIn), url(urlIn), fps(fpsIn), loop(loopIn), firstFrame(firstFrameIn), lastFrame(lastFrameIn) {} + + ClipNodeEnum clipNodeEnum; + QString url; + float fps; + bool loop; + float firstFrame; + float lastFrame; + }; struct UserAnimState { enum ClipNodeEnum { @@ -349,6 +377,7 @@ protected: }; UserAnimState _userAnimState; + NetworkAnimState _networkAnimState; std::map<QString, RoleAnimState> _roleAnimStates; float _leftHandOverlayAlpha { 0.0f }; @@ -382,9 +411,13 @@ protected: int _rigId; bool _headEnabled { false }; + bool _sendNetworkNode { false }; AnimContext _lastContext; AnimVariantMap _lastAnimVars; + + SnapshotBlendPoseHelper _hipsBlendHelper; + ControllerParameters _previousControllerParameters; }; #endif /* defined(__hifi__Rig__) */ diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index d00bc29054..bdd6d0edc1 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -244,13 +244,20 @@ AudioClient::AudioClient() : // initialize wasapi; if getAvailableDevices is called from the CheckDevicesThread before this, it will crash getAvailableDevices(QAudio::AudioInput); getAvailableDevices(QAudio::AudioOutput); - + // start a thread to detect any device changes _checkDevicesTimer = new QTimer(this); - connect(_checkDevicesTimer, &QTimer::timeout, this, [this] { - QtConcurrent::run(QThreadPool::globalInstance(), [this] { checkDevices(); }); - }); const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; + connect(_checkDevicesTimer, &QTimer::timeout, this, [=] { + QtConcurrent::run(QThreadPool::globalInstance(), [=] { + checkDevices(); + // On some systems (Ubuntu) checking all the audio devices can take more than 2 seconds. To + // avoid consuming all of the thread pool, don't start the check interval until the previous + // check has completed. + QMetaObject::invokeMethod(_checkDevicesTimer, "start", Q_ARG(int, DEVICE_CHECK_INTERVAL_MSECS)); + }); + }); + _checkDevicesTimer->setSingleShot(true); _checkDevicesTimer->start(DEVICE_CHECK_INTERVAL_MSECS); // start a thread to detect peak value changes diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index c6e6a5e8ca..23f3c36e68 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -114,27 +114,29 @@ void Avatar::setShowNamesAboveHeads(bool show) { } AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { - glm::vec3 currentPosition = _isTransiting ? _currentPosition : avatarPosition; - float oneFrameDistance = glm::length(currentPosition - _lastPosition); - const float MAX_TRANSIT_DISTANCE = 30.0f; - float scaledMaxTransitDistance = MAX_TRANSIT_DISTANCE * _scale; - if (oneFrameDistance > config._triggerDistance && !_isTransiting) { - if (oneFrameDistance < scaledMaxTransitDistance) { - start(deltaTime, _lastPosition, currentPosition, config); + float oneFrameDistance = _isActive ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition); + if (oneFrameDistance > (config._minTriggerDistance * _scale)) { + if (oneFrameDistance < (config._maxTriggerDistance * _scale)) { + start(deltaTime, _lastPosition, avatarPosition, config); } else { - _lastPosition = currentPosition; - return Status::ABORT_TRANSIT; + _lastPosition = avatarPosition; + _status = Status::ABORT_TRANSIT; } } - _lastPosition = currentPosition; + _lastPosition = avatarPosition; _status = updatePosition(deltaTime); + + if (_isActive && oneFrameDistance > (config._abortDistance * _scale) && _status == Status::POST_TRANSIT) { + reset(); + _status = Status::ENDED; + } return _status; } void AvatarTransit::reset() { _lastPosition = _endPosition; _currentPosition = _endPosition; - _isTransiting = false; + _isActive = false; } void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const AvatarTransit::TransitConfig& config) { _startPosition = startPosition; @@ -143,12 +145,14 @@ void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const _transitLine = endPosition - startPosition; _totalDistance = glm::length(_transitLine); _easeType = config._easeType; - const float REFERENCE_FRAMES_PER_SECOND = 30.0f; - + + _preTransitTime = AVATAR_PRE_TRANSIT_FRAME_COUNT / AVATAR_TRANSIT_FRAMES_PER_SECOND; + _postTransitTime = AVATAR_POST_TRANSIT_FRAME_COUNT / AVATAR_TRANSIT_FRAMES_PER_SECOND; int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance; - _totalTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND; - _currentTime = 0.0f; - _isTransiting = true; + _transitTime = (float)transitFrames / AVATAR_TRANSIT_FRAMES_PER_SECOND; + _totalTime = _transitTime + _preTransitTime + _postTransitTime; + _currentTime = _isActive ? _preTransitTime : 0.0f; + _isActive = true; } float AvatarTransit::getEaseValue(AvatarTransit::EaseType type, float value) { @@ -171,31 +175,37 @@ float AvatarTransit::getEaseValue(AvatarTransit::EaseType type, float value) { AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) { Status status = Status::IDLE; - if (_isTransiting) { + if (_isActive) { float nextTime = _currentTime + deltaTime; - if (nextTime >= _totalTime) { - _currentPosition = _endPosition; - _isTransiting = false; - status = Status::END_TRANSIT; - } else { + if (nextTime < _preTransitTime) { + _currentPosition = _startPosition; + status = Status::PRE_TRANSIT; if (_currentTime == 0) { + status = Status::STARTED; + } + } else if (nextTime < _totalTime - _postTransitTime){ + status = Status::TRANSITING; + if (_currentTime <= _preTransitTime) { status = Status::START_TRANSIT; } else { - status = Status::TRANSITING; + float percentageIntoTransit = (nextTime - _preTransitTime) / _transitTime; + _currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine; + } + } else { + status = Status::POST_TRANSIT; + _currentPosition = _endPosition; + if (nextTime >= _totalTime) { + _isActive = false; + status = Status::ENDED; + } else if (_currentTime < _totalTime - _postTransitTime) { + status = Status::END_TRANSIT; } - float percentageIntoTransit = nextTime / _totalTime; - _currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine; } _currentTime = nextTime; } return status; } -bool AvatarTransit::getNextPosition(glm::vec3& nextPosition) { - nextPosition = _currentPosition; - return _isTransiting; -} - Avatar::Avatar(QThread* thread) : _voiceSphereID(GeometryCache::UNKNOWN_ID) { @@ -488,8 +498,8 @@ void Avatar::relayJointDataToChildren() { glm::quat jointRotation; glm::vec3 jointTranslation; if (avatarJointIndex < 0) { - jointRotation = modelEntity->getAbsoluteJointRotationInObjectFrame(jointIndex); - jointTranslation = modelEntity->getAbsoluteJointTranslationInObjectFrame(jointIndex); + jointRotation = modelEntity->getLocalJointRotation(jointIndex); + jointTranslation = modelEntity->getLocalJointTranslation(jointIndex); map.push_back(-1); } else { int jointIndex = getJointIndex(jointName); @@ -512,8 +522,8 @@ void Avatar::relayJointDataToChildren() { jointRotation = getJointRotation(avatarJointIndex); jointTranslation = getJointTranslation(avatarJointIndex); } else { - jointRotation = modelEntity->getAbsoluteJointRotationInObjectFrame(jointIndex); - jointTranslation = modelEntity->getAbsoluteJointTranslationInObjectFrame(jointIndex); + jointRotation = modelEntity->getLocalJointRotation(jointIndex); + jointTranslation = modelEntity->getLocalJointTranslation(jointIndex); } modelEntity->setLocalJointRotation(jointIndex, jointRotation); modelEntity->setLocalJointTranslation(jointIndex, jointTranslation); @@ -536,16 +546,10 @@ void Avatar::relayJointDataToChildren() { void Avatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "simulate"); - - if (_transit.isTransiting()) { - glm::vec3 nextPosition; - if (_transit.getNextPosition(nextPosition)) { - _globalPosition = nextPosition; - _globalPositionChanged = usecTimestampNow(); - if (!hasParent()) { - setLocalPosition(nextPosition); - } - } + + _globalPosition = _transit.isActive() ? _transit.getCurrentPosition() : _serverPosition; + if (!hasParent()) { + setLocalPosition(_globalPosition); } _simulationRate.increment(); @@ -558,7 +562,7 @@ void Avatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "updateJoints"); if (inView) { Head* head = getHead(); - if (_hasNewJointData || _transit.isTransiting()) { + if (_hasNewJointData || _transit.isActive()) { _skeletonModel->getRig().copyJointsFromJointData(_jointData); glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset()); _skeletonModel->getRig().computeExternalPoses(rootTransform); @@ -705,10 +709,10 @@ static TextRenderer3D* textRenderer(TextRendererType type) { return displayNameRenderer; } -void Avatar::metaBlendshapeOperator(int blendshapeNumber, const QVector<BlendshapeOffset>& blendshapeOffsets, const QVector<int>& blendedMeshSizes, - const render::ItemIDs& subItemIDs) { +void Avatar::metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector<BlendshapeOffset>& blendshapeOffsets, + const QVector<int>& blendedMeshSizes, const render::ItemIDs& subItemIDs) { render::Transaction transaction; - transaction.updateItem<AvatarData>(_renderItemID, [blendshapeNumber, blendshapeOffsets, blendedMeshSizes, + transaction.updateItem<AvatarData>(renderItemID, [blendshapeNumber, blendshapeOffsets, blendedMeshSizes, subItemIDs](AvatarData& avatar) { auto avatarPtr = dynamic_cast<Avatar*>(&avatar); if (avatarPtr) { @@ -728,7 +732,7 @@ void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& sc _renderBound = getBounds(); transaction.resetItem(_renderItemID, avatarPayloadPointer); using namespace std::placeholders; - _skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, this, _1, _2, _3, _4)); + _skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, _renderItemID, _1, _2, _3, _4)); _skeletonModel->setTagMask(render::hifi::TAG_ALL_VIEWS); _skeletonModel->setGroupCulled(true); _skeletonModel->setCanCastShadow(true); @@ -953,7 +957,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { if (_skeletonModel->isRenderable() && _skeletonModel->needsFixupInScene()) { _skeletonModel->removeFromScene(scene, transaction); using namespace std::placeholders; - _skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, this, _1, _2, _3, _4)); + _skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, _renderItemID, _1, _2, _3, _4)); _skeletonModel->setTagMask(render::hifi::TAG_ALL_VIEWS); _skeletonModel->setGroupCulled(true); @@ -1310,7 +1314,7 @@ glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { case CAMERA_MATRIX_INDEX: { glm::quat rotation; if (_skeletonModel && _skeletonModel->isActive()) { - int headJointIndex = _skeletonModel->getFBXGeometry().headJointIndex; + int headJointIndex = _skeletonModel->getHFMModel().headJointIndex; if (headJointIndex >= 0) { _skeletonModel->getAbsoluteJointRotationInRigFrame(headJointIndex, rotation); } @@ -1359,7 +1363,7 @@ glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const { case CAMERA_MATRIX_INDEX: { glm::vec3 translation; if (_skeletonModel && _skeletonModel->isActive()) { - int headJointIndex = _skeletonModel->getFBXGeometry().headJointIndex; + int headJointIndex = _skeletonModel->getHFMModel().headJointIndex; if (headJointIndex >= 0) { _skeletonModel->getAbsoluteJointTranslationInRigFrame(headJointIndex, translation); } @@ -1415,7 +1419,7 @@ void Avatar::withValidJointIndicesCache(std::function<void()> const& worker) con if (!_modelJointsCached) { _modelJointIndicesCache.clear(); if (_skeletonModel && _skeletonModel->isActive()) { - _modelJointIndicesCache = _skeletonModel->getFBXGeometry().jointIndices; + _modelJointIndicesCache = _skeletonModel->getHFMModel().jointIndices; _modelJointsCached = true; } } @@ -1734,6 +1738,7 @@ void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { glm::vec3 Avatar::getWorldFeetPosition() { ShapeInfo shapeInfo; + computeShapeInfo(shapeInfo); glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight glm::vec3 localFeet(0.0f, shapeInfo.getOffset().y - halfExtents.y - halfExtents.x, 0.0f); @@ -1997,22 +2002,12 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { } } -AvatarTransit::Status Avatar::updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { +AvatarTransit::Status Avatar::updateTransit(float deltaTime, const glm::vec3& avatarPosition, float avatarScale, const AvatarTransit::TransitConfig& config) { std::lock_guard<std::mutex> lock(_transitLock); + _transit.setScale(avatarScale); return _transit.update(deltaTime, avatarPosition, config); } -void Avatar::setTransitScale(float scale) { - std::lock_guard<std::mutex> lock(_transitLock); - return _transit.setScale(scale); -} - -void Avatar::overrideNextPackagePositionData(const glm::vec3& position) { - std::lock_guard<std::mutex> lock(_transitLock); - _overrideGlobalPosition = true; - _globalPositionOverride = position; -} - void Avatar::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { std::lock_guard<std::mutex> lock(_materialsLock); _materials[parentMaterialName].push(material); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 014be23fa5..8f70b12122 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -56,9 +56,13 @@ class AvatarTransit { public: enum Status { IDLE = 0, + STARTED, + PRE_TRANSIT, START_TRANSIT, TRANSITING, END_TRANSIT, + POST_TRANSIT, + ENDED, ABORT_TRANSIT }; @@ -72,20 +76,20 @@ public: struct TransitConfig { TransitConfig() {}; int _totalFrames { 0 }; - int _framesPerMeter { 0 }; + float _framesPerMeter { 0.0f }; bool _isDistanceBased { false }; - float _triggerDistance { 0 }; + float _minTriggerDistance { 0.0f }; + float _maxTriggerDistance { 0.0f }; + float _abortDistance{ 0.0f }; EaseType _easeType { EaseType::EASE_OUT }; }; AvatarTransit() {}; Status update(float deltaTime, const glm::vec3& avatarPosition, const TransitConfig& config); Status getStatus() { return _status; } - bool isTransiting() { return _isTransiting; } + bool isActive() { return _isActive; } glm::vec3 getCurrentPosition() { return _currentPosition; } - bool getNextPosition(glm::vec3& nextPosition); glm::vec3 getEndPosition() { return _endPosition; } - float getTransitTime() { return _totalTime; } void setScale(float scale) { _scale = scale; } void reset(); @@ -93,7 +97,7 @@ private: Status updatePosition(float deltaTime); void start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const TransitConfig& config); float getEaseValue(AvatarTransit::EaseType type, float value); - bool _isTransiting { false }; + bool _isActive { false }; glm::vec3 _startPosition; glm::vec3 _endPosition; @@ -103,7 +107,10 @@ private: glm::vec3 _transitLine; float _totalDistance { 0.0f }; + float _preTransitTime { 0.0f }; float _totalTime { 0.0f }; + float _transitTime { 0.0f }; + float _postTransitTime { 0.0f }; float _currentTime { 0.0f }; EaseType _easeType { EaseType::EASE_OUT }; Status _status { Status::IDLE }; @@ -431,11 +438,7 @@ public: virtual scriptable::ScriptableModelBase getScriptableModel() override; std::shared_ptr<AvatarTransit> getTransit() { return std::make_shared<AvatarTransit>(_transit); }; - - AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config); - void setTransitScale(float scale); - - void overrideNextPackagePositionData(const glm::vec3& position); + AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, float avatarScale, const AvatarTransit::TransitConfig& config); signals: void targetScaleChanged(float targetScale); @@ -623,8 +626,8 @@ protected: LoadingStatus _loadingStatus { LoadingStatus::NoModel }; - void metaBlendshapeOperator(int blendshapeNumber, const QVector<BlendshapeOffset>& blendshapeOffsets, const QVector<int>& blendedMeshSizes, - const render::ItemIDs& subItemIDs); + static void metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector<BlendshapeOffset>& blendshapeOffsets, + const QVector<int>& blendedMeshSizes, const render::ItemIDs& subItemIDs); }; #endif // hifi_Avatar_h diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index 1ec58fd704..36e37dd3d4 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -54,9 +54,9 @@ void SkeletonModel::setTextures(const QVariantMap& textures) { } void SkeletonModel::initJointStates() { - const FBXGeometry& geometry = getFBXGeometry(); + const HFMModel& hfmModel = getHFMModel(); glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); - _rig.initJointStates(geometry, modelOffset); + _rig.initJointStates(hfmModel, modelOffset); { // initialize _jointData with proper values for default joints @@ -66,7 +66,7 @@ void SkeletonModel::initJointStates() { } // Determine the default eye position for avatar scale = 1.0 - int headJointIndex = geometry.headJointIndex; + int headJointIndex = hfmModel.headJointIndex; if (0 > headJointIndex || headJointIndex >= _rig.getJointStateCount()) { qCWarning(avatars_renderer) << "Bad head joint! Got:" << headJointIndex << "jointCount:" << _rig.getJointStateCount(); } @@ -74,7 +74,7 @@ void SkeletonModel::initJointStates() { getEyeModelPositions(leftEyePosition, rightEyePosition); glm::vec3 midEyePosition = (leftEyePosition + rightEyePosition) / 2.0f; - int rootJointIndex = geometry.rootJointIndex; + int rootJointIndex = hfmModel.rootJointIndex; glm::vec3 rootModelPosition; getJointPosition(rootJointIndex, rootModelPosition); @@ -96,7 +96,7 @@ void SkeletonModel::initJointStates() { // Called within Model::simulate call, below. void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { assert(!_owningAvatar->isMyAvatar()); - const FBXGeometry& geometry = getFBXGeometry(); + const HFMModel& hfmModel = getHFMModel(); Head* head = _owningAvatar->getHead(); @@ -124,7 +124,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { // If the head is not positioned, updateEyeJoints won't get the math right glm::quat headOrientation; - _rig.getJointRotation(geometry.headJointIndex, headOrientation); + _rig.getJointRotation(hfmModel.headJointIndex, headOrientation); glm::vec3 eulers = safeEulerAngles(headOrientation); head->setBasePitch(glm::degrees(-eulers.x)); head->setBaseYaw(glm::degrees(eulers.y)); @@ -135,8 +135,8 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { eyeParams.eyeSaccade = glm::vec3(0.0f); eyeParams.modelRotation = getRotation(); eyeParams.modelTranslation = getTranslation(); - eyeParams.leftEyeJointIndex = geometry.leftEyeJointIndex; - eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex; + eyeParams.leftEyeJointIndex = hfmModel.leftEyeJointIndex; + eyeParams.rightEyeJointIndex = hfmModel.rightEyeJointIndex; _rig.updateFromEyeParameters(eyeParams); } @@ -259,45 +259,45 @@ bool SkeletonModel::getRightShoulderPosition(glm::vec3& position) const { } bool SkeletonModel::getHeadPosition(glm::vec3& headPosition) const { - return isActive() && getJointPositionInWorldFrame(getFBXGeometry().headJointIndex, headPosition); + return isActive() && getJointPositionInWorldFrame(getHFMModel().headJointIndex, headPosition); } bool SkeletonModel::getNeckPosition(glm::vec3& neckPosition) const { - return isActive() && getJointPositionInWorldFrame(getFBXGeometry().neckJointIndex, neckPosition); + return isActive() && getJointPositionInWorldFrame(getHFMModel().neckJointIndex, neckPosition); } bool SkeletonModel::getLocalNeckPosition(glm::vec3& neckPosition) const { - return isActive() && getJointPosition(getFBXGeometry().neckJointIndex, neckPosition); + return isActive() && getJointPosition(getHFMModel().neckJointIndex, neckPosition); } bool SkeletonModel::getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { if (!isActive()) { return false; } - const FBXGeometry& geometry = getFBXGeometry(); + const HFMModel& hfmModel = getHFMModel(); - if (getJointPosition(geometry.leftEyeJointIndex, firstEyePosition) && - getJointPosition(geometry.rightEyeJointIndex, secondEyePosition)) { + if (getJointPosition(hfmModel.leftEyeJointIndex, firstEyePosition) && + getJointPosition(hfmModel.rightEyeJointIndex, secondEyePosition)) { return true; } // no eye joints; try to estimate based on head/neck joints glm::vec3 neckPosition, headPosition; - if (getJointPosition(geometry.neckJointIndex, neckPosition) && - getJointPosition(geometry.headJointIndex, headPosition)) { + if (getJointPosition(hfmModel.neckJointIndex, neckPosition) && + getJointPosition(hfmModel.headJointIndex, headPosition)) { const float EYE_PROPORTION = 0.6f; glm::vec3 baseEyePosition = glm::mix(neckPosition, headPosition, EYE_PROPORTION); glm::quat headRotation; - getJointRotation(geometry.headJointIndex, headRotation); + getJointRotation(hfmModel.headJointIndex, headRotation); const float EYES_FORWARD = 0.25f; const float EYE_SEPARATION = 0.1f; float headHeight = glm::distance(neckPosition, headPosition); firstEyePosition = baseEyePosition + headRotation * glm::vec3(EYE_SEPARATION, 0.0f, EYES_FORWARD) * headHeight; secondEyePosition = baseEyePosition + headRotation * glm::vec3(-EYE_SEPARATION, 0.0f, EYES_FORWARD) * headHeight; return true; - } else if (getJointPosition(geometry.headJointIndex, headPosition)) { + } else if (getJointPosition(hfmModel.headJointIndex, headPosition)) { glm::vec3 baseEyePosition = headPosition; glm::quat headRotation; - getJointRotation(geometry.headJointIndex, headRotation); + getJointRotation(hfmModel.headJointIndex, headRotation); const float EYES_FORWARD_HEAD_ONLY = 0.30f; const float EYE_SEPARATION = 0.1f; firstEyePosition = baseEyePosition + headRotation * glm::vec3(EYE_SEPARATION, 0.0f, EYES_FORWARD_HEAD_ONLY); @@ -330,15 +330,15 @@ void SkeletonModel::computeBoundingShape() { return; } - const FBXGeometry& geometry = getFBXGeometry(); - if (geometry.joints.isEmpty() || geometry.rootJointIndex == -1) { + const HFMModel& hfmModel = getHFMModel(); + if (hfmModel.joints.isEmpty() || hfmModel.rootJointIndex == -1) { // rootJointIndex == -1 if the avatar model has no skeleton return; } float radius, height; glm::vec3 offset; - _rig.computeAvatarBoundingCapsule(geometry, radius, height, offset); + _rig.computeAvatarBoundingCapsule(hfmModel, radius, height, offset); float invScale = 1.0f / _owningAvatar->getModelScale(); _boundingCapsuleRadius = invScale * radius; _boundingCapsuleHeight = invScale * height; @@ -369,7 +369,7 @@ void SkeletonModel::renderBoundingCollisionShapes(RenderArgs* args, gpu::Batch& } bool SkeletonModel::hasSkeleton() { - return isActive() ? getFBXGeometry().rootJointIndex != -1 : false; + return isActive() ? getHFMModel().rootJointIndex != -1 : false; } void SkeletonModel::onInvalidate() { diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h index d82fce7412..c53cf8d333 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h @@ -41,10 +41,10 @@ public: void updateAttitude(const glm::quat& orientation); /// Returns the index of the left hand joint, or -1 if not found. - int getLeftHandJointIndex() const { return isActive() ? getFBXGeometry().leftHandJointIndex : -1; } + int getLeftHandJointIndex() const { return isActive() ? getHFMModel().leftHandJointIndex : -1; } /// Returns the index of the right hand joint, or -1 if not found. - int getRightHandJointIndex() const { return isActive() ? getFBXGeometry().rightHandJointIndex : -1; } + int getRightHandJointIndex() const { return isActive() ? getHFMModel().rightHandJointIndex : -1; } bool getLeftGrabPosition(glm::vec3& position) const; bool getRightGrabPosition(glm::vec3& position) const; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 6cd2101a0e..c529865b85 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -44,6 +44,7 @@ #include "AvatarLogging.h" #include "AvatarTraits.h" #include "ClientTraitsHandler.h" +#include "ResourceRequestObserver.h" //#define WANT_DEBUG @@ -65,7 +66,7 @@ size_t AvatarDataPacket::maxFaceTrackerInfoSize(size_t numBlendshapeCoefficients } size_t AvatarDataPacket::maxJointDataSize(size_t numJoints, bool hasGrabJoints) { - const size_t validityBitsSize = (size_t)std::ceil(numJoints / (float)BITS_IN_BYTE); + const size_t validityBitsSize = calcBitVectorSize((int)numJoints); size_t totalSize = sizeof(uint8_t); // numJoints @@ -227,18 +228,18 @@ float AvatarData::getDistanceBasedMinTranslationDistance(glm::vec3 viewerPositio // we want to track outbound data in this case... QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) { - AvatarDataPacket::HasFlags hasFlagsOut; auto lastSentTime = _lastToByteArray; _lastToByteArray = usecTimestampNow(); - return AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(), - hasFlagsOut, dropFaceTracking, false, glm::vec3(0), nullptr, - &_outboundDataRate); + AvatarDataPacket::SendStatus sendStatus; + auto avatarByteArray = AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(), + sendStatus, dropFaceTracking, false, glm::vec3(0), nullptr, 0, &_outboundDataRate); + return avatarByteArray; } QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData, - AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, - glm::vec3 viewerPosition, QVector<JointData>* sentJointDataOut, AvatarDataRate* outboundDataRateOut) const { + AvatarDataPacket::SendStatus& sendStatus, bool dropFaceTracking, bool distanceAdjust, + glm::vec3 viewerPosition, QVector<JointData>* sentJointDataOut, int maxDataSize, AvatarDataRate* outboundDataRateOut) const { bool cullSmallChanges = (dataDetail == CullSmallData); bool sendAll = (dataDetail == SendAllData); @@ -246,11 +247,23 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent bool sendPALMinimum = (dataDetail == PALMinimum); lazyInitHeadData(); + ASSERT(maxDataSize == 0 || (size_t)maxDataSize >= AvatarDataPacket::MIN_BULK_PACKET_SIZE); + + // Leading flags, to indicate how much data is actually included in the packet... + AvatarDataPacket::HasFlags wantedFlags = 0; + AvatarDataPacket::HasFlags includedFlags = 0; + AvatarDataPacket::HasFlags extraReturnedFlags = 0; // For partial joint data. // special case, if we were asked for no data, then just include the flags all set to nothing if (dataDetail == NoData) { - AvatarDataPacket::HasFlags packetStateFlags = 0; - QByteArray avatarDataByteArray(reinterpret_cast<char*>(&packetStateFlags), sizeof(packetStateFlags)); + sendStatus.itemFlags = wantedFlags; + + QByteArray avatarDataByteArray; + if (sendStatus.sendUUID) { + avatarDataByteArray.append(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID); + } + + avatarDataByteArray.append((char*) &wantedFlags, sizeof wantedFlags); return avatarDataByteArray; } @@ -273,114 +286,141 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // 3 translations * 6 bytes = 6.48kbps // - auto parentID = getParentID(); - - bool hasAvatarGlobalPosition = true; // always include global position - bool hasAvatarOrientation = false; - bool hasAvatarBoundingBox = false; - bool hasAvatarScale = false; - bool hasLookAtPosition = false; - bool hasAudioLoudness = false; - bool hasSensorToWorldMatrix = false; - bool hasAdditionalFlags = false; - - // local position, and parent info only apply to avatars that are parented. The local position - // and the parent info can change independently though, so we track their "changed since" - // separately - bool hasParentInfo = false; - bool hasAvatarLocalPosition = false; - - bool hasFaceTrackerInfo = false; - bool hasJointData = false; - bool hasJointDefaultPoseFlags = false; - bool hasGrabJoints = false; + QUuid parentID; glm::mat4 leftFarGrabMatrix; glm::mat4 rightFarGrabMatrix; glm::mat4 mouseFarGrabMatrix; - if (sendPALMinimum) { - hasAudioLoudness = true; - } else { - hasAvatarOrientation = sendAll || rotationChangedSince(lastSentTime); - hasAvatarBoundingBox = sendAll || avatarBoundingBoxChangedSince(lastSentTime); - hasAvatarScale = sendAll || avatarScaleChangedSince(lastSentTime); - hasLookAtPosition = sendAll || lookAtPositionChangedSince(lastSentTime); - hasAudioLoudness = sendAll || audioLoudnessChangedSince(lastSentTime); - hasSensorToWorldMatrix = sendAll || sensorToWorldMatrixChangedSince(lastSentTime); - hasAdditionalFlags = sendAll || additionalFlagsChangedSince(lastSentTime); - hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime); - hasAvatarLocalPosition = hasParent() && (sendAll || - tranlationChangedSince(lastSentTime) || - parentInfoChangedSince(lastSentTime)); + if (sendStatus.itemFlags == 0) { + // New avatar ... + bool hasAvatarGlobalPosition = true; // always include global position + bool hasAvatarOrientation = false; + bool hasAvatarBoundingBox = false; + bool hasAvatarScale = false; + bool hasLookAtPosition = false; + bool hasAudioLoudness = false; + bool hasSensorToWorldMatrix = false; + bool hasJointData = false; + bool hasJointDefaultPoseFlags = false; + bool hasAdditionalFlags = false; - hasFaceTrackerInfo = !dropFaceTracking && (hasFaceTracker() || getHasScriptedBlendshapes()) && - (sendAll || faceTrackerInfoChangedSince(lastSentTime)); - hasJointData = sendAll || !sendMinimum; - hasJointDefaultPoseFlags = hasJointData; - if (hasJointData) { - bool leftValid; - leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid); - if (!leftValid) { - leftFarGrabMatrix = glm::mat4(); - } - bool rightValid; - rightFarGrabMatrix = _farGrabRightMatrixCache.get(rightValid); - if (!rightValid) { - rightFarGrabMatrix = glm::mat4(); - } - bool mouseValid; - mouseFarGrabMatrix = _farGrabMouseMatrixCache.get(mouseValid); - if (!mouseValid) { - mouseFarGrabMatrix = glm::mat4(); - } - hasGrabJoints = (leftValid || rightValid || mouseValid); + // local position, and parent info only apply to avatars that are parented. The local position + // and the parent info can change independently though, so we track their "changed since" + // separately + bool hasParentInfo = false; + bool hasAvatarLocalPosition = false; + + bool hasFaceTrackerInfo = false; + + if (sendPALMinimum) { + hasAudioLoudness = true; + } else { + hasAvatarOrientation = sendAll || rotationChangedSince(lastSentTime); + hasAvatarBoundingBox = sendAll || avatarBoundingBoxChangedSince(lastSentTime); + hasAvatarScale = sendAll || avatarScaleChangedSince(lastSentTime); + hasLookAtPosition = sendAll || lookAtPositionChangedSince(lastSentTime); + hasAudioLoudness = sendAll || audioLoudnessChangedSince(lastSentTime); + hasSensorToWorldMatrix = sendAll || sensorToWorldMatrixChangedSince(lastSentTime); + hasAdditionalFlags = sendAll || additionalFlagsChangedSince(lastSentTime); + hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime); + hasAvatarLocalPosition = hasParent() && (sendAll || + tranlationChangedSince(lastSentTime) || + parentInfoChangedSince(lastSentTime)); + + hasFaceTrackerInfo = !dropFaceTracking && (hasFaceTracker() || getHasScriptedBlendshapes()) && + (sendAll || faceTrackerInfoChangedSince(lastSentTime)); + hasJointData = !sendMinimum; + hasJointDefaultPoseFlags = hasJointData; + } + + wantedFlags = + (hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0) + | (hasAvatarBoundingBox ? AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX : 0) + | (hasAvatarOrientation ? AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION : 0) + | (hasAvatarScale ? AvatarDataPacket::PACKET_HAS_AVATAR_SCALE : 0) + | (hasLookAtPosition ? AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION : 0) + | (hasAudioLoudness ? AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS : 0) + | (hasSensorToWorldMatrix ? AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX : 0) + | (hasAdditionalFlags ? AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS : 0) + | (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0) + | (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0) + | (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0) + | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0) + | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) + | (hasJointData ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); + + sendStatus.itemFlags = wantedFlags; + sendStatus.rotationsSent = 0; + sendStatus.translationsSent = 0; + } else { // Continuing avatar ... + wantedFlags = sendStatus.itemFlags; + if (wantedFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { + // Must send joints for grab joints - + wantedFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; } } + if (wantedFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { + bool leftValid; + leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid); + if (!leftValid) { + leftFarGrabMatrix = glm::mat4(); + } + bool rightValid; + rightFarGrabMatrix = _farGrabRightMatrixCache.get(rightValid); + if (!rightValid) { + rightFarGrabMatrix = glm::mat4(); + } + bool mouseValid; + mouseFarGrabMatrix = _farGrabMouseMatrixCache.get(mouseValid); + if (!mouseValid) { + mouseFarGrabMatrix = glm::mat4(); + } + if (!(leftValid || rightValid || mouseValid)) { + wantedFlags &= ~AvatarDataPacket::PACKET_HAS_GRAB_JOINTS; + } + } + if (wantedFlags & (AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS | AvatarDataPacket::PACKET_HAS_PARENT_INFO)) { + parentID = getParentID(); + } - const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + - (hasFaceTrackerInfo ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) : 0) + - (hasJointData ? AvatarDataPacket::maxJointDataSize(_jointData.size(), hasGrabJoints) : 0) + - (hasJointDefaultPoseFlags ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0); + const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + NUM_BYTES_RFC4122_UUID + + AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) + + AvatarDataPacket::maxJointDataSize(_jointData.size(), true) + + AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()); + + if (maxDataSize == 0) { + maxDataSize = (int)byteArraySize; + } QByteArray avatarDataByteArray((int)byteArraySize, 0); unsigned char* destinationBuffer = reinterpret_cast<unsigned char*>(avatarDataByteArray.data()); - unsigned char* startPosition = destinationBuffer; - - // Leading flags, to indicate how much data is actually included in the packet... - AvatarDataPacket::HasFlags packetStateFlags = - (hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0) - | (hasAvatarBoundingBox ? AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX : 0) - | (hasAvatarOrientation ? AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION : 0) - | (hasAvatarScale ? AvatarDataPacket::PACKET_HAS_AVATAR_SCALE : 0) - | (hasLookAtPosition ? AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION : 0) - | (hasAudioLoudness ? AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS : 0) - | (hasSensorToWorldMatrix ? AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX : 0) - | (hasAdditionalFlags ? AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS : 0) - | (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0) - | (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0) - | (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0) - | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0) - | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) - | (hasGrabJoints ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); - - memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags)); - destinationBuffer += sizeof(packetStateFlags); + const unsigned char* const startPosition = destinationBuffer; + const unsigned char* const packetEnd = destinationBuffer + maxDataSize; #define AVATAR_MEMCPY(src) \ memcpy(destinationBuffer, &(src), sizeof(src)); \ destinationBuffer += sizeof(src); - if (hasAvatarGlobalPosition) { - auto startSection = destinationBuffer; - if (_overrideGlobalPosition) { - AVATAR_MEMCPY(_globalPositionOverride); - } else { - AVATAR_MEMCPY(_globalPosition); - } - +// If we want an item and there's sufficient space: +#define IF_AVATAR_SPACE(flag, space) \ + if ((wantedFlags & AvatarDataPacket::flag) \ + && (packetEnd - destinationBuffer) >= (ptrdiff_t)(space) \ + && (includedFlags |= AvatarDataPacket::flag)) + if (sendStatus.sendUUID) { + memcpy(destinationBuffer, getSessionUUID().toRfc4122(), NUM_BYTES_RFC4122_UUID); + destinationBuffer += NUM_BYTES_RFC4122_UUID; + } + + unsigned char * packetFlagsLocation = destinationBuffer; + destinationBuffer += sizeof(wantedFlags); + + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { + auto startSection = destinationBuffer; + AVATAR_MEMCPY(_globalPosition); + int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { @@ -388,7 +428,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAvatarBoundingBox) { + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_BOUNDING_BOX, sizeof _globalBoundingBoxDimensions + sizeof _globalBoundingBoxOffset) { auto startSection = destinationBuffer; AVATAR_MEMCPY(_globalBoundingBoxDimensions); AVATAR_MEMCPY(_globalBoundingBoxOffset); @@ -399,7 +439,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAvatarOrientation) { + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_ORIENTATION, sizeof(AvatarDataPacket::SixByteQuat)) { auto startSection = destinationBuffer; auto localOrientation = getOrientationOutbound(); destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation); @@ -410,7 +450,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAvatarScale) { + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_SCALE, sizeof(AvatarDataPacket::AvatarScale)) { auto startSection = destinationBuffer; auto data = reinterpret_cast<AvatarDataPacket::AvatarScale*>(destinationBuffer); auto scale = getDomainLimitedScale(); @@ -423,7 +463,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasLookAtPosition) { + IF_AVATAR_SPACE(PACKET_HAS_LOOK_AT_POSITION, sizeof(_headData->getLookAtPosition()) ) { auto startSection = destinationBuffer; AVATAR_MEMCPY(_headData->getLookAtPosition()); int numBytes = destinationBuffer - startSection; @@ -432,7 +472,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAudioLoudness) { + IF_AVATAR_SPACE(PACKET_HAS_AUDIO_LOUDNESS, sizeof(AvatarDataPacket::AudioLoudness)) { auto startSection = destinationBuffer; auto data = reinterpret_cast<AvatarDataPacket::AudioLoudness*>(destinationBuffer); data->audioLoudness = packFloatGainToByte(getAudioLoudness() / AUDIO_LOUDNESS_SCALE); @@ -444,7 +484,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasSensorToWorldMatrix) { + IF_AVATAR_SPACE(PACKET_HAS_SENSOR_TO_WORLD_MATRIX, sizeof(AvatarDataPacket::SensorToWorldMatrix)) { auto startSection = destinationBuffer; auto data = reinterpret_cast<AvatarDataPacket::SensorToWorldMatrix*>(destinationBuffer); glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); @@ -462,7 +502,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAdditionalFlags) { + IF_AVATAR_SPACE(PACKET_HAS_ADDITIONAL_FLAGS, sizeof (uint16_t)) { auto startSection = destinationBuffer; auto data = reinterpret_cast<AvatarDataPacket::AdditionalFlags*>(destinationBuffer); @@ -510,7 +550,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasParentInfo) { + IF_AVATAR_SPACE(PACKET_HAS_PARENT_INFO, sizeof(AvatarDataPacket::ParentInfo)) { auto startSection = destinationBuffer; auto parentInfo = reinterpret_cast<AvatarDataPacket::ParentInfo*>(destinationBuffer); QByteArray referentialAsBytes = parentID.toRfc4122(); @@ -524,7 +564,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAvatarLocalPosition) { + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_LOCAL_POSITION, sizeof(getLocalPosition()) ) { auto startSection = destinationBuffer; const auto localPosition = getLocalPosition(); AVATAR_MEMCPY(localPosition); @@ -535,11 +575,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } + const auto& blendshapeCoefficients = _headData->getBlendshapeCoefficients(); // If it is connected, pack up the data - if (hasFaceTrackerInfo) { + IF_AVATAR_SPACE(PACKET_HAS_FACE_TRACKER_INFO, sizeof(AvatarDataPacket::FaceTrackerInfo) + (size_t)blendshapeCoefficients.size() * sizeof(float)) { auto startSection = destinationBuffer; auto faceTrackerInfo = reinterpret_cast<AvatarDataPacket::FaceTrackerInfo*>(destinationBuffer); - const auto& blendshapeCoefficients = _headData->getBlendshapeCoefficients(); // note: we don't use the blink and average loudness, we just use the numBlendShapes and // compute the procedural info on the client side. faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink; @@ -559,125 +599,125 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } QVector<JointData> jointData; - if (hasJointData || hasJointDefaultPoseFlags) { + if (wantedFlags & (AvatarDataPacket::PACKET_HAS_JOINT_DATA | AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS)) { QReadLocker readLock(&_jointDataLock); jointData = _jointData; } + const int numJoints = jointData.size(); + assert(numJoints <= 255); + const int jointBitVectorSize = calcBitVectorSize(numJoints); - // If it is connected, pack up the data - if (hasJointData) { + // Start joints if room for at least the faux joints. + IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, 1 + 2 * jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE) { + // Allow for faux joints + translation bit-vector: + const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) + + jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE; auto startSection = destinationBuffer; // joint rotation data - int numJoints = jointData.size(); *destinationBuffer++ = (uint8_t)numJoints; unsigned char* validityPosition = destinationBuffer; - unsigned char validity = 0; - int validityBit = 0; - int numValidityBytes = calcBitVectorSize(numJoints); + memset(validityPosition, 0, jointBitVectorSize); #ifdef WANT_DEBUG int rotationSentCount = 0; unsigned char* beforeRotations = destinationBuffer; #endif - destinationBuffer += numValidityBytes; // Move pointer past the validity bytes + destinationBuffer += jointBitVectorSize; // Move pointer past the validity bytes // sentJointDataOut and lastSentJointData might be the same vector if (sentJointDataOut) { sentJointDataOut->resize(numJoints); // Make sure the destination is resized before using it } + const JointData *const joints = jointData.data(); + JointData *const sentJoints = sentJointDataOut ? sentJointDataOut->data() : nullptr; float minRotationDOT = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinRotationDOT(viewerPosition) : AVATAR_MIN_ROTATION_DOT; - for (int i = 0; i < jointData.size(); i++) { - const JointData& data = jointData[i]; + int i = sendStatus.rotationsSent; + for (; i < numJoints; ++i) { + const JointData& data = joints[i]; const JointData& last = lastSentJointData[i]; - if (!data.rotationIsDefaultPose) { - // The dot product for larger rotations is a lower number. - // So if the dot() is less than the value, then the rotation is a larger angle of rotation - if (sendAll || last.rotationIsDefaultPose || (!cullSmallChanges && last.rotation != data.rotation) - || (cullSmallChanges && fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT) ) { - validity |= (1 << validityBit); + if (packetEnd - destinationBuffer >= minSizeForJoint) { + if (!data.rotationIsDefaultPose) { + // The dot product for larger rotations is a lower number, + // so if the dot() is less than the value, then the rotation is a larger angle of rotation + if (sendAll || last.rotationIsDefaultPose || (!cullSmallChanges && last.rotation != data.rotation) + || (cullSmallChanges && fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT)) { + validityPosition[i / BITS_IN_BYTE] |= 1 << (i % BITS_IN_BYTE); #ifdef WANT_DEBUG - rotationSentCount++; + rotationSentCount++; #endif - destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); - if (sentJointDataOut) { - (*sentJointDataOut)[i].rotation = data.rotation; + if (sentJoints) { + sentJoints[i].rotation = data.rotation; + } } } + } else { + break; } - if (sentJointDataOut) { - (*sentJointDataOut)[i].rotationIsDefaultPose = data.rotationIsDefaultPose; + if (sentJoints) { + sentJoints[i].rotationIsDefaultPose = data.rotationIsDefaultPose; } - if (++validityBit == BITS_IN_BYTE) { - *validityPosition++ = validity; - validityBit = validity = 0; - } - } - if (validityBit != 0) { - *validityPosition++ = validity; } + sendStatus.rotationsSent = i; // joint translation data validityPosition = destinationBuffer; - validity = 0; - validityBit = 0; #ifdef WANT_DEBUG int translationSentCount = 0; unsigned char* beforeTranslations = destinationBuffer; #endif - destinationBuffer += numValidityBytes; // Move pointer past the validity bytes + memset(destinationBuffer, 0, jointBitVectorSize); + destinationBuffer += jointBitVectorSize; // Move pointer past the validity bytes float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_TRANSLATION; float maxTranslationDimension = 0.0; - for (int i = 0; i < jointData.size(); i++) { - const JointData& data = jointData[i]; + i = sendStatus.translationsSent; + for (; i < numJoints; ++i) { + const JointData& data = joints[i]; const JointData& last = lastSentJointData[i]; - if (!data.translationIsDefaultPose) { - if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) - || (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) { - - validity |= (1 << validityBit); + if (packetEnd - destinationBuffer >= minSizeForJoint) { + if (!data.translationIsDefaultPose) { + if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) + || (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) { + validityPosition[i / BITS_IN_BYTE] |= 1 << (i % BITS_IN_BYTE); #ifdef WANT_DEBUG - translationSentCount++; + translationSentCount++; #endif - maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); - destinationBuffer += - packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); + destinationBuffer += + packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); - if (sentJointDataOut) { - (*sentJointDataOut)[i].translation = data.translation; + if (sentJoints) { + sentJoints[i].translation = data.translation; + } } } + } else { + break; } - if (sentJointDataOut) { - (*sentJointDataOut)[i].translationIsDefaultPose = data.translationIsDefaultPose; + if (sentJoints) { + sentJoints[i].translationIsDefaultPose = data.translationIsDefaultPose; } - if (++validityBit == BITS_IN_BYTE) { - *validityPosition++ = validity; - validityBit = validity = 0; - } - } - - if (validityBit != 0) { - *validityPosition++ = validity; } + sendStatus.translationsSent = i; // faux joints Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); @@ -690,7 +730,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), TRANSLATION_COMPRESSION_RADIX); - if (hasGrabJoints) { + IF_AVATAR_SPACE(PACKET_HAS_GRAB_JOINTS, sizeof (AvatarDataPacket::FarGrabJoints)) { // the far-grab joints may range further than 3 meters, so we can't use packFloatVec3ToSignedTwoByteFixed etc auto startSection = destinationBuffer; @@ -732,18 +772,20 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } #endif + if (sendStatus.rotationsSent != numJoints || sendStatus.translationsSent != numJoints) { + extraReturnedFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; + } + int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { outboundDataRateOut->jointDataRate.increment(numBytes); } - } - - if (hasJointDefaultPoseFlags) { + + IF_AVATAR_SPACE(PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS, 1 + 2 * jointBitVectorSize) { auto startSection = destinationBuffer; // write numJoints - int numJoints = jointData.size(); *destinationBuffer++ = (uint8_t)numJoints; // write rotationIsDefaultPose bits @@ -762,6 +804,10 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } + memcpy(packetFlagsLocation, &includedFlags, sizeof(includedFlags)); + // Return dropped items. + sendStatus.itemFlags = (wantedFlags & ~includedFlags) | extraReturnedFlags; + int avatarDataSize = destinationBuffer - startPosition; if (avatarDataSize > (int)byteArraySize) { @@ -770,6 +816,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } return avatarDataByteArray.left(avatarDataSize); + +#undef AVATAR_MEMCPY +#undef IF_AVATAR_SPACE } // NOTE: This is never used in a "distanceAdjust" mode, so it's ok that it doesn't use a variable minimum rotation/translation @@ -886,20 +935,32 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { offset = glm::vec3(row * SPACE_BETWEEN_AVATARS, 0.0f, col * SPACE_BETWEEN_AVATARS); } - auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset; - if (_globalPosition != newValue) { - _globalPosition = newValue; - _globalPositionChanged = now; + _serverPosition = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset; + if (_isClientAvatar) { + auto oneStepDistance = glm::length(_globalPosition - _serverPosition); + if (oneStepDistance <= AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE || oneStepDistance >= AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE) { + _globalPosition = _serverPosition; + // if we don't have a parent, make sure to also set our local position + if (!hasParent()) { + setLocalPosition(_serverPosition); + } + } + if (_globalPosition != _serverPosition) { + _globalPositionChanged = now; + } + } else { + if (_globalPosition != _serverPosition) { + _globalPosition = _serverPosition; + _globalPositionChanged = now; + } + if (!hasParent()) { + setLocalPosition(_serverPosition); + } } sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); int numBytesRead = sourceBuffer - startSection; _globalPositionRate.increment(numBytesRead); _globalPositionUpdateRate.increment(); - - // if we don't have a parent, make sure to also set our local position - if (!hasParent()) { - setLocalPosition(newValue); - } } if (hasAvatarBoundingBox) { @@ -920,6 +981,8 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _avatarBoundingBoxChanged = now; } + _defaultBubbleBox = computeBubbleBox(); + sourceBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); int numBytesRead = sourceBuffer - startSection; _avatarBoundingBoxRate.increment(numBytesRead); @@ -1729,11 +1792,9 @@ glm::quat AvatarData::getOrientationOutbound() const { return (getLocalOrientation()); } -void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, +void AvatarData::processAvatarIdentity(QDataStream& packetStream, bool& identityChanged, bool& displayNameChanged) { - QDataStream packetStream(identityData); - QUuid avatarSessionID; // peek the sequence number, this will tell us if we should be processing this identity packet at all @@ -1748,17 +1809,18 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide << (udt::SequenceNumber::Type) incomingSequenceNumber; } - if (incomingSequenceNumber > _identitySequenceNumber) { - Identity identity; + Identity identity; - packetStream - >> identity.attachmentData - >> identity.displayName - >> identity.sessionDisplayName - >> identity.isReplicated - >> identity.lookAtSnappingEnabled + packetStream + >> identity.attachmentData + >> identity.displayName + >> identity.sessionDisplayName + >> identity.isReplicated + >> identity.lookAtSnappingEnabled ; + if (incomingSequenceNumber > _identitySequenceNumber) { + // set the store identity sequence number to match the incoming identity _identitySequenceNumber = incomingSequenceNumber; @@ -2114,10 +2176,6 @@ void AvatarData::sendAvatarDataPacket(bool sendAll) { } } - if (_overrideGlobalPosition) { - _overrideGlobalPosition = false; - } - doneEncoding(cullSmallData); static AvatarDataSequenceNumber sequenceNumber = 0; @@ -2161,11 +2219,21 @@ void AvatarData::updateJointMappings() { } if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { + //// + // TODO: Should we rely upon HTTPResourceRequest for ResourceRequestObserver instead? + // HTTPResourceRequest::doSend() covers all of the following and + // then some. It doesn't cover the connect() call, so we may + // want to add a HTTPResourceRequest::doSend() method that does + // connects. QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL); networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + DependencyManager::get<ResourceRequestObserver>()->update( + _skeletonModelURL, -1, "AvatarData::updateJointMappings"); QNetworkReply* networkReply = networkAccessManager.get(networkRequest); + // + //// connect(networkReply, &QNetworkReply::finished, this, &AvatarData::setJointMappingsFromNetworkReply); } } @@ -2898,3 +2966,21 @@ void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap& value[EntityID] = binaryEntityProperties; } } + +const float AvatarData::DEFAULT_BUBBLE_SCALE = 2.4f; // magic number determined empirically + +AABox AvatarData::computeBubbleBox(float bubbleScale) const { + AABox box = AABox(_globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); + glm::vec3 size = box.getScale(); + size *= bubbleScale; + const glm::vec3 MIN_BUBBLE_SCALE(0.3f, 1.3f, 0.3); + size= glm::max(size, MIN_BUBBLE_SCALE); + box.setScaleStayCentered(size); + return box; +} + +AABox AvatarData::getDefaultBubbleBox() const { + AABox bubbleBox(_defaultBubbleBox); + bubbleBox.translate(_globalPosition); + return bubbleBox; +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 46489451f7..48593de212 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -296,6 +296,17 @@ namespace AvatarDataPacket { } PACKED_END; const size_t FAR_GRAB_JOINTS_SIZE = 84; static_assert(sizeof(FarGrabJoints) == FAR_GRAB_JOINTS_SIZE, "AvatarDataPacket::FarGrabJoints size doesn't match."); + + static const size_t MIN_BULK_PACKET_SIZE = NUM_BYTES_RFC4122_UUID + HEADER_SIZE; + static const size_t FAUX_JOINTS_SIZE = 2 * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); + + struct SendStatus { + HasFlags itemFlags { 0 }; + bool sendUUID { false }; + int rotationsSent { 0 }; // ie: index of next unsent joint + int translationsSent { 0 }; + operator bool() { return itemFlags == 0; } + }; } const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation @@ -327,6 +338,17 @@ const float AVATAR_DISTANCE_LEVEL_5 = 200.0f; // meters // This is the start location in the Sandbox (xyz: 6270, 211, 6000). const glm::vec3 START_LOCATION(6270, 211, 6000); +// Avatar Transit Constants +const float AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE = 1.0f; +const float AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE = 30.0f; +const int AVATAR_TRANSIT_FRAME_COUNT = 11; +const float AVATAR_TRANSIT_FRAMES_PER_METER = 0.5f; +const float AVATAR_TRANSIT_ABORT_DISTANCE = 0.1f; +const bool AVATAR_TRANSIT_DISTANCE_BASED = true; +const float AVATAR_TRANSIT_FRAMES_PER_SECOND = 30.0f; +const float AVATAR_PRE_TRANSIT_FRAME_COUNT = 10.0f; +const float AVATAR_POST_TRANSIT_FRAME_COUNT = 27.0f; + enum KeyState { NO_KEY_DOWN = 0, INSERT_KEY_DOWN, @@ -452,8 +474,8 @@ public: virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false); virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData, - AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, - QVector<JointData>* sentJointDataOut, AvatarDataRate* outboundDataRateOut = nullptr) const; + AvatarDataPacket::SendStatus& sendStatus, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, + QVector<JointData>* sentJointDataOut, int maxDataSize = 0, AvatarDataRate* outboundDataRateOut = nullptr) const; virtual void doneEncoding(bool cullSmallChanges); @@ -960,7 +982,7 @@ public: // identityChanged returns true if identity has changed, false otherwise. // identityChanged returns true if identity has changed, false otherwise. Similarly for displayNameChanged and skeletonModelUrlChange. - void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged); + void processAvatarIdentity(QDataStream& packetStream, bool& identityChanged, bool& displayNameChanged); qint64 packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION); @@ -1101,6 +1123,7 @@ public: glm::vec3 getClientGlobalPosition() const { return _globalPosition; } AABox getGlobalBoundingBox() const { return AABox(_globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); } + AABox getDefaultBubbleBox() const; /**jsdoc * @function MyAvatar.getAvatarEntityData @@ -1193,8 +1216,12 @@ public: void setReplicaIndex(int replicaIndex) { _replicaIndex = replicaIndex; } int getReplicaIndex() { return _replicaIndex; } + static const float DEFAULT_BUBBLE_SCALE; /* = 2.4 */ + AABox computeBubbleBox(float bubbleScale = DEFAULT_BUBBLE_SCALE) const; + void setIsNewAvatar(bool isNewAvatar) { _isNewAvatar = isNewAvatar; } bool getIsNewAvatar() { return _isNewAvatar; } + void setIsClientAvatar(bool isClientAvatar) { _isClientAvatar = isClientAvatar; } signals: @@ -1378,8 +1405,7 @@ protected: // where Entities are located. This is currently only used by the mixer to decide how often to send // updates about one avatar to another. glm::vec3 _globalPosition { 0, 0, 0 }; - glm::vec3 _globalPositionOverride { 0, 0, 0 }; - bool _overrideGlobalPosition { false }; + glm::vec3 _serverPosition { 0, 0, 0 }; quint64 _globalPositionChanged { 0 }; quint64 _avatarBoundingBoxChanged { 0 }; @@ -1430,6 +1456,8 @@ protected: glm::vec3 _globalBoundingBoxDimensions; glm::vec3 _globalBoundingBoxOffset; + AABox _defaultBubbleBox; + mutable ReadWriteLockable _avatarEntitiesLock; AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar AvatarEntityIDs _avatarEntityForRecording; // create new entities id for avatar recording @@ -1459,6 +1487,7 @@ protected: float _density; int _replicaIndex { 0 }; bool _isNewAvatar { true }; + bool _isClientAvatar { false }; // null unless MyAvatar or ScriptableAvatar sending traits data to mixer std::unique_ptr<ClientTraitsHandler> _clientTraitsHandler; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 162dc86c37..41ca950b3b 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -85,8 +85,9 @@ std::vector<AvatarSharedPointer> AvatarReplicas::takeReplicas(const QUuid& paren void AvatarReplicas::processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged) { if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; + QDataStream identityDataStream(identityData); for (auto avatar : replicas) { - avatar->processAvatarIdentity(identityData, identityChanged, displayNameChanged); + avatar->processAvatarIdentity(identityDataStream, identityChanged, displayNameChanged); } } } @@ -284,39 +285,45 @@ AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointer<ReceivedMessag } void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) { + QDataStream avatarIdentityStream(message->getMessage()); - // peek the avatar UUID from the incoming packet - QUuid identityUUID = QUuid::fromRfc4122(message->peek(NUM_BYTES_RFC4122_UUID)); + while (!avatarIdentityStream.atEnd()) { + // peek the avatar UUID from the incoming packet + avatarIdentityStream.startTransaction(); + QUuid identityUUID; + avatarIdentityStream >> identityUUID; + avatarIdentityStream.rollbackTransaction(); - if (identityUUID.isNull()) { - qCDebug(avatars) << "Refusing to process identity packet for null avatar ID"; - return; - } - - // make sure this isn't for an ignored avatar - auto nodeList = DependencyManager::get<NodeList>(); - static auto EMPTY = QUuid(); - - { - QReadLocker locker(&_hashLock); - auto me = _avatarHash.find(EMPTY); - if ((me != _avatarHash.end()) && (identityUUID == me.value()->getSessionUUID())) { - // We add MyAvatar to _avatarHash with an empty UUID. Code relies on this. In order to correctly handle an - // identity packet for ourself (such as when we are assigned a sessionDisplayName by the mixer upon joining), - // we make things match here. - identityUUID = EMPTY; + if (identityUUID.isNull()) { + qCDebug(avatars) << "Refusing to process identity packet for null avatar ID"; + return; + } + + // make sure this isn't for an ignored avatar + auto nodeList = DependencyManager::get<NodeList>(); + static auto EMPTY = QUuid(); + + { + QReadLocker locker(&_hashLock); + auto me = _avatarHash.find(EMPTY); + if ((me != _avatarHash.end()) && (identityUUID == me.value()->getSessionUUID())) { + // We add MyAvatar to _avatarHash with an empty UUID. Code relies on this. In order to correctly handle an + // identity packet for ourself (such as when we are assigned a sessionDisplayName by the mixer upon joining), + // we make things match here. + identityUUID = EMPTY; + } + } + + if (!nodeList->isIgnoringNode(identityUUID) || nodeList->getRequestsDomainListData()) { + // mesh URL for a UUID, find avatar in our list + bool isNewAvatar; + auto avatar = newOrExistingAvatar(identityUUID, sendingNode, isNewAvatar); + bool identityChanged = false; + bool displayNameChanged = false; + // In this case, the "sendingNode" is the Avatar Mixer. + avatar->processAvatarIdentity(avatarIdentityStream, identityChanged, displayNameChanged); + _replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged); } - } - - if (!nodeList->isIgnoringNode(identityUUID) || nodeList->getRequestsDomainListData()) { - // mesh URL for a UUID, find avatar in our list - bool isNewAvatar; - auto avatar = newOrExistingAvatar(identityUUID, sendingNode, isNewAvatar); - bool identityChanged = false; - bool displayNameChanged = false; - // In this case, the "sendingNode" is the Avatar Mixer. - avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged); - _replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged); } } diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index a06b53da7c..f8247d9e52 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -31,7 +31,27 @@ ClientTraitsHandler::ClientTraitsHandler(AvatarData* owningAvatar) : nodeList->getPacketReceiver().registerListener(PacketType::SetAvatarTraits, this, "processTraitOverride"); } +void ClientTraitsHandler::markTraitUpdated(AvatarTraits::TraitType updatedTrait) { + Lock lock(_traitLock); + _traitStatuses[updatedTrait] = Updated; + _hasChangedTraits = true; +} + +void ClientTraitsHandler::markInstancedTraitUpdated(AvatarTraits::TraitType traitType, QUuid updatedInstanceID) { + Lock lock(_traitLock); + _traitStatuses.instanceInsert(traitType, updatedInstanceID, Updated); + _hasChangedTraits = true; +} + +void ClientTraitsHandler::markInstancedTraitDeleted(AvatarTraits::TraitType traitType, QUuid deleteInstanceID) { + Lock lock(_traitLock); + _traitStatuses.instanceInsert(traitType, deleteInstanceID, Deleted); + _hasChangedTraits = true; +} + void ClientTraitsHandler::resetForNewMixer() { + Lock lock(_traitLock); + // re-set the current version to 0 _currentTraitVersion = AvatarTraits::DEFAULT_TRAIT_VERSION; @@ -46,6 +66,8 @@ void ClientTraitsHandler::resetForNewMixer() { } void ClientTraitsHandler::sendChangedTraitsToMixer() { + Lock lock(_traitLock); + if (hasChangedTraits() || _shouldPerformInitialSend) { // we have at least one changed trait to send @@ -113,6 +135,7 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { void ClientTraitsHandler::processTraitOverride(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) { if (sendingNode->getType() == NodeType::AvatarMixer) { + Lock lock(_traitLock); while (message->getBytesLeftToRead()) { AvatarTraits::TraitType traitType; message->readPrimitive(&traitType); diff --git a/libraries/avatars/src/ClientTraitsHandler.h b/libraries/avatars/src/ClientTraitsHandler.h index 27ba58d46b..3900268101 100644 --- a/libraries/avatars/src/ClientTraitsHandler.h +++ b/libraries/avatars/src/ClientTraitsHandler.h @@ -26,14 +26,11 @@ public: void sendChangedTraitsToMixer(); - bool hasChangedTraits() { return _hasChangedTraits; } + bool hasChangedTraits() const { return _hasChangedTraits; } - void markTraitUpdated(AvatarTraits::TraitType updatedTrait) - { _traitStatuses[updatedTrait] = Updated; _hasChangedTraits = true; } - void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, QUuid updatedInstanceID) - { _traitStatuses.instanceInsert(traitType, updatedInstanceID, Updated); _hasChangedTraits = true; } - void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, QUuid deleteInstanceID) - { _traitStatuses.instanceInsert(traitType, deleteInstanceID, Deleted); _hasChangedTraits = true; } + void markTraitUpdated(AvatarTraits::TraitType updatedTrait); + void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, QUuid updatedInstanceID); + void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, QUuid deleteInstanceID); void resetForNewMixer(); @@ -41,17 +38,21 @@ public slots: void processTraitOverride(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode); private: + using Mutex = std::recursive_mutex; + using Lock = std::lock_guard<Mutex>; + enum ClientTraitStatus { Unchanged, Updated, Deleted }; - AvatarData* _owningAvatar; + AvatarData* const _owningAvatar; + Mutex _traitLock; AvatarTraits::AssociatedTraitValues<ClientTraitStatus, Unchanged> _traitStatuses; - AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION }; + AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION }; AvatarTraits::TraitVersion _currentSkeletonVersion { AvatarTraits::NULL_TRAIT_VERSION }; bool _shouldPerformInitialSend { false }; diff --git a/libraries/baking/src/FBXBaker.cpp b/libraries/baking/src/FBXBaker.cpp index b90082d969..cef6c9b900 100644 --- a/libraries/baking/src/FBXBaker.cpp +++ b/libraries/baking/src/FBXBaker.cpp @@ -206,7 +206,7 @@ void FBXBaker::importScene() { } #endif - _geometry = reader.extractFBXGeometry({}, _modelURL.toString()); + _hfmModel = reader.extractHFMModel({}, _modelURL.toString()); _textureContentMap = reader._textureContent; } @@ -231,7 +231,7 @@ void FBXBaker::rewriteAndBakeSceneModels() { for (FBXNode& objectChild : rootChild.children) { if (objectChild.name == "Geometry") { - // TODO Pull this out of _geometry instead so we don't have to reprocess it + // TODO Pull this out of _hfmModel instead so we don't have to reprocess it auto extractedMesh = FBXReader::extractMesh(objectChild, meshIndex, false); // Callback to get MaterialID @@ -293,7 +293,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() { QHash<QString, image::TextureUsage::Type> textureTypes; // enumerate the materials in the extracted geometry so we can determine the texture type for each texture ID - for (const auto& material : _geometry->materials) { + for (const auto& material : _hfmModel->materials) { if (material.normalTexture.isBumpmap) { textureTypes[material.normalTexture.id] = BUMP_TEXTURE; } else { @@ -329,7 +329,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() { for (FBXNode& textureChild : object->children) { if (textureChild.name == "RelativeFilename") { - QString fbxTextureFileName { textureChild.properties.at(0).toString() }; + QString hfmTextureFileName { textureChild.properties.at(0).toString() }; // grab the ID for this texture so we can figure out the // texture type from the loaded materials @@ -337,7 +337,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() { auto textureType = textureTypes[textureID]; // Compress the texture information and return the new filename to be added into the FBX scene - auto bakedTextureFile = compressTexture(fbxTextureFileName, textureType); + auto bakedTextureFile = compressTexture(hfmTextureFileName, textureType); // If no errors or warnings have occurred during texture compression add the filename to the FBX scene if (!bakedTextureFile.isNull()) { diff --git a/libraries/baking/src/FBXBaker.h b/libraries/baking/src/FBXBaker.h index 9d41209d4c..2af51b2190 100644 --- a/libraries/baking/src/FBXBaker.h +++ b/libraries/baking/src/FBXBaker.h @@ -53,7 +53,7 @@ private: void rewriteAndBakeSceneModels(); void rewriteAndBakeSceneTextures(); - FBXGeometry* _geometry; + HFMModel* _hfmModel; QHash<QString, int> _textureNameMatchCount; QHash<QUrl, QString> _remappedTexturePaths; diff --git a/libraries/baking/src/ModelBaker.cpp b/libraries/baking/src/ModelBaker.cpp index 75e10c54ab..ca352cebae 100644 --- a/libraries/baking/src/ModelBaker.cpp +++ b/libraries/baking/src/ModelBaker.cpp @@ -75,7 +75,7 @@ void ModelBaker::abort() { } } -bool ModelBaker::compressMesh(FBXMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback) { +bool ModelBaker::compressMesh(HFMMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback) { if (mesh.wasCompressed) { handleError("Cannot re-bake a file that contains compressed mesh"); return false; diff --git a/libraries/baking/src/ModelBaker.h b/libraries/baking/src/ModelBaker.h index 1fd77ab761..cda4478b1d 100644 --- a/libraries/baking/src/ModelBaker.h +++ b/libraries/baking/src/ModelBaker.h @@ -39,7 +39,7 @@ public: const QString& bakedOutputDirectory, const QString& originalOutputDirectory = ""); virtual ~ModelBaker(); - bool compressMesh(FBXMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback = nullptr); + bool compressMesh(HFMMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback = nullptr); QString compressTexture(QString textureFileName, image::TextureUsage::Type = image::TextureUsage::Type::DEFAULT_TEXTURE); virtual void setWasAborted(bool wasAborted) override; diff --git a/libraries/baking/src/OBJBaker.cpp b/libraries/baking/src/OBJBaker.cpp index cf62bc4fa8..d9f56b393e 100644 --- a/libraries/baking/src/OBJBaker.cpp +++ b/libraries/baking/src/OBJBaker.cpp @@ -153,7 +153,7 @@ void OBJBaker::bakeOBJ() { checkIfTexturesFinished(); } -void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) { +void OBJBaker::createFBXNodeTree(FBXNode& rootNode, HFMModel& hfmModel) { // Generating FBX Header Node FBXNode headerNode; headerNode.name = FBX_HEADER_EXTENSION; @@ -199,7 +199,7 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) { // Compress the mesh information and store in dracoNode bool hasDeformers = false; // No concept of deformers for an OBJ FBXNode dracoNode; - compressMesh(geometry.meshes[0], hasDeformers, dracoNode); + compressMesh(hfmModel.meshes[0], hasDeformers, dracoNode); geometryNode.children.append(dracoNode); // Generating Object node's child - Model node @@ -214,17 +214,17 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) { objectNode.children = { geometryNode, modelNode }; // Generating Objects node's child - Material node - auto& meshParts = geometry.meshes[0].parts; + auto& meshParts = hfmModel.meshes[0].parts; for (auto& meshPart : meshParts) { FBXNode materialNode; materialNode.name = MATERIAL_NODE_NAME; - if (geometry.materials.size() == 1) { + if (hfmModel.materials.size() == 1) { // case when no material information is provided, OBJReader considers it as a single default material - for (auto& materialID : geometry.materials.keys()) { - setMaterialNodeProperties(materialNode, materialID, geometry); + for (auto& materialID : hfmModel.materials.keys()) { + setMaterialNodeProperties(materialNode, materialID, hfmModel); } } else { - setMaterialNodeProperties(materialNode, meshPart.materialID, geometry); + setMaterialNodeProperties(materialNode, meshPart.materialID, hfmModel); } objectNode.children.append(materialNode); @@ -235,7 +235,7 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) { auto size = meshParts.size(); for (int i = 0; i < size; i++) { QString material = meshParts[i].materialID; - FBXMaterial currentMaterial = geometry.materials[material]; + HFMMaterial currentMaterial = hfmModel.materials[material]; if (!currentMaterial.albedoTexture.filename.isEmpty() || !currentMaterial.specularTexture.filename.isEmpty()) { auto textureID = nextNodeID(); _mapTextureMaterial.emplace_back(textureID, i); @@ -325,12 +325,12 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) { } // Set properties for material nodes -void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, QString material, FBXGeometry& geometry) { +void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, QString material, HFMModel& hfmModel) { auto materialID = nextNodeID(); _materialIDs.push_back(materialID); materialNode.properties = { materialID, material, MESH }; - FBXMaterial currentMaterial = geometry.materials[material]; + HFMMaterial currentMaterial = hfmModel.materials[material]; // Setting the hierarchy: Material -> Properties70 -> P -> Properties FBXNode properties70Node; diff --git a/libraries/baking/src/OBJBaker.h b/libraries/baking/src/OBJBaker.h index 8e49692d35..5aaae49d4a 100644 --- a/libraries/baking/src/OBJBaker.h +++ b/libraries/baking/src/OBJBaker.h @@ -39,8 +39,8 @@ private slots: private: void loadOBJ(); - void createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry); - void setMaterialNodeProperties(FBXNode& materialNode, QString material, FBXGeometry& geometry); + void createFBXNodeTree(FBXNode& rootNode, HFMModel& hfmModel); + void setMaterialNodeProperties(FBXNode& materialNode, QString material, HFMModel& hfmModel); NodeID nextNodeID() { return _nodeID++; } diff --git a/libraries/controllers/src/controllers/impl/Filter.cpp b/libraries/controllers/src/controllers/impl/Filter.cpp index 6e6dc816d0..f230fb83dc 100644 --- a/libraries/controllers/src/controllers/impl/Filter.cpp +++ b/libraries/controllers/src/controllers/impl/Filter.cpp @@ -31,6 +31,7 @@ #include "filters/RotateFilter.h" #include "filters/LowVelocityFilter.h" #include "filters/ExponentialSmoothingFilter.h" +#include "filters/AccelerationLimiterFilter.h" using namespace controller; @@ -51,6 +52,7 @@ REGISTER_FILTER_CLASS_INSTANCE(PostTransformFilter, "postTransform") REGISTER_FILTER_CLASS_INSTANCE(RotateFilter, "rotate") REGISTER_FILTER_CLASS_INSTANCE(LowVelocityFilter, "lowVelocity") REGISTER_FILTER_CLASS_INSTANCE(ExponentialSmoothingFilter, "exponentialSmoothing") +REGISTER_FILTER_CLASS_INSTANCE(AccelerationLimiterFilter, "accelerationLimiter") const QString JSON_FILTER_TYPE = QStringLiteral("type"); const QString JSON_FILTER_PARAMS = QStringLiteral("params"); diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp new file mode 100644 index 0000000000..3db1a9fba6 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp @@ -0,0 +1,192 @@ +// +// Created by Anthony Thibault 2018/11/09 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include "AccelerationLimiterFilter.h" + +#include <QtCore/QJsonObject> +#include <QtCore/QJsonArray> +#include "../../UserInputMapper.h" +#include "../../Input.h" +#include <DependencyManager.h> +#include <QDebug> +#include <StreamUtils.h> + +static const QString JSON_ROTATION_ACCELERATION_LIMIT = QStringLiteral("rotationAccelerationLimit"); +static const QString JSON_TRANSLATION_ACCELERATION_LIMIT = QStringLiteral("translationAccelerationLimit"); +static const QString JSON_TRANSLATION_SNAP_THRESHOLD = QStringLiteral("translationSnapThreshold"); +static const QString JSON_ROTATION_SNAP_THRESHOLD = QStringLiteral("rotationSnapThreshold"); + +static glm::vec3 angularVelFromDeltaRot(const glm::quat& deltaQ, float dt) { + // Measure the angular velocity of a delta rotation quaternion by using quaternion logarithm. + // The logarithm of a unit quternion returns the axis of rotation with a length of one half the angle of rotation in the imaginary part. + // The real part will be 0. Then we multiply it by 2 / dt. turning it into the angular velocity, (except for the extra w = 0 part). + glm::quat omegaQ((2.0f / dt) * glm::log(deltaQ)); + return glm::vec3(omegaQ.x, omegaQ.y, omegaQ.z); +} + +static glm::quat deltaRotFromAngularVel(const glm::vec3& omega, float dt) { + // Convert angular velocity into a delta quaternion by using quaternion exponent. + // The exponent of quaternion will return a delta rotation around the axis of the imaginary part, by twice the angle as determined by the length of that imaginary part. + // It is the inverse of the logarithm step in angularVelFromDeltaRot + glm::quat omegaQ(0.0f, omega.x, omega.y, omega.z); + return glm::exp((dt / 2.0f) * omegaQ); +} + +static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3, + float dt, float accLimit, float snapThreshold) { + + // measure the linear velocities of this step and the previoius step + glm::vec3 v1 = (x3 - x1) / (2.0f * dt); + glm::vec3 v0 = (x2 - x0) / (2.0f * dt); + + // compute the acceleration + const glm::vec3 a = (v1 - v0) / dt; + + // clamp the acceleration if it is over the limit + float aLen = glm::length(a); + + // pick limit based on if we are moving faster then our target + float distToTarget = glm::length(x3 - x2); + if (aLen > accLimit && distToTarget > snapThreshold) { + // Solve for a new `v1`, such that `a` does not exceed `aLimit` + // This combines two steps: + // 1) Computing a limited accelration in the direction of `a`, but with a magnitute of `aLimit`: + // `newA = a * (aLimit / aLen)` + // 2) Computing new `v1` + // `v1 = newA * dt + v0` + // We combine the scalars from step 1 and step 2 into a single term to avoid having to do multiple scalar-vec3 multiplies. + v1 = a * ((accLimit * dt) / aLen) + v0; + + // apply limited v1 to compute filtered x3 + return v1 * dt + x2; + } else { + // did not exceed limit, no filtering necesary + return x3; + } +} + +static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In, + float dt, float accLimit, float snapThreshold) { + + // ensure quaternions have the same polarity + glm::quat q0 = q0In; + glm::quat q1 = glm::dot(q0In, q1In) < 0.0f ? -q1In : q1In; + glm::quat q2 = glm::dot(q1In, q2In) < 0.0f ? -q2In : q2In; + glm::quat q3 = glm::dot(q2In, q3In) < 0.0f ? -q3In : q3In; + + // measure the angular velocities of this step and the previous step + glm::vec3 w1 = angularVelFromDeltaRot(q3 * glm::inverse(q1), 2.0f * dt); + glm::vec3 w0 = angularVelFromDeltaRot(q2 * glm::inverse(q0), 2.0f * dt); + + const glm::vec3 a = (w1 - w0) / dt; + float aLen = glm::length(a); + + // clamp the acceleration if it is over the limit + float angleToTarget = glm::angle(q3 * glm::inverse(q2)); + if (aLen > accLimit && angleToTarget > snapThreshold) { + // solve for a new w1, such that a does not exceed the accLimit + w1 = a * ((accLimit * dt) / aLen) + w0; + + // apply limited w1 to compute filtered q3 + return deltaRotFromAngularVel(w1, dt) * q2; + } else { + // did not exceed limit, no filtering necesary + return q3; + } +} + +namespace controller { + + Pose AccelerationLimiterFilter::apply(Pose value) const { + + if (value.isValid()) { + + // to perform filtering in sensor space, we need to compute the transformations. + auto userInputMapper = DependencyManager::get<UserInputMapper>(); + const InputCalibrationData calibrationData = userInputMapper->getInputCalibrationData(); + glm::mat4 sensorToAvatarMat = glm::inverse(calibrationData.avatarMat) * calibrationData.sensorToWorldMat; + glm::mat4 avatarToSensorMat = glm::inverse(calibrationData.sensorToWorldMat) * calibrationData.avatarMat; + + // transform pose into sensor space. + Pose sensorValue = value.transform(avatarToSensorMat); + + if (_prevValid) { + + const float DELTA_TIME = 0.01111111f; + + glm::vec3 unfilteredTranslation = sensorValue.translation; + sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, + DELTA_TIME, _translationAccelerationLimit, _translationSnapThreshold); + glm::quat unfilteredRot = sensorValue.rotation; + sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, + DELTA_TIME, _rotationAccelerationLimit, _rotationSnapThreshold); + + // remember previous values. + _prevPos[0] = _prevPos[1]; + _prevPos[1] = _prevPos[2]; + _prevPos[2] = sensorValue.translation; + _prevRot[0] = _prevRot[1]; + _prevRot[1] = _prevRot[2]; + _prevRot[2] = sensorValue.rotation; + + _unfilteredPrevPos[0] = _unfilteredPrevPos[1]; + _unfilteredPrevPos[1] = _unfilteredPrevPos[2]; + _unfilteredPrevPos[2] = unfilteredTranslation; + _unfilteredPrevRot[0] = _unfilteredPrevRot[1]; + _unfilteredPrevRot[1] = _unfilteredPrevRot[2]; + _unfilteredPrevRot[2] = unfilteredRot; + + // transform back into avatar space + return sensorValue.transform(sensorToAvatarMat); + } else { + // initialize previous values with the current sample. + _prevPos[0] = sensorValue.translation; + _prevPos[1] = sensorValue.translation; + _prevPos[2] = sensorValue.translation; + _prevRot[0] = sensorValue.rotation; + _prevRot[1] = sensorValue.rotation; + _prevRot[2] = sensorValue.rotation; + + _unfilteredPrevPos[0] = sensorValue.translation; + _unfilteredPrevPos[1] = sensorValue.translation; + _unfilteredPrevPos[2] = sensorValue.translation; + _unfilteredPrevRot[0] = sensorValue.rotation; + _unfilteredPrevRot[1] = sensorValue.rotation; + _unfilteredPrevRot[2] = sensorValue.rotation; + + _prevValid = true; + + // no previous value to smooth with, so return value unchanged + return value; + } + } else { + // mark previous poses as invalid. + _prevValid = false; + + // return invalid value unchanged + return value; + } + } + + bool AccelerationLimiterFilter::parseParameters(const QJsonValue& parameters) { + if (parameters.isObject()) { + auto obj = parameters.toObject(); + if (obj.contains(JSON_ROTATION_ACCELERATION_LIMIT) && obj.contains(JSON_TRANSLATION_ACCELERATION_LIMIT) && + obj.contains(JSON_ROTATION_SNAP_THRESHOLD) && obj.contains(JSON_TRANSLATION_SNAP_THRESHOLD)) { + _rotationAccelerationLimit = (float)obj[JSON_ROTATION_ACCELERATION_LIMIT].toDouble(); + _translationAccelerationLimit = (float)obj[JSON_TRANSLATION_ACCELERATION_LIMIT].toDouble(); + _rotationSnapThreshold = (float)obj[JSON_ROTATION_SNAP_THRESHOLD].toDouble(); + _translationSnapThreshold = (float)obj[JSON_TRANSLATION_SNAP_THRESHOLD].toDouble(); + return true; + } + } + return false; + } + +} diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h new file mode 100644 index 0000000000..269fd54102 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h @@ -0,0 +1,41 @@ +// +// Created by Anthony Thibault 2018/11/09 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_Controllers_Filters_Acceleration_Limiter_h +#define hifi_Controllers_Filters_Acceleration_Limiter_h + +#include "../Filter.h" + +namespace controller { + + class AccelerationLimiterFilter : public Filter { + REGISTER_FILTER_CLASS(AccelerationLimiterFilter); + + public: + AccelerationLimiterFilter() {} + + float apply(float value) const override { return value; } + Pose apply(Pose value) const override; + bool parseParameters(const QJsonValue& parameters) override; + + private: + float _rotationAccelerationLimit { FLT_MAX }; + float _translationAccelerationLimit { FLT_MAX }; + float _rotationSnapThreshold { 0.0f }; + float _translationSnapThreshold { 0.0f }; + + mutable glm::vec3 _prevPos[3]; // sensor space + mutable glm::quat _prevRot[3]; // sensor space + mutable glm::vec3 _unfilteredPrevPos[3]; // sensor space + mutable glm::quat _unfilteredPrevRot[3]; // sensor space + mutable bool _prevValid { false }; + }; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp index 9cf2673d55..0f204ce15f 100644 --- a/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp @@ -35,7 +35,7 @@ namespace controller { if (_prevSensorValue.isValid()) { // exponential smoothing filter sensorValue.translation = _translationConstant * sensorValue.getTranslation() + (1.0f - _translationConstant) * _prevSensorValue.getTranslation(); - sensorValue.rotation = safeMix(sensorValue.getRotation(), _prevSensorValue.getRotation(), _rotationConstant); + sensorValue.rotation = safeMix(sensorValue.getRotation(), _prevSensorValue.getRotation(), (1.0f - _rotationConstant)); // remember previous sensor space value. _prevSensorValue = sensorValue; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 6448c6d3a1..190d4d4104 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -88,6 +88,7 @@ public: // Move the OpenGL context to the present thread // Extra code because of the widget 'wrapper' context _context = context; + _context->doneCurrent(); _context->moveToThread(this); } @@ -179,7 +180,9 @@ public: _context->makeCurrent(); { PROFILE_RANGE(render, "PluginPresent") + gl::globalLock(); currentPlugin->present(); + gl::globalRelease(false); CHECK_GL_ERROR(); } _context->doneCurrent(); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c6337dc872..f60bf20e3d 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -268,7 +268,7 @@ EntityItemProperties RenderableModelEntityItem::getProperties(const EntityProper if (model->isLoaded()) { // TODO: improve naturalDimensions in the future, // for now we've added this hack for setting natural dimensions of models - Extents meshExtents = model->getFBXGeometry().getUnscaledMeshExtents(); + Extents meshExtents = model->getHFMModel().getUnscaledMeshExtents(); properties.setNaturalDimensions(meshExtents.maximum - meshExtents.minimum); properties.calculateNaturalPosition(meshExtents.minimum, meshExtents.maximum); } @@ -403,7 +403,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { // should never fall in here when collision model not fully loaded // TODO: assert that all geometries exist and are loaded //assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded()); - const FBXGeometry& collisionGeometry = _compoundShapeResource->getFBXGeometry(); + const HFMModel& collisionGeometry = _compoundShapeResource->getHFMModel(); ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); pointCollection.clear(); @@ -411,15 +411,15 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. - foreach (const FBXMesh& mesh, collisionGeometry.meshes) { + foreach (const HFMMesh& mesh, collisionGeometry.meshes) { // each meshPart is a convex hull - foreach (const FBXMeshPart &meshPart, mesh.parts) { + foreach (const HFMMeshPart &meshPart, mesh.parts) { pointCollection.push_back(QVector<glm::vec3>()); ShapeInfo::PointList& pointsInPart = pointCollection[i]; // run through all the triangles and (uniquely) add each point to the hull uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size(); - // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices % TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader @@ -440,7 +440,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { // run through all the quads and (uniquely) add each point to the hull numIndices = (uint32_t)meshPart.quadIndices.size(); - // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices % QUAD_STRIDE == 0); numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXReader @@ -478,7 +478,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { // to the visual model and apply them to the collision model (without regard for the // collision model's extents). - glm::vec3 scaleToFit = dimensions / model->getFBXGeometry().getUnscaledMeshExtents().size(); + glm::vec3 scaleToFit = dimensions / model->getHFMModel().getUnscaledMeshExtents().size(); // multiply each point by scale before handing the point-set off to the physics engine. // also determine the extents of the collision model. glm::vec3 registrationOffset = dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()); @@ -498,14 +498,14 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { // compute meshPart local transforms QVector<glm::mat4> localTransforms; - const FBXGeometry& fbxGeometry = model->getFBXGeometry(); - int numFbxMeshes = fbxGeometry.meshes.size(); + const HFMModel& hfmModel = model->getHFMModel(); + int numHFMMeshes = hfmModel.meshes.size(); int totalNumVertices = 0; glm::mat4 invRegistraionOffset = glm::translate(dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT)); - for (int i = 0; i < numFbxMeshes; i++) { - const FBXMesh& mesh = fbxGeometry.meshes.at(i); + for (int i = 0; i < numHFMMeshes; i++) { + const HFMMesh& mesh = hfmModel.meshes.at(i); if (mesh.clusters.size() > 0) { - const FBXCluster& cluster = mesh.clusters.at(0); + const HFMCluster& cluster = mesh.clusters.at(0); auto jointMatrix = model->getRig().getJointTransform(cluster.jointIndex); // we backtranslate by the registration offset so we can apply that offset to the shapeInfo later localTransforms.push_back(invRegistraionOffset * jointMatrix * cluster.inverseBindMatrix); @@ -524,10 +524,10 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { std::vector<std::shared_ptr<const graphics::Mesh>> meshes; if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { - auto& fbxMeshes = _compoundShapeResource->getFBXGeometry().meshes; - meshes.reserve(fbxMeshes.size()); - for (auto& fbxMesh : fbxMeshes) { - meshes.push_back(fbxMesh._mesh); + auto& hfmMeshes = _compoundShapeResource->getHFMModel().meshes; + meshes.reserve(hfmMeshes.size()); + for (auto& hfmMesh : hfmMeshes) { + meshes.push_back(hfmMesh._mesh); } } else { meshes = model->getGeometry()->getMeshes(); @@ -594,7 +594,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { while (partItr != parts.cend<const graphics::Mesh::Part>()) { auto numIndices = partItr->_numIndices; if (partItr->_topology == graphics::Mesh::TRIANGLES) { - // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices % TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader @@ -605,7 +605,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { ++indexItr; } } else if (partItr->_topology == graphics::Mesh::TRIANGLE_STRIP) { - // TODO: resurrect assert after we start sanitizing FBXMesh higher up + // TODO: resurrect assert after we start sanitizing HFMMesh higher up //assert(numIndices > 2); uint32_t approxNumIndices = TRIANGLE_STRIDE * numIndices; @@ -651,7 +651,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { std::set<int32_t> uniqueIndices; auto numIndices = partItr->_numIndices; if (partItr->_topology == graphics::Mesh::TRIANGLES) { - // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices% TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader @@ -662,7 +662,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { ++indexItr; } } else if (partItr->_topology == graphics::Mesh::TRIANGLE_STRIP) { - // TODO: resurrect assert after we start sanitizing FBXMesh higher up + // TODO: resurrect assert after we start sanitizing HFMMesh higher up //assert(numIndices > TRIANGLE_STRIDE - 1); auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex; @@ -755,7 +755,7 @@ int RenderableModelEntityItem::avatarJointIndex(int modelJointIndex) { bool RenderableModelEntityItem::contains(const glm::vec3& point) const { auto model = getModel(); if (EntityItem::contains(point) && model && _compoundShapeResource && _compoundShapeResource->isLoaded()) { - return _compoundShapeResource->getFBXGeometry().convexHullContains(worldToEntity(point)); + return _compoundShapeResource->getHFMModel().convexHullContains(worldToEntity(point)); } return false; @@ -1135,7 +1135,7 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { QVector<EntityJointData> jointsData; - const QVector<FBXAnimationFrame>& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy + const QVector<HFMAnimationFrame>& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy int frameCount = frames.size(); if (frameCount <= 0) { return; @@ -1159,11 +1159,11 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { return; } - QStringList animationJointNames = _animation->getGeometry().getJointNames(); - auto& fbxJoints = _animation->getGeometry().joints; + QStringList animationJointNames = _animation->getHFMModel().getJointNames(); + auto& hfmJoints = _animation->getHFMModel().joints; - auto& originalFbxJoints = _model->getFBXGeometry().joints; - auto& originalFbxIndices = _model->getFBXGeometry().jointIndices; + auto& originalHFMJoints = _model->getHFMModel().joints; + auto& originalHFMIndices = _model->getHFMModel().jointIndices; bool allowTranslation = entity->getAnimationAllowTranslation(); @@ -1182,22 +1182,22 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { translationMat = glm::translate(translations[index]); } } else if (index < animationJointNames.size()) { - QString jointName = fbxJoints[index].name; // Pushing this here so its not done on every entity, with the exceptions of those allowing for translation - if (originalFbxIndices.contains(jointName)) { + QString jointName = hfmJoints[index].name; // Pushing this here so its not done on every entity, with the exceptions of those allowing for translation + if (originalHFMIndices.contains(jointName)) { // Making sure the joint names exist in the original model the animation is trying to apply onto. If they do, then remap and get it's translation. - int remappedIndex = originalFbxIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual. - translationMat = glm::translate(originalFbxJoints[remappedIndex].translation); + int remappedIndex = originalHFMIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual. + translationMat = glm::translate(originalHFMJoints[remappedIndex].translation); } } glm::mat4 rotationMat; if (index < rotations.size()) { - rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * rotations[index] * fbxJoints[index].postRotation); + rotationMat = glm::mat4_cast(hfmJoints[index].preRotation * rotations[index] * hfmJoints[index].postRotation); } else { - rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * fbxJoints[index].postRotation); + rotationMat = glm::mat4_cast(hfmJoints[index].preRotation * hfmJoints[index].postRotation); } - glm::mat4 finalMat = (translationMat * fbxJoints[index].preTransform * - rotationMat * fbxJoints[index].postTransform); + glm::mat4 finalMat = (translationMat * hfmJoints[index].preTransform * + rotationMat * hfmJoints[index].postTransform); auto& jointData = jointsData[j]; jointData.translation = extractTranslation(finalMat); jointData.translationSet = true; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index bc9ac84c91..476372160e 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -54,7 +54,7 @@ WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& const QUrl url(urlString); auto scheme = url.scheme(); - if (scheme == URL_SCHEME_ABOUT || scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || + if (scheme == HIFI_URL_SCHEME_ABOUT || scheme == HIFI_URL_SCHEME_HTTP || scheme == HIFI_URL_SCHEME_HTTPS || urlString.toLower().endsWith(".htm") || urlString.toLower().endsWith(".html")) { return ContentType::HtmlContent; } @@ -108,10 +108,6 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe return true; } - if (_lastLocked != entity->getLocked()) { - return true; - } - return false; } @@ -203,7 +199,6 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene } _lastDPI = entity->getDPI(); - _lastLocked = entity->getLocked(); glm::vec2 windowSize = getWindowSize(entity); _webSurface->resize(QSize(windowSize.x, windowSize.y)); @@ -261,6 +256,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) { DependencyManager::get<GeometryCache>()->bindWebBrowserProgram(batch, fadeRatio < OPAQUE_ALPHA_THRESHOLD); DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio), _geometryId); batch.popProjectionJitter(); + batch.setResourceTexture(0, nullptr); } bool WebEntityRenderer::hasWebSurface() { @@ -361,7 +357,7 @@ glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) con } void WebEntityRenderer::hoverEnterEntity(const PointerEvent& event) { - if (!_lastLocked && _webSurface) { + if (_webSurface) { PointerEvent webEvent = event; webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI)); _webSurface->hoverBeginEvent(webEvent, _touchDevice); @@ -369,7 +365,7 @@ void WebEntityRenderer::hoverEnterEntity(const PointerEvent& event) { } void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) { - if (!_lastLocked && _webSurface) { + if (_webSurface) { PointerEvent webEvent = event; webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI)); _webSurface->hoverEndEvent(webEvent, _touchDevice); @@ -377,8 +373,7 @@ void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) { } void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) { - // Ignore mouse interaction if we're locked - if (!_lastLocked && _webSurface) { + if (_webSurface) { PointerEvent webEvent = event; webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI)); _webSurface->handlePointerEvent(webEvent, _touchDevice); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 1ba8ed0ec7..12640f697d 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -65,7 +65,6 @@ private: gpu::TexturePointer _texture; QString _lastSourceUrl; uint16_t _lastDPI; - bool _lastLocked; QTimer _timer; uint64_t _lastRenderTime { 0 }; }; diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 6f7e012bc4..bc20824d73 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -183,7 +183,7 @@ void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) { } // The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp) - if (scriptURL.scheme().isEmpty() || (scriptURL.scheme() == URL_SCHEME_FILE)) { + if (scriptURL.scheme().isEmpty() || (scriptURL.scheme() == HIFI_URL_SCHEME_FILE)) { qWarning() << "Cannot load script from local filesystem, because assignment may be on a different computer."; scriptRequestFinished(entityID); return; @@ -200,7 +200,8 @@ void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) { _filterDataMap.insert(entityID, filterData); _lock.unlock(); - auto scriptRequest = DependencyManager::get<ResourceManager>()->createResourceRequest(this, scriptURL); + auto scriptRequest = DependencyManager::get<ResourceManager>()->createResourceRequest( + this, scriptURL, true, -1, "EntityEditFilters::addFilter"); if (!scriptRequest) { qWarning() << "Could not create ResourceRequest for Entity Edit filter script at" << scriptURL.toString(); scriptRequestFinished(entityID); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 37d8dc0bdc..c243f772e2 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -3207,6 +3207,8 @@ void EntityItemProperties::markAllChanged() { _queryAACubeChanged = true; + _shapeChanged = true; + _flyingAllowedChanged = true; _ghostingAllowedChanged = true; _filterURLChanged = true; @@ -3803,6 +3805,16 @@ bool EntityItemProperties::queryAACubeRelatedPropertyChanged() const { return parentRelatedPropertyChanged() || dimensionsChanged(); } +bool EntityItemProperties::grabbingRelatedPropertyChanged() const { + const GrabPropertyGroup& grabProperties = getGrab(); + return grabProperties.triggerableChanged() || grabProperties.grabbableChanged() || + grabProperties.grabFollowsControllerChanged() || grabProperties.grabKinematicChanged() || + grabProperties.equippableChanged() || grabProperties.equippableLeftPositionChanged() || + grabProperties.equippableRightPositionChanged() || grabProperties.equippableLeftRotationChanged() || + grabProperties.equippableRightRotationChanged() || grabProperties.equippableIndicatorURLChanged() || + grabProperties.equippableIndicatorScaleChanged() || grabProperties.equippableIndicatorOffsetChanged(); +} + // Checking Certifiable Properties #define ADD_STRING_PROPERTY(n, N) if (!get##N().isEmpty()) json[#n] = get##N() #define ADD_ENUM_PROPERTY(n, N) json[#n] = get##N##AsString() diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index ae2c402d22..c91ccda5aa 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -108,6 +108,7 @@ public: bool getScalesWithParent() const; bool parentRelatedPropertyChanged() const; bool queryAACubeRelatedPropertyChanged() const; + bool grabbingRelatedPropertyChanged() const; AABox getAABox() const; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index f8b22fdbae..3491688588 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -16,6 +16,9 @@ #include <QFutureWatcher> #include <QtConcurrent/QtConcurrentRun> +#include <QJsonObject> +#include <QJsonDocument> +#include <QJsonArray> #include <shared/QtHelpers.h> #include <VariantMapToScriptValue.h> @@ -37,6 +40,7 @@ #include "WebEntityItem.h" #include <EntityScriptClient.h> #include <Profile.h> +#include "GrabPropertyGroup.h" const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}"; const QString NOT_GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":false}}"; @@ -237,6 +241,235 @@ EntityItemProperties convertPropertiesFromScriptSemantics(const EntityItemProper } +void synchronizeSpatialKey(const GrabPropertyGroup& grabProperties, QJsonObject& grabbableKey, bool& userDataChanged) { + if (grabProperties.equippableLeftPositionChanged() || + grabProperties.equippableRightPositionChanged() || + grabProperties.equippableRightRotationChanged() || + grabProperties.equippableIndicatorURLChanged() || + grabProperties.equippableIndicatorScaleChanged() || + grabProperties.equippableIndicatorOffsetChanged()) { + + QJsonObject spatialKey = grabbableKey["spatialKey"].toObject(); + + if (grabProperties.equippableLeftPositionChanged()) { + if (grabProperties.getEquippableLeftPosition() == INITIAL_LEFT_EQUIPPABLE_POSITION) { + spatialKey.remove("leftRelativePosition"); + } else { + spatialKey["leftRelativePosition"] = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableLeftPosition())); + } + } + if (grabProperties.equippableRightPositionChanged()) { + if (grabProperties.getEquippableRightPosition() == INITIAL_RIGHT_EQUIPPABLE_POSITION) { + spatialKey.remove("rightRelativePosition"); + } else { + spatialKey["rightRelativePosition"] = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableRightPosition())); + } + } + if (grabProperties.equippableLeftRotationChanged()) { + spatialKey["relativeRotation"] = + QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableLeftRotation())); + } else if (grabProperties.equippableRightRotationChanged()) { + spatialKey["relativeRotation"] = + QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableRightRotation())); + } + + grabbableKey["spatialKey"] = spatialKey; + userDataChanged = true; + } +} + + +void synchronizeGrabbableKey(const GrabPropertyGroup& grabProperties, QJsonObject& userData, bool& userDataChanged) { + if (grabProperties.triggerableChanged() || + grabProperties.grabbableChanged() || + grabProperties.grabFollowsControllerChanged() || + grabProperties.grabKinematicChanged() || + grabProperties.equippableChanged() || + grabProperties.equippableLeftPositionChanged() || + grabProperties.equippableRightPositionChanged() || + grabProperties.equippableRightRotationChanged()) { + + QJsonObject grabbableKey = userData["grabbableKey"].toObject(); + + if (grabProperties.triggerableChanged()) { + if (grabProperties.getTriggerable()) { + grabbableKey["triggerable"] = true; + } else { + grabbableKey.remove("triggerable"); + } + } + if (grabProperties.grabbableChanged()) { + if (grabProperties.getGrabbable()) { + grabbableKey.remove("grabbable"); + } else { + grabbableKey["grabbable"] = false; + } + } + if (grabProperties.grabFollowsControllerChanged()) { + if (grabProperties.getGrabFollowsController()) { + grabbableKey.remove("ignoreIK"); + } else { + grabbableKey["ignoreIK"] = false; + } + } + if (grabProperties.grabKinematicChanged()) { + if (grabProperties.getGrabKinematic()) { + grabbableKey.remove("kinematic"); + } else { + grabbableKey["kinematic"] = false; + } + } + if (grabProperties.equippableChanged()) { + if (grabProperties.getEquippable()) { + grabbableKey["equippable"] = true; + } else { + grabbableKey.remove("equippable"); + } + } + + if (grabbableKey.contains("spatialKey")) { + synchronizeSpatialKey(grabProperties, grabbableKey, userDataChanged); + } + + userData["grabbableKey"] = grabbableKey; + userDataChanged = true; + } +} + +void synchronizeGrabJoints(const GrabPropertyGroup& grabProperties, QJsonObject& joints) { + QJsonArray rightHand = joints["RightHand"].toArray(); + QJsonObject rightHandPosition = rightHand.size() > 0 ? rightHand[0].toObject() : QJsonObject(); + QJsonObject rightHandRotation = rightHand.size() > 1 ? rightHand[1].toObject() : QJsonObject(); + QJsonArray leftHand = joints["LeftHand"].toArray(); + QJsonObject leftHandPosition = leftHand.size() > 0 ? leftHand[0].toObject() : QJsonObject(); + QJsonObject leftHandRotation = leftHand.size() > 1 ? leftHand[1].toObject() : QJsonObject(); + + if (grabProperties.equippableLeftPositionChanged()) { + leftHandPosition = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableLeftPosition())).toObject(); + } + if (grabProperties.equippableRightPositionChanged()) { + rightHandPosition = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableRightPosition())).toObject(); + } + if (grabProperties.equippableLeftRotationChanged()) { + leftHandRotation = + QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableLeftRotation())).toObject(); + } + if (grabProperties.equippableRightRotationChanged()) { + rightHandRotation = + QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableRightRotation())).toObject(); + } + + rightHand = QJsonArray(); + rightHand.append(rightHandPosition); + rightHand.append(rightHandRotation); + joints["RightHand"] = rightHand; + leftHand = QJsonArray(); + leftHand.append(leftHandPosition); + leftHand.append(leftHandRotation); + joints["LeftHand"] = leftHand; +} + +void synchronizeEquipHotspot(const GrabPropertyGroup& grabProperties, QJsonObject& userData, bool& userDataChanged) { + if (grabProperties.equippableLeftPositionChanged() || + grabProperties.equippableRightPositionChanged() || + grabProperties.equippableRightRotationChanged() || + grabProperties.equippableIndicatorURLChanged() || + grabProperties.equippableIndicatorScaleChanged() || + grabProperties.equippableIndicatorOffsetChanged()) { + + QJsonArray equipHotspots = userData["equipHotspots"].toArray(); + QJsonObject equipHotspot = equipHotspots[0].toObject(); + QJsonObject joints = equipHotspot["joints"].toObject(); + + synchronizeGrabJoints(grabProperties, joints); + + if (grabProperties.equippableIndicatorURLChanged()) { + equipHotspot["modelURL"] = grabProperties.getEquippableIndicatorURL(); + } + if (grabProperties.equippableIndicatorScaleChanged()) { + QJsonObject scale = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableIndicatorScale())).toObject(); + equipHotspot["radius"] = scale; + equipHotspot["modelScale"] = scale; + + } + if (grabProperties.equippableIndicatorOffsetChanged()) { + equipHotspot["position"] = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableIndicatorOffset())).toObject(); + } + + equipHotspot["joints"] = joints; + equipHotspots = QJsonArray(); + equipHotspots.append(equipHotspot); + userData["equipHotspots"] = equipHotspots; + userDataChanged = true; + } +} + +void synchronizeWearable(const GrabPropertyGroup& grabProperties, QJsonObject& userData, bool& userDataChanged) { + if (grabProperties.equippableLeftPositionChanged() || + grabProperties.equippableRightPositionChanged() || + grabProperties.equippableRightRotationChanged() || + grabProperties.equippableIndicatorURLChanged() || + grabProperties.equippableIndicatorScaleChanged() || + grabProperties.equippableIndicatorOffsetChanged()) { + + QJsonObject wearable = userData["wearable"].toObject(); + QJsonObject joints = wearable["joints"].toObject(); + + synchronizeGrabJoints(grabProperties, joints); + + wearable["joints"] = joints; + userData["wearable"] = wearable; + userDataChanged = true; + } +} + +void synchronizeEditedGrabProperties(EntityItemProperties& properties, const QString& previousUserdata) { + // After sufficient warning to content creators, we should be able to remove this. + + if (properties.grabbingRelatedPropertyChanged()) { + // This edit touches a new-style grab property, so make userData agree... + GrabPropertyGroup& grabProperties = properties.getGrab(); + + bool userDataChanged { false }; + + // if the edit changed userData, use the updated version coming along with the edit. If not, use + // what was already in the entity. + QByteArray userDataString; + if (properties.userDataChanged()) { + userDataString = properties.getUserData().toUtf8(); + } else { + userDataString = previousUserdata.toUtf8();; + } + QJsonObject userData = QJsonDocument::fromJson(userDataString).object(); + + if (userData.contains("grabbableKey")) { + synchronizeGrabbableKey(grabProperties, userData, userDataChanged); + } + if (userData.contains("equipHotspots")) { + synchronizeEquipHotspot(grabProperties, userData, userDataChanged); + } + if (userData.contains("wearable")) { + synchronizeWearable(grabProperties, userData, userDataChanged); + } + + if (userDataChanged) { + properties.setUserData(QJsonDocument(userData).toJson()); + } + + } else if (properties.userDataChanged()) { + // This edit touches userData (and doesn't touch a new-style grab property). Check for grabbableKey in the + // userdata and make the new-style grab properties agree + convertGrabUserDataToProperties(properties); + } +} + + QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) { PROFILE_RANGE(script_entities, __FUNCTION__); @@ -257,6 +490,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); + synchronizeEditedGrabProperties(propertiesWithSimID, QString()); EntityItemID id; // If we have a local entity tree set, then also update it. @@ -559,6 +793,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& simulationOwner = entity->getSimulationOwner(); }); + QString previousUserdata; if (entity) { if (properties.hasSimulationRestrictedChanges()) { if (_bidOnSimulationOwnership) { @@ -597,6 +832,8 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& // make sure the properties has a type, so that the encode can know which properties to include properties.setType(entity->getType()); + + previousUserdata = entity->getUserData(); } else if (_bidOnSimulationOwnership) { // bail when simulation participants don't know about entity return QUuid(); @@ -605,6 +842,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& // How to check for this cheaply? properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent()); + synchronizeEditedGrabProperties(properties, previousUserdata); properties.setLastEditedBy(sessionID); // done reading and modifying properties --> start write diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 0b3b8abba2..16d7e74703 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2493,6 +2493,118 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer return true; } +void convertGrabUserDataToProperties(EntityItemProperties& properties) { + GrabPropertyGroup& grabProperties = properties.getGrab(); + QJsonObject userData = QJsonDocument::fromJson(properties.getUserData().toUtf8()).object(); + + QJsonValue grabbableKeyValue = userData["grabbableKey"]; + if (grabbableKeyValue.isObject()) { + QJsonObject grabbableKey = grabbableKeyValue.toObject(); + + QJsonValue wantsTrigger = grabbableKey["wantsTrigger"]; + if (wantsTrigger.isBool()) { + grabProperties.setTriggerable(wantsTrigger.toBool()); + } + QJsonValue triggerable = grabbableKey["triggerable"]; + if (triggerable.isBool()) { + grabProperties.setTriggerable(triggerable.toBool()); + } + QJsonValue grabbable = grabbableKey["grabbable"]; + if (grabbable.isBool()) { + grabProperties.setGrabbable(grabbable.toBool()); + } + QJsonValue ignoreIK = grabbableKey["ignoreIK"]; + if (ignoreIK.isBool()) { + grabProperties.setGrabFollowsController(ignoreIK.toBool()); + } + QJsonValue kinematic = grabbableKey["kinematic"]; + if (kinematic.isBool()) { + grabProperties.setGrabKinematic(kinematic.toBool()); + } + QJsonValue equippable = grabbableKey["equippable"]; + if (equippable.isBool()) { + grabProperties.setEquippable(equippable.toBool()); + } + + if (grabbableKey["spatialKey"].isObject()) { + QJsonObject spatialKey = grabbableKey["spatialKey"].toObject(); + grabProperties.setEquippable(true); + if (spatialKey["leftRelativePosition"].isObject()) { + grabProperties.setEquippableLeftPosition(qMapToVec3(spatialKey["leftRelativePosition"].toVariant())); + } + if (spatialKey["rightRelativePosition"].isObject()) { + grabProperties.setEquippableRightPosition(qMapToVec3(spatialKey["rightRelativePosition"].toVariant())); + } + if (spatialKey["relativeRotation"].isObject()) { + grabProperties.setEquippableLeftRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant())); + grabProperties.setEquippableRightRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant())); + } + } + } + + QJsonValue wearableValue = userData["wearable"]; + if (wearableValue.isObject()) { + QJsonObject wearable = wearableValue.toObject(); + QJsonObject joints = wearable["joints"].toObject(); + if (joints["LeftHand"].isArray()) { + QJsonArray leftHand = joints["LeftHand"].toArray(); + if (leftHand.size() == 2) { + grabProperties.setEquippable(true); + grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant())); + grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant())); + } + } + if (joints["RightHand"].isArray()) { + QJsonArray rightHand = joints["RightHand"].toArray(); + if (rightHand.size() == 2) { + grabProperties.setEquippable(true); + grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant())); + grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant())); + } + } + } + + QJsonValue equipHotspotsValue = userData["equipHotspots"]; + if (equipHotspotsValue.isArray()) { + QJsonArray equipHotspots = equipHotspotsValue.toArray(); + if (equipHotspots.size() > 0) { + // just take the first one + QJsonObject firstHotSpot = equipHotspots[0].toObject(); + QJsonObject joints = firstHotSpot["joints"].toObject(); + if (joints["LeftHand"].isArray()) { + QJsonArray leftHand = joints["LeftHand"].toArray(); + if (leftHand.size() == 2) { + grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant())); + grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant())); + } + } + if (joints["RightHand"].isArray()) { + QJsonArray rightHand = joints["RightHand"].toArray(); + if (rightHand.size() == 2) { + grabProperties.setEquippable(true); + grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant())); + grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant())); + } + } + QJsonValue indicatorURL = firstHotSpot["modelURL"]; + if (indicatorURL.isString()) { + grabProperties.setEquippableIndicatorURL(indicatorURL.toString()); + } + QJsonValue indicatorScale = firstHotSpot["modelScale"]; + if (indicatorScale.isDouble()) { + grabProperties.setEquippableIndicatorScale(glm::vec3((float)indicatorScale.toDouble())); + } else if (indicatorScale.isObject()) { + grabProperties.setEquippableIndicatorScale(qMapToVec3(indicatorScale.toVariant())); + } + QJsonValue indicatorOffset = firstHotSpot["position"]; + if (indicatorOffset.isObject()) { + grabProperties.setEquippableIndicatorOffset(qMapToVec3(indicatorOffset.toVariant())); + } + } + } +} + + bool EntityTree::readFromMap(QVariantMap& map) { // These are needed to deal with older content (before adding inheritance modes) int contentVersion = map["Version"].toInt(); @@ -2639,104 +2751,7 @@ bool EntityTree::readFromMap(QVariantMap& map) { // convert old grab-related userData to new grab properties if (contentVersion < (int)EntityVersion::GrabProperties) { - QJsonObject userData = QJsonDocument::fromJson(properties.getUserData().toUtf8()).object(); - QJsonObject grabbableKey = userData["grabbableKey"].toObject(); - QJsonValue wantsTrigger = grabbableKey["wantsTrigger"]; - - GrabPropertyGroup& grabProperties = properties.getGrab(); - - if (wantsTrigger.isBool()) { - grabProperties.setTriggerable(wantsTrigger.toBool()); - } - QJsonValue triggerable = grabbableKey["triggerable"]; - if (triggerable.isBool()) { - grabProperties.setTriggerable(triggerable.toBool()); - } - QJsonValue grabbable = grabbableKey["grabbable"]; - if (grabbable.isBool()) { - grabProperties.setGrabbable(grabbable.toBool()); - } - QJsonValue ignoreIK = grabbableKey["ignoreIK"]; - if (ignoreIK.isBool()) { - grabProperties.setGrabFollowsController(ignoreIK.toBool()); - } - QJsonValue kinematic = grabbableKey["kinematic"]; - if (kinematic.isBool()) { - grabProperties.setGrabKinematic(kinematic.toBool()); - } - - if (grabbableKey["spatialKey"].isObject()) { - QJsonObject spatialKey = grabbableKey["spatialKey"].toObject(); - grabProperties.setEquippable(true); - if (spatialKey["leftRelativePosition"].isObject()) { - grabProperties.setEquippableLeftPosition(qMapToVec3(spatialKey["leftRelativePosition"].toVariant())); - } - if (spatialKey["rightRelativePosition"].isObject()) { - grabProperties.setEquippableRightPosition(qMapToVec3(spatialKey["rightRelativePosition"].toVariant())); - } - if (spatialKey["relativeRotation"].isObject()) { - grabProperties.setEquippableLeftRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant())); - grabProperties.setEquippableRightRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant())); - } - } - - QJsonObject wearable = userData["wearable"].toObject(); - QJsonObject joints = wearable["joints"].toObject(); - if (joints["LeftHand"].isArray()) { - QJsonArray leftHand = joints["LeftHand"].toArray(); - if (leftHand.size() == 2) { - grabProperties.setEquippable(true); - grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant())); - grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant())); - } - } - if (joints["RightHand"].isArray()) { - QJsonArray rightHand = joints["RightHand"].toArray(); - if (rightHand.size() == 2) { - grabProperties.setEquippable(true); - grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant())); - grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant())); - } - } - - if (userData["equipHotspots"].isArray()) { - QJsonArray equipHotspots = userData["equipHotspots"].toArray(); - if (equipHotspots.size() > 0) { - // just take the first one - QJsonObject firstHotSpot = equipHotspots[0].toObject(); - QJsonObject joints = firstHotSpot["joints"].toObject(); - if (joints["LeftHand"].isArray()) { - QJsonArray leftHand = joints["LeftHand"].toArray(); - if (leftHand.size() == 2) { - grabProperties.setEquippable(true); - grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant())); - grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant())); - } - } - if (joints["RightHand"].isArray()) { - QJsonArray rightHand = joints["RightHand"].toArray(); - if (rightHand.size() == 2) { - grabProperties.setEquippable(true); - grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant())); - grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant())); - } - } - QJsonValue indicatorURL = firstHotSpot["modelURL"]; - if (indicatorURL.isString()) { - grabProperties.setEquippableIndicatorURL(indicatorURL.toString()); - } - QJsonValue indicatorScale = firstHotSpot["modelScale"]; - if (indicatorScale.isDouble()) { - grabProperties.setEquippableIndicatorScale(glm::vec3((float)indicatorScale.toDouble())); - } else if (indicatorScale.isObject()) { - grabProperties.setEquippableIndicatorScale(qMapToVec3(indicatorScale.toVariant())); - } - QJsonValue indicatorOffset = firstHotSpot["position"]; - if (indicatorOffset.isObject()) { - grabProperties.setEquippableIndicatorOffset(qMapToVec3(indicatorOffset.toVariant())); - } - } - } + convertGrabUserDataToProperties(properties); } // Zero out the spread values that were fixed in version ParticleEntityFix so they behave the same as before @@ -2752,9 +2767,11 @@ bool EntityTree::readFromMap(QVariantMap& map) { success = false; } - const QUuid& cloneOriginID = entity->getCloneOriginID(); - if (!cloneOriginID.isNull()) { - cloneIDs[cloneOriginID].push_back(entity->getEntityItemID()); + if (entity) { + const QUuid& cloneOriginID = entity->getCloneOriginID(); + if (!cloneOriginID.isNull()) { + cloneIDs[cloneOriginID].push_back(entity->getEntityItemID()); + } } } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 2f971b8566..634ffcc1f3 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -424,4 +424,6 @@ private: std::map<QString, QString> _namedPaths; }; +void convertGrabUserDataToProperties(EntityItemProperties& properties); + #endif // hifi_EntityTree_h diff --git a/libraries/entities/src/GrabPropertyGroup.cpp b/libraries/entities/src/GrabPropertyGroup.cpp index c433043e31..996eed4720 100644 --- a/libraries/entities/src/GrabPropertyGroup.cpp +++ b/libraries/entities/src/GrabPropertyGroup.cpp @@ -51,6 +51,9 @@ void GrabPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _d COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableLeftRotation, quat, setEquippableLeftRotation); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableRightPosition, vec3, setEquippableRightPosition); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableRightRotation, quat, setEquippableRightRotation); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableIndicatorURL, QString, setEquippableIndicatorURL); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableIndicatorScale, vec3, setEquippableIndicatorScale); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableIndicatorOffset, vec3, setEquippableIndicatorOffset); } void GrabPropertyGroup::merge(const GrabPropertyGroup& other) { @@ -63,6 +66,9 @@ void GrabPropertyGroup::merge(const GrabPropertyGroup& other) { COPY_PROPERTY_IF_CHANGED(equippableLeftRotation); COPY_PROPERTY_IF_CHANGED(equippableRightPosition); COPY_PROPERTY_IF_CHANGED(equippableRightRotation); + COPY_PROPERTY_IF_CHANGED(equippableIndicatorURL); + COPY_PROPERTY_IF_CHANGED(equippableIndicatorScale); + COPY_PROPERTY_IF_CHANGED(equippableIndicatorOffset); } void GrabPropertyGroup::debugDump() const { @@ -77,6 +83,9 @@ void GrabPropertyGroup::debugDump() const { qCDebug(entities) << " _equippableLeftRotation:" << _equippableLeftRotation; qCDebug(entities) << " _equippableRightPosition:" << _equippableRightPosition; qCDebug(entities) << " _equippableRightRotation:" << _equippableRightRotation; + qCDebug(entities) << " _equippableIndicatorURL:" << _equippableIndicatorURL; + qCDebug(entities) << " _equippableIndicatorScale:" << _equippableIndicatorScale; + qCDebug(entities) << " _equippableIndicatorOffset:" << _equippableIndicatorOffset; } void GrabPropertyGroup::listChangedProperties(QList<QString>& out) { @@ -107,6 +116,15 @@ void GrabPropertyGroup::listChangedProperties(QList<QString>& out) { if (equippableRightRotationChanged()) { out << "grab-equippableRightRotation"; } + if (equippableIndicatorURLChanged()) { + out << "grab-equippableIndicatorURL"; + } + if (equippableIndicatorScaleChanged()) { + out << "grab-equippableIndicatorScale"; + } + if (equippableIndicatorOffsetChanged()) { + out << "grab-equippableIndicatorOffset"; + } } bool GrabPropertyGroup::appendToEditPacket(OctreePacketData* packetData, @@ -184,6 +202,9 @@ void GrabPropertyGroup::markAllChanged() { _equippableLeftRotationChanged = true; _equippableRightPositionChanged = true; _equippableRightRotationChanged = true; + _equippableIndicatorURLChanged = true; + _equippableIndicatorScaleChanged = true; + _equippableIndicatorOffsetChanged = true; } EntityPropertyFlags GrabPropertyGroup::getChangedProperties() const { @@ -215,6 +236,9 @@ void GrabPropertyGroup::getProperties(EntityItemProperties& properties) const { COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableLeftRotation, getEquippableLeftRotation); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableRightPosition, getEquippableRightPosition); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableRightRotation, getEquippableRightRotation); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableIndicatorURL, getEquippableIndicatorURL); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableIndicatorScale, getEquippableIndicatorScale); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableIndicatorOffset, getEquippableIndicatorOffset); } bool GrabPropertyGroup::setProperties(const EntityItemProperties& properties) { @@ -231,6 +255,12 @@ bool GrabPropertyGroup::setProperties(const EntityItemProperties& properties) { setEquippableRightPosition); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableRightRotation, equippableRightRotation, setEquippableRightRotation); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableIndicatorURL, equippableIndicatorURL, + setEquippableIndicatorURL); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableIndicatorScale, equippableIndicatorScale, + setEquippableIndicatorScale); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableIndicatorOffset, equippableIndicatorOffset, + setEquippableIndicatorOffset); return somethingChanged; } diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 46dcd1b006..acdeac0e93 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -90,10 +90,10 @@ bool operator==(const Properties& a, const Properties& b) { return (a.color == b.color) && (a.alpha == b.alpha) && + (a.radiusStart == b.radiusStart) && (a.radius == b.radius) && (a.spin == b.spin) && (a.rotateWithEntity == b.rotateWithEntity) && - (a.radiusStart == b.radiusStart) && (a.lifespan == b.lifespan) && (a.maxParticles == b.maxParticles) && (a.emission == b.emission) && @@ -117,18 +117,7 @@ bool Properties::valid() const { (alpha.range.start == glm::clamp(alpha.range.start, MINIMUM_ALPHA, MAXIMUM_ALPHA)) && (alpha.range.finish == glm::clamp(alpha.range.finish, MINIMUM_ALPHA, MAXIMUM_ALPHA)) && (alpha.gradient.spread == glm::clamp(alpha.gradient.spread, MINIMUM_ALPHA, MAXIMUM_ALPHA)) && - (lifespan == glm::clamp(lifespan, MINIMUM_LIFESPAN, MAXIMUM_LIFESPAN)) && - (emission.rate == glm::clamp(emission.rate, MINIMUM_EMIT_RATE, MAXIMUM_EMIT_RATE)) && - (emission.speed.target == glm::clamp(emission.speed.target, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) && - (emission.speed.spread == glm::clamp(emission.speed.spread, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) && - (emission.dimensions == glm::clamp(emission.dimensions, vec3(MINIMUM_EMIT_DIMENSION), vec3(MAXIMUM_EMIT_DIMENSION))) && (radiusStart == glm::clamp(radiusStart, MINIMUM_EMIT_RADIUS_START, MAXIMUM_EMIT_RADIUS_START)) && - (polar.start == glm::clamp(polar.start, MINIMUM_POLAR, MAXIMUM_POLAR)) && - (polar.finish == glm::clamp(polar.finish, MINIMUM_POLAR, MAXIMUM_POLAR)) && - (azimuth.start == glm::clamp(azimuth.start, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH)) && - (azimuth.finish == glm::clamp(azimuth.finish, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH)) && - (emission.acceleration.target == glm::clamp(emission.acceleration.target, vec3(MINIMUM_EMIT_ACCELERATION), vec3(MAXIMUM_EMIT_ACCELERATION))) && - (emission.acceleration.spread == glm::clamp(emission.acceleration.spread, vec3(MINIMUM_ACCELERATION_SPREAD), vec3(MAXIMUM_ACCELERATION_SPREAD))) && (radius.gradient.target == glm::clamp(radius.gradient.target, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && (radius.range.start == glm::clamp(radius.range.start, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && (radius.range.finish == glm::clamp(radius.range.finish, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && @@ -136,7 +125,19 @@ bool Properties::valid() const { (spin.gradient.target == glm::clamp(spin.gradient.target, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && (spin.range.start == glm::clamp(spin.range.start, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && (spin.range.finish == glm::clamp(spin.range.finish, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && - (spin.gradient.spread == glm::clamp(spin.gradient.spread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)); + (spin.gradient.spread == glm::clamp(spin.gradient.spread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && + (lifespan == glm::clamp(lifespan, MINIMUM_LIFESPAN, MAXIMUM_LIFESPAN)) && + (maxParticles == glm::clamp(maxParticles, MINIMUM_MAX_PARTICLES, MAXIMUM_MAX_PARTICLES)) && + (emission.rate == glm::clamp(emission.rate, MINIMUM_EMIT_RATE, MAXIMUM_EMIT_RATE)) && + (emission.speed.target == glm::clamp(emission.speed.target, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) && + (emission.speed.spread == glm::clamp(emission.speed.spread, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) && + (emission.acceleration.target == glm::clamp(emission.acceleration.target, vec3(MINIMUM_EMIT_ACCELERATION), vec3(MAXIMUM_EMIT_ACCELERATION))) && + (emission.acceleration.spread == glm::clamp(emission.acceleration.spread, vec3(MINIMUM_ACCELERATION_SPREAD), vec3(MAXIMUM_ACCELERATION_SPREAD)) && + (emission.dimensions == glm::clamp(emission.dimensions, vec3(MINIMUM_EMIT_DIMENSION), vec3(MAXIMUM_EMIT_DIMENSION))) && + (polar.start == glm::clamp(polar.start, MINIMUM_POLAR, MAXIMUM_POLAR)) && + (polar.finish == glm::clamp(polar.finish, MINIMUM_POLAR, MAXIMUM_POLAR)) && + (azimuth.start == glm::clamp(azimuth.start, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH)) && + (azimuth.finish == glm::clamp(azimuth.finish, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH))); } bool Properties::emitting() const { diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index a89d7afc06..89f1e834ea 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -177,9 +177,10 @@ namespace particle { Properties& operator =(const Properties& other) { color = other.color; alpha = other.alpha; + radiusStart = other.radiusStart; + radius = other.radius; spin = other.spin; rotateWithEntity = other.rotateWithEntity; - radius = other.radius; lifespan = other.lifespan; maxParticles = other.maxParticles; emission = other.emission; diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index fdebb16bc8..84caa98ace 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -70,8 +70,8 @@ public: }; -/// A single blendshape extracted from an FBX document. -class FBXBlendshape { +/// A single blendshape. +class HFMBlendshape { public: QVector<int> indices; QVector<glm::vec3> vertices; @@ -79,19 +79,19 @@ public: QVector<glm::vec3> tangents; }; -struct FBXJointShapeInfo { - // same units and frame as FBXJoint.translation +struct HFMJointShapeInfo { + // same units and frame as HFMJoint.translation glm::vec3 avgPoint; std::vector<float> dots; std::vector<glm::vec3> points; std::vector<glm::vec3> debugLines; }; -/// A single joint (transformation node) extracted from an FBX document. -class FBXJoint { +/// A single joint (transformation node). +class HFMJoint { public: - FBXJointShapeInfo shapeInfo; + HFMJointShapeInfo shapeInfo; QVector<int> freeLineage; bool isFree; int parentIndex; @@ -126,8 +126,8 @@ public: }; -/// A single binding to a joint in an FBX document. -class FBXCluster { +/// A single binding to a joint. +class HFMCluster { public: int jointIndex; @@ -137,8 +137,8 @@ public: const int MAX_NUM_PIXELS_FOR_FBX_TEXTURE = 2048 * 2048; -/// A texture map in an FBX document. -class FBXTexture { +/// A texture map. +class HFMTexture { public: QString id; QString name; @@ -156,7 +156,7 @@ public: }; /// A single part of a mesh (with the same material). -class FBXMeshPart { +class HFMMeshPart { public: QVector<int> quadIndices; // original indices from the FBX mesh @@ -166,10 +166,10 @@ public: QString materialID; }; -class FBXMaterial { +class HFMMaterial { public: - FBXMaterial() {}; - FBXMaterial(const glm::vec3& diffuseColor, const glm::vec3& specularColor, const glm::vec3& emissiveColor, + HFMMaterial() {}; + HFMMaterial(const glm::vec3& diffuseColor, const glm::vec3& specularColor, const glm::vec3& emissiveColor, float shininess, float opacity) : diffuseColor(diffuseColor), specularColor(specularColor), @@ -203,17 +203,17 @@ public: QString shadingModel; graphics::MaterialPointer _material; - FBXTexture normalTexture; - FBXTexture albedoTexture; - FBXTexture opacityTexture; - FBXTexture glossTexture; - FBXTexture roughnessTexture; - FBXTexture specularTexture; - FBXTexture metallicTexture; - FBXTexture emissiveTexture; - FBXTexture occlusionTexture; - FBXTexture scatteringTexture; - FBXTexture lightmapTexture; + HFMTexture normalTexture; + HFMTexture albedoTexture; + HFMTexture opacityTexture; + HFMTexture glossTexture; + HFMTexture roughnessTexture; + HFMTexture specularTexture; + HFMTexture metallicTexture; + HFMTexture emissiveTexture; + HFMTexture occlusionTexture; + HFMTexture scatteringTexture; + HFMTexture lightmapTexture; glm::vec2 lightmapParams{ 0.0f, 1.0f }; @@ -231,11 +231,11 @@ public: bool needTangentSpace() const; }; -/// A single mesh (with optional blendshapes) extracted from an FBX document. -class FBXMesh { +/// A single mesh (with optional blendshapes). +class HFMMesh { public: - QVector<FBXMeshPart> parts; + QVector<HFMMeshPart> parts; QVector<glm::vec3> vertices; QVector<glm::vec3> normals; @@ -247,12 +247,12 @@ public: QVector<uint16_t> clusterWeights; QVector<int32_t> originalIndices; - QVector<FBXCluster> clusters; + QVector<HFMCluster> clusters; Extents meshExtents; glm::mat4 modelTransform; - QVector<FBXBlendshape> blendshapes; + QVector<HFMBlendshape> blendshapes; unsigned int meshIndex; // the order the meshes appeared in the object file @@ -265,7 +265,7 @@ public: class ExtractedMesh { public: - FBXMesh mesh; + HFMMesh mesh; QMultiHash<int, int> newIndices; QVector<QHash<int, int> > blendshapeIndexMaps; QVector<QPair<int, int> > partMaterialTextures; @@ -277,15 +277,15 @@ public: * @property {Quat[]} rotations * @property {Vec3[]} translations */ -/// A single animation frame extracted from an FBX document. -class FBXAnimationFrame { +/// A single animation frame. +class HFMAnimationFrame { public: QVector<glm::quat> rotations; QVector<glm::vec3> translations; }; -/// A light in an FBX document. -class FBXLight { +/// A light. +class HFMLight { public: QString name; Transform transform; @@ -293,7 +293,7 @@ public: float fogValue; glm::vec3 color; - FBXLight() : + HFMLight() : name(), transform(), intensity(1.0f), @@ -302,26 +302,26 @@ public: {} }; -Q_DECLARE_METATYPE(FBXAnimationFrame) -Q_DECLARE_METATYPE(QVector<FBXAnimationFrame>) +Q_DECLARE_METATYPE(HFMAnimationFrame) +Q_DECLARE_METATYPE(QVector<HFMAnimationFrame>) -/// A set of meshes extracted from an FBX document. -class FBXGeometry { +/// The runtime model format. +class HFMModel { public: - using Pointer = std::shared_ptr<FBXGeometry>; + using Pointer = std::shared_ptr<HFMModel>; QString originalURL; QString author; QString applicationName; ///< the name of the application that generated the model - QVector<FBXJoint> joints; + QVector<HFMJoint> joints; QHash<QString, int> jointIndices; ///< 1-based, so as to more easily detect missing indices bool hasSkeletonJoints; - QVector<FBXMesh> meshes; + QVector<HFMMesh> meshes; QVector<QString> scripts; - QHash<QString, FBXMaterial> materials; + QHash<QString, HFMMaterial> materials; glm::mat4 offset; // This includes offset, rotation, and scale as specified by the FST file @@ -348,7 +348,7 @@ public: Extents bindExtents; Extents meshExtents; - QVector<FBXAnimationFrame> animationFrames; + QVector<HFMAnimationFrame> animationFrames; int getJointIndex(const QString& name) const { return jointIndices.value(name) - 1; } QStringList getJointNames() const; @@ -368,7 +368,7 @@ public: QList<QString> blendshapeChannelNames; }; -Q_DECLARE_METATYPE(FBXGeometry) -Q_DECLARE_METATYPE(FBXGeometry::Pointer) +Q_DECLARE_METATYPE(HFMModel) +Q_DECLARE_METATYPE(HFMModel::Pointer) #endif // hifi_FBX_h_ diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index dd766f002c..2cf57e54b4 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -40,19 +40,19 @@ using namespace std; -int FBXGeometryPointerMetaTypeId = qRegisterMetaType<FBXGeometry::Pointer>(); +int HFMModelPointerMetaTypeId = qRegisterMetaType<HFMModel::Pointer>(); -QStringList FBXGeometry::getJointNames() const { +QStringList HFMModel::getJointNames() const { QStringList names; - foreach (const FBXJoint& joint, joints) { + foreach (const HFMJoint& joint, joints) { names.append(joint.name); } return names; } -bool FBXGeometry::hasBlendedMeshes() const { +bool HFMModel::hasBlendedMeshes() const { if (!meshes.isEmpty()) { - foreach (const FBXMesh& mesh, meshes) { + foreach (const HFMMesh& mesh, meshes) { if (!mesh.blendshapes.isEmpty()) { return true; } @@ -61,7 +61,7 @@ bool FBXGeometry::hasBlendedMeshes() const { return false; } -Extents FBXGeometry::getUnscaledMeshExtents() const { +Extents HFMModel::getUnscaledMeshExtents() const { const Extents& extents = meshExtents; // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which @@ -74,12 +74,12 @@ Extents FBXGeometry::getUnscaledMeshExtents() const { } // TODO: Move to graphics::Mesh when Sam's ready -bool FBXGeometry::convexHullContains(const glm::vec3& point) const { +bool HFMModel::convexHullContains(const glm::vec3& point) const { if (!getUnscaledMeshExtents().containsPoint(point)) { return false; } - auto checkEachPrimitive = [=](FBXMesh& mesh, QVector<int> indices, int primitiveSize) -> bool { + auto checkEachPrimitive = [=](HFMMesh& mesh, QVector<int> indices, int primitiveSize) -> bool { // Check whether the point is "behind" all the primitives. int verticesSize = mesh.vertices.size(); for (int j = 0; @@ -124,16 +124,16 @@ bool FBXGeometry::convexHullContains(const glm::vec3& point) const { return false; } -QString FBXGeometry::getModelNameOfMesh(int meshIndex) const { +QString HFMModel::getModelNameOfMesh(int meshIndex) const { if (meshIndicesToModelNames.contains(meshIndex)) { return meshIndicesToModelNames.value(meshIndex); } return QString(); } -int fbxGeometryMetaTypeId = qRegisterMetaType<FBXGeometry>(); -int fbxAnimationFrameMetaTypeId = qRegisterMetaType<FBXAnimationFrame>(); -int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType<QVector<FBXAnimationFrame> >(); +int hfmModelMetaTypeId = qRegisterMetaType<HFMModel>(); +int hfmAnimationFrameMetaTypeId = qRegisterMetaType<HFMAnimationFrame>(); +int hfmAnimationFrameVectorMetaTypeId = qRegisterMetaType<QVector<HFMAnimationFrame>>(); glm::vec3 parseVec3(const QString& string) { @@ -264,17 +264,17 @@ public: }; glm::mat4 getGlobalTransform(const QMultiMap<QString, QString>& _connectionParentMap, - const QHash<QString, FBXModel>& models, QString nodeID, bool mixamoHack, const QString& url) { + const QHash<QString, FBXModel>& fbxModels, QString nodeID, bool mixamoHack, const QString& url) { glm::mat4 globalTransform; QVector<QString> visitedNodes; // Used to prevent following a cycle while (!nodeID.isNull()) { visitedNodes.append(nodeID); // Append each node we visit - const FBXModel& model = models.value(nodeID); - globalTransform = glm::translate(model.translation) * model.preTransform * glm::mat4_cast(model.preRotation * - model.rotation * model.postRotation) * model.postTransform * globalTransform; - if (model.hasGeometricOffset) { - glm::mat4 geometricOffset = createMatFromScaleQuatAndPos(model.geometricScaling, model.geometricRotation, model.geometricTranslation); + const FBXModel& fbxModel = fbxModels.value(nodeID); + globalTransform = glm::translate(fbxModel.translation) * fbxModel.preTransform * glm::mat4_cast(fbxModel.preRotation * + fbxModel.rotation * fbxModel.postRotation) * fbxModel.postTransform * globalTransform; + if (fbxModel.hasGeometricOffset) { + glm::mat4 geometricOffset = createMatFromScaleQuatAndPos(fbxModel.geometricScaling, fbxModel.geometricRotation, fbxModel.geometricTranslation); globalTransform = globalTransform * geometricOffset; } @@ -290,7 +290,7 @@ glm::mat4 getGlobalTransform(const QMultiMap<QString, QString>& _connectionParen continue; } - if (models.contains(parentID)) { + if (fbxModels.contains(parentID)) { nodeID = parentID; break; } @@ -303,7 +303,7 @@ glm::mat4 getGlobalTransform(const QMultiMap<QString, QString>& _connectionParen class ExtractedBlendshape { public: QString id; - FBXBlendshape blendshape; + HFMBlendshape blendshape; }; void printNode(const FBXNode& node, int indentLevel) { @@ -329,7 +329,7 @@ public: }; void appendModelIDs(const QString& parentID, const QMultiMap<QString, QString>& connectionChildMap, - QHash<QString, FBXModel>& models, QSet<QString>& remainingModels, QVector<QString>& modelIDs, bool isRootNode = false) { + QHash<QString, FBXModel>& fbxModels, QSet<QString>& remainingModels, QVector<QString>& modelIDs, bool isRootNode = false) { if (remainingModels.contains(parentID)) { modelIDs.append(parentID); remainingModels.remove(parentID); @@ -337,17 +337,17 @@ void appendModelIDs(const QString& parentID, const QMultiMap<QString, QString>& int parentIndex = isRootNode ? -1 : modelIDs.size() - 1; foreach (const QString& childID, connectionChildMap.values(parentID)) { if (remainingModels.contains(childID)) { - FBXModel& model = models[childID]; - if (model.parentIndex == -1) { - model.parentIndex = parentIndex; - appendModelIDs(childID, connectionChildMap, models, remainingModels, modelIDs); + FBXModel& fbxModel = fbxModels[childID]; + if (fbxModel.parentIndex == -1) { + fbxModel.parentIndex = parentIndex; + appendModelIDs(childID, connectionChildMap, fbxModels, remainingModels, modelIDs); } } } } -FBXBlendshape extractBlendshape(const FBXNode& object) { - FBXBlendshape blendshape; +HFMBlendshape extractBlendshape(const FBXNode& object) { + HFMBlendshape blendshape; foreach (const FBXNode& data, object.children) { if (data.name == "Indexes") { blendshape.indices = FBXReader::getIntVector(data); @@ -362,9 +362,9 @@ FBXBlendshape extractBlendshape(const FBXNode& object) { return blendshape; } -using IndexAccessor = std::function<glm::vec3*(const FBXMesh&, int, int, glm::vec3*, glm::vec3&)>; +using IndexAccessor = std::function<glm::vec3*(const HFMMesh&, int, int, glm::vec3*, glm::vec3&)>; -static void setTangents(const FBXMesh& mesh, const IndexAccessor& vertexAccessor, int firstIndex, int secondIndex, +static void setTangents(const HFMMesh& mesh, const IndexAccessor& vertexAccessor, int firstIndex, int secondIndex, const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals, QVector<glm::vec3>& tangents) { glm::vec3 vertex[2]; glm::vec3 normal; @@ -381,14 +381,14 @@ static void setTangents(const FBXMesh& mesh, const IndexAccessor& vertexAccessor } } -static void createTangents(const FBXMesh& mesh, bool generateFromTexCoords, +static void createTangents(const HFMMesh& mesh, bool generateFromTexCoords, const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals, QVector<glm::vec3>& tangents, IndexAccessor accessor) { // if we have a normal map (and texture coordinates), we must compute tangents if (generateFromTexCoords && !mesh.texCoords.isEmpty()) { tangents.resize(vertices.size()); - foreach(const FBXMeshPart& part, mesh.parts) { + foreach(const HFMMeshPart& part, mesh.parts) { for (int i = 0; i < part.quadIndices.size(); i += 4) { setTangents(mesh, accessor, part.quadIndices.at(i), part.quadIndices.at(i + 1), vertices, normals, tangents); setTangents(mesh, accessor, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2), vertices, normals, tangents); @@ -403,27 +403,27 @@ static void createTangents(const FBXMesh& mesh, bool generateFromTexCoords, setTangents(mesh, accessor, part.triangleIndices.at(i + 2), part.triangleIndices.at(i), vertices, normals, tangents); } if ((part.triangleIndices.size() % 3) != 0) { - qCDebug(modelformat) << "Error in extractFBXGeometry part.triangleIndices.size() is not divisible by three "; + qCDebug(modelformat) << "Error in extractHFMModel part.triangleIndices.size() is not divisible by three "; } } } } -static void _createBlendShapeTangents(FBXMesh& mesh, bool generateFromTexCoords, FBXBlendshape& blendShape); +static void _createBlendShapeTangents(HFMMesh& mesh, bool generateFromTexCoords, HFMBlendshape& blendShape); -void FBXMesh::createBlendShapeTangents(bool generateTangents) { +void HFMMesh::createBlendShapeTangents(bool generateTangents) { for (auto& blendShape : blendshapes) { _createBlendShapeTangents(*this, generateTangents, blendShape); } } -void FBXMesh::createMeshTangents(bool generateFromTexCoords) { - FBXMesh& mesh = *this; +void HFMMesh::createMeshTangents(bool generateFromTexCoords) { + HFMMesh& mesh = *this; // This is the only workaround I've found to trick the compiler into understanding that mesh.tangents isn't // const in the lambda function. auto& tangents = mesh.tangents; createTangents(mesh, generateFromTexCoords, mesh.vertices, mesh.normals, mesh.tangents, - [&](const FBXMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { + [&](const HFMMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { outVertices[0] = mesh.vertices[firstIndex]; outVertices[1] = mesh.vertices[secondIndex]; outNormal = mesh.normals[firstIndex]; @@ -431,7 +431,7 @@ void FBXMesh::createMeshTangents(bool generateFromTexCoords) { }); } -static void _createBlendShapeTangents(FBXMesh& mesh, bool generateFromTexCoords, FBXBlendshape& blendShape) { +static void _createBlendShapeTangents(HFMMesh& mesh, bool generateFromTexCoords, HFMBlendshape& blendShape) { // Create lookup to get index in blend shape from vertex index in mesh std::vector<int> reverseIndices; reverseIndices.resize(mesh.vertices.size()); @@ -443,7 +443,7 @@ static void _createBlendShapeTangents(FBXMesh& mesh, bool generateFromTexCoords, } createTangents(mesh, generateFromTexCoords, blendShape.vertices, blendShape.normals, blendShape.tangents, - [&](const FBXMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { + [&](const HFMMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { const auto index1 = reverseIndices[firstIndex]; const auto index2 = reverseIndices[secondIndex]; @@ -481,7 +481,7 @@ void addBlendshapes(const ExtractedBlendshape& extracted, const QList<WeightedIn foreach (const WeightedIndex& index, indices) { extractedMesh.mesh.blendshapes.resize(max(extractedMesh.mesh.blendshapes.size(), index.first + 1)); extractedMesh.blendshapeIndexMaps.resize(extractedMesh.mesh.blendshapes.size()); - FBXBlendshape& blendshape = extractedMesh.mesh.blendshapes[index.first]; + HFMBlendshape& blendshape = extractedMesh.mesh.blendshapes[index.first]; QHash<int, int>& blendshapeIndexMap = extractedMesh.blendshapeIndexMaps[index.first]; for (int i = 0; i < extracted.blendshape.indices.size(); i++) { int oldIndex = extracted.blendshape.indices.at(i); @@ -503,7 +503,7 @@ void addBlendshapes(const ExtractedBlendshape& extracted, const QList<WeightedIn } QString getTopModelID(const QMultiMap<QString, QString>& connectionParentMap, - const QHash<QString, FBXModel>& models, const QString& modelID, const QString& url) { + const QHash<QString, FBXModel>& fbxModels, const QString& modelID, const QString& url) { QString topID = modelID; QVector<QString> visitedNodes; // Used to prevent following a cycle forever { @@ -515,7 +515,7 @@ QString getTopModelID(const QMultiMap<QString, QString>& connectionParentMap, continue; } - if (models.contains(parentID)) { + if (fbxModels.contains(parentID)) { topID = parentID; goto outerContinue; } @@ -539,7 +539,7 @@ public: QVector<float> values; }; -bool checkMaterialsHaveTextures(const QHash<QString, FBXMaterial>& materials, +bool checkMaterialsHaveTextures(const QHash<QString, HFMMaterial>& materials, const QHash<QString, QByteArray>& textureFilenames, const QMultiMap<QString, QString>& _connectionChildMap) { foreach (const QString& materialID, materials.keys()) { foreach (const QString& childID, _connectionChildMap.values(materialID)) { @@ -569,8 +569,8 @@ int matchTextureUVSetToAttributeChannel(const QString& texUVSetName, const QHash } -FBXLight extractLight(const FBXNode& object) { - FBXLight light; +HFMLight extractLight(const FBXNode& object) { + HFMLight light; foreach (const FBXNode& subobject, object.children) { QString childname = QString(subobject.name); if (subobject.name == "Properties70") { @@ -615,7 +615,7 @@ QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) { return filepath.mid(filepath.lastIndexOf('/') + 1); } -FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) { +HFMModel* FBXReader::extractHFMModel(const QVariantHash& mapping, const QString& url) { const FBXNode& node = _rootNode; QMap<QString, ExtractedMesh> meshes; QHash<QString, QString> modelIDsToNames; @@ -624,7 +624,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS QVector<ExtractedBlendshape> blendshapes; - QHash<QString, FBXModel> models; + QHash<QString, FBXModel> fbxModels; QHash<QString, Cluster> clusters; QHash<QString, AnimationCurve> animationCurves; @@ -636,7 +636,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS QHash<QString, QString> yComponents; QHash<QString, QString> zComponents; - std::map<QString, FBXLight> lights; + std::map<QString, HFMLight> lights; QVariantHash joints = mapping.value("joint").toHash(); QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft"))); @@ -689,10 +689,10 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS #if defined(DEBUG_FBXREADER) int unknown = 0; #endif - FBXGeometry* geometryPtr = new FBXGeometry; - FBXGeometry& geometry = *geometryPtr; + HFMModel* hfmModelPtr = new HFMModel; + HFMModel& hfmModel = *hfmModelPtr; - geometry.originalURL = url; + hfmModel.originalURL = url; float unitScaleFactor = 1.0f; glm::vec3 ambientColor; @@ -708,7 +708,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS if (subobject.name == "MetaData") { foreach (const FBXNode& subsubobject, subobject.children) { if (subsubobject.name == "Author") { - geometry.author = subsubobject.properties.at(0).toString(); + hfmModel.author = subsubobject.properties.at(0).toString(); } } } else if (subobject.name == "Properties70") { @@ -716,7 +716,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS static const QVariant APPLICATION_NAME = QVariant(QByteArray("Original|ApplicationName")); if (subsubobject.name == "P" && subsubobject.properties.size() >= 5 && subsubobject.properties.at(0) == APPLICATION_NAME) { - geometry.applicationName = subsubobject.properties.at(4).toString(); + hfmModel.applicationName = subsubobject.properties.at(4).toString(); } } } @@ -814,7 +814,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS glm::vec3 geometricRotation; glm::vec3 rotationMin, rotationMax; - FBXModel model = { name, -1, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(), glm::quat(), + FBXModel fbxModel = { name, -1, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(), glm::quat(), glm::mat4(), glm::vec3(), glm::vec3(), false, glm::vec3(), glm::quat(), glm::vec3(1.0f) }; ExtractedMesh* mesh = NULL; @@ -944,7 +944,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS lightprop = vprop.toString(); } - FBXLight light = extractLight(object); + HFMLight light = extractLight(object); } } } else { @@ -963,27 +963,27 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } // see FBX documentation, http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/index.html - model.translation = translation; + fbxModel.translation = translation; - model.preTransform = glm::translate(rotationOffset) * glm::translate(rotationPivot); - model.preRotation = glm::quat(glm::radians(preRotation)); - model.rotation = glm::quat(glm::radians(rotation)); - model.postRotation = glm::inverse(glm::quat(glm::radians(postRotation))); - model.postTransform = glm::translate(-rotationPivot) * glm::translate(scaleOffset) * + fbxModel.preTransform = glm::translate(rotationOffset) * glm::translate(rotationPivot); + fbxModel.preRotation = glm::quat(glm::radians(preRotation)); + fbxModel.rotation = glm::quat(glm::radians(rotation)); + fbxModel.postRotation = glm::inverse(glm::quat(glm::radians(postRotation))); + fbxModel.postTransform = glm::translate(-rotationPivot) * glm::translate(scaleOffset) * glm::translate(scalePivot) * glm::scale(scale) * glm::translate(-scalePivot); // NOTE: angles from the FBX file are in degrees // so we convert them to radians for the FBXModel class - model.rotationMin = glm::radians(glm::vec3(rotationMinX ? rotationMin.x : -180.0f, + fbxModel.rotationMin = glm::radians(glm::vec3(rotationMinX ? rotationMin.x : -180.0f, rotationMinY ? rotationMin.y : -180.0f, rotationMinZ ? rotationMin.z : -180.0f)); - model.rotationMax = glm::radians(glm::vec3(rotationMaxX ? rotationMax.x : 180.0f, + fbxModel.rotationMax = glm::radians(glm::vec3(rotationMaxX ? rotationMax.x : 180.0f, rotationMaxY ? rotationMax.y : 180.0f, rotationMaxZ ? rotationMax.z : 180.0f)); - model.hasGeometricOffset = hasGeometricOffset; - model.geometricTranslation = geometricTranslation; - model.geometricRotation = glm::quat(glm::radians(geometricRotation)); - model.geometricScaling = geometricScaling; + fbxModel.hasGeometricOffset = hasGeometricOffset; + fbxModel.geometricTranslation = geometricTranslation; + fbxModel.geometricRotation = glm::quat(glm::radians(geometricRotation)); + fbxModel.geometricScaling = geometricScaling; - models.insert(getID(object.properties), model); + fbxModels.insert(getID(object.properties), fbxModel); } else if (object.name == "Texture") { TextureParam tex; foreach (const FBXNode& subobject, object.children) { @@ -1102,7 +1102,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS _textureContent.insert(filepath, content); } } else if (object.name == "Material") { - FBXMaterial material; + HFMMaterial material; material.name = (object.properties.at(1).toString()); foreach (const FBXNode& subobject, object.children) { bool properties = false; @@ -1255,7 +1255,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS #endif } material.materialID = getID(object.properties); - _fbxMaterials.insert(material.materialID, material); + _hfmMaterials.insert(material.materialID, material); } else if (object.name == "NodeAttribute") { @@ -1276,7 +1276,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS if (!attributetype.isEmpty()) { if (attributetype == "Light") { - FBXLight light = extractLight(object); + HFMLight light = extractLight(object); lights[attribID] = light; } } @@ -1307,7 +1307,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS name = name.mid(name.lastIndexOf('.') + 1); } QString id = getID(object.properties); - geometry.blendshapeChannelNames << name; + hfmModel.blendshapeChannelNames << name; foreach (const WeightedIndex& index, blendshapeIndices.values(name)) { blendshapeChannelIndices.insert(id, index); } @@ -1345,7 +1345,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS QString parentID = getID(connection.properties, 2); ooChildToParent.insert(childID, parentID); if (!hifiGlobalNodeID.isEmpty() && (parentID == hifiGlobalNodeID)) { - std::map< QString, FBXLight >::iterator lightIt = lights.find(childID); + std::map< QString, HFMLight >::iterator lightIt = lights.find(childID); if (lightIt != lights.end()) { _lightmapLevel = (*lightIt).second.intensity; if (_lightmapLevel <= 0.0f) { @@ -1454,26 +1454,26 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS float offsetScale = mapping.value("scale", 1.0f).toFloat() * unitScaleFactor * METERS_PER_CENTIMETER; glm::quat offsetRotation = glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(), mapping.value("ry").toFloat(), mapping.value("rz").toFloat()))); - geometry.offset = glm::translate(glm::vec3(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(), + hfmModel.offset = glm::translate(glm::vec3(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(), mapping.value("tz").toFloat())) * glm::mat4_cast(offsetRotation) * glm::scale(glm::vec3(offsetScale, offsetScale, offsetScale)); // get the list of models in depth-first traversal order QVector<QString> modelIDs; - QSet<QString> remainingModels; - for (QHash<QString, FBXModel>::const_iterator model = models.constBegin(); model != models.constEnd(); model++) { + QSet<QString> remainingFBXModels; + for (QHash<QString, FBXModel>::const_iterator fbxModel = fbxModels.constBegin(); fbxModel != fbxModels.constEnd(); fbxModel++) { // models with clusters must be parented to the cluster top // Unless the model is a root node. - bool isARootNode = !modelIDs.contains(_connectionParentMap.value(model.key())); + bool isARootNode = !modelIDs.contains(_connectionParentMap.value(fbxModel.key())); if (!isARootNode) { - foreach(const QString& deformerID, _connectionChildMap.values(model.key())) { + foreach(const QString& deformerID, _connectionChildMap.values(fbxModel.key())) { foreach(const QString& clusterID, _connectionChildMap.values(deformerID)) { if (!clusters.contains(clusterID)) { continue; } - QString topID = getTopModelID(_connectionParentMap, models, _connectionChildMap.value(clusterID), url); - _connectionChildMap.remove(_connectionParentMap.take(model.key()), model.key()); - _connectionParentMap.insert(model.key(), topID); + QString topID = getTopModelID(_connectionParentMap, fbxModels, _connectionChildMap.value(clusterID), url); + _connectionChildMap.remove(_connectionParentMap.take(fbxModel.key()), fbxModel.key()); + _connectionParentMap.insert(fbxModel.key(), topID); goto outerBreak; } } @@ -1481,21 +1481,21 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } // make sure the parent is in the child map - QString parent = _connectionParentMap.value(model.key()); - if (!_connectionChildMap.contains(parent, model.key())) { - _connectionChildMap.insert(parent, model.key()); + QString parent = _connectionParentMap.value(fbxModel.key()); + if (!_connectionChildMap.contains(parent, fbxModel.key())) { + _connectionChildMap.insert(parent, fbxModel.key()); } - remainingModels.insert(model.key()); + remainingFBXModels.insert(fbxModel.key()); } - while (!remainingModels.isEmpty()) { - QString first = *remainingModels.constBegin(); - foreach (const QString& id, remainingModels) { + while (!remainingFBXModels.isEmpty()) { + QString first = *remainingFBXModels.constBegin(); + foreach (const QString& id, remainingFBXModels) { if (id < first) { first = id; } } - QString topID = getTopModelID(_connectionParentMap, models, first, url); - appendModelIDs(_connectionParentMap.value(topID), _connectionChildMap, models, remainingModels, modelIDs, true); + QString topID = getTopModelID(_connectionParentMap, fbxModels, first, url); + appendModelIDs(_connectionParentMap.value(topID), _connectionChildMap, fbxModels, remainingFBXModels, modelIDs, true); } // figure the number of animation frames from the curves @@ -1504,56 +1504,56 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS frameCount = qMax(frameCount, curve.values.size()); } for (int i = 0; i < frameCount; i++) { - FBXAnimationFrame frame; + HFMAnimationFrame frame; frame.rotations.resize(modelIDs.size()); frame.translations.resize(modelIDs.size()); - geometry.animationFrames.append(frame); + hfmModel.animationFrames.append(frame); } // convert the models to joints QVariantList freeJoints = mapping.values("freeJoint"); - geometry.hasSkeletonJoints = false; + hfmModel.hasSkeletonJoints = false; foreach (const QString& modelID, modelIDs) { - const FBXModel& model = models[modelID]; - FBXJoint joint; - joint.isFree = freeJoints.contains(model.name); - joint.parentIndex = model.parentIndex; + const FBXModel& fbxModel = fbxModels[modelID]; + HFMJoint joint; + joint.isFree = freeJoints.contains(fbxModel.name); + joint.parentIndex = fbxModel.parentIndex; // get the indices of all ancestors starting with the first free one (if any) - int jointIndex = geometry.joints.size(); + int jointIndex = hfmModel.joints.size(); joint.freeLineage.append(jointIndex); int lastFreeIndex = joint.isFree ? 0 : -1; - for (int index = joint.parentIndex; index != -1; index = geometry.joints.at(index).parentIndex) { - if (geometry.joints.at(index).isFree) { + for (int index = joint.parentIndex; index != -1; index = hfmModel.joints.at(index).parentIndex) { + if (hfmModel.joints.at(index).isFree) { lastFreeIndex = joint.freeLineage.size(); } joint.freeLineage.append(index); } joint.freeLineage.remove(lastFreeIndex + 1, joint.freeLineage.size() - lastFreeIndex - 1); - joint.translation = model.translation; // these are usually in centimeters - joint.preTransform = model.preTransform; - joint.preRotation = model.preRotation; - joint.rotation = model.rotation; - joint.postRotation = model.postRotation; - joint.postTransform = model.postTransform; - joint.rotationMin = model.rotationMin; - joint.rotationMax = model.rotationMax; + joint.translation = fbxModel.translation; // these are usually in centimeters + joint.preTransform = fbxModel.preTransform; + joint.preRotation = fbxModel.preRotation; + joint.rotation = fbxModel.rotation; + joint.postRotation = fbxModel.postRotation; + joint.postTransform = fbxModel.postTransform; + joint.rotationMin = fbxModel.rotationMin; + joint.rotationMax = fbxModel.rotationMax; - joint.hasGeometricOffset = model.hasGeometricOffset; - joint.geometricTranslation = model.geometricTranslation; - joint.geometricRotation = model.geometricRotation; - joint.geometricScaling = model.geometricScaling; + joint.hasGeometricOffset = fbxModel.hasGeometricOffset; + joint.geometricTranslation = fbxModel.geometricTranslation; + joint.geometricRotation = fbxModel.geometricRotation; + joint.geometricScaling = fbxModel.geometricScaling; glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; if (joint.parentIndex == -1) { - joint.transform = geometry.offset * glm::translate(joint.translation) * joint.preTransform * + joint.transform = hfmModel.offset * glm::translate(joint.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; joint.inverseDefaultRotation = glm::inverse(combinedRotation); joint.distanceToParent = 0.0f; } else { - const FBXJoint& parentJoint = geometry.joints.at(joint.parentIndex); + const HFMJoint& parentJoint = hfmModel.joints.at(joint.parentIndex); joint.transform = parentJoint.transform * glm::translate(joint.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; joint.inverseDefaultRotation = glm::inverse(combinedRotation) * parentJoint.inverseDefaultRotation; @@ -1561,20 +1561,20 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS extractTranslation(joint.transform)); } joint.inverseBindRotation = joint.inverseDefaultRotation; - joint.name = model.name; + joint.name = fbxModel.name; foreach (const QString& childID, _connectionChildMap.values(modelID)) { QString type = typeFlags.value(childID); if (!type.isEmpty()) { - geometry.hasSkeletonJoints |= (joint.isSkeletonJoint = type.toLower().contains("Skeleton")); + hfmModel.hasSkeletonJoints |= (joint.isSkeletonJoint = type.toLower().contains("Skeleton")); break; } } joint.bindTransformFoundInCluster = false; - geometry.joints.append(joint); - geometry.jointIndices.insert(model.name, geometry.joints.size()); + hfmModel.joints.append(joint); + hfmModel.jointIndices.insert(fbxModel.name, hfmModel.joints.size()); QString rotationID = localRotations.value(modelID); AnimationCurve xRotCurve = animationCurves.value(xComponents.value(rotationID)); @@ -1590,11 +1590,11 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS glm::vec3 defaultPosValues = joint.translation; for (int i = 0; i < frameCount; i++) { - geometry.animationFrames[i].rotations[jointIndex] = glm::quat(glm::radians(glm::vec3( + hfmModel.animationFrames[i].rotations[jointIndex] = glm::quat(glm::radians(glm::vec3( xRotCurve.values.isEmpty() ? defaultRotValues.x : xRotCurve.values.at(i % xRotCurve.values.size()), yRotCurve.values.isEmpty() ? defaultRotValues.y : yRotCurve.values.at(i % yRotCurve.values.size()), zRotCurve.values.isEmpty() ? defaultRotValues.z : zRotCurve.values.at(i % zRotCurve.values.size())))); - geometry.animationFrames[i].translations[jointIndex] = glm::vec3( + hfmModel.animationFrames[i].translations[jointIndex] = glm::vec3( xPosCurve.values.isEmpty() ? defaultPosValues.x : xPosCurve.values.at(i % xPosCurve.values.size()), yPosCurve.values.isEmpty() ? defaultPosValues.y : yPosCurve.values.at(i % yPosCurve.values.size()), zPosCurve.values.isEmpty() ? defaultPosValues.z : zPosCurve.values.at(i % zPosCurve.values.size())); @@ -1603,35 +1603,35 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS // NOTE: shapeVertices are in joint-frame std::vector<ShapeVertices> shapeVertices; - shapeVertices.resize(std::max(1, geometry.joints.size()) ); + shapeVertices.resize(std::max(1, hfmModel.joints.size()) ); // find our special joints - geometry.leftEyeJointIndex = modelIDs.indexOf(jointEyeLeftID); - geometry.rightEyeJointIndex = modelIDs.indexOf(jointEyeRightID); - geometry.neckJointIndex = modelIDs.indexOf(jointNeckID); - geometry.rootJointIndex = modelIDs.indexOf(jointRootID); - geometry.leanJointIndex = modelIDs.indexOf(jointLeanID); - geometry.headJointIndex = modelIDs.indexOf(jointHeadID); - geometry.leftHandJointIndex = modelIDs.indexOf(jointLeftHandID); - geometry.rightHandJointIndex = modelIDs.indexOf(jointRightHandID); - geometry.leftToeJointIndex = modelIDs.indexOf(jointLeftToeID); - geometry.rightToeJointIndex = modelIDs.indexOf(jointRightToeID); + hfmModel.leftEyeJointIndex = modelIDs.indexOf(jointEyeLeftID); + hfmModel.rightEyeJointIndex = modelIDs.indexOf(jointEyeRightID); + hfmModel.neckJointIndex = modelIDs.indexOf(jointNeckID); + hfmModel.rootJointIndex = modelIDs.indexOf(jointRootID); + hfmModel.leanJointIndex = modelIDs.indexOf(jointLeanID); + hfmModel.headJointIndex = modelIDs.indexOf(jointHeadID); + hfmModel.leftHandJointIndex = modelIDs.indexOf(jointLeftHandID); + hfmModel.rightHandJointIndex = modelIDs.indexOf(jointRightHandID); + hfmModel.leftToeJointIndex = modelIDs.indexOf(jointLeftToeID); + hfmModel.rightToeJointIndex = modelIDs.indexOf(jointRightToeID); foreach (const QString& id, humanIKJointIDs) { - geometry.humanIKJointIndices.append(modelIDs.indexOf(id)); + hfmModel.humanIKJointIndices.append(modelIDs.indexOf(id)); } // extract the translation component of the neck transform - if (geometry.neckJointIndex != -1) { - const glm::mat4& transform = geometry.joints.at(geometry.neckJointIndex).transform; - geometry.neckPivot = glm::vec3(transform[3][0], transform[3][1], transform[3][2]); + if (hfmModel.neckJointIndex != -1) { + const glm::mat4& transform = hfmModel.joints.at(hfmModel.neckJointIndex).transform; + hfmModel.neckPivot = glm::vec3(transform[3][0], transform[3][1], transform[3][2]); } - geometry.bindExtents.reset(); - geometry.meshExtents.reset(); + hfmModel.bindExtents.reset(); + hfmModel.meshExtents.reset(); // Create the Material Library - consolidateFBXMaterials(mapping); + consolidateHFMMaterials(mapping); // We can't allow the scaling of a given image to different sizes, because the hash used for the KTX cache is based on the original image // Allowing scaling of the same image to different sizes would cause different KTX files to target the same cache key @@ -1643,7 +1643,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS // 33 - 128 textures --> 512 // etc... QSet<QString> uniqueTextures; - for (auto& material : _fbxMaterials) { + for (auto& material : _hfmMaterials) { material.getTextureNames(uniqueTextures); } int numTextures = uniqueTextures.size(); @@ -1659,15 +1659,15 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } while (numTextureThreshold < numTextures && maxWidth > MIN_MIP_TEXTURE_WIDTH); qCDebug(modelformat) << "Capped square texture width =" << maxWidth << "for model" << url << "with" << numTextures << "textures"; - for (auto& material : _fbxMaterials) { + for (auto& material : _hfmMaterials) { material.setMaxNumPixelsPerTexture(maxWidth * maxWidth); } } #endif - geometry.materials = _fbxMaterials; + hfmModel.materials = _hfmMaterials; // see if any materials have texture children - bool materialsHaveTextures = checkMaterialsHaveTextures(_fbxMaterials, _textureFilenames, _connectionChildMap); + bool materialsHaveTextures = checkMaterialsHaveTextures(_hfmMaterials, _textureFilenames, _connectionChildMap); for (QMap<QString, ExtractedMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) { ExtractedMesh& extracted = it.value(); @@ -1675,14 +1675,14 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS extracted.mesh.meshExtents.reset(); // accumulate local transforms - QString modelID = models.contains(it.key()) ? it.key() : _connectionParentMap.value(it.key()); - glm::mat4 modelTransform = getGlobalTransform(_connectionParentMap, models, modelID, geometry.applicationName == "mixamo.com", url); + QString modelID = fbxModels.contains(it.key()) ? it.key() : _connectionParentMap.value(it.key()); + glm::mat4 modelTransform = getGlobalTransform(_connectionParentMap, fbxModels, modelID, hfmModel.applicationName == "mixamo.com", url); // compute the mesh extents from the transformed vertices foreach (const glm::vec3& vertex, extracted.mesh.vertices) { glm::vec3 transformedVertex = glm::vec3(modelTransform * glm::vec4(vertex, 1.0f)); - geometry.meshExtents.minimum = glm::min(geometry.meshExtents.minimum, transformedVertex); - geometry.meshExtents.maximum = glm::max(geometry.meshExtents.maximum, transformedVertex); + hfmModel.meshExtents.minimum = glm::min(hfmModel.meshExtents.minimum, transformedVertex); + hfmModel.meshExtents.maximum = glm::max(hfmModel.meshExtents.maximum, transformedVertex); extracted.mesh.meshExtents.minimum = glm::min(extracted.mesh.meshExtents.minimum, transformedVertex); extracted.mesh.meshExtents.maximum = glm::max(extracted.mesh.meshExtents.maximum, transformedVertex); @@ -1698,13 +1698,13 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS for (int i = children.size() - 1; i >= 0; i--) { const QString& childID = children.at(i); - if (_fbxMaterials.contains(childID)) { + if (_hfmMaterials.contains(childID)) { // the pure material associated with this part - FBXMaterial material = _fbxMaterials.value(childID); + HFMMaterial material = _hfmMaterials.value(childID); for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { if (extracted.partMaterialTextures.at(j).first == materialIndex) { - FBXMeshPart& part = extracted.mesh.parts[j]; + HFMMeshPart& part = extracted.mesh.parts[j]; part.materialID = material.materialID; generateTangents |= material.needTangentSpace(); } @@ -1713,7 +1713,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS materialIndex++; } else if (_textureFilenames.contains(childID)) { - FBXTexture texture = getTexture(childID); + HFMTexture texture = getTexture(childID); for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { int partTexture = extracted.partMaterialTextures.at(j).second; if (partTexture == textureIndex && !(partTexture == 0 && materialsHaveTextures)) { @@ -1736,47 +1736,47 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS if (!clusters.contains(clusterID)) { continue; } - FBXCluster fbxCluster; + HFMCluster hfmCluster; const Cluster& cluster = clusters[clusterID]; clusterIDs.append(clusterID); // see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion // of skinning information in FBX QString jointID = _connectionChildMap.value(clusterID); - fbxCluster.jointIndex = modelIDs.indexOf(jointID); - if (fbxCluster.jointIndex == -1) { + hfmCluster.jointIndex = modelIDs.indexOf(jointID); + if (hfmCluster.jointIndex == -1) { qCDebug(modelformat) << "Joint not in model list: " << jointID; - fbxCluster.jointIndex = 0; + hfmCluster.jointIndex = 0; } - fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform; + hfmCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform; // slam bottom row to (0, 0, 0, 1), we KNOW this is not a perspective matrix and // sometimes floating point fuzz can be introduced after the inverse. - fbxCluster.inverseBindMatrix[0][3] = 0.0f; - fbxCluster.inverseBindMatrix[1][3] = 0.0f; - fbxCluster.inverseBindMatrix[2][3] = 0.0f; - fbxCluster.inverseBindMatrix[3][3] = 1.0f; + hfmCluster.inverseBindMatrix[0][3] = 0.0f; + hfmCluster.inverseBindMatrix[1][3] = 0.0f; + hfmCluster.inverseBindMatrix[2][3] = 0.0f; + hfmCluster.inverseBindMatrix[3][3] = 1.0f; - fbxCluster.inverseBindTransform = Transform(fbxCluster.inverseBindMatrix); + hfmCluster.inverseBindTransform = Transform(hfmCluster.inverseBindMatrix); - extracted.mesh.clusters.append(fbxCluster); + extracted.mesh.clusters.append(hfmCluster); // override the bind rotation with the transform link - FBXJoint& joint = geometry.joints[fbxCluster.jointIndex]; + HFMJoint& joint = hfmModel.joints[hfmCluster.jointIndex]; joint.inverseBindRotation = glm::inverse(extractRotation(cluster.transformLink)); joint.bindTransform = cluster.transformLink; joint.bindTransformFoundInCluster = true; // update the bind pose extents - glm::vec3 bindTranslation = extractTranslation(geometry.offset * joint.bindTransform); - geometry.bindExtents.addPoint(bindTranslation); + glm::vec3 bindTranslation = extractTranslation(hfmModel.offset * joint.bindTransform); + hfmModel.bindExtents.addPoint(bindTranslation); } } // if we don't have a skinned joint, parent to the model itself if (extracted.mesh.clusters.isEmpty()) { - FBXCluster cluster; + HFMCluster cluster; cluster.jointIndex = modelIDs.indexOf(modelID); if (cluster.jointIndex == -1) { qCDebug(modelformat) << "Model not in model list: " << modelID; @@ -1786,7 +1786,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } // whether we're skinned depends on how many clusters are attached - const FBXCluster& firstFBXCluster = extracted.mesh.clusters.at(0); + const HFMCluster& firstHFMCluster = extracted.mesh.clusters.at(0); glm::mat4 inverseModelTransform = glm::inverse(modelTransform); if (clusterIDs.size() > 1) { // this is a multi-mesh joint @@ -1799,16 +1799,16 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS for (int i = 0; i < clusterIDs.size(); i++) { QString clusterID = clusterIDs.at(i); const Cluster& cluster = clusters[clusterID]; - const FBXCluster& fbxCluster = extracted.mesh.clusters.at(i); - int jointIndex = fbxCluster.jointIndex; - FBXJoint& joint = geometry.joints[jointIndex]; + const HFMCluster& hfmCluster = extracted.mesh.clusters.at(i); + int jointIndex = hfmCluster.jointIndex; + HFMJoint& joint = hfmModel.joints[jointIndex]; glm::mat4 transformJointToMesh = inverseModelTransform * joint.bindTransform; glm::vec3 boneEnd = extractTranslation(transformJointToMesh); glm::vec3 boneBegin = boneEnd; glm::vec3 boneDirection; float boneLength = 0.0f; if (joint.parentIndex != -1) { - boneBegin = extractTranslation(inverseModelTransform * geometry.joints[joint.parentIndex].bindTransform); + boneBegin = extractTranslation(inverseModelTransform * hfmModel.joints[joint.parentIndex].bindTransform); boneDirection = boneEnd - boneBegin; boneLength = glm::length(boneDirection); if (boneLength > EPSILON) { @@ -1881,8 +1881,8 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } else { // this is a single-mesh joint - int jointIndex = firstFBXCluster.jointIndex; - FBXJoint& joint = geometry.joints[jointIndex]; + int jointIndex = firstHFMCluster.jointIndex; + HFMJoint& joint = hfmModel.joints[jointIndex]; // transform cluster vertices to joint-frame and save for later glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; @@ -1902,8 +1902,8 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } buildModelMesh(extracted.mesh, url); - geometry.meshes.append(extracted.mesh); - int meshIndex = geometry.meshes.size() - 1; + hfmModel.meshes.append(extracted.mesh); + int meshIndex = hfmModel.meshes.size() - 1; if (extracted.mesh._mesh) { extracted.mesh._mesh->displayName = QString("%1#/mesh/%2").arg(url).arg(meshIndex).toStdString(); extracted.mesh._mesh->modelName = modelIDsToNames.value(modelID).toStdString(); @@ -1923,8 +1923,8 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS }; // now that all joints have been scanned compute a k-Dop bounding volume of mesh - for (int i = 0; i < geometry.joints.size(); ++i) { - FBXJoint& joint = geometry.joints[i]; + for (int i = 0; i < hfmModel.joints.size(); ++i) { + HFMJoint& joint = hfmModel.joints[i]; // NOTE: points are in joint-frame ShapeVertices& points = shapeVertices.at(i); @@ -1958,7 +1958,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS generateBoundryLinesForDop14(joint.shapeInfo.dots, joint.shapeInfo.avgPoint, joint.shapeInfo.debugLines); } } - geometry.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString()); + hfmModel.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString()); // attempt to map any meshes to a named model for (QHash<QString, int>::const_iterator m = meshIDsToMeshIndices.constBegin(); @@ -1971,14 +1971,14 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS const QString& modelID = ooChildToParent.value(meshID); if (modelIDsToNames.contains(modelID)) { const QString& modelName = modelIDsToNames.value(modelID); - geometry.meshIndicesToModelNames.insert(meshIndex, modelName); + hfmModel.meshIndicesToModelNames.insert(meshIndex, modelName); } } } { int i = 0; - for (const auto& mesh : geometry.meshes) { - auto name = geometry.getModelNameOfMesh(i++); + for (const auto& mesh : hfmModel.meshes) { + auto name = hfmModel.getModelNameOfMesh(i++); if (!name.isEmpty()) { if (mesh._mesh) { mesh._mesh->modelName = name.toStdString(); @@ -1991,16 +1991,16 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } } - return geometryPtr; + return hfmModelPtr; } -FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { - QBuffer buffer(const_cast<QByteArray*>(&model)); +HFMModel* readFBX(const QByteArray& data, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { + QBuffer buffer(const_cast<QByteArray*>(&data)); buffer.open(QIODevice::ReadOnly); return readFBX(&buffer, mapping, url, loadLightmaps, lightmapLevel); } -FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { +HFMModel* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { FBXReader reader; reader._rootNode = FBXReader::parseFBX(device); reader._loadLightmaps = loadLightmaps; @@ -2008,5 +2008,5 @@ FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QStri qCDebug(modelformat) << "Reading FBX: " << url; - return reader.extractFBXGeometry(mapping, url); + return reader.extractHFMModel(mapping, url); } diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index c391ea6647..d9a216eeb8 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -34,13 +34,13 @@ class QIODevice; class FBXNode; -/// Reads FBX geometry from the supplied model and mapping data. +/// Reads HFMModel from the supplied model and mapping data. /// \exception QString if an error occurs in parsing -FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); +HFMModel* readFBX(const QByteArray& data, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); -/// Reads FBX geometry from the supplied model and mapping data. +/// Reads HFMModel from the supplied model and mapping data. /// \exception QString if an error occurs in parsing -FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); +HFMModel* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); class TextureParam { public: @@ -103,20 +103,20 @@ class ExtractedMesh; class FBXReader { public: - FBXGeometry* _fbxGeometry; + HFMModel* _hfmModel; FBXNode _rootNode; static FBXNode parseFBX(QIODevice* device); - FBXGeometry* extractFBXGeometry(const QVariantHash& mapping, const QString& url); + HFMModel* extractHFMModel(const QVariantHash& mapping, const QString& url); static ExtractedMesh extractMesh(const FBXNode& object, unsigned int& meshIndex, bool deduplicate = true); QHash<QString, ExtractedMesh> meshes; - static void buildModelMesh(FBXMesh& extractedMesh, const QString& url); + static void buildModelMesh(HFMMesh& extractedMesh, const QString& url); static glm::vec3 normalizeDirForPacking(const glm::vec3& dir); - FBXTexture getTexture(const QString& textureID); + HFMTexture getTexture(const QString& textureID); QHash<QString, QString> _textureNames; // Hashes the original RelativeFilename of textures @@ -142,9 +142,9 @@ public: QHash<QString, QString> ambientFactorTextures; QHash<QString, QString> occlusionTextures; - QHash<QString, FBXMaterial> _fbxMaterials; + QHash<QString, HFMMaterial> _hfmMaterials; - void consolidateFBXMaterials(const QVariantHash& mapping); + void consolidateHFMMaterials(const QVariantHash& mapping); bool _loadLightmaps = true; float _lightmapOffset = 0.0f; diff --git a/libraries/fbx/src/FBXReader_Material.cpp b/libraries/fbx/src/FBXReader_Material.cpp index d5902962e5..ff1de30b97 100644 --- a/libraries/fbx/src/FBXReader_Material.cpp +++ b/libraries/fbx/src/FBXReader_Material.cpp @@ -27,7 +27,7 @@ #include "ModelFormatLogging.h" -void FBXMaterial::getTextureNames(QSet<QString>& textureList) const { +void HFMMaterial::getTextureNames(QSet<QString>& textureList) const { if (!normalTexture.isNull()) { textureList.insert(normalTexture.name); } @@ -63,7 +63,7 @@ void FBXMaterial::getTextureNames(QSet<QString>& textureList) const { } } -void FBXMaterial::setMaxNumPixelsPerTexture(int maxNumPixels) { +void HFMMaterial::setMaxNumPixelsPerTexture(int maxNumPixels) { normalTexture.maxNumPixels = maxNumPixels; albedoTexture.maxNumPixels = maxNumPixels; opacityTexture.maxNumPixels = maxNumPixels; @@ -77,12 +77,12 @@ void FBXMaterial::setMaxNumPixelsPerTexture(int maxNumPixels) { lightmapTexture.maxNumPixels = maxNumPixels; } -bool FBXMaterial::needTangentSpace() const { +bool HFMMaterial::needTangentSpace() const { return !normalTexture.isNull(); } -FBXTexture FBXReader::getTexture(const QString& textureID) { - FBXTexture texture; +HFMTexture FBXReader::getTexture(const QString& textureID) { + HFMTexture texture; const QByteArray& filepath = _textureFilepaths.value(textureID); texture.content = _textureContent.value(filepath); @@ -123,7 +123,7 @@ FBXTexture FBXReader::getTexture(const QString& textureID) { return texture; } -void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { +void FBXReader::consolidateHFMMaterials(const QVariantHash& mapping) { QString materialMapString = mapping.value("materialMap").toString(); QJsonDocument materialMapDocument = QJsonDocument::fromJson(materialMapString.toUtf8()); @@ -133,16 +133,16 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { qCDebug(modelformat) << "fbx Material Map found but did not produce valid JSON:" << materialMapString; } } - for (QHash<QString, FBXMaterial>::iterator it = _fbxMaterials.begin(); it != _fbxMaterials.end(); it++) { - FBXMaterial& material = (*it); + for (QHash<QString, HFMMaterial>::iterator it = _hfmMaterials.begin(); it != _hfmMaterials.end(); it++) { + HFMMaterial& material = (*it); // Maya is the exporting the shading model and we are trying to use it bool isMaterialLambert = (material.shadingModel.toLower() == "lambert"); // the pure material associated with this part bool detectDifferentUVs = false; - FBXTexture diffuseTexture; - FBXTexture diffuseFactorTexture; + HFMTexture diffuseTexture; + HFMTexture diffuseFactorTexture; QString diffuseTextureID = diffuseTextures.value(material.materialID); QString diffuseFactorTextureID = diffuseFactorTextures.value(material.materialID); @@ -169,7 +169,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { detectDifferentUVs = (diffuseTexture.texcoordSet != 0) || (!diffuseTexture.transform.isIdentity()); } - FBXTexture transparentTexture; + HFMTexture transparentTexture; QString transparentTextureID = transparentTextures.value(material.materialID); // If PBS Material, systematically bind the albedo texture as transparency texture and check for the alpha channel if (material.isPBSMaterial) { @@ -181,7 +181,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { detectDifferentUVs |= (transparentTexture.texcoordSet != 0) || (!transparentTexture.transform.isIdentity()); } - FBXTexture normalTexture; + HFMTexture normalTexture; QString bumpTextureID = bumpTextures.value(material.materialID); QString normalTextureID = normalTextures.value(material.materialID); if (!normalTextureID.isNull()) { @@ -198,7 +198,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { detectDifferentUVs |= (normalTexture.texcoordSet != 0) || (!normalTexture.transform.isIdentity()); } - FBXTexture specularTexture; + HFMTexture specularTexture; QString specularTextureID = specularTextures.value(material.materialID); if (!specularTextureID.isNull()) { specularTexture = getTexture(specularTextureID); @@ -206,7 +206,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { material.specularTexture = specularTexture; } - FBXTexture metallicTexture; + HFMTexture metallicTexture; QString metallicTextureID = metallicTextures.value(material.materialID); if (!metallicTextureID.isNull()) { metallicTexture = getTexture(metallicTextureID); @@ -214,7 +214,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { material.metallicTexture = metallicTexture; } - FBXTexture roughnessTexture; + HFMTexture roughnessTexture; QString roughnessTextureID = roughnessTextures.value(material.materialID); if (!roughnessTextureID.isNull()) { roughnessTexture = getTexture(roughnessTextureID); @@ -222,7 +222,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { detectDifferentUVs |= (roughnessTexture.texcoordSet != 0) || (!roughnessTexture.transform.isIdentity()); } - FBXTexture shininessTexture; + HFMTexture shininessTexture; QString shininessTextureID = shininessTextures.value(material.materialID); if (!shininessTextureID.isNull()) { shininessTexture = getTexture(shininessTextureID); @@ -230,7 +230,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { detectDifferentUVs |= (shininessTexture.texcoordSet != 0) || (!shininessTexture.transform.isIdentity()); } - FBXTexture emissiveTexture; + HFMTexture emissiveTexture; QString emissiveTextureID = emissiveTextures.value(material.materialID); if (!emissiveTextureID.isNull()) { emissiveTexture = getTexture(emissiveTextureID); @@ -245,7 +245,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { } } - FBXTexture occlusionTexture; + HFMTexture occlusionTexture; QString occlusionTextureID = occlusionTextures.value(material.materialID); if (occlusionTextureID.isNull()) { // 2nd chance @@ -265,7 +265,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { lightmapParams.x = _lightmapOffset; lightmapParams.y = _lightmapLevel; - FBXTexture ambientTexture; + HFMTexture ambientTexture; QString ambientTextureID = ambientTextures.value(material.materialID); if (ambientTextureID.isNull()) { // 2nd chance @@ -326,7 +326,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { if (materialOptions.contains("scatteringMap")) { QByteArray scatteringMap = materialOptions.value("scatteringMap").toVariant().toByteArray(); - material.scatteringTexture = FBXTexture(); + material.scatteringTexture = HFMTexture(); material.scatteringTexture.name = material.name + ".scatteringMap"; material.scatteringTexture.filename = scatteringMap; } diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index c9b004c3a8..e098aff99a 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -42,9 +42,9 @@ using vec2h = glm::tvec2<glm::detail::hdata>; -#define FBX_PACK_COLORS 1 +#define HFM_PACK_COLORS 1 -#if FBX_PACK_COLORS +#if HFM_PACK_COLORS using ColorType = glm::uint32; #define FBX_COLOR_ELEMENT gpu::Element::COLOR_RGBA_32 #else @@ -469,7 +469,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn QPair<int, int> materialTexture(materialID, 0); - // grab or setup the FBXMeshPart for the part this face belongs to + // grab or setup the HFMMeshPart for the part this face belongs to int& partIndexPlusOne = materialTextureParts[materialTexture]; if (partIndexPlusOne == 0) { data.extracted.partMaterialTextures.append(materialTexture); @@ -478,7 +478,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn } // give the mesh part this index - FBXMeshPart& part = data.extracted.mesh.parts[partIndexPlusOne - 1]; + HFMMeshPart& part = data.extracted.mesh.parts[partIndexPlusOne - 1]; part.triangleIndices.append(firstCorner.value()); part.triangleIndices.append(dracoFace[1].value()); part.triangleIndices.append(dracoFace[2].value()); @@ -511,7 +511,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn data.extracted.mesh.parts.resize(data.extracted.mesh.parts.size() + 1); partIndex = data.extracted.mesh.parts.size(); } - FBXMeshPart& part = data.extracted.mesh.parts[partIndex - 1]; + HFMMeshPart& part = data.extracted.mesh.parts[partIndex - 1]; if (endIndex - beginIndex == 4) { appendIndex(data, part.quadIndices, beginIndex++, deduplicate); @@ -565,9 +565,9 @@ glm::vec3 FBXReader::normalizeDirForPacking(const glm::vec3& dir) { return dir; } -void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { +void FBXReader::buildModelMesh(HFMMesh& extractedMesh, const QString& url) { unsigned int totalSourceIndices = 0; - foreach(const FBXMeshPart& part, extractedMesh.parts) { + foreach(const HFMMeshPart& part, extractedMesh.parts) { totalSourceIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); } @@ -583,17 +583,17 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { return; } - FBXMesh& fbxMesh = extractedMesh; + HFMMesh& hfmMesh = extractedMesh; graphics::MeshPointer mesh(new graphics::Mesh()); int numVerts = extractedMesh.vertices.size(); - if (!fbxMesh.normals.empty() && fbxMesh.tangents.empty()) { + if (!hfmMesh.normals.empty() && hfmMesh.tangents.empty()) { // Fill with a dummy value to force tangents to be present if there are normals - fbxMesh.tangents.reserve(fbxMesh.normals.size()); - std::fill_n(std::back_inserter(fbxMesh.tangents), fbxMesh.normals.size(), Vectors::UNIT_X); + hfmMesh.tangents.reserve(hfmMesh.normals.size()); + std::fill_n(std::back_inserter(hfmMesh.tangents), hfmMesh.normals.size(), Vectors::UNIT_X); } // Same thing with blend shapes - for (auto& blendShape : fbxMesh.blendshapes) { + for (auto& blendShape : hfmMesh.blendshapes) { if (!blendShape.normals.empty() && blendShape.tangents.empty()) { // Fill with a dummy value to force tangents to be present if there are normals blendShape.tangents.reserve(blendShape.normals.size()); @@ -609,8 +609,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { // Normal and tangent are always there together packed in normalized xyz32bits word (times 2) const auto normalElement = FBX_NORMAL_ELEMENT; - const int normalsSize = fbxMesh.normals.size() * normalElement.getSize(); - const int tangentsSize = fbxMesh.tangents.size() * normalElement.getSize(); + const int normalsSize = hfmMesh.normals.size() * normalElement.getSize(); + const int tangentsSize = hfmMesh.tangents.size() * normalElement.getSize(); // If there are normals then there should be tangents assert(normalsSize <= tangentsSize); if (tangentsSize > normalsSize) { @@ -620,22 +620,22 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { // Color attrib const auto colorElement = FBX_COLOR_ELEMENT; - const int colorsSize = fbxMesh.colors.size() * colorElement.getSize(); + const int colorsSize = hfmMesh.colors.size() * colorElement.getSize(); // Texture coordinates are stored in 2 half floats const auto texCoordsElement = gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV); - const int texCoordsSize = fbxMesh.texCoords.size() * texCoordsElement.getSize(); - const int texCoords1Size = fbxMesh.texCoords1.size() * texCoordsElement.getSize(); + const int texCoordsSize = hfmMesh.texCoords.size() * texCoordsElement.getSize(); + const int texCoords1Size = hfmMesh.texCoords1.size() * texCoordsElement.getSize(); // Support for 4 skinning clusters: // 4 Indices are uint8 ideally, uint16 if more than 256. - const auto clusterIndiceElement = (fbxMesh.clusters.size() < UINT8_MAX ? gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW) : gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW)); + const auto clusterIndiceElement = (hfmMesh.clusters.size() < UINT8_MAX ? gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW) : gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW)); // 4 Weights are normalized 16bits const auto clusterWeightElement = gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW); // Cluster indices and weights must be the same sizes const int NUM_CLUSTERS_PER_VERT = 4; - const int numVertClusters = (fbxMesh.clusterIndices.size() == fbxMesh.clusterWeights.size() ? fbxMesh.clusterIndices.size() / NUM_CLUSTERS_PER_VERT : 0); + const int numVertClusters = (hfmMesh.clusterIndices.size() == hfmMesh.clusterWeights.size() ? hfmMesh.clusterIndices.size() / NUM_CLUSTERS_PER_VERT : 0); const int clusterIndicesSize = numVertClusters * clusterIndiceElement.getSize(); const int clusterWeightsSize = numVertClusters * clusterWeightElement.getSize(); @@ -660,9 +660,9 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { if (normalsSize > 0) { std::vector<NormalType> normalsAndTangents; - normalsAndTangents.reserve(fbxMesh.normals.size() + fbxMesh.tangents.size()); - for (auto normalIt = fbxMesh.normals.constBegin(), tangentIt = fbxMesh.tangents.constBegin(); - normalIt != fbxMesh.normals.constEnd(); + normalsAndTangents.reserve(hfmMesh.normals.size() + hfmMesh.tangents.size()); + for (auto normalIt = hfmMesh.normals.constBegin(), tangentIt = hfmMesh.tangents.constBegin(); + normalIt != hfmMesh.normals.constEnd(); ++normalIt, ++tangentIt) { #if FBX_PACK_NORMALS const auto normal = normalizeDirForPacking(*normalIt); @@ -681,24 +681,24 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { // Pack colors if (colorsSize > 0) { -#if FBX_PACK_COLORS +#if HFM_PACK_COLORS std::vector<ColorType> colors; - colors.reserve(fbxMesh.colors.size()); - for (const auto& color : fbxMesh.colors) { + colors.reserve(hfmMesh.colors.size()); + for (const auto& color : hfmMesh.colors) { colors.push_back(glm::packUnorm4x8(glm::vec4(color, 1.0f))); } vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) colors.data()); #else - vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) fbxMesh.colors.constData()); + vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) hfmMesh.colors.constData()); #endif } // Pack Texcoords 0 and 1 (if exists) if (texCoordsSize > 0) { QVector<vec2h> texCoordData; - texCoordData.reserve(fbxMesh.texCoords.size()); - for (auto& texCoordVec2f : fbxMesh.texCoords) { + texCoordData.reserve(hfmMesh.texCoords.size()); + for (auto& texCoordVec2f : hfmMesh.texCoords) { vec2h texCoordVec2h; texCoordVec2h.x = glm::detail::toFloat16(texCoordVec2f.x); @@ -709,8 +709,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { } if (texCoords1Size > 0) { QVector<vec2h> texCoordData; - texCoordData.reserve(fbxMesh.texCoords1.size()); - for (auto& texCoordVec2f : fbxMesh.texCoords1) { + texCoordData.reserve(hfmMesh.texCoords1.size()); + for (auto& texCoordVec2f : hfmMesh.texCoords1) { vec2h texCoordVec2h; texCoordVec2h.x = glm::detail::toFloat16(texCoordVec2f.x); @@ -722,22 +722,22 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { // Clusters data if (clusterIndicesSize > 0) { - if (fbxMesh.clusters.size() < UINT8_MAX) { + if (hfmMesh.clusters.size() < UINT8_MAX) { // yay! we can fit the clusterIndices within 8-bits - int32_t numIndices = fbxMesh.clusterIndices.size(); + int32_t numIndices = hfmMesh.clusterIndices.size(); QVector<uint8_t> clusterIndices; clusterIndices.resize(numIndices); for (int32_t i = 0; i < numIndices; ++i) { - assert(fbxMesh.clusterIndices[i] <= UINT8_MAX); - clusterIndices[i] = (uint8_t)(fbxMesh.clusterIndices[i]); + assert(hfmMesh.clusterIndices[i] <= UINT8_MAX); + clusterIndices[i] = (uint8_t)(hfmMesh.clusterIndices[i]); } vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) clusterIndices.constData()); } else { - vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) fbxMesh.clusterIndices.constData()); + vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) hfmMesh.clusterIndices.constData()); } } if (clusterWeightsSize > 0) { - vertBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) fbxMesh.clusterWeights.constData()); + vertBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) hfmMesh.clusterWeights.constData()); } @@ -856,7 +856,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { // Index and Part Buffers unsigned int totalIndices = 0; - foreach(const FBXMeshPart& part, extractedMesh.parts) { + foreach(const HFMMeshPart& part, extractedMesh.parts) { totalIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); } @@ -875,7 +875,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { if (extractedMesh.parts.size() > 1) { indexNum = 0; } - foreach(const FBXMeshPart& part, extractedMesh.parts) { + foreach(const HFMMeshPart& part, extractedMesh.parts) { graphics::Mesh::Part modelPart(indexNum, 0, 0, graphics::Mesh::TRIANGLES); if (part.quadTrianglesIndices.size()) { diff --git a/libraries/fbx/src/GLTFReader.cpp b/libraries/fbx/src/GLTFReader.cpp index 317342b886..9cd43ddf08 100644 --- a/libraries/fbx/src/GLTFReader.cpp +++ b/libraries/fbx/src/GLTFReader.cpp @@ -533,10 +533,10 @@ bool GLTFReader::addTexture(const QJsonObject& object) { return true; } -bool GLTFReader::parseGLTF(const QByteArray& model) { +bool GLTFReader::parseGLTF(const QByteArray& data) { PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr); - QJsonDocument d = QJsonDocument::fromJson(model); + QJsonDocument d = QJsonDocument::fromJson(data); QJsonObject jsFile = d.object(); bool isvalid = setAsset(jsFile); @@ -697,7 +697,7 @@ glm::mat4 GLTFReader::getModelTransform(const GLTFNode& node) { return tmat; } -bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { +bool GLTFReader::buildGeometry(HFMModel& hfmModel, const QUrl& url) { //Build dependencies QVector<QVector<int>> nodeDependencies(_file.nodes.size()); @@ -727,17 +727,17 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { } //Build default joints - geometry.joints.resize(1); - geometry.joints[0].isFree = false; - geometry.joints[0].parentIndex = -1; - geometry.joints[0].distanceToParent = 0; - geometry.joints[0].translation = glm::vec3(0, 0, 0); - geometry.joints[0].rotationMin = glm::vec3(0, 0, 0); - geometry.joints[0].rotationMax = glm::vec3(0, 0, 0); - geometry.joints[0].name = "OBJ"; - geometry.joints[0].isSkeletonJoint = true; + hfmModel.joints.resize(1); + hfmModel.joints[0].isFree = false; + hfmModel.joints[0].parentIndex = -1; + hfmModel.joints[0].distanceToParent = 0; + hfmModel.joints[0].translation = glm::vec3(0, 0, 0); + hfmModel.joints[0].rotationMin = glm::vec3(0, 0, 0); + hfmModel.joints[0].rotationMax = glm::vec3(0, 0, 0); + hfmModel.joints[0].name = "OBJ"; + hfmModel.joints[0].isSkeletonJoint = true; - geometry.jointIndices["x"] = 1; + hfmModel.jointIndices["x"] = 1; //Build materials QVector<QString> materialIDs; @@ -750,10 +750,10 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { for (int i = 0; i < materialIDs.size(); i++) { QString& matid = materialIDs[i]; - geometry.materials[matid] = FBXMaterial(); - FBXMaterial& fbxMaterial = geometry.materials[matid]; - fbxMaterial._material = std::make_shared<graphics::Material>(); - setFBXMaterial(fbxMaterial, _file.materials[i]); + hfmModel.materials[matid] = HFMMaterial(); + HFMMaterial& hfmMaterial = hfmModel.materials[matid]; + hfmMaterial._material = std::make_shared<graphics::Material>(); + setHFMMaterial(hfmMaterial, _file.materials[i]); } @@ -765,9 +765,9 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { if (node.defined["mesh"]) { qCDebug(modelformat) << "node_transforms" << node.transforms; foreach(auto &primitive, _file.meshes[node.mesh].primitives) { - geometry.meshes.append(FBXMesh()); - FBXMesh& mesh = geometry.meshes[geometry.meshes.size() - 1]; - FBXCluster cluster; + hfmModel.meshes.append(HFMMesh()); + HFMMesh& mesh = hfmModel.meshes[hfmModel.meshes.size() - 1]; + HFMCluster cluster; cluster.jointIndex = 0; cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0, 0, 1, 0, 0, @@ -775,7 +775,7 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { 0, 0, 0, 1); mesh.clusters.append(cluster); - FBXMeshPart part = FBXMeshPart(); + HFMMeshPart part = HFMMeshPart(); int indicesAccessorIdx = primitive.indices; @@ -886,7 +886,7 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { mesh.meshExtents.reset(); foreach(const glm::vec3& vertex, mesh.vertices) { mesh.meshExtents.addPoint(vertex); - geometry.meshExtents.addPoint(vertex); + hfmModel.meshExtents.addPoint(vertex); } // since mesh.modelTransform seems to not have any effect I apply the transformation the model @@ -898,7 +898,7 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { } } - mesh.meshIndex = geometry.meshes.size(); + mesh.meshIndex = hfmModel.meshes.size(); FBXReader::buildModelMesh(mesh, url.toString()); } @@ -910,7 +910,7 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { return true; } -FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping, +HFMModel* GLTFReader::readGLTF(QByteArray& data, const QVariantHash& mapping, const QUrl& url, bool loadLightmaps, float lightmapLevel) { _url = url; @@ -922,15 +922,15 @@ FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping _url = QUrl(QFileInfo(localFileName).absoluteFilePath()); } - parseGLTF(model); + parseGLTF(data); //_file.dump(); - FBXGeometry* geometryPtr = new FBXGeometry(); - FBXGeometry& geometry = *geometryPtr; + HFMModel* hfmModelPtr = new HFMModel(); + HFMModel& hfmModel = *hfmModelPtr; - buildGeometry(geometry, url); + buildGeometry(hfmModel, url); - //fbxDebugDump(geometry); - return geometryPtr; + //hfmDebugDump(data); + return hfmModelPtr; } @@ -953,7 +953,8 @@ bool GLTFReader::doesResourceExist(const QString& url) { } std::tuple<bool, QByteArray> GLTFReader::requestData(QUrl& url) { - auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(nullptr, url); + auto request = DependencyManager::get<ResourceManager>()->createResourceRequest( + nullptr, url, true, -1, "GLTFReader::requestData"); if (!request) { return std::make_tuple(false, QByteArray()); @@ -996,8 +997,8 @@ QNetworkReply* GLTFReader::request(QUrl& url, bool isTest) { return netReply; // trying to sync later on. } -FBXTexture GLTFReader::getFBXTexture(const GLTFTexture& texture) { - FBXTexture fbxtex = FBXTexture(); +HFMTexture GLTFReader::getHFMTexture(const GLTFTexture& texture) { + HFMTexture fbxtex = HFMTexture(); fbxtex.texcoordSet = 0; if (texture.defined["source"]) { @@ -1013,7 +1014,7 @@ FBXTexture GLTFReader::getFBXTexture(const GLTFTexture& texture) { return fbxtex; } -void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& material) { +void GLTFReader::setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& material) { if (material.defined["name"]) { @@ -1028,17 +1029,17 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia } if (material.defined["emissiveTexture"]) { - fbxmat.emissiveTexture = getFBXTexture(_file.textures[material.emissiveTexture]); + fbxmat.emissiveTexture = getHFMTexture(_file.textures[material.emissiveTexture]); fbxmat.useEmissiveMap = true; } if (material.defined["normalTexture"]) { - fbxmat.normalTexture = getFBXTexture(_file.textures[material.normalTexture]); + fbxmat.normalTexture = getHFMTexture(_file.textures[material.normalTexture]); fbxmat.useNormalMap = true; } if (material.defined["occlusionTexture"]) { - fbxmat.occlusionTexture = getFBXTexture(_file.textures[material.occlusionTexture]); + fbxmat.occlusionTexture = getHFMTexture(_file.textures[material.occlusionTexture]); fbxmat.useOcclusionMap = true; } @@ -1049,14 +1050,14 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia fbxmat.metallic = material.pbrMetallicRoughness.metallicFactor; } if (material.pbrMetallicRoughness.defined["baseColorTexture"]) { - fbxmat.opacityTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]); - fbxmat.albedoTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]); + fbxmat.opacityTexture = getHFMTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]); + fbxmat.albedoTexture = getHFMTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]); fbxmat.useAlbedoMap = true; } if (material.pbrMetallicRoughness.defined["metallicRoughnessTexture"]) { - fbxmat.roughnessTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]); + fbxmat.roughnessTexture = getHFMTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]); fbxmat.useRoughnessMap = true; - fbxmat.metallicTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]); + fbxmat.metallicTexture = getHFMTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]); fbxmat.useMetallicMap = true; } if (material.pbrMetallicRoughness.defined["roughnessFactor"]) { @@ -1180,37 +1181,37 @@ void GLTFReader::retriangulate(const QVector<int>& inIndices, const QVector<glm: } } -void GLTFReader::fbxDebugDump(const FBXGeometry& fbxgeo) { - qCDebug(modelformat) << "---------------- fbxGeometry ----------------"; - qCDebug(modelformat) << " hasSkeletonJoints =" << fbxgeo.hasSkeletonJoints; - qCDebug(modelformat) << " offset =" << fbxgeo.offset; +void GLTFReader::hfmDebugDump(const HFMModel& hfmModel) { + qCDebug(modelformat) << "---------------- hfmModel ----------------"; + qCDebug(modelformat) << " hasSkeletonJoints =" << hfmModel.hasSkeletonJoints; + qCDebug(modelformat) << " offset =" << hfmModel.offset; - qCDebug(modelformat) << " leftEyeJointIndex =" << fbxgeo.leftEyeJointIndex; - qCDebug(modelformat) << " rightEyeJointIndex =" << fbxgeo.rightEyeJointIndex; - qCDebug(modelformat) << " neckJointIndex =" << fbxgeo.neckJointIndex; - qCDebug(modelformat) << " rootJointIndex =" << fbxgeo.rootJointIndex; - qCDebug(modelformat) << " leanJointIndex =" << fbxgeo.leanJointIndex; - qCDebug(modelformat) << " headJointIndex =" << fbxgeo.headJointIndex; - qCDebug(modelformat) << " leftHandJointIndex" << fbxgeo.leftHandJointIndex; - qCDebug(modelformat) << " rightHandJointIndex" << fbxgeo.rightHandJointIndex; - qCDebug(modelformat) << " leftToeJointIndex" << fbxgeo.leftToeJointIndex; - qCDebug(modelformat) << " rightToeJointIndex" << fbxgeo.rightToeJointIndex; - qCDebug(modelformat) << " leftEyeSize = " << fbxgeo.leftEyeSize; - qCDebug(modelformat) << " rightEyeSize = " << fbxgeo.rightEyeSize; + qCDebug(modelformat) << " leftEyeJointIndex =" << hfmModel.leftEyeJointIndex; + qCDebug(modelformat) << " rightEyeJointIndex =" << hfmModel.rightEyeJointIndex; + qCDebug(modelformat) << " neckJointIndex =" << hfmModel.neckJointIndex; + qCDebug(modelformat) << " rootJointIndex =" << hfmModel.rootJointIndex; + qCDebug(modelformat) << " leanJointIndex =" << hfmModel.leanJointIndex; + qCDebug(modelformat) << " headJointIndex =" << hfmModel.headJointIndex; + qCDebug(modelformat) << " leftHandJointIndex" << hfmModel.leftHandJointIndex; + qCDebug(modelformat) << " rightHandJointIndex" << hfmModel.rightHandJointIndex; + qCDebug(modelformat) << " leftToeJointIndex" << hfmModel.leftToeJointIndex; + qCDebug(modelformat) << " rightToeJointIndex" << hfmModel.rightToeJointIndex; + qCDebug(modelformat) << " leftEyeSize = " << hfmModel.leftEyeSize; + qCDebug(modelformat) << " rightEyeSize = " << hfmModel.rightEyeSize; - qCDebug(modelformat) << " palmDirection = " << fbxgeo.palmDirection; + qCDebug(modelformat) << " palmDirection = " << hfmModel.palmDirection; - qCDebug(modelformat) << " neckPivot = " << fbxgeo.neckPivot; + qCDebug(modelformat) << " neckPivot = " << hfmModel.neckPivot; - qCDebug(modelformat) << " bindExtents.size() = " << fbxgeo.bindExtents.size(); - qCDebug(modelformat) << " meshExtents.size() = " << fbxgeo.meshExtents.size(); + qCDebug(modelformat) << " bindExtents.size() = " << hfmModel.bindExtents.size(); + qCDebug(modelformat) << " meshExtents.size() = " << hfmModel.meshExtents.size(); - qCDebug(modelformat) << " jointIndices.size() =" << fbxgeo.jointIndices.size(); - qCDebug(modelformat) << " joints.count() =" << fbxgeo.joints.count(); + qCDebug(modelformat) << " jointIndices.size() =" << hfmModel.jointIndices.size(); + qCDebug(modelformat) << " joints.count() =" << hfmModel.joints.count(); qCDebug(modelformat) << "---------------- Meshes ----------------"; - qCDebug(modelformat) << " meshes.count() =" << fbxgeo.meshes.count(); - qCDebug(modelformat) << " blendshapeChannelNames = " << fbxgeo.blendshapeChannelNames; - foreach(FBXMesh mesh, fbxgeo.meshes) { + qCDebug(modelformat) << " meshes.count() =" << hfmModel.meshes.count(); + qCDebug(modelformat) << " blendshapeChannelNames = " << hfmModel.blendshapeChannelNames; + foreach(HFMMesh mesh, hfmModel.meshes) { qCDebug(modelformat) << "\n"; qCDebug(modelformat) << " meshpointer =" << mesh._mesh.get(); qCDebug(modelformat) << " meshindex =" << mesh.meshIndex; @@ -1226,7 +1227,7 @@ void GLTFReader::fbxDebugDump(const FBXGeometry& fbxgeo) { qCDebug(modelformat) << " modelTransform =" << mesh.modelTransform; qCDebug(modelformat) << " parts.count() =" << mesh.parts.count(); qCDebug(modelformat) << "---------------- Meshes (blendshapes)--------"; - foreach(FBXBlendshape bshape, mesh.blendshapes) { + foreach(HFMBlendshape bshape, mesh.blendshapes) { qCDebug(modelformat) << "\n"; qCDebug(modelformat) << " bshape.indices.count() =" << bshape.indices.count(); qCDebug(modelformat) << " bshape.vertices.count() =" << bshape.vertices.count(); @@ -1234,7 +1235,7 @@ void GLTFReader::fbxDebugDump(const FBXGeometry& fbxgeo) { qCDebug(modelformat) << "\n"; } qCDebug(modelformat) << "---------------- Meshes (meshparts)--------"; - foreach(FBXMeshPart meshPart, mesh.parts) { + foreach(HFMMeshPart meshPart, mesh.parts) { qCDebug(modelformat) << "\n"; qCDebug(modelformat) << " quadIndices.count() =" << meshPart.quadIndices.count(); qCDebug(modelformat) << " triangleIndices.count() =" << meshPart.triangleIndices.count(); @@ -1244,7 +1245,7 @@ void GLTFReader::fbxDebugDump(const FBXGeometry& fbxgeo) { } qCDebug(modelformat) << "---------------- Meshes (clusters)--------"; qCDebug(modelformat) << " clusters.count() =" << mesh.clusters.count(); - foreach(FBXCluster cluster, mesh.clusters) { + foreach(HFMCluster cluster, mesh.clusters) { qCDebug(modelformat) << "\n"; qCDebug(modelformat) << " jointIndex =" << cluster.jointIndex; qCDebug(modelformat) << " inverseBindMatrix =" << cluster.inverseBindMatrix; @@ -1253,18 +1254,18 @@ void GLTFReader::fbxDebugDump(const FBXGeometry& fbxgeo) { qCDebug(modelformat) << "\n"; } qCDebug(modelformat) << "---------------- AnimationFrames ----------------"; - foreach(FBXAnimationFrame anim, fbxgeo.animationFrames) { + foreach(HFMAnimationFrame anim, hfmModel.animationFrames) { qCDebug(modelformat) << " anim.translations = " << anim.translations; qCDebug(modelformat) << " anim.rotations = " << anim.rotations; } - QList<int> mitomona_keys = fbxgeo.meshIndicesToModelNames.keys(); + QList<int> mitomona_keys = hfmModel.meshIndicesToModelNames.keys(); foreach(int key, mitomona_keys) { - qCDebug(modelformat) << " meshIndicesToModelNames key =" << key << " val =" << fbxgeo.meshIndicesToModelNames[key]; + qCDebug(modelformat) << " meshIndicesToModelNames key =" << key << " val =" << hfmModel.meshIndicesToModelNames[key]; } qCDebug(modelformat) << "---------------- Materials ----------------"; - foreach(FBXMaterial mat, fbxgeo.materials) { + foreach(HFMMaterial mat, hfmModel.materials) { qCDebug(modelformat) << "\n"; qCDebug(modelformat) << " mat.materialID =" << mat.materialID; qCDebug(modelformat) << " diffuseColor =" << mat.diffuseColor; @@ -1313,7 +1314,7 @@ void GLTFReader::fbxDebugDump(const FBXGeometry& fbxgeo) { qCDebug(modelformat) << "---------------- Joints ----------------"; - foreach(FBXJoint joint, fbxgeo.joints) { + foreach(HFMJoint joint, hfmModel.joints) { qCDebug(modelformat) << "\n"; qCDebug(modelformat) << " shapeInfo.avgPoint =" << joint.shapeInfo.avgPoint; qCDebug(modelformat) << " shapeInfo.debugLines =" << joint.shapeInfo.debugLines; diff --git a/libraries/fbx/src/GLTFReader.h b/libraries/fbx/src/GLTFReader.h index 2183256b87..44186504f0 100644 --- a/libraries/fbx/src/GLTFReader.h +++ b/libraries/fbx/src/GLTFReader.h @@ -706,7 +706,7 @@ class GLTFReader : public QObject { Q_OBJECT public: GLTFReader(); - FBXGeometry* readGLTF(QByteArray& model, const QVariantHash& mapping, + HFMModel* readGLTF(QByteArray& data, const QVariantHash& mapping, const QUrl& url, bool loadLightmaps = true, float lightmapLevel = 1.0f); private: GLTFFile _file; @@ -714,8 +714,8 @@ private: glm::mat4 getModelTransform(const GLTFNode& node); - bool buildGeometry(FBXGeometry& geometry, const QUrl& url); - bool parseGLTF(const QByteArray& model); + bool buildGeometry(HFMModel& hfmModel, const QUrl& url); + bool parseGLTF(const QByteArray& data); bool getStringVal(const QJsonObject& object, const QString& fieldname, QString& value, QMap<QString, bool>& defined); @@ -778,9 +778,9 @@ private: bool doesResourceExist(const QString& url); - void setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& material); - FBXTexture getFBXTexture(const GLTFTexture& texture); - void fbxDebugDump(const FBXGeometry& fbxgeo); + void setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& material); + HFMTexture getHFMTexture(const GLTFTexture& texture); + void hfmDebugDump(const HFMModel& hfmModel); }; #endif // hifi_GLTFReader_h \ No newline at end of file diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 37809585d2..9a2d16f5d1 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -175,7 +175,7 @@ glm::vec2 OBJTokenizer::getVec2() { } -void setMeshPartDefaults(FBXMeshPart& meshPart, QString materialID) { +void setMeshPartDefaults(HFMMeshPart& meshPart, QString materialID) { meshPart.materialID = materialID; } @@ -443,7 +443,8 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file } std::tuple<bool, QByteArray> requestData(QUrl& url) { - auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(nullptr, url); + auto request = DependencyManager::get<ResourceManager>()->createResourceRequest( + nullptr, url, true, -1, "(OBJReader) requestData"); if (!request) { return std::make_tuple(false, QByteArray()); @@ -487,12 +488,12 @@ QNetworkReply* request(QUrl& url, bool isTest) { } -bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, +bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, HFMModel& hfmModel, float& scaleGuess, bool combineParts) { FaceGroup faces; - FBXMesh& mesh = geometry.meshes[0]; - mesh.parts.append(FBXMeshPart()); - FBXMeshPart& meshPart = mesh.parts.last(); + HFMMesh& mesh = hfmModel.meshes[0]; + mesh.parts.append(HFMMeshPart()); + HFMMeshPart& meshPart = mesh.parts.last(); bool sawG = false; bool result = true; int originalFaceCountForDebugging = 0; @@ -651,43 +652,43 @@ done: } -FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url) { +HFMModel::Pointer OBJReader::readOBJ(QByteArray& data, const QVariantHash& mapping, bool combineParts, const QUrl& url) { PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr); - QBuffer buffer { &model }; + QBuffer buffer { &data }; buffer.open(QIODevice::ReadOnly); - auto geometryPtr { std::make_shared<FBXGeometry>() }; - FBXGeometry& geometry { *geometryPtr }; + auto hfmModelPtr { std::make_shared<HFMModel>() }; + HFMModel& hfmModel { *hfmModelPtr }; OBJTokenizer tokenizer { &buffer }; float scaleGuess = 1.0f; bool needsMaterialLibrary = false; _url = url; - geometry.meshExtents.reset(); - geometry.meshes.append(FBXMesh()); + hfmModel.meshExtents.reset(); + hfmModel.meshes.append(HFMMesh()); try { // call parseOBJGroup as long as it's returning true. Each successful call will - // add a new meshPart to the geometry's single mesh. - while (parseOBJGroup(tokenizer, mapping, geometry, scaleGuess, combineParts)) {} + // add a new meshPart to the model's single mesh. + while (parseOBJGroup(tokenizer, mapping, hfmModel, scaleGuess, combineParts)) {} - FBXMesh& mesh = geometry.meshes[0]; + HFMMesh& mesh = hfmModel.meshes[0]; mesh.meshIndex = 0; - geometry.joints.resize(1); - geometry.joints[0].isFree = false; - geometry.joints[0].parentIndex = -1; - geometry.joints[0].distanceToParent = 0; - geometry.joints[0].translation = glm::vec3(0, 0, 0); - geometry.joints[0].rotationMin = glm::vec3(0, 0, 0); - geometry.joints[0].rotationMax = glm::vec3(0, 0, 0); - geometry.joints[0].name = "OBJ"; - geometry.joints[0].isSkeletonJoint = true; + hfmModel.joints.resize(1); + hfmModel.joints[0].isFree = false; + hfmModel.joints[0].parentIndex = -1; + hfmModel.joints[0].distanceToParent = 0; + hfmModel.joints[0].translation = glm::vec3(0, 0, 0); + hfmModel.joints[0].rotationMin = glm::vec3(0, 0, 0); + hfmModel.joints[0].rotationMax = glm::vec3(0, 0, 0); + hfmModel.joints[0].name = "OBJ"; + hfmModel.joints[0].isSkeletonJoint = true; - geometry.jointIndices["x"] = 1; + hfmModel.jointIndices["x"] = 1; - FBXCluster cluster; + HFMCluster cluster; cluster.jointIndex = 0; cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0, 0, 1, 0, 0, @@ -696,20 +697,20 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m mesh.clusters.append(cluster); QMap<QString, int> materialMeshIdMap; - QVector<FBXMeshPart> fbxMeshParts; + QVector<HFMMeshPart> hfmMeshParts; for (int i = 0, meshPartCount = 0; i < mesh.parts.count(); i++, meshPartCount++) { - FBXMeshPart& meshPart = mesh.parts[i]; + HFMMeshPart& meshPart = mesh.parts[i]; FaceGroup faceGroup = faceGroups[meshPartCount]; bool specifiesUV = false; foreach(OBJFace face, faceGroup) { // Go through all of the OBJ faces and determine the number of different materials necessary (each different material will be a unique mesh). // NOTE (trent/mittens 3/30/17): this seems hardcore wasteful and is slowed down a bit by iterating through the face group twice, but it's the best way I've thought of to hack multi-material support in an OBJ into this pipeline. if (!materialMeshIdMap.contains(face.materialName)) { - // Create a new FBXMesh for this material mapping. + // Create a new HFMMesh for this material mapping. materialMeshIdMap.insert(face.materialName, materialMeshIdMap.count()); - fbxMeshParts.append(FBXMeshPart()); - FBXMeshPart& meshPartNew = fbxMeshParts.last(); + hfmMeshParts.append(HFMMeshPart()); + HFMMeshPart& meshPartNew = hfmMeshParts.last(); meshPartNew.quadIndices = QVector<int>(meshPart.quadIndices); // Copy over quad indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway]. meshPartNew.quadTrianglesIndices = QVector<int>(meshPart.quadTrianglesIndices); // Copy over quad triangulated indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway]. meshPartNew.triangleIndices = QVector<int>(meshPart.triangleIndices); // Copy over triangle indices. @@ -744,14 +745,14 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m // clean up old mesh parts. int unmodifiedMeshPartCount = mesh.parts.count(); mesh.parts.clear(); - mesh.parts = QVector<FBXMeshPart>(fbxMeshParts); + mesh.parts = QVector<HFMMeshPart>(hfmMeshParts); for (int i = 0, meshPartCount = 0; i < unmodifiedMeshPartCount; i++, meshPartCount++) { FaceGroup faceGroup = faceGroups[meshPartCount]; // Now that each mesh has been created with its own unique material mappings, fill them with data (vertex data is duplicated, face data is not). foreach(OBJFace face, faceGroup) { - FBXMeshPart& meshPart = mesh.parts[materialMeshIdMap[face.materialName]]; + HFMMeshPart& meshPart = mesh.parts[materialMeshIdMap[face.materialName]]; glm::vec3 v0 = checked_at(vertices, face.vertexIndices[0]); glm::vec3 v1 = checked_at(vertices, face.vertexIndices[1]); @@ -817,13 +818,13 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m mesh.meshExtents.reset(); foreach(const glm::vec3& vertex, mesh.vertices) { mesh.meshExtents.addPoint(vertex); - geometry.meshExtents.addPoint(vertex); + hfmModel.meshExtents.addPoint(vertex); } // Build the single mesh. FBXReader::buildModelMesh(mesh, url.toString()); - // fbxDebugDump(geometry); + // hfmDebugDump(hfmModel); } catch(const std::exception& e) { qCDebug(modelformat) << "OBJ reader fail: " << e.what(); } @@ -884,38 +885,38 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m if (!objMaterial.used) { continue; } - geometry.materials[materialID] = FBXMaterial(objMaterial.diffuseColor, + hfmModel.materials[materialID] = HFMMaterial(objMaterial.diffuseColor, objMaterial.specularColor, objMaterial.emissiveColor, objMaterial.shininess, objMaterial.opacity); - FBXMaterial& fbxMaterial = geometry.materials[materialID]; - fbxMaterial.materialID = materialID; - fbxMaterial._material = std::make_shared<graphics::Material>(); - graphics::MaterialPointer modelMaterial = fbxMaterial._material; + HFMMaterial& hfmMaterial = hfmModel.materials[materialID]; + hfmMaterial.materialID = materialID; + hfmMaterial._material = std::make_shared<graphics::Material>(); + graphics::MaterialPointer modelMaterial = hfmMaterial._material; if (!objMaterial.diffuseTextureFilename.isEmpty()) { - fbxMaterial.albedoTexture.filename = objMaterial.diffuseTextureFilename; + hfmMaterial.albedoTexture.filename = objMaterial.diffuseTextureFilename; } if (!objMaterial.specularTextureFilename.isEmpty()) { - fbxMaterial.specularTexture.filename = objMaterial.specularTextureFilename; + hfmMaterial.specularTexture.filename = objMaterial.specularTextureFilename; } if (!objMaterial.emissiveTextureFilename.isEmpty()) { - fbxMaterial.emissiveTexture.filename = objMaterial.emissiveTextureFilename; + hfmMaterial.emissiveTexture.filename = objMaterial.emissiveTextureFilename; } if (!objMaterial.bumpTextureFilename.isEmpty()) { - fbxMaterial.normalTexture.filename = objMaterial.bumpTextureFilename; - fbxMaterial.normalTexture.isBumpmap = true; - fbxMaterial.bumpMultiplier = objMaterial.bumpTextureOptions.bumpMultiplier; + hfmMaterial.normalTexture.filename = objMaterial.bumpTextureFilename; + hfmMaterial.normalTexture.isBumpmap = true; + hfmMaterial.bumpMultiplier = objMaterial.bumpTextureOptions.bumpMultiplier; } if (!objMaterial.opacityTextureFilename.isEmpty()) { - fbxMaterial.opacityTexture.filename = objMaterial.opacityTextureFilename; + hfmMaterial.opacityTexture.filename = objMaterial.opacityTextureFilename; } - modelMaterial->setEmissive(fbxMaterial.emissiveColor); - modelMaterial->setAlbedo(fbxMaterial.diffuseColor); - modelMaterial->setMetallic(glm::length(fbxMaterial.specularColor)); - modelMaterial->setRoughness(graphics::Material::shininessToRoughness(fbxMaterial.shininess)); + modelMaterial->setEmissive(hfmMaterial.emissiveColor); + modelMaterial->setAlbedo(hfmMaterial.diffuseColor); + modelMaterial->setMetallic(glm::length(hfmMaterial.specularColor)); + modelMaterial->setRoughness(graphics::Material::shininessToRoughness(hfmMaterial.shininess)); bool applyTransparency = false; bool applyShininess = false; @@ -970,7 +971,7 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m } if (applyTransparency) { - fbxMaterial.opacity = std::max(fbxMaterial.opacity, ILLUMINATION_MODEL_MIN_OPACITY); + hfmMaterial.opacity = std::max(hfmMaterial.opacity, ILLUMINATION_MODEL_MIN_OPACITY); } if (applyShininess) { modelMaterial->setRoughness(ILLUMINATION_MODEL_APPLY_SHININESS); @@ -984,18 +985,18 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m // TODO: how to turn fresnel on? } - modelMaterial->setOpacity(fbxMaterial.opacity); + modelMaterial->setOpacity(hfmMaterial.opacity); } - return geometryPtr; + return hfmModelPtr; } -void fbxDebugDump(const FBXGeometry& fbxgeo) { - qCDebug(modelformat) << "---------------- fbxGeometry ----------------"; - qCDebug(modelformat) << " hasSkeletonJoints =" << fbxgeo.hasSkeletonJoints; - qCDebug(modelformat) << " offset =" << fbxgeo.offset; - qCDebug(modelformat) << " meshes.count() =" << fbxgeo.meshes.count(); - foreach (FBXMesh mesh, fbxgeo.meshes) { +void hfmDebugDump(const HFMModel& hfmModel) { + qCDebug(modelformat) << "---------------- hfmModel ----------------"; + qCDebug(modelformat) << " hasSkeletonJoints =" << hfmModel.hasSkeletonJoints; + qCDebug(modelformat) << " offset =" << hfmModel.offset; + qCDebug(modelformat) << " meshes.count() =" << hfmModel.meshes.count(); + foreach (HFMMesh mesh, hfmModel.meshes) { qCDebug(modelformat) << " vertices.count() =" << mesh.vertices.count(); qCDebug(modelformat) << " colors.count() =" << mesh.colors.count(); qCDebug(modelformat) << " normals.count() =" << mesh.normals.count(); @@ -1013,7 +1014,7 @@ void fbxDebugDump(const FBXGeometry& fbxgeo) { qCDebug(modelformat) << " meshExtents =" << mesh.meshExtents; qCDebug(modelformat) << " modelTransform =" << mesh.modelTransform; qCDebug(modelformat) << " parts.count() =" << mesh.parts.count(); - foreach (FBXMeshPart meshPart, mesh.parts) { + foreach (HFMMeshPart meshPart, mesh.parts) { qCDebug(modelformat) << " quadIndices.count() =" << meshPart.quadIndices.count(); qCDebug(modelformat) << " triangleIndices.count() =" << meshPart.triangleIndices.count(); /* @@ -1030,16 +1031,16 @@ void fbxDebugDump(const FBXGeometry& fbxgeo) { */ } qCDebug(modelformat) << " clusters.count() =" << mesh.clusters.count(); - foreach (FBXCluster cluster, mesh.clusters) { + foreach (HFMCluster cluster, mesh.clusters) { qCDebug(modelformat) << " jointIndex =" << cluster.jointIndex; qCDebug(modelformat) << " inverseBindMatrix =" << cluster.inverseBindMatrix; } } - qCDebug(modelformat) << " jointIndices =" << fbxgeo.jointIndices; - qCDebug(modelformat) << " joints.count() =" << fbxgeo.joints.count(); + qCDebug(modelformat) << " jointIndices =" << hfmModel.jointIndices; + qCDebug(modelformat) << " joints.count() =" << hfmModel.joints.count(); - foreach (FBXJoint joint, fbxgeo.joints) { + foreach (HFMJoint joint, hfmModel.joints) { qCDebug(modelformat) << " isFree =" << joint.isFree; qCDebug(modelformat) << " freeLineage" << joint.freeLineage; qCDebug(modelformat) << " parentIndex" << joint.parentIndex; diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index e432a3ea51..0088e8e9d7 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -42,7 +42,7 @@ public: bool add(const QByteArray& vertexIndex, const QByteArray& textureIndex, const QByteArray& normalIndex, const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& vertexColors); // Return a set of one or more OBJFaces from this one, in which each is just a triangle. - // Even though FBXMeshPart can handle quads, it would be messy to try to keep track of mixed-size faces, so we treat everything as triangles. + // Even though HFMMeshPart can handle quads, it would be messy to try to keep track of mixed-size faces, so we treat everything as triangles. QVector<OBJFace> triangulate(); private: void addFrom(const OBJFace* face, int index); @@ -54,7 +54,7 @@ public: } ; // Materials and references to material names can come in any order, and different mesh parts can refer to the same material. -// Therefore it would get pretty hacky to try to use FBXMeshPart to store these as we traverse the files. +// Therefore it would get pretty hacky to try to use HFMMeshPart to store these as we traverse the files. class OBJMaterial { public: float shininess; @@ -87,13 +87,13 @@ public: QString currentMaterialName; QHash<QString, OBJMaterial> materials; - FBXGeometry::Pointer readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url = QUrl()); + HFMModel::Pointer readOBJ(QByteArray& data, const QVariantHash& mapping, bool combineParts, const QUrl& url = QUrl()); private: QUrl _url; QHash<QByteArray, bool> librariesSeen; - bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, + bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, HFMModel& hfmModel, float& scaleGuess, bool combineParts); void parseMaterialLibrary(QIODevice* device); void parseTextureLine(const QByteArray& textureLine, QByteArray& filename, OBJMaterialTextureOptions& textureOptions); @@ -103,5 +103,5 @@ private: }; // What are these utilities doing here? One is used by fbx loading code in VHACD Utils, and the other a general debugging utility. -void setMeshPartDefaults(FBXMeshPart& meshPart, QString materialID); -void fbxDebugDump(const FBXGeometry& fbxgeo); +void setMeshPartDefaults(HFMMeshPart& meshPart, QString materialID); +void hfmDebugDump(const HFMModel& hfmModel); diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index ad7e51fbd3..d4ecbaa5ba 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -25,25 +25,24 @@ #include "GLLogging.h" #include "Config.h" #include "GLHelpers.h" +#include "QOpenGLContextWrapper.h" using namespace gl; bool Context::enableDebugLogger() { +#if defined(Q_OS_MAC) + // OSX does not support GL_KHR_debug or GL_ARB_debug_output + return false; +#else #if defined(DEBUG) || defined(USE_GLES) static bool enableDebugLogger = true; #else static const QString DEBUG_FLAG("HIFI_DEBUG_OPENGL"); static bool enableDebugLogger = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); #endif - static std::once_flag once; - std::call_once(once, [&] { - // If the previous run crashed, force GL debug logging on - if (qApp->property(hifi::properties::CRASHED).toBool()) { - enableDebugLogger = true; - } - }); return enableDebugLogger; +#endif } @@ -68,8 +67,6 @@ void Context::updateSwapchainMemoryUsage(size_t prevSize, size_t newSize) { } -Context* Context::PRIMARY = nullptr; - Context::Context() {} Context::Context(QWindow* window) { @@ -97,9 +94,6 @@ void Context::release() { _context = nullptr; #endif _window = nullptr; - if (PRIMARY == this) { - PRIMARY = nullptr; - } updateSwapchainMemoryCounter(); } @@ -235,16 +229,9 @@ typedef HGLRC(APIENTRYP PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, HGLRC hShare GLAPI PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB; GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; +Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); -void Context::create() { - if (!PRIMARY) { - PRIMARY = static_cast<Context*>(qApp->property(hifi::properties::gl::PRIMARY_CONTEXT).value<void*>()); - } - - if (PRIMARY) { - _version = PRIMARY->_version; - } - +void Context::create(QOpenGLContext* shareContext) { assert(0 != _hwnd); assert(0 == _hdc); auto hwnd = _hwnd; @@ -338,7 +325,10 @@ void Context::create() { contextAttribs.push_back(0); } contextAttribs.push_back(0); - auto shareHglrc = PRIMARY ? PRIMARY->_hglrc : 0; + if (!shareContext) { + shareContext = qt_gl_global_share_context(); + } + HGLRC shareHglrc = (HGLRC)QOpenGLContextWrapper::nativeContext(shareContext); _hglrc = wglCreateContextAttribsARB(_hdc, shareHglrc, &contextAttribs[0]); } @@ -346,11 +336,6 @@ void Context::create() { throw std::runtime_error("Could not create GL context"); } - if (!PRIMARY) { - PRIMARY = this; - qApp->setProperty(hifi::properties::gl::PRIMARY_CONTEXT, QVariant::fromValue((void*)PRIMARY)); - } - if (!makeCurrent()) { throw std::runtime_error("Could not make context current"); } @@ -363,12 +348,11 @@ void Context::create() { #endif - OffscreenContext::~OffscreenContext() { _window->deleteLater(); } -void OffscreenContext::create() { +void OffscreenContext::create(QOpenGLContext* shareContext) { if (!_window) { _window = new QWindow(); _window->setFlags(Qt::MSWindowsOwnDC); @@ -379,5 +363,5 @@ void OffscreenContext::create() { qCDebug(glLogging) << "New Offscreen GLContext, window size = " << windowSize.width() << " , " << windowSize.height(); QGuiApplication::processEvents(); } - Parent::create(); + Parent::create(shareContext); } diff --git a/libraries/gl/src/gl/Context.h b/libraries/gl/src/gl/Context.h index b6160cbd6c..05cb361725 100644 --- a/libraries/gl/src/gl/Context.h +++ b/libraries/gl/src/gl/Context.h @@ -21,6 +21,7 @@ class QSurface; class QWindow; class QOpenGLContext; class QThread; +class QOpenGLDebugMessage; #if defined(Q_OS_WIN) #define GL_CUSTOM_CONTEXT @@ -30,7 +31,6 @@ namespace gl { class Context { protected: QWindow* _window { nullptr }; - static Context* PRIMARY; static void destroyContext(QOpenGLContext* context); #if defined(GL_CUSTOM_CONTEXT) uint32_t _version { 0x0401 }; @@ -48,6 +48,9 @@ namespace gl { public: static bool enableDebugLogger(); + static void debugMessageHandler(const QOpenGLDebugMessage &debugMessage); + static void setupDebugLogging(QOpenGLContext* context); + Context(); Context(QWindow* window); void release(); @@ -59,14 +62,14 @@ namespace gl { static void makeCurrent(QOpenGLContext* context, QSurface* surface); void swapBuffers(); void doneCurrent(); - virtual void create(); + virtual void create(QOpenGLContext* shareContext = nullptr); QOpenGLContext* qglContext(); void moveToThread(QThread* thread); static size_t evalSurfaceMemoryUsage(uint32_t width, uint32_t height, uint32_t pixelSize); static size_t getSwapchainMemoryUsage(); static void updateSwapchainMemoryUsage(size_t prevSize, size_t newSize); - + private: static std::atomic<size_t> _totalSwapchainMemoryUsage; @@ -81,7 +84,7 @@ namespace gl { QWindow* _window { nullptr }; public: virtual ~OffscreenContext(); - void create() override; + void create(QOpenGLContext* shareContext = nullptr) override; }; } diff --git a/libraries/gl/src/gl/ContextQt.cpp b/libraries/gl/src/gl/ContextQt.cpp index dd65c3076c..f554877b2a 100644 --- a/libraries/gl/src/gl/ContextQt.cpp +++ b/libraries/gl/src/gl/ContextQt.cpp @@ -17,6 +17,8 @@ #include <QtPlatformHeaders/QWGLNativeContext> #endif +#include <QtGui/QOpenGLDebugMessage> + #include "GLHelpers.h" using namespace gl; @@ -47,6 +49,32 @@ void Context::moveToThread(QThread* thread) { qglContext()->moveToThread(thread); } +void Context::debugMessageHandler(const QOpenGLDebugMessage& debugMessage) { + auto severity = debugMessage.severity(); + switch (severity) { + case QOpenGLDebugMessage::NotificationSeverity: + case QOpenGLDebugMessage::LowSeverity: + return; + default: + break; + } + qDebug(glLogging) << debugMessage; + return; +} + +void Context::setupDebugLogging(QOpenGLContext *context) { + QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(context); + QObject::connect(logger, &QOpenGLDebugLogger::messageLogged, nullptr, [](const QOpenGLDebugMessage& message){ + Context::debugMessageHandler(message); + }); + if (logger->initialize()) { + logger->enableMessages(); + logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); + } else { + qCWarning(glLogging) << "OpenGL context does not support debugging"; + } +} + #ifndef GL_CUSTOM_CONTEXT bool Context::makeCurrent() { updateSwapchainMemoryCounter(); @@ -65,21 +93,29 @@ void Context::doneCurrent() { } } +Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); const QSurfaceFormat& getDefaultOpenGLSurfaceFormat(); - -void Context::create() { +void Context::create(QOpenGLContext* shareContext) { _context = new QOpenGLContext(); - if (PRIMARY) { - _context->setShareContext(PRIMARY->qglContext()); - } else { - PRIMARY = this; + _context->setFormat(_window->format()); + if (!shareContext) { + shareContext = qt_gl_global_share_context(); } - _context->setFormat(getDefaultOpenGLSurfaceFormat()); - _context->create(); + _context->setShareContext(shareContext); + _context->create(); _swapchainPixelSize = evalGLFormatSwapchainPixelSize(_context->format()); updateSwapchainMemoryCounter(); + + if (!makeCurrent()) { + throw std::runtime_error("Could not make context current"); + } + if (enableDebugLogger()) { + setupDebugLogging(_context); + } + doneCurrent(); + } #endif diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index 7ebba4f8d8..15a41c3dc1 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -13,6 +13,8 @@ #include <QtGui/QOpenGLContext> #include <QtGui/QOpenGLDebugLogger> +#include "Context.h" + size_t evalGLFormatSwapchainPixelSize(const QSurfaceFormat& format) { size_t pixelSize = format.redBufferSize() + format.greenBufferSize() + format.blueBufferSize() + format.alphaBufferSize(); // We don't apply the length of the swap chain into this pixelSize since it is not vsible for the Process (on windows). @@ -34,14 +36,54 @@ bool gl::disableGl45() { #endif } +#ifdef Q_OS_MAC +#define SERIALIZE_GL_RENDERING +#endif + +#ifdef SERIALIZE_GL_RENDERING + +// This terrible terrible hack brought to you by the complete lack of reasonable +// OpenGL debugging tools on OSX. Without this serialization code, the UI textures +// frequently become 'glitchy' and get composited onto the main scene in what looks +// like a partially rendered state. +// This looks very much like either state bleeding across the contexts, or bad +// synchronization for the shared OpenGL textures. However, previous attempts to resolve +// it, even with gratuitous use of glFinish hasn't improved the situation + +static std::mutex _globalOpenGLLock; + +void gl::globalLock() { + _globalOpenGLLock.lock(); +} + +void gl::globalRelease(bool finish) { + if (finish) { + glFinish(); + } + _globalOpenGLLock.unlock(); +} + +#else + +void gl::globalLock() {} +void gl::globalRelease(bool finish) {} + +#endif + + void gl::getTargetVersion(int& major, int& minor) { #if defined(USE_GLES) major = 3; minor = 2; +#else +#if defined(Q_OS_MAC) + major = 4; + minor = 1; #else major = 4; minor = disableGl45() ? 1 : 5; #endif +#endif } const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { @@ -57,6 +99,9 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { #else format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); #endif + if (gl::Context::enableDebugLogger()) { + format.setOption(QSurfaceFormat::DebugContext); + } // Qt Quick may need a depth and stencil buffer. Always make sure these are available. format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS); format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS); @@ -64,7 +109,6 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { ::gl::getTargetVersion(major, minor); format.setMajorVersion(major); format.setMinorVersion(minor); - QSurfaceFormat::setDefaultFormat(format); }); return format; } diff --git a/libraries/gl/src/gl/GLHelpers.h b/libraries/gl/src/gl/GLHelpers.h index 6252eba2f0..1865442ef6 100644 --- a/libraries/gl/src/gl/GLHelpers.h +++ b/libraries/gl/src/gl/GLHelpers.h @@ -35,6 +35,9 @@ int glVersionToInteger(QString glVersion); bool isRenderThread(); namespace gl { + void globalLock(); + void globalRelease(bool finish = true); + void withSavedContext(const std::function<void()>& f); bool checkGLError(const char* name); diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp index 1c0ad1a85e..94702a9906 100644 --- a/libraries/gl/src/gl/GLWidget.cpp +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -63,10 +63,10 @@ int GLWidget::getDeviceHeight() const { return height() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f); } -void GLWidget::createContext() { +void GLWidget::createContext(QOpenGLContext* shareContext) { _context = new gl::Context(); _context->setWindow(windowHandle()); - _context->create(); + _context->create(shareContext); _context->makeCurrent(); _context->clear(); _context->doneCurrent(); diff --git a/libraries/gl/src/gl/GLWidget.h b/libraries/gl/src/gl/GLWidget.h index a0bf8ea0b0..777d43e8af 100644 --- a/libraries/gl/src/gl/GLWidget.h +++ b/libraries/gl/src/gl/GLWidget.h @@ -29,7 +29,7 @@ public: int getDeviceHeight() const; QSize getDeviceSize() const { return QSize(getDeviceWidth(), getDeviceHeight()); } QPaintEngine* paintEngine() const override; - void createContext(); + void createContext(QOpenGLContext* shareContext = nullptr); bool makeCurrent(); void doneCurrent(); void swapBuffers(); diff --git a/libraries/gl/src/gl/GLWindow.cpp b/libraries/gl/src/gl/GLWindow.cpp index e1e6279b1c..7930e050de 100644 --- a/libraries/gl/src/gl/GLWindow.cpp +++ b/libraries/gl/src/gl/GLWindow.cpp @@ -22,7 +22,7 @@ void GLWindow::createContext(QOpenGLContext* shareContext) { void GLWindow::createContext(const QSurfaceFormat& format, QOpenGLContext* shareContext) { _context = new gl::Context(); _context->setWindow(this); - _context->create(); + _context->create(shareContext); _context->makeCurrent(); _context->clear(); } diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 6598a26c99..f05acb50e9 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -33,6 +33,7 @@ OffscreenGLCanvas::OffscreenGLCanvas() : _context(new QOpenGLContext), _offscreenSurface(new QOffscreenSurface) { + setFormat(getDefaultOpenGLSurfaceFormat()); } OffscreenGLCanvas::~OffscreenGLCanvas() { @@ -49,12 +50,15 @@ OffscreenGLCanvas::~OffscreenGLCanvas() { } +void OffscreenGLCanvas::setFormat(const QSurfaceFormat& format) { + _context->setFormat(format); +} + bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { if (nullptr != sharedContext) { sharedContext->doneCurrent(); _context->setShareContext(sharedContext); } - _context->setFormat(getDefaultOpenGLSurfaceFormat()); if (!_context->create()) { qFatal("Failed to create OffscreenGLCanvas context"); } @@ -69,38 +73,16 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { if (!_context->makeCurrent(_offscreenSurface)) { qFatal("Unable to make offscreen surface current"); } + _context->doneCurrent(); #else if (!_offscreenSurface->isValid()) { qFatal("Offscreen surface is invalid"); } #endif - if (gl::Context::enableDebugLogger()) { - _context->makeCurrent(_offscreenSurface); - QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this); - connect(logger, &QOpenGLDebugLogger::messageLogged, this, &OffscreenGLCanvas::onMessageLogged); - logger->initialize(); - logger->enableMessages(); - logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); - _context->doneCurrent(); - } - return true; } -void OffscreenGLCanvas::onMessageLogged(const QOpenGLDebugMessage& debugMessage) { - auto severity = debugMessage.severity(); - switch (severity) { - case QOpenGLDebugMessage::NotificationSeverity: - case QOpenGLDebugMessage::LowSeverity: - return; - default: - break; - } - qDebug(glLogging) << debugMessage; - return; -} - bool OffscreenGLCanvas::makeCurrent() { bool result = _context->makeCurrent(_offscreenSurface); if (glGetString) { diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.h b/libraries/gl/src/gl/OffscreenGLCanvas.h index a4960ae234..3946f760cf 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.h +++ b/libraries/gl/src/gl/OffscreenGLCanvas.h @@ -18,11 +18,13 @@ class QOpenGLContext; class QOffscreenSurface; class QOpenGLDebugMessage; +class QSurfaceFormat; class OffscreenGLCanvas : public QObject { public: OffscreenGLCanvas(); ~OffscreenGLCanvas(); + void setFormat(const QSurfaceFormat& format); bool create(QOpenGLContext* sharedContext = nullptr); bool makeCurrent(); void doneCurrent(); @@ -35,9 +37,6 @@ public: void setThreadContext(); static bool restoreThreadContext(); -private slots: - void onMessageLogged(const QOpenGLDebugMessage &debugMessage); - protected: void clearThreadContext(); diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp index 0b153a5ae8..fbebb1128d 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp @@ -13,6 +13,10 @@ #include <QOpenGLContext> +#ifdef Q_OS_WIN +#include <QtPlatformHeaders/QWGLNativeContext> +#endif + uint32_t QOpenGLContextWrapper::currentContextVersion() { QOpenGLContext* context = QOpenGLContext::currentContext(); if (!context) { @@ -45,6 +49,19 @@ void QOpenGLContextWrapper::setFormat(const QSurfaceFormat& format) { _context->setFormat(format); } +#ifdef Q_OS_WIN +void* QOpenGLContextWrapper::nativeContext(QOpenGLContext* context) { + HGLRC result = 0; + if (context != nullptr) { + auto nativeHandle = context->nativeHandle(); + if (nativeHandle.canConvert<QWGLNativeContext>()) { + result = nativeHandle.value<QWGLNativeContext>().context(); + } + } + return result; +} +#endif + bool QOpenGLContextWrapper::create() { return _context->create(); } diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.h b/libraries/gl/src/gl/QOpenGLContextWrapper.h index b09ad1a4c3..32ba7f22e8 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.h +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.h @@ -13,6 +13,7 @@ #define hifi_QOpenGLContextWrapper_h #include <stdint.h> +#include <QtGlobal> class QOpenGLContext; class QSurface; @@ -21,6 +22,10 @@ class QThread; class QOpenGLContextWrapper { public: +#ifdef Q_OS_WIN + static void* nativeContext(QOpenGLContext* context); +#endif + QOpenGLContextWrapper(); QOpenGLContextWrapper(QOpenGLContext* context); virtual ~QOpenGLContextWrapper(); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index 1203e65685..c1ce05c18b 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -708,37 +708,37 @@ void GLBackend::do_glColor4f(const Batch& batch, size_t paramOffset) { void GLBackend::releaseBuffer(GLuint id, Size size) const { Lock lock(_trashMutex); - _buffersTrash.push_back({ id, size }); + _currentFrameTrash.buffersTrash.push_back({ id, size }); } void GLBackend::releaseExternalTexture(GLuint id, const Texture::ExternalRecycler& recycler) const { Lock lock(_trashMutex); - _externalTexturesTrash.push_back({ id, recycler }); + _currentFrameTrash.externalTexturesTrash.push_back({ id, recycler }); } void GLBackend::releaseTexture(GLuint id, Size size) const { Lock lock(_trashMutex); - _texturesTrash.push_back({ id, size }); + _currentFrameTrash.texturesTrash.push_back({ id, size }); } void GLBackend::releaseFramebuffer(GLuint id) const { Lock lock(_trashMutex); - _framebuffersTrash.push_back(id); + _currentFrameTrash.framebuffersTrash.push_back(id); } void GLBackend::releaseShader(GLuint id) const { Lock lock(_trashMutex); - _shadersTrash.push_back(id); + _currentFrameTrash.shadersTrash.push_back(id); } void GLBackend::releaseProgram(GLuint id) const { Lock lock(_trashMutex); - _programsTrash.push_back(id); + _currentFrameTrash.programsTrash.push_back(id); } void GLBackend::releaseQuery(GLuint id) const { Lock lock(_trashMutex); - _queriesTrash.push_back(id); + _currentFrameTrash.queriesTrash.push_back(id); } void GLBackend::queueLambda(const std::function<void()> lambda) const { @@ -746,6 +746,81 @@ void GLBackend::queueLambda(const std::function<void()> lambda) const { _lambdaQueue.push_back(lambda); } +void GLBackend::FrameTrash::cleanup() { + glWaitSync(fence, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(fence); + + { + std::vector<GLuint> ids; + ids.reserve(buffersTrash.size()); + for (auto pair : buffersTrash) { + ids.push_back(pair.first); + } + if (!ids.empty()) { + glDeleteBuffers((GLsizei)ids.size(), ids.data()); + } + } + + { + std::vector<GLuint> ids; + ids.reserve(framebuffersTrash.size()); + for (auto id : framebuffersTrash) { + ids.push_back(id); + } + if (!ids.empty()) { + glDeleteFramebuffers((GLsizei)ids.size(), ids.data()); + } + } + + { + std::vector<GLuint> ids; + ids.reserve(texturesTrash.size()); + for (auto pair : texturesTrash) { + ids.push_back(pair.first); + } + if (!ids.empty()) { + glDeleteTextures((GLsizei)ids.size(), ids.data()); + } + } + + { + if (!externalTexturesTrash.empty()) { + std::vector<GLsync> fences; + fences.resize(externalTexturesTrash.size()); + for (size_t i = 0; i < externalTexturesTrash.size(); ++i) { + fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } + // External texture fences will be read in another thread/context, so we need a flush + glFlush(); + size_t index = 0; + for (auto pair : externalTexturesTrash) { + auto fence = fences[index++]; + pair.second(pair.first, fence); + } + } + } + + for (auto id : programsTrash) { + glDeleteProgram(id); + } + + for (auto id : shadersTrash) { + glDeleteShader(id); + } + + { + std::vector<GLuint> ids; + ids.reserve(queriesTrash.size()); + for (auto id : queriesTrash) { + ids.push_back(id); + } + if (!ids.empty()) { + glDeleteQueries((GLsizei)ids.size(), ids.data()); + } + } + +} + void GLBackend::recycle() const { PROFILE_RANGE(render_gpu_gl, __FUNCTION__) { @@ -759,112 +834,16 @@ void GLBackend::recycle() const { } } - { - std::vector<GLuint> ids; - std::list<std::pair<GLuint, Size>> buffersTrash; - { - Lock lock(_trashMutex); - std::swap(_buffersTrash, buffersTrash); - } - ids.reserve(buffersTrash.size()); - for (auto pair : buffersTrash) { - ids.push_back(pair.first); - } - if (!ids.empty()) { - glDeleteBuffers((GLsizei)ids.size(), ids.data()); - } + while (!_previousFrameTrashes.empty()) { + _previousFrameTrashes.front().cleanup(); + _previousFrameTrashes.pop_front(); } + _previousFrameTrashes.emplace_back(); { - std::vector<GLuint> ids; - std::list<GLuint> framebuffersTrash; - { - Lock lock(_trashMutex); - std::swap(_framebuffersTrash, framebuffersTrash); - } - ids.reserve(framebuffersTrash.size()); - for (auto id : framebuffersTrash) { - ids.push_back(id); - } - if (!ids.empty()) { - glDeleteFramebuffers((GLsizei)ids.size(), ids.data()); - } - } - - { - std::vector<GLuint> ids; - std::list<std::pair<GLuint, Size>> texturesTrash; - { - Lock lock(_trashMutex); - std::swap(_texturesTrash, texturesTrash); - } - ids.reserve(texturesTrash.size()); - for (auto pair : texturesTrash) { - ids.push_back(pair.first); - } - if (!ids.empty()) { - glDeleteTextures((GLsizei)ids.size(), ids.data()); - } - } - - { - std::list<std::pair<GLuint, Texture::ExternalRecycler>> externalTexturesTrash; - { - Lock lock(_trashMutex); - std::swap(_externalTexturesTrash, externalTexturesTrash); - } - if (!externalTexturesTrash.empty()) { - std::vector<GLsync> fences; - fences.resize(externalTexturesTrash.size()); - for (size_t i = 0; i < externalTexturesTrash.size(); ++i) { - fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - } - // External texture fences will be read in another thread/context, so we need a flush - glFlush(); - size_t index = 0; - for (auto pair : externalTexturesTrash) { - auto fence = fences[index++]; - pair.second(pair.first, fence); - } - } - } - - { - std::list<GLuint> programsTrash; - { - Lock lock(_trashMutex); - std::swap(_programsTrash, programsTrash); - } - for (auto id : programsTrash) { - glDeleteProgram(id); - } - } - - { - std::list<GLuint> shadersTrash; - { - Lock lock(_trashMutex); - std::swap(_shadersTrash, shadersTrash); - } - for (auto id : shadersTrash) { - glDeleteShader(id); - } - } - - { - std::vector<GLuint> ids; - std::list<GLuint> queriesTrash; - { - Lock lock(_trashMutex); - std::swap(_queriesTrash, queriesTrash); - } - ids.reserve(queriesTrash.size()); - for (auto id : queriesTrash) { - ids.push_back(id); - } - if (!ids.empty()) { - glDeleteQueries((GLsizei)ids.size(), ids.data()); - } + Lock lock(_trashMutex); + _previousFrameTrashes.back().swap(_currentFrameTrash); + _previousFrameTrashes.back().fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); } _textureManagement._transferEngine->manageMemory(); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index c309bcb864..37dde5b08e 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -419,16 +419,34 @@ protected: static const size_t INVALID_OFFSET = (size_t)-1; bool _inRenderTransferPass{ false }; int _currentDraw{ -1 }; - - std::list<std::string> profileRanges; + + struct FrameTrash { + GLsync fence = nullptr; + std::list<std::pair<GLuint, Size>> buffersTrash; + std::list<std::pair<GLuint, Size>> texturesTrash; + std::list<std::pair<GLuint, Texture::ExternalRecycler>> externalTexturesTrash; + std::list<GLuint> framebuffersTrash; + std::list<GLuint> shadersTrash; + std::list<GLuint> programsTrash; + std::list<GLuint> queriesTrash; + + void swap(FrameTrash& other) { + buffersTrash.swap(other.buffersTrash); + texturesTrash.swap(other.texturesTrash); + externalTexturesTrash.swap(other.externalTexturesTrash); + framebuffersTrash.swap(other.framebuffersTrash); + shadersTrash.swap(other.shadersTrash); + programsTrash.swap(other.programsTrash); + queriesTrash.swap(other.queriesTrash); + } + + void cleanup(); + }; + mutable Mutex _trashMutex; - mutable std::list<std::pair<GLuint, Size>> _buffersTrash; - mutable std::list<std::pair<GLuint, Size>> _texturesTrash; - mutable std::list<std::pair<GLuint, Texture::ExternalRecycler>> _externalTexturesTrash; - mutable std::list<GLuint> _framebuffersTrash; - mutable std::list<GLuint> _shadersTrash; - mutable std::list<GLuint> _programsTrash; - mutable std::list<GLuint> _queriesTrash; + mutable FrameTrash _currentFrameTrash; + mutable std::list<FrameTrash> _previousFrameTrashes; + std::list<std::string> profileRanges; mutable std::list<std::function<void()>> _lambdaQueue; void renderPassTransfer(const Batch& batch); diff --git a/libraries/gpu/src/gpu/MipGeneration.slh b/libraries/gpu/src/gpu/MipGeneration.slh index bc8dd39042..b5d4ab3303 100644 --- a/libraries/gpu/src/gpu/MipGeneration.slh +++ b/libraries/gpu/src/gpu/MipGeneration.slh @@ -13,7 +13,7 @@ <@include gpu/ShaderConstants.h@> -layout(binding=GPU_TEXTURE_MIP_CREATION_INPUT) uniform sampler2D texMap; +LAYOUT(binding=GPU_TEXTURE_MIP_CREATION_INPUT) uniform sampler2D texMap; in vec2 varTexCoord0; diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index e96815d391..dd58b0e75e 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -128,7 +128,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { void GeometryMappingResource::onGeometryMappingLoaded(bool success) { if (success && _geometryResource) { - _fbxGeometry = _geometryResource->_fbxGeometry; + _hfmModel = _geometryResource->_hfmModel; _meshParts = _geometryResource->_meshParts; _meshes = _geometryResource->_meshes; _materials = _geometryResource->_materials; @@ -193,38 +193,38 @@ void GeometryReader::run() { _url.path().toLower().endsWith(".obj.gz") || _url.path().toLower().endsWith(".gltf"))) { - FBXGeometry::Pointer fbxGeometry; + HFMModel::Pointer hfmModel; if (_url.path().toLower().endsWith(".fbx")) { - fbxGeometry.reset(readFBX(_data, _mapping, _url.path())); - if (fbxGeometry->meshes.size() == 0 && fbxGeometry->joints.size() == 0) { + hfmModel.reset(readFBX(_data, _mapping, _url.path())); + if (hfmModel->meshes.size() == 0 && hfmModel->joints.size() == 0) { throw QString("empty geometry, possibly due to an unsupported FBX version"); } } else if (_url.path().toLower().endsWith(".obj")) { - fbxGeometry = OBJReader().readOBJ(_data, _mapping, _combineParts, _url); + hfmModel = OBJReader().readOBJ(_data, _mapping, _combineParts, _url); } else if (_url.path().toLower().endsWith(".obj.gz")) { QByteArray uncompressedData; if (gunzip(_data, uncompressedData)){ - fbxGeometry = OBJReader().readOBJ(uncompressedData, _mapping, _combineParts, _url); + hfmModel = OBJReader().readOBJ(uncompressedData, _mapping, _combineParts, _url); } else { throw QString("failed to decompress .obj.gz"); } } else if (_url.path().toLower().endsWith(".gltf")) { std::shared_ptr<GLTFReader> glreader = std::make_shared<GLTFReader>(); - fbxGeometry.reset(glreader->readGLTF(_data, _mapping, _url)); - if (fbxGeometry->meshes.size() == 0 && fbxGeometry->joints.size() == 0) { + hfmModel.reset(glreader->readGLTF(_data, _mapping, _url)); + if (hfmModel->meshes.size() == 0 && hfmModel->joints.size() == 0) { throw QString("empty geometry, possibly due to an unsupported GLTF version"); } } else { throw QString("unsupported format"); } - // Add scripts to fbxgeometry + // Add scripts to hfmModel if (!_mapping.value(SCRIPT_FIELD).isNull()) { QVariantList scripts = _mapping.values(SCRIPT_FIELD); for (auto &script : scripts) { - fbxGeometry->scripts.push_back(script.toString()); + hfmModel->scripts.push_back(script.toString()); } } @@ -234,7 +234,7 @@ void GeometryReader::run() { qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref"; } else { QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition", - Q_ARG(FBXGeometry::Pointer, fbxGeometry)); + Q_ARG(HFMModel::Pointer, hfmModel)); } } else { throw QString("url is invalid"); @@ -262,7 +262,7 @@ public: virtual void downloadFinished(const QByteArray& data) override; protected: - Q_INVOKABLE void setGeometryDefinition(FBXGeometry::Pointer fbxGeometry); + Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel); private: QVariantHash _mapping; @@ -277,13 +277,13 @@ void GeometryDefinitionResource::downloadFinished(const QByteArray& data) { QThreadPool::globalInstance()->start(new GeometryReader(_self, _effectiveBaseURL, _mapping, data, _combineParts)); } -void GeometryDefinitionResource::setGeometryDefinition(FBXGeometry::Pointer fbxGeometry) { - // Assume ownership of the geometry pointer - _fbxGeometry = fbxGeometry; +void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmModel) { + // Assume ownership of the HFMModel pointer + _hfmModel = hfmModel; // Copy materials QHash<QString, size_t> materialIDAtlas; - for (const FBXMaterial& material : _fbxGeometry->materials) { + for (const HFMMaterial& material : _hfmModel->materials) { materialIDAtlas[material.materialID] = _materials.size(); _materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseUrl)); } @@ -291,11 +291,11 @@ void GeometryDefinitionResource::setGeometryDefinition(FBXGeometry::Pointer fbxG std::shared_ptr<GeometryMeshes> meshes = std::make_shared<GeometryMeshes>(); std::shared_ptr<GeometryMeshParts> parts = std::make_shared<GeometryMeshParts>(); int meshID = 0; - for (const FBXMesh& mesh : _fbxGeometry->meshes) { + for (const HFMMesh& mesh : _hfmModel->meshes) { // Copy mesh pointers meshes->emplace_back(mesh._mesh); int partID = 0; - for (const FBXMeshPart& part : mesh.parts) { + for (const HFMMeshPart& part : mesh.parts) { // Construct local parts parts->push_back(std::make_shared<MeshPart>(meshID, partID, (int)materialIDAtlas[part.materialID])); partID++; @@ -371,7 +371,7 @@ const QVariantMap Geometry::getTextures() const { // FIXME: The materials should only be copied when modified, but the Model currently caches the original Geometry::Geometry(const Geometry& geometry) { - _fbxGeometry = geometry._fbxGeometry; + _hfmModel = geometry._hfmModel; _meshes = geometry._meshes; _meshParts = geometry._meshParts; @@ -444,8 +444,8 @@ void GeometryResource::deleter() { } void GeometryResource::setTextures() { - if (_fbxGeometry) { - for (const FBXMaterial& material : _fbxGeometry->materials) { + if (_hfmModel) { + for (const HFMMaterial& material : _hfmModel->materials) { _materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseUrl)); } } @@ -512,7 +512,7 @@ const QString& NetworkMaterial::getTextureName(MapChannel channel) { return NO_TEXTURE; } -QUrl NetworkMaterial::getTextureUrl(const QUrl& baseUrl, const FBXTexture& texture) { +QUrl NetworkMaterial::getTextureUrl(const QUrl& baseUrl, const HFMTexture& texture) { if (texture.content.isEmpty()) { // External file: search relative to the baseUrl, in case filename is relative return baseUrl.resolved(QUrl(texture.filename)); @@ -529,29 +529,29 @@ QUrl NetworkMaterial::getTextureUrl(const QUrl& baseUrl, const FBXTexture& textu } } -graphics::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl, const FBXTexture& fbxTexture, +graphics::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl, const HFMTexture& hfmTexture, image::TextureUsage::Type type, MapChannel channel) { if (baseUrl.isEmpty()) { return nullptr; } - const auto url = getTextureUrl(baseUrl, fbxTexture); - const auto texture = DependencyManager::get<TextureCache>()->getTexture(url, type, fbxTexture.content, fbxTexture.maxNumPixels); - _textures[channel] = Texture { fbxTexture.name, texture }; + const auto url = getTextureUrl(baseUrl, hfmTexture); + const auto texture = DependencyManager::get<TextureCache>()->getTexture(url, type, hfmTexture.content, hfmTexture.maxNumPixels); + _textures[channel] = Texture { hfmTexture.name, texture }; auto map = std::make_shared<graphics::TextureMap>(); if (texture) { map->setTextureSource(texture->_textureSource); } - map->setTextureTransform(fbxTexture.transform); + map->setTextureTransform(hfmTexture.transform); return map; } graphics::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& url, image::TextureUsage::Type type, MapChannel channel) { auto textureCache = DependencyManager::get<TextureCache>(); - if (textureCache) { + if (textureCache && !url.isEmpty()) { auto texture = textureCache->getTexture(url, type); _textures[channel].texture = texture; @@ -624,21 +624,23 @@ void NetworkMaterial::setLightmapMap(const QUrl& url) { } } -NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl) : +NetworkMaterial::NetworkMaterial(const HFMMaterial& material, const QUrl& textureBaseUrl) : graphics::Material(*material._material), _textures(MapChannel::NUM_MAP_CHANNELS) { _name = material.name.toStdString(); if (!material.albedoTexture.filename.isEmpty()) { auto map = fetchTextureMap(textureBaseUrl, material.albedoTexture, image::TextureUsage::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP); - _albedoTransform = material.albedoTexture.transform; - map->setTextureTransform(_albedoTransform); + if (map) { + _albedoTransform = material.albedoTexture.transform; + map->setTextureTransform(_albedoTransform); - if (!material.opacityTexture.filename.isEmpty()) { - if (material.albedoTexture.filename == material.opacityTexture.filename) { - // Best case scenario, just indicating that the albedo map contains transparency - // TODO: Different albedo/opacity maps are not currently supported - map->setUseAlphaChannel(true); + if (!material.opacityTexture.filename.isEmpty()) { + if (material.albedoTexture.filename == material.opacityTexture.filename) { + // Best case scenario, just indicating that the albedo map contains transparency + // TODO: Different albedo/opacity maps are not currently supported + map->setUseAlphaChannel(true); + } } } @@ -670,7 +672,9 @@ NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textur if (!material.occlusionTexture.filename.isEmpty()) { auto map = fetchTextureMap(textureBaseUrl, material.occlusionTexture, image::TextureUsage::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP); - map->setTextureTransform(material.occlusionTexture.transform); + if (map) { + map->setTextureTransform(material.occlusionTexture.transform); + } setTextureMap(MapChannel::OCCLUSION_MAP, map); } @@ -686,10 +690,12 @@ NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textur if (!material.lightmapTexture.filename.isEmpty()) { auto map = fetchTextureMap(textureBaseUrl, material.lightmapTexture, image::TextureUsage::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP); - _lightmapTransform = material.lightmapTexture.transform; - _lightmapParams = material.lightmapParams; - map->setTextureTransform(_lightmapTransform); - map->setLightmapOffsetScale(_lightmapParams.x, _lightmapParams.y); + if (map) { + _lightmapTransform = material.lightmapTexture.transform; + _lightmapParams = material.lightmapParams; + map->setTextureTransform(_lightmapTransform); + map->setLightmapOffsetScale(_lightmapParams.x, _lightmapParams.y); + } setTextureMap(MapChannel::LIGHTMAP_MAP, map); } } @@ -709,9 +715,11 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) { if (!albedoName.isEmpty()) { auto url = textureMap.contains(albedoName) ? textureMap[albedoName].toUrl() : QUrl(); auto map = fetchTextureMap(url, image::TextureUsage::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP); - map->setTextureTransform(_albedoTransform); - // when reassigning the albedo texture we also check for the alpha channel used as opacity - map->setUseAlphaChannel(true); + if (map) { + map->setTextureTransform(_albedoTransform); + // when reassigning the albedo texture we also check for the alpha channel used as opacity + map->setUseAlphaChannel(true); + } setTextureMap(MapChannel::ALBEDO_MAP, map); } @@ -757,8 +765,10 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) { if (!lightmapName.isEmpty()) { auto url = textureMap.contains(lightmapName) ? textureMap[lightmapName].toUrl() : QUrl(); auto map = fetchTextureMap(url, image::TextureUsage::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP); - map->setTextureTransform(_lightmapTransform); - map->setLightmapOffsetScale(_lightmapParams.x, _lightmapParams.y); + if (map) { + map->setTextureTransform(_lightmapTransform); + map->setLightmapOffsetScale(_lightmapParams.x, _lightmapParams.y); + } setTextureMap(MapChannel::LIGHTMAP_MAP, map); } } diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 5cbe96ea03..1bb340b83c 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -45,9 +45,9 @@ public: // Mutable, but must retain structure of vector using NetworkMaterials = std::vector<std::shared_ptr<NetworkMaterial>>; - bool isGeometryLoaded() const { return (bool)_fbxGeometry; } + bool isHFMModelLoaded() const { return (bool)_hfmModel; } - const FBXGeometry& getFBXGeometry() const { return *_fbxGeometry; } + const HFMModel& getHFMModel() const { return *_hfmModel; } const GeometryMeshes& getMeshes() const { return *_meshes; } const std::shared_ptr<NetworkMaterial> getShapeMaterial(int shapeID) const; @@ -62,7 +62,7 @@ protected: friend class GeometryMappingResource; // Shared across all geometries, constant throughout lifetime - std::shared_ptr<const FBXGeometry> _fbxGeometry; + std::shared_ptr<const HFMModel> _hfmModel; std::shared_ptr<const GeometryMeshes> _meshes; std::shared_ptr<const GeometryMeshParts> _meshParts; @@ -94,7 +94,7 @@ protected: // Geometries may not hold onto textures while cached - that is for the texture cache // Instead, these methods clear and reset textures from the geometry when caching/loading - bool shouldSetTextures() const { return _fbxGeometry && _materials.empty(); } + bool shouldSetTextures() const { return _hfmModel && _materials.empty(); } void setTextures(); void resetTextures(); @@ -165,7 +165,7 @@ public: using MapChannel = graphics::Material::MapChannel; NetworkMaterial() : _textures(MapChannel::NUM_MAP_CHANNELS) {} - NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl); + NetworkMaterial(const HFMMaterial& material, const QUrl& textureBaseUrl); NetworkMaterial(const NetworkMaterial& material); void setAlbedoMap(const QUrl& url, bool useAlphaChannel); @@ -201,8 +201,8 @@ protected: private: // Helpers for the ctors - QUrl getTextureUrl(const QUrl& baseUrl, const FBXTexture& fbxTexture); - graphics::TextureMapPointer fetchTextureMap(const QUrl& baseUrl, const FBXTexture& fbxTexture, + QUrl getTextureUrl(const QUrl& baseUrl, const HFMTexture& hfmTexture); + graphics::TextureMapPointer fetchTextureMap(const QUrl& baseUrl, const HFMTexture& hfmTexture, image::TextureUsage::Type type, MapChannel channel); graphics::TextureMapPointer fetchTextureMap(const QUrl& url, image::TextureUsage::Type type, MapChannel channel); diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index e8aec5e60e..337ad68a99 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -329,7 +329,7 @@ _maxNumPixels(100) static bool isLocalUrl(const QUrl& url) { auto scheme = url.scheme(); - return (scheme == URL_SCHEME_FILE || scheme == URL_SCHEME_QRC || scheme == RESOURCE_SCHEME); + return (scheme == HIFI_URL_SCHEME_FILE || scheme == URL_SCHEME_QRC || scheme == RESOURCE_SCHEME); } NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) : @@ -456,7 +456,8 @@ void NetworkTexture::makeRequest() { // Add a fragment to the base url so we can identify the section of the ktx being requested when debugging // The actual requested url is _activeUrl and will not contain the fragment _url.setFragment("head"); - _ktxHeaderRequest = DependencyManager::get<ResourceManager>()->createResourceRequest(this, _activeUrl); + _ktxHeaderRequest = DependencyManager::get<ResourceManager>()->createResourceRequest( + this, _activeUrl, true, -1, "NetworkTexture::makeRequest"); if (!_ktxHeaderRequest) { qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString(); @@ -502,7 +503,7 @@ void NetworkTexture::handleLocalRequestCompleted() { void NetworkTexture::makeLocalRequest() { const QString scheme = _activeUrl.scheme(); QString path; - if (scheme == URL_SCHEME_FILE) { + if (scheme == HIFI_URL_SCHEME_FILE) { path = PathUtils::expandToLocalDataAbsolutePath(_activeUrl).toLocalFile(); } else { path = ":" + _activeUrl.path(); @@ -617,7 +618,8 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) { bool isHighMipRequest = low == NULL_MIP_LEVEL && high == NULL_MIP_LEVEL; - _ktxMipRequest = DependencyManager::get<ResourceManager>()->createResourceRequest(this, _activeUrl); + _ktxMipRequest = DependencyManager::get<ResourceManager>()->createResourceRequest( + this, _activeUrl, true, -1, "NetworkTexture::startMipRangeRequest"); if (!_ktxMipRequest) { qCWarning(networking).noquote() << "Failed to get request for" << _url.toDisplayString(); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index d99c0020da..2d1a0c0afb 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -837,3 +837,7 @@ void AccountManager::handleKeypairGenerationError() { // reset our waiting state for keypair response _isWaitingForKeypairResponse = false; } + +void AccountManager::setLimitedCommerce(bool isLimited) { + _limitedCommerce = isLimited; +} diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index f3b81cf1c9..093ac9a27c 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -98,6 +98,9 @@ public: void removeAccountFromFile(); + bool getLimitedCommerce() { return _limitedCommerce; } + void setLimitedCommerce(bool isLimited); + public slots: void requestAccessToken(const QString& login, const QString& password); void requestAccessTokenWithSteam(QByteArray authSessionTicket); @@ -121,6 +124,7 @@ signals: void loginFailed(); void logoutComplete(); void newKeypair(); + void limitedCommerceChanged(); private slots: void handleKeypairGenerationError(); @@ -150,6 +154,8 @@ private: QByteArray _pendingPrivateKey; QUuid _sessionID { QUuid::createUuid() }; + + bool _limitedCommerce { false }; }; #endif // hifi_AccountManager_h diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index f8ab8ceaec..e6957728e8 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -155,12 +155,12 @@ void AddressManager::goForward() { void AddressManager::storeCurrentAddress() { auto url = currentAddress(); - if (url.scheme() == URL_SCHEME_FILE || + if (url.scheme() == HIFI_URL_SCHEME_FILE || (url.scheme() == URL_SCHEME_HIFI && !url.host().isEmpty())) { // TODO -- once Octree::readFromURL no-longer takes over the main event-loop, serverless-domain urls can // be loaded over http(s) - // url.scheme() == URL_SCHEME_HTTP || - // url.scheme() == URL_SCHEME_HTTPS || + // url.scheme() == HIFI_URL_SCHEME_HTTP || + // url.scheme() == HIFI_URL_SCHEME_HTTPS || bool isInErrorState = DependencyManager::get<NodeList>()->getDomainHandler().isInErrorState(); if (isConnected()) { if (isInErrorState) { @@ -331,11 +331,11 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { emit lookupResultsFinished(); return true; - } else if (lookupUrl.scheme() == URL_SCHEME_FILE) { + } else if (lookupUrl.scheme() == HIFI_URL_SCHEME_FILE) { // TODO -- once Octree::readFromURL no-longer takes over the main event-loop, serverless-domain urls can // be loaded over http(s) // lookupUrl.scheme() == URL_SCHEME_HTTP || - // lookupUrl.scheme() == URL_SCHEME_HTTPS || + // lookupUrl.scheme() == HIFI_URL_SCHEME_HTTPS || // TODO once a file can return a connection refusal if there were to be some kind of load error, we'd // need to store the previous domain tried in _lastVisitedURL. For now , do not store it. diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp index 6d5bbb3ac5..6f5b13f98d 100644 --- a/libraries/networking/src/AssetResourceRequest.cpp +++ b/libraries/networking/src/AssetResourceRequest.cpp @@ -24,8 +24,12 @@ static const int DOWNLOAD_PROGRESS_LOG_INTERVAL_SECONDS = 5; -AssetResourceRequest::AssetResourceRequest(const QUrl& url) : - ResourceRequest(url) +AssetResourceRequest::AssetResourceRequest( + const QUrl& url, + const bool isObservable, + const qint64 callerId, + const QString& extra) : + ResourceRequest(url, isObservable, callerId, extra) { _lastProgressDebug = p_high_resolution_clock::now() - std::chrono::seconds(DOWNLOAD_PROGRESS_LOG_INTERVAL_SECONDS); } diff --git a/libraries/networking/src/AssetResourceRequest.h b/libraries/networking/src/AssetResourceRequest.h index 2fe79040ca..07baca5416 100644 --- a/libraries/networking/src/AssetResourceRequest.h +++ b/libraries/networking/src/AssetResourceRequest.h @@ -22,7 +22,11 @@ class AssetResourceRequest : public ResourceRequest { Q_OBJECT public: - AssetResourceRequest(const QUrl& url); + AssetResourceRequest( + const QUrl& url, + const bool isObservable = true, + const qint64 callerId = -1, + const QString& extra = ""); virtual ~AssetResourceRequest() override; protected: diff --git a/libraries/networking/src/AtpReply.cpp b/libraries/networking/src/AtpReply.cpp index b2b7e8bee7..3ec9b23f5f 100644 --- a/libraries/networking/src/AtpReply.cpp +++ b/libraries/networking/src/AtpReply.cpp @@ -14,7 +14,8 @@ #include "ResourceManager.h" AtpReply::AtpReply(const QUrl& url, QObject* parent) : - _resourceRequest(DependencyManager::get<ResourceManager>()->createResourceRequest(parent, url)) { + _resourceRequest(DependencyManager::get<ResourceManager>()->createResourceRequest( + parent, url, true, -1, "AtpReply::AtpReply")) { setOperation(QNetworkAccessManager::GetOperation); connect(_resourceRequest, &AssetResourceRequest::progress, this, &AtpReply::downloadProgress); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 182a79ec4b..2513510b05 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -196,7 +196,7 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { _sockAddr.clear(); // if this is a file URL we need to see if it has a ~ for us to expand - if (domainURL.scheme() == URL_SCHEME_FILE) { + if (domainURL.scheme() == HIFI_URL_SCHEME_FILE) { domainURL = PathUtils::expandToLocalDataAbsolutePath(domainURL); } } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index ddd23339df..c0c5a4d059 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -231,7 +231,11 @@ private: QString _pendingPath; QTimer _settingsTimer; mutable ReadWriteLockable _interstitialModeSettingLock; - Setting::Handle<bool> _enableInterstitialMode{ "enableInterstitialMode", true }; +#ifdef Q_OS_ANDROID + Setting::Handle<bool> _enableInterstitialMode{ "enableInterstitialMode", false }; +#else + Setting::Handle<bool> _enableInterstitialMode { "enableInterstitialMode", true }; +#endif QSet<QString> _domainConnectionRefusals; bool _hasCheckedForAccessToken { false }; diff --git a/libraries/networking/src/FileResourceRequest.h b/libraries/networking/src/FileResourceRequest.h index 547b754cb5..fa9a1617a9 100644 --- a/libraries/networking/src/FileResourceRequest.h +++ b/libraries/networking/src/FileResourceRequest.h @@ -19,7 +19,12 @@ class FileResourceRequest : public ResourceRequest { Q_OBJECT public: - FileResourceRequest(const QUrl& url) : ResourceRequest(url) { } + FileResourceRequest( + const QUrl& url, + const bool isObservable = true, + const qint64 callerId = -1, + const QString& extra = "" + ) : ResourceRequest(url, isObservable, callerId, extra) { } protected: virtual void doSend() override; diff --git a/libraries/networking/src/HTTPResourceRequest.h b/libraries/networking/src/HTTPResourceRequest.h index cc628d8855..c725934f2f 100644 --- a/libraries/networking/src/HTTPResourceRequest.h +++ b/libraries/networking/src/HTTPResourceRequest.h @@ -21,7 +21,12 @@ class HTTPResourceRequest : public ResourceRequest { Q_OBJECT public: - HTTPResourceRequest(const QUrl& url) : ResourceRequest(url) { } + HTTPResourceRequest( + const QUrl& url, + const bool isObservable = true, + const qint64 callerId = -1, + const QString& = "" + ) : ResourceRequest(url, isObservable, callerId) { } ~HTTPResourceRequest(); protected: diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index db6ed15792..37b914143e 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -1187,12 +1187,24 @@ void LimitedNodeList::sendPeerQueryToIceServer(const HifiSockAddr& iceServerSock SharedNodePointer LimitedNodeList::findNodeWithAddr(const HifiSockAddr& addr) { QReadLocker locker(&_nodeMutex); - auto it = std::find_if(std::begin(_nodeHash), std::end(_nodeHash), [&](const UUIDNodePair& pair) { - return pair.second->getActiveSocket() ? (*pair.second->getActiveSocket() == addr) : false; + auto it = std::find_if(std::begin(_nodeHash), std::end(_nodeHash), [&addr](const UUIDNodePair& pair) { + return pair.second->getPublicSocket() == addr + || pair.second->getLocalSocket() == addr + || pair.second->getSymmetricSocket() == addr; }); return (it != std::end(_nodeHash)) ? it->second : SharedNodePointer(); } +bool LimitedNodeList::sockAddrBelongsToNode(const HifiSockAddr& sockAddr) { + QReadLocker locker(&_nodeMutex); + auto it = std::find_if(std::begin(_nodeHash), std::end(_nodeHash), [&sockAddr](const UUIDNodePair& pair) { + return pair.second->getPublicSocket() == sockAddr + || pair.second->getLocalSocket() == sockAddr + || pair.second->getSymmetricSocket() == sockAddr; + }); + return it != std::end(_nodeHash); +} + void LimitedNodeList::sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID) { auto icePacket = NLPacket::create(packetType); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index cffc49521a..dacefa8e40 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -386,7 +386,7 @@ protected: void sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerRequestID = QUuid()); - bool sockAddrBelongsToNode(const HifiSockAddr& sockAddr) { return findNodeWithAddr(sockAddr) != SharedNodePointer(); } + bool sockAddrBelongsToNode(const HifiSockAddr& sockAddr); NodeHash _nodeHash; mutable QReadWriteLock _nodeMutex { QReadWriteLock::Recursive }; diff --git a/libraries/networking/src/NetworkingConstants.h b/libraries/networking/src/NetworkingConstants.h index 839e269fd4..302e0efa02 100644 --- a/libraries/networking/src/NetworkingConstants.h +++ b/libraries/networking/src/NetworkingConstants.h @@ -30,14 +30,14 @@ namespace NetworkingConstants { QUrl METAVERSE_SERVER_URL(); } -const QString URL_SCHEME_ABOUT = "about"; +const QString HIFI_URL_SCHEME_ABOUT = "about"; const QString URL_SCHEME_HIFI = "hifi"; const QString URL_SCHEME_HIFIAPP = "hifiapp"; const QString URL_SCHEME_QRC = "qrc"; -const QString URL_SCHEME_FILE = "file"; -const QString URL_SCHEME_HTTP = "http"; -const QString URL_SCHEME_HTTPS = "https"; -const QString URL_SCHEME_FTP = "ftp"; +const QString HIFI_URL_SCHEME_FILE = "file"; +const QString HIFI_URL_SCHEME_HTTP = "http"; +const QString HIFI_URL_SCHEME_HTTPS = "https"; +const QString HIFI_URL_SCHEME_FTP = "ftp"; const QString URL_SCHEME_ATP = "atp"; #endif // hifi_NetworkingConstants_h diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 076db44ea6..8e67d7e633 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -10,6 +10,7 @@ // #include "ResourceCache.h" +#include "ResourceRequestObserver.h" #include <cfloat> #include <cmath> @@ -117,7 +118,7 @@ QSharedPointer<Resource> ResourceCacheSharedItems::getHighestPendingRequest() { // Check load priority float priority = resource->getLoadPriority(); - bool isFile = resource->getURL().scheme() == URL_SCHEME_FILE; + bool isFile = resource->getURL().scheme() == HIFI_URL_SCHEME_FILE; if (priority >= highestPriority && (isFile || !currentHighestIsFile)) { highestPriority = priority; highestIndex = i; @@ -252,7 +253,9 @@ ResourceCache::ResourceCache(QObject* parent) : QObject(parent) { } } -ResourceCache::~ResourceCache() {} +ResourceCache::~ResourceCache() { + clearUnusedResources(); +} void ResourceCache::clearATPAssets() { { @@ -338,28 +341,31 @@ QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl& } if (resource) { removeUnusedResource(resource); - return resource; } - if (!url.isValid() && !url.isEmpty() && fallback.isValid()) { - return getResource(fallback, QUrl()); + if (!resource && !url.isValid() && !url.isEmpty() && fallback.isValid()) { + resource = getResource(fallback, QUrl()); } - resource = createResource( - url, - fallback.isValid() ? getResource(fallback, QUrl()) : QSharedPointer<Resource>(), - extra); - resource->setSelf(resource); - resource->setCache(this); - resource->moveToThread(qApp->thread()); - connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); - { - QWriteLocker locker(&_resourcesLock); - _resources.insert(url, resource); + if (!resource) { + resource = createResource( + url, + fallback.isValid() ? getResource(fallback, QUrl()) : QSharedPointer<Resource>(), + extra); + resource->setSelf(resource); + resource->setCache(this); + resource->moveToThread(qApp->thread()); + connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); + { + QWriteLocker locker(&_resourcesLock); + _resources.insert(url, resource); + } + removeUnusedResource(resource); + resource->ensureLoading(); } - removeUnusedResource(resource); - resource->ensureLoading(); + DependencyManager::get<ResourceRequestObserver>()->update( + resource->getURL(), -1, "ResourceCache::getResource"); return resource; } @@ -682,7 +688,8 @@ void Resource::makeRequest() { PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } }); - _request = DependencyManager::get<ResourceManager>()->createResourceRequest(this, _activeUrl); + _request = DependencyManager::get<ResourceManager>()->createResourceRequest( + this, _activeUrl, true, -1, "Resource::makeRequest"); if (!_request) { qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString(); @@ -733,6 +740,8 @@ void Resource::handleReplyFinished() { setSize(_bytesTotal); + // Make sure we keep the Resource alive here + auto self = _self.lock(); ResourceCache::requestCompleted(_self); auto result = _request->getResult(); diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 33490301d7..c632399ad4 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -62,7 +62,7 @@ static const qint64 MAX_UNUSED_MAX_SIZE = MAXIMUM_CACHE_SIZE; class ResourceCacheSharedItems : public Dependency { SINGLETON_DEPENDENCY - using Mutex = std::mutex; + using Mutex = std::recursive_mutex; using Lock = std::unique_lock<Mutex>; public: diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index 553f0d0a61..f4f5525ddc 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -82,10 +82,10 @@ const QSet<QString>& getKnownUrls() { static std::once_flag once; std::call_once(once, [] { knownUrls.insert(URL_SCHEME_QRC); - knownUrls.insert(URL_SCHEME_FILE); - knownUrls.insert(URL_SCHEME_HTTP); - knownUrls.insert(URL_SCHEME_HTTPS); - knownUrls.insert(URL_SCHEME_FTP); + knownUrls.insert(HIFI_URL_SCHEME_FILE); + knownUrls.insert(HIFI_URL_SCHEME_HTTP); + knownUrls.insert(HIFI_URL_SCHEME_HTTPS); + knownUrls.insert(HIFI_URL_SCHEME_FTP); knownUrls.insert(URL_SCHEME_ATP); }); return knownUrls; @@ -97,7 +97,7 @@ QUrl ResourceManager::normalizeURL(const QUrl& originalUrl) { if (!getKnownUrls().contains(scheme)) { // check the degenerative file case: on windows we can often have urls of the form c:/filename // this checks for and works around that case. - QUrl urlWithFileScheme{ URL_SCHEME_FILE + ":///" + url.toString() }; + QUrl urlWithFileScheme{ HIFI_URL_SCHEME_FILE + ":///" + url.toString() }; if (!urlWithFileScheme.toLocalFile().isEmpty()) { return urlWithFileScheme; } @@ -112,22 +112,28 @@ void ResourceManager::cleanup() { _thread.wait(); } -ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) { +ResourceRequest* ResourceManager::createResourceRequest( + QObject* parent, + const QUrl& url, + const bool isObservable, + const qint64 callerId, + const QString& extra +) { auto normalizedURL = normalizeURL(url); auto scheme = normalizedURL.scheme(); ResourceRequest* request = nullptr; - if (scheme == URL_SCHEME_FILE || scheme == URL_SCHEME_QRC) { - request = new FileResourceRequest(normalizedURL); - } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) { - request = new HTTPResourceRequest(normalizedURL); + if (scheme == HIFI_URL_SCHEME_FILE || scheme == URL_SCHEME_QRC) { + request = new FileResourceRequest(normalizedURL, isObservable, callerId, extra); + } else if (scheme == HIFI_URL_SCHEME_HTTP || scheme == HIFI_URL_SCHEME_HTTPS || scheme == HIFI_URL_SCHEME_FTP) { + request = new HTTPResourceRequest(normalizedURL, isObservable, callerId, extra); } else if (scheme == URL_SCHEME_ATP) { if (!_atpSupportEnabled) { qCDebug(networking) << "ATP support not enabled, unable to create request for URL: " << url.url(); return nullptr; } - request = new AssetResourceRequest(normalizedURL); + request = new AssetResourceRequest(normalizedURL, isObservable, callerId, extra); } else { qCDebug(networking) << "Unknown scheme (" << scheme << ") for URL: " << url.url(); return nullptr; @@ -143,10 +149,10 @@ ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const Q bool ResourceManager::resourceExists(const QUrl& url) { auto scheme = url.scheme(); - if (scheme == URL_SCHEME_FILE) { + if (scheme == HIFI_URL_SCHEME_FILE) { QFileInfo file{ url.toString() }; return file.exists(); - } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) { + } else if (scheme == HIFI_URL_SCHEME_HTTP || scheme == HIFI_URL_SCHEME_HTTPS || scheme == HIFI_URL_SCHEME_FTP) { auto& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request{ url }; @@ -163,7 +169,7 @@ bool ResourceManager::resourceExists(const QUrl& url) { return reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200; } else if (scheme == URL_SCHEME_ATP && _atpSupportEnabled) { - auto request = new AssetResourceRequest(url); + auto request = new AssetResourceRequest(url, ResourceRequest::IS_NOT_OBSERVABLE); ByteRange range; range.fromInclusive = 1; range.toExclusive = 1; diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h index a79222d2d8..adaa1cf886 100644 --- a/libraries/networking/src/ResourceManager.h +++ b/libraries/networking/src/ResourceManager.h @@ -34,7 +34,12 @@ public: QString normalizeURL(const QString& urlString); QUrl normalizeURL(const QUrl& url); - ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url); + ResourceRequest* createResourceRequest( + QObject* parent, + const QUrl& url, + const bool isObservable = true, + const qint64 callerId = -1, + const QString& extra = ""); void init(); void cleanup(); diff --git a/libraries/networking/src/ResourceRequest.cpp b/libraries/networking/src/ResourceRequest.cpp index 115e665b77..c63bd4c563 100644 --- a/libraries/networking/src/ResourceRequest.cpp +++ b/libraries/networking/src/ResourceRequest.cpp @@ -10,13 +10,13 @@ // #include "ResourceRequest.h" +#include "ResourceRequestObserver.h" #include <DependencyManager.h> #include <StatTracker.h> #include <QtCore/QThread> -ResourceRequest::ResourceRequest(const QUrl& url) : _url(url) { } void ResourceRequest::send() { if (QThread::currentThread() != thread()) { @@ -24,6 +24,10 @@ void ResourceRequest::send() { return; } + if (_isObservable) { + DependencyManager::get<ResourceRequestObserver>()->update(_url, _callerId, _extra + " => ResourceRequest::send"); + } + Q_ASSERT(_state == NotStarted); _state = InProgress; diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h index 8dd09ccc49..eb306ca5be 100644 --- a/libraries/networking/src/ResourceRequest.h +++ b/libraries/networking/src/ResourceRequest.h @@ -40,7 +40,20 @@ const QString STAT_FILE_RESOURCE_TOTAL_BYTES = "FILEBytesDownloaded"; class ResourceRequest : public QObject { Q_OBJECT public: - ResourceRequest(const QUrl& url); + static const bool IS_OBSERVABLE = true; + static const bool IS_NOT_OBSERVABLE = false; + + ResourceRequest( + const QUrl& url, + const bool isObservable = IS_OBSERVABLE, + const qint64 callerId = -1, + const QString& extra = "" + ) : _url(url), + _isObservable(isObservable), + _callerId(callerId), + _extra(extra) + { } + virtual ~ResourceRequest() = default; enum State { @@ -99,6 +112,9 @@ protected: bool _rangeRequestSuccessful { false }; uint64_t _totalSizeOfResource { 0 }; int64_t _lastRecordedBytesDownloaded { 0 }; + bool _isObservable; + qint64 _callerId; + QString _extra; }; #endif diff --git a/libraries/networking/src/udt/CongestionControl.h b/libraries/networking/src/udt/CongestionControl.h index 7093e8bd96..bfe7f552d1 100644 --- a/libraries/networking/src/udt/CongestionControl.h +++ b/libraries/networking/src/udt/CongestionControl.h @@ -45,8 +45,10 @@ public: virtual void onTimeout() {} virtual void onPacketSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) {} + virtual void onPacketReSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) {} virtual int estimatedTimeout() const = 0; + protected: void setMSS(int mss) { _mss = mss; } virtual void setInitialSendSequenceNumber(SequenceNumber seqNum) = 0; diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 24e294881a..4798288a18 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -195,7 +195,7 @@ void Connection::recordSentPackets(int wireSize, int payloadSize, void Connection::recordRetransmission(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) { _stats.record(ConnectionStats::Stats::Retransmission); - _congestionControl->onPacketSent(wireSize, seqNum, timePoint); + _congestionControl->onPacketReSent(wireSize, seqNum, timePoint); } void Connection::sendACK() { @@ -303,7 +303,7 @@ void Connection::processControl(ControlPacketPointer controlPacket) { // where the other end expired our connection. Let's reset. #ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Got HandshakeRequest from" << _destination << ", stopping SendQueue"; + qCDebug(networking) << "Got HandshakeRequest from" << _destination << ", stopping SendQueue"; #endif _hasReceivedHandshakeACK = false; stopSendQueue(); @@ -327,19 +327,19 @@ void Connection::processACK(ControlPacketPointer controlPacket) { return; } - if (ack <= _lastReceivedACK) { + if (ack < _lastReceivedACK) { // this is an out of order ACK, bail - // or - // processing an already received ACK, bail return; } - - _lastReceivedACK = ack; - - // ACK the send queue so it knows what was received - getSendQueue().ack(ack); - + if (ack > _lastReceivedACK) { + // this is not a repeated ACK, so update our member and tell the send queue + _lastReceivedACK = ack; + + // ACK the send queue so it knows what was received + getSendQueue().ack(ack); + } + // give this ACK to the congestion control and update the send queue parameters updateCongestionControlAndSendQueue([this, ack, &controlPacket] { if (_congestionControl->onACK(ack, controlPacket->getReceiveTime())) { diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index b1dfb9a8cf..9cba4970ac 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -481,6 +481,7 @@ bool SendQueue::isInactive(bool attemptedToSendPacket) { auto cvStatus = _emptyCondition.wait_for(locker, EMPTY_QUEUES_INACTIVE_TIMEOUT); if (cvStatus == std::cv_status::timeout && (_packets.isEmpty() || isFlowWindowFull()) && _naks.isEmpty()) { + #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "SendQueue to" << _destination << "has been empty for" << EMPTY_QUEUES_INACTIVE_TIMEOUT.count() diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 6de43219e5..25e6fae023 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -228,13 +228,13 @@ qint64 Socket::writeDatagram(const QByteArray& datagram, const HifiSockAddr& soc return bytesWritten; } -Connection* Socket::findOrCreateConnection(const HifiSockAddr& sockAddr) { +Connection* Socket::findOrCreateConnection(const HifiSockAddr& sockAddr, bool filterCreate) { auto it = _connectionsHash.find(sockAddr); if (it == _connectionsHash.end()) { // we did not have a matching connection, time to see if we should make one - if (_connectionCreationFilterOperator && !_connectionCreationFilterOperator(sockAddr)) { + if (filterCreate && _connectionCreationFilterOperator && !_connectionCreationFilterOperator(sockAddr)) { // the connection creation filter did not allow us to create a new connection #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "Socket::findOrCreateConnection refusing to create connection for" << sockAddr @@ -376,7 +376,7 @@ void Socket::readPendingDatagrams() { controlPacket->setReceiveTime(receiveTime); // move this control packet to the matching connection, if there is one - auto connection = findOrCreateConnection(senderSockAddr); + auto connection = findOrCreateConnection(senderSockAddr, true); if (connection) { connection->processControl(move(controlPacket)); @@ -394,7 +394,7 @@ void Socket::readPendingDatagrams() { if (!_packetFilterOperator || _packetFilterOperator(*packet)) { if (packet->isReliable()) { // if this was a reliable packet then signal the matching connection with the sequence number - auto connection = findOrCreateConnection(senderSockAddr); + auto connection = findOrCreateConnection(senderSockAddr, true); if (!connection || !connection->processReceivedSequenceNumber(packet->getSequenceNumber(), packet->getDataSize(), @@ -409,7 +409,7 @@ void Socket::readPendingDatagrams() { } if (packet->isPartOfMessage()) { - auto connection = findOrCreateConnection(senderSockAddr); + auto connection = findOrCreateConnection(senderSockAddr, true); if (connection) { connection->queueReceivedMessagePacket(std::move(packet)); } diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index 30058e1d23..99266e105e 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -109,7 +109,7 @@ private slots: private: void setSystemBufferSizes(); - Connection* findOrCreateConnection(const HifiSockAddr& sockAddr); + Connection* findOrCreateConnection(const HifiSockAddr& sockAddr, bool filterCreation = false); bool socketMatchesNodeOrDomain(const HifiSockAddr& sockAddr); // privatized methods used by UDTTest - they are private since they must be called on the Socket thread diff --git a/libraries/networking/src/udt/TCPVegasCC.cpp b/libraries/networking/src/udt/TCPVegasCC.cpp index 4842e5a204..f2119237c2 100644 --- a/libraries/networking/src/udt/TCPVegasCC.cpp +++ b/libraries/networking/src/udt/TCPVegasCC.cpp @@ -27,112 +27,106 @@ TCPVegasCC::TCPVegasCC() { _baseRTT = std::numeric_limits<int>::max(); } +bool TCPVegasCC::calculateRTT(p_high_resolution_clock::time_point sendTime, p_high_resolution_clock::time_point receiveTime) { + // calculate the RTT (receive time - time ACK sent) + int lastRTT = duration_cast<microseconds>(receiveTime - sendTime).count(); + + const int MAX_RTT_SAMPLE_MICROSECONDS = 10000000; + + if (lastRTT < 0) { + Q_ASSERT_X(false, __FUNCTION__, "calculated an RTT that is not > 0"); + return false; + } else if (lastRTT == 0) { + // we do not allow a zero microsecond RTT (as per the UNIX kernel implementation of TCP Vegas) + lastRTT = 1; + } else if (lastRTT > MAX_RTT_SAMPLE_MICROSECONDS) { + // we cap the lastRTT to MAX_RTT_SAMPLE_MICROSECONDS to avoid overflows in window size calculations + lastRTT = MAX_RTT_SAMPLE_MICROSECONDS; + } + + if (_ewmaRTT == -1) { + // first RTT sample - set _ewmaRTT to the value and set the variance to half the value + _ewmaRTT = lastRTT; + _rttVariance = lastRTT / 2; + } else { + // This updates the RTT using exponential weighted moving average + // This is the Jacobson's forumla for RTT estimation + // http://www.mathcs.emory.edu/~cheung/Courses/455/Syllabus/7-transport/Jacobson-88.pdf + + // Estimated RTT = (1 - x)(estimatedRTT) + (x)(sampleRTT) + // (where x = 0.125 via Jacobson) + + // Deviation = (1 - x)(deviation) + x |sampleRTT - estimatedRTT| + // (where x = 0.25 via Jacobson) + + static const int RTT_ESTIMATION_ALPHA = 8; + static const int RTT_ESTIMATION_VARIANCE_ALPHA = 4; + + _ewmaRTT = (_ewmaRTT * (RTT_ESTIMATION_ALPHA - 1) + lastRTT) / RTT_ESTIMATION_ALPHA; + _rttVariance = (_rttVariance * (RTT_ESTIMATION_VARIANCE_ALPHA- 1) + + abs(lastRTT - _ewmaRTT)) / RTT_ESTIMATION_VARIANCE_ALPHA; + } + + // keep track of the lowest RTT during connection + _baseRTT = std::min(_baseRTT, lastRTT); + + // find the min RTT during the last RTT + _currentMinRTT = std::min(_currentMinRTT, lastRTT); + + // add 1 to the number of RTT samples collected during this RTT window + ++_numRTTs; + + return true; +} + bool TCPVegasCC::onACK(SequenceNumber ack, p_high_resolution_clock::time_point receiveTime) { - auto it = _sentPacketTimes.find(ack); auto previousAck = _lastACK; _lastACK = ack; - if (it != _sentPacketTimes.end()) { + bool wasDuplicateACK = (ack == previousAck); - // calculate the RTT (receive time - time ACK sent) - int lastRTT = duration_cast<microseconds>(receiveTime - it->second).count(); + auto it = std::find_if(_sentPacketDatas.begin(), _sentPacketDatas.end(), [ack](SentPacketData& packetTime){ + return packetTime.sequenceNumber == ack; + }); - const int MAX_RTT_SAMPLE_MICROSECONDS = 10000000; + if (!wasDuplicateACK && it != _sentPacketDatas.end()) { + // check if we can unambigiously calculate an RTT from this ACK - if (lastRTT < 0) { - Q_ASSERT_X(false, __FUNCTION__, "calculated an RTT that is not > 0"); + // for that to be the case, + // any of the packets this ACK covers (from the current ACK back to our previous ACK) + // must not have been re-sent + bool canBeUsedForRTT = std::none_of(_sentPacketDatas.begin(), _sentPacketDatas.end(), + [ack, previousAck](SentPacketData& sentPacketData) + { + return sentPacketData.sequenceNumber > previousAck + && sentPacketData.sequenceNumber <= ack + && sentPacketData.wasResent; + }); + + auto sendTime = it->timePoint; + + // remove all sent packet times up to this sequence number + it = _sentPacketDatas.erase(_sentPacketDatas.begin(), it + 1); + + // if we can use this ACK for an RTT calculation then do so + // returning false if we calculate an invalid RTT + if (canBeUsedForRTT && !calculateRTT(sendTime, receiveTime)) { return false; - } else if (lastRTT == 0) { - // we do not allow a zero microsecond RTT (as per the UNIX kernel implementation of TCP Vegas) - lastRTT = 1; - } else if (lastRTT > MAX_RTT_SAMPLE_MICROSECONDS) { - // we cap the lastRTT to MAX_RTT_SAMPLE_MICROSECONDS to avoid overflows in window size calculations - lastRTT = MAX_RTT_SAMPLE_MICROSECONDS; } + } - if (_ewmaRTT == -1) { - // first RTT sample - set _ewmaRTT to the value and set the variance to half the value - _ewmaRTT = lastRTT; - _rttVariance = lastRTT / 2; - } else { - // This updates the RTT using exponential weighted moving average - // This is the Jacobson's forumla for RTT estimation - // http://www.mathcs.emory.edu/~cheung/Courses/455/Syllabus/7-transport/Jacobson-88.pdf - - // Estimated RTT = (1 - x)(estimatedRTT) + (x)(sampleRTT) - // (where x = 0.125 via Jacobson) - - // Deviation = (1 - x)(deviation) + x |sampleRTT - estimatedRTT| - // (where x = 0.25 via Jacobson) - - static const int RTT_ESTIMATION_ALPHA = 8; - static const int RTT_ESTIMATION_VARIANCE_ALPHA = 4; - - _ewmaRTT = (_ewmaRTT * (RTT_ESTIMATION_ALPHA - 1) + lastRTT) / RTT_ESTIMATION_ALPHA; - _rttVariance = (_rttVariance * (RTT_ESTIMATION_VARIANCE_ALPHA- 1) - + abs(lastRTT - _ewmaRTT)) / RTT_ESTIMATION_VARIANCE_ALPHA; - } - - // add 1 to the number of ACKs during this RTT - ++_numACKs; - - // keep track of the lowest RTT during connection - _baseRTT = std::min(_baseRTT, lastRTT); - - // find the min RTT during the last RTT - _currentMinRTT = std::min(_currentMinRTT, lastRTT); - - auto sinceLastAdjustment = duration_cast<microseconds>(p_high_resolution_clock::now() - _lastAdjustmentTime).count(); - if (sinceLastAdjustment >= _ewmaRTT) { - performCongestionAvoidance(ack); - } - - // remove this sent packet time from the hash - _sentPacketTimes.erase(it); + auto sinceLastAdjustment = duration_cast<microseconds>(p_high_resolution_clock::now() - _lastAdjustmentTime).count(); + if (sinceLastAdjustment >= _ewmaRTT) { + performCongestionAvoidance(ack); } ++_numACKSinceFastRetransmit; // perform the fast re-transmit check if this is a duplicate ACK or if this is the first or second ACK // after a previous fast re-transmit - if (ack == previousAck || _numACKSinceFastRetransmit < 3) { - // we may need to re-send ackNum + 1 if it has been more than our estimated timeout since it was sent - - auto it = _sentPacketTimes.find(ack + 1); - if (it != _sentPacketTimes.end()) { - - auto now = p_high_resolution_clock::now(); - auto sinceSend = duration_cast<microseconds>(now - it->second).count(); - - if (sinceSend >= estimatedTimeout()) { - // break out of slow start, we've decided this is loss - _slowStart = false; - - // reset the fast re-transmit counter - _numACKSinceFastRetransmit = 0; - - // return true so the caller knows we needed a fast re-transmit - return true; - } - } - - // if this is the 3rd duplicate ACK, we fallback to Reno's fast re-transmit - static const int RENO_FAST_RETRANSMIT_DUPLICATE_COUNT = 3; - - ++_duplicateACKCount; - - if (ack == previousAck && _duplicateACKCount == RENO_FAST_RETRANSMIT_DUPLICATE_COUNT) { - // break out of slow start, we just hit loss - _slowStart = false; - - // reset our fast re-transmit counters - _numACKSinceFastRetransmit = 0; - _duplicateACKCount = 0; - - // return true so the caller knows we needed a fast re-transmit - return true; - } + if (wasDuplicateACK || _numACKSinceFastRetransmit < 3) { + return needsFastRetransmit(ack, wasDuplicateACK); } else { _duplicateACKCount = 0; } @@ -141,6 +135,49 @@ bool TCPVegasCC::onACK(SequenceNumber ack, p_high_resolution_clock::time_point r return false; } +bool TCPVegasCC::needsFastRetransmit(SequenceNumber ack, bool wasDuplicateACK) { + // we may need to re-send ackNum + 1 if it has been more than our estimated timeout since it was sent + + auto nextIt = std::find_if(_sentPacketDatas.begin(), _sentPacketDatas.end(), [ack](SentPacketData& packetTime){ + return packetTime.sequenceNumber == ack + 1; + }); + + if (nextIt != _sentPacketDatas.end()) { + auto now = p_high_resolution_clock::now(); + auto sinceSend = duration_cast<microseconds>(now - nextIt->timePoint).count(); + + if (sinceSend >= estimatedTimeout()) { + // break out of slow start, we've decided this is loss + _slowStart = false; + + // reset the fast re-transmit counter + _numACKSinceFastRetransmit = 0; + + // return true so the caller knows we needed a fast re-transmit + return true; + } + } + + // if this is the 3rd duplicate ACK, we fallback to Reno's fast re-transmit + static const int RENO_FAST_RETRANSMIT_DUPLICATE_COUNT = 3; + + ++_duplicateACKCount; + + if (wasDuplicateACK && _duplicateACKCount == RENO_FAST_RETRANSMIT_DUPLICATE_COUNT) { + // break out of slow start, we just hit loss + _slowStart = false; + + // reset our fast re-transmit counters + _numACKSinceFastRetransmit = 0; + _duplicateACKCount = 0; + + // return true so the caller knows we needed a fast re-transmit + return true; + } + + return false; +} + void TCPVegasCC::performCongestionAvoidance(udt::SequenceNumber ack) { static int VEGAS_ALPHA_SEGMENTS = 4; static int VEGAS_BETA_SEGMENTS = 6; @@ -158,7 +195,7 @@ void TCPVegasCC::performCongestionAvoidance(udt::SequenceNumber ack) { int64_t windowSizeDiff = (int64_t) _congestionWindowSize * (rtt - _baseRTT) / _baseRTT; - if (_numACKs <= 2) { + if (_numRTTs <= 2) { performRenoCongestionAvoidance(ack); } else { if (_slowStart) { @@ -209,7 +246,7 @@ void TCPVegasCC::performCongestionAvoidance(udt::SequenceNumber ack) { _currentMinRTT = std::numeric_limits<int>::max(); // reset our count of collected RTT samples - _numACKs = 0; + _numRTTs = 0; } @@ -230,29 +267,29 @@ void TCPVegasCC::performRenoCongestionAvoidance(SequenceNumber ack) { return; } - int numAcked = _numACKs; + int numRTTCollected = _numRTTs; if (_slowStart) { // while in slow start we grow the congestion window by the number of ACKed packets // allowing it to grow as high as the slow start threshold - int congestionWindow = _congestionWindowSize + numAcked; + int congestionWindow = _congestionWindowSize + numRTTCollected; if (congestionWindow > udt::MAX_PACKETS_IN_FLIGHT) { // we're done with slow start, set the congestion window to the slow start threshold _congestionWindowSize = udt::MAX_PACKETS_IN_FLIGHT; // figure out how many left over ACKs we should apply using the regular reno congestion avoidance - numAcked = congestionWindow - udt::MAX_PACKETS_IN_FLIGHT; + numRTTCollected = congestionWindow - udt::MAX_PACKETS_IN_FLIGHT; } else { _congestionWindowSize = congestionWindow; - numAcked = 0; + numRTTCollected = 0; } } // grab the size of the window prior to reno additive increase int preAIWindowSize = _congestionWindowSize; - if (numAcked > 0) { + if (numRTTCollected > 0) { // Once we are out of slow start, we use additive increase to grow the window slowly. // We grow the congestion window by a single packet everytime the entire congestion window is sent. @@ -263,7 +300,7 @@ void TCPVegasCC::performRenoCongestionAvoidance(SequenceNumber ack) { } // increase the window size by (1 / window size) for every ACK received - _ackAICount += numAcked; + _ackAICount += numRTTCollected; if (_ackAICount >= preAIWindowSize) { // when _ackAICount % preAIWindowSize == 0 then _ackAICount is 0 // when _ackAICount % preAIWindowSize != 0 then _ackAICount is _ackAICount - (_ackAICount % preAIWindowSize) @@ -277,8 +314,19 @@ void TCPVegasCC::performRenoCongestionAvoidance(SequenceNumber ack) { } void TCPVegasCC::onPacketSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) { - if (_sentPacketTimes.find(seqNum) == _sentPacketTimes.end()) { - _sentPacketTimes[seqNum] = timePoint; + _sentPacketDatas.emplace_back(seqNum, timePoint); +} + +void TCPVegasCC::onPacketReSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) { + // look for our information for this sent packet + auto it = std::find_if(_sentPacketDatas.begin(), _sentPacketDatas.end(), [seqNum](SentPacketData& sentPacketInfo){ + return sentPacketInfo.sequenceNumber == seqNum; + }); + + // if we found information for this packet (it hasn't been erased because it hasn't yet been ACKed) + // then mark it as re-sent so we know it cannot be used for RTT calculations + if (it != _sentPacketDatas.end()) { + it->wasResent = true; } } diff --git a/libraries/networking/src/udt/TCPVegasCC.h b/libraries/networking/src/udt/TCPVegasCC.h index bb14728d4b..1d83c4c992 100644 --- a/libraries/networking/src/udt/TCPVegasCC.h +++ b/libraries/networking/src/udt/TCPVegasCC.h @@ -30,6 +30,7 @@ public: virtual void onTimeout() override {}; virtual void onPacketSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) override; + virtual void onPacketReSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) override; virtual int estimatedTimeout() const override; @@ -37,11 +38,23 @@ protected: virtual void performCongestionAvoidance(SequenceNumber ack); virtual void setInitialSendSequenceNumber(SequenceNumber seqNum) override { _lastACK = seqNum - 1; } private: + bool calculateRTT(p_high_resolution_clock::time_point sendTime, p_high_resolution_clock::time_point receiveTime); + bool needsFastRetransmit(SequenceNumber ack, bool wasDuplicateACK); + bool isCongestionWindowLimited(); void performRenoCongestionAvoidance(SequenceNumber ack); - using PacketTimeList = std::map<SequenceNumber, p_high_resolution_clock::time_point>; - PacketTimeList _sentPacketTimes; // Map of sequence numbers to sent time + struct SentPacketData { + SentPacketData(SequenceNumber seqNum, p_high_resolution_clock::time_point tPoint) + : sequenceNumber(seqNum), timePoint(tPoint) {}; + + SequenceNumber sequenceNumber; + p_high_resolution_clock::time_point timePoint; + bool wasResent { false }; + }; + + using PacketTimeList = std::vector<SentPacketData>; + PacketTimeList _sentPacketDatas; // association of sequence numbers to sent time, for RTT calc p_high_resolution_clock::time_point _lastAdjustmentTime; // Time of last congestion control adjustment @@ -56,7 +69,7 @@ private: int _ewmaRTT { -1 }; // Exponential weighted moving average RTT int _rttVariance { 0 }; // Variance in collected RTT values - int _numACKs { 0 }; // Number of ACKs received during the last RTT (since last performed congestion avoidance) + int _numRTTs { 0 }; // Number of RTTs calculated during the last RTT (since last performed congestion avoidance) int _ackAICount { 0 }; // Counter for number of ACKs received for Reno additive increase int _duplicateACKCount { 0 }; // Counter for duplicate ACKs received diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 3d387e0956..06db92bcf7 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -734,11 +734,17 @@ QString getMarketplaceID(const QString& urlString) { return QString(); } -bool Octree::readFromURL(const QString& urlString) { +bool Octree::readFromURL( + const QString& urlString, + const bool isObservable, + const qint64 callerId +) { QString trimmedUrl = urlString.trimmed(); QString marketplaceID = getMarketplaceID(trimmedUrl); - auto request = - std::unique_ptr<ResourceRequest>(DependencyManager::get<ResourceManager>()->createResourceRequest(this, trimmedUrl)); + qDebug() << "!!!!! going to createResourceRequest " << callerId; + auto request = std::unique_ptr<ResourceRequest>( + DependencyManager::get<ResourceManager>()->createResourceRequest( + this, trimmedUrl, isObservable, callerId, "Octree::readFromURL")); if (!request) { return false; @@ -768,7 +774,11 @@ bool Octree::readFromURL(const QString& urlString) { } -bool Octree::readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID) { +bool Octree::readFromStream( + uint64_t streamLength, + QDataStream& inputStream, + const QString& marketplaceID +) { // decide if this is binary SVO or JSON-formatted SVO QIODevice *device = inputStream.device(); char firstChar; @@ -809,7 +819,11 @@ QJsonDocument addMarketplaceIDToDocumentEntities(QJsonDocument& doc, const QStri const int READ_JSON_BUFFER_SIZE = 2048; -bool Octree::readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID /*=""*/) { +bool Octree::readJSONFromStream( + uint64_t streamLength, + QDataStream& inputStream, + const QString& marketplaceID /*=""*/ +) { // if the data is gzipped we may not have a useful bytesAvailable() result, so just keep reading until // we get an eof. Leave streamLength parameter for consistency. diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index a2b2f227cb..44b429582a 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -210,7 +210,7 @@ public: // Octree importers bool readFromFile(const char* filename); - bool readFromURL(const QString& url); // will support file urls as well... + bool readFromURL(const QString& url, const bool isObservable = true, const qint64 callerId = -1); // will support file urls as well... bool readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID=""); bool readSVOFromStream(uint64_t streamLength, QDataStream& inputStream); bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID=""); diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 626184d1dc..8fd6d4eada 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -766,14 +766,16 @@ void CharacterController::updateState() { SET_STATE(State::InAir, "takeoff done"); // compute jumpSpeed based on the scaled jump height for the default avatar in default gravity. - float jumpSpeed = sqrtf(2.0f * DEFAULT_AVATAR_GRAVITY * _scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT); + const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); + const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); velocity += jumpSpeed * _currentUp; _rigidBody->setLinearVelocity(velocity); } break; case State::InAir: { - const float JUMP_SPEED = _scaleFactor * DEFAULT_AVATAR_JUMP_SPEED; - if ((velocity.dot(_currentUp) <= (JUMP_SPEED / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) { + const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); + const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); + if ((velocity.dot(_currentUp) <= (jumpSpeed / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) { SET_STATE(State::Ground, "hit ground"); } else if (_flyingAllowed) { btVector3 desiredVelocity = _targetVelocity; diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index c814140930..acfb0c9236 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -305,15 +305,16 @@ bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* } return true; } - } - if (_shape == newShape) { - // the shape didn't actually change, so we clear the DIRTY_SHAPE flag - flags &= ~Simulation::DIRTY_SHAPE; - // and clear the reference we just created - getShapeManager()->releaseShape(_shape); } else { - _body->setCollisionShape(const_cast<btCollisionShape*>(newShape)); - setShape(newShape); + if (_shape == newShape) { + // the shape didn't actually change, so we clear the DIRTY_SHAPE flag + flags &= ~Simulation::DIRTY_SHAPE; + // and clear the reference we just created + getShapeManager()->releaseShape(_shape); + } else { + _body->setCollisionShape(const_cast<btCollisionShape*>(newShape)); + setShape(newShape); + } } } if (flags & EASY_DIRTY_PHYSICS_FLAGS) { diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 8343919ae1..bf6e2463e5 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -289,6 +289,12 @@ void PhysicsEngine::processTransaction(PhysicsEngine::Transaction& transaction) bumpAndPruneContacts(object); btRigidBody* body = object->getRigidBody(); if (body) { + if (body->isStaticObject() && _activeStaticBodies.size() > 0) { + std::set<btRigidBody*>::iterator itr = _activeStaticBodies.find(body); + if (itr != _activeStaticBodies.end()) { + _activeStaticBodies.erase(itr); + } + } removeDynamicsForBody(body); _dynamicsWorld->removeRigidBody(body); diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index 39f3123d40..46fc3490a7 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -12,6 +12,7 @@ #include <gl/Config.h> #include <gl/QOpenGLContextWrapper.h> +#include <gl/GLHelpers.h> #include <QtQuick/QQuickWindow> @@ -114,6 +115,7 @@ void RenderEventHandler::onRender() { PROFILE_RANGE(render_qml_gl, __FUNCTION__); + gl::globalLock(); if (!_shared->preRender()) { return; } @@ -139,11 +141,12 @@ void RenderEventHandler::onRender() { glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); auto fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + // Fence will be used in another thread / context, so a flush is required glFlush(); _shared->updateTextureAndFence({ texture, fence }); - // Fence will be used in another thread / context, so a flush is required _shared->_quickWindow->resetOpenGLState(); } + gl::globalRelease(); } void RenderEventHandler::onQuit() { @@ -167,4 +170,5 @@ void RenderEventHandler::onQuit() { moveToThread(qApp->thread()); QThread::currentThread()->quit(); } -#endif \ No newline at end of file + +#endif diff --git a/libraries/qml/src/qml/impl/TextureCache.cpp b/libraries/qml/src/qml/impl/TextureCache.cpp index 7af8fa1ac9..7b4fb3adaf 100644 --- a/libraries/qml/src/qml/impl/TextureCache.cpp +++ b/libraries/qml/src/qml/impl/TextureCache.cpp @@ -51,8 +51,10 @@ uint32_t TextureCache::acquireTexture(const QSize& size) { if (!textureSet.returnedTextures.empty()) { auto textureAndFence = textureSet.returnedTextures.front(); textureSet.returnedTextures.pop_front(); - glWaitSync((GLsync)textureAndFence.second, 0, GL_TIMEOUT_IGNORED); - glDeleteSync((GLsync)textureAndFence.second); + if (textureAndFence.second) { + glWaitSync((GLsync)textureAndFence.second, 0, GL_TIMEOUT_IGNORED); + glDeleteSync((GLsync)textureAndFence.second); + } return textureAndFence.first; } return createTexture(size); @@ -101,9 +103,11 @@ void TextureCache::destroyTexture(uint32_t texture) { void TextureCache::destroy(const Value& textureAndFence) { const auto& fence = textureAndFence.second; - // FIXME prevents crash on shutdown, but we should migrate to a global functions object owned by the shared context. - glWaitSync((GLsync)fence, 0, GL_TIMEOUT_IGNORED); - glDeleteSync((GLsync)fence); + if (fence) { + // FIXME prevents crash on shutdown, but we should migrate to a global functions object owned by the shared context. + glWaitSync((GLsync)fence, 0, GL_TIMEOUT_IGNORED); + glDeleteSync((GLsync)fence); + } destroyTexture(textureAndFence.first); } diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 81a017a46d..c31345bc55 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -32,8 +32,8 @@ bool CauterizedModel::updateGeometry() { bool needsFullUpdate = Model::updateGeometry(); if (_isCauterized && needsFullUpdate) { assert(_cauterizeMeshStates.empty()); - const FBXGeometry& fbxGeometry = getFBXGeometry(); - foreach (const FBXMesh& mesh, fbxGeometry.meshes) { + const HFMModel& hfmModel = getHFMModel(); + foreach (const HFMMesh& mesh, hfmModel.meshes) { Model::MeshState state; if (_useDualQuaternionSkinning) { state.clusterDualQuaternions.resize(mesh.clusters.size()); @@ -76,7 +76,7 @@ void CauterizedModel::createRenderItemSet() { // Run through all of the meshes, and place them into their segregated, but unsorted buckets int shapeID = 0; uint32_t numMeshes = (uint32_t)meshes.size(); - const FBXGeometry& fbxGeometry = getFBXGeometry(); + const HFMModel& hfmModel = getHFMModel(); for (uint32_t i = 0; i < numMeshes; i++) { const auto& mesh = meshes.at(i); if (!mesh) { @@ -86,7 +86,7 @@ void CauterizedModel::createRenderItemSet() { // Create the render payloads int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { - initializeBlendshapes(fbxGeometry.meshes[i], i); + initializeBlendshapes(hfmModel.meshes[i], i); auto ptr = std::make_shared<CauterizedMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset); _modelMeshRenderItems << std::static_pointer_cast<ModelMeshPartPayload>(ptr); @@ -109,13 +109,13 @@ void CauterizedModel::updateClusterMatrices() { return; } _needsUpdateClusterMatrices = false; - const FBXGeometry& geometry = getFBXGeometry(); + const HFMModel& hfmModel = getHFMModel(); for (int i = 0; i < (int)_meshStates.size(); i++) { Model::MeshState& state = _meshStates[i]; - const FBXMesh& mesh = geometry.meshes.at(i); + const HFMMesh& mesh = hfmModel.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { - const FBXCluster& cluster = mesh.clusters.at(j); + const HFMCluster& cluster = mesh.clusters.at(j); if (_useDualQuaternionSkinning) { auto jointPose = _rig.getJointPose(cluster.jointIndex); Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); @@ -133,7 +133,7 @@ void CauterizedModel::updateClusterMatrices() { // as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty. if (!_cauterizeBoneSet.empty()) { - AnimPose cauterizePose = _rig.getJointPose(geometry.neckJointIndex); + AnimPose cauterizePose = _rig.getJointPose(hfmModel.neckJointIndex); cauterizePose.scale() = glm::vec3(0.0001f, 0.0001f, 0.0001f); static const glm::mat4 zeroScale( @@ -141,14 +141,14 @@ void CauterizedModel::updateClusterMatrices() { glm::vec4(0.0f, 0.0001f, 0.0f, 0.0f), glm::vec4(0.0f, 0.0f, 0.0001f, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); - auto cauterizeMatrix = _rig.getJointTransform(geometry.neckJointIndex) * zeroScale; + auto cauterizeMatrix = _rig.getJointTransform(hfmModel.neckJointIndex) * zeroScale; for (int i = 0; i < _cauterizeMeshStates.size(); i++) { Model::MeshState& state = _cauterizeMeshStates[i]; - const FBXMesh& mesh = geometry.meshes.at(i); + const HFMMesh& mesh = hfmModel.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { - const FBXCluster& cluster = mesh.clusters.at(j); + const HFMCluster& cluster = mesh.clusters.at(j); if (_useDualQuaternionSkinning) { if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) { @@ -175,7 +175,7 @@ void CauterizedModel::updateClusterMatrices() { // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get<ModelBlender>(); - if (_blendshapeOffsetsInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + if (_blendshapeOffsetsInitialized && modelBlender->shouldComputeBlendshapes() && hfmModel.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 2fe0368db2..b493780aff 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -242,9 +242,13 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in #ifdef Q_OS_MAC // On mac AMD, we specifically need to have a _meshBlendshapeBuffer bound when using a deformed mesh pipeline // it cannot be null otherwise we crash in the drawcall using a deformed pipeline with a skinned only (not blendshaped) mesh - if ((_isBlendShaped || _isSkinned)) { - glm::vec4 data; - _meshBlendshapeBuffer = std::make_shared<gpu::Buffer>(sizeof(glm::vec4), reinterpret_cast<const gpu::Byte*>(&data)); + if (_isBlendShaped) { + std::vector<BlendshapeOffset> data(_meshNumVertices); + const auto blendShapeBufferSize = _meshNumVertices * sizeof(BlendshapeOffset); + _meshBlendshapeBuffer = std::make_shared<gpu::Buffer>(blendShapeBufferSize, reinterpret_cast<const gpu::Byte*>(data.data()), blendShapeBufferSize); + } else if (_isSkinned) { + BlendshapeOffset data; + _meshBlendshapeBuffer = std::make_shared<gpu::Buffer>(sizeof(BlendshapeOffset), reinterpret_cast<const gpu::Byte*>(&data), sizeof(BlendshapeOffset)); } #endif @@ -256,8 +260,8 @@ void ModelMeshPartPayload::initCache(const ModelPointer& model) { _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR); _isSkinned = vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT) && vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_INDEX); - const FBXGeometry& geometry = model->getFBXGeometry(); - const FBXMesh& mesh = geometry.meshes.at(_meshIndex); + const HFMModel& hfmModel = model->getHFMModel(); + const HFMMesh& mesh = hfmModel.meshes.at(_meshIndex); _isBlendShaped = !mesh.blendshapes.isEmpty(); _hasTangents = !mesh.tangents.isEmpty(); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 53009e8bfa..7da7a45e83 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -183,11 +183,11 @@ bool Model::shouldInvalidatePayloadShapeKey(int meshIndex) { return true; } - const FBXGeometry& geometry = getFBXGeometry(); + const HFMModel& hfmModel = getHFMModel(); const auto& networkMeshes = getGeometry()->getMeshes(); // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown // to false to rebuild out mesh groups. - if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)geometry.meshes.size() || meshIndex >= (int)_meshStates.size()) { + if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)hfmModel.meshes.size() || meshIndex >= (int)_meshStates.size()) { _needsFixupInScene = true; // trigger remove/add cycle invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid return true; @@ -278,8 +278,8 @@ void Model::setRenderItemsNeedUpdate() { void Model::reset() { if (isLoaded()) { - const FBXGeometry& geometry = getFBXGeometry(); - _rig.reset(geometry); + const HFMModel& hfmModel = getHFMModel(); + _rig.reset(hfmModel); emit rigReset(); emit rigReady(); } @@ -295,13 +295,13 @@ bool Model::updateGeometry() { _needsReload = false; // TODO: should all Models have a valid _rig? - if (_rig.jointStatesEmpty() && getFBXGeometry().joints.size() > 0) { + if (_rig.jointStatesEmpty() && getHFMModel().joints.size() > 0) { initJointStates(); assert(_meshStates.empty()); - const FBXGeometry& fbxGeometry = getFBXGeometry(); + const HFMModel& hfmModel = getHFMModel(); int i = 0; - foreach (const FBXMesh& mesh, fbxGeometry.meshes) { + foreach (const HFMMesh& mesh, hfmModel.meshes) { MeshState state; state.clusterDualQuaternions.resize(mesh.clusters.size()); state.clusterMatrices.resize(mesh.clusters.size()); @@ -319,10 +319,10 @@ bool Model::updateGeometry() { // virtual void Model::initJointStates() { - const FBXGeometry& geometry = getFBXGeometry(); + const HFMModel& hfmModel = getHFMModel(); glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); - _rig.initJointStates(geometry, modelOffset); + _rig.initJointStates(hfmModel, modelOffset); } bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, @@ -363,9 +363,9 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g int bestShapeID = 0; int bestSubMeshIndex = 0; - const FBXGeometry& geometry = getFBXGeometry(); + const HFMModel& hfmModel = getHFMModel(); if (!_triangleSetsValid) { - calculateTriangleSets(geometry); + calculateTriangleSets(hfmModel); } glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); @@ -448,7 +448,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g extraInfo["shapeID"] = bestShapeID; if (pickAgainstTriangles) { extraInfo["subMeshIndex"] = bestSubMeshIndex; - extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex); + extraInfo["subMeshName"] = hfmModel.getModelNameOfMesh(bestSubMeshIndex); extraInfo["subMeshTriangleWorld"] = QVariantMap{ { "v0", vec3toVariant(bestWorldTriangle.v0) }, { "v1", vec3toVariant(bestWorldTriangle.v1) }, @@ -506,9 +506,9 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co int bestShapeID = 0; int bestSubMeshIndex = 0; - const FBXGeometry& geometry = getFBXGeometry(); + const HFMModel& hfmModel = getHFMModel(); if (!_triangleSetsValid) { - calculateTriangleSets(geometry); + calculateTriangleSets(hfmModel); } glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); @@ -595,7 +595,7 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co extraInfo["shapeID"] = bestShapeID; if (pickAgainstTriangles) { extraInfo["subMeshIndex"] = bestSubMeshIndex; - extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex); + extraInfo["subMeshName"] = hfmModel.getModelNameOfMesh(bestSubMeshIndex); extraInfo["subMeshTriangleWorld"] = QVariantMap{ { "v0", vec3toVariant(bestWorldTriangle.v0) }, { "v1", vec3toVariant(bestWorldTriangle.v1) }, @@ -641,7 +641,7 @@ bool Model::convexHullContains(glm::vec3 point) { QMutexLocker locker(&_mutex); if (!_triangleSetsValid) { - calculateTriangleSets(getFBXGeometry()); + calculateTriangleSets(getHFMModel()); } // If we are inside the models box, then consider the submeshes... @@ -753,29 +753,29 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe } // update triangles for picking { - FBXGeometry geometry; + HFMModel hfmModel; for (const auto& newMesh : meshes) { - FBXMesh mesh; + HFMMesh mesh; mesh._mesh = newMesh.getMeshPointer(); mesh.vertices = buffer_helpers::mesh::attributeToVector<glm::vec3>(mesh._mesh, gpu::Stream::POSITION); int numParts = (int)newMesh.getMeshPointer()->getNumParts(); for (int partID = 0; partID < numParts; partID++) { - FBXMeshPart part; + HFMMeshPart part; part.triangleIndices = buffer_helpers::bufferToVector<int>(mesh._mesh->getIndexBuffer(), "part.triangleIndices"); mesh.parts << part; } { foreach (const glm::vec3& vertex, mesh.vertices) { glm::vec3 transformedVertex = glm::vec3(mesh.modelTransform * glm::vec4(vertex, 1.0f)); - geometry.meshExtents.minimum = glm::min(geometry.meshExtents.minimum, transformedVertex); - geometry.meshExtents.maximum = glm::max(geometry.meshExtents.maximum, transformedVertex); + hfmModel.meshExtents.minimum = glm::min(hfmModel.meshExtents.minimum, transformedVertex); + hfmModel.meshExtents.maximum = glm::max(hfmModel.meshExtents.maximum, transformedVertex); mesh.meshExtents.minimum = glm::min(mesh.meshExtents.minimum, transformedVertex); mesh.meshExtents.maximum = glm::max(mesh.meshExtents.maximum, transformedVertex); } } - geometry.meshes << mesh; + hfmModel.meshes << mesh; } - calculateTriangleSets(geometry); + calculateTriangleSets(hfmModel); } return true; } @@ -789,12 +789,12 @@ scriptable::ScriptableModelBase Model::getScriptableModel() { return result; } - const FBXGeometry& geometry = getFBXGeometry(); - int numberOfMeshes = geometry.meshes.size(); + const HFMModel& hfmModel = getHFMModel(); + int numberOfMeshes = hfmModel.meshes.size(); int shapeID = 0; for (int i = 0; i < numberOfMeshes; i++) { - const FBXMesh& fbxMesh = geometry.meshes.at(i); - if (auto mesh = fbxMesh._mesh) { + const HFMMesh& hfmMesh = hfmModel.meshes.at(i); + if (auto mesh = hfmMesh._mesh) { result.append(mesh); int numParts = (int)mesh->getNumParts(); @@ -808,24 +808,24 @@ scriptable::ScriptableModelBase Model::getScriptableModel() { return result; } -void Model::calculateTriangleSets(const FBXGeometry& geometry) { +void Model::calculateTriangleSets(const HFMModel& hfmModel) { PROFILE_RANGE(render, __FUNCTION__); - int numberOfMeshes = geometry.meshes.size(); + int numberOfMeshes = hfmModel.meshes.size(); _triangleSetsValid = true; _modelSpaceMeshTriangleSets.clear(); _modelSpaceMeshTriangleSets.resize(numberOfMeshes); for (int i = 0; i < numberOfMeshes; i++) { - const FBXMesh& mesh = geometry.meshes.at(i); + const HFMMesh& mesh = hfmModel.meshes.at(i); const int numberOfParts = mesh.parts.size(); auto& meshTriangleSets = _modelSpaceMeshTriangleSets[i]; meshTriangleSets.resize(numberOfParts); for (int j = 0; j < numberOfParts; j++) { - const FBXMeshPart& part = mesh.parts.at(j); + const HFMMeshPart& part = mesh.parts.at(j); auto& partTriangleSet = meshTriangleSets[j]; @@ -839,7 +839,7 @@ void Model::calculateTriangleSets(const FBXGeometry& geometry) { int totalTriangles = (numberOfQuads * TRIANGLES_PER_QUAD) + numberOfTris; partTriangleSet.reserve(totalTriangles); - auto meshTransform = geometry.offset * mesh.modelTransform; + auto meshTransform = hfmModel.offset * mesh.modelTransform; if (part.quadIndices.size() > 0) { int vIndex = 0; @@ -1114,7 +1114,7 @@ Extents Model::getBindExtents() const { if (!isActive()) { return Extents(); } - const Extents& bindExtents = getFBXGeometry().bindExtents; + const Extents& bindExtents = getHFMModel().bindExtents; Extents scaledExtents = { bindExtents.minimum * _scale, bindExtents.maximum * _scale }; return scaledExtents; } @@ -1128,12 +1128,12 @@ Extents Model::getMeshExtents() const { if (!isActive()) { return Extents(); } - const Extents& extents = getFBXGeometry().meshExtents; + const Extents& extents = getHFMModel().meshExtents; // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which // is captured in the offset matrix - glm::vec3 minimum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f)); - glm::vec3 maximum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f)); + glm::vec3 minimum = glm::vec3(getHFMModel().offset * glm::vec4(extents.minimum, 1.0f)); + glm::vec3 maximum = glm::vec3(getHFMModel().offset * glm::vec4(extents.maximum, 1.0f)); Extents scaledExtents = { minimum * _scale, maximum * _scale }; return scaledExtents; } @@ -1143,12 +1143,12 @@ Extents Model::getUnscaledMeshExtents() const { return Extents(); } - const Extents& extents = getFBXGeometry().meshExtents; + const Extents& extents = getHFMModel().meshExtents; // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which // is captured in the offset matrix - glm::vec3 minimum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f)); - glm::vec3 maximum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f)); + glm::vec3 minimum = glm::vec3(getHFMModel().offset * glm::vec4(extents.minimum, 1.0f)); + glm::vec3 maximum = glm::vec3(getHFMModel().offset * glm::vec4(extents.maximum, 1.0f)); Extents scaledExtents = { minimum, maximum }; return scaledExtents; @@ -1171,18 +1171,18 @@ void Model::setJointTranslation(int index, bool valid, const glm::vec3& translat } int Model::getParentJointIndex(int jointIndex) const { - return (isActive() && jointIndex != -1) ? getFBXGeometry().joints.at(jointIndex).parentIndex : -1; + return (isActive() && jointIndex != -1) ? getHFMModel().joints.at(jointIndex).parentIndex : -1; } int Model::getLastFreeJointIndex(int jointIndex) const { - return (isActive() && jointIndex != -1) ? getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1; + return (isActive() && jointIndex != -1) ? getHFMModel().joints.at(jointIndex).freeLineage.last() : -1; } void Model::setTextures(const QVariantMap& textures) { if (isLoaded()) { - _pendingTextures.clear(); _needsFixupInScene = true; _renderGeometry->setTextures(textures); + _pendingTextures.clear(); } else { _pendingTextures = textures; } @@ -1275,7 +1275,7 @@ QStringList Model::getJointNames() const { Q_RETURN_ARG(QStringList, result)); return result; } - return isActive() ? getFBXGeometry().getJointNames() : QStringList(); + return isActive() ? getHFMModel().getJointNames() : QStringList(); } void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale) { @@ -1415,12 +1415,12 @@ void Model::updateClusterMatrices() { } _needsUpdateClusterMatrices = false; - const FBXGeometry& geometry = getFBXGeometry(); + const HFMModel& hfmModel = getHFMModel(); for (int i = 0; i < (int) _meshStates.size(); i++) { MeshState& state = _meshStates[i]; - const FBXMesh& mesh = geometry.meshes.at(i); + const HFMMesh& mesh = hfmModel.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { - const FBXCluster& cluster = mesh.clusters.at(j); + const HFMCluster& cluster = mesh.clusters.at(j); if (_useDualQuaternionSkinning) { auto jointPose = _rig.getJointPose(cluster.jointIndex); Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); @@ -1436,7 +1436,7 @@ void Model::updateClusterMatrices() { // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get<ModelBlender>(); - if (_blendshapeOffsetsInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + if (_blendshapeOffsetsInitialized && modelBlender->shouldComputeBlendshapes() && hfmModel.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } @@ -1505,7 +1505,7 @@ void Model::createRenderItemSet() { // Run through all of the meshes, and place them into their segregated, but unsorted buckets int shapeID = 0; uint32_t numMeshes = (uint32_t)meshes.size(); - auto& fbxGeometry = getFBXGeometry(); + auto& hfmModel = getHFMModel(); for (uint32_t i = 0; i < numMeshes; i++) { const auto& mesh = meshes.at(i); if (!mesh) { @@ -1515,7 +1515,7 @@ void Model::createRenderItemSet() { // Create the render payloads int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { - initializeBlendshapes(fbxGeometry.meshes[i], i); + initializeBlendshapes(hfmModel.meshes[i], i); _modelMeshRenderItems << std::make_shared<ModelMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset); auto material = getGeometry()->getShapeMaterial(shapeID); _modelMeshMaterialNames.push_back(material ? material->getName() : ""); @@ -1600,7 +1600,7 @@ void Model::removeMaterial(graphics::MaterialPointer material, const std::string class CollisionRenderGeometry : public Geometry { public: CollisionRenderGeometry(graphics::MeshPointer mesh) { - _fbxGeometry = std::make_shared<FBXGeometry>(); + _hfmModel = std::make_shared<HFMModel>(); std::shared_ptr<GeometryMeshes> meshes = std::make_shared<GeometryMeshes>(); meshes->push_back(mesh); _meshes = meshes; @@ -1656,9 +1656,9 @@ void Blender::run() { if (_model && _model->isLoaded()) { DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } }); int offset = 0; - auto meshes = _model->getFBXGeometry().meshes; + auto meshes = _model->getHFMModel().meshes; int meshIndex = 0; - foreach(const FBXMesh& mesh, meshes) { + foreach(const HFMMesh& mesh, meshes) { auto modelMeshBlendshapeOffsets = _model->_blendshapeOffsets.find(meshIndex++); if (mesh.blendshapes.isEmpty() || modelMeshBlendshapeOffsets == _model->_blendshapeOffsets.end()) { // Not blendshaped or not initialized @@ -1688,7 +1688,7 @@ void Blender::run() { } float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE; - const FBXBlendshape& blendshape = mesh.blendshapes.at(i); + const HFMBlendshape& blendshape = mesh.blendshapes.at(i); tbb::parallel_for(tbb::blocked_range<int>(0, blendshape.indices.size()), [&](const tbb::blocked_range<int>& range) { for (auto j = range.begin(); j < range.end(); j++) { @@ -1731,7 +1731,7 @@ bool Model::maybeStartBlender() { return false; } -void Model::initializeBlendshapes(const FBXMesh& mesh, int index) { +void Model::initializeBlendshapes(const HFMMesh& mesh, int index) { if (mesh.blendshapes.empty()) { // mesh doesn't have blendshape, did we allocate one though ? if (_blendshapeOffsets.find(index) != _blendshapeOffsets.end()) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 71809821eb..93a0626d28 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -163,7 +163,7 @@ public: bool maybeStartBlender(); - bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isGeometryLoaded(); } + bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isHFMModelLoaded(); } bool isAddedToScene() const { return _addedToScene; } void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; } @@ -184,8 +184,8 @@ public: Q_INVOKABLE virtual void setTextures(const QVariantMap& textures); /// Provided as a convenience, will crash if !isLoaded() - // And so that getGeometry() isn't chained everywhere - const FBXGeometry& getFBXGeometry() const { assert(isLoaded()); return _renderGeometry->getFBXGeometry(); } + // And so that getHFMModel() isn't chained everywhere + const HFMModel& getHFMModel() const { assert(isLoaded()); return _renderGeometry->getHFMModel(); } bool isActive() const { return isLoaded(); } @@ -450,7 +450,7 @@ protected: bool _overrideModelTransform { false }; bool _triangleSetsValid { false }; - void calculateTriangleSets(const FBXGeometry& geometry); + void calculateTriangleSets(const HFMModel& hfmModel); std::vector<std::vector<TriangleSet>> _modelSpaceMeshTriangleSets; // model space triangles for all sub meshes virtual void createRenderItemSet(); @@ -506,7 +506,7 @@ protected: bool shouldInvalidatePayloadShapeKey(int meshIndex); - void initializeBlendshapes(const FBXMesh& mesh, int index); + void initializeBlendshapes(const HFMMesh& mesh, int index); private: float _loadingPriority { 0.0f }; diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index 90015768d0..f26bad86b0 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -41,14 +41,14 @@ void SoftAttachmentModel::updateClusterMatrices() { _needsUpdateClusterMatrices = false; - const FBXGeometry& geometry = getFBXGeometry(); + const HFMModel& hfmModel = getHFMModel(); for (int i = 0; i < (int) _meshStates.size(); i++) { MeshState& state = _meshStates[i]; - const FBXMesh& mesh = geometry.meshes.at(i); + const HFMMesh& mesh = hfmModel.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { - const FBXCluster& cluster = mesh.clusters.at(j); + const HFMCluster& cluster = mesh.clusters.at(j); // TODO: cache these look-ups as an optimization int jointIndexOverride = getJointIndexOverride(cluster.jointIndex); @@ -78,7 +78,7 @@ void SoftAttachmentModel::updateClusterMatrices() { // post the blender if we're not currently waiting for one to finish auto modelBlender = DependencyManager::get<ModelBlender>(); - if (_blendshapeOffsetsInitialized && modelBlender->shouldComputeBlendshapes() && geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + if (_blendshapeOffsetsInitialized && modelBlender->shouldComputeBlendshapes() && hfmModel.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { _blendedBlendshapeCoefficients = _blendshapeCoefficients; modelBlender->noteRequiresBlend(getThisPointer()); } diff --git a/libraries/render-utils/src/debug_deferred_buffer.slf b/libraries/render-utils/src/debug_deferred_buffer.slf index c6e3c49e54..ccbe5c491f 100644 --- a/libraries/render-utils/src/debug_deferred_buffer.slf +++ b/libraries/render-utils/src/debug_deferred_buffer.slf @@ -25,7 +25,7 @@ LAYOUT(binding=RENDER_UTILS_TEXTURE_SHADOW) uniform sampler2DArrayShadow shadowM <@include debug_deferred_buffer_shared.slh@> -layout(std140, binding=RENDER_UTILS_BUFFER_DEBUG_DEFERRED_PARAMS) uniform parametersBuffer { +LAYOUT_STD140(binding=RENDER_UTILS_BUFFER_DEBUG_DEFERRED_PARAMS) uniform parametersBuffer { DebugParameters parameters; }; diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index b2930032a3..5e5c6b4c6e 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -205,7 +205,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, if (!srcFilter.selectsNothing()) { auto filter = render::ItemFilter::Builder(srcFilter).withoutSubMetaCulled().build(); - // Now get the bound, and + // Now get the bound, and // filter individually against the _filter // visibility cull if partially selected ( octree cell contianing it was partial) // distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item) diff --git a/libraries/script-engine/src/FileScriptingInterface.cpp b/libraries/script-engine/src/FileScriptingInterface.cpp index 3bf044fd8b..4e07877d57 100644 --- a/libraries/script-engine/src/FileScriptingInterface.cpp +++ b/libraries/script-engine/src/FileScriptingInterface.cpp @@ -138,7 +138,8 @@ QString FileScriptingInterface::convertUrlToPath(QUrl url) { // this function is not in use void FileScriptingInterface::downloadZip(QString path, const QString link) { QUrl url = QUrl(link); - auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(nullptr, url); + auto request = DependencyManager::get<ResourceManager>()->createResourceRequest( + nullptr, url, true, -1, "FileScriptingInterface::downloadZip"); connect(request, &ResourceRequest::finished, this, [this, path]{ unzipFile(path, ""); // so intellisense isn't mad }); diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp index dba2db0458..8acf88a7ce 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -109,7 +109,8 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "about to call: ResourceManager::createResourceRequest(this, url); on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; #endif - auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(nullptr, url); + auto request = DependencyManager::get<ResourceManager>()->createResourceRequest( + nullptr, url, true, -1, "ScriptCache::getScriptContents"); Q_ASSERT(request); request->setCacheEnabled(!forceDownload); connect(request, &ResourceRequest::finished, this, [=]{ scriptContentAvailable(maxRetries); }); @@ -166,7 +167,8 @@ void ScriptCache::scriptContentAvailable(int maxRetries) { qCDebug(scriptengine) << QString("Retrying script request [%1 / %2]: %3") .arg(attempt).arg(maxRetries).arg(url.toString()); - auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(nullptr, url); + auto request = DependencyManager::get<ResourceManager>()->createResourceRequest( + nullptr, url, true, -1, "ScriptCache::scriptContentAvailable"); Q_ASSERT(request); // We've already made a request, so the cache must be disabled or it wasn't there, so enabling diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index ebc459b2d1..a74d185c6a 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -21,6 +21,7 @@ #include <NetworkAccessManager.h> #include <NetworkingConstants.h> +#include "ResourceRequestObserver.h" #include "ScriptEngine.h" const QString METAVERSE_API_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/api/"; @@ -189,7 +190,7 @@ void XMLHttpRequestClass::send(const QScriptValue& data) { } void XMLHttpRequestClass::doSend() { - + DependencyManager::get<ResourceRequestObserver>()->update(_url, -1, "XMLHttpRequestClass::doSend"); _reply = NetworkAccessManager::getInstance().sendCustomRequest(_request, _method.toLatin1(), _sendData); connectToReply(_reply); diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 6c38d08c96..87da47a27a 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -70,9 +70,10 @@ const float DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED = 2.2f; // meters / second const float DEFAULT_AVATAR_MAX_FLYING_SPEED = 30.0f; // meters / second const float DEFAULT_AVATAR_WALK_SPEED_THRESHOLD = 0.15f; -const float DEFAULT_AVATAR_GRAVITY = -5.0f; // meters / second^2 -const float DEFAULT_AVATAR_JUMP_SPEED = 3.5f; // meters / second -const float DEFAULT_AVATAR_JUMP_HEIGHT = (DEFAULT_AVATAR_JUMP_SPEED * DEFAULT_AVATAR_JUMP_SPEED) / (2.0f * DEFAULT_AVATAR_GRAVITY); // meters +const float DEFAULT_AVATAR_GRAVITY = -5.0f; // meters / second^2 (world) +const float DEFAULT_AVATAR_JUMP_SPEED = 3.5f; // meters / second (sensor) +const float DEFAULT_AVATAR_JUMP_HEIGHT = (DEFAULT_AVATAR_JUMP_SPEED * DEFAULT_AVATAR_JUMP_SPEED) / (2.0f * -DEFAULT_AVATAR_GRAVITY); // meters (sensor) +const float DEFAULT_AVATAR_MIN_JUMP_HEIGHT = 0.25f; // meters (world) // hack const float DEFAULT_AVATAR_FALL_HEIGHT = 20.0f; // meters const float DEFAULT_AVATAR_MIN_HOVER_HEIGHT = 2.5f; // meters diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index b11021c48f..d6a740231c 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -68,8 +68,14 @@ namespace PrioritySortUtil { void reserve(size_t num) { _vector.reserve(num); } - const std::vector<T>& getSortedVector() { - std::sort(_vector.begin(), _vector.end(), [](const T& left, const T& right) { return left.getPriority() > right.getPriority(); }); + const std::vector<T>& getSortedVector(int numToSort = 0) { + if (numToSort == 0 || numToSort >= (int)_vector.size()) { + std::sort(_vector.begin(), _vector.end(), + [](const T& left, const T& right) { return left.getPriority() > right.getPriority(); }); + } else { + std::partial_sort(_vector.begin(), _vector.begin() + numToSort, _vector.end(), + [](const T& left, const T& right) { return left.getPriority() > right.getPriority(); }); + } return _vector; } @@ -99,6 +105,9 @@ namespace PrioritySortUtil { float radius = glm::max(thing.getRadius(), MIN_RADIUS); // Other item's angle from view centre: float cosineAngle = glm::dot(offset, view.getDirection()) / distance; + if (cosineAngle > 0.0f) { + cosineAngle = std::sqrt(cosineAngle); + } float age = float((_usecCurrentTime - thing.getTimestamp()) / USECS_PER_SECOND); // the "age" term accumulates at the sum of all weights diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 64a874f63d..ed637fe771 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -269,6 +269,7 @@ void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay); * * @typedef {object} StylusTip * @property {number} side - The hand the tip is attached to: <code>0</code> for left, <code>1</code> for right. + * @property {Vec3} tipOffset - the position offset of the stylus tip. * @property {Vec3} position - The position of the stylus tip. * @property {Quat} orientation - The orientation of the stylus tip. * @property {Vec3} velocity - The velocity of the stylus tip. @@ -276,12 +277,14 @@ void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay); class StylusTip : public MathPick { public: StylusTip() : position(NAN), velocity(NAN) {} - StylusTip(const bilateral::Side& side, const glm::vec3& position = Vectors::ZERO, const glm::quat& orientation = Quaternions::IDENTITY, const glm::vec3& velocity = Vectors::ZERO) : - side(side), position(position), orientation(orientation), velocity(velocity) {} - StylusTip(const QVariantMap& pickVariant) : side(bilateral::Side(pickVariant["side"].toInt())), position(vec3FromVariant(pickVariant["position"])), - orientation(quatFromVariant(pickVariant["orientation"])), velocity(vec3FromVariant(pickVariant["velocity"])) {} + StylusTip(const bilateral::Side& side, const glm::vec3& tipOffset = Vectors::ZERO ,const glm::vec3& position = Vectors::ZERO, + const glm::quat& orientation = Quaternions::IDENTITY, const glm::vec3& velocity = Vectors::ZERO) : + side(side), tipOffset(tipOffset), position(position), orientation(orientation), velocity(velocity) {} + StylusTip(const QVariantMap& pickVariant) : side(bilateral::Side(pickVariant["side"].toInt())), tipOffset(vec3FromVariant(pickVariant["tipOffset"])), + position(vec3FromVariant(pickVariant["position"])), orientation(quatFromVariant(pickVariant["orientation"])), velocity(vec3FromVariant(pickVariant["velocity"])) {} bilateral::Side side { bilateral::Side::Invalid }; + glm::vec3 tipOffset; glm::vec3 position; glm::quat orientation; glm::vec3 velocity; @@ -289,12 +292,13 @@ public: operator bool() const override { return side != bilateral::Side::Invalid; } bool operator==(const StylusTip& other) const { - return (side == other.side && position == other.position && orientation == other.orientation && velocity == other.velocity); + return (side == other.side && tipOffset == other.tipOffset && position == other.position && orientation == other.orientation && velocity == other.velocity); } QVariantMap toVariantMap() const override { QVariantMap stylusTip; stylusTip["side"] = (int)side; + stylusTip["tipOffset"] = vec3toVariant(tipOffset); stylusTip["position"] = vec3toVariant(position); stylusTip["orientation"] = quatToVariant(orientation); stylusTip["velocity"] = vec3toVariant(velocity); diff --git a/libraries/shared/src/ResourceRequestObserver.cpp b/libraries/shared/src/ResourceRequestObserver.cpp new file mode 100644 index 0000000000..5e0925520a --- /dev/null +++ b/libraries/shared/src/ResourceRequestObserver.cpp @@ -0,0 +1,28 @@ +// +// ResourceAccessMonitor.h +// libraries/networking/src +// +// Created by Kerry Ivan Kurian on 9/27/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include <QJsonArray> +#include <QJsonObject> +#include <QString> +#include <QUrl> +#include "ResourceRequestObserver.h" + +void ResourceRequestObserver::update(const QUrl& requestUrl, + const qint64 callerId, + const QString& extra) { + QJsonArray array; + QJsonObject data { { "url", requestUrl.toString() }, + { "callerId", callerId }, + { "extra", extra } + }; + emit resourceRequestEvent(data.toVariantMap()); +} diff --git a/libraries/shared/src/ResourceRequestObserver.h b/libraries/shared/src/ResourceRequestObserver.h new file mode 100644 index 0000000000..1b1bc322e5 --- /dev/null +++ b/libraries/shared/src/ResourceRequestObserver.h @@ -0,0 +1,29 @@ +// +// ResourceRequestObserver.h +// libraries/commerce/src +// +// Created by Kerry Ivan Kurian on 9/27/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include <QJsonObject> +#include <QString> +#include <QNetworkRequest> + +#include "DependencyManager.h" + + +class ResourceRequestObserver : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + void update(const QUrl& requestUrl, const qint64 callerId = -1, const QString& extra = ""); + +signals: + void resourceRequestEvent(QVariantMap result); +}; diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index fd2ff6e790..97e20f5627 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -74,10 +74,12 @@ void SpatiallyNestable::setParentID(const QUuid& parentID) { } }); - bool success = false; - auto parent = getParentPointer(success); - if (success && parent) { - parent->updateQueryAACube(); + if (!_parentKnowsMe) { + bool success = false; + auto parent = getParentPointer(success); + if (success && parent) { + parent->updateQueryAACube(); + } } } diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 74098f69c7..f67a356078 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -250,6 +250,7 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { engine->setNetworkAccessManagerFactory(new QmlNetworkAccessManagerFactory); auto importList = engine->importPathList(); + importList.insert(importList.begin(), PathUtils::resourcesPath() + "qml/"); importList.insert(importList.begin(), PathUtils::resourcesPath()); engine->setImportPathList(importList); for (const auto& path : importList) { diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 1081f8c4e7..52d359ad0d 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -888,6 +888,12 @@ void TabletProxy::desktopWindowClosed() { gotoHomeScreen(); } +void TabletProxy::unfocus() { + if (_qmlOffscreenSurface) { + _qmlOffscreenSurface->lowerKeyboard(); + } +} + QQuickItem* TabletProxy::getQmlTablet() const { if (!_qmlTabletRoot) { diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index 2d37402d01..9821ad1263 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -232,6 +232,7 @@ public: const QString getName() const { return _name; } bool getToolbarMode() const { return _toolbarMode; } void setToolbarMode(bool toolbarMode); + void unfocus(); /**jsdoc * @function TabletProxy#gotoMenuScreen diff --git a/libraries/ui/src/ui/types/RequestFilters.cpp b/libraries/ui/src/ui/types/RequestFilters.cpp index 7f192d6e52..a3b3b7dc57 100644 --- a/libraries/ui/src/ui/types/RequestFilters.cpp +++ b/libraries/ui/src/ui/types/RequestFilters.cpp @@ -70,9 +70,9 @@ void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info, // check if this is a request to a highfidelity URL bool isAuthable = isAuthableHighFidelityURL(info.requestUrl()); + auto accountManager = DependencyManager::get<AccountManager>(); if (isAuthable) { // if we have an access token, add it to the right HTTP header for authorization - auto accountManager = DependencyManager::get<AccountManager>(); if (accountManager->hasValidAccessToken()) { static const QString OAUTH_AUTHORIZATION_HEADER = "Authorization"; @@ -84,13 +84,9 @@ void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info, static const QString USER_AGENT = "User-Agent"; const QString tokenStringMobile{ "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36" }; const QString tokenStringMetaverse{ "Chrome/48.0 (HighFidelityInterface)" }; + const QString tokenStringLimitedCommerce{ "Chrome/48.0 (HighFidelityInterface limitedCommerce)" }; - // During the period in which we have HFC commerce in the system, but not applied everywhere: - const QString tokenStringCommerce{ "Chrome/48.0 (HighFidelityInterface WithHFC)" }; - Setting::Handle<bool> _settingSwitch{ "commerce", true }; - bool isMoney = _settingSwitch.get(); - - const QString tokenString = !isAuthable ? tokenStringMobile : (isMoney ? tokenStringCommerce : tokenStringMetaverse); + const QString tokenString = !isAuthable ? tokenStringMobile : (accountManager->getLimitedCommerce() ? tokenStringLimitedCommerce : tokenStringMetaverse); info.setHttpHeader(USER_AGENT.toLocal8Bit(), tokenString.toLocal8Bit()); } diff --git a/libraries/workload/src/workload/ViewTask.cpp b/libraries/workload/src/workload/ViewTask.cpp index 9d0c693149..0a268df9fc 100644 --- a/libraries/workload/src/workload/ViewTask.cpp +++ b/libraries/workload/src/workload/ViewTask.cpp @@ -31,13 +31,25 @@ void SetupViews::run(const WorkloadContextPointer& renderContext, const Input& i auto& outViews = outputs; outViews.clear(); - // Filter the first view centerer on the avatar head if needed if (_views.size() >= 2) { + // when inputs contains two or more views: + // index 0 = view from avatar's head + // index 1 = view from camera + // index 2 and higher = secondary camera and whatever if (data.useAvatarView) { + // for debug purposes we keep the head view and skip that of the camera outViews.push_back(_views[0]); outViews.insert(outViews.end(), _views.begin() + 2, _views.end()); } else { - outViews.insert(outViews.end(), _views.begin() + 1, _views.end()); + // otherwise we use all of the views... + const float MIN_HEAD_CAMERA_SEPARATION_SQUARED = MIN_VIEW_BACK_FRONTS[0][1] * MIN_VIEW_BACK_FRONTS[0][1]; + if (glm::distance2(_views[0].origin, _views[1].origin) < MIN_HEAD_CAMERA_SEPARATION_SQUARED) { + // ... unless the first two are close enough to be considered the same + // in which case we only keep one of them + outViews.insert(outViews.end(), _views.begin() + 1, _views.end()); + } else { + outViews = _views; + } } } else { outViews = _views; @@ -177,7 +189,6 @@ void ControlViews::regulateViews(workload::Views& outViews, const workload::Timi outView.regionBackFronts[workload::Region::R1] = regionBackFronts[workload::Region::R1]; outView.regionBackFronts[workload::Region::R2] = regionBackFronts[workload::Region::R2]; outView.regionBackFronts[workload::Region::R3] = regionBackFronts[workload::Region::R3]; - workload::View::updateRegionsFromBackFronts(outView); } } diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 511984c657..402b05f39c 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -85,6 +85,11 @@ private: if (!OVR_SUCCESS(ovr_Create(&session, &luid))) { qCWarning(oculusLog) << "Failed to acquire Oculus session" << ovr::getError(); return; + } else { + ovrResult setFloorLevelOrigin = ovr_SetTrackingOriginType(session, ovrTrackingOrigin::ovrTrackingOrigin_FloorLevel); + if (!OVR_SUCCESS(setFloorLevelOrigin)) { + qCWarning(oculusLog) << "Failed to set the Oculus tracking origin to floor level" << ovr::getError(); + } } } diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index ef0ac65c2a..99c861871d 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -463,7 +463,7 @@ bool OpenVrDisplayPlugin::internalActivate() { auto chaperone = vr::VRChaperone(); if (chaperone) { float const UI_RADIUS = 1.0f; - float const UI_HEIGHT = 1.6f; + float const UI_HEIGHT = 0.0f; float const UI_Z_OFFSET = 0.5; float xSize, zSize; diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 3e26f304f8..69797340dd 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -129,6 +129,28 @@ static glm::mat4 calculateResetMat() { return glm::mat4(); } +static QString outOfRangeDataStrategyToString(ViveControllerManager::OutOfRangeDataStrategy strategy) { + switch (strategy) { + default: + case ViveControllerManager::OutOfRangeDataStrategy::None: + return "None"; + case ViveControllerManager::OutOfRangeDataStrategy::Freeze: + return "Freeze"; + case ViveControllerManager::OutOfRangeDataStrategy::Drop: + return "Drop"; + } +} + +static ViveControllerManager::OutOfRangeDataStrategy stringToOutOfRangeDataStrategy(const QString& string) { + if (string == "Drop") { + return ViveControllerManager::OutOfRangeDataStrategy::Drop; + } else if (string == "Freeze") { + return ViveControllerManager::OutOfRangeDataStrategy::Freeze; + } else { + return ViveControllerManager::OutOfRangeDataStrategy::None; + } +} + bool ViveControllerManager::isDesktopMode() { if (_container) { return !_container->getActiveDisplayPlugin()->isHmd(); @@ -288,8 +310,10 @@ void ViveControllerManager::loadSettings() { if (_inputDevice) { const double DEFAULT_ARM_CIRCUMFERENCE = 0.33; const double DEFAULT_SHOULDER_WIDTH = 0.48; + const QString DEFAULT_OUT_OF_RANGE_STRATEGY = "Drop"; _inputDevice->_armCircumference = settings.value("armCircumference", QVariant(DEFAULT_ARM_CIRCUMFERENCE)).toDouble(); _inputDevice->_shoulderWidth = settings.value("shoulderWidth", QVariant(DEFAULT_SHOULDER_WIDTH)).toDouble(); + _inputDevice->_outOfRangeDataStrategy = stringToOutOfRangeDataStrategy(settings.value("outOfRangeDataStrategy", QVariant(DEFAULT_OUT_OF_RANGE_STRATEGY)).toString()); } } settings.endGroup(); @@ -303,6 +327,7 @@ void ViveControllerManager::saveSettings() const { if (_inputDevice) { settings.setValue(QString("armCircumference"), _inputDevice->_armCircumference); settings.setValue(QString("shoulderWidth"), _inputDevice->_shoulderWidth); + settings.setValue(QString("outOfRangeDataStrategy"), outOfRangeDataStrategyToString(_inputDevice->_outOfRangeDataStrategy)); } } settings.endGroup(); @@ -446,6 +471,8 @@ void ViveControllerManager::InputDevice::configureCalibrationSettings(const QJso hmdDesktopTracking = iter.value().toBool(); } else if (iter.key() == "desktopMode") { hmdDesktopMode = iter.value().toBool(); + } else if (iter.key() == "outOfRangeDataStrategy") { + _outOfRangeDataStrategy = stringToOutOfRangeDataStrategy(iter.value().toString()); } iter++; } @@ -468,6 +495,7 @@ QJsonObject ViveControllerManager::InputDevice::configurationSettings() { configurationSettings["puckCount"] = (int)_validTrackedObjects.size(); configurationSettings["armCircumference"] = (double)_armCircumference * M_TO_CM; configurationSettings["shoulderWidth"] = (double)_shoulderWidth * M_TO_CM; + configurationSettings["outOfRangeDataStrategy"] = outOfRangeDataStrategyToString(_outOfRangeDataStrategy); return configurationSettings; } @@ -484,6 +512,10 @@ void ViveControllerManager::InputDevice::emitCalibrationStatus() { emit inputConfiguration->calibrationStatus(status); } +static controller::Pose buildPose(const glm::mat4& mat, const glm::vec3& linearVelocity, const glm::vec3& angularVelocity) { + return controller::Pose(extractTranslation(mat), glmExtractRotation(mat), linearVelocity, angularVelocity); +} + void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData) { uint32_t poseIndex = controller::TRACKED_OBJECT_00 + deviceIndex; printDeviceTrackingResultChange(deviceIndex); @@ -492,35 +524,48 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde _nextSimPoseData.vrPoses[deviceIndex].bPoseIsValid && poseIndex <= controller::TRACKED_OBJECT_15) { - mat4& mat = mat4(); - vec3 linearVelocity = vec3(); - vec3 angularVelocity = vec3(); - // check if the device is tracking out of range, then process the correct pose depending on the result. - if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult != vr::TrackingResult_Running_OutOfRange) { - mat = _nextSimPoseData.poses[deviceIndex]; - linearVelocity = _nextSimPoseData.linearVelocities[deviceIndex]; - angularVelocity = _nextSimPoseData.angularVelocities[deviceIndex]; - } else { - mat = _lastSimPoseData.poses[deviceIndex]; - linearVelocity = _lastSimPoseData.linearVelocities[deviceIndex]; - angularVelocity = _lastSimPoseData.angularVelocities[deviceIndex]; - - // make sure that we do not overwrite the pose in the _lastSimPose with incorrect data. - _nextSimPoseData.poses[deviceIndex] = _lastSimPoseData.poses[deviceIndex]; - _nextSimPoseData.linearVelocities[deviceIndex] = _lastSimPoseData.linearVelocities[deviceIndex]; - _nextSimPoseData.angularVelocities[deviceIndex] = _lastSimPoseData.angularVelocities[deviceIndex]; + controller::Pose pose; + switch (_outOfRangeDataStrategy) { + case OutOfRangeDataStrategy::Drop: + default: + // Drop - Mark all non Running_OK results as invald + if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult == vr::TrackingResult_Running_OK) { + pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]); + } else { + pose.valid = false; + } + break; + case OutOfRangeDataStrategy::None: + // None - Ignore eTrackingResult all together + pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]); + break; + case OutOfRangeDataStrategy::Freeze: + // Freeze - Dont invalide non Running_OK poses, instead just return the last good pose. + if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult == vr::TrackingResult_Running_OK) { + pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]); + } else { + pose = buildPose(_lastSimPoseData.poses[deviceIndex], _lastSimPoseData.linearVelocities[deviceIndex], _lastSimPoseData.angularVelocities[deviceIndex]); + // make sure that we do not overwrite the pose in the _lastSimPose with incorrect data. + _nextSimPoseData.poses[deviceIndex] = _lastSimPoseData.poses[deviceIndex]; + _nextSimPoseData.linearVelocities[deviceIndex] = _lastSimPoseData.linearVelocities[deviceIndex]; + _nextSimPoseData.angularVelocities[deviceIndex] = _lastSimPoseData.angularVelocities[deviceIndex]; + } + break; } - controller::Pose pose(extractTranslation(mat), glmExtractRotation(mat), linearVelocity, angularVelocity); + if (pose.valid) { + // transform into avatar frame + glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; + _poseStateMap[poseIndex] = pose.transform(controllerToAvatar); - // transform into avatar frame - glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; - _poseStateMap[poseIndex] = pose.transform(controllerToAvatar); - - // but _validTrackedObjects remain in sensor frame - _validTrackedObjects.push_back(std::make_pair(poseIndex, pose)); - _trackedControllers++; + // but _validTrackedObjects remain in sensor frame + _validTrackedObjects.push_back(std::make_pair(poseIndex, pose)); + _trackedControllers++; + } else { + // insert invalid pose into state map + _poseStateMap[poseIndex] = pose; + } } else { controller::Pose invalidPose; _poseStateMap[poseIndex] = invalidPose; diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 30f8590062..06e13e1c49 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -60,11 +60,18 @@ public: virtual void saveSettings() const override; virtual void loadSettings() override; + enum class OutOfRangeDataStrategy { + None, + Freeze, + Drop + }; + private: class InputDevice : public controller::InputDevice { public: InputDevice(vr::IVRSystem*& system); bool isHeadControllerMounted() const { return _overrideHead; } + private: // Device functions controller::Input::NamedVector getAvailableInputs() const override; @@ -162,6 +169,7 @@ private: FilteredStick _filteredLeftStick; FilteredStick _filteredRightStick; std::string _headsetName {""}; + OutOfRangeDataStrategy _outOfRangeDataStrategy { OutOfRangeDataStrategy::Drop }; std::vector<PuckPosePair> _validTrackedObjects; std::map<uint32_t, glm::mat4> _pucksPostOffset; diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 9efb040624..5df1b3e511 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -45,7 +45,7 @@ if (Window.interstitialModeEnabled) { } // add a menu item for debugging -var MENU_CATEGORY = "Developer"; +var MENU_CATEGORY = "Developer > Scripting"; var MENU_ITEM = "Debug defaultScripts.js"; var SETTINGS_KEY = '_debugDefaultScriptsIsChecked'; diff --git a/scripts/developer/accelerationFilterApp.js b/scripts/developer/accelerationFilterApp.js new file mode 100644 index 0000000000..a2ef937e52 --- /dev/null +++ b/scripts/developer/accelerationFilterApp.js @@ -0,0 +1,222 @@ +var LEFT_HAND_INDEX = 0; +var RIGHT_HAND_INDEX = 1; +var LEFT_FOOT_INDEX = 2; +var RIGHT_FOOT_INDEX = 3; +var HIPS_INDEX = 4; +var SPINE2_INDEX = 5; + +var mappingJson = { + name: "com.highfidelity.testing.accelerationTest", + channels: [ + { + from: "Standard.LeftHand", + to: "Actions.LeftHand", + filters: [ + { + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + translationAccelerationLimit: 100.0, + } + ] + }, + { + from: "Standard.RightHand", + to: "Actions.RightHand", + filters: [ + { + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + translationAccelerationLimit: 100.0, + } + ] + }, + { + from: "Standard.LeftFoot", + to: "Actions.LeftFoot", + filters: [ + { + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + translationAccelerationLimit: 100.0, + } + ] + }, + { + from: "Standard.RightFoot", + to: "Actions.RightFoot", + filters: [ + { + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + translationAccelerationLimit: 100.0, + } + ] + }, + { + from: "Standard.Hips", + to: "Actions.Hips", + filters: [ + { + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + translationAccelerationLimit: 100.0, + } + ] + }, + { + from: "Standard.Spine2", + to: "Actions.Spine2", + filters: [ + { + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + translationAccelerationLimit: 100.0, + } + ] + } + ] +}; + +// +// tablet app boiler plate +// + +var TABLET_BUTTON_NAME = "ACCFILT"; +var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/accelerationFilterApp.html?2"; + +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var tabletButton = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-i.svg", + activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-a.svg" +}); + +tabletButton.clicked.connect(function () { + if (shown) { + tablet.gotoHomeScreen(); + } else { + tablet.gotoWebScreen(HTML_URL); + } +}); + +var shown = false; + +function onScreenChanged(type, url) { + if (type === "Web" && url === HTML_URL) { + tabletButton.editProperties({isActive: true}); + if (!shown) { + // hook up to event bridge + tablet.webEventReceived.connect(onWebEventReceived); + shownChanged(true); + } + shown = true; + } else { + tabletButton.editProperties({isActive: false}); + if (shown) { + // disconnect from event bridge + tablet.webEventReceived.disconnect(onWebEventReceived); + shownChanged(false); + } + shown = false; + } +} + +function getTranslationAccelerationLimit(i) { + return mappingJson.channels[i].filters[0].translationAccelerationLimit; +} +function setTranslationAccelerationLimit(i, value) { + mappingJson.channels[i].filters[0].translationAccelerationLimit = value; + mappingChanged(); +} +function getRotationAccelerationLimit(i) { + return mappingJson.channels[i].filters[0].rotationAccelerationLimit; +} +function setRotationAccelerationLimit(i, value) { + mappingJson.channels[i].filters[0].rotationAccelerationLimit = value; mappingChanged(); +} + +function onWebEventReceived(msg) { + if (msg.name === "init-complete") { + var values = [ + {name: "left-hand-translation-acceleration-limit", val: getTranslationAccelerationLimit(LEFT_HAND_INDEX), checked: false}, + {name: "left-hand-rotation-acceleration-limit", val: getRotationAccelerationLimit(LEFT_HAND_INDEX), checked: false}, + {name: "right-hand-translation-acceleration-limit", val: getTranslationAccelerationLimit(RIGHT_HAND_INDEX), checked: false}, + {name: "right-hand-rotation-acceleration-limit", val: getRotationAccelerationLimit(RIGHT_HAND_INDEX), checked: false}, + {name: "left-foot-translation-acceleration-limit", val: getTranslationAccelerationLimit(LEFT_FOOT_INDEX), checked: false}, + {name: "left-foot-rotation-acceleration-limit", val: getRotationAccelerationLimit(LEFT_FOOT_INDEX), checked: false}, + {name: "right-foot-translation-acceleration-limit", val: getTranslationAccelerationLimit(RIGHT_FOOT_INDEX), checked: false}, + {name: "right-foot-rotation-acceleration-limit", val: getRotationAccelerationLimit(RIGHT_FOOT_INDEX), checked: false}, + {name: "hips-translation-acceleration-limit", val: getTranslationAccelerationLimit(HIPS_INDEX), checked: false}, + {name: "hips-rotation-acceleration-limit", val: getRotationAccelerationLimit(HIPS_INDEX), checked: false}, + {name: "spine2-translation-acceleration-limit", val: getTranslationAccelerationLimit(SPINE2_INDEX), checked: false}, + {name: "spine2-rotation-acceleration-limit", val: getRotationAccelerationLimit(SPINE2_INDEX), checked: false} + ]; + tablet.emitScriptEvent(JSON.stringify(values)); + } else if (msg.name === "left-hand-translation-acceleration-limit") { + setTranslationAccelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "left-hand-rotation-acceleration-limit") { + setRotationAccelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "right-hand-translation-acceleration-limit") { + setTranslationAccelerationLimit(RIGHT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "right-hand-rotation-acceleration-limit") { + setRotationAccelerationLimit(RIGHT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "left-foot-translation-acceleration-limit") { + setTranslationAccelerationLimit(LEFT_FOOT_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "left-foot-rotation-acceleration-limit") { + setRotationAccelerationLimit(LEFT_FOOT_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "right-foot-translation-acceleration-limit") { + setTranslationAccelerationLimit(RIGHT_FOOT_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "right-foot-rotation-acceleration-limit") { + setRotationAccelerationLimit(RIGHT_FOOT_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "hips-translation-acceleration-limit") { + setTranslationAccelerationLimit(HIPS_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "hips-rotation-acceleration-limit") { + setRotationAccelerationLimit(HIPS_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "spine2-translation-acceleration-limit") { + setTranslationAccelerationLimit(SPINE2_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "spine2-rotation-acceleration-limit") { + setRotationAccelerationLimit(SPINE2_INDEX, parseInt(msg.val, 10)); + } +} + +tablet.screenChanged.connect(onScreenChanged); + +function shutdownTabletApp() { + tablet.removeButton(tabletButton); + if (shown) { + tablet.webEventReceived.disconnect(onWebEventReceived); + tablet.gotoHomeScreen(); + } + tablet.screenChanged.disconnect(onScreenChanged); +} + +// +// end tablet app boiler plate +// + +var mapping; +function mappingChanged() { + if (mapping) { + mapping.disable(); + } + mapping = Controller.parseMapping(JSON.stringify(mappingJson)); + mapping.enable(); +} + +function shownChanged(newShown) { + if (newShown) { + mappingChanged(); + } else { + mapping.disable(); + } +} + +mappingChanged(); + +Script.scriptEnding.connect(function() { + if (mapping) { + mapping.disable(); + } + tablet.removeButton(tabletButton); +}); + diff --git a/scripts/developer/debugging/debugWindow.js b/scripts/developer/debugging/debugWindow.js index 993ca49a40..84bd3c323c 100644 --- a/scripts/developer/debugging/debugWindow.js +++ b/scripts/developer/debugging/debugWindow.js @@ -19,6 +19,23 @@ if (scripts.length >= 2) { return; } +var SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME = "Developer" +var SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME = "Suppress messages from default scripts in Debug Window"; +var DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS = 'debugWindowSuppressDefaultScripts'; +var suppressDefaultScripts = Settings.getValue(DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS, false) +Menu.addMenuItem({ + menuName: SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME, + menuItemName: SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME, + isCheckable: true, + isChecked: suppressDefaultScripts +}); + +Menu.menuItemEvent.connect(function(menuItem) { + if (menuItem === SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME) { + suppressDefaultScripts = Menu.isOptionChecked(SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME); + } +}); + // Set up the qml ui var qml = Script.resolvePath('debugWindow.qml'); @@ -61,17 +78,24 @@ window.visibleChanged.connect(function() { window.closed.connect(function () { Script.stop(); }); +function shouldLogMessage(scriptFileName) { + return !suppressDefaultScripts + || (scriptFileName !== "defaultScripts.js" && scriptFileName != "controllerScripts.js"); +} + var getFormattedDate = function() { var date = new Date(); return date.getMonth() + "/" + date.getDate() + " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds(); }; var sendToLogWindow = function(type, message, scriptFileName) { - var typeFormatted = ""; - if (type) { - typeFormatted = type + " - "; + if (shouldLogMessage(scriptFileName)) { + var typeFormatted = ""; + if (type) { + typeFormatted = type + " - "; + } + window.sendToQml("[" + getFormattedDate() + "] " + "[" + scriptFileName + "] " + typeFormatted + message); } - window.sendToQml("[" + getFormattedDate() + "] " + "[" + scriptFileName + "] " + typeFormatted + message); }; ScriptDiscoveryService.printedMessage.connect(function(message, scriptFileName) { @@ -95,6 +119,10 @@ ScriptDiscoveryService.clearDebugWindow.connect(function() { }); Script.scriptEnding.connect(function () { + Settings.setValue(DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS, + Menu.isOptionChecked(SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME)); + Menu.removeMenuItem(SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME, SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME); + var geometry = JSON.stringify({ x: window.position.x, y: window.position.y, diff --git a/scripts/developer/exponentialFilterApp.js b/scripts/developer/exponentialFilterApp.js new file mode 100644 index 0000000000..774ea95533 --- /dev/null +++ b/scripts/developer/exponentialFilterApp.js @@ -0,0 +1,240 @@ +var LEFT_HAND_INDEX = 0; +var RIGHT_HAND_INDEX = 1; +var LEFT_FOOT_INDEX = 2; +var RIGHT_FOOT_INDEX = 3; +var HIPS_INDEX = 4; +var SPINE2_INDEX = 5; + +var HAND_SMOOTHING_TRANSLATION = 0.3; +var HAND_SMOOTHING_ROTATION = 0.15; +var FOOT_SMOOTHING_TRANSLATION = 0.3; +var FOOT_SMOOTHING_ROTATION = 0.15; +var TORSO_SMOOTHING_TRANSLATION = 0.3; +var TORSO_SMOOTHING_ROTATION = 0.16; + +var mappingJson = { + name: "com.highfidelity.testing.exponentialFilterApp", + channels: [ + { + from: "Standard.LeftHand", + to: "Actions.LeftHand", + filters: [ + { + type: "exponentialSmoothing", + translation: HAND_SMOOTHING_TRANSLATION, + rotation: HAND_SMOOTHING_ROTATION + } + ] + }, + { + from: "Standard.RightHand", + to: "Actions.RightHand", + filters: [ + { + type: "exponentialSmoothing", + translation: HAND_SMOOTHING_TRANSLATION, + rotation: HAND_SMOOTHING_ROTATION + } + ] + }, + { + from: "Standard.LeftFoot", + to: "Actions.LeftFoot", + filters: [ + { + type: "exponentialSmoothing", + translation: FOOT_SMOOTHING_TRANSLATION, + rotation: FOOT_SMOOTHING_ROTATION + } + ] + }, + { + from: "Standard.RightFoot", + to: "Actions.RightFoot", + filters: [ + { + type: "exponentialSmoothing", + translation: FOOT_SMOOTHING_TRANSLATION, + rotation: FOOT_SMOOTHING_ROTATION + } + ] + }, + { + from: "Standard.Hips", + to: "Actions.Hips", + filters: [ + { + type: "exponentialSmoothing", + translation: TORSO_SMOOTHING_TRANSLATION, + rotation: TORSO_SMOOTHING_ROTATION + } + ] + }, + { + from: "Standard.Spine2", + to: "Actions.Spine2", + filters: [ + { + type: "exponentialSmoothing", + translation: TORSO_SMOOTHING_TRANSLATION, + rotation: TORSO_SMOOTHING_ROTATION + } + ] + } + ] +}; + +// +// tablet app boiler plate +// + +var TABLET_BUTTON_NAME = "EXPFILT"; +var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/exponentialFilterApp.html?7"; + +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var tabletButton = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-i.svg", + activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-a.svg" +}); + +tabletButton.clicked.connect(function () { + if (shown) { + tablet.gotoHomeScreen(); + } else { + tablet.gotoWebScreen(HTML_URL); + } +}); + +var shown = false; + +function onScreenChanged(type, url) { + if (type === "Web" && url === HTML_URL) { + tabletButton.editProperties({isActive: true}); + if (!shown) { + // hook up to event bridge + tablet.webEventReceived.connect(onWebEventReceived); + shownChanged(true); + } + shown = true; + } else { + tabletButton.editProperties({isActive: false}); + if (shown) { + // disconnect from event bridge + tablet.webEventReceived.disconnect(onWebEventReceived); + shownChanged(false); + } + shown = false; + } +} + +function getTranslation(i) { + return mappingJson.channels[i].filters[0].translation; +} +function setTranslation(i, value) { + mappingJson.channels[i].filters[0].translation = value; + mappingChanged(); +} +function getRotation(i) { + return mappingJson.channels[i].filters[0].rotation; +} +function setRotation(i, value) { + mappingJson.channels[i].filters[0].rotation = value; mappingChanged(); +} + +function onWebEventReceived(msg) { + if (msg.name === "init-complete") { + var values = [ + {name: "enable-filtering", val: filterEnabled ? "on" : "off", checked: false}, + {name: "left-hand-translation", val: getTranslation(LEFT_HAND_INDEX), checked: false}, + {name: "left-hand-rotation", val: getRotation(LEFT_HAND_INDEX), checked: false}, + {name: "right-hand-translation", val: getTranslation(RIGHT_HAND_INDEX), checked: false}, + {name: "right-hand-rotation", val: getRotation(RIGHT_HAND_INDEX), checked: false}, + {name: "left-foot-translation", val: getTranslation(LEFT_FOOT_INDEX), checked: false}, + {name: "left-foot-rotation", val: getRotation(LEFT_FOOT_INDEX), checked: false}, + {name: "right-foot-translation", val: getTranslation(RIGHT_FOOT_INDEX), checked: false}, + {name: "right-foot-rotation", val: getRotation(RIGHT_FOOT_INDEX), checked: false}, + {name: "hips-translation", val: getTranslation(HIPS_INDEX), checked: false}, + {name: "hips-rotation", val: getRotation(HIPS_INDEX), checked: false}, + {name: "spine2-translation", val: getTranslation(SPINE2_INDEX), checked: false}, + {name: "spine2-rotation", val: getRotation(SPINE2_INDEX), checked: false} + ]; + tablet.emitScriptEvent(JSON.stringify(values)); + } else if (msg.name === "enable-filtering") { + if (msg.val === "on") { + filterEnabled = true; + } else if (msg.val === "off") { + filterEnabled = false; + } + mappingChanged(); + } else if (msg.name === "left-hand-translation") { + setTranslation(LEFT_HAND_INDEX, Number(msg.val)); + } else if (msg.name === "left-hand-rotation") { + setRotation(LEFT_HAND_INDEX, Number(msg.val)); + } else if (msg.name === "right-hand-translation") { + setTranslation(RIGHT_HAND_INDEX, Number(msg.val)); + } else if (msg.name === "right-hand-rotation") { + setRotation(RIGHT_HAND_INDEX, Number(msg.val)); + } else if (msg.name === "left-foot-translation") { + setTranslation(LEFT_FOOT_INDEX, Number(msg.val)); + } else if (msg.name === "left-foot-rotation") { + setRotation(LEFT_FOOT_INDEX, Number(msg.val)); + } else if (msg.name === "right-foot-translation") { + setTranslation(RIGHT_FOOT_INDEX, Number(msg.val)); + } else if (msg.name === "right-foot-rotation") { + setRotation(RIGHT_FOOT_INDEX, Number(msg.val)); + } else if (msg.name === "hips-translation") { + setTranslation(HIPS_INDEX, Number(msg.val)); + } else if (msg.name === "hips-rotation") { + setRotation(HIPS_INDEX, Number(msg.val)); + } else if (msg.name === "spine2-translation") { + setTranslation(SPINE2_INDEX, Number(msg.val)); + } else if (msg.name === "spine2-rotation") { + setRotation(SPINE2_INDEX, Number(msg.val)); + } +} + +tablet.screenChanged.connect(onScreenChanged); + +function shutdownTabletApp() { + tablet.removeButton(tabletButton); + if (shown) { + tablet.webEventReceived.disconnect(onWebEventReceived); + tablet.gotoHomeScreen(); + } + tablet.screenChanged.disconnect(onScreenChanged); +} + +// +// end tablet app boiler plate +// + +var filterEnabled = true; +var mapping; +function mappingChanged() { + if (mapping) { + mapping.disable(); + } + if (filterEnabled) { + mapping = Controller.parseMapping(JSON.stringify(mappingJson)); + mapping.enable(); + } +} + +function shownChanged(newShown) { + if (newShown) { + mappingChanged(); + } else { + mapping.disable(); + } +} + +mappingChanged(); + +Script.scriptEnding.connect(function() { + if (mapping) { + mapping.disable(); + } + tablet.removeButton(tabletButton); +}); + diff --git a/scripts/developer/tests/ControlsGallery.qml b/scripts/developer/tests/ControlsGallery.qml index ceb8a26dc9..9685fa6fe8 100644 --- a/scripts/developer/tests/ControlsGallery.qml +++ b/scripts/developer/tests/ControlsGallery.qml @@ -2,16 +2,9 @@ import QtQuick 2.10 import QtQuick.Window 2.10 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 -import "qrc:////qml//styles-uit" as HifiStylesUit -import "qrc:////qml//controls-uit" as HifiControlsUit -//uncomment to use from qmlscratch tool -//import '../../../interface/resources/qml/controls-uit' as HifiControlsUit -//import '../../../interface/resources/qml/styles-uit' - -//uncomment to use with HIFI_USE_SOURCE_TREE_RESOURCES=1 -//import '../../../resources/qml/controls-uit' as HifiControlsUit -//import '../../../resources/qml/styles-uit' +import stylesUit 1.0 as HifiStylesUit +import controlsUit 1.0 as HifiControlsUit Item { visible: true diff --git a/scripts/developer/tests/viveTrackedObjects.js b/scripts/developer/tests/drawTrackedObjects.js similarity index 62% rename from scripts/developer/tests/viveTrackedObjects.js rename to scripts/developer/tests/drawTrackedObjects.js index 1d60f658d9..c7d886c319 100644 --- a/scripts/developer/tests/viveTrackedObjects.js +++ b/scripts/developer/tests/drawTrackedObjects.js @@ -21,16 +21,14 @@ function shutdown() { var BLUE = {x: 0, y: 0, z: 1, w: 1}; function update(dt) { - if (Controller.Hardware.Vive) { - TRACKED_OBJECT_POSES.forEach(function (key) { - var pose = Controller.getPoseValue(Controller.Standard[key]); - if (pose.valid) { - DebugDraw.addMyAvatarMarker(key, pose.rotation, pose.translation, BLUE); - } else { - DebugDraw.removeMyAvatarMarker(key); - } - }); - } + TRACKED_OBJECT_POSES.forEach(function (key) { + var pose = Controller.getPoseValue(Controller.Standard[key]); + if (pose.valid) { + DebugDraw.addMyAvatarMarker(key, pose.rotation, pose.translation, BLUE); + } else { + DebugDraw.removeMyAvatarMarker(key); + } + }); } init(); diff --git a/scripts/developer/tests/filtered-puck-attach.js b/scripts/developer/tests/filtered-puck-attach.js new file mode 100644 index 0000000000..ad9b17a0e4 --- /dev/null +++ b/scripts/developer/tests/filtered-puck-attach.js @@ -0,0 +1,438 @@ +// +// Created by Anthony J. Thibault on 2017/06/20 +// Modified by Robbie Uvanni to support multiple pucks and easier placement of pucks on entities, on 2017/08/01 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// When this script is running, a new app button, named "PUCKTACH", will be added to the toolbar/tablet. +// Click this app to bring up the puck attachment panel. +// + +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +/* global Xform */ +Script.include("/~/system/libraries/Xform.js"); + +(function() { // BEGIN LOCAL_SCOPE + +var TABLET_BUTTON_NAME = "PUCKATTACH"; +var TABLET_APP_URL = "https://s3.amazonaws.com/hifi-public/tony/html/filtered-puck-attach.html?2"; +var NUM_TRACKED_OBJECTS = 16; + +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var tabletButton = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: "https://s3.amazonaws.com/hifi-public/tony/icons/puck-i.svg", + activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/puck-a.svg" +}); + +var shown = false; +function onScreenChanged(type, url) { + if (type === "Web" && url === TABLET_APP_URL) { + tabletButton.editProperties({isActive: true}); + if (!shown) { + // hook up to event bridge + tablet.webEventReceived.connect(onWebEventReceived); + shownChanged(true); + } + shown = true; + } else { + tabletButton.editProperties({isActive: false}); + if (shown) { + // disconnect from event bridge + tablet.webEventReceived.disconnect(onWebEventReceived); + shownChanged(false); + } + shown = false; + } +} +tablet.screenChanged.connect(onScreenChanged); + +function pad(num, size) { + var tempString = "000000000" + num; + return tempString.substr(tempString.length - size); +} +function indexToTrackedObjectName(index) { + return "TrackedObject" + pad(index, 2); +} +function getAvailableTrackedObjects() { + var available = []; + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + var key = indexToTrackedObjectName(i); + var pose = Controller.getPoseValue(Controller.Standard[key]); + if (pose && pose.valid) { + available.push(i); + } + } + return available; +} +function sendAvailableTrackedObjects() { + tablet.emitScriptEvent(JSON.stringify({ + pucks: getAvailableTrackedObjects(), + selectedPuck: ((lastPuck === undefined) ? -1 : lastPuck.name) + })); +} + +function getRelativePosition(origin, rotation, offset) { + var relativeOffset = Vec3.multiplyQbyV(rotation, offset); + var worldPosition = Vec3.sum(origin, relativeOffset); + return worldPosition; +} +function getPropertyForEntity(entityID, propertyName) { + return Entities.getEntityProperties(entityID, [propertyName])[propertyName]; +} +function entityExists(entityID) { + return Object.keys(Entities.getEntityProperties(entityID)).length > 0; +} + +var VIVE_PUCK_MODEL = "https://s3.amazonaws.com/hifi-public/tony/vive_tracker_puck_y180z180.obj"; +var VIVE_PUCK_DIMENSIONS = { x: 0.0945, y: 0.0921, z: 0.0423 }; // 1/1000th scale of model +var VIVE_PUCK_SEARCH_DISTANCE = 1.5; // metres +var VIVE_PUCK_SPAWN_DISTANCE = 0.5; // metres +var VIVE_PUCK_TRACKED_OBJECT_MAX_DISTANCE = 10.0; // metres +var VIVE_PUCK_NAME = "Tracked Puck"; + +var trackedPucks = { }; +var lastPuck; + +var DEFAULT_ROTATION_SMOOTHING_CONSTANT = 1.0; // no smoothing +var DEFAULT_TRANSLATION_SMOOTHING_CONSTANT = 1.0; // no smoothing +var DEFAULT_TRANSLATION_ACCELERATION_LIMIT = 1000; // only extreme accelerations are smoothed +var DEFAULT_ROTATION_ACCELERATION_LIMIT = 10000; // only extreme accelerations are smoothed +var DEFAULT_TRANSLATION_SNAP_THRESHOLD = 0; // no snapping +var DEFAULT_ROTATION_SNAP_THRESHOLD = 0; // no snapping + +function buildMappingJson() { + var obj = {name: "com.highfidelity.testing.filteredPuckAttach", channels: []}; + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + obj.channels.push({ + from: "Vive." + indexToTrackedObjectName(i), + to: "Standard." + indexToTrackedObjectName(i), + filters: [ + { + type: "accelerationLimiter", + translationAccelerationLimit: DEFAULT_TRANSLATION_ACCELERATION_LIMIT, + rotationAccelerationLimit: DEFAULT_ROTATION_ACCELERATION_LIMIT, + translationSnapThreshold: DEFAULT_TRANSLATION_SNAP_THRESHOLD, + rotationSnapThreshold: DEFAULT_ROTATION_SNAP_THRESHOLD, + }, + { + type: "exponentialSmoothing", + translation: DEFAULT_TRANSLATION_SMOOTHING_CONSTANT, + rotation: DEFAULT_ROTATION_SMOOTHING_CONSTANT + } + ] + }); + } + return obj; +} + +var mappingJson = buildMappingJson(); + +var mapping; +function mappingChanged() { + if (mapping) { + mapping.disable(); + } + mapping = Controller.parseMapping(JSON.stringify(mappingJson)); + mapping.enable(); +} + +function shownChanged(newShown) { + if (newShown) { + mappingChanged(); + } else { + mapping.disable(); + } +} + +function setTranslationAccelerationLimit(value) { + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[0].translationAccelerationLimit = value; + } + mappingChanged(); +} + +function setTranslationSnapThreshold(value) { + // convert from mm + var MM_PER_M = 1000; + var meters = value / MM_PER_M; + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[0].translationSnapThreshold = meters; + } + mappingChanged(); +} + +function setRotationAccelerationLimit(value) { + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[0].rotationAccelerationLimit = value; + } + mappingChanged(); +} + +function setRotationSnapThreshold(value) { + // convert from degrees + var PI_IN_DEGREES = 180; + var radians = value * (Math.pi / PI_IN_DEGREES); + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[0].translationSnapThreshold = radians; + } + mappingChanged(); +} + +function setTranslationSmoothingConstant(value) { + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[1].translation = value; + } + mappingChanged(); +} + +function setRotationSmoothingConstant(value) { + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[1].rotation = value; + } + mappingChanged(); +} + + +function createPuck(puck) { + // create a puck entity and add it to our list of pucks + var action = indexToTrackedObjectName(puck.puckno); + var pose = Controller.getPoseValue(Controller.Standard[action]); + + if (pose && pose.valid) { + var spawnOffset = Vec3.multiply(Vec3.FRONT, VIVE_PUCK_SPAWN_DISTANCE); + var spawnPosition = getRelativePosition(MyAvatar.position, MyAvatar.orientation, spawnOffset); + + // should be an overlay + var puckEntityProperties = { + name: "Tracked Puck", + type: "Model", + modelURL: VIVE_PUCK_MODEL, + dimensions: VIVE_PUCK_DIMENSIONS, + position: spawnPosition, + userData: '{ "grabbableKey": { "grabbable": true, "kinematic": false } }' + }; + + var puckEntityID = Entities.addEntity(puckEntityProperties); + + // if we've already created this puck, destroy it + if (trackedPucks.hasOwnProperty(puck.puckno)) { + destroyPuck(puck.puckno); + } + // if we had an unfinalized puck, destroy it + if (lastPuck !== undefined) { + destroyPuck(lastPuck.name); + } + // create our new unfinalized puck + trackedPucks[puck.puckno] = { + puckEntityID: puckEntityID, + trackedEntityID: "" + }; + lastPuck = trackedPucks[puck.puckno]; + lastPuck.name = Number(puck.puckno); + } +} +function finalizePuck(puckName) { + // find nearest entity and change its parent to the puck + + if (!trackedPucks.hasOwnProperty(puckName)) { + print('2'); + return; + } + if (lastPuck === undefined) { + print('3'); + return; + } + if (lastPuck.name !== Number(puckName)) { + print('1'); + return; + } + + var puckPosition = getPropertyForEntity(lastPuck.puckEntityID, "position"); + var foundEntities = Entities.findEntities(puckPosition, VIVE_PUCK_SEARCH_DISTANCE); + + var foundEntity; + var leastDistance = Number.MAX_VALUE; + + for (var i = 0; i < foundEntities.length; i++) { + var entity = foundEntities[i]; + + if (getPropertyForEntity(entity, "name") !== VIVE_PUCK_NAME) { + var entityPosition = getPropertyForEntity(entity, "position"); + var d = Vec3.distance(entityPosition, puckPosition); + + if (d < leastDistance) { + leastDistance = d; + foundEntity = entity; + } + } + } + + if (foundEntity) { + lastPuck.trackedEntityID = foundEntity; + // remember the userdata and collisionless flag for the tracked entity since + // we're about to remove it and make it ungrabbable and collisionless + lastPuck.trackedEntityUserData = getPropertyForEntity(foundEntity, "userData"); + lastPuck.trackedEntityCollisionFlag = getPropertyForEntity(foundEntity, "collisionless"); + // update properties of the tracked entity + Entities.editEntity(lastPuck.trackedEntityID, { + "parentID": lastPuck.puckEntityID, + "userData": '{ "grabbableKey": { "grabbable": false } }', + "collisionless": 1 + }); + // remove reference to puck since it is now calibrated and finalized + lastPuck = undefined; + } +} +function updatePucks() { + // for each puck, update its position and orientation + for (var puckName in trackedPucks) { + if (!trackedPucks.hasOwnProperty(puckName)) { + continue; + } + var action = indexToTrackedObjectName(puckName); + var pose = Controller.getPoseValue(Controller.Standard[action]); + if (pose && pose.valid) { + var puck = trackedPucks[puckName]; + if (puck.trackedEntityID) { + if (entityExists(puck.trackedEntityID)) { + var avatarXform = new Xform(MyAvatar.orientation, MyAvatar.position); + var puckXform = new Xform(pose.rotation, pose.translation); + var finalXform = Xform.mul(avatarXform, puckXform); + + var d = Vec3.distance(MyAvatar.position, finalXform.pos); + if (d > VIVE_PUCK_TRACKED_OBJECT_MAX_DISTANCE) { + print('tried to move tracked object too far away: ' + d); + return; + } + + Entities.editEntity(puck.puckEntityID, { + position: finalXform.pos, + rotation: finalXform.rot + }); + + // in case someone grabbed both entities and destroyed the + // child/parent relationship + Entities.editEntity(puck.trackedEntityID, { + parentID: puck.puckEntityID + }); + } else { + destroyPuck(puckName); + } + } + } + } +} +function destroyPuck(puckName) { + // unparent entity and delete its parent + if (!trackedPucks.hasOwnProperty(puckName)) { + return; + } + + var puck = trackedPucks[puckName]; + var puckEntityID = puck.puckEntityID; + var trackedEntityID = puck.trackedEntityID; + + // remove the puck as a parent entity and restore the tracked entities + // former userdata and collision flag + Entities.editEntity(trackedEntityID, { + "parentID": "{00000000-0000-0000-0000-000000000000}", + "userData": puck.trackedEntityUserData, + "collisionless": puck.trackedEntityCollisionFlag + }); + + delete trackedPucks[puckName]; + + // in some cases, the entity deletion may occur before the parent change + // has been processed, resulting in both the puck and the tracked entity + // to be deleted so we wait 100ms before deleting the puck, assuming + // that the parent change has occured + var DELETE_TIMEOUT = 100; // ms + Script.setTimeout(function() { + // delete the puck + Entities.deleteEntity(puckEntityID); + }, DELETE_TIMEOUT); +} +function destroyPucks() { + // remove all pucks and unparent entities + for (var puckName in trackedPucks) { + if (trackedPucks.hasOwnProperty(puckName)) { + destroyPuck(puckName); + } + } +} + +function onWebEventReceived(msg) { + var obj = {}; + + try { + obj = JSON.parse(msg); + } catch (err) { + return; + } + + switch (obj.cmd) { + case "ready": + sendAvailableTrackedObjects(); + break; + case "create": + createPuck(obj); + break; + case "finalize": + finalizePuck(obj.puckno); + break; + case "destroy": + destroyPuck(obj.puckno); + break; + case "translation-acceleration-limit": + setTranslationAccelerationLimit(Number(obj.val)); + break; + case "translation-snap-threshold": + setTranslationSnapThreshold(Number(obj.val)); + break; + case "rotation-acceleration-limit": + setRotationAccelerationLimit(Number(obj.val)); + break; + case "rotation-snap-threshold": + setRotationSnapThreshold(Number(obj.val)); + break; + case "translation-smoothing-constant": + setTranslationSmoothingConstant(Number(obj.val)); + break; + case "rotation-smoothing-constant": + setRotationSmoothingConstant(Number(obj.val)); + break; + } +} + +Script.update.connect(updatePucks); +Script.scriptEnding.connect(function () { + tablet.removeButton(tabletButton); + destroyPucks(); + if (shown) { + tablet.webEventReceived.disconnect(onWebEventReceived); + tablet.gotoHomeScreen(); + } + tablet.screenChanged.disconnect(onScreenChanged); + if (mapping) { + mapping.disable(); + } +}); +tabletButton.clicked.connect(function () { + if (shown) { + tablet.gotoHomeScreen(); + } else { + tablet.gotoWebScreen(TABLET_APP_URL); + } +}); +}()); // END LOCAL_SCOPE diff --git a/scripts/developer/tests/puck-attach.js b/scripts/developer/tests/puck-attach.js index 04d5db5710..019a911535 100644 --- a/scripts/developer/tests/puck-attach.js +++ b/scripts/developer/tests/puck-attach.js @@ -85,7 +85,7 @@ function entityExists(entityID) { return Object.keys(Entities.getEntityProperties(entityID)).length > 0; } -var VIVE_PUCK_MODEL = "http://content.highfidelity.com/seefo/production/puck-attach/vive_tracker_puck.obj"; +var VIVE_PUCK_MODEL = "https://s3.amazonaws.com/hifi-public/tony/vive_tracker_puck_y180z180.obj"; var VIVE_PUCK_DIMENSIONS = { x: 0.0945, y: 0.0921, z: 0.0423 }; // 1/1000th scale of model var VIVE_PUCK_SEARCH_DISTANCE = 1.5; // metres var VIVE_PUCK_SPAWN_DISTANCE = 0.5; // metres @@ -304,4 +304,4 @@ tabletButton.clicked.connect(function () { tablet.gotoWebScreen(TABLET_APP_URL); } }); -}()); // END LOCAL_SCOPE \ No newline at end of file +}()); // END LOCAL_SCOPE diff --git a/scripts/developer/utilities/audio/Stats.qml b/scripts/developer/utilities/audio/Stats.qml index f359e9b04c..e2291e485d 100644 --- a/scripts/developer/utilities/audio/Stats.qml +++ b/scripts/developer/utilities/audio/Stats.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "qrc:////qml//controls-uit" as HifiControls +import controlsUit 1.0 as HifiControls Column { id: stats diff --git a/scripts/developer/utilities/audio/TabletStats.qml b/scripts/developer/utilities/audio/TabletStats.qml index 2f8d212a2a..b50acabec4 100644 --- a/scripts/developer/utilities/audio/TabletStats.qml +++ b/scripts/developer/utilities/audio/TabletStats.qml @@ -11,8 +11,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 - -import "qrc:////qml//styles-uit" +import stylesUit 1.0 Item { id: dialog diff --git a/scripts/developer/utilities/lib/jet/qml/TaskList.qml b/scripts/developer/utilities/lib/jet/qml/TaskList.qml index 5b1aa0afb5..166f604666 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskList.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskList.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 as Original import QtQuick.Controls.Styles 1.4 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../jet.js" as Jet diff --git a/scripts/developer/utilities/lib/jet/qml/TaskListView.qml b/scripts/developer/utilities/lib/jet/qml/TaskListView.qml index 2c75865698..0f083aa72c 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskListView.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskListView.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 as Original import QtQuick.Controls.Styles 1.4 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../jet.js" as Jet diff --git a/scripts/developer/utilities/lib/plotperf/Color.qml b/scripts/developer/utilities/lib/plotperf/Color.qml index 15d7f9fcc9..1ad72fe2e6 100644 --- a/scripts/developer/utilities/lib/plotperf/Color.qml +++ b/scripts/developer/utilities/lib/plotperf/Color.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 as Original import QtQuick.Controls.Styles 1.4 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Item { diff --git a/scripts/developer/utilities/render/antialiasing.qml b/scripts/developer/utilities/render/antialiasing.qml index 1a8f9dac2d..5abfd30935 100644 --- a/scripts/developer/utilities/render/antialiasing.qml +++ b/scripts/developer/utilities/render/antialiasing.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "configSlider" import "../lib/plotperf" diff --git a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml index 41de77fb09..bf9089d82c 100644 --- a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml +++ b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 as Original import QtQuick.Controls.Styles 1.4 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Item { diff --git a/scripts/developer/utilities/render/configSlider/RichSlider.qml b/scripts/developer/utilities/render/configSlider/RichSlider.qml index 01b14f3d48..ff16cb32ad 100644 --- a/scripts/developer/utilities/render/configSlider/RichSlider.qml +++ b/scripts/developer/utilities/render/configSlider/RichSlider.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 as Original import QtQuick.Controls.Styles 1.4 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Item { diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml index a9479b2935..4bc4941358 100644 --- a/scripts/developer/utilities/render/deferredLighting.qml +++ b/scripts/developer/utilities/render/deferredLighting.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "configSlider" import "../lib/jet/qml" as Jet @@ -279,11 +279,27 @@ Rectangle { } } Separator {} - HifiControls.Button { - text: "Engine" - // activeFocusOnPress: false - onClicked: { - sendToScript({method: "openEngineView"}); + Row { + HifiControls.Button { + text: "Engine" + // activeFocusOnPress: false + onClicked: { + sendToScript({method: "openEngineView"}); + } + } + HifiControls.Button { + text: "LOD" + // activeFocusOnPress: false + onClicked: { + sendToScript({method: "openEngineLODView"}); + } + } + HifiControls.Button { + text: "Cull" + // activeFocusOnPress: false + onClicked: { + sendToScript({method: "openCullInspectorView"}); + } } } } diff --git a/scripts/developer/utilities/render/engineInspector.qml b/scripts/developer/utilities/render/engineInspector.qml index 1b9941e64e..16dd8eb985 100644 --- a/scripts/developer/utilities/render/engineInspector.qml +++ b/scripts/developer/utilities/render/engineInspector.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../lib/jet/qml" as Jet diff --git a/scripts/developer/utilities/render/highlight.qml b/scripts/developer/utilities/render/highlight.qml index 88d6a807ae..d8af2a828e 100644 --- a/scripts/developer/utilities/render/highlight.qml +++ b/scripts/developer/utilities/render/highlight.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "configSlider" import "../lib/plotperf" import "highlight" diff --git a/scripts/developer/utilities/render/highlight/HighlightStyle.qml b/scripts/developer/utilities/render/highlight/HighlightStyle.qml index 371b7e81f7..475aadfdce 100644 --- a/scripts/developer/utilities/render/highlight/HighlightStyle.qml +++ b/scripts/developer/utilities/render/highlight/HighlightStyle.qml @@ -12,8 +12,8 @@ import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 import "../configSlider" import "../../lib/plotperf" -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Item { id: root diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index 889d8db836..892b43d8be 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../lib/plotperf" import "configSlider" diff --git a/scripts/developer/utilities/render/luci.js b/scripts/developer/utilities/render/luci.js index cb5b01f9b2..cffeb615c9 100644 --- a/scripts/developer/utilities/render/luci.js +++ b/scripts/developer/utilities/render/luci.js @@ -11,122 +11,131 @@ // (function() { - var TABLET_BUTTON_NAME = "LUCI"; - var QMLAPP_URL = Script.resolvePath("./deferredLighting.qml"); - var ICON_URL = Script.resolvePath("../../../system/assets/images/luci-i.svg"); - var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/luci-a.svg"); - - - var onLuciScreen = false; - - function onClicked() { - if (onLuciScreen) { - tablet.gotoHomeScreen(); - } else { - tablet.loadQMLSource(QMLAPP_URL); - } - } - - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - var button = tablet.addButton({ - text: TABLET_BUTTON_NAME, - icon: ICON_URL, - activeIcon: ACTIVE_ICON_URL - }); - - var hasEventBridge = false; - - function wireEventBridge(on) { - if (!tablet) { - print("Warning in wireEventBridge(): 'tablet' undefined!"); - return; - } - if (on) { - if (!hasEventBridge) { - tablet.fromQml.connect(fromQml); - hasEventBridge = true; - } - } else { - if (hasEventBridge) { - tablet.fromQml.disconnect(fromQml); - hasEventBridge = false; - } - } - } - - function onScreenChanged(type, url) { - if (url === QMLAPP_URL) { - onLuciScreen = true; - } else { - onLuciScreen = false; - } - - button.editProperties({isActive: onLuciScreen}); - wireEventBridge(onLuciScreen); - } - - button.clicked.connect(onClicked); - tablet.screenChanged.connect(onScreenChanged); + var AppUi = Script.require('appUi'); var moveDebugCursor = false; - Controller.mousePressEvent.connect(function (e) { + var onMousePressEvent = function (e) { if (e.isMiddleButton) { moveDebugCursor = true; setDebugCursor(e.x, e.y); } - }); - Controller.mouseReleaseEvent.connect(function() { moveDebugCursor = false; }); - Controller.mouseMoveEvent.connect(function (e) { if (moveDebugCursor) setDebugCursor(e.x, e.y); }); + }; + Controller.mousePressEvent.connect(onMousePressEvent); + var onMouseReleaseEvent = function () { + moveDebugCursor = false; + }; + Controller.mouseReleaseEvent.connect(onMouseReleaseEvent); + var onMouseMoveEvent = function (e) { + if (moveDebugCursor) { + setDebugCursor(e.x, e.y); + } + }; + Controller.mouseMoveEvent.connect(onMouseMoveEvent); function setDebugCursor(x, y) { - nx = 2.0 * (x / Window.innerWidth) - 1.0; - ny = 1.0 - 2.0 * ((y) / (Window.innerHeight)); + var nx = 2.0 * (x / Window.innerWidth) - 1.0; + var ny = 1.0 - 2.0 * ((y) / (Window.innerHeight)); - Render.getConfig("RenderMainView").getConfig("DebugDeferredBuffer").size = { x: nx, y: ny, z: 1.0, w: 1.0 }; + Render.getConfig("RenderMainView").getConfig("DebugDeferredBuffer").size = { x: nx, y: ny, z: 1.0, w: 1.0 }; } + function Page(title, qmlurl, width, height) { + this.title = title; + this.qml = qmlurl; + this.width = width; + this.height = height; + + this.window; + + print("Page: New Page:" + JSON.stringify(this)); + } + + Page.prototype.killView = function () { + print("Page: Kill window for page:" + JSON.stringify(this)); + if (this.window) { + print("Page: Kill window for page:" + this.title); + //this.window.closed.disconnect(function () { + // this.killView(); + //}); + this.window.close(); + this.window = false; + } + }; + + Page.prototype.createView = function () { + var that = this; + if (!this.window) { + print("Page: New window for page:" + this.title); + this.window = Desktop.createWindow(Script.resolvePath(this.qml), { + title: this.title, + presentationMode: Desktop.PresentationMode.NATIVE, + size: {x: this.width, y: this.height} + }); + this.window.closed.connect(function () { + that.killView(); + }); + } + }; + + + var Pages = function () { + this._pages = {}; + }; + + Pages.prototype.addPage = function (command, title, qmlurl, width, height) { + this._pages[command] = new Page(title, qmlurl, width, height); + }; + + Pages.prototype.open = function (command) { + print("Pages: command = " + command); + if (!this._pages[command]) { + print("Pages: unknown command = " + command); + return; + } + this._pages[command].createView(); + }; + + Pages.prototype.clear = function () { + for (var p in this._pages) { + print("Pages: kill page: " + p); + this._pages[p].killView(); + delete this._pages[p]; + } + this._pages = {}; + }; + var pages = new Pages(); + + pages.addPage('openEngineView', 'Render Engine', 'engineInspector.qml', 300, 400); + pages.addPage('openEngineLODView', 'Render LOD', 'lod.qml', 300, 400); + pages.addPage('openCullInspectorView', 'Cull Inspector', 'culling.qml', 300, 400); function fromQml(message) { - switch (message.method) { - case "openEngineView": - openEngineTaskView(); - break; - } + if (pages.open(message.method)) { + return; + } } - - var engineInspectorView = null - function openEngineTaskView() { - if (engineInspectorView == null) { - var qml = Script.resolvePath('engineInspector.qml'); - var window = new OverlayWindow({ - title: 'Render Engine', - source: qml, - width: 300, - height: 400 - }); - window.setPosition(200, 50); - engineInspectorView = window - window.closed.connect(function() { engineInspectorView = null; }); - } else { - engineInspectorView.setPosition(200, 50); - } + var ui; + function startup() { + ui = new AppUi({ + buttonName: "LUCI", + home: Script.resolvePath("deferredLighting.qml"), + additionalAppScreens: Script.resolvePath("engineInspector.qml"), + onMessage: fromQml, + normalButton: Script.resolvePath("../../../system/assets/images/luci-i.svg"), + activeButton: Script.resolvePath("../../../system/assets/images/luci-a.svg") + }); } - - - + startup(); Script.scriptEnding.connect(function () { - if (onLuciScreen) { - tablet.gotoHomeScreen(); - } - button.clicked.disconnect(onClicked); - tablet.screenChanged.disconnect(onScreenChanged); - tablet.removeButton(button); - - if (engineInspectorView !== null) { - engineInspectorView.close() - } + Controller.mousePressEvent.disconnect(onMousePressEvent); + Controller.mouseReleaseEvent.disconnect(onMouseReleaseEvent); + Controller.mouseMoveEvent.disconnect(onMouseMoveEvent); + pages.clear(); + // killEngineInspectorView(); + // killCullInspectorView(); + // killEngineLODWindow(); }); -}()); \ No newline at end of file +}()); diff --git a/scripts/developer/utilities/render/shadow.qml b/scripts/developer/utilities/render/shadow.qml index 464fe00eb9..a1d6777a68 100644 --- a/scripts/developer/utilities/render/shadow.qml +++ b/scripts/developer/utilities/render/shadow.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "configSlider" diff --git a/scripts/developer/utilities/render/transition.qml b/scripts/developer/utilities/render/transition.qml index f74468a273..c150c523f9 100644 --- a/scripts/developer/utilities/render/transition.qml +++ b/scripts/developer/utilities/render/transition.qml @@ -13,8 +13,8 @@ import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 import QtQuick.Dialogs 1.0 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "configSlider" import "../lib/plotperf" diff --git a/scripts/developer/utilities/workload/workloadInspector.qml b/scripts/developer/utilities/workload/workloadInspector.qml index 2eaa9d8133..746a572f29 100644 --- a/scripts/developer/utilities/workload/workloadInspector.qml +++ b/scripts/developer/utilities/workload/workloadInspector.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../render/configSlider" import "../lib/jet/qml" as Jet import "../lib/plotperf" diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index efb842a9bb..e267293977 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -95,16 +95,16 @@ function AppUi(properties) { activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton }); }; - that.notificationPollTimeout = false; - that.notificationPollTimeoutMs = 60000; - that.notificationPollEndpoint = false; - that.notificationPollStopPaginatingConditionMet = false; + that.notificationPollTimeout = [false]; + that.notificationPollTimeoutMs = [60000]; + that.notificationPollEndpoint = [false]; + that.notificationPollStopPaginatingConditionMet = [false]; that.notificationDataProcessPage = function (data) { return data; }; - that.notificationPollCallback = that.ignore; - that.notificationPollCaresAboutSince = false; - that.notificationInitialCallbackMade = false; + that.notificationPollCallback = [that.ignore]; + that.notificationPollCaresAboutSince = [false]; + that.notificationInitialCallbackMade = [false]; that.notificationDisplayBanner = function (message) { if (!that.isOpen) { Window.displayAnnouncement(message); @@ -129,7 +129,9 @@ function AppUi(properties) { } that.isOpen = true; } - } else { // Not us. Should we do something for type Home, Menu, and particularly Closed (meaning tablet hidden? + } else { + // A different screen is now visible, or the tablet has been closed. + // Tablet visibility is controlled separately by `tabletShownChanged()` that.wireEventBridge(false); if (that.isOpen) { that.buttonActive(false); @@ -139,83 +141,124 @@ function AppUi(properties) { that.isOpen = false; } } - // console.debug(that.buttonName + " app reports: Tablet screen changed.\nNew screen type: " + type + - // "\nNew screen URL: " + url + "\nCurrent app open status: " + that.isOpen + "\n"); }; // Overwrite with the given properties: - Object.keys(properties).forEach(function (key) { that[key] = properties[key]; }); + Object.keys(properties).forEach(function (key) { + that[key] = properties[key]; + }); // // START Notification Handling // + + var currentDataPageToRetrieve = []; + var concatenatedServerResponse = []; + for (var i = 0; i < that.notificationPollEndpoint.length; i++) { + currentDataPageToRetrieve[i] = 1; + concatenatedServerResponse[i] = new Array(); + } + + var MAX_LOG_LENGTH_CHARACTERS = 300; + function requestCallback(error, response, optionalParams) { + var indexOfRequest = optionalParams.indexOfRequest; + var urlOfRequest = optionalParams.urlOfRequest; + + if (error || (response.status !== 'success')) { + print("Error: unable to get", urlOfRequest, error || response.status); + startNotificationTimer(indexOfRequest); + return; + } + + if (!that.notificationPollStopPaginatingConditionMet[indexOfRequest] || + that.notificationPollStopPaginatingConditionMet[indexOfRequest](response)) { + startNotificationTimer(indexOfRequest); + + var notificationData; + if (concatenatedServerResponse[indexOfRequest].length) { + notificationData = concatenatedServerResponse[indexOfRequest]; + } else { + notificationData = that.notificationDataProcessPage[indexOfRequest](response); + } + console.debug(that.buttonName, that.notificationPollEndpoint[indexOfRequest], + 'truncated notification data for processing:', + JSON.stringify(notificationData).substring(0, MAX_LOG_LENGTH_CHARACTERS)); + that.notificationPollCallback[indexOfRequest](notificationData); + that.notificationInitialCallbackMade[indexOfRequest] = true; + currentDataPageToRetrieve[indexOfRequest] = 1; + concatenatedServerResponse[indexOfRequest] = new Array(); + } else { + concatenatedServerResponse[indexOfRequest] = + concatenatedServerResponse[indexOfRequest].concat(that.notificationDataProcessPage[indexOfRequest](response)); + currentDataPageToRetrieve[indexOfRequest]++; + request({ + json: true, + uri: (urlOfRequest + "&page=" + currentDataPageToRetrieve[indexOfRequest]) + }, requestCallback, optionalParams); + } + } + + var METAVERSE_BASE = Account.metaverseServerURL; - var currentDataPageToRetrieve = 1; - var concatenatedServerResponse = new Array(); - that.notificationPoll = function () { - if (!that.notificationPollEndpoint) { + var MS_IN_SEC = 1000; + that.notificationPoll = function (i) { + if (!that.notificationPollEndpoint[i]) { return; } - // User is "appearing offline" or is offline - if (GlobalServices.findableBy === "none" || Account.username === "") { - that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs); + // User is "appearing offline" or is not logged in + if (GlobalServices.findableBy === "none" || Account.username === "Unknown user") { + // The notification polling will restart when the user changes their availability + // or when they log in, so it's not necessary to restart a timer here. + console.debug(that.buttonName + " Notifications: User is appearing offline or not logged in. " + + that.buttonName + " will poll for notifications when user logs in and has their availability " + + "set to not appear offline."); return; } - var url = METAVERSE_BASE + that.notificationPollEndpoint; + var url = METAVERSE_BASE + that.notificationPollEndpoint[i]; - var settingsKey = "notifications/" + that.buttonName + "/lastPoll"; + var settingsKey = "notifications/" + that.notificationPollEndpoint[i] + "/lastPoll"; var currentTimestamp = new Date().getTime(); var lastPollTimestamp = Settings.getValue(settingsKey, currentTimestamp); - if (that.notificationPollCaresAboutSince) { - url = url + "&since=" + lastPollTimestamp/1000; + if (that.notificationPollCaresAboutSince[i]) { + url = url + "&since=" + lastPollTimestamp / MS_IN_SEC; } Settings.setValue(settingsKey, currentTimestamp); console.debug(that.buttonName, 'polling for notifications at endpoint', url); - function requestCallback(error, response) { - if (error || (response.status !== 'success')) { - print("Error: unable to get", url, error || response.status); - that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs); - return; - } - - if (!that.notificationPollStopPaginatingConditionMet || that.notificationPollStopPaginatingConditionMet(response)) { - that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs); - - var notificationData; - if (concatenatedServerResponse.length) { - notificationData = concatenatedServerResponse; - } else { - notificationData = that.notificationDataProcessPage(response); - } - console.debug(that.buttonName, 'notification data for processing:', JSON.stringify(notificationData)); - that.notificationPollCallback(notificationData); - that.notificationInitialCallbackMade = true; - currentDataPageToRetrieve = 1; - concatenatedServerResponse = new Array(); - } else { - concatenatedServerResponse = concatenatedServerResponse.concat(that.notificationDataProcessPage(response)); - currentDataPageToRetrieve++; - request({ json: true, uri: (url + "&page=" + currentDataPageToRetrieve) }, requestCallback); - } - } - - request({ json: true, uri: url }, requestCallback); + request({ + json: true, + uri: url + }, + requestCallback, + { + indexOfRequest: i, + urlOfRequest: url + }); }; // This won't do anything if there isn't a notification endpoint set - that.notificationPoll(); + for (i = 0; i < that.notificationPollEndpoint.length; i++) { + that.notificationPoll(i); + } + + function startNotificationTimer(indexOfRequest) { + that.notificationPollTimeout[indexOfRequest] = Script.setTimeout(function () { + that.notificationPoll(indexOfRequest); + }, that.notificationPollTimeoutMs[indexOfRequest]); + } function restartNotificationPoll() { - that.notificationInitialCallbackMade = false; - if (that.notificationPollTimeout) { - Script.clearTimeout(that.notificationPollTimeout); - that.notificationPollTimeout = false; + for (var j = 0; j < that.notificationPollEndpoint.length; j++) { + that.notificationInitialCallbackMade[j] = false; + if (that.notificationPollTimeout[j]) { + Script.clearTimeout(that.notificationPollTimeout[j]); + that.notificationPollTimeout[j] = false; + } + that.notificationPoll(j); } - that.notificationPoll(); } // // END Notification Handling @@ -322,9 +365,11 @@ function AppUi(properties) { } that.tablet.removeButton(that.button); } - if (that.notificationPollTimeout) { - Script.clearInterval(that.notificationPollTimeout); - that.notificationPollTimeout = false; + for (var i = 0; i < that.notificationPollTimeout.length; i++) { + if (that.notificationPollTimeout[i]) { + Script.clearInterval(that.notificationPollTimeout[i]); + that.notificationPollTimeout[i] = false; + } } }; // Set up the handlers. @@ -333,7 +378,7 @@ function AppUi(properties) { Script.scriptEnding.connect(that.onScriptEnding); GlobalServices.findableByChanged.connect(restartNotificationPoll); GlobalServices.myUsernameChanged.connect(restartNotificationPoll); - if (that.buttonName == Settings.getValue("startUpApp")) { + if (that.buttonName === Settings.getValue("startUpApp")) { Settings.setValue("startUpApp", ""); Script.setTimeout(function () { that.open(); diff --git a/scripts/modules/request.js b/scripts/modules/request.js index d0037f9b43..37f3ac0d7b 100644 --- a/scripts/modules/request.js +++ b/scripts/modules/request.js @@ -18,7 +18,8 @@ module.exports = { // ------------------------------------------------------------------ - request: function (options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request. + // cb(error, responseOfCorrectContentType, optionalCallbackParameter) of url. A subset of npm request. + request: function (options, callback, optionalCallbackParameter) { var httpRequest = new XMLHttpRequest(), key; // QT bug: apparently doesn't handle onload. Workaround using readyState. httpRequest.onreadystatechange = function () { @@ -38,7 +39,7 @@ module.exports = { if (error) { response = { statusCode: httpRequest.status }; } - callback(error, response); + callback(error, response, optionalCallbackParameter); } }; if (typeof options === 'string') { diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json new file mode 100644 index 0000000000..5572779d46 --- /dev/null +++ b/scripts/system/assets/data/createAppTooltips.json @@ -0,0 +1,476 @@ +{ + "shape": { + "tooltip": "The shape of this entity's geometry." + }, + "color": { + "tooltip": "The RGB value of this entity." + }, + "text": { + "tooltip": "The text to display on the entity." + }, + "textColor": { + "tooltip": "The color of the text." + }, + "backgroundColor": { + "tooltip": "The color of the background." + }, + "lineHeight": { + "tooltip": "The height of each line of text. This determines the size of the text." + }, + "faceCamera": { + "tooltip": "If enabled, the entity follows the camera of each user, creating a billboard effect." + }, + "flyingAllowed": { + "tooltip": "If enabled, users can fly in the zone." + }, + "ghostingAllowed": { + "tooltip": "If enabled, users with avatar collisions turned off will not collide with content in the zone." + }, + "filterURL": { + "tooltip": "The URL of a JS file that checks for changes to entity properties within the zone. Runs periodically." + }, + "keyLightMode": { + "tooltip": "Configures the key light in the zone. This light is directional." + }, + "keyLight.color": { + "tooltip": "The color of the key light." + }, + "keyLight.intensity": { + "tooltip": "The intensity of the key light." + }, + "keyLight.direction.y": { + "tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its y axis." + }, + "keyLight.direction.x": { + "tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its x axis." + }, + "keyLight.castShadows": { + "tooltip": "If enabled, shadows are cast. The entity or avatar casting the shadow must also have Cast Shadows enabled." + }, + "skyboxMode": { + "tooltip": "Configures the skybox in the zone. The skybox is a cube map image." + }, + "skybox.color": { + "tooltip": "If the URL is blank, this changes the color of the sky, otherwise it modifies the color of the skybox." + }, + "skybox.url": { + "tooltip": "A cube map image that is used to render the sky." + }, + "ambientLightMode": { + "tooltip": "Configures the ambient light in the zone. Use this if you want your skybox to reflect light on the content." + }, + "ambientLight.ambientIntensity": { + "tooltip": "The intensity of the ambient light." + }, + "ambientLight.ambientURL": { + "tooltip": "A cube map image that defines the color of the light coming from each direction." + }, + "hazeMode": { + "tooltip": "Configures the haze in the scene." + }, + "haze.hazeRange": { + "tooltip": "How far the haze extends out. This is measured in meters." + }, + "haze.hazeAltitudeEffect": { + "tooltip": "If enabled, this adjusts the haze intensity as it gets higher." + }, + "haze.hazeBaseRef": { + "tooltip": "The base of the altitude range. Measured in entity space." + }, + "haze.hazeCeiling": { + "tooltip": "The ceiling of the altitude range. Measured in entity space." + }, + "haze.hazeColor": { + "tooltip": "The color of the haze." + }, + "haze.hazeBackgroundBlend": { + "tooltip": "How much the skybox shows through the haze. The higher the value, the more it shows through." + }, + "haze.hazeEnableGlare": { + "tooltip": "If enabled, a glare is enabled on the skybox, based on the key light." + }, + "haze.hazeGlareColor": { + "tooltip": "The color of the glare based on the key light." + }, + "haze.hazeGlareAngle": { + "tooltip": "The angular size of the glare and how much it encompasses the skybox, based on the key light." + }, + "bloomMode": { + "tooltip": "Configures how much bright areas of the scene glow." + }, + "bloom.bloomIntensity": { + "tooltip": "The intensity, or brightness, of the bloom effect." + }, + "bloom.bloomThreshold": { + "tooltip": "The cutoff of the bloom. The higher the value, the more only bright areas of the scene will glow." + }, + "bloom.bloomSize": { + "tooltip": "The radius of bloom. The higher the value, the larger the bloom." + }, + "modelURL": { + "tooltip": "A mesh model from an FBX or OBJ file." + }, + "shapeType": { + "tooltip": "The shape of the collision hull used if collisions are enabled. This affects how an entity collides." + }, + "compoundShapeURL": { + "tooltip": "The OBJ file to use for the compound shape if Collision Shape is \"compound\"." + }, + "animation.url": { + "tooltip": "An animation to play on the model." + }, + "animation.running": { + "tooltip": "If enabled, the animation on the model will play automatically." + }, + "animation.allowTranslation": { + "tooltip": "If enabled, this allows an entity to move in space during an animation." + }, + "animation.loop": { + "tooltip": "If enabled, then the animation will continuously repeat." + }, + "animation.hold": { + "tooltip": "If enabled, then rotations and translations of the last frame played are maintained when the animation stops." + }, + "animation.currentFrame": { + "tooltip": "The current frame being played in the animation." + }, + "animation.firstFrame": { + "tooltip": "The first frame to play in the animation." + }, + "animation.lastFrame": { + "tooltip": "The last frame to play in the animation." + }, + "animation.fps": { + "tooltip": "The speed of the animation." + }, + "textures": { + "tooltip": "A JSON string containing a texture. Use a name from the Original Texture property to override it." + }, + "originalTextures": { + "tooltip": "A JSON string containing the original texture used on the model." + }, + "image": { + "tooltip": "The URL for the image source.", + "jsPropertyName": "textures" + }, + "sourceUrl": { + "tooltip": "The URL for the web page source." + }, + "dpi": { + "tooltip": "The resolution to display the page at, in pixels per inch. Use this to resize your web source in the frame." + }, + "isEmitting": { + "tooltip": "If enabled, then particles are emitted." + }, + "lifespan": { + "tooltip": "How long each particle lives, measured in seconds." + }, + "maxParticles": { + "tooltip": "The maximum number of particles to render at one time. Older particles are swapped out for new ones." + }, + "particleTextures": { + "tooltip": "The URL of a JPG or PNG image file to display for each particle.", + "jsPropertyName": "textures" + }, + "emitRate": { + "tooltip": "The number of particles per second to emit." + }, + "emitSpeed": { + "tooltip": "The speed that each particle is emitted at, measured in m/s." + }, + "speedSpread": { + "tooltip": "The spread in speeds at which particles are emitted at, resulting in a variety of speeds." + }, + "emitDimensions": { + "tooltip": "The outer limit radius in dimensions that the particles can be emitted from." + }, + "emitOrientation": { + "tooltip": "The orientation of particle emission relative to the entity's axes." + }, + "emitRadiusStart": { + "tooltip": "The inner limit radius in dimensions that the particles start emitting from." + }, + "emitterShouldTrail": { + "tooltip": "If enabled, then particles are \"left behind\" as the emitter moves, otherwise they are not." + }, + "particleRadius": { + "tooltip": "The size of each particle." + }, + "radiusSpread": { + "tooltip": "The spread in size that each particle is given, resulting in a variety of sizes." + }, + "particleColor": { + "tooltip": "The color of each particle.", + "jsPropertyName": "color" + }, + "colorSpread": { + "tooltip": "The spread in color that each particle is given, resulting in a variety of colors." + }, + "alpha": { + "tooltip": "The alpha of each particle." + }, + "alphaSpread": { + "tooltip": "The spread in alpha that each particle is given, resulting in a variety of alphas." + }, + "emitAcceleration": { + "tooltip": "The acceleration that is applied to each particle during its lifetime." + }, + "accelerationSpread": { + "tooltip": "The spread in accelerations that each particle is given, resulting in a variety of accelerations." + }, + "particleSpin": { + "tooltip": "The spin of each particle in the system." + }, + "spinSpread": { + "tooltip": "The spread in spin that each particle is given, resulting in a variety of spins." + }, + "rotateWithEntity": { + "tooltip": "If enabled, each particle will spin relative to the roation of the entity as a whole." + }, + "polarStart": { + "tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + }, + "azimuthStart": { + "tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + }, + "lightColor": { + "tooltip": "The color of the light emitted.", + "jsPropertyName": "color" + }, + "intensity": { + "tooltip": "The brightness of the light." + }, + "falloffRadius": { + "tooltip": "The distance from the light's center where the intensity is reduced." + }, + "isSpotlight": { + "tooltip": "If enabled, then the light is directional, otherwise the light is a point light which emits light in all directions." + }, + "exponent": { + "tooltip": "Affects the softness of the spotlight beam; the higher the value, the softer the beam." + }, + "cutoff": { + "tooltip": "Affects the size of the spotlight beam; the higher the value, the larger the beam." + }, + "materialURL": { + "tooltip": "The URL to an external JSON file or \"materialData\", \"materialData?<material name> to use Material Data." + }, + "materialData": { + "tooltip": "Can be used instead of a JSON file when material set to materialData." + }, + "materialNameToReplace": { + "tooltip": "Material name of parent entity to map this material entity on.", + "jsPropertyName": "parentMaterialName" + }, + "submeshToReplace": { + "tooltip": "Submesh index of the parent entity to map this material on.", + "jsPropertyName": "parentMaterialName" + }, + "selectSubmesh": { + "tooltip": "If enabled, \"Select Submesh\" property will show up, otherwise \"Material Name to Replace\" will be shown.", + "skipJSProperty": true + }, + "priority": { + "tooltip": "The priority of the material, where a larger number means higher priority. Original materials = 0." + }, + "materialMappingPos": { + "tooltip": "The offset position of the bottom left of the material within the parent's UV space." + }, + "materialMappingScale": { + "tooltip": "How many times the material will repeat in each direction within the parent's UV space." + }, + "materialMappingRot": { + "tooltip": "How much to rotate the material within the parent's UV-space, in degrees." + }, + "id": { + "tooltip": "The unique identifier of this entity." + }, + "name": { + "tooltip": "The name of this entity." + }, + "description": { + "tooltip": "Use this field to describe the entity." + }, + "position": { + "tooltip": "The global position of this entity." + }, + "rotation": { + "tooltip": "The rotation of the entity with respect to world coordinates." + }, + "dimensions": { + "tooltip": "The global dimensions of this entity." + }, + "scale": { + "tooltip": "The global scaling of this entity.", + "skipJSProperty": true + }, + "registrationPoint": { + "tooltip": "The point in the entity at which the entity is rotated about." + }, + "visible": { + "tooltip": "If enabled, this entity will be visible." + }, + "locked": { + "tooltip": "If enabled, this entity will be locked." + }, + "collisionless": { + "tooltip": "If enabled, this entity will collide with other entities or avatars." + }, + "dynamic": { + "tooltip": "If enabled, this entity has collisions associated with it that can affect its movement." + }, + "collidesWithStatic": { + "tooltip": "If enabled, this entity will collide with other non-moving, static entities.", + "jsPropertyName": "collidesWith" + }, + "collidesWithDynamic": { + "tooltip": "If enabled, this entity will collide with other dynamic entities.", + "jsPropertyName": "collidesWith" + }, + "collidesWithKinematic": { + "tooltip": "If enabled, this entity will collide with other kinematic entities (they have velocity but are not dynamic).", + "jsPropertyName": "collidesWith" + }, + "collidesWithOtherAvatar": { + "tooltip": "If enabled, this entity will collide with other user's avatars.", + "jsPropertyName": "collidesWith" + }, + "collidesWithMyAvatar": { + "tooltip": "If enabled, this entity will collide with your own avatar.", + "jsPropertyName": "collidesWith" + }, + "collisionSoundURL": { + "tooltip": "The URL of a sound to play when the entity collides with something else." + }, + "grab.grabbable": { + "tooltip": "If enabled, this entity will allow grabbing input and will be moveable." + }, + "grab.triggerable": { + "tooltip": "If enabled, the collider on this entity is used for triggering events." + }, + "cloneable": { + "tooltip": "If enabled, this entity can be duplicated." + }, + "cloneLifetime": { + "tooltip": "The lifetime for clones of this entity." + }, + "cloneLimit": { + "tooltip": "The total number of clones of this entity that can exist in the domain at any given time." + }, + "cloneDynamic": { + "tooltip": "If enabled, then clones created from this entity will be dynamic, allowing the clone to collide." + }, + "cloneAvatarEntity": { + "tooltip": "If enabled, then clones created from this entity will be created as avatar entities." + }, + "grab.grabFollowsController": { + "tooltip": "If enabled, grabbed entities will follow the movements of your hand controller instead of your avatar's hand." + }, + "canCastShadow": { + "tooltip": "If enabled, this geometry of this entity casts shadows when a shadow-casting light source shines on it." + }, + "parentID": { + "tooltip": "The ID of the entity or avatar that this entity is parented to." + }, + "parentJointIndex": { + "tooltip": "If the entity is parented to an avatar, this joint defines where on the avatar the entity is parented." + }, + "href": { + "tooltip": "The URL that will be opened when a user clicks on this entity. Useful for web pages and portals." + }, + "script": { + "tooltip": "The URL to an external JS file to add behaviors to the client." + }, + "serverScripts": { + "tooltip": "The URL to an external JS file to add behaviors to the server." + }, + "serverScriptsStatus": { + "tooltip": "The status of the server script, if provided. This shows if it's running or has an error.", + "skipJSProperty": true + }, + "hasLifetime": { + "tooltip": "If enabled, the entity will disappear after a certain amount of time specified by Lifetime.", + "jsPropertyName": "lifetime" + }, + "lifetime": { + "tooltip": "The time this entity will exist in the environment for." + }, + "userData": { + "tooltip": "Used to store extra data about the entity in JSON format." + }, + "velocity": { + "tooltip": "The linear velocity vector of the entity. The velocity at which this entity moves forward in space." + }, + "damping": { + "tooltip": "The linear damping to slow down the linear velocity of an entity over time." + }, + "angularVelocity": { + "tooltip": "The angular velocity of the entity in rad/s with respect to its axes, about its pivot point." + }, + "angularDamping": { + "tooltip": "The angular damping to slow down the angular velocity of an entity over time." + }, + "restitution": { + "tooltip": "If enabled, the entity can bounce against other objects that also have Bounciness." + }, + "friction": { + "tooltip": "The friction applied to slow down an entity when it's moving against another entity." + }, + "density": { + "tooltip": "The density of the entity. The higher the density, the harder the entity is to move." + }, + "gravity": { + "tooltip": "The acceleration due to gravity that the entity should move with, in world space." + }, + "acceleration": { + "tooltip": "A acceleration that the entity should move with, in world space." + }, + "alignToGrid": { + "tooltip": "Used to align entities to the grid, or floor of the environment.", + "skipJSProperty": true + }, + "createModel": { + "tooltip": "An entity that is based on a custom mesh created from an .OBJ or .FBX.", + "skipJSProperty": true + }, + "createShape": { + "tooltip": "An entity that has many different primitive shapes.", + "skipJSProperty": true + }, + "createLight": { + "tooltip": "An entity that emits light.", + "skipJSProperty": true + }, + "createText": { + "tooltip": "An entity that displays text on a panel.", + "skipJSProperty": true + }, + "createImage": { + "tooltip": "An entity that displays an image on a panel.", + "skipJSProperty": true + }, + "createWeb": { + "tooltip": "An entity that displays a web page on a panel.", + "skipJSProperty": true + }, + "createZone": { + "tooltip": "An entity that can be used for skyboxes, lighting, and can constrain or change avatar behaviors.", + "skipJSProperty": true + }, + "createParticle": { + "tooltip": "An entity that emits particles.", + "skipJSProperty": true + }, + "createMaterial": { + "tooltip": "An entity that creates a material that can be attached to a Shape or Model.", + "skipJSProperty": true + }, + "useAssetServer": { + "tooltip": "A server that hosts content and assets. You can't take items that are hosted here into other domains.", + "skipJSProperty": true + }, + "importNewEntity": { + "tooltip": "Import a local or hosted file that can be used across domains.", + "skipJSProperty": true + } +} diff --git a/scripts/system/assets/models/black-sphere.fbx b/scripts/system/assets/models/black-sphere.fbx new file mode 100644 index 0000000000..2e6dea233f Binary files /dev/null and b/scripts/system/assets/models/black-sphere.fbx differ diff --git a/scripts/system/assets/sounds/crystals_and_voices.mp3 b/scripts/system/assets/sounds/crystals_and_voices.mp3 new file mode 100644 index 0000000000..1dd2037e6b Binary files /dev/null and b/scripts/system/assets/sounds/crystals_and_voices.mp3 differ diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index ece35acce7..44f10396ca 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -159,7 +159,7 @@ var selectedAvatarEntityGrabbable = false; var selectedAvatarEntityID = null; var grabbedAvatarEntityChangeNotifier = null; -var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; +var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("html/js/marketplacesInject.js"); @@ -285,9 +285,9 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See case 'navigate': var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system") if(message.url.indexOf('app://') === 0) { - if(message.url === 'app://marketplace') { + if (message.url === 'app://marketplace') { tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); - } else if(message.url === 'app://purchases') { + } else if (message.url === 'app://purchases') { tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); } diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 5b91afea33..9fb336f79c 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global getConnectionData */ +/* global getConnectionData getControllerWorldLocation openLoginWindow WalletScriptingInterface */ (function () { // BEGIN LOCAL_SCOPE Script.include("/~/system/libraries/accountUtils.js"); @@ -20,7 +20,6 @@ var AppUi = Script.require('appUi'); var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; - // BEGIN AVATAR SELECTOR LOGIC var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; @@ -48,7 +47,6 @@ ExtendedOverlay.prototype.editOverlay = function (properties) { // change displa function color(selected, hovering) { var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; function scale(component) { - var delta = 0xFF - component; return component; } return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; @@ -105,7 +103,8 @@ ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId // 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. + // Depends on nearer coverOverlays to extend closer to us than farther ones. + var pickedOverlay = Overlays.findRayIntersection(pickRay); if (!pickedOverlay.intersects) { if (noHit) { return noHit(); @@ -131,6 +130,7 @@ function addAvatarNode(id) { } var pingPong = true; +var OVERLAY_SCALE = 0.032; function updateOverlays() { var eye = Camera.position; AvatarList.getAvatarIdentifiers().forEach(function (id) { @@ -148,7 +148,8 @@ function updateOverlays() { 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) + // get diff between target and eye (a vector pointing to the eye from avatar position) + var diff = Vec3.subtract(target, eye); 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; @@ -164,7 +165,7 @@ function updateOverlays() { overlay.editOverlay({ color: color(ExtendedOverlay.isSelected(id), overlay.hovering), position: target, - dimensions: 0.032 * distance + dimensions: OVERLAY_SCALE * distance }); }); pingPong = !pingPong; @@ -376,10 +377,35 @@ function deleteSendMoneyParticleEffect() { } function onUsernameChanged() { - if (Account.username !== Settings.getValue("wallet/savedUsername")) { - Settings.setValue("wallet/autoLogout", false); - Settings.setValue("wallet/savedUsername", ""); + if (Account.username !== Settings.getValue("keepMeLoggedIn/savedUsername")) { + Settings.setValue("keepMeLoggedIn", false); + Settings.setValue("keepMeLoggedIn/savedUsername", ""); } +} + +var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); +var METAVERSE_SERVER_URL = Account.metaverseServerURL; +var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. +function openMarketplace(optionalItemOrUrl) { + // This is a bit of a kluge, but so is the whole file. + // If given a whole path, use it with no cta. + // If given an id, build the appropriate url and use the id as the cta. + // Otherwise, use home and 'marketplace cta'. + // AND... if call onMarketplaceOpen to setupWallet if we need to. + var url = optionalItemOrUrl || MARKETPLACE_URL_INITIAL; + // If optionalItemOrUrl contains the metaverse base, then it's a url, not an item id. + if (optionalItemOrUrl && optionalItemOrUrl.indexOf(METAVERSE_SERVER_URL) === -1) { + url = MARKETPLACE_URL + '/items/' + optionalItemOrUrl; + } + ui.open(url, MARKETPLACES_INJECT_SCRIPT_URL); +} + +function setCertificateInfo(itemCertificateId) { + ui.tablet.sendToQml({ + method: 'inspectionCertificate_setCertificateId', + entityId: "", + certificateId: itemCertificateId + }); } // Function Name: fromQml() @@ -387,8 +413,6 @@ function onUsernameChanged() { // Description: // -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML // in the format "{method, params}", like json-rpc. See also sendToQml(). -var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; -var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); function fromQml(message) { switch (message.method) { case 'passphrasePopup_cancelClicked': @@ -413,6 +437,7 @@ function fromQml(message) { } break; case 'needsLogIn_loginClicked': + ui.close(); openLoginWindow(); break; case 'disableHmdPreview': @@ -422,10 +447,6 @@ function fromQml(message) { case 'transactionHistory_linkClicked': ui.open(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL); break; - case 'goToPurchases_fromWalletHome': - case 'goToPurchases': - ui.open(MARKETPLACE_PURCHASES_QML_PATH); - break; case 'goToMarketplaceMainPage': ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); break; @@ -450,22 +471,20 @@ function fromQml(message) { removeOverlays(); break; case 'sendAsset_sendPublicly': - if (message.assetName === "") { - deleteSendMoneyParticleEffect(); - sendMoneyRecipient = message.recipient; - var amount = message.amount; - var props = SEND_MONEY_PARTICLE_PROPERTIES; - props.parentID = MyAvatar.sessionUUID; - props.position = MyAvatar.position; - props.position.y += 0.2; - if (message.effectImage) { - props.textures = message.effectImage; - } - sendMoneyParticleEffect = Entities.addEntity(props, true); - particleEffectTimestamp = Date.now(); - updateSendMoneyParticleEffect(); - sendMoneyParticleEffectUpdateTimer = Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE); + deleteSendMoneyParticleEffect(); + sendMoneyRecipient = message.recipient; + var props = SEND_MONEY_PARTICLE_PROPERTIES; + props.parentID = MyAvatar.sessionUUID; + props.position = MyAvatar.position; + props.position.y += 0.2; + if (message.effectImage) { + props.textures = message.effectImage; } + sendMoneyParticleEffect = Entities.addEntity(props, true); + particleEffectTimestamp = Date.now(); + updateSendMoneyParticleEffect(); + sendMoneyParticleEffectUpdateTimer = + Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE); break; case 'transactionHistory_goToBank': if (Account.metaverseServerURL.indexOf("staging") >= 0) { @@ -474,6 +493,49 @@ function fromQml(message) { Window.location = "hifi://BankOfHighFidelity"; } break; + case 'purchases_updateWearables': + var currentlyWornWearables = []; + var ATTACHMENT_SEARCH_RADIUS = 100; // meters (just in case) + + var nearbyEntities = Entities.findEntitiesByType('Model', MyAvatar.position, ATTACHMENT_SEARCH_RADIUS); + + for (var i = 0; i < nearbyEntities.length; i++) { + var currentProperties = Entities.getEntityProperties( + nearbyEntities[i], ['certificateID', 'editionNumber', 'parentID'] + ); + if (currentProperties.parentID === MyAvatar.sessionUUID) { + currentlyWornWearables.push({ + entityID: nearbyEntities[i], + entityCertID: currentProperties.certificateID, + entityEdition: currentProperties.editionNumber + }); + } + } + + ui.tablet.sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables }); + break; + case 'purchases_walletNotSetUp': + ui.tablet.sendToQml({ + method: 'updateWalletReferrer', + referrer: "purchases" + }); + break; + case 'purchases_openGoTo': + ui.open("hifi/tablet/TabletAddressDialog.qml"); + break; + case 'purchases_itemInfoClicked': + var itemId = message.itemId; + if (itemId && itemId !== "") { + openMarketplace(itemId); + } + break; + case 'purchases_itemCertificateClicked': + setCertificateInfo(message.itemCertificateId); + break; + case 'clearShouldShowDotHistory': + shouldShowDotHistory = false; + ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory); + break; case 'http.request': // Handled elsewhere, don't log. break; @@ -482,41 +544,76 @@ function fromQml(message) { } } +var isWired = false; function walletOpened() { Users.usernameFromIDReply.connect(usernameFromIDReply); Controller.mousePressEvent.connect(handleMouseEvent); Controller.mouseMoveEvent.connect(handleMouseMoveEvent); triggerMapping.enable(); triggerPressMapping.enable(); - shouldShowDot = false; - ui.messagesWaiting(shouldShowDot); + isWired = true; + + if (shouldShowDotHistory) { + ui.sendMessage({ + method: 'updateRecentActivityMessageLight', + messagesWaiting: shouldShowDotHistory + }); + } } function walletClosed() { off(); } -function notificationDataProcessPage(data) { +function notificationDataProcessPageUpdates(data) { + return data.data.updates; +} + +function notificationDataProcessPageHistory(data) { return data.data.history; } -var shouldShowDot = false; -function notificationPollCallback(historyArray) { +var shouldShowDotUpdates = false; +function notificationPollCallbackUpdates(updatesArray) { + shouldShowDotUpdates = shouldShowDotUpdates || updatesArray.length > 0; + ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory); + + if (updatesArray.length > 0) { + var message; + if (!ui.notificationInitialCallbackMade[0]) { + message = updatesArray.length + " of your purchased items " + + (updatesArray.length === 1 ? "has an update " : "have updates ") + + "available. Open INVENTORY to update."; + ui.notificationDisplayBanner(message); + + ui.notificationPollCaresAboutSince[0] = true; + } else { + for (var i = 0; i < updatesArray.length; i++) { + message = "Update available for \"" + + updatesArray[i].base_item_title + "\"." + + "Open INVENTORY to update."; + ui.notificationDisplayBanner(message); + } + } + } +} +var shouldShowDotHistory = false; +function notificationPollCallbackHistory(historyArray) { if (!ui.isOpen) { var notificationCount = historyArray.length; - shouldShowDot = shouldShowDot || notificationCount > 0; - ui.messagesWaiting(shouldShowDot); + shouldShowDotHistory = shouldShowDotHistory || notificationCount > 0; + ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory); if (notificationCount > 0) { var message; - if (!ui.notificationInitialCallbackMade) { - message = "You have " + notificationCount + " unread wallet " + - "transaction" + (notificationCount === 1 ? "" : "s") + ". Open WALLET to see all activity."; + if (!ui.notificationInitialCallbackMade[1]) { + message = "You have " + notificationCount + " unread recent " + + "transaction" + (notificationCount === 1 ? "" : "s") + ". Open INVENTORY to see all activity."; ui.notificationDisplayBanner(message); } else { for (var i = 0; i < notificationCount; i++) { message = '"' + (historyArray[i].message) + '" ' + - "Open WALLET to see all activity."; + "Open INVENTORY to see all activity."; ui.notificationDisplayBanner(message); } } @@ -524,7 +621,12 @@ function notificationPollCallback(historyArray) { } } -function isReturnedDataEmpty(data) { +function isReturnedDataEmptyUpdates(data) { + var updatesArray = data.data.updates; + return updatesArray.length === 0; +} + +function isReturnedDataEmptyHistory(data) { var historyArray = data.data.history; return historyArray.length === 0; } @@ -559,10 +661,27 @@ function uninstallMarketplaceItemTester() { } } -var BUTTON_NAME = "WALLET"; +var BUTTON_NAME = "INVENTORY"; var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; +var NOTIFICATION_POLL_TIMEOUT = 300000; var ui; function startup() { + var notificationPollEndpointArray = ["/api/v1/commerce/available_updates?per_page=10"]; + var notificationPollTimeoutMsArray = [NOTIFICATION_POLL_TIMEOUT]; + var notificationDataProcessPageArray = [notificationDataProcessPageUpdates]; + var notificationPollCallbackArray = [notificationPollCallbackUpdates]; + var notificationPollStopPaginatingConditionMetArray = [isReturnedDataEmptyUpdates]; + var notificationPollCaresAboutSinceArray = [false]; + + if (!WalletScriptingInterface.limitedCommerce) { + notificationPollEndpointArray[1] = "/api/v1/commerce/history?per_page=10"; + notificationPollTimeoutMsArray[1] = NOTIFICATION_POLL_TIMEOUT; + notificationDataProcessPageArray[1] = notificationDataProcessPageHistory; + notificationPollCallbackArray[1] = notificationPollCallbackHistory; + notificationPollStopPaginatingConditionMetArray[1] = isReturnedDataEmptyHistory; + notificationPollCaresAboutSinceArray[1] = true; + } + ui = new AppUi({ buttonName: BUTTON_NAME, sortOrder: 10, @@ -570,12 +689,12 @@ function startup() { onOpened: walletOpened, onClosed: walletClosed, onMessage: fromQml, - notificationPollEndpoint: "/api/v1/commerce/history?per_page=10", - notificationPollTimeoutMs: 300000, - notificationDataProcessPage: notificationDataProcessPage, - notificationPollCallback: notificationPollCallback, - notificationPollStopPaginatingConditionMet: isReturnedDataEmpty, - notificationPollCaresAboutSince: true + notificationPollEndpoint: notificationPollEndpointArray, + notificationPollTimeoutMs: notificationPollTimeoutMsArray, + notificationDataProcessPage: notificationDataProcessPageArray, + notificationPollCallback: notificationPollCallbackArray, + notificationPollStopPaginatingConditionMet: notificationPollStopPaginatingConditionMetArray, + notificationPollCaresAboutSince: notificationPollCaresAboutSinceArray }); GlobalServices.myUsernameChanged.connect(onUsernameChanged); installMarketplaceItemTester(); @@ -583,11 +702,14 @@ function startup() { var isUpdateOverlaysWired = false; function off() { - Users.usernameFromIDReply.disconnect(usernameFromIDReply); - Controller.mousePressEvent.disconnect(handleMouseEvent); - Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); - triggerMapping.disable(); - triggerPressMapping.disable(); + if (isWired) { + Users.usernameFromIDReply.disconnect(usernameFromIDReply); + Controller.mousePressEvent.disconnect(handleMouseEvent); + Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); + triggerMapping.disable(); + triggerPressMapping.disable(); + isWired = false; + } if (isUpdateOverlaysWired) { Script.update.disconnect(updateOverlays); diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index f4d9c731b7..12a69d7b27 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -167,16 +167,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa }; -var alreadyWarned = {}; -function warnAboutUserData(props) { - if (alreadyWarned[props.id]) { - return; - } - print("Warning -- overriding grab properties with userData for " + props.id + " / " + props.name); - alreadyWarned[props.id] = true; -} - - (function() { var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints"; @@ -200,7 +190,6 @@ function warnAboutUserData(props) { function getWearableData(props) { if (props.grab.equippable) { - // if equippable is true, we know this was already converted from the old userData style to properties return { joints: { LeftHand: [ props.grab.equippableLeftPosition, props.grab.equippableLeftRotation ], @@ -211,67 +200,7 @@ function warnAboutUserData(props) { indicatorOffset: props.grab.equippableIndicatorOffset }; } else { - // check for old userData equippability. The JSON reader will convert userData to properties - // in EntityTree.cpp, but this won't catch things created from scripts or some items in - // the market. Eventually we'll remove this section. - try { - if (!props.userDataParsed) { - props.userDataParsed = JSON.parse(props.userData); - } - var userDataParsed = props.userDataParsed; - - // userData: { wearable: { joints: { LeftHand: {...}, RightHand: {...} } } } - if (userDataParsed.wearable && userDataParsed.wearable.joints) { - warnAboutUserData(props); - userDataParsed.wearable.indicatorURL = ""; - userDataParsed.wearable.indicatorScale = { x: 1, y: 1, z: 1 }; - userDataParsed.wearable.indicatorOffset = { x: 0, y: 0, z: 0 }; - return userDataParsed.wearable; - } - - // userData: { equipHotspots: { joints: { LeftHand: {...}, RightHand: {...} } } } - // https://highfidelity.atlassian.net/wiki/spaces/HOME/pages/51085337/Authoring+Equippable+Entities - if (userDataParsed.equipHotspots && - userDataParsed.equipHotspots.length > 0 && - userDataParsed.equipHotspots[0].joints) { - warnAboutUserData(props); - var hotSpot = userDataParsed.equipHotspots[0]; - - var indicatorScale = { x: hotSpot.radius, y: hotSpot.radius, z: hotSpot.radius }; - if (hotSpot.modelURL && hotSpot.modelURL !== "") { - indicatorScale = hotSpot.modelScale; - } - - return { - joints: hotSpot.joints, - indicatorURL: hotSpot.modelURL, - indicatorScale: indicatorScale, - indicatorOffset: hotSpot.position, - }; - } - - // userData:{grabbableKey:{spatialKey:{leftRelativePosition:{...},rightRelativePosition:{...}}}} - if (userDataParsed.grabbableKey && - userDataParsed.grabbableKey.spatialKey) { - warnAboutUserData(props); - var joints = {}; - joints.LeftHand = [ { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0, w: 1 } ]; - joints.RightHand = [ { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0, w: 1 } ]; - if (userDataParsed.grabbableKey.spatialKey.leftRelativePosition) { - joints.LeftHand = [userDataParsed.grabbableKey.spatialKey.leftRelativePosition, - userDataParsed.grabbableKey.spatialKey.relativeRotation]; - } - if (userDataParsed.grabbableKey.spatialKey.rightRelativePosition) { - joints.RightHand = [userDataParsed.grabbableKey.spatialKey.rightRelativePosition, - userDataParsed.grabbableKey.spatialKey.relativeRotation]; - } - return { joints: joints }; - } - - } catch (err) { - // don't spam logs - } - return null; + return null } } @@ -556,7 +485,7 @@ function warnAboutUserData(props) { } var handJointIndex; - if (grabData.grabFollowsController) { + if (HMD.mounted && HMD.isHandControllerAvailable() && grabData.grabFollowsController) { handJointIndex = this.controllerJointIndex; } else { handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 53248980f1..2a360c0f31 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -298,7 +298,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.restoreIgnoredEntities = function() { - for (var i = 0; i < this.ignoredEntities; i++) { + for (var i = 0; i < this.ignoredEntities.length; i++) { var data = { action: 'remove', id: this.ignoredEntities[i] @@ -317,15 +317,6 @@ Script.include("/~/system/libraries/controllers.js"); if ((intersection.type === Picks.INTERSECTED_ENTITY && entityType === "Web") || intersection.type === Picks.INTERSECTED_OVERLAY || Window.isPointOnDesktopWindow(point2d)) { return true; - } else if (intersection.type === Picks.INTERSECTED_ENTITY && !Window.isPhysicsEnabled()) { - // add to ignored items. - var data = { - action: 'add', - id: intersection.objectID - }; - Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)); - this.ignoredEntities.push(intersection.objectID); - } return false; }; @@ -386,7 +377,6 @@ Script.include("/~/system/libraries/controllers.js"); this.isReady = function (controllerData) { if (HMD.active) { if (this.notPointingAtEntity(controllerData)) { - this.restoreIgnoredEntities(); return makeRunningValues(false, [], []); } @@ -398,17 +388,28 @@ Script.include("/~/system/libraries/controllers.js"); return makeRunningValues(true, [], []); } else { this.destroyContextOverlay(); - this.restoreIgnoredEntities(); return makeRunningValues(false, [], []); } } - this.restoreIgnoredEntities(); return makeRunningValues(false, [], []); }; this.run = function (controllerData) { + + var intersection = controllerData.rayPicks[this.hand]; + if (intersection.type === Picks.INTERSECTED_ENTITY && !Window.isPhysicsEnabled()) { + // add to ignored items. + if (this.ignoredEntities.indexOf(intersection.objectID) === -1) { + var data = { + action: 'add', + id: intersection.objectID + }; + Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)); + this.ignoredEntities.push(intersection.objectID); + } + } if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || - this.notPointingAtEntity(controllerData) || this.targetIsNull()) { + (this.notPointingAtEntity(controllerData) && Window.isPhysicsEnabled()) || this.targetIsNull()) { this.endFarGrabAction(); Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); diff --git a/scripts/system/controllers/controllerModules/farParentGrabEntity.js b/scripts/system/controllers/controllerModules/farParentGrabEntity.js index ac6c41d4d6..f85869aa7f 100644 --- a/scripts/system/controllers/controllerModules/farParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farParentGrabEntity.js @@ -262,7 +262,9 @@ Script.include("/~/system/libraries/controllers.js"); if (this.thisFarGrabJointIsParent(endProps)) { Entities.editEntity(this.targetEntityID, { parentID: this.previousParentID[this.targetEntityID], - parentJointIndex: this.previousParentJointIndex[this.targetEntityID] + parentJointIndex: this.previousParentJointIndex[this.targetEntityID], + localVelocity: {x: 0, y: 0, z: 0}, + localAngularVelocity: {x: 0, y: 0, z: 0} }); } diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index 2b17f447a0..6adfa88fb2 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -175,6 +175,23 @@ Script.include("/~/system/libraries/utils.js"); return this.exitModule(); } } + + var stopRunning = false; + + if ((controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0)) { + var stopRunning = false; + controllerData.nearbyOverlayIDs[this.hand].forEach(function(overlayID) { + var overlayName = Overlays.getProperty(overlayID, "name"); + if (overlayName === "KeyboardAnchor") { + stopRunning = true; + } + }); + + if (stopRunning) { + return this.exitModule(); + } + } + this.sendPickData(controllerData); return this.isReady(controllerData); }; diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index bbdcbaaa64..f354067a77 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -149,20 +149,12 @@ Script.include("/~/system/libraries/controllers.js"); this.hapticTargetID = null; var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID]; if (this.thisHandIsParent(props) && !this.robbed) { - if (this.previousParentID[this.targetEntityID] === Uuid.NULL || this.previousParentID === undefined) { - Entities.editEntity(this.targetEntityID, { - parentID: this.previousParentID[this.targetEntityID], - parentJointIndex: this.previousParentJointIndex[this.targetEntityID] - }); - } else { - // we're putting this back as a child of some other parent, so zero its velocity - Entities.editEntity(this.targetEntityID, { - parentID: this.previousParentID[this.targetEntityID], - parentJointIndex: this.previousParentJointIndex[this.targetEntityID], - localVelocity: {x: 0, y: 0, z: 0}, - localAngularVelocity: {x: 0, y: 0, z: 0} - }); - } + Entities.editEntity(this.targetEntityID, { + parentID: this.previousParentID[this.targetEntityID], + parentJointIndex: this.previousParentJointIndex[this.targetEntityID], + localVelocity: {x: 0, y: 0, z: 0}, + localAngularVelocity: {x: 0, y: 0, z: 0} + }); } var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; diff --git a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js index 763a0a0a27..9bddeb236a 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js @@ -46,6 +46,10 @@ Script.include("/~/system/libraries/utils.js"); return this.getOtherModule().thisHandIsParent(props); }; + this.isGrabbedThingVisible = function() { + return Overlays.getProperty(this.grabbedThingID, "visible"); + }; + this.thisHandIsParent = function(props) { if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== MyAvatar.SELF_ID) { return false; @@ -198,7 +202,7 @@ Script.include("/~/system/libraries/utils.js"); }; this.run = function (controllerData) { - if (controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0) { + if ((controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0) || !this.isGrabbedThingVisible()) { this.endNearParentingGrabOverlay(); this.robbed = false; return makeRunningValues(false, [], []); diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index 20a1e47bf2..1aba6b92f6 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -701,6 +701,10 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function(controllerData, deltaTime) { + if (Window.interstitialModeEnabled && !Window.isPhysicsEnabled()) { + return makeRunningValues(false, [], []); + } + var otherModule = this.getOtherModule(); if (!this.disabled && this.buttonValue !== 0 && !otherModule.active) { this.active = true; diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index da7743f7d6..d2cb7fffd1 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -18,6 +18,7 @@ Script.include("/~/system/libraries/controllers.js"); this.hand = hand; this.otherHand = this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND; this.running = false; + this.ignoredObjects = []; this.parameters = makeDispatcherModuleParameters( 160, @@ -72,6 +73,48 @@ Script.include("/~/system/libraries/controllers.js"); return this.hand === RIGHT_HAND ? leftOverlayLaserInput : rightOverlayLaserInput; }; + this.addObjectToIgnoreList = function(controllerData) { + if (Window.interstitialModeEnabled && !Window.isPhysicsEnabled()) { + var intersection = controllerData.rayPicks[this.hand]; + var objectID = intersection.objectID; + + if (intersection.type === Picks.INTERSECTED_OVERLAY) { + var overlayIndex = this.ignoredObjects.indexOf(objectID); + + var overlayName = Overlays.getProperty(objectID, "name"); + if (overlayName !== "Loading-Destination-Card-Text" && overlayName !== "Loading-Destination-Card-GoTo-Image" && + overlayName !== "Loading-Destination-Card-GoTo-Image-Hover") { + var data = { + action: 'add', + id: objectID + }; + Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)); + this.ignoredObjects.push(objectID); + } + } else if (intersection.type === Picks.INTERSECTED_ENTITY) { + var entityIndex = this.ignoredObjects.indexOf(objectID); + var data = { + action: 'add', + id: objectID + }; + Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)); + this.ignoredObjects.push(objectID); + } + } + }; + + this.restoreIgnoredObjects = function() { + for (var index = 0; index < this.ignoredObjects.length; index++) { + var data = { + action: 'remove', + id: this.ignoredObjects[index] + }; + Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)); + } + + this.ignoredObjects = []; + }; + this.isPointingAtTriggerable = function(controllerData, triggerPressed, checkEntitiesOnly) { // allow pointing at tablet, unlocked web entities, or web overlays automatically without pressing trigger, // but for pointing at locked web entities or non-web overlays user must be pressing trigger @@ -137,6 +180,10 @@ Script.include("/~/system/libraries/controllers.js"); return makeRunningValues(true, [], []); } } + + if (Window.interstitialModeEnabled && Window.isPhysicsEnabled()) { + this.restoreIgnoredObjects(); + } return makeRunningValues(false, [], []); }; @@ -149,6 +196,7 @@ Script.include("/~/system/libraries/controllers.js"); var allowThisModule = !otherModuleRunning && !grabModuleNeedsToRun; var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn; + this.addObjectToIgnoreList(controllerData); if (allowThisModule) { if (isTriggerPressed && !this.isPointingAtTriggerable(controllerData, isTriggerPressed, true)) { // if trigger is down + not pointing at a web entity, keep running web surface laser diff --git a/scripts/system/edit.js b/scripts/system/edit.js index b911541f79..c8811bd603 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -12,7 +12,7 @@ /* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, - progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool, OverlaySystemWindow */ + progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, OverlaySystemWindow */ (function() { // BEGIN LOCAL_SCOPE @@ -32,7 +32,6 @@ Script.include([ "libraries/gridTool.js", "libraries/entityList.js", "libraries/utils.js", - "particle_explorer/particleExplorerTool.js", "libraries/entityIconOverlayManager.js" ]); @@ -42,6 +41,9 @@ var TITLE_OFFSET = 60; var CREATE_TOOLS_WIDTH = 490; var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; +var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; +var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; + var createToolsWindow = new CreateWindow( Script.resourcesPath() + "qml/hifi/tablet/EditTools.qml", 'Create Tools', @@ -109,28 +111,6 @@ var entityListTool = new EntityListTool(shouldUseEditTabletApp); selectionManager.addEventListener(function () { selectionDisplay.updateHandles(); entityIconOverlayManager.updatePositions(); - - // Update particle explorer - var needToDestroyParticleExplorer = false; - if (selectionManager.selections.length === 1) { - var selectedEntityID = selectionManager.selections[0]; - if (selectedEntityID === selectedParticleEntityID) { - return; - } - var type = Entities.getEntityProperties(selectedEntityID, "type").type; - if (type === "ParticleEffect") { - selectParticleEntity(selectedEntityID); - } else { - needToDestroyParticleExplorer = true; - } - } else { - needToDestroyParticleExplorer = true; - } - - if (needToDestroyParticleExplorer && selectedParticleEntityID !== null) { - selectedParticleEntityID = null; - particleExplorerTool.destroyWebView(); - } }); var KEY_P = 80; //Key code for letter p used for Parenting hotkey. @@ -294,6 +274,202 @@ function checkEditPermissionsAndUpdate() { } } +const DEFAULT_ENTITY_PROPERTIES = { + All: { + description: "", + rotation: { x: 0, y: 0, z: 0, w: 1 }, + collidesWith: "static,dynamic,kinematic,otherAvatar,myAvatar", + collisionSoundURL: "", + cloneable: false, + ignoreIK: true, + canCastShadow: true, + href: "", + script: "", + serverScripts:"", + velocity: { + x: 0, + y: 0, + z: 0 + }, + damping: 0, + angularVelocity: { + x: 0, + y: 0, + z: 0 + }, + angularDamping: 0, + restitution: 0.5, + friction: 0.5, + density: 1000, + gravity: { + x: 0, + y: 0, + z: 0 + }, + acceleration: { + x: 0, + y: 0, + z: 0 + }, + dynamic: false, + }, + Shape: { + shape: "Box", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + color: { red: 0, green: 180, blue: 239 }, + }, + Text: { + text: "Text", + dimensions: { + x: 0.65, + y: 0.3, + z: 0.01 + }, + textColor: { red: 255, green: 255, blue: 255 }, + backgroundColor: { red: 0, green: 0, blue: 0 }, + lineHeight: 0.06, + faceCamera: false, + }, + Zone: { + dimensions: { + x: 10, + y: 10, + z: 10 + }, + flyingAllowed: true, + ghostingAllowed: true, + filter: "", + keyLightMode: "inherit", + keyLightColor: { red: 255, green: 255, blue: 255 }, + keyLight: { + intensity: 1.0, + direction: { + x: 0.0, + y: -0.707106769084930, // 45 degrees + z: 0.7071067690849304 + }, + castShadows: true + }, + ambientLightMode: "inherit", + ambientLight: { + ambientIntensity: 0.5, + ambientURL: "" + }, + hazeMode: "inherit", + haze: { + hazeRange: 1000, + hazeAltitudeEffect: false, + hazeBaseRef: 0, + hazeColor: { + red: 128, + green: 154, + blue: 179 + }, + hazeBackgroundBlend: 0, + hazeEnableGlare: false, + hazeGlareColor: { + red: 255, + green: 229, + blue: 179 + }, + }, + bloomMode: "inherit" + }, + Model: { + collisionShape: "none", + compoundShapeURL: "", + animation: { + url: "", + running: false, + allowTranslation: false, + loop: true, + hold: false, + currentFrame: 0, + firstFrame: 0, + lastFrame: 100000, + fps: 30.0, + } + }, + Image: { + dimensions: { + x: 0.5385, + y: 0.2819, + z: 0.0092 + }, + shapeType: "box", + collisionless: true, + modelURL: IMAGE_MODEL, + textures: JSON.stringify({ "tex.picture": "" }) + }, + Web: { + dimensions: { + x: 1.6, + y: 0.9, + z: 0.01 + }, + sourceUrl: "https://highfidelity.com/", + dpi: 30, + }, + ParticleEffect: { + lifespan: 1.5, + maxParticles: 10, + textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png", + emitRate: 5.5, + emitSpeed: 0, + speedSpread: 0, + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0, w: 1 }, + emitterShouldTrail: true, + particleRadius: 0.25, + radiusStart: 0, + radiusFinish: 0.1, + radiusSpread: 0, + particleColor: { + red: 255, + green: 255, + blue: 255 + }, + colorSpread: { + red: 0, + green: 0, + blue: 0 + }, + alpha: 0, + alphaStart: 1, + alphaFinish: 0, + alphaSpread: 0, + emitAcceleration: { + x: 0, + y: 2.5, + z: 0 + }, + accelerationSpread: { + x: 0, + y: 0, + z: 0 + }, + particleSpin: 0, + spinStart: 0, + spinFinish: 0, + spinSpread: 0, + rotateWithEntity: false, + polarStart: 0, + polarFinish: 0, + azimuthStart: -Math.PI, + azimuthFinish: Math.PI + }, + Light: { + color: { red: 255, green: 255, blue: 255 }, + intensity: 5.0, + dimensions: DEFAULT_LIGHT_DIMENSIONS, + falloffRadius: 1.0, + isSpotlight: false, + exponent: 1.0, + cutoff: 75.0, + dimensions: { x: 20, y: 20, z: 20 }, + }, +}; + var toolBar = (function () { var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts var that = {}, @@ -303,11 +479,34 @@ var toolBar = (function () { dialogWindow = null, tablet = null; - function createNewEntity(properties) { - var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS; + function applyProperties(originalProperties, newProperties) { + for (var key in newProperties) { + originalProperties[key] = newProperties[key]; + } + } + function createNewEntity(requestedProperties) { + var dimensions = requestedProperties.dimensions ? requestedProperties.dimensions : DEFAULT_DIMENSIONS; var position = getPositionToCreateEntity(); var entityID = null; + var properties = {}; + + applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All); + + var type = requestedProperties.type; + if (type == "Box" || type == "Sphere") { + applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape); + } else if (type == "Image") { + requestedProperties.type = "Model"; + applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Image); + } else { + applyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]); + } + + // We apply the requested properties first so that they take priority over any default properties. + applyProperties(properties, requestedProperties); + + if (position !== null && position !== undefined) { var direction; if (Camera.mode === "entity" || Camera.mode === "independent") { @@ -363,10 +562,6 @@ var toolBar = (function () { properties: properties }], [], true); - if (properties.type === "ParticleEffect") { - selectParticleEntity(entityID); - } - var POST_ADJUST_ENTITY_TYPES = ["Model"]; if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { // Adjust position of entity per bounding box after it has been created and auto-resized. @@ -385,7 +580,7 @@ var toolBar = (function () { Entities.editEntity(entityID, { position: position }); - selectionManager._update(); + selectionManager._update(false, this); } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) { Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); } @@ -397,9 +592,9 @@ var toolBar = (function () { properties.type + " would be out of bounds."); } - selectionManager.clearSelections(); + selectionManager.clearSelections(this); entityListTool.sendUpdate(); - selectionManager.setSelections([entityID]); + selectionManager.setSelections([entityID], this); Window.setFocus(); @@ -550,7 +745,7 @@ var toolBar = (function () { } deletedEntityTimer = Script.setTimeout(function () { if (entitiesToDelete.length > 0) { - selectionManager.removeEntities(entitiesToDelete); + selectionManager.removeEntities(entitiesToDelete, this); } entityListTool.removeEntities(entitiesToDelete, selectionManager.selections); entitiesToDelete = []; @@ -655,159 +850,48 @@ var toolBar = (function () { addButton("newCubeButton", function () { createNewEntity({ type: "Box", - dimensions: DEFAULT_DIMENSIONS, - color: { - red: 255, - green: 0, - blue: 0 - } }); }); addButton("newSphereButton", function () { createNewEntity({ type: "Sphere", - dimensions: DEFAULT_DIMENSIONS, - color: { - red: 255, - green: 0, - blue: 0 - } }); }); addButton("newLightButton", function () { createNewEntity({ type: "Light", - dimensions: DEFAULT_LIGHT_DIMENSIONS, - isSpotlight: false, - color: { - red: 150, - green: 150, - blue: 150 - }, - - constantAttenuation: 1, - linearAttenuation: 0, - quadraticAttenuation: 0, - exponent: 0, - cutoff: 180 // in degrees }); }); addButton("newTextButton", function () { createNewEntity({ type: "Text", - dimensions: { - x: 0.65, - y: 0.3, - z: 0.01 - }, - backgroundColor: { - red: 64, - green: 64, - blue: 64 - }, - textColor: { - red: 255, - green: 255, - blue: 255 - }, - text: "some text", - lineHeight: 0.06 }); }); addButton("newImageButton", function () { - var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; - var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; createNewEntity({ - type: "Model", - dimensions: { - x: 0.5385, - y: 0.2819, - z: 0.0092 - }, - shapeType: "box", - collisionless: true, - modelURL: IMAGE_MODEL, - textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE }) + type: "Image", }); }); addButton("newWebButton", function () { createNewEntity({ type: "Web", - dimensions: { - x: 1.6, - y: 0.9, - z: 0.01 - }, - sourceUrl: "https://highfidelity.com/" }); }); addButton("newZoneButton", function () { createNewEntity({ type: "Zone", - dimensions: { - x: 10, - y: 10, - z: 10 - } }); }); addButton("newParticleButton", function () { createNewEntity({ type: "ParticleEffect", - isEmitting: true, - emitterShouldTrail: true, - color: { - red: 200, - green: 200, - blue: 200 - }, - colorSpread: { - red: 0, - green: 0, - blue: 0 - }, - colorStart: { - red: 200, - green: 200, - blue: 200 - }, - colorFinish: { - red: 0, - green: 0, - blue: 0 - }, - emitAcceleration: { - x: -0.5, - y: 2.5, - z: -0.5 - }, - accelerationSpread: { - x: 0.5, - y: 1, - z: 0.5 - }, - emitRate: 5.5, - emitSpeed: 0, - speedSpread: 0, - lifespan: 1.5, - maxParticles: 10, - particleRadius: 0.25, - radiusStart: 0, - radiusFinish: 0.1, - radiusSpread: 0, - alpha: 0, - alphaStart: 1, - alphaFinish: 0, - polarStart: 0, - polarFinish: 0, - textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png" }); }); @@ -866,7 +950,7 @@ var toolBar = (function () { gridTool.setVisible(false); grid.setEnabled(false); propertiesTool.setVisible(false); - selectionManager.clearSelections(); + selectionManager.clearSelections(this); cameraManager.disable(); selectionDisplay.disableTriggerMapping(); tablet.landscape = false; @@ -994,7 +1078,7 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) { var entity = entityIconOverlayManager.findEntity(data.overlayID); if (entity !== null) { - selectionManager.setSelections([entity]); + selectionManager.setSelections([entity], this); } } } @@ -1141,7 +1225,7 @@ function mouseClickEvent(event) { if (result === null || result === undefined) { if (!event.isShifted) { - selectionManager.clearSelections(); + selectionManager.clearSelections(this); } return; } @@ -1185,17 +1269,10 @@ function mouseClickEvent(event) { orientation = MyAvatar.orientation; intersection = rayPlaneIntersection(pickRay, P, Quat.getForward(orientation)); - if (event.isShifted) { - particleExplorerTool.destroyWebView(); - } - if (properties.type !== "ParticleEffect") { - particleExplorerTool.destroyWebView(); - } - if (!event.isShifted) { - selectionManager.setSelections([foundEntity]); + selectionManager.setSelections([foundEntity], this); } else { - selectionManager.addEntity(foundEntity, true); + selectionManager.addEntity(foundEntity, true, this); } if (wantDebug) { @@ -1493,7 +1570,7 @@ function selectAllEtitiesInCurrentSelectionBox(keepIfTouching) { } } } - selectionManager.setSelections(entities); + selectionManager.setSelections(entities, this); } } @@ -1610,8 +1687,6 @@ function deleteSelectedEntities() { if (SelectionManager.hasSelection()) { var deletedIDs = []; - selectedParticleEntityID = null; - particleExplorerTool.destroyWebView(); SelectionManager.saveProperties(); var savedProperties = []; var newSortedSelection = sortSelectedEntities(selectionManager.selections); @@ -1633,7 +1708,7 @@ function deleteSelectedEntities() { } if (savedProperties.length > 0) { - SelectionManager.clearSelections(); + SelectionManager.clearSelections(this); pushCommandForSelections([], savedProperties); entityListTool.deleteEntities(deletedIDs); } @@ -1650,7 +1725,7 @@ function toggleSelectedEntitiesLocked() { }); } entityListTool.sendUpdate(); - selectionManager._update(); + selectionManager._update(false, this); } } @@ -1664,7 +1739,7 @@ function toggleSelectedEntitiesVisible() { }); } entityListTool.sendUpdate(); - selectionManager._update(); + selectionManager._update(false, this); } } @@ -1861,7 +1936,7 @@ function importSVO(importURL) { } if (isActive) { - selectionManager.setSelections(pastedEntityIDs); + selectionManager.setSelections(pastedEntityIDs, this); } } else { Window.notifyEditError("Can't import entities: entities would be out of bounds."); @@ -1909,7 +1984,7 @@ function deleteKey(value) { } function deselectKey(value) { if (value === 0) { // on release - selectionManager.clearSelections(); + selectionManager.clearSelections(this); } } function toggleKey(value) { @@ -2069,7 +2144,7 @@ function applyEntityProperties(data) { // We might be getting an undo while edit.js is disabled. If that is the case, don't set // our selections, causing the edit widgets to display. if (isActive) { - selectionManager.setSelections(selectedEntityIDs); + selectionManager.setSelections(selectedEntityIDs, this); } } @@ -2218,9 +2293,11 @@ var PropertiesTool = function (opts) { if (entity.properties.rotation !== undefined) { entity.properties.rotation = Quat.safeEulerAngles(entity.properties.rotation); } + if (entity.properties.emitOrientation !== undefined) { + entity.properties.emitOrientation = Quat.safeEulerAngles(entity.properties.emitOrientation); + } if (entity.properties.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) { - entity.properties.keyLight.direction = Vec3.multiply(RADIANS_TO_DEGREES, - Vec3.toPolar(entity.properties.keyLight.direction)); + entity.properties.keyLight.direction = Vec3.toPolar(entity.properties.keyLight.direction); entity.properties.keyLight.direction.z = 0.0; } selections.push(entity); @@ -2256,23 +2333,29 @@ var PropertiesTool = function (opts) { data.properties.angularVelocity = Vec3.ZERO; } if (data.properties.rotation !== undefined) { - var rotation = data.properties.rotation; - data.properties.rotation = Quat.fromPitchYawRollDegrees(rotation.x, rotation.y, rotation.z); + data.properties.rotation = Quat.fromVec3Degrees(data.properties.rotation); + } + if (data.properties.emitOrientation !== undefined) { + data.properties.emitOrientation = Quat.fromVec3Degrees(data.properties.emitOrientation); } if (data.properties.keyLight !== undefined && data.properties.keyLight.direction !== undefined) { - data.properties.keyLight.direction = Vec3.fromPolar( - data.properties.keyLight.direction.x * DEGREES_TO_RADIANS, - data.properties.keyLight.direction.y * DEGREES_TO_RADIANS - ); + var currentKeyLightDirection = Vec3.toPolar(Entities.getEntityProperties(selectionManager.selections[0], ['keyLight.direction']).keyLight.direction); + if (data.properties.keyLight.direction.x === undefined) { + data.properties.keyLight.direction.x = currentKeyLightDirection.x; + } + if (data.properties.keyLight.direction.y === undefined) { + data.properties.keyLight.direction.y = currentKeyLightDirection.y; + } + data.properties.keyLight.direction = Vec3.fromPolar(data.properties.keyLight.direction.x, data.properties.keyLight.direction.y); } Entities.editEntity(selectionManager.selections[0], data.properties); if (data.properties.name !== undefined || data.properties.modelURL !== undefined || data.properties.materialURL !== undefined || - data.properties.visible !== undefined || data.properties.locked !== undefined) { + data.properties.visible !== undefined || data.properties.locked !== undefined) { entityListTool.sendUpdate(); } } pushCommandForSelections(); - selectionManager._update(); + selectionManager._update(false, this); } else if (data.type === 'parent') { parentSelectedEntities(); } else if (data.type === 'unparent') { @@ -2301,7 +2384,7 @@ var PropertiesTool = function (opts) { }); } pushCommandForSelections(); - selectionManager._update(); + selectionManager._update(false, this); } } else if (data.action === "moveAllToGrid") { if (selectionManager.hasSelection()) { @@ -2321,7 +2404,7 @@ var PropertiesTool = function (opts) { }); } pushCommandForSelections(); - selectionManager._update(); + selectionManager._update(false, this); } } else if (data.action === "resetToNaturalDimensions") { if (selectionManager.hasSelection()) { @@ -2342,7 +2425,7 @@ var PropertiesTool = function (opts) { } } pushCommandForSelections(); - selectionManager._update(); + selectionManager._update(false, this); } } else if (data.action === "previewCamera") { if (selectionManager.hasSelection()) { @@ -2360,7 +2443,7 @@ var PropertiesTool = function (opts) { }); } pushCommandForSelections(); - selectionManager._update(); + selectionManager._update(false, this); } } else if (data.action === "reloadClientScripts") { if (selectionManager.hasSelection()) { @@ -2380,9 +2463,22 @@ var PropertiesTool = function (opts) { } } else if (data.type === "propertiesPageReady") { updateSelections(true); + } else if (data.type === "tooltipsRequest") { + emitScriptEvent({ + type: 'tooltipsReply', + tooltips: Script.require('./assets/data/createAppTooltips.json'), + hmdActive: HMD.active, + }); } }; + HMD.displayModeChanged.connect(function() { + emitScriptEvent({ + type: 'hmdActiveChanged', + hmdActive: HMD.active, + }); + }); + createToolsWindow.webEventReceived.addListener(this, onWebEventReceived); webView.webEventReceived.connect(onWebEventReceived); @@ -2613,31 +2709,6 @@ propertyMenu.onSelectMenuItem = function (name) { var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); var propertiesTool = new PropertiesTool(); -var particleExplorerTool = new ParticleExplorerTool(createToolsWindow); -var selectedParticleEntityID = null; - -function selectParticleEntity(entityID) { - selectedParticleEntityID = entityID; - - var properties = Entities.getEntityProperties(entityID); - if (properties.emitOrientation) { - properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation); - } - - particleExplorerTool.destroyWebView(); - particleExplorerTool.createWebView(); - - particleExplorerTool.setActiveParticleEntity(entityID); - - // Switch to particle explorer - var selectTabMethod = { method: 'selectTab', params: { id: 'particle' } }; - if (shouldUseEditTabletApp()) { - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - tablet.sendToQml(selectTabMethod); - } else { - createToolsWindow.sendToQml(selectTabMethod); - } -} entityListTool.webView.webEventReceived.connect(function(data) { try { @@ -2651,20 +2722,6 @@ entityListTool.webView.webEventReceived.connect(function(data) { parentSelectedEntities(); } else if (data.type === 'unparent') { unparentSelectedEntities(); - } else if (data.type === "selectionUpdate") { - var ids = data.entityIds; - if (ids.length === 1) { - if (Entities.getEntityProperties(ids[0], "type").type === "ParticleEffect") { - if (JSON.stringify(selectedParticleEntityID) === JSON.stringify(ids[0])) { - // This particle entity is already selected, so return - return; - } - // Destroy the old particles web view first - } else { - selectedParticleEntityID = 0; - particleExplorerTool.destroyWebView(); - } - } } }); diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 8e7b3f1ad5..7d2350e1c8 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -198,7 +198,7 @@ td.url { } -input[type="text"], input[type="number"], textarea { +input[type="text"], input[type="search"], input[type="number"], textarea { margin: 0; padding: 0 12px; color: #afafaf; @@ -257,12 +257,22 @@ input[type="text"] { width: 100%; } +input[type="search"] { + height: 28px; + width: 100%; +} +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; + height: 20px; + width: 20px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goNAQIFbBwsbwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAZfSURBVDgRAVQGq/kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9PT0YAwMDBgAAAAD8/Pz5+vr67MrKyv0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAA+Pj4KAgICQgAAAE3///9RAQEBFQAAAAD////pAQEBu/39/ab+/v7BxcXF9gAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAADs7OzMEBASIAQEBRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAACm+/v7cMXFxewAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAPT09OwEBAagBAQEcAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8AAAAA2f///2XCwsLDAAAAAAAAAAABAAAAAAAAAAA9PT0KAwMDt////z4AAAAAAAAAAAEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAcIBAQFJvr6+9gAAAAACAAAAAAAAAAAAAABg////PgEBAQAAAAAAS0tLADg4OAAAAAAAAAAAAP///wADAwMAQEBAACEhIQD///8A////AP7+/j76+vpWAAAAAAAAAAACAAAAAD09PQ8CAgJkAQEBAP///wD///8ACgoKAFhYWAAyMjIAAAAAAAICAgBGRkYAT09PABEREQAAAAAAAAAAAAAAAAACAgJwOjo6EAAAAAAEAAAAAAICAg8BAQExAAAAAAEBAQABAQEAsrKyAAoKCgBaWloA9/f3ABsbGwBISEgAtra2AM7OzgACAgIA////AP///wABAQEuBQUFDgAAAPAEAAAAAPz8/BkEBAQAAQEBAAAAAAAAAAAA+vr6AKioqAALCwsAZWVlAAcHBwC/v78Au7u7AAEBAQD///8AAAAAAAAAAAAAAAABAAAAAAAAAAACAAAAAAQEBOgBAQEAAQEBAAEBAQABAQEAAQEBAPz8/ADT09MADg4OAP39/QDQ0NAA/v7+AP///wAAAAAAAAAAAAEBAQABAQEAAQEBAAAAAAACAAAAAAAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAACkpKQBQUFAAx8fHAObm5gBfX18AFxcXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAP39/fz+/v7z////AP///wD///8AJycnAGFhYQDc3NwApaWlAJaWlgD29vYAZmZmABQUFAACAgIAAQEBAAEBAQABAQH1AAAA/AAAAAACAAAAAPr6+ukBAQGkAAAAAAAAAAABAQEAQEBAAObm5gCmpqYA+fn5APPz8wCdnZ0A////ACwsLAD///8AAAAAAAAAAAD///+k9vb26QAAAAABAAAAAAAAAAA+Pj4uAgICxgAAAAsAAAAAEBAQAPr6+gD29vYAAAAAAAAAAAABAQEAAgICAP///wD+/v4AAAAAAAAAAPL8/Pw/xMTE0AAAAAACAAAAAAAAAAD5+fnV////nQICAgABAQEA8fHxAPX19QABAQEAAAAAAAAAAAD///8A/v7+AP7+/gAAAAAAAAAAAP7+/p36+vrSAAAAAAAAAAADAAAAAAAAAADl5eX/ICAgwQAAAA////8q////BgEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD1/f39mAEBAXrGxsb7AAAAAAAAAAADAAAAAAAAAAAAAAAA4eHh/BgYGLsBAQHDBAQEHAAAACP///8AAQEBAAAAAAAAAAAAAAAA+////7QBAQFu+fn5m8bGxvoAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPz8/Cv7+/iUBAQFMAgICEQICAgD8/PzdAwMDs/j4+OvHx8f5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8TUnpZ7EwQgAAAABJRU5ErkJggg==') +} + input[type="number"] { position: relative; height: 28px; width: 124px; } - input[type=number] { padding-right: 3px; } @@ -300,6 +310,28 @@ input[type=number].hover-down::-webkit-inner-spin-button:after { color: #ffffff; } +input[type=range] { + -webkit-appearance: none; + background: #2e2e2e; + height: 1.8rem; + border-radius: 1rem; +} +input[type=range]::-webkit-slider-thumb { + -webkit-appearance:none; + width: 0.6rem; + height: 1.8rem; + padding:0; + margin: 0; + background-color: #696969; + border-radius: 1rem; +} +input[type=range]::-webkit-slider-thumb:hover { + background-color: white; +} +input[type=range]:focus { /*#252525*/ + outline: none; +} + input.no-spin::-webkit-outer-spin-button, input.no-spin::-webkit-inner-spin-button { display: none; @@ -448,14 +480,15 @@ input[type=checkbox]:checked + label:hover { border: 1.5pt solid black; } -.shape-section, .light-section, .model-section, .web-section, .image-section, .hyperlink-section, .text-section, .zone-section, .material-section { - display: table; +#properties-list { + display: flex; + flex-direction: column; } #properties-list fieldset { position: relative; /* 0.1px on the top is to prevent margin collapsing between this and it's first child */ - margin: 21px -21px 0 -21px; + margin: 0 -21px 21px -21px; padding: 0.1px 21px 0 21px; border: none; border-top: 1px rgb(90,90,90) solid; @@ -482,13 +515,6 @@ input[type=checkbox]:checked + label:hover { box-shadow: none; } -#properties-list > fieldset#properties-header { - margin-top: 0; - padding-bottom: 0; -} - - - #properties-list > fieldset > legend { position: relative; display: table; @@ -507,7 +533,7 @@ input[type=checkbox]:checked + label:hover { box-shadow: 0 -1px 0 rgb(37,37,37), 0 4px 4px 0 rgba(0,0,0,0.75); } -div.section-header, .sub-section-header, hr { +div.section-header, hr { display: table; width: 100%; margin: 21px -21px 0 -21px; @@ -520,17 +546,6 @@ div.section-header, .sub-section-header, hr { outline: none; } - - -.column .sub-section-header { - background-image: none; - padding-top: 0; -} - -.sub-section-header, .no-collapse, hr { - background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left; -} - div.section-header:first-child { margin-top: -2px; padding-top: 0; @@ -538,10 +553,6 @@ div.section-header:first-child { height: auto; } -.sub-section-header { - margin-bottom: -10px; -} - #properties-list > fieldset > legend span, .section-header span { font-family: HiFi-Glyphs; font-size: 30px; @@ -555,26 +566,21 @@ div.section-header:first-child { margin-bottom: -21px; } +#properties-list .sub-section-header { + border-top: none; + box-shadow: none; + margin-top: 8px; +} + +.sub-section-header + .property { + margin-top: 0; +} + hr { border: none; padding-top: 2px; } -.text-group[collapsed="true"] ~ .text-group, -.zone-group[collapsed="true"] ~ .zone-group, -.image-group[collapsed="true"] ~ .image-group, -.web-group[collapsed="true"] ~ .web-group, -.hyperlink-group[collapsed="true"] ~ .hyperlink-group, -.spatial-group[collapsed="true"] ~ .spatial-group, -.physical-group[collapsed="true"] ~ .physical-group, -.behavior-group[collapsed="true"] ~ .behavior-group, -.model-group[collapsed="true"] ~ .model-group, -.material-group[collapsed="true"] ~ .material-group, -.light-group[collapsed="true"] ~ .light-group { - display: none !important; -} - - .property { display: table; width: 100%; @@ -644,7 +650,16 @@ hr { margin-left: 10px; } -.text label, .url label, .number label, .textarea label, .xy label, .wh label, .rgb label, .xyz label,.pyr label, .dropdown label, .gen label { +.property.range label{ + padding-bottom: 3px; +} +.property.range input[type=number]{ + margin-left: 0.8rem; + width: 5.4rem; + height: 1.8rem; +} + +.text label, .url label, .number label, .textarea label, .xy label, .wh label, .rgb label, .xyz label, .pyr label, .dropdown label, .gen label { float: left; margin-left: 1px; margin-bottom: 3px; @@ -658,6 +673,10 @@ hr { margin-top: -2px; } +.xy > div, .wh > div, .xyz > div, .pyr > div, .gen > div { + clear: both; +} + .number > input { clear: both; float: left; @@ -666,9 +685,6 @@ hr { clear: both; float: left; } -.xy > div, .wh > div, .xyz > div, .pyr > div, .gen > div { - clear: both; -} .dropdown { position: relative; @@ -761,6 +777,29 @@ hr { color: #252525; } +.multiselect { + position: relative; +} +.select-box { + position: absolute; +} +.select-box select { + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #afafaf; + background-color: #252525; + border: none; + height: 28px; + width: 107px; + text-align-last: center; +} +.over-select { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; +} div.refresh { box-sizing: border-box; @@ -800,15 +839,6 @@ div.refresh input[type="button"] { display: none !important; } -#property-color-control1 { - display: table-cell; - float: none; -} - -#property-color-control1 + label { - border-left: 20px transparent solid; -} - .rgb label { float: left; margin-top: 10px; @@ -873,23 +903,23 @@ div.refresh input[type="button"] { font-family: FiraSans-SemiBold; font-size: 12px; } -.tuple .red + label, .tuple .x + label, .tuple .pitch + label { +.tuple .red + label, .tuple .x + label, .tuple .pitch + label, .tuple .width + label { color: #e2334d; } -.tuple .green + label, .tuple .y + label, .tuple .yaw + label { +.tuple .green + label, .tuple .y + label, .tuple .yaw + label, .tuple .height + label { color: #1ac567; } .tuple .blue + label, .tuple .z + label, .tuple .roll + label { color: #1080b8; } -.tuple .red:focus, .tuple .x:focus, .tuple .pitch:focus { +.tuple .red:focus, .tuple .x:focus, .tuple .pitch:focus, .tuple .width:focus { outline-color: #e2334d; } -.tuple .green:focus, .tuple .y:focus, .tuple .yaw:focus { +.tuple .green:focus, .tuple .y:focus, .tuple .yaw:focus, .tuple .height:focus { outline-color: #1ac567; } -tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { +.tuple .blue:focus, .tuple .z:focus, .tuple .roll:focus { outline-color: #1080b8; } @@ -914,6 +944,37 @@ tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { float: left; } +.property.texture { + display: block; +} +.property.texture input{ + margin: 0.4rem 0; +} +.texture-image img{ + padding: 0; + margin: 0; + width: 100%; + height: 100%; + display: none; +} +.texture-image { + display: block; + position: relative; + background-repeat: no-repeat; + background-position: center; + background-size: 100% 100%; + margin-top: 0.4rem; + height:128px; + width: 128px; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAABhNJREFUeNrsnVFy4joQRVsSCwAqBMwqsrRsIavMEkICoeAf2+8j1R5ZGDBgpLzoUDVVmTT2dc8It/paOpi3t7faOSciImVZyn6/l6qqRETEWivj8VistYPFd7ud1HUtIiLGGBmPx5JKX0RkMplIzvmPnHNijBERaS7Ef1lrB40bY1oXgH5a/ZH+8P7+LlVVycfHR/MGa60URdGcYOi4MUaKomhGaGx9EZHlcplMP2X+Ly8vPwOgLEtxzklVVVJVVfOznqAsy9YFXhuvqqq5AF/Lj+srtr7+LpV+yvz1mNF+vxcRkdVqJdZaeXp6ap1ws9m0TjibzVoj6lJ8vV6fjJdlKev1ujViU+j7t8tc8p9Op1KWpYw06L9JL0Av0r9l+jXl3nhd11JV1VE8tn5YM3PI3xjzoxVOGvyDU7zQj6s/0tGmI++amtNV087F9Wf/FnVPzRtCXz8RdV1nlb/efUbaJy4Wi0FqzjU1yRgjs9ls0Jp3jb6IyPPzczL9lPkvFot/dwCtB/om/x9oyJoXxps65NW8mPpdNTeX/JtBEtYE/+AUL/Tj6g/qA3TVnD41a6g++Bp9rYOp9FPnH80HOBcvy1I2m81D++BL+o/2AX5r/vgA+AD4AOif8AH8EdpVcy71sX3jWp/8W2AKff/TkUv+Oufr9AF0YuKc66xJ18T7eNP3nP9WfZ0EzufzJPqp8y+KQuq67vYBdETqCDpVU/rEw5oUnr+rD46h73/qUuinzh8fAP22D6AjxznXcqq6akrf+KmaFB6vf4+t7/sAelfIJf/GB9jtdmKMkdVq1dQM3zg4VVNU/NY+1Bgjh8Oh6YM1+dj6X19fzXwgp/wbH0DFtS7oyf0RdKqmhPFr+1RdseKfP7a+Px/IKX98APTbPoDOJrv60L417d54TH3V8lfS5pT/yfUA6/X6qOZcqkm3xrUm6X9CTH3fB0ihnzr/Ix9A/3T1qbfWpGvjMfX9T0UK/dT54wOg/88H8EfGPTVr6D740frhLDmn/Hv5AH1qku9t31KTzh3/aP1LPsBfzr+XDxCO0K6ack/N6qp5MfUv+QB/Of/ePsCQfWmfc6EfV3/kjzZrrRwOh9YtKHSm/LjOH3yrMTzej4c1y//51PHoP0a/tR7AOSdFURw9rz5VU049zw7jl2qWrosP++BY+iI/+wJS6afMv9kXoA6gvimsieHzZr/m6MTp3PPuc3G9SP95OPpx9JtOgT4cHwA+QCJ9+ADwAeADsC+AfQHo/4b1APAB4APAB4APAB8APgB9OD4AfAD4AFFqEnwA+AD4APgA6P86HwA+AHyAZhIBHwA+AHwA+AD04X/eB4APAB8APgB8APgA8AHow/P0AeADwAeADwAfAD4AfAD68Px8APgA8AHgA8AHgA8AH0DO70/v6lHvjaOfVn8U/iLcXx5OUML96X49vRTX3/nPw9FPo9+sB5hMJuKck+VyeVRTrLWtdfNdcf95eldNCuOfn5+tSYy/Pz+2voi0fICc8p/P5z93gJAPEN4+wufN4evaePj99eH+ePTj6p/1Abp60kt9Ksf/v46HDwAfAD6A/6gUPgD7AtgXwPP4DNcDwAeADwAfAD4AfAD4ADyPz289AHyA+Pqp84cPIPAB8AHwAfAB8AHgA7Q+HfAB4APAB4APAB+APjw3HwA+AHwA+ADwAeADwAegD8/TB4APAB8APgB8APgA8AHow/PzAeADwAeADwAfAD4AfACJ//316KfVH/mjLeb31+vx/kWhH0+/tR7AOSdFUUT9/nq9oK4+OJa+iLT25+eUf7MvIOQDxPr+en2F++PRj6PfdAr04fgA8AES6cMHgA8AH4B9AewLQP83rAeADwAfAD4AfAD4APAB6MPxAeADwAeIUpPgA8AHwAfAB0D/1/kA8AHgAzSTCPgA8AHgA8AHoA//8z4AfAD4APAB4APAB4APQB+epw8AHwA+AHwA+ADwAeAD0Ifn5wPAB4APAB8APgB8gBz5AOb19bX2TYLpdNpqQ7bbbctJGjJeVZVst9vWLSu2/vf3t+Sc/yicFIRr0C7Fu76f/lw8XBePflr9/wYAqWwWUSLcO54AAAAASUVORK5CYII='); +} +.texture-image.no-texture { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAB81JREFUeNrsnTGPm0oXht97FWm2Ch2pTEeHpUihsyvTuXO67Ta/IPkr+Qfp3MWdO7Zad0SKZDo6XIWOrTzV9xVXZ8SygGHXG4/t96lW68GGw8vMmZlzDv98+/btfyBXy780wXXzTv74/fs3rXFFfPz4kT0AoQAoAJqAAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQCgAQgEQCoBQAIQCIBQAoQAIBUAoAHLmvDv3C7i7u4PjOMiyDOv1+mC75XKJoiga2wRBAN/34TgOHMdBWZYoigJpmiLPcwrARhzHAQD4vg/P81pvlLRrwvM8zGYz00ZrbY5xHAe+7yPPc9zf36MsSwrAVmazGX78+DHoGM/zsFgsAAB5nmOz2ZgeQimF8XiMMAxNu+VyaQRCH8Ai8jyH4zgIw7D3MUopzOdzAECaplitVk+GB601kiTBz58/obWG4ziIoohOoI38+vULABCGYWd3X2U6nUIphbIsEcdxa7uiKPDw8GCGGtd1KQDbKMsSWZZBKYXJZNLrGN/3zdN/iDRNTdcvx1EAFqGUwmazeeIQduG6LpRSAIAsy3r9hrRjD2BxL5AkiXEI+8wetNa9PXtp13eIoQBOQJIkxmHrcgjlJkov8JKpJwVgIVpr47CFYdh6g/f7/ZM5/9CehgKwmDRNURQFlFKYTqeNN/rx8dH0AH2faBn7KYAzQKZ1QRCYZd0qf/78MX+PRqNe3ymO5W63owBsR9bwZShoGirEq++zeBQEweBZAwVwYh4eHqC1RhAErQ6jOHVdK3yu65qhJE1TDgHn5BDKTW6auxdFYdYOgiDAYrF40k4phTAM8fnzZyilUBRF54rhOfIOF06SJMYPaPt8v99jOp3C8zx4nget9bPZQ5ZlF3fzL0IAZVke9OLv7+/Njl/brCHLMozHY4xGI3z48MH0EEVRIMuyi40H+EdqBbNS6HXBSqGEAiAUAAVAE1AAhAIgFAChAAgFQCgAQgGQq+Eom0GLxeJgGHYVSdCUhM02yrI0qV5hGGIymaAsy9b0LNd1cXt7CwDYbDa98wOA/zKLVquVSQGr/nYTbe2iKDIh53JtZVmiLEvsdjtst9tn5z7EDmfXA3QFXdaTMbvYbrdm568tgkdueJ7njbt3QwJA+8YJ1tsFQQDXdXFzc2N2E0Uwk8kEX758eXbMEDtY2QOsVqtn//v69SsAYL1eH9xK7dNGgjuiKMJ4PH4WmSN7+QBMFu/3798bn1oAzz47NvVrqmYgz2azRpv1scNV+wDVaN969y6JIEmSWBmyJenlIgZbcgvOzgmUqJxqkmY18ldCvGwkz/MntQcogBcgETrVMV98Aptvfh1JTKEAXsBms4HWGp7nYT6fw3Ec5Hlufbi253lQSkFr3VqmhgLoQVmW2G63ZigQx8/2my/FKCR17WLWAV7LfD5vzOFLkqS1W0/T1HT9RVFY5/jNZjMz3ouvorVGHMet9QheYoer7AGq478Y2LaiDTc3N3Bd90megSwG2YQVPcDQ+a/ccK01ttutWSWsetl/i7bfq16TzP1lGFgul0exw9X2AJLGJV3joRXCl3rnXbUDhmQKl2WJ9XoNrbV1vdXZCUCWWqvVQGR8HFIgqmuaKUiCSJcA+nrzWmvzdA/ZN6EAKlTz/eXmA3iSuXOoNEzfBRsA+PTpU+PnUjxSfnvo9/ZNR6cAakjFj2rqd3VtQJ6u1z5h1e+SdYbqdK5aWHLImC0OoFQgpRN4YPoD/LfRVC8C2TQlkhVC3/dfVDG0/l1xHCOKIvi+b572atJoURSdtYnbfAHxV0aj0TP/oY8dzqYH6OscHXK26tO+rqcujmNTIKqtJkDfc0vTFMvl8smu436/R57niOO4NSbh0HfLkFHtpYbY4dgwOfRKYXIooQAIBUAB0AQUAKEACAVAKABCARAKgFAA5Gp4s93AKIrw/v17ExsnFEWB/X6P3W6HLMtaN0+GJkwOad+W2FlPLq3GHFSRdq85h2PYyGoByG6cvJOnHiEryZJSg7e+s1ZNmOyzSza0ffWYJsIwbMzk7Tp+6Dm81kZWC0BoCnSU7dowDE2K12q1alT60EDJYwVWKqUQRdHgPf9jnfMQG52dDyA5fLKnLlGztiB5Bn1eP3fuNvr31IaWZM9jhHIdEwk5G1Jk4hxtdPJZQJZlJrLWlnBpx3FMmrnrup3RReduIyumgXJxtryRUxw4mQXIO4Yv0UZWCMDWN3I2vX7u0mxk1RtDmp6yoQmTbe27kjK7iOMYt7e3CIIA2+22VyLIWyZ5Hrsnsmol0Jac+fo51QtSXJKNrOgBuvLsTrUOUO8FxAP3ff/gTXiLc3irt5aevAdQSpmpja0vZqq+fm4ymfz18i5vaaOTC0DSvapv8rQRmRY6joPxeHwxNjqpAGSpUwx8ikKJQ5AyNFKb4BJsdBIfwPM8BEFgFjXSNG3debMJSUv7GyuWf8tGby6Aaq2c+qvaJce/a3p2ioTJQ73A3d3di6aBbef8WhtZKQDJ6K1fTJ7neHx8PFjWTcbbvvPePm8QbVtc6ft/+UwKUdfbDT3n19roGDA59EphciihAAgFQAHQBBQAoQAIBUAoAEIBEAqAUACEAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQC4CkxgiceKEPQC5Iv4/APgB2O7x8IXXAAAAAElFTkSuQmCC'); +} +.texture-image.no-preview { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAA8sSURBVHhe7Z3rbxXFG8d7B9SWthRabLmIYlHkIEXKJdXYBEXxHtEXprwxxsR3/jG+8PLCaDDGeAkmKsTEoCUVKoVCA6WNtLS2UEUKBSy0tKW/D+eZM9nu7tmz55z+mC2Zz4tl9tk5c2bnO/PMM2dnS+6nn36aYzFH7vvvv6+SFhMoAY4fPy7nljvG448/zjFPTiymsAIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjmLhQgPz+/pKRk3rx56jzaRHFf0ObNmxctWkTi7Nmzp0+fFqNm+/btRUVFP/30kzp3UFtbu27duqVLl+bl3e5Y169f7+rqam1tvXnzpmSIFNHdF1RTU7M6TkNDQ0FBgbImWLVqFZfUSQKyvfzyy88991x1dfXU1NSFCxdGRkbuueeeurq6pqam0tJSlS96RNcFSQvSo9V5IC+88MIDDzwwOjr6448/fvTRR19++eVnn322Z8+ev//+u7i4+M0331ywYIHKGjGiK8Aff/zBMRaL5ebmiiUZjz322MqVK/Ez33333ZkzZxgBYh8eHt67d++lS5do/W3btokxakRXANxIf38/3mPNmjXKlARxpkeOHKGtxaIZHx9vaWkhwfTg9WZRILoCgIQG0r7JKC8vlxm7s7NTLC6YyW/cuFFYWIiPUqYoEWkB+vr6cOJLlizBwyiTB2l9vA0xj1hcTE9PDw4OkiA6EkukiLQAcOzYMY4bN26UUy8LFy7k+O+//8qpL1euXOF43333yWmkiLoATKqEQwSmlZWVyjQTIiWOwZG+npYjSNQFwIG0tbWRqK+vF4sL1r0qlZzJyUmOYXLeeaIuAHR3d+PfmQbE27hgguUY3LgS/0RzHMwBAei/R48ezcvL8x0EOCiOxEJy6osoJ1JFjTkgAHR0dExMTBDLexe0EvsTKQUMgsWLF3OUWChqzA0BGARoQBN7wyHWa6Ojo1x6+OGHlWkmaEOoeuvWrXPnzilTlJgbAgBeiEEQi8W8Pf3kyZMct27d6v0JGsf15JNPkmA5lmyhYJY5IwAenNmYBW1RUZEyJSBMYiYoLi7etWtXWVmZsubkkHPHjh2EsCjX3NysrBFjzggANDSeRJ04wEF9//33rLYqKip27979yiuvNDY2Pvvss2+//TZ+ieBn//79V69eVbkjRv6WLVv4hxW/nEcB+iyuo6ura3x8XJnicIqToV8zGpgSlDXO2NhYZ2cnV+WnIVZtTLxEn+fPn9+3b180p9+qqiqOd9ub8ihH67M8xuPT65mf1YXocXe+KY+PGhoa6unp4Rjl1tfcbQLMOawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGyfy3oNdff72mpkadJLh27Vpvb29LS8vExIRYdu7c6dpLOz09ffPmTXLypadOnVLWnJzGxsZYLKZOPHR0dDQ3N7/33nv5+fkff/yx7/PFBQsWvPPOO5T/4YcfLly4sKmpaXBw8Ntvv5Wr7777bsAOUbINDw+Th5IpX1kTyGcPHz7c2tqqTHG4NW7wzz//9N2tHczs/BY0NjZ2PQFVLy4uXr9+/UsvvaQuJxgfH1eZ4tkKCwsrKiq2b9/u3XbozOkEzaamps6ePUueZHvcsOfl5ZFHtkH4oorzQOFU7MqVKzS0S6fy8nKxeDvckiVLOGbza2u22yW/+eYbOo46ie9Te/XVV5ctW7Z8+fK//vpLWXNyfvjhB2ctaaaGhoYNGzZs3bq1q6tLWeP88ssvdCh14oFLDz30EA3tuxFRhBGRkvHJJ5+olB8XLlxg6NCs/f39ypRo93/++Wfp0qWMP+fuCnna7N2TGp5ZngMQ48iRIyQefPBBsfhy69atgwcPjo6OlpSU+G42SQaicv80tPfBJBbslBwsQDBDQ0McpVk1CMBAx2HyFa79jUhFfeRTmTH7k7DsEky5DxBPffHiRRKytS0kNMTAwAAN4d0tigX7+fPnfaeHkEjlxbFoEIAvlTFRXV0tRhBnNTIy4hwT6TL7Asgz2zBvBUlO/K+chkQc1IoVK+RUI5YzZ87IaWZIX3buMpIJAP+Jroxv5zQgOmW52WL2BZDtyv/995+cJkMeHHJX6T42wcPgZ5gJ1HkCsWTjf4C+TCuXlpZqFyctLl6etpZpIH5F6eScAjNglgVg+n3iiSdIuHoiI/f2S19xamtrN23a9NprrzEVt7W1uSKWtWvXPu2HuhzfHkF/pFfef//9ypSTQxoLPi3lw3dV3Ez4UnU5/nicJpZuBAigvTzfyyU9DWQfAkG2UdCLL76oPeC99947f/58Et3d3cQMYhTk0b8TejGhfXt7uzpPgCfxuhf49ddfVSonp6enhyhr1apVeHyxkOYYxv8QJauUA9yaXpEQCKEH8zAJThGA1pd7lLamM0mCPNhl73vGZDsCGK10FgGffvnyZZYqP//8s7qcgCY7EUemMvz+F198ceDAAaZiyaA5duwYixov6nIcaWhpdEHSfIucBqCKm4m8hSDIBhHp3URoMgHEr9wefHoaYChw71qbjMlWgK+//pp1o/DBBx98/vnnLBfp3epyAmI4ujDs3bv3t99+I/J5/vnnfd++4/7pj17U5TjohzsuKysTL8yRNM5HwqpgVHEzce7KoYlpUynZO83qaYAOxzGbFYCQrQAsXOkXgrc7+4IYuA5WwgHvvaSEVuMoKy859vb23r6QNbQ+zof2Je2cAAQ9DYhCWU4AMPtRUBhko2B9fX1aiwAnEu3IakCOYfxPSFgN4HnwP7h7xHA6GT0NyFScZQgEZgRgimYyKCwsrKurU6Y0weHIbwO0FEfGX5bxuBPp8kR0jAPX22d8EY2Oa6qqqiJt3gVlzKFDhzjGYjFaUCzpgs/BGzQ2NnJkWg7pAMMg8Y/8Wul1Mn19fUiONtl3fzAmAP0XN8IgcM0EGzZs2JkElSOBTAMsLDiGnwBUWR74XpUjvuxiJS/TgK8AdBpUz34CAGMCgPy27hoEdC5Zr3lRORIQ8krYMzExMTAwIMaUqLI8iE/XyCCgj+NnxKLRoWf2/gcyfyBDGDNv3jw6csCP70C0QPvSUq6tzgKelK5EUxJZElazlFMX/PB6efkIJXsD0IKCgsrKSuclmpi1t6S9uBy6lJzMy1My5ae892DExdn/R8wYd+fu6DmHFcAwVgDDWAEMYwUwjBXAMFYAw1gBDGMFMIwVwDBp/xSxZs2aqqqqsbGxw4cPK1PiD2W0t7cne0K9ePHitWvXXr9+Xf4aKFRWVj7yyCMkKIfSxKgpLS1lpT4yMqIrxinGU6dOBf95OGH16tXV1dWuSmrkmbs6iTM5OXnjxo2enh7560Oap+O7MZz7AVzIF6kTPwI+m+FPEbT1+vXrN2/eXFJSokzxfXAYH330UXXuYd26dWRw/uoZi8WwgPPZukYKdO5vJI0FDdR5IL6V1KxYseL2FzvYuHFjQ0NDU1OTa7uRXFUnftTU1EieZKh8yUlPALott3T58mXSiC9GkJ/mA/aDyo1JNsjPz6fdr169OjU15SxnVqioqCgrK/NW0oXefrF///4DBw5QN2r1zDPPFBcXqxyhOXnypBTlReVITnoCyP20tLS4Gq6/v58hvGjRIudfi9HIrqnR0VG9jWfZsmXz58/nnoeGhiQt9llBVxIXFCCA3n7R3d3d0dFBY3EXRUVF4hjTAq8oRXlROZKTtgATExN9fX0DAwMyGsQ+PT0te3V8b1iMztqIpbe3l6JkNIh9VtCVpEGdlUyJPOjnI3J6Z0hDALkZbozuL63pbG6vReMSQFqcEcOACPhUZoj/kUrKPonwhcvTlTDbimeRNASQt1mkp9N5uUPn+y2Dg4M4Ge7f1eOQTR4taf+zcuVKfI6UI5sbli9f7pyfs0GaWwpnmLoqGYxswwr/dHNWSEMA7o37kfdecK+4b+luchUv5NudnS0iiEU/Rmfg5+XlBb/QEZ7gSjoh0CpPwOy1adMmQrVz58653tgJAz1MFTQT79+w8xJWACZSvobeoWN2r9MXAWSfmkb8u8v/UIjuaOk6igCkrYMrqXnqqad2JyAA3bZtG8N037593n2VKamvr1cFzaS2tlblSE5YAeQenLvPpJc57w0ng0thYaL3u0mLcGN6Bwf+p7CwkOmRfiqWixcv4rsIqLP3QmEqqRkeHqZWQK8njMH1U+233nor5FLDCcs3KcpFypckIOz2dLkHhiqrG7EAlZYmlqAb6Oksaoj65W+6iWOhG+pdU1IOGjjLQSGGF5nlD1BmTMhKCq2trXpcAkOT5RuV37Fjx1dffaWs4Whvb3f9DbvwhBoBdE8aiASr5y0O5B0j519MlVvSDt21/iooKBCPxFEVEYcGwhhmwAYgrUwiZSV9YUQeOnQI31VVVZXWe4NZEkoAqT3tyIrRibwQ6Ww4Qho6mvgTmoNG4ZZ0/EO70/cZ7+rzDojc+VTGe3VBur+3kvq/MInnCgINqD+JDLxQxqQWIDc3VzoyHYSB5uT333/HfUtDS2agCYhqWN8CpxKwyiVpI/XhmUhQJBkyQz7rrWRbWxvu3lXJZMhw0RW+A6QWQLoz9+DyoYI3hmFlzxHN+CAJp/+RAMk5SWqyjIXE/ySrJOsyjikLp+OzaiEKohxl+v+TWgCpt2+rgTfOu3TpEoENrQ/OcBP/w0RHyMGUKxYnrAbod84IyheCa/K4YH4KrqSvAK6i6urq3njjDcbu6dOnXTVUOWZCf1KX48opqweZOwNIEQVp/6PXTS7w77SyDHC9C5NeT0RBorOz0+V/5PcWL5OTk0hFkEq2EydOKKsHJlWVcoCjl8KTVVJUd1XStyjmp4MHD6qTBLt27VIpB3v27NEDZUMcSbugbrhBdeJHij9dTDyAvFQrWaMQXyLS+Pj4tWvX9PAn/kV5hgJhJXYxMgLIQDm+u3SBeZgOKJM2/YuhwJSoN+SWlJTQiJTphTZlzRlQSXBWkjUwsan6cBy+iLD9+PHjzc3Nzv22RLQqhwfEphBukx6mTH6wEEn2kOru/NPFc4gMn4hZZhcrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYdS2FIsp7AgwSk7O/wCqCi/+JioQYgAAAABJRU5ErkJggg=='); +} + .two-column { display: table; width: 100%; @@ -924,12 +985,11 @@ tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { } #properties-list fieldset .two-column { - padding-top:21px; + padding-top: 10px; display: flex; } -#properties-list .two-column fieldset { - /*display: table-cell;*/ +#properties-list .two-column fieldset { width: 50%; margin: 0; padding: 0; @@ -937,22 +997,30 @@ tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { box-shadow: none; } +#properties-list .two-column .column { + position: relative; + top: -10px; +} + #properties-list .two-column fieldset legend { - display: table; width: 100%; margin: 21px -21px 0 -21px; - padding: 0 0 0 21px; + padding: 16px 0 0 21px; font-family: Raleway-Regular; font-size: 12px; color: #afafaf; - height: 28px; + height: 10px; text-transform: uppercase; outline: none; } +#properties-list .two-column + .property { + margin-top: 6px; +} + fieldset .checkbox-sub-props { margin-top: 0; - } +} fieldset .checkbox-sub-props .property:first-child { margin-top: 0; @@ -999,6 +1067,10 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { } +body#entity-list-body { + padding-bottom: 0; +} + #entity-list-header { margin-bottom: 36px; } @@ -1035,41 +1107,125 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { position: relative; /* New positioning context. */ } -#footer-text { +#filter-area { + padding-right: 168px; + padding-bottom: 24px; +} + +#filter-type-select-box select { + border-radius: 14.5px; +} +#filter-type-checkboxes { + position: absolute; + z-index: 2; + top: 48px; + display: none; + border: none; +} +#filter-type-checkboxes div { + position: relative; + height: 22px; +} +#filter-type-checkboxes span { + position: relative; + top: 3px; + font-family: hifi-glyphs; + font-size: 13px; + color: #000000; + padding-left: 6px; + padding-right: 4px; +} +#filter-type-checkboxes label { + position: absolute; + top: -20px; + z-index: 2; + display: block; + font-family: FiraSans-SemiBold; + font-size: 11px; + color: #000000; + background-color: #afafaf; + width: 200px; + height: 22px; + padding-top: 1px; +} +#filter-type-checkboxes label:hover { + background-color: #1e90ff; +} +#filter-type-checkboxes input[type=checkbox] + label { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADUOYnF4LQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIMSURBVFjD7ZmxkqowFIZ/7mwJPen1AezV3t6hFvrQhweAHvrQ8wL2xt4HwD7ppd+tvHOvu0gCYdEZTsmAfpNzzpcTcAB84o3iD94sZuCx4+Pxwvl8dl4JcL1ef84lMQPPwBZDSgkp5XsASylBKUUYhhBCvDbw7XYDpRRKKTRNA8YYOOevC5ymKZRS/13jnHdCTwLMOW8tAc45GGNomuY1gKuq6lxFIQQopdMDXy4X5HmudW8URdMCSynBGNOG3Ww20wHf9dVWl4+wbav7a8CMsW9G+Cm22+1T2F8BzvMc1+u18z5CCJIkseNhKSX2+z2qqjLWl84zhBAURQHXde0A31Oa57nWbqSrLwDwPA9FUcD3fTtb82NKu8QOAHVda+srSRJt2E7gtpQKIXA4HH6csmzpyxj4dDo9TalSCpRS1HX9TV86RujSlxGwlBJpmnY+rJRCGIZ/s2BTX9qnZgBwHAee52mJ/l7nx+PRqr6MVtj3fZRlaVRf/5aGDX0Z17DrusiyrHfqhuqrt9aiKEIcx4OBTfU1aOMIggBlWYIQ0utP+uhr8CyxXC5RFIUxdBAE1srKePgxbcbVamWlnAZNa7rNSAhBlmWv8yLlWTPa0Nco83BbM2ZZZsUIowzwj80YxzEWi8VoB4IPGz9yb0YhBHa73agnGGtHJNd1R4ed9FVV33Awf6ebgd8b+Av9A/rq6s3hjgAAAABJRU5ErkJggg=='); + background-size: 11px 11px; + background-position: top 5px left 14px; +} +#filter-type-checkboxes input[type=checkbox]:checked + label { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADMveELP9QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIqSURBVFjD7ZmxkqowFIb/7GwJPfT6APZib+9QC33o4QGghz70vIC9sfcBsE966bPNWlxnlQTDRWc4JUT4hpPz5SQSAAofFF/4sJiBx47v+wun04m8E+B6vVbzlJiBZ2CLIYRQQgj1EcBCCEUpRRRF4Jyrtwa+Xq+glEJKia7rkKYpGGPqbYHzPFdSyn+uMcZ6oScBZowpzvmje0jTVHVd9x7ATdMoxtjTMZxzUErV5MDn81mVZak1No7jab+wEEKlaaoNGwQBmQz4pq9H8/IeNo5jMmnRpWmKeyP8FZvN5insfwEuy1JdLpfecb7vI8uy3tb2Szelu91ONU1jtP9jjKmmabRgq6qC4zh2VrpbSsuy1FqNdPUFAK7roqoqeJ6ntXH4Mk1pn9gBoG1bbX1lWaYN2wv8KKWcc+z3+z+7LFv6MgY+Ho9PUyqlBKUUbduqe33pGKFPX0bAQgiV53nvj6WUiKIIt2K0qS/tXTMAEELguq6W6H/nOQ6Hg1V9GX1hz/NIXdckCALtB7Vta1VfxnPYcRwURUEeNSGmYaqvwVqL45gkSfIysKm+Xlo4wjAkdV3D9/1BLxmir5d7ieVySaqqMoYOw3CwEV5ufkyLcbVaIUkSq2d1xt2abjH6vo+iKKwfLA5uL58Vow19jdIPPyrGoiisGGGUBv6+GJMkwWKxGO2M+dvGQ36LEZxztd1uRz0Qt7ZFchwHY8NOelQ1NAjm/+lm4M8G/gH2zx33BSr7jAAAAABJRU5ErkJggg=='); + background-size: 11px 11px; + background-position: top 5px left 14px; +} +#filter-type-checkboxes input[type=checkbox]:hover + label { + background-color: #1e90ff; +} + +#filter-search-and-icon { + position: relative; + left: 118px; + width: calc(100% - 126px); +} + +#filter-in-view { + position: absolute; + top: 0px; + right: 126px; +} + +#filter-radius-and-unit { + position: relative; float: right; - padding-top: 12px; - padding-right: 22px; + margin-right: -168px; + top: -45px; +} +#filter-radius-and-unit label { + margin-left: 2px; +} +#filter-radius-and-unit span { + position: relative; + top: 25px; + right: 9px; + z-index: 2; + font-style: italic; +} +#filter-radius-and-unit input { + width: 120px; + border-radius: 14.5px; + font-style: italic; +} +#filter-radius-and-unit input[type=number]::-webkit-inner-spin-button { + display: none; } #entity-list-footer { padding-top: 9px; } -#search-area { - padding-right: 168px; - padding-bottom: 24px; -} - -#filter { - width: 98%; -} - -#in-view { - position: absolute; - right: 126px; -} - -#radius-and-unit { +#footer-text { float: right; - margin-right: -168px; - position: relative; - top: -17px; + padding-top: 12px; + padding-right: 22px; } -#radius-and-unit label { - margin-left: 2px; + +input[type=button]#export { + height: 38px; + width: 180px; } -#radius-and-unit input { - width: 120px; + +#no-entities { + display: none; + position: absolute; + top: 80px; + padding: 12px; + font-family: FiraSans-SemiBold; + font-size: 15px; + font-style: italic; + color: #afafaf; } #entity-table-scroll { @@ -1289,63 +1445,51 @@ th#entity-hasScript { border-right: 1px solid #575757; } - -#no-entities { - display: none; - position: absolute; - top: 80px; - padding: 12px; - font-family: FiraSans-SemiBold; - font-size: 15px; - font-style: italic; - color: #afafaf; +#properties-base { + border-top: none !important; + box-shadow: none !important; + margin-bottom: 5px !important; + top: -15px; } - -#properties-list #properties-header { - display: table-row; - height: 28px; - border-top: none; - box-shadow: none; -} - -#properties-header .property { - display: table-cell; - vertical-align: middle; -} -#properties-header .checkbox { - position: relative; - top: -1px; -} - -#properties-header #type-icon { +#properties-base #property-type-icon { font-family: hifi-glyphs; font-size: 31px; color: #00b4ef; margin: -4px 12px -4px -2px; width: auto; display: none; - vertical-align: middle; } -#properties-header #property-type { +#properties-base #property-type { padding: 5px 24px 5px 0; border-right: 1px solid #808080; - height: 100%; width: auto; display: inline-block; +} + +#properties-base #div-property-locked { + position: absolute; + top: 6px; + right: 140px; + display: table-cell; vertical-align: middle; } -#properties-header .checkbox:last-child { - padding-left: 24px; +#properties-base #div-property-visible { + position: absolute; + top: 26px; + right: 20px; + display: table-cell; + vertical-align: middle; } -#properties-header .checkbox label { +#properties-base #div-property-locked label, +#properties-base #div-property-visible label { background-position-y: 1px; } -#properties-header .checkbox label span { +#properties-base .checkbox label span { font-family: HiFi-Glyphs; font-size: 20px; padding-right: 6px; @@ -1354,15 +1498,10 @@ th#entity-hasScript { top: -4px; } -#properties-header input[type=checkbox]:checked + label span { +#properties-base input[type=checkbox]:checked + label span { color: #ffffff; } -#properties-header + hr { - margin-top: 12px; -} - - #id label { width: 24px; } @@ -1374,430 +1513,110 @@ th#entity-hasScript { background-color: #00b4ef; } -input#property-parent-id { - width: 340px; -} - -input#dimension-rescale-button { +input#property-scale-button-rescale { min-width: 50px; margin-left: 6px; } -input#reset-to-natural-dimensions { +input#property-scale-button-reset { margin-right: 0; } -#animation-fps { - margin-top: 48px; -} - -#userdata-clear, -#materialdata-clear { +#property-userData-button-clear, +#property-materialData-button-clear { margin-bottom: 10px; } - -#static-userdata, -#static-materialData { +#property-userData-static, +#property-materialData-static { display: none; z-index: 99; position: absolute; width: 96%; - padding-left:1%; - margin-top:5px; - margin-bottom:10px; + padding-left: 1%; + margin-top: 5px; + margin-bottom: 10px; background-color: #2e2e2e; } -#userdata-saved, -#materialData-saved { - margin-top:5px; - font-size:16px; - display:none; +#property-userData-saved, +#property-materialData-saved { + margin-top: 5px; + font-size: 16px; + display: none; } -#properties-list #collision-info > fieldset:first-of-type { - border-top: none !important; - box-shadow: none; +#div-property-serverScripts-status label { + position: relative; + top: 8px; +} +#property-serverScripts-status { + position: relative; + top: 5px; + right: -20px; +} + +#div-property-collisionSoundURL[style*="display: none"] + .property { margin-top: 0; } -#properties-list { - display: flex; - flex-direction: column; -} - -/* ----- Order of Menu items for Primitive ----- */ -/* Entity Menu classes are specified by selected entity - within entityProperties.js -*/ -#properties-list.ShapeMenu #general, -#properties-list.BoxMenu #general, -#properties-list.SphereMenu #general { - order: 1; -} - -#properties-list.ShapeMenu #collision-info, -#properties-list.BoxMenu #collision-info, -#properties-list.SphereMenu #collision-info { - order: 2; -} - -#properties-list.ShapeMenu #physical, -#properties-list.BoxMenu #physical, -#properties-list.SphereMenu #physical { - order: 3; -} - -#properties-list.ShapeMenu #spatial, -#properties-list.BoxMenu #spatial, -#properties-list.SphereMenu #spatial { - order: 4; -} - -#properties-list.ShapeMenu #behavior, -#properties-list.BoxMenu #behavior, -#properties-list.SphereMenu #behavior { - order: 5; -} - -#properties-list.ShapeMenu #hyperlink, -#properties-list.BoxMenu #hyperlink, -#properties-list.SphereMenu #hyperlink { - order: 6; -} - -#properties-list.ShapeMenu #material, -#properties-list.BoxMenu #material, -#properties-list.SphereMenu #material, -#properties-list.ShapeMenu #light, -#properties-list.BoxMenu #light, -#properties-list.SphereMenu #light, -#properties-list.ShapeMenu #model, -#properties-list.BoxMenu #model, -#properties-list.SphereMenu #model, -#properties-list.ShapeMenu #zone, -#properties-list.BoxMenu #zone, -#properties-list.SphereMenu #zone, -#properties-list.ShapeMenu #text, -#properties-list.BoxMenu #text, -#properties-list.SphereMenu #text, -#properties-list.ShapeMenu #image, -#properties-list.BoxMenu #image, -#properties-list.SphereMenu #image, -#properties-list.ShapeMenu #web, -#properties-list.BoxMenu #web, -#properties-list.SphereMenu #web { +.context-menu { display: none; + position: fixed; + color: #000000; + background-color: #afafaf; + padding: 5px 0 5px 0; + cursor: default; +} +.context-menu li { + list-style-type: none; + padding: 4px 18px 4px 18px; + margin: 0; + white-space: nowrap; +} +.context-menu li:hover { + background-color: #e3e3e3; +} +.context-menu li.separator { + border-top: 1px solid #333333; + margin: 5px 5px; + padding: 0 0; +} +.context-menu li.disabled { + color: #333333; +} +.context-menu li.separator:hover, .context-menu li.disabled:hover { + background-color: #afafaf; } -/* ----- ParticleEffectMenu ----- */ -#properties-list.ParticleEffectMenu #general { - order: 1; -} -#properties-list.ParticleEffectMenu #collision-info { - order: 2; -} -#properties-list.ParticleEffectMenu #physical { - order: 3; -} -#properties-list.ParticleEffectMenu #spatial { - order: 4; -} -#properties-list.ParticleEffectMenu #behavior { - order: 5; +input.rename-entity { + height: 100%; + width: 100%; + border: none; + font-family: FiraSans-SemiBold; + font-size: 15px; + /* need this to show the text cursor when the input field is empty */ + padding-left: 2px; } -/* items to hide */ -#properties-list.ParticleEffectMenu #material, -#properties-list.ParticleEffectMenu #base-color-section, -#properties-list.ParticleEffectMenu #hyperlink, -#properties-list.ParticleEffectMenu #light, -#properties-list.ParticleEffectMenu #model, -#properties-list.ParticleEffectMenu #shape-list, -#properties-list.ParticleEffectMenu #text, -#properties-list.ParticleEffectMenu #web, -#properties-list.ParticleEffectMenu #image, -#properties-list.ParticleEffectMenu #zone { - display: none; +.create-app-tooltip { + position: absolute; + background: #6a6a6a; + border: 1px solid black; + width: 258px; + min-height: 20px; + padding: 5px; } -/* ----- Order of Menu items for Light ----- */ -#properties-list.LightMenu #general { - order: 1; -} -#properties-list.LightMenu #light { - order: 2; -} -#properties-list.LightMenu #physical { - order: 3; -} -#properties-list.LightMenu #spatial { - order: 4; -} -#properties-list.LightMenu #behavior { - order: 5; -} -#properties-list.LightMenu #collision-info { - order: 6; -} -#properties-list.LightMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.LightMenu #material, -#properties-list.LightMenu #model, -#properties-list.LightMenu #zone, -#properties-list.LightMenu #text, -#properties-list.LightMenu #image, -#properties-list.LightMenu #web { - display: none; -} -/* items to hide */ -#properties-list.LightMenu #shape-list, -#properties-list.LightMenu #base-color-section { - display: none +.create-app-tooltip .create-app-tooltip-description { + font-size: 12px; + font-style: italic; + color: #ffffff; } - -/* ----- Order of Menu items for Model ----- */ -#properties-list.ModelMenu #general { - order: 1; -} -#properties-list.ModelMenu #model { - order: 2; -} -#properties-list.ModelMenu #collision-info { - order: 3; -} -#properties-list.ModelMenu #physical { - order: 4; -} -#properties-list.ModelMenu #spatial { - order: 5; -} -#properties-list.ModelMenu #behavior { - order: 6; -} -#properties-list.ModelMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.ModelMenu #material, -#properties-list.ModelMenu #light, -#properties-list.ModelMenu #zone, -#properties-list.ModelMenu #text, -#properties-list.ModelMenu #image, -#properties-list.ModelMenu #web { - display: none; -} -/* items to hide */ -#properties-list.ModelMenu #shape-list, -#properties-list.ModelMenu #base-color-section { - display: none -} - - -/* ----- Order of Menu items for Zone ----- */ -#properties-list.ZoneMenu #general { - order: 1; -} -#properties-list.ZoneMenu #zone { - order: 2; -} -#properties-list.ZoneMenu #physical { - order: 3; -} -#properties-list.ZoneMenu #spatial { - order: 4; -} -#properties-list.ZoneMenu #behavior { - order: 5; -} -#properties-list.ZoneMenu #collision-info { - order: 6; -} -#properties-list.ZoneMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.ZoneMenu #material, -#properties-list.ZoneMenu #light, -#properties-list.ZoneMenu #model, -#properties-list.ZoneMenu #text, -#properties-list.ZoneMenu #image, -#properties-list.ZoneMenu #web { - display: none; -} -/* items to hide */ -#properties-list.ZoneMenu #shape-list, -#properties-list.ZoneMenu #base-color-section { - display: none -} - - -/* ----- Order of Menu items for Image ----- */ -#properties-list.ImageMenu #general { - order: 1; -} -#properties-list.ImageMenu #image { - order: 2; -} -#properties-list.ImageMenu #collision-info { - order: 3; -} -#properties-list.ImageMenu #physical { - order: 4; -} -#properties-list.ImageMenu #spatial { - order: 5; -} -#properties-list.ImageMenu #behavior { - order: 6; -} -#properties-list.ImageMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.ImageMenu #material, -#properties-list.ImageMenu #light, -#properties-list.ImageMenu #model, -#properties-list.ImageMenu #zone, -#properties-list.ImageMenu #web, -#properties-list.ImageMenu #text { - display: none; -} -/* items to hide */ -#properties-list.ImageMenu #shape-list, -#properties-list.ImageMenu #base-color-section { - display: none; -} - - -/* ----- Order of Menu items for Web ----- */ -#properties-list.WebMenu #general { - order: 1; -} -#properties-list.WebMenu #web { - order: 2; -} -#properties-list.WebMenu #collision-info { - order: 3; -} -#properties-list.WebMenu #physical { - order: 4; -} -#properties-list.WebMenu #spatial { - order: 5; -} -#properties-list.WebMenu #behavior { - order: 6; -} -#properties-list.WebMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.WebMenu #material, -#properties-list.WebMenu #light, -#properties-list.WebMenu #model, -#properties-list.WebMenu #zone, -#properties-list.WebMenu #image, -#properties-list.WebMenu #text { - display: none; -} -/* items to hide */ -#properties-list.WebMenu #shape-list, -#properties-list.WebMenu #base-color-section { - display: none; -} - - - -/* ----- Order of Menu items for Text ----- */ -#properties-list.TextMenu #general { - order: 1; -} -#properties-list.TextMenu #text { - order: 2; -} -#properties-list.TextMenu #collision-info { - order: 3; -} -#properties-list.TextMenu #physical { - order: 4; -} -#properties-list.TextMenu #spatial { - order: 5; -} -#properties-list.TextMenu #behavior { - order: 6; -} -#properties-list.TextMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.TextMenu #material, -#properties-list.TextMenu #light, -#properties-list.TextMenu #model, -#properties-list.TextMenu #zone, -#properties-list.TextMenu #image, -#properties-list.TextMenu #web { - display: none; -} -/* items to hide */ -#properties-list.TextMenu #shape-list, -#properties-list.TextMenu #base-color-section { - display: none -} - -/* ----- Order of Menu items for Material ----- */ -#properties-list.MaterialMenu #general { - order: 1; -} -#properties-list.MaterialMenu #material { - order: 2; -} -#properties-list.MaterialMenu #spatial { - order: 3; -} -#properties-list.MaterialMenu #hyperlink { - order: 4; -} -#properties-list.MaterialMenu #behavior { - order: 5; -} - -/* sections to hide */ -#properties-list.MaterialMenu #physical, -#properties-list.MaterialMenu #collision-info, -#properties-list.MaterialMenu #model, -#properties-list.MaterialMenu #light, -#properties-list.MaterialMenu #zone, -#properties-list.MaterialMenu #text, -#properties-list.MaterialMenu #web, -#properties-list.MaterialMenu #image { - display: none; -} -/* items to hide */ -#properties-list.MaterialMenu #shape-list, -#properties-list.MaterialMenu #base-color-section { - display: none -} - - -/* Currently always hidden */ -#properties-list #polyvox { - display: none; -} - -.skybox-section { - display: none; -} - -input[type=button]#export { - height: 38px; - width: 180px; -} - -body#entity-list-body { - padding-bottom: 0; +.create-app-tooltip .create-app-tooltip-js-attribute { + font-family: Raleway-SemiBold; + font-size: 11px; + color: #000000; + bottom: 0; + margin-top: 5px; } diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index c62c785c99..2e7ac58ac1 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -16,6 +16,7 @@ <script type="text/javascript" src="js/eventBridgeLoader.js"></script> <script type="text/javascript" src="js/spinButtons.js"></script> <script type="text/javascript" src="js/listView.js"></script> + <script type="text/javascript" src="js/entityListContextMenu.js"></script> <script type="text/javascript" src="js/entityList.js"></script> </head> <body onload='loaded();' id="entity-list-body"> @@ -29,12 +30,25 @@ <input type="button" class="red" id="delete" value="Delete" /> </div> <div id="entity-list"> - <div id="search-area"> - <span class="icon-input"><input type="text" class="search" id="filter" placeholder="Filter" /><span>Y</span></span> - <input type="button" id="in-view" class="glyph" value="" /> - <div id="radius-and-unit" class="number"> + <div id="filter-area"> + <div class="multiselect"> + <div class="select-box" id="filter-type-select-box"> + <select> + <option id="filter-type-text">All Types</option> + </select> + <div class="over-select"></div> + </div> + <div id="filter-type-checkboxes"> + <!-- type options with checkbox, icon, and label are added at runtime in entityList --> + </div> + </div> + <div id="filter-search-and-icon"> + <span class="icon-input"><input type="search" class="search" id="filter-search" placeholder="Search" /><span>Y</span></span> + </div> + <input type="button" id="filter-in-view" class="glyph" value="" /> + <div id="filter-radius-and-unit" class="number"> <label for="radius">Search radius <span class="unit">m</span></label> - <input type="number" id="radius" value="100" /> + <input type="text" id="filter-radius" maxlength="9" value="100" /> </div> </div> <div id="entity-table-scroll"> @@ -87,9 +101,8 @@ </tr> </tbody> </table> - <div id="no-entities"> - No entities found <span id="no-entities-in-view">in view</span> within a <span id="no-entities-radius">100</span> meter radius. Try moving to a different location and refreshing. + There are no entities to display. Please check your filters or create an entity to begin. </div> </div> </div> diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 8b27efa1fe..b56ad346e2 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -1,4 +1,4 @@ -<!-- +<!-- // entityProperties.html // // Created by Ryan Huffman on 13 Nov 2014 @@ -20,882 +20,15 @@ <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script> <script type="text/javascript" src="js/eventBridgeLoader.js"></script> <script type="text/javascript" src="js/spinButtons.js"></script> + <script type="text/javascript" src="js/underscore-min.js"></script> + <script type="text/javascript" src="js/createAppTooltip.js"></script> <script type="text/javascript" src="js/entityProperties.js"></script> <script src="js/jsoneditor.min.js"></script> </head> <body onload='loaded();'> <div id="properties-list"> - - <fieldset id="properties-header"> - <div id="type" class="property value"> - <span id="type-icon"></span><label id="property-type"><i>No selection</i></label> - </div> - <div class="property checkbox"> - <input type="checkbox" id="property-locked"> - <label for="property-locked"><span></span> Locked</label> - </div> - <div class="property checkbox"> - <input type="checkbox" id="property-visible"> - <label for="property-visible"><span></span> Visible</label> - </div> - </fieldset> - - <fieldset id="general" class="major"> - <div class="shape-group shape-section property dropdown" id="shape-list"> - <label for="property-shape">Shape</label> - <select name="SelectShape" id="property-shape"> - <option value="Cube">Box</option> - <option value="Sphere">Sphere</option> - <option value="Tetrahedron">Tetrahedron</option> - <option value="Octahedron">Octahedron</option> - <option value="Icosahedron">Icosahedron</option> - <option value="Dodecahedron">Dodecahedron</option> - <option value="Hexagon">Hexagon</option> - <option value="Triangle">Triangle</option> - <option value="Octagon">Octagon</option> - <option value="Cylinder">Cylinder</option> - <option value="Cone">Cone</option> - <option value="Circle">Circle</option> - <option value="Quad">Quad</option> - </select> - </div> - <div class="property text"> - <label for="property-name">Name</label> - <input type="text" id="property-name"> - </div> - <div class="physical-group color-section property rgb fstuple" id="base-color-section"> - <div class="color-picker" id="property-color-control2"></div> - <label>Entity color</label> - <div class="tuple"> - <div><input type="number" class="red" id="property-color-red"><label for="property-color-red">Red:</label></div> - <div><input type="number" class="green" id="property-color-green"><label for="property-color-green">Green:</label></div> - <div><input type="number" class="blue" id="property-color-blue"><label for="property-color-blue">Blue:</label></div> - </div> - </div> - </fieldset> - - <fieldset id="collision-info" class="major"> - <legend class="section-header"> Collision<span class=".collapse-icon">M</span> </legend> - <fieldset class="minor"> - <div class="behavior-group property checkbox"> - <input type="checkbox" id="property-collisionless"> - <label for="property-collisionless">Collisionless</label> - </div> - <div class="behavior-group property checkbox"> - <input type="checkbox" id="property-dynamic"> - <label for="property-dynamic">Dynamic</label> - </div> - </fieldset> - <fieldset class="minor"> - <div class="behavior-group two-column"> - <fieldset class="column"> - <legend class="sub-section-header"> - Collides With - </legend> - <div class="checkbox-sub-props"> - <div class="property checkbox"> - <input type="checkbox" id="property-collide-static"> - <label for="property-collide-static">Static entities</label> - </div> - <div class="property checkbox"> - <input type="checkbox" id="property-collide-dynamic"> - <label for="property-collide-dynamic">Dynamic entities</label> - </div> - <div class="property checkbox"> - <input type="checkbox" id="property-collide-kinematic"> - <label for="property-collide-kinematic">Kinematic entities</label> - </div> - <div class="property checkbox"> - <input type="checkbox" id="property-collide-myAvatar"> - <label for="property-collide-myAvatar">My avatar</label> - </div> - <div class="property checkbox"> - <input type="checkbox" id="property-collide-otherAvatar"> - <label for="property-collide-otherAvatar">Other avatars</label> - </div> - </div> - </fieldset> - <fieldset class="column"> - <legend class="sub-section-header"> - Grabbing - </legend> - <div class="checkbox-sub-props"> - <div class="property checkbox"> - <input type="checkbox" id="property-grabbable"> - <label for="property-grabbable">Grabbable</label> - </div> - <div class="property checkbox"> - <input type="checkbox" id="property-triggerable"> - <label for="property-triggerable">Triggerable</label> - </div> - <div class="property checkbox"> - <input type="checkbox" id="property-cloneable"> - <label for="property-cloneable">Cloneable</label> - </div> - <div class="property checkbox"> - <input type="checkbox" id="property-grab-follows-controller"> - <label for="property-grab-follows-controller">Follow Controller</label> - </div> - </div> - </fieldset> - - <fieldset class="column" id="group-cloneable-group" style="display:none;"> - <legend class="sub-section-header"> - <span>Cloneable Settings</span> - </legend> - <fieldset class="minor"> - <div><label>Clone Lifetime</label><input type="number" data-user-data-type="cloneLifetime" id="property-cloneable-lifetime"></div> - </fieldset> - <fieldset class="minor"> - <div><label>Clone Limit</label><input type="number" data-user-data-type="cloneLimit" id="property-cloneable-limit"></div> - </fieldset> - <div class="property checkbox"> - <input type="checkbox" id="property-cloneable-dynamic"> - <label for="property-cloneable-dynamic">Clone Dynamic</label> - </div> - <div class="property checkbox"> - <input type="checkbox" id="property-cloneable-avatarEntity"> - <label for="property-cloneable-avatarEntity">Clone Avatar Entity</label> - </div> - </fieldset> - </div> - </fieldset> - </fieldset> - - - <fieldset id="physical" class="major"> - <legend class="section-header physical-group"> - Physical<span class=".collapse-icon"> M</span> - </legend> - <fieldset class="minor"> - <fieldset class="physical-group property xyz fstuple"> - <legend>Linear velocity <span class="unit">m/s</span></legend> - <div class="tuple"> - <div><input type="number" class="x" id="property-lvel-x"><label for="property-lvel-x">X:</label></div> - <div><input type="number" class="y" id="property-lvel-y"><label for="property-lvel-y">Y:</label></div> - <div><input type="number" class="z" id="property-lvel-z"><label for="property-lvel-z">Z:</label></div> - </div> - </fieldset> - <div class="physical-group property number"> - <label>Linear damping</label> - <input type="number" id="property-ldamping"> - </div> - </fieldset> - <fieldset class="minor"> - <fieldset class="physical-group property pyr fstuple"> - <legend>Angular velocity <span class="unit">deg/s</span></legend> - <div class="tuple"> - <div><input type="number" class="pitch" id="property-avel-x"><label for="property-avel-x">Pitch:</label></div> - <div><input type="number" class="yaw" id="property-avel-y"><label for="property-avel-y">Yaw:</label></div> - <div><input type="number" class="roll" id="property-avel-z"><label for="property-avel-z">Roll:</label></div> - </div> - </fieldset> - <div class="physical-group property number"> - <label>Angular damping</label> - <input type="number" id="property-adamping"> - </div> - </fieldset> - <fieldset class="physical-group property gen fstuple"> - <div class="tuple"> - <div><label>Restitution</label><input type="number" id="property-restitution"></div> - <div><label>Friction</label><input type="number" id="property-friction"></div> - <div><label>Density</label><input type="number" id="property-density"></div> - </div> - </fieldset> - <fieldset class="minor"> - <fieldset class="physical-group property xyz fstuple"> - <legend>Gravity <span class="unit">m/s<sup>2</sup></span></legend> - <div class="tuple"> - <div><input type="number" class="x" id="property-grav-x"><label for="property-grav-x">X:</label></div> - <div><input type="number" class="y" id="property-grav-y"><label for="property-grav-y">Y:</label></div> - <div><input type="number" class="z" id="property-grav-z"><label for="property-grav-z">Z:</label></div> - </div> - </fieldset> - <fieldset class="physical-group property xyz fstuple"> - <legend>Acceleration <span class="unit">m/s<sup>2</sup></span></legend> - <div class="tuple"> - <div><input type="number" class="x" id="property-lacc-x"><label for="property-lacc-x">X:</label></div> - <div><input type="number" class="y" id="property-lacc-y"><label for="property-lacc-y">Y:</label></div> - <div><input type="number" class="z" id="property-lacc-z"><label for="property-lacc-z">Z:</label></div> - </div> - </fieldset> - </fieldset> - </fieldset> - - - <fieldset id="spatial" class="major"> - <legend class="section-header spatial-group"> - Spatial<span class=".collapse-icon" >M</span> - </legend> - <fieldset class="spatial-group property xyz fstuple"> - <legend>Position <span class="unit">m</span></legend> - <div class="tuple"> - <div><input type="number" class="x" id="property-pos-x"><label for="property-pos-x">X:</label></div> - <div><input type="number" class="y" id="property-pos-y"><label for="property-pos-y">Y:</label></div> - <div><input type="number" class="z" id="property-pos-z"><label for="property-pos-z">Z:</label></div> - </div> - </fieldset> - <fieldset class="spatial-group property pyr fstuple"> - <legend>Rotation <span class="unit">deg</span></legend> - <div class="tuple"> - <div><input type="number" class="pitch" id="property-rot-x" step="0.1"><label for="property-rot-x">Pitch:</label></div> - <div><input type="number" class="yaw" id="property-rot-y" step="0.1"><label for="property-rot-y">Yaw:</label></div> - <div><input type="number" class="roll" id="property-rot-z" step="0.1"><label for="property-rot-z">Roll:</label></div> - </div> - </fieldset> - <fieldset class="spatial-group property xyz fstuple"> - <legend>Dimensions <span class="unit">m</span></legend> - <div class="tuple"> - <div><input type="number" class="x" id="property-dim-x" step="0.1"><label for="property-dim-x">X:</label></div> - <div><input type="number" class="y" id="property-dim-y" step="0.1"><label for="property-dim-y">Y:</label></div> - <div><input type="number" class="z" id="property-dim-z" step="0.1"><label for="property-dim-z">Z:</label></div> - </div> - </fieldset> - <fieldset class="spatial-group property xyz fstuple"> - <legend>Registration <span class="unit">(pivot offset as ratio of dimension)</span></legend> - <div class="tuple"> - <div><input type="number" class="x" id="property-reg-x" step="0.1"><label for="property-reg-x">X:</label></div> - <div><input type="number" class="y" id="property-reg-y" step="0.1"><label for="property-reg-y">Y:</label></div> - <div><input type="number" class="z" id="property-reg-z" step="0.1"><label for="property-reg-z">Z:</label></div> - </div> - </fieldset> - <fieldset class="spatial-group property gen fsrow"> - <legend>Scale <span class="unit">%</span></legend> - <div class="row"> - <input type="number" id="dimension-rescale-pct" value=100> - <input type="button" class="blue" id="dimension-rescale-button" value="Rescale"> - <input type="button" class="red" id="reset-to-natural-dimensions" value="Reset Dimensions"> - </div> - </fieldset> - <div class="spatial-group row"> - <div class="property text"> - <label for="property-parent-id">Parent ID</label> - <input type="text" id="property-parent-id"> - </div> - <div class="property number"> - <label for="property-parent-joint-index">Parent joint index</label> - <input type="number" id="property-parent-joint-index"> - </div> - </div> - <div class="spatial-group "> - <div class="property text"> - <label>Align</label> - </div> - <div class="row"> - <div class="buttons"> - <input type="button" id="move-selection-to-grid" value="Selection to Grid"> - <input type="button" id="move-all-to-grid" value="All to Grid"> - </div> - </div> - </div> - </fieldset> - - <fieldset id="behavior" class="major"> - <legend class="section-header behavior-group"> - Behavior<span class=".collapse-icon">M</span> - </legend> - <div class="property textarea"> - <label for="property-user-data">User data</label> - <br> - <div class="row"> - <input type="button" class="red" id="userdata-clear" value="Clear User Data"> - <input type="button" class="blue" id="userdata-new-editor" value="Edit as JSON"> - <input disabled type="button" class="black" id="userdata-save" value="Save User Data"> - <span id="userdata-saved">Saved!</span> - </div> - <div id="static-userdata"></div> - <div id="userdata-editor"></div> - <textarea id="property-user-data"></textarea> - </div> - <div id="id" class="property value"> - <label>ID:</label> - <input type="text" id="property-id" readonly> - </div> - <div class="can-cast-shadow-section property checkbox"> - <input type="checkbox" id="property-can-cast-shadow"> - <label for="property-can-cast-shadow">Can cast shadow</label> - </div> - - <fieldset class="minor"> - <div class="behavior-group property url "> - <label for="property-collision-sound-url">Collision sound URL</label> - <input type="text" id="property-collision-sound-url"> - </div> - <div class="behavior-group property number"> - <label>Lifetime <span class="unit">s</span></label> - <input type="number" id="property-lifetime"> - </div> - </fieldset> - <fieldset class="minor"> - <div class="behavior-group property url refresh"> - <input type="hidden" id="property-script-timestamp" class="value"> - <label for="property-script-url">Script URL</label> - <input type="text" id="property-script-url"> - <input type="button" id="reload-script-button" class="glyph" value="F"> - </div> - </fieldset> - <fieldset class="minor"> - <div class="behavior-group property url refresh"> - <label for="property-server-scripts">Server Script URL</label> - <input type="text" id="property-server-scripts"> - <input type="button" id="reload-server-scripts-button" class="glyph" value="F"> - </div> - <div class="behavior-group property"> - <label for="server-script-status">Server Script Status</label> - <span id="server-script-status"></span> - </div> - <div class="behavior-group property"> - <textarea id="server-script-error"></textarea> - </div> - </fieldset> - <div class="property text"> - <label for="property-description">Description</label> - <input type="text" id="property-description"> - </div> - </fieldset> - - - <fieldset id="hyperlink" class="major"> - <legend class="section-header hyperlink-group hyperlink-section"> - Hyperlink<span class=".collapse-icon">M</span> - </legend> - <div class="hyperlink-group hyperlink-section property url"> - <label for="property-hyperlink-href">Href - hifi://address</label> - <input type="text" id="property-hyperlink-href"> - </div> - </fieldset> - - <fieldset id="light" class="major"> - <legend class="section-header light-group light-section"> - Light<span class=".collapse-icon">M</span> - </legend> - <fieldset class="light-group light-section property rgb fstuple"> - <div class="color-picker" id="property-light-color"></div> - <legend>Light color</legend> - <div class="tuple"> - <div><input type="number" class="red" id="property-light-color-red"><label for="property-light-color-red">Red:</label></div> - <div><input type="number" class="green" id="property-light-color-green"><label for="property-light-color-green">Green:</label></div> - <div><input type="number" class="blue" id="property-light-color-blue"><label for="property-light-color-blue">Blue:</label></div> - </div> - </fieldset> - <fieldset class="light-group light-section property gen fstuple"> - <div class="tuple"> - <div><label>Intensity</label><input type="number" id="property-light-intensity" min="0" step="0.1"></div> - <div><label>Fall-off radius <span class="unit">m</span></label><input type="number" id="property-light-falloff-radius" min="0" step="0.1"></div> - <div></div> - </div> - </fieldset> - <div class="light-group light-section property checkbox"> - <input type="checkbox" id="property-light-spot-light"> - <label for="property-light-spot-light">Spotlight</label> - </div> - <fieldset class="light-group light-section property gen fstuple"> - <div class="tuple"> - <div><label>Spotlight exponent</label><input type="number" id="property-light-exponent" step="0.01"></div> - <div><label>Spotlight cut-off</label><input type="number" id="property-light-cutoff" step="0.01"></div> - <div></div> - </div> - </fieldset> - </fieldset> - - <fieldset id="model" class="major"> - <legend class="section-header model-group model-section zone-section"> - Model<span class=".collapse-icon">M</span> - </legend> - <fieldset class="minor"> - <div class="model-group model-section property url "> - <label for="property-model-url">Model URL</label> - <input type="text" id="property-model-url"> - </div> - <div class="model-group model-section zone-section property dropdown"> - <label>Collision shape type</label> - <select name="SelectShapeType" id="property-shape-type"> - <option value="none">No Collision</option> - <option value="box">Box</option> - <option value="sphere">Sphere</option> - <option value="compound">Compound</option> - <option value="simple-hull">Basic - Whole model</option> - <option value="simple-compound">Good - Sub-meshes</option> - <option value="static-mesh">Exact - All polygons (non-dynamic only)</option> - </select> - </div> - <div class="model-group model-section zone-section property url "> - <label for="property-compound-shape-url">Compound shape URL</label> - <input type="text" id="property-compound-shape-url"> - </div> - </fieldset> - <fieldset class="minor"> - <div class="model-group model-section property url "> - <label for="property-model-animation-url">Animation URL</label> - <input type="text" id="property-model-animation-url"> - </div> - - <div class="model-group model-section two-column"> - <div class="column"> - <div class="property checkbox"> - <input type="checkbox" id="property-model-animation-playing"> - <label for="property-model-animation-playing">Animation playing</label> - </div> - <div class="property checkbox indent"> - <input type="checkbox" id="property-model-animation-loop"> - <label for="property-model-animation-loop">Animation loop</label> - </div> - <div class="property checkbox indent"> - <input type="checkbox" id="property-model-animation-hold"> - <label for="property-model-animation-hold">Animation hold</label> - </div> - <div class="property checkbox indent"> - <input type="checkbox" id="property-model-animation-allow-translation"> - <label for="property-model-animation-allow-translation">Animation Allow Translation</label> - </div> - <div id="animation-fps" class="property number"> - <label>Animation FPS</label> - <input type="number" id="property-model-animation-fps"> - </div> - </div> - <div class="column"> - <div class="property number"> - <label>Animation frame</label> - <input type="number" id="property-model-animation-frame"> - </div> - <div class="property number"> - <label>First frame</label> - <input type="number" id="property-model-animation-first-frame"> - </div> - <div class="property number"> - <label>Last frame</label> - <input type="number" id="property-model-animation-last-frame"> - </div> - </div> - </div> - </fieldset> - <fieldset class="minor"> - <div class="model-group model-section property textarea"> - <label for="property-model-textures">Textures</label> - <textarea id="property-model-textures"></textarea> - </div> - <div class="model-group model-section property textarea"> - <label for="property-model-original-textures">Original textures</label> - <textarea id="property-model-original-textures" readonly></textarea> - </div> - </fieldset> - </fieldset> - - <fieldset id="zone" class="major"> - <legend class="section-header zone-group zone-section"> - Zone<span class=".collapse-icon">M</span> - </legend> - <fieldset class="minor"> - <div class="zone-group zone-section property checkbox"> - <input type="checkbox" id="property-zone-flying-allowed"> - <label for="property-zone-flying-allowed">Flying allowed</label> - </div> - <div class="zone-group zone-section property checkbox"> - <input type="checkbox" id="property-zone-ghosting-allowed"> - <label for="property-zone-ghosting-allowed">Ghosting allowed</label> - </div> - </fieldset> - <fieldset class="minor"> - <div class="zone-group zone-section property url "> - <label for="property-zone-filter-url">Filter URL</label> - <input type="text" id="property-zone-filter-url"> - </div> - <div class="sub-section-header zone-group zone-section"> - <label>Key Light</label> - </div> - <form> - <input type="radio" name="keyLightMode" value="inherit" id="property-zone-key-light-mode-inherit" checked> Inherit - <input type="radio" name="keyLightMode" value="disabled" id="property-zone-key-light-mode-disabled"> Off - <input type="radio" name="keyLightMode" value="enabled" id="property-zone-key-light-mode-enabled"> On - </form> - <div class="zone-section keylight-section zone-group property rgb"> - <div class="color-picker" id="property-zone-key-light-color"></div> - <label>Key light color</label> - <div class="tuple"> - <div><input type="number" class="red" id="property-zone-key-light-color-red" min="0" max="255" step="1"><label for="property-zone-key-light-color-red">Red:</label></div> - <div><input type="number" class="green" id="property-zone-key-light-color-green" min="0" max="255" step="1"><label for="property-zone-key-light-color-green">Green:</label></div> - <div><input type="number" class="blue" id="property-zone-key-light-color-blue" min="0" max="255" step="1"><label for="property-zone-key-light-color-blue">Blue:</label></div> - </div> - </div> - <div class="zone-section keylight-section zone-group property number"> - <label>Light intensity</label> - <input type="number" id="property-zone-key-intensity" min="0" max="10" step="0.1"> - </div> - <div class="zone-group zone-section keylight-section property gen"> - <div class="tuple"> - <div><label>Light altitude <span class="unit">deg</span></label><input type="number" id="property-zone-key-light-direction-x"></div> - <div><label>Light azimuth <span class="unit">deg</span></label><input type="number" id="property-zone-key-light-direction-y"></div> - <div></div> - </div> - </div> - <div class="zone-group zone-section keylight-section property checkbox"> - <input type="checkbox" id="property-zone-key-light-cast-shadows"> - <label for="property-zone-key-light-cast-shadows">Cast Shadows</label> - </div> - <fieldset class="minor"> - <legend class="sub-section-header zone-group zone-section background-section"> - Skybox - </legend> - <form> - <input type="radio" name="skyboxMode" value="inherit" id="property-zone-skybox-mode-inherit" checked> Inherit - <input type="radio" name="skyboxMode" value="disabled" id="property-zone-skybox-mode-disabled"> Off - <input type="radio" name="skyboxMode" value="enabled" id="property-zone-skybox-mode-enabled"> On - </form> - <fieldset class="zone-group zone-section skybox-section property rgb fstuple"> - <div class="color-picker" id="property-zone-skybox-color"></div> - <legend>Skybox color</legend> - <div class="tuple"> - <div> - <input type="number" class="red" id="property-zone-skybox-color-red"> - <label for="property-zone-skybox-color-red">Red:</label> - </div> - <div> - <input type="number" class="green" id="property-zone-skybox-color-green"> - <label for="property-zone-skybox-color-green">Green:</label> - </div> - <div> - <input type="number" class="blue" id="property-zone-skybox-color-blue"> - <label for="property-zone-skybox-color-blue">Blue:</label> - </div> - </div> - </fieldset> - <div class="zone-group zone-section skybox-section property url "> - <br> - <label for="property-zone-skybox-url" width="80">Skybox URL</label> - <input type="text" id="property-zone-skybox-url"> - <br> - <br> - <input type="button" id="copy-skybox-url-to-ambient-url" value="Copy URL to Ambient"> - </div> - </fieldset> - <div class="sub-section-header zone-group zone-section"> - <label>Ambient Light</label> - </div> - <form> - <input type="radio" name="ambientLightMode" value="inherit" id="property-zone-ambient-light-mode-inherit" checked> Inherit - <input type="radio" name="ambientLightMode" value="disabled" id="property-zone-ambient-light-mode-disabled"> Off - <input type="radio" name="ambientLightMode" value="enabled" id="property-zone-ambient-light-mode-enabled"> On - </form> - <div class="zone-group zone-section ambient-section property number"> - <label>Ambient intensity</label> - <input type="number" id="property-zone-key-ambient-intensity" min="0" max="10" step="0.1"> - </div> - <div class="zone-group zone-section ambient-section property url "> - <label for="property-zone-key-ambient-url">Ambient URL</label> - <input type="text" id="property-zone-key-ambient-url"> - </div> - </fieldset> - <fieldset class="minor"> - <legend class="sub-section-header zone-group zone-section"> - Haze - </legend> - <form> - <input type="radio" name="hazeMode" value="inherit" id="property-zone-haze-mode-inherit" checked> Inherit - <input type="radio" name="hazeMode" value="disabled" id="property-zone-haze-mode-disabled"> Off - <input type="radio" name="hazeMode" value="enabled" id="property-zone-haze-mode-enabled"> On - </form> - <fieldset class="zone-group zone-section haze-section property gen fstuple"> - <div class="tuple"> - <div> - <label>Range<span class="unit">m</span></label> - <input type="number" id="property-zone-haze-range" min="5" max="10000" step="5"> - </div> - </div> - </fieldset> - <div class="zone-group zone-section haze-section property checkbox"> - <input type="checkbox" id="property-zone-haze-altitude-effect"> - <label for="property-zone-haze-altitude-effect">Use Altitude</label> - </div> - <fieldset class="zone-group zone-section haze-section property gen fstuple"> - <div class="tuple"> - <div><label>Base<span class="unit">m</span></label><input type="number" id="property-zone-haze-base" min="-1000" max="1000" step="10"></div> - <div><label>Ceiling<span class="unit">m</span></label><input type="number" id="property-zone-haze-ceiling" min="-1000" max="5000" step="10"></div> - </div> - </fieldset> - <fieldset class="zone-group zone-section haze-section property rgb fstuple"> - <div class="color-picker" id="property-zone-haze-color"></div> - <legend>Haze Color</legend> - <div class="tuple"> - <div><input type="number" class="red" id="property-zone-haze-color-red" min="0" max="255" step="1"> - <label for="property-zone-haze-blend-in-color-red">Red:</label></div> - <div><input type="number" class="green" id="property-zone-haze-color-green" min="0" max="255" step="1"> - <label for="property-zone-haze-blend-in-color-green">Green:</label></div> - <div><input type="number" class="blue" id="property-zone-haze-color-blue" min="0" max="255" step="1"> - <label for="property-zone-haze-blend-in-color-blue">Blue:</label></div> - </div> - </fieldset> - <fieldset class="zone-group zone-section haze-section property gen fstuple"> - <div class="tuple"> - <div> - <br> - <table> - <td><label>Background Blend 0.0</label></td> - <td><input type="range" class="slider" id="property-zone-haze-background-blend" min="0.0" max="1.0" step="0.01" value="0.0"></td> - <td><label>1.0</label></td> - </table> - </div> - </div> - </fieldset> - <div class="zone-group zone-section haze-section property checkbox"> - <input type="checkbox" id="property-zone-haze-enable-light-blend"> - <label for="property-zone-haze-enable-light-blend">Enable Glare</label> - </div> - <fieldset class="zone-group zone-section haze-section property rgb fstuple"> - <div class="color-picker" id="property-zone-haze-glare-color"></div> - <legend>Glare Color</legend> - <div class="tuple"> - <div><input type="number" class="red" id="property-zone-haze-glare-color-red" min="0" max="255" step="1"> - <label for="property-zone-haze-blend-out-color-red">Red:</label></div> - <div><input type="number" class="green" id="property-zone-haze-glare-color-green" min="0" max="255" step="1"> - <label for="property-zone-haze-blend-out-color-green">Green:</label></div> - <div><input type="number" class="blue" id="property-zone-haze-glare-color-blue" min="0" max="255" step="1"> - <label for="property-zone-haze-blend-out-color-blue">Blue:</label></div> - </div> - </fieldset> - <fieldset class="zone-group zone-section haze-section property gen fstuple"> - <div class="tuple"> - <div> - <br> - <table> - <td><label>Glare Angle 0</label></td> - <td><input type="range" class="slider" id="property-zone-haze-blend-angle" min="0" max="180" step="1"></td> - <td><label>180</label> - </table> - </div> - </div> - </fieldset> - <!--div class="zone-group zone-section haze-section property checkbox"> - <input type="checkbox" id="property-zone-haze-attenuate-keylight"> - <label for="property-zone-haze-attenuate-keylight">Attenuate Keylight</label> - </div> - <fieldset class="zone-group zone-section haze-section property gen fstuple"> - <div class="tuple"> - <div><label>Range<span class="unit">m</span></label><input type="number" id="property-zone-haze-keylight-range" - min="5" max="1000000" step="5"></div> - <div><label>Altitude<span class="unit">m</span></label><input type="number" id="property-zone-haze-keylight-altitude" - min="-1000" max="50000" step="10"></div> - </div> - </fieldset--> - </fieldset> - <fieldset class="minor"> - <legend class="sub-section-header zone-group zone-section"> - Bloom - </legend> - <form> - <input type="radio" name="bloomMode" value="inherit" id="property-zone-bloom-mode-inherit" checked> Inherit - <input type="radio" name="bloomMode" value="disabled" id="property-zone-bloom-mode-disabled"> Off - <input type="radio" name="bloomMode" value="enabled" id="property-zone-bloom-mode-enabled"> On - </form> - <fieldset class="zone-group zone-section bloom-section property gen fstuple"> - <div class="tuple"> - <div> - <br> - <table> - <td><label>Bloom Intensity 0</label></td> - <td><input type="range" class="slider" id="property-zone-bloom-intensity" min="0" max="1" step="0.01"></td> - <td><label>1</label> - </table> - </div> - </div> - </fieldset> - <fieldset class="zone-group zone-section bloom-section property gen fstuple"> - <div class="tuple"> - <div> - <br> - <table> - <td><label>Bloom Threshold 0</label></td> - <td><input type="range" class="slider" id="property-zone-bloom-threshold" min="0" max="1" step="0.01"></td> - <td><label>1</label> - </table> - </div> - </div> - </fieldset> - <fieldset class="zone-group zone-section bloom-section property gen fstuple"> - <div class="tuple"> - <div> - <br> - <table> - <td><label>Bloom Size 0</label></td> - <td><input type="range" class="slider" id="property-zone-bloom-size" min="0" max="2" step="0.01"></td> - <td><label>2</label> - </table> - </div> - </div> - </fieldset> - </fieldset> - </fieldset> - <fieldset id="text" class="major"> - <legend class="section-header text-group text-section"> - Text<span class=".collapse-icon">M</span> - </legend> - <div class="text-group text-section property text"> - <label for="property-text-text">Text content</label> - <input type="text" id="property-text-text"> - </div> - <div class="text-group text-section property checkbox"> - <input type="checkbox" id="property-text-face-camera"> - <label for="property-text-face-camera"> Face Camera</label> - </div> - <div class="text-group text-section property number"> - <label>Line height <span class="unit">m</span></label> - <input type="number" id="property-text-line-height" min="0" step="0.005"> - </div> - <div class="text-group text-section property rgb"> - <div class="color-picker" id="property-text-text-color"></div> - <label>Text color</label> - <div class="tuple"> - <div><input type="number" class="red" id="property-text-text-color-red"><label for="property-text-text-color-red">Red:</label></div> - <div><input type="number" class="green" id="property-text-text-color-green"><label for="property-text-text-color-green">Green:</label></div> - <div><input type="number" class="blue" id="property-text-text-color-blue"><label for="property-text-text-color-blue">Blue:</label></div> - </div> - </div> - <fieldset class="text-group text-section property rgb fstuple"> - <div class="color-picker" id="property-text-background-color"></div> - <legend>Background color</legend> - <div class="tuple"> - <div><input type="number" class="red" id="property-text-background-color-red"><label for="roperty-text-background-color-red">Red:</label></div> - <div><input type="number" class="green" id="property-text-background-color-green"><label for="property-text-background-color-green">Green:</label></div> - <div><input type="number" class="blue" id="property-text-background-color-blue"><label for="property-text-background-color-blue">Blue:</label></div> - </div> - </fieldset> - </fieldset> - - <fieldset id="image" class="major"> - <legend class="section-header image-group image-section"> - Image<span>M</span> - </legend> - <div class="image-group image-section property url "> - <label for="property-image-url">Image URL</label> - <input type="text" id="property-image-url"> - </div> - </fieldset> - - <fieldset id="web" class="major"> - <legend class="section-header web-group web-section"> - Web<span class=".collapse-icon">M</span> - </legend> - <div class="web-group web-section property url "> - <label for="property-web-source-url">Source URL</label> - <input type="text" id="property-web-source-url"> - </div> - <div class="web-group web-section property dpi "> - <label for="property-web-dpi">Resolution (DPI)</label> - <input type="number" id="property-web-dpi"> - </div> - </fieldset> - - <fieldset id="polyvox" class="major"> - <legend class="section-header spatial-group poly-vox-section property xyz"> - Voxel volume size <span>m</span> - </legend> - <div class="tuple"> - <div><input type="number" class="x" id="property-voxel-volume-size-x"><label for="property-voxel-volume-size-x">X:</label></div> - <div><input type="number" class="y" id="property-voxel-volume-size-y"><label for="property-voxel-volume-size-y">Y:</label></div> - <div><input type="number" class="z" id="property-voxel-volume-size-z"><label for="property-voxel-volume-size-z">Z:</label></div> - </div> - <div class="spatial-group poly-vox-section property dropdown"> - <label>Surface extractor</label> - <select name="SelectVoxelSurfaceStyle" id="property-voxel-surface-style"> - <option value="0">Marching cubes</option> - <option value="1">Cubic</option> - <option value="2">Edged cubic</option> - <option value="3">Edged marching cubes</option> - </select> - </div> - <div class="spatial-group poly-vox-section property url "> - <label for="property-x-texture-url">X-axis texture URL</label> - <input type="text" id="property-x-texture-url"> - </div> - <div class="spatial-group poly-vox-section property url "> - <label for="property-y-texture-url">Y-axis texture URL</label> - <input type="text" id="property-y-texture-url"> - </div> - <div class="spatial-group poly-vox-section property url "> - <label for="property-z-texture-url">Z-axis texture URL</label> - <input type="text" id="property-z-texture-url"> - </div> - </fieldset> - - <fieldset id="material" class="major"> - <legend class="section-header material-group material-section"> - Material<span>M</span> - </legend> - <fieldset class="minor"> - <div class="material-group material-section property url"> - <label for="property-material-url">Material URL</label> - <input type="text" id="property-material-url"> - </div> - - <div class="property textarea"> - <label for="property-material-data">Material data</label> - <br><br> - <div class="row"> - <input type="button" class="red" id="materialdata-clear" value="Clear Material Data"> - <input type="button" class="blue" id="materialdata-new-editor" value="Edit as JSON"> - <input disabled type="button" class="black" id="materialdata-save" value="Save Material Data"> - <span id="materialdata-saved" visible="false">Saved!</span> - </div> - <div id="static-naterialdata"></div> - <div id="materialdata-editor"></div> - <textarea id="property-material-data"></textarea> - </div> - </fieldset> - <fieldset class="minor"> - <div class="material-group material-section property text" id="property-parent-material-id-string-container"> - <label for="property-parent-material-id-string">Material Name to Replace </label> - <input type="text" id="property-parent-material-id-string"> - </div> - - <div class="material-group material-section property number" id="property-parent-material-id-number-container"> - <label for="property-parent-material-id-number">Submesh to Replace </label> - <input type="number" min="0" step="1" value="0" id="property-parent-material-id-number"> - </div> - - <div class="material-group material-section property checkbox"> - <input type="checkbox" id="property-parent-material-id-checkbox"> - <label for="property-parent-material-id-checkbox">Select Submesh</label> - </div> - - <div class="material-group material-section property number"> - <label>Priority </label> - <input type="number" id="property-priority" min="0"> - </div> - - <fieldset> - <!-- TODO: support 3D projected materials - <div class="material-group material-section property dropdown"> - <label>Material mode </label> - <select name="SelectMaterialMode" id="property-material-mapping-mode"> - <option value="uv">UV space material</option> - <option value="projected">3D projected material</option> - </select> - </div> - --> - - <div class="material-group material-section property xy fstuple"> - <label>Material Position </label> - <div class="tuple"> - <div><input type="number" class="x" id="property-material-mapping-pos-x" min="0" max="1" step="0.1"><label for="property-material-mapping-pos-x">X:</label></div> - <div><input type="number" class="y" id="property-material-mapping-pos-y" min="0" max="1" step="0.1"><label for="property-material-mapping-pos-y">Y:</label></div> - </div> - - <div class="material-group material-section property wh fstuple"> - <label>Material Scale </label> - <div class="tuple"> - <div><input type="number" class="x" id="property-material-mapping-scale-x" min="0" step="0.1"><label for="property-material-mapping-scale-x">Width:</label></div> - <div><input type="number" class="y" id="property-material-mapping-scale-y" min="0" step="0.1"><label for="property-material-mapping-scale-y">Height:</label></div> - </div> - - <div class="material-group material-section property number"> - <label>Material Rotation <span class="unit">deg</span></label> - <input type="number" id="property-material-mapping-rot" step="0.1"> - </div> - </fieldset> - - </fieldset> - </fieldset> - + <!-- each property is added at runtime in entityProperties --> </div> </body> diff --git a/scripts/system/html/js/createAppTooltip.js b/scripts/system/html/js/createAppTooltip.js new file mode 100644 index 0000000000..a5c961a7e2 --- /dev/null +++ b/scripts/system/html/js/createAppTooltip.js @@ -0,0 +1,116 @@ +// createAppTooltip.js +// +// Created by Thijs Wenker on 17 Oct 2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +const CREATE_APP_TOOLTIP_OFFSET = 20; +const TOOLTIP_DELAY = 500; // ms +const TOOLTIP_DEBUG = false; + +function CreateAppTooltip() { + this._tooltipData = null; + this._tooltipDiv = null; + this._delayTimeout = null; + this._isEnabled = false; +} + +CreateAppTooltip.prototype = { + _tooltipData: null, + _tooltipDiv: null, + _delayTimeout: null, + _isEnabled: null, + + _removeTooltipIfExists: function() { + if (this._delayTimeout !== null) { + window.clearTimeout(this._delayTimeout); + this._delayTimeout = null; + } + + if (this._tooltipDiv !== null) { + this._tooltipDiv.remove(); + this._tooltipDiv = null; + } + }, + + setIsEnabled: function(isEnabled) { + this._isEnabled = isEnabled; + }, + + setTooltipData: function(tooltipData) { + this._tooltipData = tooltipData; + }, + + registerTooltipElement: function(element, tooltipID) { + element.addEventListener("mouseover", function() { + if (!this._isEnabled) { + return; + } + + this._removeTooltipIfExists(); + + this._delayTimeout = window.setTimeout(function() { + let tooltipData = this._tooltipData[tooltipID]; + + if (!tooltipData || tooltipData.tooltip === "") { + if (!TOOLTIP_DEBUG) { + return; + } + tooltipData = { tooltip: 'PLEASE SET THIS TOOLTIP' }; + } + + let elementRect = element.getBoundingClientRect(); + let elTip = document.createElement("div"); + elTip.className = "create-app-tooltip"; + + let elTipDescription = document.createElement("div"); + elTipDescription.className = "create-app-tooltip-description"; + elTipDescription.innerText = tooltipData.tooltip; + elTip.appendChild(elTipDescription); + + let jsAttribute = tooltipID; + if (tooltipData.jsPropertyName) { + jsAttribute = tooltipData.jsPropertyName; + } + + if (!tooltipData.skipJSProperty) { + let elTipJSAttribute = document.createElement("div"); + elTipJSAttribute.className = "create-app-tooltip-js-attribute"; + elTipJSAttribute.innerText = `JS Attribute: ${jsAttribute}`; + elTip.appendChild(elTipJSAttribute); + } + + document.body.appendChild(elTip); + + let elementTop = window.pageYOffset + elementRect.top; + + let desiredTooltipTop = elementTop + element.clientHeight + CREATE_APP_TOOLTIP_OFFSET; + let desiredTooltipLeft = window.pageXOffset + elementRect.left; + + if ((window.innerHeight + window.pageYOffset) < (desiredTooltipTop + elTip.clientHeight)) { + // show above when otherwise out of bounds + elTip.style.top = elementTop - CREATE_APP_TOOLTIP_OFFSET - elTip.clientHeight; + } else { + // show tooltip below by default + elTip.style.top = desiredTooltipTop; + } + if ((window.innerWidth + window.pageXOffset) < (desiredTooltipLeft + elTip.clientWidth)) { + elTip.style.left = document.body.clientWidth + window.pageXOffset - elTip.offsetWidth; + } else { + elTip.style.left = desiredTooltipLeft; + } + + this._tooltipDiv = elTip; + }.bind(this), TOOLTIP_DELAY); + }.bind(this), false); + element.addEventListener("mouseout", function() { + if (!this._isEnabled) { + return; + } + + this._removeTooltipIfExists(); + }.bind(this), false); + } +}; diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 0ced016d26..41c957c4fa 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -13,7 +13,7 @@ const DESCENDING_STRING = '▾'; const LOCKED_GLYPH = ""; const VISIBLE_GLYPH = ""; const TRANSPARENCY_GLYPH = ""; -const BAKED_GLYPH = "" +const BAKED_GLYPH = ""; const SCRIPT_GLYPH = "k"; const BYTES_PER_MEGABYTE = 1024 * 1024; const IMAGE_MODEL_NAME = 'default-image-model.fbx'; @@ -23,6 +23,7 @@ const FILTER_IN_VIEW_ATTRIBUTE = "pressed"; const WINDOW_NONVARIABLE_HEIGHT = 227; const NUM_COLUMNS = 12; const EMPTY_ENTITY_ID = "0"; +const MAX_LENGTH_RADIUS = 9; const DELETE = 46; // Key code for the delete key. const KEY_P = 80; // Key code for letter p used for Parenting hotkey. @@ -54,10 +55,34 @@ const COMPARE_ASCENDING = function(a, b) { } return 1; -} +}; const COMPARE_DESCENDING = function(a, b) { return COMPARE_ASCENDING(b, a); -} +}; + +const FILTER_TYPES = [ + "Shape", + "Model", + "Image", + "Light", + "Zone", + "Web", + "Material", + "ParticleEffect", + "Text", +]; + +const ICON_FOR_TYPE = { + Shape: "n", + Model: "", + Image: "", + Light: "p", + Zone: "o", + Web: "q", + Material: "", + ParticleEffect: "", + Text: "l", +}; // List of all entities var entities = []; @@ -70,8 +95,14 @@ var selectedEntities = []; var entityList = null; // The ListView +/** + * @type EntityListContextMenu + */ +var entityListContextMenu = null; + var currentSortColumn = 'type'; var currentSortOrder = ASCENDING_SORT; +var typeFilters = []; var isFilterInView = false; var showExtraInfo = false; @@ -105,9 +136,12 @@ function loaded() { elToggleLocked = document.getElementById("locked"); elToggleVisible = document.getElementById("visible"); elDelete = document.getElementById("delete"); - elFilter = document.getElementById("filter"); - elInView = document.getElementById("in-view") - elRadius = document.getElementById("radius"); + elFilterTypeSelectBox = document.getElementById("filter-type-select-box"); + elFilterTypeText = document.getElementById("filter-type-text"); + elFilterTypeCheckboxes = document.getElementById("filter-type-checkboxes"); + elFilterSearch = document.getElementById("filter-search"); + elFilterInView = document.getElementById("filter-in-view") + elFilterRadius = document.getElementById("filter-radius"); elExport = document.getElementById("export"); elPal = document.getElementById("pal"); elInfoToggle = document.getElementById("info-toggle"); @@ -115,9 +149,8 @@ function loaded() { elSelectedEntitiesCount = document.getElementById("selected-entities-count"); elVisibleEntitiesCount = document.getElementById("visible-entities-count"); elNoEntitiesMessage = document.getElementById("no-entities"); - elNoEntitiesInView = document.getElementById("no-entities-in-view"); - elNoEntitiesRadius = document.getElementById("no-entities-radius"); + document.body.onclick = onBodyClick; document.getElementById("entity-name").onclick = function() { setSortColumn('name'); }; @@ -127,74 +160,185 @@ function loaded() { document.getElementById("entity-url").onclick = function() { setSortColumn('url'); }; - document.getElementById("entity-locked").onclick = function () { + document.getElementById("entity-locked").onclick = function() { setSortColumn('locked'); }; - document.getElementById("entity-visible").onclick = function () { + document.getElementById("entity-visible").onclick = function() { setSortColumn('visible'); }; - document.getElementById("entity-verticesCount").onclick = function () { + document.getElementById("entity-verticesCount").onclick = function() { setSortColumn('verticesCount'); }; - document.getElementById("entity-texturesCount").onclick = function () { + document.getElementById("entity-texturesCount").onclick = function() { setSortColumn('texturesCount'); }; - document.getElementById("entity-texturesSize").onclick = function () { + document.getElementById("entity-texturesSize").onclick = function() { setSortColumn('texturesSize'); }; - document.getElementById("entity-hasTransparent").onclick = function () { + document.getElementById("entity-hasTransparent").onclick = function() { setSortColumn('hasTransparent'); }; - document.getElementById("entity-isBaked").onclick = function () { + document.getElementById("entity-isBaked").onclick = function() { setSortColumn('isBaked'); }; - document.getElementById("entity-drawCalls").onclick = function () { + document.getElementById("entity-drawCalls").onclick = function() { setSortColumn('drawCalls'); }; - document.getElementById("entity-hasScript").onclick = function () { + document.getElementById("entity-hasScript").onclick = function() { setSortColumn('hasScript'); }; elRefresh.onclick = function() { refreshEntities(); - } + }; elToggleLocked.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleLocked' })); - } + }; elToggleVisible.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleVisible' })); - } + }; elExport.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'export'})); - } + }; elPal.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'pal' })); - } + }; elDelete.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); - } - elFilter.onkeyup = refreshEntityList; - elFilter.onpaste = refreshEntityList; - elFilter.onchange = onFilterChange; - elFilter.onblur = refreshFooter; - elInView.onclick = toggleFilterInView; - elRadius.onchange = onRadiusChange; + }; + elFilterTypeSelectBox.onclick = onToggleTypeDropdown; + elFilterSearch.onkeyup = refreshEntityList; + elFilterSearch.onsearch = refreshEntityList; + elFilterInView.onclick = toggleFilterInView; + elFilterRadius.onkeyup = onRadiusChange; + elFilterRadius.onchange = onRadiusChange; + elFilterRadius.onclick = onRadiusChange; elInfoToggle.onclick = toggleInfo; - elNoEntitiesInView.style.display = "none"; + // create filter type dropdown checkboxes with label and icon for each type + for (let i = 0; i < FILTER_TYPES.length; ++i) { + let type = FILTER_TYPES[i]; + let typeFilterID = "filter-type-" + type; + let elDiv = document.createElement('div'); + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", typeFilterID); + elLabel.innerText = type; + let elSpan = document.createElement('span'); + elSpan.setAttribute("class", "typeIcon"); + elSpan.innerHTML = ICON_FOR_TYPE[type]; + let elInput = document.createElement('input'); + elInput.setAttribute("type", "checkbox"); + elInput.setAttribute("id", typeFilterID); + elInput.setAttribute("filterType", type); + elInput.checked = true; // all types are checked initially + toggleTypeFilter(elInput, false); // add all types to the initial types filter + elDiv.appendChild(elInput); + elLabel.insertBefore(elSpan, elLabel.childNodes[0]); + elDiv.appendChild(elLabel); + elFilterTypeCheckboxes.appendChild(elDiv); + elDiv.onclick = onToggleTypeFilter; + } entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow, createRow, updateRow, clearRow, WINDOW_NONVARIABLE_HEIGHT); - + + entityListContextMenu = new EntityListContextMenu(); + + + function startRenamingEntity(entityID) { + let entity = entitiesByID[entityID]; + if (!entity || entity.locked || !entity.elRow) { + return; + } + + let elCell = entity.elRow.childNodes[COLUMN_INDEX.NAME]; + let elRenameInput = document.createElement("input"); + elRenameInput.setAttribute('class', 'rename-entity'); + elRenameInput.value = entity.name; + let ignoreClicks = function(event) { + event.stopPropagation(); + }; + elRenameInput.onclick = ignoreClicks; + elRenameInput.ondblclick = ignoreClicks; + elRenameInput.onkeyup = function(keyEvent) { + if (keyEvent.key === "Enter") { + elRenameInput.blur(); + } + }; + + elRenameInput.onblur = function(event) { + let value = elRenameInput.value; + EventBridge.emitWebEvent(JSON.stringify({ + type: 'rename', + entityID: entityID, + name: value + })); + entity.name = value; + elCell.innerText = value; + }; + + elCell.innerHTML = ""; + elCell.appendChild(elRenameInput); + + elRenameInput.select(); + } + + entityListContextMenu.setOnSelectedCallback(function(optionName, selectedEntityID) { + switch (optionName) { + case "Cut": + EventBridge.emitWebEvent(JSON.stringify({ type: 'cut' })); + break; + case "Copy": + EventBridge.emitWebEvent(JSON.stringify({ type: 'copy' })); + break; + case "Paste": + EventBridge.emitWebEvent(JSON.stringify({ type: 'paste' })); + break; + case "Rename": + startRenamingEntity(selectedEntityID); + break; + case "Duplicate": + EventBridge.emitWebEvent(JSON.stringify({ type: 'duplicate' })); + break; + case "Delete": + EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); + break; + } + }); + + function onRowContextMenu(clickEvent) { + let entityID = this.dataset.entityID; + + if (!selectedEntities.includes(entityID)) { + let selection = [entityID]; + updateSelectedEntities(selection); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "selectionUpdate", + focus: false, + entityIds: selection, + })); + } + + let enabledContextMenuItems = ['Copy', 'Paste', 'Duplicate']; + if (entitiesByID[entityID] && !entitiesByID[entityID].locked) { + enabledContextMenuItems.push('Cut'); + enabledContextMenuItems.push('Rename'); + enabledContextMenuItems.push('Delete'); + } + + entityListContextMenu.open(clickEvent, entityID, enabledContextMenuItems); + } + function onRowClicked(clickEvent) { let entityID = this.dataset.entityID; let selection = [entityID]; - + if (clickEvent.ctrlKey) { let selectedIndex = selectedEntities.indexOf(entityID); if (selectedIndex >= 0) { selection = []; selection = selection.concat(selectedEntities); - selection.splice(selectedIndex, 1) + selection.splice(selectedIndex, 1); } else { selection = selection.concat(selectedEntities); } @@ -221,28 +365,29 @@ function loaded() { } } } else if (!clickEvent.ctrlKey && !clickEvent.shiftKey && selectedEntities.length === 1) { - // if reselecting the same entity then deselect it + // if reselecting the same entity then start renaming it if (selectedEntities[0] === entityID) { - selection = []; + startRenamingEntity(entityID); } } - updateSelectedEntities(selection); + updateSelectedEntities(selection, false); EventBridge.emitWebEvent(JSON.stringify({ type: "selectionUpdate", focus: false, entityIds: selection, })); - - refreshFooter(); } function onRowDoubleClicked() { + let selection = [this.dataset.entityID]; + updateSelectedEntities(selection, false); + EventBridge.emitWebEvent(JSON.stringify({ type: "selectionUpdate", focus: true, - entityIds: [this.dataset.entityID], + entityIds: selection, })); } @@ -289,7 +434,7 @@ function loaded() { hasScript: entity.hasScript, elRow: null, // if this entity has a visible row element assigned to it selected: false // if this entity is selected for edit regardless of having a visible row - } + }; entities.push(entityData); entitiesByID[entityData.id] = entityData; @@ -302,17 +447,16 @@ function loaded() { function refreshEntityList() { PROFILE("refresh-entity-list", function() { PROFILE("filter", function() { - let searchTerm = elFilter.value.toLowerCase(); - if (searchTerm === '') { - visibleEntities = entities.slice(0); - } else { - visibleEntities = entities.filter(function(e) { - return e.name.toLowerCase().indexOf(searchTerm) > -1 - || e.type.toLowerCase().indexOf(searchTerm) > -1 - || e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 - || e.id.toLowerCase().indexOf(searchTerm) > -1; - }); - } + let searchTerm = elFilterSearch.value.toLowerCase(); + visibleEntities = entities.filter(function(e) { + let type = e.type === "Box" || e.type === "Sphere" ? "Shape" : e.type; + let typeFilter = typeFilters.indexOf(type) > -1; + let searchFilter = searchTerm === '' || (e.name.toLowerCase().indexOf(searchTerm) > -1 || + e.type.toLowerCase().indexOf(searchTerm) > -1 || + e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 || + e.id.toLowerCase().indexOf(searchTerm) > -1); + return typeFilter && searchFilter; + }); }); PROFILE("sort", function() { @@ -418,7 +562,7 @@ function loaded() { isBaked: document.querySelector('#entity-isBaked .sort-order'), drawCalls: document.querySelector('#entity-drawCalls .sort-order'), hasScript: document.querySelector('#entity-hasScript .sort-order'), - } + }; function setSortColumn(column) { PROFILE("set-sort-column", function() { if (currentSortColumn === column) { @@ -453,7 +597,7 @@ function loaded() { } } - function updateSelectedEntities(selectedIDs) { + function updateSelectedEntities(selectedIDs, autoScroll) { let notFound = false; // reset all currently selected entities and their rows first @@ -482,6 +626,26 @@ function loaded() { } }); + if (autoScroll && selectedIDs.length > 0) { + let firstItem = Number.MAX_VALUE; + let lastItem = -1; + let itemFound = false; + visibleEntities.forEach(function(entity, index) { + if (selectedIDs.indexOf(entity.id) !== -1) { + if (firstItem > index) { + firstItem = index; + } + if (lastItem < index) { + lastItem = index; + } + itemFound = true; + } + }); + if (itemFound) { + entityList.scrollToRow(firstItem, lastItem); + } + } + refreshFooter(); return notFound; @@ -502,6 +666,7 @@ function loaded() { } row.appendChild(column); } + row.oncontextmenu = onRowContextMenu; row.onclick = onRowClicked; row.ondblclick = onRowDoubleClicked; return row; @@ -582,29 +747,74 @@ function loaded() { function toggleFilterInView() { isFilterInView = !isFilterInView; if (isFilterInView) { - elInView.setAttribute(FILTER_IN_VIEW_ATTRIBUTE, FILTER_IN_VIEW_ATTRIBUTE); - elNoEntitiesInView.style.display = "inline"; + elFilterInView.setAttribute(FILTER_IN_VIEW_ATTRIBUTE, FILTER_IN_VIEW_ATTRIBUTE); } else { - elInView.removeAttribute(FILTER_IN_VIEW_ATTRIBUTE); - elNoEntitiesInView.style.display = "none"; + elFilterInView.removeAttribute(FILTER_IN_VIEW_ATTRIBUTE); } EventBridge.emitWebEvent(JSON.stringify({ type: "filterInView", filterInView: isFilterInView })); refreshEntities(); } - function onFilterChange() { - refreshEntityList(); - entityList.resize(); - } - function onRadiusChange() { - elRadius.value = Math.max(elRadius.value, 0); - elNoEntitiesRadius.firstChild.nodeValue = elRadius.value; - elNoEntitiesMessage.style.display = "none"; - EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elRadius.value })); + elFilterRadius.value = elFilterRadius.value.replace(/[^0-9]/g, ''); + elFilterRadius.value = Math.max(elFilterRadius.value, 0); + EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elFilterRadius.value })); refreshEntities(); } - + + function isTypeDropdownVisible() { + return elFilterTypeCheckboxes.style.display === "block"; + } + + function toggleTypeDropdown() { + elFilterTypeCheckboxes.style.display = isTypeDropdownVisible() ? "none" : "block"; + } + + function onToggleTypeDropdown(event) { + toggleTypeDropdown(); + event.stopPropagation(); + } + + function toggleTypeFilter(elInput, refresh) { + let type = elInput.getAttribute("filterType"); + let typeChecked = elInput.checked; + + let typeFilterIndex = typeFilters.indexOf(type); + if (!typeChecked && typeFilterIndex > -1) { + typeFilters.splice(typeFilterIndex, 1); + } else if (typeChecked && typeFilterIndex === -1) { + typeFilters.push(type); + } + + if (typeFilters.length === 0) { + elFilterTypeText.innerText = "No Types"; + } else if (typeFilters.length === FILTER_TYPES.length) { + elFilterTypeText.innerText = "All Types"; + } else { + elFilterTypeText.innerText = "Types..."; + } + + if (refresh) { + refreshEntityList(); + } + } + + function onToggleTypeFilter(event) { + let elTarget = event.target; + if (elTarget instanceof HTMLInputElement) { + toggleTypeFilter(elTarget, true); + } + event.stopPropagation(); + } + + function onBodyClick(event) { + // if clicking anywhere outside of the type filter dropdown (since click event bubbled up to onBodyClick and + // propagation wasn't stopped by onToggleTypeFilter or onToggleTypeDropdown) and the dropdown is open then close it + if (isTypeDropdownVisible()) { + toggleTypeDropdown(); + } + } + function toggleInfo(event) { showExtraInfo = !showExtraInfo; if (showExtraInfo) { @@ -617,7 +827,7 @@ function loaded() { entityList.resize(); event.stopPropagation(); } - + document.addEventListener("keydown", function (keyDownEvent) { if (keyDownEvent.target.nodeName === "INPUT") { return; @@ -641,7 +851,7 @@ function loaded() { if (data.type === "clearEntityList") { clearEntities(); } else if (data.type === "selectionUpdate") { - let notFound = updateSelectedEntities(data.selectedIDs); + let notFound = updateSelectedEntities(data.selectedIDs, true); if (notFound) { refreshEntities(); } @@ -653,13 +863,13 @@ function loaded() { clearEntities(); } else { updateEntityData(newEntities); - updateSelectedEntities(data.selectedIDs); + updateSelectedEntities(data.selectedIDs, true); } } }); } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { removeEntities(data.deletedIDs); - updateSelectedEntities(data.selectedIDs); + updateSelectedEntities(data.selectedIDs, true); } else if (data.type === "deleted" && data.ids) { removeEntities(data.ids); } @@ -672,8 +882,15 @@ function loaded() { augmentSpinButtons(); - // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked document.addEventListener("contextmenu", function (event) { + entityListContextMenu.close(); + + // Disable default right-click context menu which is not visible in the HMD and makes it seem like the app has locked event.preventDefault(); }, false); + + // close context menu when switching focus to another window + $(window).blur(function() { + entityListContextMenu.close(); + }); } diff --git a/scripts/system/html/js/entityListContextMenu.js b/scripts/system/html/js/entityListContextMenu.js new file mode 100644 index 0000000000..d71719f252 --- /dev/null +++ b/scripts/system/html/js/entityListContextMenu.js @@ -0,0 +1,163 @@ +// +// entityListContextMenu.js +// +// exampleContextMenus.js was originally created by David Rowe on 22 Aug 2018. +// Modified to entityListContextMenu.js by Thijs Wenker on 10 Oct 2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* eslint-env browser */ +const CONTEXT_MENU_CLASS = "context-menu"; + +/** + * ContextMenu class for EntityList + * @constructor + */ +function EntityListContextMenu() { + this._elContextMenu = null; + this._onSelectedCallback = null; + this._listItems = []; + this._initialize(); +} + +EntityListContextMenu.prototype = { + + /** + * @private + */ + _elContextMenu: null, + + /** + * @private + */ + _onSelectedCallback: null, + + /** + * @private + */ + _selectedEntityID: null, + + /** + * @private + */ + _listItems: null, + + /** + * Close the context menu + */ + close: function() { + if (this.isContextMenuOpen()) { + this._elContextMenu.style.display = "none"; + } + }, + + isContextMenuOpen: function() { + return this._elContextMenu.style.display === "block"; + }, + + /** + * Open the context menu + * @param clickEvent + * @param selectedEntityID + * @param enabledOptions + */ + open: function(clickEvent, selectedEntityID, enabledOptions) { + this._selectedEntityID = selectedEntityID; + + this._listItems.forEach(function(listItem) { + let enabled = enabledOptions.includes(listItem.label); + listItem.enabled = enabled; + listItem.element.setAttribute('class', enabled ? '' : 'disabled'); + }); + + this._elContextMenu.style.display = "block"; + this._elContextMenu.style.left + = Math.min(clickEvent.pageX, document.body.offsetWidth - this._elContextMenu.offsetWidth).toString() + "px"; + this._elContextMenu.style.top + = Math.min(clickEvent.pageY, document.body.clientHeight - this._elContextMenu.offsetHeight).toString() + "px"; + clickEvent.stopPropagation(); + }, + + /** + * Set the callback for when a menu item is selected + * @param onSelectedCallback + */ + setOnSelectedCallback: function(onSelectedCallback) { + this._onSelectedCallback = onSelectedCallback; + }, + + /** + * Add a labeled item to the context menu + * @param itemLabel + * @private + */ + _addListItem: function(itemLabel) { + let elListItem = document.createElement("li"); + elListItem.innerText = itemLabel; + + let listItem = { + label: itemLabel, + element: elListItem, + enabled: false + }; + + elListItem.addEventListener("click", function () { + if (listItem.enabled && this._onSelectedCallback) { + this._onSelectedCallback.call(this, itemLabel, this._selectedEntityID); + } + }.bind(this), false); + + elListItem.setAttribute('class', 'disabled'); + + this._listItems.push(listItem); + this._elContextMenu.appendChild(elListItem); + }, + + /** + * Add a separator item to the context menu + * @private + */ + _addListSeparator: function() { + let elListItem = document.createElement("li"); + elListItem.setAttribute('class', 'separator'); + this._elContextMenu.appendChild(elListItem); + }, + + /** + * Initialize the context menu. + * @private + */ + _initialize: function() { + this._elContextMenu = document.createElement("ul"); + this._elContextMenu.setAttribute("class", CONTEXT_MENU_CLASS); + document.body.appendChild(this._elContextMenu); + + this._addListItem("Cut"); + this._addListItem("Copy"); + this._addListItem("Paste"); + this._addListSeparator(); + this._addListItem("Rename"); + this._addListItem("Duplicate"); + this._addListItem("Delete"); + + // Ignore clicks on context menu background or separator. + this._elContextMenu.addEventListener("click", function(event) { + // Sink clicks on context menu background or separator but let context menu item clicks through. + if (event.target.classList.contains(CONTEXT_MENU_CLASS)) { + event.stopPropagation(); + } + }); + + // Provide means to close context menu without clicking menu item. + document.body.addEventListener("click", this.close.bind(this)); + document.body.addEventListener("keydown", function(event) { + // Close context menu with Esc key. + if (this.isContextMenuOpen() && event.key === "Escape") { + this.close(); + } + }.bind(this)); + } +}; diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 2d2e4d5675..b66d7e19c6 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1,18 +1,16 @@ // entityProperties.js // // Created by Ryan Huffman on 13 Nov 2014 +// Modified by David Back on 19 Oct 2018 // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global alert, augmentSpinButtons, clearTimeout, document, Element, EventBridge, - JSONEditor, openEventBridge, setTimeout, window, $ */ - -var PI = 3.14159265358979; -var DEGREES_TO_RADIANS = PI / 180.0; -var RADIANS_TO_DEGREES = 180.0 / PI; -var ICON_FOR_TYPE = { +/* global alert, augmentSpinButtons, clearTimeout, console, document, Element, + EventBridge, JSONEditor, openEventBridge, setTimeout, window, _ $ */ + +const ICON_FOR_TYPE = { Box: "V", Sphere: "n", Shape: "n", @@ -27,16 +25,1298 @@ var ICON_FOR_TYPE = { Multiple: "", PolyLine: "", Material: "" +}; + +const DEGREES_TO_RADIANS = Math.PI / 180.0; + +const NO_SELECTION = "<i>No selection</i>"; + +const GROUPS = [ + { + id: "base", + properties: [ + { + label: NO_SELECTION, + type: "icon", + icons: ICON_FOR_TYPE, + propertyID: "type", + }, + { + label: "Name", + type: "string", + propertyID: "name", + }, + { + label: "ID", + type: "string", + propertyID: "id", + readOnly: true, + }, + { + label: "Description", + type: "string", + propertyID: "description", + }, + { + label: "Parent", + type: "string", + propertyID: "parentID", + }, + { + label: "Parent Joint Index", + type: "number", + propertyID: "parentJointIndex", + }, + { + label: "Locked", + glyph: "", + type: "bool", + propertyID: "locked", + }, + { + label: "Visible", + glyph: "", + type: "bool", + propertyID: "visible", + }, + ] + }, + { + id: "shape", + addToGroup: "base", + properties: [ + { + label: "Shape", + type: "dropdown", + options: { Cube: "Box", Sphere: "Sphere", Tetrahedron: "Tetrahedron", Octahedron: "Octahedron", + Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", + Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", + Circle: "Circle", Quad: "Quad" }, + propertyID: "shape", + }, + { + label: "Color", + type: "color", + propertyID: "color", + }, + ] + }, + { + id: "text", + addToGroup: "base", + properties: [ + { + label: "Text", + type: "string", + propertyID: "text", + }, + { + label: "Text Color", + type: "color", + propertyID: "textColor", + }, + { + label: "Background Color", + type: "color", + propertyID: "backgroundColor", + }, + { + label: "Line Height", + type: "number", + min: 0, + step: 0.005, + decimals: 4, + unit: "m", + propertyID: "lineHeight" + }, + { + label: "Face Camera", + type: "bool", + propertyID: "faceCamera" + }, + ] + }, + { + id: "zone", + addToGroup: "base", + properties: [ + { + label: "Flying Allowed", + type: "bool", + propertyID: "flyingAllowed", + }, + { + label: "Ghosting Allowed", + type: "bool", + propertyID: "ghostingAllowed", + }, + { + label: "Filter", + type: "string", + propertyID: "filterURL", + }, + { + label: "Key Light", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "keyLightMode", + + }, + { + label: "Key Light Color", + type: "color", + propertyID: "keyLight.color", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Light Intensity", + type: "number", + min: 0, + max: 10, + step: 0.1, + decimals: 2, + propertyID: "keyLight.intensity", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Light Horizontal Angle", + type: "number", + multiplier: DEGREES_TO_RADIANS, + decimals: 2, + unit: "deg", + propertyID: "keyLight.direction.x", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Light Vertical Angle", + type: "number", + multiplier: DEGREES_TO_RADIANS, + decimals: 2, + unit: "deg", + propertyID: "keyLight.direction.y", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Cast Shadows", + type: "bool", + propertyID: "keyLight.castShadows", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Skybox", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "skyboxMode", + }, + { + label: "Skybox Color", + type: "color", + propertyID: "skybox.color", + showPropertyRule: { "skyboxMode": "enabled" }, + }, + { + label: "Skybox Source", + type: "string", + propertyID: "skybox.url", + showPropertyRule: { "skyboxMode": "enabled" }, + }, + { + label: "Ambient Light", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "ambientLightMode", + }, + { + label: "Ambient Intensity", + type: "number", + min: 0, + max: 10, + step: 0.1, + decimals: 2, + propertyID: "ambientLight.ambientIntensity", + showPropertyRule: { "ambientLightMode": "enabled" }, + }, + { + label: "Ambient Source", + type: "string", + propertyID: "ambientLight.ambientURL", + showPropertyRule: { "ambientLightMode": "enabled" }, + }, + { + type: "buttons", + buttons: [ { id: "copy", label: "Copy from Skybox", + className: "black", onClick: copySkyboxURLToAmbientURL } ], + propertyID: "copyURLToAmbient", + showPropertyRule: { "skyboxMode": "enabled" }, + }, + { + label: "Haze", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "hazeMode", + }, + { + label: "Range", + type: "number", + min: 5, + max: 10000, + step: 5, + decimals: 0, + unit: "m", + propertyID: "haze.hazeRange", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Use Altitude", + type: "bool", + propertyID: "haze.hazeAltitudeEffect", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Base", + type: "number", + min: -1000, + max: 1000, + step: 10, + decimals: 0, + unit: "m", + propertyID: "haze.hazeBaseRef", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Ceiling", + type: "number", + min: -1000, + max: 5000, + step: 10, + decimals: 0, + unit: "m", + propertyID: "haze.hazeCeiling", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Haze Color", + type: "color", + propertyID: "haze.hazeColor", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Background Blend", + type: "slider", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "haze.hazeBackgroundBlend", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Enable Glare", + type: "bool", + propertyID: "haze.hazeEnableGlare", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Glare Color", + type: "color", + propertyID: "haze.hazeGlareColor", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Glare Angle", + type: "slider", + min: 0, + max: 180, + step: 1, + decimals: 0, + propertyID: "haze.hazeGlareAngle", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Bloom", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "bloomMode", + }, + { + label: "Bloom Intensity", + type: "slider", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "bloom.bloomIntensity", + showPropertyRule: { "bloomMode": "enabled" }, + }, + { + label: "Bloom Threshold", + type: "slider", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "bloom.bloomThreshold", + showPropertyRule: { "bloomMode": "enabled" }, + }, + { + label: "Bloom Size", + type: "slider", + min: 0, + max: 2, + step: 0.01, + decimals: 2, + propertyID: "bloom.bloomSize", + showPropertyRule: { "bloomMode": "enabled" }, + }, + ] + }, + { + id: "model", + addToGroup: "base", + properties: [ + { + label: "Model", + type: "string", + propertyID: "modelURL", + }, + { + label: "Collision Shape", + type: "dropdown", + options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , + "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , + "static-mesh": "Exact - All polygons (non-dynamic only)" }, + propertyID: "shapeType", + }, + { + label: "Compound Shape", + type: "string", + propertyID: "compoundShapeURL", + }, + { + label: "Animation", + type: "string", + propertyID: "animation.url", + }, + { + label: "Play Automatically", + type: "bool", + propertyID: "animation.running", + }, + { + label: "Loop", + type: "bool", + propertyID: "animation.loop", + }, + { + label: "Allow Transition", + type: "bool", + propertyID: "animation.allowTranslation", + }, + { + label: "Hold", + type: "bool", + propertyID: "animation.hold", + }, + { + label: "Animation Frame", + type: "number", + propertyID: "animation.currentFrame", + }, + { + label: "First Frame", + type: "number", + propertyID: "animation.firstFrame", + }, + { + label: "Last Frame", + type: "number", + propertyID: "animation.lastFrame", + }, + { + label: "Animation FPS", + type: "number", + propertyID: "animation.fps", + }, + { + label: "Texture", + type: "textarea", + propertyID: "textures", + }, + { + label: "Original Texture", + type: "textarea", + propertyID: "originalTextures", + readOnly: true, + }, + ] + }, + { + id: "image", + addToGroup: "base", + properties: [ + { + label: "Image", + type: "string", + propertyID: "image", + }, + ] + }, + { + id: "web", + addToGroup: "base", + properties: [ + { + label: "Source", + type: "string", + propertyID: "sourceUrl", + }, + { + label: "Source Resolution", + type: "number", + propertyID: "dpi", + }, + ] + }, + { + id: "light", + addToGroup: "base", + properties: [ + { + label: "Light Color", + type: "color", + propertyID: "lightColor", + propertyName: "color", // actual entity property name + }, + { + label: "Intensity", + type: "number", + min: 0, + step: 0.1, + decimals: 1, + propertyID: "intensity", + }, + { + label: "Fall-Off Radius", + type: "number", + min: 0, + step: 0.1, + decimals: 1, + unit: "m", + propertyID: "falloffRadius", + }, + { + label: "Spotlight", + type: "bool", + propertyID: "isSpotlight", + }, + { + label: "Spotlight Exponent", + type: "number", + step: 0.01, + decimals: 2, + propertyID: "exponent", + }, + { + label: "Spotlight Cut-Off", + type: "number", + step: 0.01, + decimals: 2, + propertyID: "cutoff", + }, + ] + }, + { + id: "material", + addToGroup: "base", + properties: [ + { + label: "Material URL", + type: "string", + propertyID: "materialURL", + }, + { + label: "Material Data", + type: "textarea", + buttons: [ { id: "clear", label: "Clear Material Data", className: "red", onClick: clearMaterialData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, + { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], + propertyID: "materialData", + }, + { + label: "Submesh to Replace", + type: "number", + min: 0, + step: 1, + propertyID: "submeshToReplace", + }, + { + label: "Material Name to Replace", + type: "string", + propertyID: "materialNameToReplace", + }, + { + label: "Select Submesh", + type: "bool", + propertyID: "selectSubmesh", + }, + { + label: "Priority", + type: "number", + min: 0, + propertyID: "priority", + }, + { + label: "Material Position", + type: "vec2", + vec2Type: "xy", + min: 0, + max: 1, + step: 0.1, + decimals: 4, + subLabels: [ "x", "y" ], + propertyID: "materialMappingPos", + }, + { + label: "Material Scale", + type: "vec2", + vec2Type: "wh", + min: 0, + step: 0.1, + decimals: 4, + subLabels: [ "width", "height" ], + propertyID: "materialMappingScale", + }, + { + label: "Material Rotation", + type: "number", + step: 0.1, + decimals: 2, + unit: "deg", + propertyID: "materialMappingRot", + }, + ] + }, + { + id: "particles", + addToGroup: "base", + properties: [ + { + label: "Emit", + type: "bool", + propertyID: "isEmitting", + }, + { + label: "Lifespan", + type: "slider", + unit: "s", + min: 0.01, + max: 10, + step: 0.01, + decimals: 2, + propertyID: "lifespan", + }, + { + label: "Max Particles", + type: "slider", + min: 1, + max: 10000, + step: 1, + propertyID: "maxParticles", + }, + { + label: "Texture", + type: "texture", + propertyID: "particleTextures", + propertyName: "textures", // actual entity property name + }, + ] + }, + { + id: "particles_emit", + label: "EMIT", + properties: [ + { + label: "Emit Rate", + type: "slider", + min: 1, + max: 1000, + step: 1, + propertyID: "emitRate", + }, + { + label: "Emit Speed", + type: "slider", + min: 0, + max: 5, + step: 0.01, + decimals: 2, + propertyID: "emitSpeed", + }, + { + label: "Speed Spread", + type: "slider", + min: 0, + max: 5, + step: 0.01, + decimals: 2, + propertyID: "speedSpread", + }, + { + label: "Emit Dimension", + type: "vec3", + vec3Type: "xyz", + min: 0, + step: 0.01, + round: 100, + subLabels: [ "x", "y", "z" ], + propertyID: "emitDimensions", + }, + { + label: "Emit Radius Start", + type: "slider", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "emitRadiusStart" + }, + { + label: "Emit Orientation", + type: "vec3", + vec3Type: "pyr", + step: 0.01, + round: 100, + subLabels: [ "pitch", "yaw", "roll" ], + unit: "deg", + propertyID: "emitOrientation", + }, + { + label: "Trails", + type: "bool", + propertyID: "emitterShouldTrail", + }, + ] + }, + { + id: "particles_size", + label: "SIZE", + properties: [ + { + label: "Size", + type: "slider", + min: 0, + max: 4, + step: 0.01, + decimals: 2, + propertyID: "particleRadius", + }, + { + label: "Size Start", + type: "slider", + min: 0, + max: 4, + step: 0.01, + decimals: 2, + propertyID: "radiusStart", + fallbackProperty: "particleRadius", + }, + { + label: "Size Finish", + type: "slider", + min: 0, + max: 4, + step: 0.01, + decimals: 2, + propertyID: "radiusFinish", + fallbackProperty: "particleRadius", + }, + { + label: "Size Spread", + type: "slider", + min: 0, + max: 4, + step: 0.01, + decimals: 2, + propertyID: "radiusSpread", + }, + ] + }, + { + id: "particles_color", + label: "COLOR", + properties: [ + { + label: "Color", + type: "color", + propertyID: "particleColor", + propertyName: "color", // actual entity property name + }, + { + label: "Color Start", + type: "color", + propertyID: "colorStart", + fallbackProperty: "color", + }, + { + label: "Color Finish", + type: "color", + propertyID: "colorFinish", + fallbackProperty: "color", + }, + { + label: "Color Spread", + type: "color", + propertyID: "colorSpread", + }, + ] + }, + { + id: "particles_alpha", + label: "ALPHA", + properties: [ + { + label: "Alpha", + type: "slider", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "alpha", + }, + { + label: "Alpha Start", + type: "slider", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "alphaStart", + fallbackProperty: "alpha", + }, + { + label: "Alpha Finish", + type: "slider", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "alphaFinish", + fallbackProperty: "alpha", + }, + { + label: "Alpha Spread", + type: "slider", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "alphaSpread", + }, + ] + }, + { + id: "particles_acceleration", + label: "ACCELERATION", + properties: [ + { + label: "Emit Acceleration", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + round: 100, + subLabels: [ "x", "y", "z" ], + propertyID: "emitAcceleration", + }, + { + label: "Acceleration Spread", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + round: 100, + subLabels: [ "x", "y", "z" ], + propertyID: "accelerationSpread", + }, + ] + }, + { + id: "particles_spin", + label: "SPIN", + properties: [ + { + label: "Spin", + type: "slider", + min: -360, + max: 360, + step: 1, + decimals: 0, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "particleSpin", + }, + { + label: "Spin Start", + type: "slider", + min: -360, + max: 360, + step: 1, + decimals: 0, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinStart", + fallbackProperty: "particleSpin", + }, + { + label: "Spin Finish", + type: "slider", + min: -360, + max: 360, + step: 1, + decimals: 0, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinFinish", + fallbackProperty: "particleSpin", + }, + { + label: "Spin Spread", + type: "slider", + min: 0, + max: 360, + step: 1, + decimals: 0, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinSpread", + }, + { + label: "Rotate with Entity", + type: "bool", + propertyID: "rotateWithEntity", + }, + ] + }, + { + id: "particles_constraints", + label: "CONSTRAINTS", + properties: [ + { + label: "Horizontal Angle Start", + type: "slider", + min: 0, + max: 180, + step: 1, + decimals: 0, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "polarStart", + }, + { + label: "Horizontal Angle Finish", + type: "slider", + min: 0, + max: 180, + step: 1, + decimals: 0, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "polarFinish", + }, + { + label: "Vertical Angle Start", + type: "slider", + min: -180, + max: 0, + step: 1, + decimals: 0, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "azimuthStart", + }, + { + label: "Vertical Angle Finish", + type: "slider", + min: 0, + max: 180, + step: 1, + decimals: 0, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "azimuthFinish", + }, + ] + }, + { + id: "spatial", + label: "SPATIAL", + properties: [ + { + label: "Position", + type: "vec3", + vec3Type: "xyz", + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "m", + propertyID: "position", + }, + { + label: "Rotation", + type: "vec3", + vec3Type: "pyr", + step: 0.1, + decimals: 4, + subLabels: [ "pitch", "yaw", "roll" ], + unit: "deg", + propertyID: "rotation", + }, + { + label: "Dimension", + type: "vec3", + vec3Type: "xyz", + min: 0, + step: 0.1, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "m", + propertyID: "dimensions", + }, + { + label: "Scale", + type: "number", + defaultValue: 100, + unit: "%", + buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, + { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], + propertyID: "scale", + }, + { + label: "Pivot", + type: "vec3", + vec3Type: "xyz", + step: 0.1, + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "(ratio of dimension)", + propertyID: "registrationPoint", + }, + { + label: "Align", + type: "buttons", + buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, + { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], + propertyID: "alignToGrid", + }, + ] + }, + { + id: "behavior", + label: "BEHAVIOR", + properties: [ + { + label: "Grabbable", + type: "bool", + propertyID: "grab.grabbable", + }, + { + label: "Cloneable", + type: "bool", + propertyID: "cloneable", + }, + { + label: "Clone Lifetime", + type: "number", + unit: "s", + propertyID: "cloneLifetime", + showPropertyRule: { "cloneable": "true" }, + }, + { + label: "Clone Limit", + type: "number", + propertyID: "cloneLimit", + showPropertyRule: { "cloneable": "true" }, + }, + { + label: "Clone Dynamic", + type: "bool", + propertyID: "cloneDynamic", + showPropertyRule: { "cloneable": "true" }, + }, + { + label: "Clone Avatar Entity", + type: "bool", + propertyID: "cloneAvatarEntity", + showPropertyRule: { "cloneable": "true" }, + }, + { + label: "Triggerable", + type: "bool", + propertyID: "grab.triggerable", + }, + { + label: "Follow Controller", + type: "bool", + propertyID: "grab.grabFollowsController", + }, + { + label: "Cast shadows", + type: "bool", + propertyID: "canCastShadow", + }, + { + label: "Link", + type: "string", + propertyID: "href", + }, + { + label: "Script", + type: "string", + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], + propertyID: "script", + }, + { + label: "Server Script", + type: "string", + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], + propertyID: "serverScripts", + }, + { + label: "Lifetime", + type: "number", + unit: "s", + propertyID: "lifetime", + }, + { + label: "User Data", + type: "textarea", + buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, + { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], + propertyID: "userData", + }, + ] + }, + { + id: "collision", + label: "COLLISION", + properties: [ + { + label: "Collides", + type: "bool", + inverse: true, + propertyID: "collisionless", + }, + { + label: "Collides With", + type: "sub-header", + propertyID: "collidesWithHeader", // not actually a property but used for naming/storing this element + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "Static Entities", + type: "bool", + propertyID: "collidesWithStatic", + propertyName: "static", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "Kinematic Entities", + type: "bool", + propertyID: "collidesWithKinematic", + propertyName: "kinematic", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "Dynamic Entities", + type: "bool", + propertyID: "collidesWithDynamic", + propertyName: "dynamic", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "My Avatar", + type: "bool", + propertyID: "collidesWithMyAvatar", + propertyName: "myAvatar", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "Other Avatars", + type: "bool", + propertyID: "collidesWithOtherAvatar", + propertyName: "otherAvatar", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "Collision sound URL", + type: "string", + propertyID: "collisionSoundURL", + showPropertyRule: { "collisionless": "false" }, + }, + { + label: "Dynamic", + type: "bool", + propertyID: "dynamic", + }, + ] + }, + { + id: "physics", + label: "PHYSICS", + properties: [ + { + label: "Linear Velocity", + type: "vec3", + vec3Type: "xyz", + decimals: 4, + subLabels: [ "x", "y", "z" ], + unit: "m/s", + propertyID: "velocity", + }, + { + label: "Linear Damping", + type: "number", + decimals: 2, + propertyID: "damping", + }, + { + label: "Angular Velocity", + type: "vec3", + vec3Type: "pyr", + multiplier: DEGREES_TO_RADIANS, + decimals: 4, + subLabels: [ "pitch", "yaw", "roll" ], + unit: "deg/s", + propertyID: "angularVelocity", + }, + { + label: "Angular Damping", + type: "number", + decimals: 4, + propertyID: "angularDamping", + }, + { + label: "Bounciness", + type: "number", + decimals: 4, + propertyID: "restitution", + }, + { + label: "Friction", + type: "number", + decimals: 4, + propertyID: "friction", + }, + { + label: "Density", + type: "number", + decimals: 4, + propertyID: "density", + }, + { + label: "Gravity", + type: "vec3", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m/s<sup>2</sup>", + propertyID: "gravity", + }, + { + label: "Acceleration", + type: "vec3", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + decimals: 4, + unit: "m/s<sup>2</sup>", + propertyID: "acceleration", + }, + ] + }, +]; + +const GROUPS_PER_TYPE = { + None: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], + Shape: [ 'base', 'shape', 'spatial', 'behavior', 'collision', 'physics' ], + Text: [ 'base', 'text', 'spatial', 'behavior', 'collision', 'physics' ], + Zone: [ 'base', 'zone', 'spatial', 'behavior', 'collision', 'physics' ], + Model: [ 'base', 'model', 'spatial', 'behavior', 'collision', 'physics' ], + Image: [ 'base', 'image', 'spatial', 'behavior', 'collision', 'physics' ], + Web: [ 'base', 'web', 'spatial', 'behavior', 'collision', 'physics' ], + Light: [ 'base', 'light', 'spatial', 'behavior', 'collision', 'physics' ], + Material: [ 'base', 'material', 'spatial', 'behavior' ], + ParticleEffect: [ 'base', 'particles', 'particles_emit', 'particles_size', 'particles_color', 'particles_alpha', + 'particles_acceleration', 'particles_spin', 'particles_constraints', 'spatial', 'behavior', 'physics' ], + Multiple: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], }; -var EDITOR_TIMEOUT_DURATION = 1500; -var KEY_P = 80; // Key code for letter p used for Parenting hotkey. +const EDITOR_TIMEOUT_DURATION = 1500; +const DEBOUNCE_TIMEOUT = 125; + +const COLOR_MIN = 0; +const COLOR_MAX = 255; +const COLOR_STEP = 1; + +const KEY_P = 80; // Key code for letter p used for Parenting hotkey. + +const MATERIAL_PREFIX_STRING = "mat::"; + +const PENDING_SCRIPT_STATUS = "[ Fetching status ]"; +const NOT_RUNNING_SCRIPT_STATUS = "Not running"; +const ENTITY_SCRIPT_STATUS = { + pending: "Pending", + loading: "Loading", + error_loading_script: "Error loading script", // eslint-disable-line camelcase + error_running_script: "Error running script", // eslint-disable-line camelcase + running: "Running", + unloaded: "Unloaded" +}; + +const PROPERTY_NAME_DIVISION = { + GROUP: 0, + PROPERTY: 1, + SUBPROPERTY: 2, +}; + +const VECTOR_ELEMENTS = { + X_INPUT: 0, + Y_INPUT: 1, + Z_INPUT: 2, +}; + +const COLOR_ELEMENTS = { + COLOR_PICKER: 0, + RED_INPUT: 1, + GREEN_INPUT: 2, + BLUE_INPUT: 3, +}; + +const SLIDER_ELEMENTS = { + SLIDER: 0, + NUMBER_INPUT: 1, +}; + +const ICON_ELEMENTS = { + ICON: 0, + LABEL: 1, +}; + +const TEXTURE_ELEMENTS = { + IMAGE: 0, + TEXT_INPUT: 1, +}; + +const JSON_EDITOR_ROW_DIV_INDEX = 2; + +var elGroups = {}; +var properties = {}; var colorPickers = {}; +var particlePropertyUpdates = {}; +var selectedEntityProperties; var lastEntityID = null; - -var MATERIAL_PREFIX_STRING = "mat::"; - -var PENDING_SCRIPT_STATUS = "[ Fetching status ]"; +var createAppTooltip = new CreateAppTooltip(); function debugPrint(message) { EventBridge.emitWebEvent( @@ -47,16 +1327,44 @@ function debugPrint(message) { ); } + +/** + * GENERAL PROPERTY/GROUP FUNCTIONS + */ + +function getPropertyInputElement(propertyID) { + let property = properties[propertyID]; + switch (property.data.type) { + case 'string': + case 'bool': + case 'number': + case 'slider': + case 'dropdown': + case 'textarea': + case 'texture': + return property.elInput; + case 'vec3': + case 'vec2': + return { x: property.elInputX, y: property.elInputY, z: property.elInputZ }; + case 'color': + return { red: property.elInputR, green: property.elInputG, blue: property.elInputB }; + case 'icon': + return property.elLabel; + default: + return undefined; + } +} + function enableChildren(el, selector) { - var elSelectors = el.querySelectorAll(selector); - for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { + let elSelectors = el.querySelectorAll(selector); + for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { elSelectors[selectorIndex].removeAttribute('disabled'); } } function disableChildren(el, selector) { - var elSelectors = el.querySelectorAll(selector); - for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { + let elSelectors = el.querySelectorAll(selector); + for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { elSelectors[selectorIndex].setAttribute('disabled', 'disabled'); } } @@ -64,7 +1372,7 @@ function disableChildren(el, selector) { function enableProperties() { enableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker"); enableChildren(document, ".colpick"); - var elLocked = document.getElementById("property-locked"); + let elLocked = getPropertyInputElement("locked"); if (elLocked.checked === false) { removeStaticUserData(); @@ -72,205 +1380,261 @@ function enableProperties() { } } - function disableProperties() { disableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker"); disableChildren(document, ".colpick"); - for (var pickKey in colorPickers) { + for (let pickKey in colorPickers) { colorPickers[pickKey].colpickHide(); } - var elLocked = document.getElementById("property-locked"); + let elLocked = getPropertyInputElement("locked"); if (elLocked.checked === true) { - if ($('#userdata-editor').css('display') === "block") { + if ($('#property-userData-editor').css('display') === "block") { showStaticUserData(); } - if ($('#materialdata-editor').css('display') === "block") { + if ($('#property-materialData-editor').css('display') === "block") { showStaticMaterialData(); } } } -function showElements(els, show) { - for (var i = 0; i < els.length; i++) { - els[i].style.display = (show) ? 'table' : 'none'; +function showPropertyElement(propertyID, show) { + let elProperty = properties[propertyID].elProperty; + elProperty.style.display = show ? "table" : "none"; +} + +function resetProperties() { + for (let propertyID in properties) { + let property = properties[propertyID]; + let propertyData = property.data; + + switch (propertyData.type) { + case 'string': { + property.elInput.value = ""; + break; + } + case 'bool': { + property.elInput.checked = false; + break; + } + case 'number': + case 'slider': { + if (propertyData.defaultValue !== undefined) { + property.elInput.value = propertyData.defaultValue; + } else { + property.elInput.value = ""; + } + if (property.elSlider !== undefined) { + property.elSlider.value = property.elInput.value; + } + break; + } + case 'vec3': + case 'vec2': { + property.elInputX.value = ""; + property.elInputY.value = ""; + if (property.elInputZ !== undefined) { + property.elInputZ.value = ""; + } + break; + } + case 'color': { + property.elColorPicker.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + property.elInputR.value = ""; + property.elInputG.value = ""; + property.elInputB.value = ""; + break; + } + case 'dropdown': { + property.elInput.value = ""; + setDropdownText(property.elInput); + break; + } + case 'textarea': { + property.elInput.value = ""; + setTextareaScrolling(property.elInput); + break; + } + case 'icon': { + property.elSpan.style.display = "none"; + property.elLabel.innerHTML = propertyData.label; + break; + } + case 'texture': { + property.elInput.value = ""; + property.elInput.imageLoad(property.elInput.value); + break; + } + } + + let showPropertyRules = properties[propertyID].showPropertyRules; + if (showPropertyRules !== undefined) { + for (let propertyToHide in showPropertyRules) { + showPropertyElement(propertyToHide, false); + } + } + } + + let elServerScriptError = document.getElementById("property-serverScripts-error"); + let elServerScriptStatus = document.getElementById("property-serverScripts-status"); + elServerScriptError.parentElement.style.display = "none"; + elServerScriptStatus.innerText = NOT_RUNNING_SCRIPT_STATUS; +} + +function showGroupsForType(type) { + if (type === "Box" || type === "Sphere") { + type = "Shape"; + } + + let typeGroups = GROUPS_PER_TYPE[type]; + + for (let groupKey in elGroups) { + let elGroup = elGroups[groupKey]; + if (typeGroups && typeGroups.indexOf(groupKey) > -1) { + elGroup.style.display = "block"; + } else { + elGroup.style.display = "none"; + } } } -function updateProperty(propertyName, propertyValue) { - var properties = {}; - properties[propertyName] = propertyValue; - updateProperties(properties); +function getPropertyValue(originalPropertyName) { + // if this is a compound property name (i.e. animation.running) + // then split it by . up to 3 times to find property value + let propertyValue; + let splitPropertyName = originalPropertyName.split('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[PROPERTY_NAME_DIVISION.GROUP]; + let propertyName = splitPropertyName[PROPERTY_NAME_DIVISION.PROPERTY]; + let groupProperties = selectedEntityProperties[propertyGroupName]; + if (groupProperties === undefined || groupProperties[propertyName] === undefined) { + return undefined; + } + if (splitPropertyName.length === PROPERTY_NAME_DIVISION.SUBPROPERTY + 1) { + let subPropertyName = splitPropertyName[PROPERTY_NAME_DIVISION.SUBPROPERTY]; + propertyValue = groupProperties[propertyName][subPropertyName]; + } else { + propertyValue = groupProperties[propertyName]; + } + } else { + propertyValue = selectedEntityProperties[originalPropertyName]; + } + return propertyValue; } -function updateProperties(properties) { + +/** + * PROPERTY UPDATE FUNCTIONS + */ + +function updateProperty(originalPropertyName, propertyValue, isParticleProperty) { + 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('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[PROPERTY_NAME_DIVISION.GROUP]; + let propertyName = splitPropertyName[PROPERTY_NAME_DIVISION.PROPERTY]; + propertyUpdate[propertyGroupName] = {}; + if (splitPropertyName.length === PROPERTY_NAME_DIVISION.SUBPROPERTY + 1) { + let subPropertyName = splitPropertyName[PROPERTY_NAME_DIVISION.SUBPROPERTY]; + propertyUpdate[propertyGroupName][propertyName] = {}; + propertyUpdate[propertyGroupName][propertyName][subPropertyName] = propertyValue; + } else { + propertyUpdate[propertyGroupName][propertyName] = propertyValue; + } + } else { + propertyUpdate[originalPropertyName] = propertyValue; + } + // queue up particle property changes with the debounced sync to avoid + // causing particle emitting to reset excessively with each value change + if (isParticleProperty) { + Object.keys(propertyUpdate).forEach(function (propertyUpdateKey) { + particlePropertyUpdates[propertyUpdateKey] = propertyUpdate[propertyUpdateKey]; + }); + particleSyncDebounce(); + } else { + updateProperties(propertyUpdate); + } +} + +var particleSyncDebounce = _.debounce(function () { + updateProperties(particlePropertyUpdates); + particlePropertyUpdates = {}; +}, DEBOUNCE_TIMEOUT); + +function updateProperties(propertiesToUpdate) { EventBridge.emitWebEvent(JSON.stringify({ id: lastEntityID, type: "update", - properties: properties + properties: propertiesToUpdate })); } -function createEmitCheckedPropertyUpdateFunction(propertyName) { +function createEmitTextPropertyUpdateFunction(propertyName, isParticleProperty) { return function() { - updateProperty(propertyName, this.checked); + updateProperty(propertyName, this.value, isParticleProperty); }; } -function createEmitGroupCheckedPropertyUpdateFunction(group, propertyName) { +function createEmitCheckedPropertyUpdateFunction(propertyName, inverse, isParticleProperty) { return function() { - var properties = {}; - properties[group] = {}; - properties[group][propertyName] = this.checked; - updateProperties(properties); + updateProperty(propertyName, inverse ? !this.checked : this.checked, isParticleProperty); }; } -function createEmitNumberPropertyUpdateFunction(propertyName, decimals) { - decimals = ((decimals === undefined) ? 4 : decimals); +function createEmitNumberPropertyUpdateFunction(propertyName, multiplier, isParticleProperty) { return function() { - var value = parseFloat(this.value).toFixed(decimals); - updateProperty(propertyName, value); - }; -} - -function createEmitGroupNumberPropertyUpdateFunction(group, propertyName) { - return function() { - var properties = {}; - properties[group] = {}; - properties[group][propertyName] = this.value; - updateProperties(properties); - }; -} - -function createImageURLUpdateFunction(propertyName) { - return function () { - var newTextures = JSON.stringify({ "tex.picture": this.value }); - updateProperty(propertyName, newTextures); - }; -} - -function createEmitTextPropertyUpdateFunction(propertyName) { - return function() { - updateProperty(propertyName, this.value); - }; -} - -function createZoneComponentModeChangedFunction(zoneComponent, zoneComponentModeInherit, - zoneComponentModeDisabled, zoneComponentModeEnabled) { - - return function() { - var zoneComponentMode; - - if (zoneComponentModeInherit.checked) { - zoneComponentMode = 'inherit'; - } else if (zoneComponentModeDisabled.checked) { - zoneComponentMode = 'disabled'; - } else if (zoneComponentModeEnabled.checked) { - zoneComponentMode = 'enabled'; + if (multiplier === undefined) { + multiplier = 1; } - - updateProperty(zoneComponent, zoneComponentMode); + let value = parseFloat(this.value) * multiplier; + updateProperty(propertyName, value, isParticleProperty); }; } -function createEmitGroupTextPropertyUpdateFunction(group, propertyName) { - return function() { - var properties = {}; - properties[group] = {}; - properties[group][propertyName] = this.value; - updateProperties(properties); - }; -} - -function createEmitVec2PropertyUpdateFunction(property, elX, elY) { +function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY, multiplier, isParticleProperty) { return function () { - var properties = {}; - properties[property] = { - x: elX.value, - y: elY.value + if (multiplier === undefined) { + multiplier = 1; + } + let newValue = { + x: elX.value * multiplier, + y: elY.value * multiplier }; - updateProperties(properties); + updateProperty(propertyName, newValue, isParticleProperty); }; } -function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) { +function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ, multiplier, isParticleProperty) { return function() { - var properties = {}; - properties[property] = { - x: elX.value, - y: elY.value, - z: elZ.value - }; - updateProperties(properties); - }; -} - -function createEmitGroupVec3PropertyUpdateFunction(group, property, elX, elY, elZ) { - return function() { - var properties = {}; - properties[group] = {}; - properties[group][property] = { - x: elX.value, - y: elY.value, - z: elZ ? elZ.value : 0 - }; - updateProperties(properties); - }; -} - -function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, elZ, multiplier) { - return function() { - var properties = {}; - properties[property] = { + if (multiplier === undefined) { + multiplier = 1; + } + let newValue = { x: elX.value * multiplier, y: elY.value * multiplier, z: elZ.value * multiplier }; - updateProperties(properties); + updateProperty(propertyName, newValue, isParticleProperty); }; } -function createEmitColorPropertyUpdateFunction(property, elRed, elGreen, elBlue) { +function createEmitColorPropertyUpdateFunction(propertyName, elRed, elGreen, elBlue, isParticleProperty) { return function() { - emitColorPropertyUpdate(property, elRed.value, elGreen.value, elBlue.value); + emitColorPropertyUpdate(propertyName, elRed.value, elGreen.value, elBlue.value, isParticleProperty); }; } -function emitColorPropertyUpdate(property, red, green, blue, group) { - var properties = {}; - if (group) { - properties[group] = {}; - properties[group][property] = { - red: red, - green: green, - blue: blue - }; - } else { - properties[property] = { - red: red, - green: green, - blue: blue - }; - } - updateProperties(properties); -} - - -function createEmitGroupColorPropertyUpdateFunction(group, property, elRed, elGreen, elBlue) { - return function() { - var properties = {}; - properties[group] = {}; - properties[group][property] = { - red: elRed.value, - green: elGreen.value, - blue: elBlue.value - }; - updateProperties(properties); +function emitColorPropertyUpdate(propertyName, red, green, blue, isParticleProperty) { + let newValue = { + red: red, + green: green, + blue: blue }; + updateProperty(propertyName, newValue, isParticleProperty); } -function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString) { +function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString, isParticleProperty) { if (subPropertyElement.checked) { if (propertyValue.indexOf(subPropertyString)) { propertyValue += subPropertyString + ','; @@ -279,11 +1643,597 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen // We've unchecked, so remove propertyValue = propertyValue.replace(subPropertyString + ",", ""); } - updateProperty(propertyName, propertyValue); + updateProperty(propertyName, propertyValue, isParticleProperty); +} + +function createImageURLUpdateFunction(propertyName, isParticleProperty) { + return function () { + let newTextures = JSON.stringify({ "tex.picture": this.value }); + updateProperty(propertyName, newTextures, isParticleProperty); + }; +} + + +/** + * PROPERTY ELEMENT CREATION FUNCTIONS + */ + +function createStringProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "property text"; + + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("type", "text"); + if (propertyData.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, elementID, propertyData.buttons, false); + } + + return elInput; +} + +function createBoolProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "property checkbox"; + + if (propertyData.glyph !== undefined) { + elLabel.innerText = " " + propertyData.label; + let elSpan = document.createElement('span'); + elSpan.innerHTML = propertyData.glyph; + elLabel.insertBefore(elSpan, elLabel.firstChild); + } + + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("type", "checkbox"); + + elProperty.appendChild(elInput); + elProperty.appendChild(elLabel); + + let subPropertyOf = propertyData.subPropertyOf; + if (subPropertyOf !== undefined) { + elInput.addEventListener('change', function() { + updateCheckedSubProperty(subPropertyOf, selectedEntityProperties[subPropertyOf], elInput, propertyName, property.isParticleProperty); + }); + } else { + elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, propertyData.inverse, property.isParticleProperty)); + } + + return elInput; +} + +function createNumberProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "property number"; + + addUnit(propertyData.unit, elLabel); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("type", "number"); + if (propertyData.min !== undefined) { + elInput.setAttribute("min", propertyData.min); + } + if (propertyData.max !== undefined) { + elInput.setAttribute("max", propertyData.max); + } + if (propertyData.step !== undefined) { + elInput.setAttribute("step", propertyData.step); + } + + let defaultValue = propertyData.defaultValue; + if (defaultValue !== undefined) { + elInput.value = defaultValue; + } + + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, propertyData.multiplier, propertyData.decimals, property.isParticleProperty)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, elementID, propertyData.buttons, true); + } + + return elInput; +} + +function createSliderProperty(property, elProperty, elLabel) { + let propertyData = property.data; + + elProperty.className = "property range"; + + let elDiv = document.createElement("div"); + elDiv.className = "slider-wrapper"; + + let elSlider = document.createElement("input"); + elSlider.setAttribute("type", "range"); + + let elInput = document.createElement("input"); + elInput.setAttribute("type", "number"); + + if (propertyData.min !== undefined) { + elInput.setAttribute("min", propertyData.min); + elSlider.setAttribute("min", propertyData.min); + } + if (propertyData.max !== undefined) { + elInput.setAttribute("max", propertyData.max); + elSlider.setAttribute("max", propertyData.max); + elSlider.setAttribute("data-max", propertyData.max); + } + if (propertyData.step !== undefined) { + elInput.setAttribute("step", propertyData.step); + elSlider.setAttribute("step", propertyData.step); + } + + elInput.onchange = function (event) { + let inputValue = event.target.value; + elSlider.value = inputValue; + if (propertyData.multiplier !== undefined) { + inputValue *= propertyData.multiplier; + } + updateProperty(property.name, inputValue, property.isParticleProperty); + }; + elSlider.oninput = function (event) { + let sliderValue = event.target.value; + if (propertyData.step === 1) { + if (sliderValue > 0) { + elInput.value = Math.floor(sliderValue); + } else { + elInput.value = Math.ceil(sliderValue); + } + } else { + elInput.value = sliderValue; + } + if (propertyData.multiplier !== undefined) { + sliderValue *= propertyData.multiplier; + } + updateProperty(property.name, sliderValue, property.isParticleProperty); + }; + + elDiv.appendChild(elLabel); + elDiv.appendChild(elSlider); + elDiv.appendChild(elInput); + elProperty.appendChild(elDiv); + + let elResult = []; + elResult[SLIDER_ELEMENTS.SLIDER] = elSlider; + elResult[SLIDER_ELEMENTS.NUMBER_INPUT] = elInput; + return elResult; +} + +function createVec3Property(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "property " + propertyData.vec3Type + " fstuple"; + + let elTuple = document.createElement('div'); + elTuple.className = "tuple"; + + addUnit(propertyData.unit, elLabel); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputX = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.X_INPUT], + propertyData.min, propertyData.max, propertyData.step); + let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_INPUT], + propertyData.min, propertyData.max, propertyData.step); + let elInputZ = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Z_INPUT], + propertyData.min, propertyData.max, propertyData.step); + + let inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ, + propertyData.multiplier, property.isParticleProperty); + elInputX.addEventListener('change', inputChangeFunction); + elInputY.addEventListener('change', inputChangeFunction); + elInputZ.addEventListener('change', inputChangeFunction); + + let elResult = []; + elResult[VECTOR_ELEMENTS.X_INPUT] = elInputX; + elResult[VECTOR_ELEMENTS.Y_INPUT] = elInputY; + elResult[VECTOR_ELEMENTS.Z_INPUT] = elInputZ; + return elResult; +} + +function createVec2Property(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "property " + propertyData.vec2Type + " fstuple"; + + let elTuple = document.createElement('div'); + elTuple.className = "tuple"; + + addUnit(propertyData.unit, elLabel); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputX = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.X_INPUT], + propertyData.min, propertyData.max, propertyData.step); + let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_INPUT], + propertyData.min, propertyData.max, propertyData.step); + + let inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, elInputY, + propertyData.multiplier, property.isParticleProperty); + elInputX.addEventListener('change', inputChangeFunction); + elInputY.addEventListener('change', inputChangeFunction); + + let elResult = []; + elResult[VECTOR_ELEMENTS.X_INPUT] = elInputX; + elResult[VECTOR_ELEMENTS.Y_INPUT] = elInputY; + return elResult; +} + +function createColorProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + + elProperty.className = "property rgb fstuple"; + + let elColorPicker = document.createElement('div'); + elColorPicker.className = "color-picker"; + elColorPicker.setAttribute("id", elementID); + + let elTuple = document.createElement('div'); + elTuple.className = "tuple"; + + elProperty.appendChild(elColorPicker); + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputR = createTupleNumberInput(elTuple, elementID, "red", COLOR_MIN, COLOR_MAX, COLOR_STEP); + let elInputG = createTupleNumberInput(elTuple, elementID, "green", COLOR_MIN, COLOR_MAX, COLOR_STEP); + let elInputB = createTupleNumberInput(elTuple, elementID, "blue", COLOR_MIN, COLOR_MAX, COLOR_STEP); + + let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB, + property.isParticleProperty); + elInputR.addEventListener('change', inputChangeFunction); + elInputG.addEventListener('change', inputChangeFunction); + elInputB.addEventListener('change', inputChangeFunction); + + let colorPickerID = "#" + elementID; + colorPickers[colorPickerID] = $(colorPickerID).colpick({ + colorScheme: 'dark', + layout: 'hex', + color: '000000', + submit: false, // We don't want to have a submission button + onShow: function(colpick) { + $(colorPickerID).attr('active', 'true'); + // The original color preview within the picker needs to be updated on show because + // prior to the picker being shown we don't have access to the selections' starting color. + colorPickers[colorPickerID].colpickSetColor({ + "r": elInputR.value, + "g": elInputG.value, + "b": elInputB.value + }); + }, + onHide: function(colpick) { + $(colorPickerID).attr('active', 'false'); + }, + onChange: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + emitColorPropertyUpdate(propertyName, rgb.r, rgb.g, rgb.b); + } + }); + + let elResult = []; + elResult[COLOR_ELEMENTS.COLOR_PICKER] = elColorPicker; + elResult[COLOR_ELEMENTS.RED_INPUT] = elInputR; + elResult[COLOR_ELEMENTS.GREEN_INPUT] = elInputG; + elResult[COLOR_ELEMENTS.BLUE_INPUT] = elInputB; + return elResult; +} + +function createDropdownProperty(property, propertyID, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "property dropdown"; + + let elInput = document.createElement('select'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("propertyID", propertyID); + + for (let optionKey in propertyData.options) { + let option = document.createElement('option'); + option.value = optionKey; + option.text = propertyData.options[optionKey]; + elInput.add(option); + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + return elInput; +} + +function createTextareaProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "property textarea"; + + elProperty.appendChild(elLabel); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, elementID, propertyData.buttons, true); + } + + let elInput = document.createElement('textarea'); + elInput.setAttribute("id", elementID); + if (propertyData.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty)); + + elProperty.appendChild(elInput); + + return elInput; +} + +function createIconProperty(property, elProperty, elLabel) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "property value"; + + elLabel.setAttribute("id", elementID); + elLabel.innerHTML = " " + propertyData.label; + + let elSpan = document.createElement('span'); + elSpan.setAttribute("id", elementID + "-icon"); + + elProperty.appendChild(elSpan); + elProperty.appendChild(elLabel); + + let elResult = []; + elResult[ICON_ELEMENTS.ICON] = elSpan; + elResult[ICON_ELEMENTS.LABEL] = elLabel; + return elResult; +} + +function createTextureProperty(property, elProperty, elLabel) { + let elementID = property.elementID; + + elProperty.className = "property texture"; + + let elDiv = document.createElement("div"); + let elImage = document.createElement("img"); + elDiv.className = "texture-image no-texture"; + elDiv.appendChild(elImage); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("type", "text"); + + let imageLoad = _.debounce(function (url) { + if (url.slice(0, 5).toLowerCase() === "atp:/") { + elImage.src = ""; + elImage.style.display = "none"; + elDiv.classList.remove("with-texture"); + elDiv.classList.remove("no-texture"); + elDiv.classList.add("no-preview"); + } else if (url.length > 0) { + elDiv.classList.remove("no-texture"); + elDiv.classList.remove("no-preview"); + elDiv.classList.add("with-texture"); + elImage.src = url; + elImage.style.display = "block"; + } else { + elImage.src = ""; + elImage.style.display = "none"; + elDiv.classList.remove("with-texture"); + elDiv.classList.remove("no-preview"); + elDiv.classList.add("no-texture"); + } + }, DEBOUNCE_TIMEOUT * 2); + elInput.imageLoad = imageLoad; + elInput.oninput = function (event) { + // Add throttle + let url = event.target.value; + imageLoad(url); + updateProperty(property.name, url, property.isParticleProperty) + }; + elInput.onchange = elInput.oninput; + + elProperty.appendChild(elLabel); + elProperty.appendChild(elDiv); + elProperty.appendChild(elInput); + + let elResult = []; + elResult[TEXTURE_ELEMENTS.IMAGE] = elImage; + elResult[TEXTURE_ELEMENTS.TEXT_INPUT] = elInput; + return elResult; +} + +function createButtonsProperty(property, elProperty, elLabel) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "property text"; + + let hasLabel = propertyData.label !== undefined; + if (hasLabel) { + elProperty.appendChild(elLabel); + } + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, elementID, propertyData.buttons, hasLabel); + } + + return elProperty; +} + +function createTupleNumberInput(elTuple, propertyElementID, subLabel, min, max, step) { + let elementID = propertyElementID + "-" + subLabel.toLowerCase(); + + let elDiv = document.createElement('div'); + let elLabel = document.createElement('label'); + elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1) + ":"; + elLabel.setAttribute("for", elementID); + + let elInput = document.createElement('input'); + elInput.className = subLabel; + elInput.setAttribute("id", elementID); + elInput.setAttribute("type", "number"); + elInput.setAttribute("min", min); + elInput.setAttribute("max", max); + elInput.setAttribute("step", step); + + elDiv.appendChild(elInput); + elDiv.appendChild(elLabel); + elTuple.appendChild(elDiv); + + return elInput; +} + +function addUnit(unit, elLabel) { + if (unit !== undefined) { + let elSpan = document.createElement('span'); + elSpan.className = "unit"; + elSpan.innerHTML = unit; + elLabel.appendChild(elSpan); + } +} + +function addButtons(elProperty, propertyID, buttons, newRow) { + let elDiv = document.createElement('div'); + elDiv.className = "row"; + + buttons.forEach(function(button) { + let elButton = document.createElement('input'); + elButton.className = button.className; + elButton.setAttribute("type", "button"); + elButton.setAttribute("id", propertyID + "-button-" + button.id); + elButton.setAttribute("value", button.label); + elButton.addEventListener("click", button.onClick); + if (newRow) { + elDiv.appendChild(elButton); + } else { + elProperty.appendChild(elButton); + } + }); + + if (newRow) { + elProperty.appendChild(document.createElement('br')); + elProperty.appendChild(elDiv); + } +} + + +/** + * BUTTON CALLBACKS + */ + +function rescaleDimensions() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "rescaleDimensions", + percentage: parseFloat(document.getElementById("property-scale").value) + })); +} + +function moveSelectionToGrid() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveSelectionToGrid" + })); +} + +function moveAllToGrid() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveAllToGrid" + })); +} + +function resetToNaturalDimensions() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "resetToNaturalDimensions" + })); +} + +function reloadScripts() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadClientScripts" + })); +} + +function reloadServerScripts() { + // invalidate the current status (so that same-same updates can still be observed visually) + document.getElementById("property-serverScripts-status").innerText = PENDING_SCRIPT_STATUS; + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadServerScripts" + })); +} + +function copySkyboxURLToAmbientURL() { + let skyboxURL = getPropertyInputElement("skybox.url").value; + getPropertyInputElement("ambientLight.ambientURL").value = skyboxURL; + updateProperty("ambientLight.ambientURL", skyboxURL, false); +} + + +/** + * USER DATA FUNCTIONS + */ + +function clearUserData() { + let elUserData = getPropertyInputElement("userData"); + deleteJSONEditor(); + elUserData.value = ""; + showUserDataTextArea(); + showNewJSONEditorButton(); + hideSaveUserDataButton(); + updateProperty('userData', elUserData.value, false); +} + +function newJSONEditor() { + deleteJSONEditor(); + createJSONEditor(); + let data = {}; + setEditorJSON(data); + hideUserDataTextArea(); + hideNewJSONEditorButton(); + showSaveUserDataButton(); +} + +function saveUserData() { + saveJSONUserData(true); } function setUserDataFromEditor(noUpdate) { - var json = null; + let json = null; try { json = editor.get(); } catch (e) { @@ -292,7 +2242,7 @@ function setUserDataFromEditor(noUpdate) { if (json === null) { return; } else { - var text = editor.getText(); + let text = editor.getText(); if (noUpdate === true) { EventBridge.emitWebEvent( JSON.stringify({ @@ -305,17 +2255,17 @@ function setUserDataFromEditor(noUpdate) { ); return; } else { - updateProperty('userData', text); + updateProperty('userData', text, false); } } } function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, removeKeys) { - var properties = {}; - var parsedData = {}; - var keysToBeRemoved = removeKeys ? removeKeys : []; + let propertyUpdate = {}; + let parsedData = {}; + let keysToBeRemoved = removeKeys ? removeKeys : []; try { - if ($('#userdata-editor').css('height') !== "0px") { + if ($('#property-userData-editor').css('height') !== "0px") { // if there is an expanded, we want to use its json. parsedData = getEditorJSON(); } else { @@ -328,14 +2278,14 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r if (!(groupName in parsedData)) { parsedData[groupName] = {}; } - var keys = Object.keys(updateKeyPair); + let keys = Object.keys(updateKeyPair); keys.forEach(function (key) { if (updateKeyPair[key] !== null && updateKeyPair[key] !== "null") { if (updateKeyPair[key] instanceof Element) { if (updateKeyPair[key].type === "checkbox") { parsedData[groupName][key] = updateKeyPair[key].checked; } else { - var val = isNaN(updateKeyPair[key].value) ? updateKeyPair[key].value : parseInt(updateKeyPair[key].value); + let val = isNaN(updateKeyPair[key].value) ? updateKeyPair[key].value : parseInt(updateKeyPair[key].value); parsedData[groupName][key] = val; } } else { @@ -355,153 +2305,21 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r delete parsedData[groupName]; } if (Object.keys(parsedData).length > 0) { - properties.userData = JSON.stringify(parsedData); + propertyUpdate.userData = JSON.stringify(parsedData); } else { - properties.userData = ''; + propertyUpdate.userData = ''; } - userDataElement.value = properties.userData; + userDataElement.value = propertyUpdate.userData; - updateProperties(properties); -} - -function setMaterialDataFromEditor(noUpdate) { - var json = null; - try { - json = materialEditor.get(); - } catch (e) { - alert('Invalid JSON code - look for red X in your code ', +e); - } - if (json === null) { - return; - } else { - var text = materialEditor.getText(); - if (noUpdate === true) { - EventBridge.emitWebEvent( - JSON.stringify({ - id: lastEntityID, - type: "saveMaterialData", - properties: { - materialData: text - } - }) - ); - return; - } else { - updateProperty('materialData', text); - } - } -} - -function setTextareaScrolling(element) { - var isScrolling = element.scrollHeight > element.offsetHeight; - element.setAttribute("scrolling", isScrolling ? "true" : "false"); -} - - -var materialEditor = null; - -function createJSONMaterialEditor() { - var container = document.getElementById("materialdata-editor"); - var options = { - search: false, - mode: 'tree', - modes: ['code', 'tree'], - name: 'materialData', - onModeChange: function() { - $('.jsoneditor-poweredBy').remove(); - }, - onError: function(e) { - alert('JSON editor:' + e); - }, - onChange: function() { - var currentJSONString = materialEditor.getText(); - - if (currentJSONString === '{"":""}') { - return; - } - $('#materialdata-save').attr('disabled', false); - - - } - }; - materialEditor = new JSONEditor(container, options); -} - -function hideNewJSONMaterialEditorButton() { - $('#materialdata-new-editor').hide(); -} - -function showSaveMaterialDataButton() { - $('#materialdata-save').show(); -} - -function hideSaveMaterialDataButton() { - $('#materialdata-save').hide(); -} - -function showNewJSONMaterialEditorButton() { - $('#materialdata-new-editor').show(); -} - -function showMaterialDataTextArea() { - $('#property-material-data').show(); -} - -function hideMaterialDataTextArea() { - $('#property-material-data').hide(); -} - -function showStaticMaterialData() { - if (materialEditor !== null) { - $('#static-materialdata').show(); - $('#static-materialdata').css('height', $('#materialdata-editor').height()); - $('#static-materialdata').text(materialEditor.getText()); - } -} - -function removeStaticMaterialData() { - $('#static-materialdata').hide(); -} - -function setMaterialEditorJSON(json) { - materialEditor.set(json); - if (materialEditor.hasOwnProperty('expandAll')) { - materialEditor.expandAll(); - } -} - -function getMaterialEditorJSON() { - return materialEditor.get(); -} - -function deleteJSONMaterialEditor() { - if (materialEditor !== null) { - materialEditor.destroy(); - materialEditor = null; - } -} - -var savedMaterialJSONTimer = null; - -function saveJSONMaterialData(noUpdate) { - setMaterialDataFromEditor(noUpdate); - $('#materialdata-saved').show(); - $('#materialdata-save').attr('disabled', true); - if (savedMaterialJSONTimer !== null) { - clearTimeout(savedMaterialJSONTimer); - } - savedMaterialJSONTimer = setTimeout(function() { - $('#materialdata-saved').hide(); - - }, EDITOR_TIMEOUT_DURATION); + updateProperties(propertyUpdate); } var editor = null; function createJSONEditor() { - var container = document.getElementById("userdata-editor"); - var options = { + let container = document.getElementById("property-userData-editor"); + let options = { search: false, mode: 'tree', modes: ['code', 'tree'], @@ -513,12 +2331,12 @@ function createJSONEditor() { alert('JSON editor:' + e); }, onChange: function() { - var currentJSONString = editor.getText(); + let currentJSONString = editor.getText(); if (currentJSONString === '{"":""}') { return; } - $('#userdata-save').attr('disabled', false); + $('#property-userData-button-save').attr('disabled', false); } @@ -526,40 +2344,48 @@ function createJSONEditor() { editor = new JSONEditor(container, options); } -function hideNewJSONEditorButton() { - $('#userdata-new-editor').hide(); -} - function showSaveUserDataButton() { - $('#userdata-save').show(); + $('#property-userData-button-save').show(); } function hideSaveUserDataButton() { - $('#userdata-save').hide(); + $('#property-userData-button-save').hide(); +} + +function disableSaveUserDataButton() { + $('#property-userData-button-save').attr('disabled', true); } function showNewJSONEditorButton() { - $('#userdata-new-editor').show(); + $('#property-userData-button-edit').show(); +} + +function hideNewJSONEditorButton() { + $('#property-userData-button-edit').hide(); } function showUserDataTextArea() { - $('#property-user-data').show(); + $('#property-userData').show(); } function hideUserDataTextArea() { - $('#property-user-data').hide(); + $('#property-userData').hide(); +} + +function hideUserDataSaved() { + $('#property-userData-saved').hide(); } function showStaticUserData() { if (editor !== null) { - $('#static-userdata').show(); - $('#static-userdata').css('height', $('#userdata-editor').height()); - $('#static-userdata').text(editor.getText()); + $('#property-userData-static').show(); + $('#property-userData-static').css('height', $('#property-userData-editor').height()); + $('#property-userData-static').text(editor.getText()); } } function removeStaticUserData() { - $('#static-userdata').hide(); + $('#property-userData-static').hide(); } function setEditorJSON(json) { @@ -584,307 +2410,443 @@ var savedJSONTimer = null; function saveJSONUserData(noUpdate) { setUserDataFromEditor(noUpdate); - $('#userdata-saved').show(); - $('#userdata-save').attr('disabled', true); + $('#property-userData-saved').show(); + $('#property-userData-button-save').attr('disabled', true); if (savedJSONTimer !== null) { clearTimeout(savedJSONTimer); } savedJSONTimer = setTimeout(function() { - $('#userdata-saved').hide(); + hideUserDataSaved(); + }, EDITOR_TIMEOUT_DURATION); +} + +/** + * MATERIAL DATA FUNCTIONS + */ + +function clearMaterialData() { + let elMaterialData = getPropertyInputElement("materialData"); + deleteJSONMaterialEditor(); + elMaterialData.value = ""; + showMaterialDataTextArea(); + showNewJSONMaterialEditorButton(); + hideSaveMaterialDataButton(); + updateProperty('materialData', elMaterialData.value, false); +} + +function newJSONMaterialEditor() { + deleteJSONMaterialEditor(); + createJSONMaterialEditor(); + let data = {}; + setMaterialEditorJSON(data); + hideMaterialDataTextArea(); + hideNewJSONMaterialEditorButton(); + showSaveMaterialDataButton(); +} + +function saveMaterialData() { + saveJSONMaterialData(true); +} + +function setMaterialDataFromEditor(noUpdate) { + let json = null; + try { + json = materialEditor.get(); + } catch (e) { + alert('Invalid JSON code - look for red X in your code ', +e); + } + if (json === null) { + return; + } else { + let text = materialEditor.getText(); + if (noUpdate === true) { + EventBridge.emitWebEvent( + JSON.stringify({ + id: lastEntityID, + type: "saveMaterialData", + properties: { + materialData: text + } + }) + ); + return; + } else { + updateProperty('materialData', text, false); + } + } +} + +var materialEditor = null; + +function createJSONMaterialEditor() { + let container = document.getElementById("property-materialData-editor"); + let options = { + search: false, + mode: 'tree', + modes: ['code', 'tree'], + name: 'materialData', + onModeChange: function() { + $('.jsoneditor-poweredBy').remove(); + }, + onError: function(e) { + alert('JSON editor:' + e); + }, + onChange: function() { + let currentJSONString = materialEditor.getText(); + + if (currentJSONString === '{"":""}') { + return; + } + $('#property-materialData-button-save').attr('disabled', false); + + + } + }; + materialEditor = new JSONEditor(container, options); +} + +function showSaveMaterialDataButton() { + $('#property-materialData-button-save').show(); +} + +function hideSaveMaterialDataButton() { + $('#property-materialData-button-save').hide(); +} + +function disableSaveMaterialDataButton() { + $('#property-materialData-button-save').attr('disabled', true); +} + +function showNewJSONMaterialEditorButton() { + $('#property-materialData-button-edit').show(); +} + +function hideNewJSONMaterialEditorButton() { + $('#property-materialData-button-edit').hide(); +} + +function showMaterialDataTextArea() { + $('#property-materialData').show(); +} + +function hideMaterialDataTextArea() { + $('#property-materialData').hide(); +} + +function hideMaterialDataSaved() { + $('#property-materialData-saved').hide(); +} + +function showStaticMaterialData() { + if (materialEditor !== null) { + $('#property-materialData-static').show(); + $('#property-materialData-static').css('height', $('#property-materialData-editor').height()); + $('#property-materialData-static').text(materialEditor.getText()); + } +} + +function removeStaticMaterialData() { + $('#property-materialData-static').hide(); +} + +function setMaterialEditorJSON(json) { + materialEditor.set(json); + if (materialEditor.hasOwnProperty('expandAll')) { + materialEditor.expandAll(); + } +} + +function getMaterialEditorJSON() { + return materialEditor.get(); +} + +function deleteJSONMaterialEditor() { + if (materialEditor !== null) { + materialEditor.destroy(); + materialEditor = null; + } +} + +var savedMaterialJSONTimer = null; + +function saveJSONMaterialData(noUpdate) { + setMaterialDataFromEditor(noUpdate); + $('#property-materialData-saved').show(); + $('#property-materialData-button-save').attr('disabled', true); + if (savedMaterialJSONTimer !== null) { + clearTimeout(savedMaterialJSONTimer); + } + savedMaterialJSONTimer = setTimeout(function() { + hideMaterialDataSaved(); }, EDITOR_TIMEOUT_DURATION); } function bindAllNonJSONEditorElements() { - var inputs = $('input'); - var i; - for (i = 0; i < inputs.length; i++) { - var input = inputs[i]; - var field = $(input); + let inputs = $('input'); + let i; + for (i = 0; i < inputs.length; ++i) { + let input = inputs[i]; + let field = $(input); // TODO FIXME: (JSHint) Functions declared within loops referencing // an outer scoped variable may lead to confusing semantics. field.on('focus', function(e) { - if (e.target.id === "userdata-new-editor" || e.target.id === "userdata-clear" || e.target.id === "materialdata-new-editor" || e.target.id === "materialdata-clear") { + if (e.target.id === "property-userData-button-edit" || e.target.id === "property-userData-button-clear" || + e.target.id === "property-materialData-button-edit" || e.target.id === "property-materialData-button-clear") { return; } else { - if ($('#userdata-editor').css('height') !== "0px") { - saveJSONUserData(true); + if ($('#property-userData-editor').css('height') !== "0px") { + saveUserData(); } - if ($('#materialdata-editor').css('height') !== "0px") { - saveJSONMaterialData(true); + if ($('#property-materialData-editor').css('height') !== "0px") { + saveMaterialData(); } } }); } } -function unbindAllInputs() { - var inputs = $('input'); - var i; - for (i = 0; i < inputs.length; i++) { - var input = inputs[i]; - var field = $(input); - field.unbind(); + +/** + * DROPDOWN FUNCTIONS + */ + +function setDropdownText(dropdown) { + let lis = dropdown.parentNode.getElementsByTagName("li"); + let text = ""; + for (let i = 0; i < lis.length; ++i) { + if (String(lis[i].getAttribute("value")) === String(dropdown.value)) { + text = lis[i].textContent; + } } + dropdown.firstChild.textContent = text; +} + +function toggleDropdown(event) { + let element = event.target; + if (element.nodeName !== "DT") { + element = element.parentNode; + } + element = element.parentNode; + let isDropped = element.getAttribute("dropped"); + element.setAttribute("dropped", isDropped !== "true" ? "true" : "false"); +} + +function setDropdownValue(event) { + let dt = event.target.parentNode.parentNode.previousSibling; + dt.value = event.target.getAttribute("value"); + dt.firstChild.textContent = event.target.textContent; + + dt.parentNode.setAttribute("dropped", "false"); + + let evt = document.createEvent("HTMLEvents"); + evt.initEvent("change", true, true); + dt.dispatchEvent(evt); +} + + +/** + * TEXTAREA / PARENT MATERIAL NAME FUNCTIONS + */ + +function setTextareaScrolling(element) { + let isScrolling = element.scrollHeight > element.offsetHeight; + element.setAttribute("scrolling", isScrolling ? "true" : "false"); } function showParentMaterialNameBox(number, elNumber, elString) { if (number) { - $('#property-parent-material-id-number-container').show(); - $('#property-parent-material-id-string-container').hide(); + $('#property-submeshToReplace').parent().show(); + $('#property-materialNameToReplace').parent().hide(); elString.value = ""; } else { - $('#property-parent-material-id-string-container').show(); - $('#property-parent-material-id-number-container').hide(); + $('#property-materialNameToReplace').parent().show(); + $('#property-submeshToReplace').parent().hide(); elNumber.value = 0; } } + + function loaded() { openEventBridge(function() { - - var elPropertiesList = document.getElementById("properties-list"); - var elID = document.getElementById("property-id"); - var elType = document.getElementById("property-type"); - var elTypeIcon = document.getElementById("type-icon"); - var elName = document.getElementById("property-name"); - var elLocked = document.getElementById("property-locked"); - var elVisible = document.getElementById("property-visible"); - var elPositionX = document.getElementById("property-pos-x"); - var elPositionY = document.getElementById("property-pos-y"); - var elPositionZ = document.getElementById("property-pos-z"); - var elMoveSelectionToGrid = document.getElementById("move-selection-to-grid"); - var elMoveAllToGrid = document.getElementById("move-all-to-grid"); - - var elDimensionsX = document.getElementById("property-dim-x"); - var elDimensionsY = document.getElementById("property-dim-y"); - var elDimensionsZ = document.getElementById("property-dim-z"); - var elResetToNaturalDimensions = document.getElementById("reset-to-natural-dimensions"); - var elRescaleDimensionsPct = document.getElementById("dimension-rescale-pct"); - var elRescaleDimensionsButton = document.getElementById("dimension-rescale-button"); - - var elParentID = document.getElementById("property-parent-id"); - var elParentJointIndex = document.getElementById("property-parent-joint-index"); - - var elRegistrationX = document.getElementById("property-reg-x"); - var elRegistrationY = document.getElementById("property-reg-y"); - var elRegistrationZ = document.getElementById("property-reg-z"); - - var elRotationX = document.getElementById("property-rot-x"); - var elRotationY = document.getElementById("property-rot-y"); - var elRotationZ = document.getElementById("property-rot-z"); - - var elLinearVelocityX = document.getElementById("property-lvel-x"); - var elLinearVelocityY = document.getElementById("property-lvel-y"); - var elLinearVelocityZ = document.getElementById("property-lvel-z"); - var elLinearDamping = document.getElementById("property-ldamping"); - - var elAngularVelocityX = document.getElementById("property-avel-x"); - var elAngularVelocityY = document.getElementById("property-avel-y"); - var elAngularVelocityZ = document.getElementById("property-avel-z"); - var elAngularDamping = document.getElementById("property-adamping"); - - var elRestitution = document.getElementById("property-restitution"); - var elFriction = document.getElementById("property-friction"); - - var elGravityX = document.getElementById("property-grav-x"); - var elGravityY = document.getElementById("property-grav-y"); - var elGravityZ = document.getElementById("property-grav-z"); - - var elAccelerationX = document.getElementById("property-lacc-x"); - var elAccelerationY = document.getElementById("property-lacc-y"); - var elAccelerationZ = document.getElementById("property-lacc-z"); - - var elDensity = document.getElementById("property-density"); - var elCollisionless = document.getElementById("property-collisionless"); - var elDynamic = document.getElementById("property-dynamic"); - var elCollideStatic = document.getElementById("property-collide-static"); - var elCollideDynamic = document.getElementById("property-collide-dynamic"); - var elCollideKinematic = document.getElementById("property-collide-kinematic"); - var elCollideMyAvatar = document.getElementById("property-collide-myAvatar"); - var elCollideOtherAvatar = document.getElementById("property-collide-otherAvatar"); - var elCollisionSoundURL = document.getElementById("property-collision-sound-url"); - - var elGrabbable = document.getElementById("property-grabbable"); - var elTriggerable = document.getElementById("property-triggerable"); - var elGrabFollowsController = document.getElementById("property-grab-follows-controller"); - - var elCloneable = document.getElementById("property-cloneable"); - var elCloneableDynamic = document.getElementById("property-cloneable-dynamic"); - var elCloneableAvatarEntity = document.getElementById("property-cloneable-avatarEntity"); - var elCloneableGroup = document.getElementById("group-cloneable-group"); - var elCloneableLifetime = document.getElementById("property-cloneable-lifetime"); - var elCloneableLimit = document.getElementById("property-cloneable-limit"); - - var elLifetime = document.getElementById("property-lifetime"); - var elScriptURL = document.getElementById("property-script-url"); - var elScriptTimestamp = document.getElementById("property-script-timestamp"); - var elReloadScriptsButton = document.getElementById("reload-script-button"); - var elServerScripts = document.getElementById("property-server-scripts"); - var elReloadServerScriptsButton = document.getElementById("reload-server-scripts-button"); - var elServerScriptStatus = document.getElementById("server-script-status"); - var elServerScriptError = document.getElementById("server-script-error"); - var elUserData = document.getElementById("property-user-data"); - var elClearUserData = document.getElementById("userdata-clear"); - var elSaveUserData = document.getElementById("userdata-save"); - var elNewJSONEditor = document.getElementById('userdata-new-editor'); - var elColorControlVariant2 = document.getElementById("property-color-control2"); - var elColorRed = document.getElementById("property-color-red"); - var elColorGreen = document.getElementById("property-color-green"); - var elColorBlue = document.getElementById("property-color-blue"); - - var elShape = document.getElementById("property-shape"); - - var elCanCastShadow = document.getElementById("property-can-cast-shadow"); - - var elLightSpotLight = document.getElementById("property-light-spot-light"); - var elLightColor = document.getElementById("property-light-color"); - var elLightColorRed = document.getElementById("property-light-color-red"); - var elLightColorGreen = document.getElementById("property-light-color-green"); - var elLightColorBlue = document.getElementById("property-light-color-blue"); - - var elLightIntensity = document.getElementById("property-light-intensity"); - var elLightFalloffRadius = document.getElementById("property-light-falloff-radius"); - var elLightExponent = document.getElementById("property-light-exponent"); - var elLightCutoff = document.getElementById("property-light-cutoff"); - - var elModelURL = document.getElementById("property-model-url"); - var elShapeType = document.getElementById("property-shape-type"); - var elCompoundShapeURL = document.getElementById("property-compound-shape-url"); - var elModelAnimationURL = document.getElementById("property-model-animation-url"); - var elModelAnimationPlaying = document.getElementById("property-model-animation-playing"); - var elModelAnimationFPS = document.getElementById("property-model-animation-fps"); - var elModelAnimationFrame = document.getElementById("property-model-animation-frame"); - var elModelAnimationFirstFrame = document.getElementById("property-model-animation-first-frame"); - var elModelAnimationLastFrame = document.getElementById("property-model-animation-last-frame"); - var elModelAnimationLoop = document.getElementById("property-model-animation-loop"); - var elModelAnimationHold = document.getElementById("property-model-animation-hold"); - var elModelAnimationAllowTranslation = document.getElementById("property-model-animation-allow-translation"); - var elModelTextures = document.getElementById("property-model-textures"); - var elModelOriginalTextures = document.getElementById("property-model-original-textures"); - - var elMaterialURL = document.getElementById("property-material-url"); - //var elMaterialMappingMode = document.getElementById("property-material-mapping-mode"); - var elPriority = document.getElementById("property-priority"); - var elParentMaterialNameString = document.getElementById("property-parent-material-id-string"); - var elParentMaterialNameNumber = document.getElementById("property-parent-material-id-number"); - var elParentMaterialNameCheckbox = document.getElementById("property-parent-material-id-checkbox"); - var elMaterialMappingPosX = document.getElementById("property-material-mapping-pos-x"); - var elMaterialMappingPosY = document.getElementById("property-material-mapping-pos-y"); - var elMaterialMappingScaleX = document.getElementById("property-material-mapping-scale-x"); - var elMaterialMappingScaleY = document.getElementById("property-material-mapping-scale-y"); - var elMaterialMappingRot = document.getElementById("property-material-mapping-rot"); - var elMaterialData = document.getElementById("property-material-data"); - var elClearMaterialData = document.getElementById("materialdata-clear"); - var elSaveMaterialData = document.getElementById("materialdata-save"); - var elNewJSONMaterialEditor = document.getElementById('materialdata-new-editor'); - - var elImageURL = document.getElementById("property-image-url"); - - var elWebSourceURL = document.getElementById("property-web-source-url"); - var elWebDPI = document.getElementById("property-web-dpi"); - - var elDescription = document.getElementById("property-description"); - - var elHyperlinkHref = document.getElementById("property-hyperlink-href"); - - var elTextText = document.getElementById("property-text-text"); - var elTextLineHeight = document.getElementById("property-text-line-height"); - var elTextTextColor = document.getElementById("property-text-text-color"); - var elTextFaceCamera = document.getElementById("property-text-face-camera"); - var elTextTextColorRed = document.getElementById("property-text-text-color-red"); - var elTextTextColorGreen = document.getElementById("property-text-text-color-green"); - var elTextTextColorBlue = document.getElementById("property-text-text-color-blue"); - var elTextBackgroundColor = document.getElementById("property-text-background-color"); - var elTextBackgroundColorRed = document.getElementById("property-text-background-color-red"); - var elTextBackgroundColorGreen = document.getElementById("property-text-background-color-green"); - var elTextBackgroundColorBlue = document.getElementById("property-text-background-color-blue"); - - // Key light - var elZoneKeyLightModeInherit = document.getElementById("property-zone-key-light-mode-inherit"); - var elZoneKeyLightModeDisabled = document.getElementById("property-zone-key-light-mode-disabled"); - var elZoneKeyLightModeEnabled = document.getElementById("property-zone-key-light-mode-enabled"); - - var elZoneKeyLightColor = document.getElementById("property-zone-key-light-color"); - var elZoneKeyLightColorRed = document.getElementById("property-zone-key-light-color-red"); - var elZoneKeyLightColorGreen = document.getElementById("property-zone-key-light-color-green"); - var elZoneKeyLightColorBlue = document.getElementById("property-zone-key-light-color-blue"); - var elZoneKeyLightIntensity = document.getElementById("property-zone-key-intensity"); - var elZoneKeyLightDirectionX = document.getElementById("property-zone-key-light-direction-x"); - var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); - - var elZoneKeyLightCastShadows = document.getElementById("property-zone-key-light-cast-shadows"); - - // Skybox - var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); - var elZoneSkyboxModeDisabled = document.getElementById("property-zone-skybox-mode-disabled"); - var elZoneSkyboxModeEnabled = document.getElementById("property-zone-skybox-mode-enabled"); - - // Ambient light - var elCopySkyboxURLToAmbientURL = document.getElementById("copy-skybox-url-to-ambient-url"); - - var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); - var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); - var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); - - var elZoneAmbientLightIntensity = document.getElementById("property-zone-key-ambient-intensity"); - var elZoneAmbientLightURL = document.getElementById("property-zone-key-ambient-url"); - - // Haze - var elZoneHazeModeInherit = document.getElementById("property-zone-haze-mode-inherit"); - var elZoneHazeModeDisabled = document.getElementById("property-zone-haze-mode-disabled"); - var elZoneHazeModeEnabled = document.getElementById("property-zone-haze-mode-enabled"); - - var elZoneHazeRange = document.getElementById("property-zone-haze-range"); - var elZoneHazeColor = document.getElementById("property-zone-haze-color"); - var elZoneHazeColorRed = document.getElementById("property-zone-haze-color-red"); - var elZoneHazeColorGreen = document.getElementById("property-zone-haze-color-green"); - var elZoneHazeColorBlue = document.getElementById("property-zone-haze-color-blue"); - var elZoneHazeGlareColor = document.getElementById("property-zone-haze-glare-color"); - var elZoneHazeGlareColorRed = document.getElementById("property-zone-haze-glare-color-red"); - var elZoneHazeGlareColorGreen = document.getElementById("property-zone-haze-glare-color-green"); - var elZoneHazeGlareColorBlue = document.getElementById("property-zone-haze-glare-color-blue"); - var elZoneHazeEnableGlare = document.getElementById("property-zone-haze-enable-light-blend"); - var elZoneHazeGlareAngle = document.getElementById("property-zone-haze-blend-angle"); + let elPropertiesList = document.getElementById("properties-list"); - var elZoneHazeAltitudeEffect = document.getElementById("property-zone-haze-altitude-effect"); - var elZoneHazeBaseRef = document.getElementById("property-zone-haze-base"); - var elZoneHazeCeiling = document.getElementById("property-zone-haze-ceiling"); + GROUPS.forEach(function(group) { + let elGroup; + if (group.addToGroup !== undefined) { + let fieldset = document.getElementById("properties-" + group.addToGroup); + elGroup = document.createElement('div'); + fieldset.appendChild(elGroup); + } else { + elGroup = document.createElement('fieldset'); + elGroup.className = "major"; + elGroup.setAttribute("id", "properties-" + group.id); + elPropertiesList.appendChild(elGroup); + } - var elZoneHazeBackgroundBlend = document.getElementById("property-zone-haze-background-blend"); - - // Bloom - var elZoneBloomModeInherit = document.getElementById("property-zone-bloom-mode-inherit"); - var elZoneBloomModeDisabled = document.getElementById("property-zone-bloom-mode-disabled"); - var elZoneBloomModeEnabled = document.getElementById("property-zone-bloom-mode-enabled"); - - var elZoneBloomIntensity = document.getElementById("property-zone-bloom-intensity"); - var elZoneBloomThreshold = document.getElementById("property-zone-bloom-threshold"); - var elZoneBloomSize = document.getElementById("property-zone-bloom-size"); - - var elZoneSkyboxColor = document.getElementById("property-zone-skybox-color"); - var elZoneSkyboxColorRed = document.getElementById("property-zone-skybox-color-red"); - var elZoneSkyboxColorGreen = document.getElementById("property-zone-skybox-color-green"); - var elZoneSkyboxColorBlue = document.getElementById("property-zone-skybox-color-blue"); - var elZoneSkyboxURL = document.getElementById("property-zone-skybox-url"); - - var elZoneFlyingAllowed = document.getElementById("property-zone-flying-allowed"); - var elZoneGhostingAllowed = document.getElementById("property-zone-ghosting-allowed"); - var elZoneFilterURL = document.getElementById("property-zone-filter-url"); - - var elVoxelVolumeSizeX = document.getElementById("property-voxel-volume-size-x"); - var elVoxelVolumeSizeY = document.getElementById("property-voxel-volume-size-y"); - var elVoxelVolumeSizeZ = document.getElementById("property-voxel-volume-size-z"); - var elVoxelSurfaceStyle = document.getElementById("property-voxel-surface-style"); - var elXTextureURL = document.getElementById("property-x-texture-url"); - var elYTextureURL = document.getElementById("property-y-texture-url"); - var elZTextureURL = document.getElementById("property-z-texture-url"); + if (group.label !== undefined) { + let elLegend = document.createElement('legend'); + elLegend.className = "section-header"; + elLegend.innerText = group.label; + let elSpan = document.createElement('span'); + elSpan.className = ".collapse-icon"; + elSpan.innerText = "M"; + elLegend.appendChild(elSpan); + elGroup.appendChild(elLegend); + } + + group.properties.forEach(function(propertyData) { + let propertyType = propertyData.type; + let propertyID = propertyData.propertyID; + let propertyName = propertyData.propertyName !== undefined ? propertyData.propertyName : propertyID; + let propertyElementID = "property-" + propertyID; + propertyElementID = propertyElementID.replace('.', '-'); + + let elProperty; + if (propertyType === "sub-header") { + elProperty = document.createElement('legend'); + elProperty.className = "sub-section-header"; + elProperty.innerText = propertyData.label; + } else { + elProperty = document.createElement('div'); + elProperty.setAttribute("id", "div-" + propertyElementID); + } + + if (group.twoColumn && propertyData.column !== undefined && propertyData.column !== -1) { + let columnName = group.id + "column" + propertyData.column; + let elColumn = document.getElementById(columnName); + if (!elColumn) { + let columnDivName = group.id + "columnDiv"; + let elColumnDiv = document.getElementById(columnDivName); + if (!elColumnDiv) { + elColumnDiv = document.createElement('div'); + elColumnDiv.className = "two-column"; + elColumnDiv.setAttribute("id", group.id + "columnDiv"); + elGroup.appendChild(elColumnDiv); + } + elColumn = document.createElement('fieldset'); + elColumn.className = "column"; + elColumn.setAttribute("id", columnName); + elColumnDiv.appendChild(elColumn); + } + elColumn.appendChild(elProperty); + } else { + elGroup.appendChild(elProperty); + } + + let elLabel = document.createElement('label'); + elLabel.innerText = propertyData.label; + elLabel.setAttribute("for", propertyElementID); + createAppTooltip.registerTooltipElement(elLabel, propertyID); + + let property = { + data: propertyData, + elementID: propertyElementID, + name: propertyName, + isParticleProperty: group.id.includes("particles"), + elProperty: elProperty + }; + properties[propertyID] = property; + + switch (propertyType) { + case 'string': { + properties[propertyID].elInput = createStringProperty(property, elProperty, elLabel); + break; + } + case 'bool': { + properties[propertyID].elInput = createBoolProperty(property, elProperty, elLabel); + break; + } + case 'number': { + properties[propertyID].elInput = createNumberProperty(property, elProperty, elLabel); + break; + } + case 'slider': { + let elSlider = createSliderProperty(property, elProperty, elLabel); + properties[propertyID].elSlider = elSlider[SLIDER_ELEMENTS.SLIDER]; + properties[propertyID].elInput = elSlider[SLIDER_ELEMENTS.NUMBER_INPUT]; + break; + } + case 'vec3': { + let elVec3 = createVec3Property(property, elProperty, elLabel); + properties[propertyID].elInputX = elVec3[VECTOR_ELEMENTS.X_INPUT]; + properties[propertyID].elInputY = elVec3[VECTOR_ELEMENTS.Y_INPUT]; + properties[propertyID].elInputZ = elVec3[VECTOR_ELEMENTS.Z_INPUT]; + break; + } + case 'vec2': { + let elVec2 = createVec2Property(property, elProperty, elLabel); + properties[propertyID].elInputX = elVec2[VECTOR_ELEMENTS.X_INPUT]; + properties[propertyID].elInputY = elVec2[VECTOR_ELEMENTS.Y_INPUT]; + break; + } + case 'color': { + let elColor = createColorProperty(property, elProperty, elLabel); + properties[propertyID].elColorPicker = elColor[COLOR_ELEMENTS.COLOR_PICKER]; + properties[propertyID].elInputR = elColor[COLOR_ELEMENTS.RED_INPUT]; + properties[propertyID].elInputG = elColor[COLOR_ELEMENTS.GREEN_INPUT]; + properties[propertyID].elInputB = elColor[COLOR_ELEMENTS.BLUE_INPUT]; + break; + } + case 'dropdown': { + properties[propertyID].elInput = createDropdownProperty(property, propertyID, elProperty, elLabel); + break; + } + case 'textarea': { + properties[propertyID].elInput = createTextareaProperty(property, elProperty, elLabel); + break; + } + case 'icon': { + let elIcon = createIconProperty(property, elProperty, elLabel); + properties[propertyID].elSpan = elIcon[ICON_ELEMENTS.ICON]; + properties[propertyID].elLabel = elIcon[ICON_ELEMENTS.LABEL]; + break; + } + case 'texture': { + let elTexture = createTextureProperty(property, elProperty, elLabel); + properties[propertyID].elImage = elTexture[TEXTURE_ELEMENTS.IMAGE]; + properties[propertyID].elInput = elTexture[TEXTURE_ELEMENTS.TEXT_INPUT]; + break; + } + case 'buttons': { + properties[propertyID].elProperty = createButtonsProperty(property, elProperty, elLabel); + break; + } + case 'sub-header': { + break; + } + default: { + console.log("EntityProperties - Unknown property type " + + propertyType + " set to property " + propertyID); + break; + } + } + + let showPropertyRule = propertyData.showPropertyRule; + if (showPropertyRule !== undefined) { + let dependentProperty = Object.keys(showPropertyRule)[0]; + let dependentPropertyValue = showPropertyRule[dependentProperty]; + if (properties[dependentProperty] === undefined) { + properties[dependentProperty] = {}; + } + if (properties[dependentProperty].showPropertyRules === undefined) { + properties[dependentProperty].showPropertyRules = {}; + } + properties[dependentProperty].showPropertyRules[propertyID] = dependentPropertyValue; + } + }); + + elGroups[group.id] = elGroup; + }); + if (window.EventBridge !== undefined) { - var properties; EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); if (data.type === "server_script_status") { + let elServerScriptError = document.getElementById("property-serverScripts-error"); + let elServerScriptStatus = document.getElementById("property-serverScripts-status"); elServerScriptError.value = data.errorInfo; // If we just set elServerScriptError's diplay to block or none, we still end up with // it's parent contributing 21px bottom padding even when elServerScriptError is display:none. @@ -893,231 +2855,34 @@ function loaded() { if (data.statusRetrieved === false) { elServerScriptStatus.innerText = "Failed to retrieve status"; } else if (data.isRunning) { - var ENTITY_SCRIPT_STATUS = { - pending: "Pending", - loading: "Loading", - error_loading_script: "Error loading script", // eslint-disable-line camelcase - error_running_script: "Error running script", // eslint-disable-line camelcase - running: "Running", - unloaded: "Unloaded" - }; elServerScriptStatus.innerText = ENTITY_SCRIPT_STATUS[data.status] || data.status; } else { - elServerScriptStatus.innerText = "Not running"; + elServerScriptStatus.innerText = NOT_RUNNING_SCRIPT_STATUS; } } else if (data.type === "update" && data.selections) { - if (data.selections.length === 0) { if (lastEntityID !== null) { if (editor !== null) { - saveJSONUserData(true); + saveUserData(); deleteJSONEditor(); } if (materialEditor !== null) { - saveJSONMaterialData(true); + saveMaterialData(); deleteJSONMaterialEditor(); } } - - elTypeIcon.style.display = "none"; - elType.innerHTML = "<i>No selection</i>"; - elPropertiesList.className = ''; - - elID.value = ""; - elName.value = ""; - elLocked.checked = false; - elVisible.checked = false; - - elParentID.value = ""; - elParentJointIndex.value = ""; - - elColorRed.value = ""; - elColorGreen.value = ""; - elColorBlue.value = ""; - elColorControlVariant2.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - - elPositionX.value = ""; - elPositionY.value = ""; - elPositionZ.value = ""; - - elRotationX.value = ""; - elRotationY.value = ""; - elRotationZ.value = ""; - - elDimensionsX.value = ""; - elDimensionsY.value = ""; - elDimensionsZ.value = ""; - - elRegistrationX.value = ""; - elRegistrationY.value = ""; - elRegistrationZ.value = ""; - - elLinearVelocityX.value = ""; - elLinearVelocityY.value = ""; - elLinearVelocityZ.value = ""; - elLinearDamping.value = ""; - - elAngularVelocityX.value = ""; - elAngularVelocityY.value = ""; - elAngularVelocityZ.value = ""; - elAngularDamping.value = ""; - - elGravityX.value = ""; - elGravityY.value = ""; - elGravityZ.value = ""; - - elAccelerationX.value = ""; - elAccelerationY.value = ""; - elAccelerationZ.value = ""; - - elRestitution.value = ""; - elFriction.value = ""; - elDensity.value = ""; - - elCollisionless.checked = false; - elDynamic.checked = false; - - elCollideStatic.checked = false; - elCollideKinematic.checked = false; - elCollideDynamic.checked = false; - elCollideMyAvatar.checked = false; - elCollideOtherAvatar.checked = false; - - elGrabbable.checked = false; - elTriggerable.checked = false; - elGrabFollowsController.checked = false; - - elCloneable.checked = false; - elCloneableDynamic.checked = false; - elCloneableAvatarEntity.checked = false; - elCloneableGroup.style.display = "none"; - elCloneableLimit.value = ""; - elCloneableLifetime.value = ""; - - showElements(document.getElementsByClassName('can-cast-shadow-section'), true); - elCanCastShadow.checked = false; - - elCollisionSoundURL.value = ""; - elLifetime.value = ""; - elScriptURL.value = ""; - elServerScripts.value = ""; - elHyperlinkHref.value = ""; - elDescription.value = ""; - + + resetProperties(); + showGroupsForType("None"); + deleteJSONEditor(); - elUserData.value = ""; + getPropertyInputElement("userData").value = ""; showUserDataTextArea(); showSaveUserDataButton(); showNewJSONEditorButton(); - // Shape Properties - elShape.value = "Cube"; - setDropdownText(elShape); - - // Light Properties - elLightSpotLight.checked = false; - elLightColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elLightColorRed.value = ""; - elLightColorGreen.value = ""; - elLightColorBlue.value = ""; - elLightIntensity.value = ""; - elLightFalloffRadius.value = ""; - elLightExponent.value = ""; - elLightCutoff.value = ""; - - // Model Properties - elModelURL.value = ""; - elCompoundShapeURL.value = ""; - elShapeType.value = "none"; - setDropdownText(elShapeType); - elModelAnimationURL.value = ""; - elModelAnimationPlaying.checked = false; - elModelAnimationFPS.value = ""; - elModelAnimationFrame.value = ""; - elModelAnimationFirstFrame.value = ""; - elModelAnimationLastFrame.value = ""; - elModelAnimationLoop.checked = false; - elModelAnimationHold.checked = false; - elModelAnimationAllowTranslation.checked = false; - elModelTextures.value = ""; - elModelOriginalTextures.value = ""; - - // Zone Properties - elZoneFlyingAllowed.checked = false; - elZoneGhostingAllowed.checked = false; - elZoneFilterURL.value = ""; - elZoneKeyLightColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elZoneKeyLightColorRed.value = ""; - elZoneKeyLightColorGreen.value = ""; - elZoneKeyLightColorBlue.value = ""; - elZoneKeyLightIntensity.value = ""; - elZoneKeyLightDirectionX.value = ""; - elZoneKeyLightDirectionY.value = ""; - elZoneKeyLightCastShadows.checked = false; - elZoneAmbientLightIntensity.value = ""; - elZoneAmbientLightURL.value = ""; - elZoneHazeRange.value = ""; - elZoneHazeColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elZoneHazeColorRed.value = ""; - elZoneHazeColorGreen.value = ""; - elZoneHazeColorBlue.value = ""; - elZoneHazeBackgroundBlend.value = 0; - elZoneHazeGlareColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elZoneHazeGlareColorRed.value = ""; - elZoneHazeGlareColorGreen.value = ""; - elZoneHazeGlareColorBlue.value = ""; - elZoneHazeEnableGlare.checked = false; - elZoneHazeGlareAngle.value = ""; - elZoneHazeAltitudeEffect.checked = false; - elZoneHazeBaseRef.value = ""; - elZoneHazeCeiling.value = ""; - elZoneBloomIntensity.value = ""; - elZoneBloomThreshold.value = ""; - elZoneBloomSize.value = ""; - elZoneSkyboxColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elZoneSkyboxColorRed.value = ""; - elZoneSkyboxColorGreen.value = ""; - elZoneSkyboxColorBlue.value = ""; - elZoneSkyboxURL.value = ""; - showElements(document.getElementsByClassName('keylight-section'), true); - showElements(document.getElementsByClassName('skybox-section'), true); - showElements(document.getElementsByClassName('ambient-section'), true); - showElements(document.getElementsByClassName('haze-section'), true); - showElements(document.getElementsByClassName('bloom-section'), true); - - // Text Properties - elTextText.value = ""; - elTextLineHeight.value = ""; - elTextFaceCamera.checked = false; - elTextTextColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elTextTextColorRed.value = ""; - elTextTextColorGreen.value = ""; - elTextTextColorBlue.value = ""; - elTextBackgroundColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elTextBackgroundColorRed.value = ""; - elTextBackgroundColorGreen.value = ""; - elTextBackgroundColorBlue.value = ""; - - // Image Properties - elImageURL.value = ""; - - // Web Properties - elWebSourceURL.value = ""; - elWebDPI.value = ""; - - // Material Properties - elMaterialURL.value = ""; - elParentMaterialNameNumber.value = ""; - elParentMaterialNameCheckbox.checked = false; - elPriority.value = ""; - elMaterialMappingPosX.value = ""; - elMaterialMappingPosY.value = ""; - elMaterialMappingScaleX.value = ""; - elMaterialMappingScaleY.value = ""; - elMaterialMappingRot.value = ""; - deleteJSONMaterialEditor(); - elMaterialData.value = ""; + getPropertyInputElement("materialData").value = ""; showMaterialDataTextArea(); showSaveMaterialDataButton(); showNewJSONMaterialEditorButton(); @@ -1126,15 +2891,16 @@ function loaded() { } else if (data.selections.length > 1) { deleteJSONEditor(); deleteJSONMaterialEditor(); - var selections = data.selections; + + let selections = data.selections; - var ids = []; - var types = {}; - var numTypes = 0; + let ids = []; + let types = {}; + let numTypes = 0; - for (var i = 0; i < selections.length; i++) { + for (let i = 0; i < selections.length; ++i) { ids.push(selections[i].id); - var currentSelectedType = selections[i].properties.type; + let currentSelectedType = selections[i].properties.type; if (types[currentSelectedType] === undefined) { types[currentSelectedType] = 0; numTypes += 1; @@ -1142,1002 +2908,459 @@ function loaded() { types[currentSelectedType]++; } - var type = "Multiple"; + let type = "Multiple"; if (numTypes === 1) { type = selections[0].properties.type; } - - elType.innerHTML = type + " (" + data.selections.length + ")"; - elTypeIcon.innerHTML = ICON_FOR_TYPE[type]; - elTypeIcon.style.display = "inline-block"; - elPropertiesList.className = ''; - - elID.value = ""; - + + resetProperties(); + showGroupsForType(type); + + let typeProperty = properties["type"]; + typeProperty.elSpan.innerHTML = typeProperty.data.icons[type]; + typeProperty.elSpan.style.display = "inline-block"; + typeProperty.elLabel.innerHTML = type + " (" + data.selections.length + ")"; + disableProperties(); } else { - - properties = data.selections[0].properties; - if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null) { + selectedEntityProperties = data.selections[0].properties; + + if (lastEntityID !== '"' + selectedEntityProperties.id + '"' && lastEntityID !== null) { if (editor !== null) { - saveJSONUserData(true); + saveUserData(); } if (materialEditor !== null) { - saveJSONMaterialData(true); + saveMaterialData(); } } - var doSelectElement = lastEntityID === '"' + properties.id + '"'; + let doSelectElement = lastEntityID === '"' + selectedEntityProperties.id + '"'; // the event bridge and json parsing handle our avatar id string differently. - lastEntityID = '"' + properties.id + '"'; - elID.value = properties.id; + lastEntityID = '"' + selectedEntityProperties.id + '"'; // HTML workaround since image is not yet a separate entity type - var IMAGE_MODEL_NAME = 'default-image-model.fbx'; - if (properties.type === "Model") { - var urlParts = properties.modelURL.split('/'); - var propsFilename = urlParts[urlParts.length - 1]; + let IMAGE_MODEL_NAME = 'default-image-model.fbx'; + if (selectedEntityProperties.type === "Model") { + let urlParts = selectedEntityProperties.modelURL.split('/'); + let propsFilename = urlParts[urlParts.length - 1]; if (propsFilename === IMAGE_MODEL_NAME) { - properties.type = "Image"; + selectedEntityProperties.type = "Image"; } } - // Create class name for css ruleset filtering - elPropertiesList.className = properties.type + 'Menu'; - - elType.innerHTML = properties.type; - elTypeIcon.innerHTML = ICON_FOR_TYPE[properties.type]; - elTypeIcon.style.display = "inline-block"; - - elLocked.checked = properties.locked; - - elName.value = properties.name; - - elVisible.checked = properties.visible; - - elPositionX.value = properties.position.x.toFixed(4); - elPositionY.value = properties.position.y.toFixed(4); - elPositionZ.value = properties.position.z.toFixed(4); - - elDimensionsX.value = properties.dimensions.x.toFixed(4); - elDimensionsY.value = properties.dimensions.y.toFixed(4); - elDimensionsZ.value = properties.dimensions.z.toFixed(4); - - elParentID.value = properties.parentID; - elParentJointIndex.value = properties.parentJointIndex; - - elRegistrationX.value = properties.registrationPoint.x.toFixed(4); - elRegistrationY.value = properties.registrationPoint.y.toFixed(4); - elRegistrationZ.value = properties.registrationPoint.z.toFixed(4); - - elRotationX.value = properties.rotation.x.toFixed(4); - elRotationY.value = properties.rotation.y.toFixed(4); - elRotationZ.value = properties.rotation.z.toFixed(4); - - elLinearVelocityX.value = properties.velocity.x.toFixed(4); - elLinearVelocityY.value = properties.velocity.y.toFixed(4); - elLinearVelocityZ.value = properties.velocity.z.toFixed(4); - elLinearDamping.value = properties.damping.toFixed(2); - - elAngularVelocityX.value = (properties.angularVelocity.x * RADIANS_TO_DEGREES).toFixed(4); - elAngularVelocityY.value = (properties.angularVelocity.y * RADIANS_TO_DEGREES).toFixed(4); - elAngularVelocityZ.value = (properties.angularVelocity.z * RADIANS_TO_DEGREES).toFixed(4); - elAngularDamping.value = properties.angularDamping.toFixed(4); - - elRestitution.value = properties.restitution.toFixed(4); - elFriction.value = properties.friction.toFixed(4); - - elGravityX.value = properties.gravity.x.toFixed(4); - elGravityY.value = properties.gravity.y.toFixed(4); - elGravityZ.value = properties.gravity.z.toFixed(4); - - elAccelerationX.value = properties.acceleration.x.toFixed(4); - elAccelerationY.value = properties.acceleration.y.toFixed(4); - elAccelerationZ.value = properties.acceleration.z.toFixed(4); - - elDensity.value = properties.density.toFixed(4); - elCollisionless.checked = properties.collisionless; - elDynamic.checked = properties.dynamic; - - elCollideStatic.checked = properties.collidesWith.indexOf("static") > -1; - elCollideKinematic.checked = properties.collidesWith.indexOf("kinematic") > -1; - elCollideDynamic.checked = properties.collidesWith.indexOf("dynamic") > -1; - elCollideMyAvatar.checked = properties.collidesWith.indexOf("myAvatar") > -1; - elCollideOtherAvatar.checked = properties.collidesWith.indexOf("otherAvatar") > -1; - - elGrabbable.checked = properties.grab.grabbable; - elTriggerable.checked = properties.grab.triggerable; - elGrabFollowsController.checked = properties.grab.grabFollowsController; - - elCloneable.checked = properties.cloneable; - elCloneableDynamic.checked = properties.cloneDynamic; - elCloneableAvatarEntity.checked = properties.cloneAvatarEntity; - elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; - elCloneableLimit.value = properties.cloneLimit; - elCloneableLifetime.value = properties.cloneLifetime; - - elCollisionSoundURL.value = properties.collisionSoundURL; - elLifetime.value = properties.lifetime; - elScriptURL.value = properties.script; - elScriptTimestamp.value = properties.scriptTimestamp; - elServerScripts.value = properties.serverScripts; - - var json = null; + showGroupsForType(selectedEntityProperties.type); + + for (let propertyID in properties) { + let property = properties[propertyID]; + let propertyData = property.data; + let propertyName = property.name; + let propertyValue = getPropertyValue(propertyName); + + let isSubProperty = propertyData.subPropertyOf !== undefined; + if (propertyValue === undefined && !isSubProperty) { + continue; + } + + let isPropertyNotNumber = false; + switch (propertyData.type) { + case 'number': + case 'slider': + isPropertyNotNumber = isNaN(propertyValue) || propertyValue === null; + break; + case 'vec3': + case 'vec2': + isPropertyNotNumber = isNaN(propertyValue.x) || propertyValue.x === null; + break; + case 'color': + isPropertyNotNumber = isNaN(propertyValue.red) || propertyValue.red === null; + break; + } + if (isPropertyNotNumber && propertyData.fallbackProperty !== undefined) { + propertyValue = getPropertyValue(propertyData.fallbackProperty); + } + + switch (propertyData.type) { + case 'string': { + property.elInput.value = propertyValue; + break; + } + case 'bool': { + let inverse = propertyData.inverse !== undefined ? propertyData.inverse : false; + if (isSubProperty) { + let propertyValue = selectedEntityProperties[propertyData.subPropertyOf]; + let subProperties = propertyValue.split(","); + let subPropertyValue = subProperties.indexOf(propertyName) > -1; + property.elInput.checked = inverse ? !subPropertyValue : subPropertyValue; + } else { + property.elInput.checked = inverse ? !propertyValue : propertyValue; + } + break; + } + case 'number': + case 'slider': { + let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; + let value = propertyValue / multiplier; + if (propertyData.round !== undefined) { + value = Math.round(value.round) / propertyData.round; + } + if (propertyData.decimals !== undefined) { + property.elInput.value = value.toFixed(propertyData.decimals); + } else { + property.elInput.value = value; + } + if (property.elSlider !== undefined) { + property.elSlider.value = property.elInput.value; + } + break; + } + case 'vec3': + case 'vec2': { + let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; + let valueX = propertyValue.x / multiplier; + let valueY = propertyValue.y / multiplier; + let valueZ = propertyValue.z / multiplier; + if (propertyData.round !== undefined) { + valueX = Math.round(valueX * propertyData.round) / propertyData.round; + valueY = Math.round(valueY * propertyData.round) / propertyData.round; + valueZ = Math.round(valueZ * propertyData.round) / propertyData.round; + } + if (propertyData.decimals !== undefined) { + property.elInputX.value = valueX.toFixed(propertyData.decimals); + property.elInputY.value = valueY.toFixed(propertyData.decimals); + if (property.elInputZ !== undefined) { + property.elInputZ.value = valueZ.toFixed(propertyData.decimals); + } + } else { + property.elInputX.value = valueX; + property.elInputY.value = valueY; + if (property.elInputZ !== undefined) { + property.elInputZ.value = valueZ; + } + } + break; + } + case 'color': { + property.elColorPicker.style.backgroundColor = "rgb(" + propertyValue.red + "," + + propertyValue.green + "," + + propertyValue.blue + ")"; + property.elInputR.value = propertyValue.red; + property.elInputG.value = propertyValue.green; + property.elInputB.value = propertyValue.blue; + break; + } + case 'dropdown': { + property.elInput.value = propertyValue; + setDropdownText(property.elInput); + break; + } + case 'textarea': { + property.elInput.value = propertyValue; + setTextareaScrolling(property.elInput); + break; + } + case 'icon': { + property.elSpan.innerHTML = propertyData.icons[propertyValue]; + property.elSpan.style.display = "inline-block"; + property.elLabel.innerHTML = propertyValue; + break; + } + case 'texture': { + property.elInput.value = propertyValue; + property.elInput.imageLoad(property.elInput.value); + break; + } + } + + let showPropertyRules = property.showPropertyRules; + if (showPropertyRules !== undefined) { + for (let propertyToShow in showPropertyRules) { + let showIfThisPropertyValue = showPropertyRules[propertyToShow]; + let show = String(propertyValue) === String(showIfThisPropertyValue); + showPropertyElement(propertyToShow, show); + } + } + } + + if (selectedEntityProperties.type === "Image") { + let imageLink = JSON.parse(selectedEntityProperties.textures)["tex.picture"]; + getPropertyInputElement("image").value = imageLink; + } else if (selectedEntityProperties.type === "Material") { + let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace"); + let elParentMaterialNameNumber = getPropertyInputElement("submeshToReplace"); + let elParentMaterialNameCheckbox = getPropertyInputElement("selectSubmesh"); + let parentMaterialName = selectedEntityProperties.parentMaterialName; + if (parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { + elParentMaterialNameString.value = parentMaterialName.replace(MATERIAL_PREFIX_STRING, ""); + showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); + elParentMaterialNameCheckbox.checked = false; + } else { + elParentMaterialNameNumber.value = parseInt(parentMaterialName); + showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString); + elParentMaterialNameCheckbox.checked = true; + } + } + + let json = null; try { - json = JSON.parse(properties.userData); + json = JSON.parse(selectedEntityProperties.userData); } catch (e) { // normal text deleteJSONEditor(); - elUserData.value = properties.userData; + getPropertyInputElement("userData").value = selectedEntityProperties.userData; showUserDataTextArea(); showNewJSONEditorButton(); hideSaveUserDataButton(); + hideUserDataSaved(); } if (json !== null) { if (editor === null) { createJSONEditor(); } - setEditorJSON(json); showSaveUserDataButton(); hideUserDataTextArea(); hideNewJSONEditorButton(); + hideUserDataSaved(); } - var materialJson = null; + let materialJson = null; try { - materialJson = JSON.parse(properties.materialData); + materialJson = JSON.parse(selectedEntityProperties.materialData); } catch (e) { // normal text deleteJSONMaterialEditor(); - elMaterialData.value = properties.materialData; + getPropertyInputElement("materialData").value = selectedEntityProperties.materialData; showMaterialDataTextArea(); showNewJSONMaterialEditorButton(); hideSaveMaterialDataButton(); + hideMaterialDataSaved(); } if (materialJson !== null) { if (materialEditor === null) { createJSONMaterialEditor(); } - setMaterialEditorJSON(materialJson); showSaveMaterialDataButton(); hideMaterialDataTextArea(); hideNewJSONMaterialEditorButton(); + hideMaterialDataSaved(); } - - elHyperlinkHref.value = properties.href; - elDescription.value = properties.description; - - - if (properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { - elShape.value = properties.shape; - setDropdownText(elShape); - } - - if (properties.type === "Shape" || properties.type === "Box" || - properties.type === "Sphere" || properties.type === "ParticleEffect") { - elColorRed.value = properties.color.red; - elColorGreen.value = properties.color.green; - elColorBlue.value = properties.color.blue; - elColorControlVariant2.style.backgroundColor = "rgb(" + properties.color.red + "," + - properties.color.green + "," + properties.color.blue + ")"; - } - - if (properties.type === "Model" || - properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { - - elCanCastShadow.checked = properties.canCastShadow; - } - - if (properties.type === "Model") { - elModelURL.value = properties.modelURL; - elShapeType.value = properties.shapeType; - setDropdownText(elShapeType); - elCompoundShapeURL.value = properties.compoundShapeURL; - elModelAnimationURL.value = properties.animation.url; - elModelAnimationPlaying.checked = properties.animation.running; - elModelAnimationFPS.value = properties.animation.fps; - elModelAnimationFrame.value = properties.animation.currentFrame; - elModelAnimationFirstFrame.value = properties.animation.firstFrame; - elModelAnimationLastFrame.value = properties.animation.lastFrame; - elModelAnimationLoop.checked = properties.animation.loop; - elModelAnimationHold.checked = properties.animation.hold; - elModelAnimationAllowTranslation.checked = properties.animation.allowTranslation; - elModelTextures.value = properties.textures; - setTextareaScrolling(elModelTextures); - elModelOriginalTextures.value = properties.originalTextures; - setTextareaScrolling(elModelOriginalTextures); - } else if (properties.type === "Web") { - elWebSourceURL.value = properties.sourceUrl; - elWebDPI.value = properties.dpi; - } else if (properties.type === "Image") { - var imageLink = JSON.parse(properties.textures)["tex.picture"]; - elImageURL.value = imageLink; - } else if (properties.type === "Text") { - elTextText.value = properties.text; - elTextLineHeight.value = properties.lineHeight.toFixed(4); - elTextFaceCamera.checked = properties.faceCamera; - elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + - properties.textColor.green + "," + - properties.textColor.blue + ")"; - elTextTextColorRed.value = properties.textColor.red; - elTextTextColorGreen.value = properties.textColor.green; - elTextTextColorBlue.value = properties.textColor.blue; - elTextBackgroundColor.style.backgroundColor = "rgb(" + properties.backgroundColor.red + "," + - properties.backgroundColor.green + "," + - properties.backgroundColor.blue + ")"; - elTextBackgroundColorRed.value = properties.backgroundColor.red; - elTextBackgroundColorGreen.value = properties.backgroundColor.green; - elTextBackgroundColorBlue.value = properties.backgroundColor.blue; - } else if (properties.type === "Light") { - elLightSpotLight.checked = properties.isSpotlight; - - elLightColor.style.backgroundColor = "rgb(" + properties.color.red + "," + - properties.color.green + "," + properties.color.blue + ")"; - elLightColorRed.value = properties.color.red; - elLightColorGreen.value = properties.color.green; - elLightColorBlue.value = properties.color.blue; - - elLightIntensity.value = properties.intensity.toFixed(1); - elLightFalloffRadius.value = properties.falloffRadius.toFixed(1); - elLightExponent.value = properties.exponent.toFixed(2); - elLightCutoff.value = properties.cutoff.toFixed(2); - } else if (properties.type === "Zone") { - // Key light - elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); - elZoneKeyLightModeDisabled.checked = (properties.keyLightMode === 'disabled'); - elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); - - elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + - properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; - elZoneKeyLightColorRed.value = properties.keyLight.color.red; - elZoneKeyLightColorGreen.value = properties.keyLight.color.green; - elZoneKeyLightColorBlue.value = properties.keyLight.color.blue; - elZoneKeyLightIntensity.value = properties.keyLight.intensity.toFixed(2); - elZoneKeyLightDirectionX.value = properties.keyLight.direction.x.toFixed(2); - elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2); - - elZoneKeyLightCastShadows.checked = properties.keyLight.castShadows; - - // Skybox - elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); - elZoneSkyboxModeDisabled.checked = (properties.skyboxMode === 'disabled'); - elZoneSkyboxModeEnabled.checked = (properties.skyboxMode === 'enabled'); - - // Ambient light - elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); - elZoneAmbientLightModeDisabled.checked = (properties.ambientLightMode === 'disabled'); - elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); - - elZoneAmbientLightIntensity.value = properties.ambientLight.ambientIntensity.toFixed(2); - elZoneAmbientLightURL.value = properties.ambientLight.ambientURL; - - // Haze - elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); - elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); - elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); - - elZoneHazeRange.value = properties.haze.hazeRange.toFixed(0); - elZoneHazeColor.style.backgroundColor = "rgb(" + - properties.haze.hazeColor.red + "," + - properties.haze.hazeColor.green + "," + - properties.haze.hazeColor.blue + ")"; - - elZoneHazeColorRed.value = properties.haze.hazeColor.red; - elZoneHazeColorGreen.value = properties.haze.hazeColor.green; - elZoneHazeColorBlue.value = properties.haze.hazeColor.blue; - elZoneHazeBackgroundBlend.value = properties.haze.hazeBackgroundBlend.toFixed(2); - - elZoneHazeGlareColor.style.backgroundColor = "rgb(" + - properties.haze.hazeGlareColor.red + "," + - properties.haze.hazeGlareColor.green + "," + - properties.haze.hazeGlareColor.blue + ")"; - - elZoneHazeGlareColorRed.value = properties.haze.hazeGlareColor.red; - elZoneHazeGlareColorGreen.value = properties.haze.hazeGlareColor.green; - elZoneHazeGlareColorBlue.value = properties.haze.hazeGlareColor.blue; - - elZoneHazeEnableGlare.checked = properties.haze.hazeEnableGlare; - elZoneHazeGlareAngle.value = properties.haze.hazeGlareAngle.toFixed(0); - - elZoneHazeAltitudeEffect.checked = properties.haze.hazeAltitudeEffect; - elZoneHazeBaseRef.value = properties.haze.hazeBaseRef.toFixed(0); - elZoneHazeCeiling.value = properties.haze.hazeCeiling.toFixed(0); - - elZoneBloomModeInherit.checked = (properties.bloomMode === 'inherit'); - elZoneBloomModeDisabled.checked = (properties.bloomMode === 'disabled'); - elZoneBloomModeEnabled.checked = (properties.bloomMode === 'enabled'); - - elZoneBloomIntensity.value = properties.bloom.bloomIntensity.toFixed(2); - elZoneBloomThreshold.value = properties.bloom.bloomThreshold.toFixed(2); - elZoneBloomSize.value = properties.bloom.bloomSize.toFixed(2); - - elShapeType.value = properties.shapeType; - elCompoundShapeURL.value = properties.compoundShapeURL; - - elZoneSkyboxColor.style.backgroundColor = "rgb(" + properties.skybox.color.red + "," + - properties.skybox.color.green + "," + properties.skybox.color.blue + ")"; - elZoneSkyboxColorRed.value = properties.skybox.color.red; - elZoneSkyboxColorGreen.value = properties.skybox.color.green; - elZoneSkyboxColorBlue.value = properties.skybox.color.blue; - elZoneSkyboxURL.value = properties.skybox.url; - - elZoneFlyingAllowed.checked = properties.flyingAllowed; - elZoneGhostingAllowed.checked = properties.ghostingAllowed; - elZoneFilterURL.value = properties.filterURL; - - // Show/hide sections as required - showElements(document.getElementsByClassName('skybox-section'), - elZoneSkyboxModeEnabled.checked); - - showElements(document.getElementsByClassName('keylight-section'), - elZoneKeyLightModeEnabled.checked); - - showElements(document.getElementsByClassName('ambient-section'), - elZoneAmbientLightModeEnabled.checked); - - showElements(document.getElementsByClassName('haze-section'), - elZoneHazeModeEnabled.checked); - - showElements(document.getElementsByClassName('bloom-section'), - elZoneBloomModeEnabled.checked); - } else if (properties.type === "PolyVox") { - elVoxelVolumeSizeX.value = properties.voxelVolumeSize.x.toFixed(2); - elVoxelVolumeSizeY.value = properties.voxelVolumeSize.y.toFixed(2); - elVoxelVolumeSizeZ.value = properties.voxelVolumeSize.z.toFixed(2); - elVoxelSurfaceStyle.value = properties.voxelSurfaceStyle; - setDropdownText(elVoxelSurfaceStyle); - elXTextureURL.value = properties.xTextureURL; - elYTextureURL.value = properties.yTextureURL; - elZTextureURL.value = properties.zTextureURL; - } else if (properties.type === "Material") { - elMaterialURL.value = properties.materialURL; - //elMaterialMappingMode.value = properties.materialMappingMode; - //setDropdownText(elMaterialMappingMode); - elPriority.value = properties.priority; - if (properties.parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { - elParentMaterialNameString.value = properties.parentMaterialName.replace(MATERIAL_PREFIX_STRING, ""); - showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); - elParentMaterialNameCheckbox.checked = false; - } else { - elParentMaterialNameNumber.value = parseInt(properties.parentMaterialName); - showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString); - elParentMaterialNameCheckbox.checked = true; - } - elMaterialMappingPosX.value = properties.materialMappingPos.x.toFixed(4); - elMaterialMappingPosY.value = properties.materialMappingPos.y.toFixed(4); - elMaterialMappingScaleX.value = properties.materialMappingScale.x.toFixed(4); - elMaterialMappingScaleY.value = properties.materialMappingScale.y.toFixed(4); - elMaterialMappingRot.value = properties.materialMappingRot.toFixed(2); - } - - // Only these types can cast a shadow - if (properties.type === "Model" || - properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { - - showElements(document.getElementsByClassName('can-cast-shadow-section'), true); - } else { - showElements(document.getElementsByClassName('can-cast-shadow-section'), false); - } - - if (properties.locked) { + + if (selectedEntityProperties.locked) { disableProperties(); - elLocked.removeAttribute('disabled'); + getPropertyInputElement("locked").removeAttribute('disabled'); } else { enableProperties(); - elSaveUserData.disabled = true; - elSaveMaterialData.disabled = true; + disableSaveUserDataButton(); + disableSaveMaterialDataButton() } - - var activeElement = document.activeElement; + + let activeElement = document.activeElement; if (doSelectElement && typeof activeElement.select !== "undefined") { activeElement.select(); } } + } else if (data.type === 'tooltipsReply') { + createAppTooltip.setIsEnabled(!data.hmdActive); + createAppTooltip.setTooltipData(data.tooltips); + } else if (data.type === 'hmdActiveChanged') { + createAppTooltip.setIsEnabled(!data.hmdActive); } }); + + // Request tooltips as soon as we can process a reply: + EventBridge.emitWebEvent(JSON.stringify({ type: 'tooltipsRequest' })); } - - elLocked.addEventListener('change', createEmitCheckedPropertyUpdateFunction('locked')); - elName.addEventListener('change', createEmitTextPropertyUpdateFunction('name')); - elHyperlinkHref.addEventListener('change', createEmitTextPropertyUpdateFunction('href')); - elDescription.addEventListener('change', createEmitTextPropertyUpdateFunction('description')); - elVisible.addEventListener('change', createEmitCheckedPropertyUpdateFunction('visible')); - - var positionChangeFunction = createEmitVec3PropertyUpdateFunction( - 'position', elPositionX, elPositionY, elPositionZ); - elPositionX.addEventListener('change', positionChangeFunction); - elPositionY.addEventListener('change', positionChangeFunction); - elPositionZ.addEventListener('change', positionChangeFunction); - - var dimensionsChangeFunction = createEmitVec3PropertyUpdateFunction( - 'dimensions', elDimensionsX, elDimensionsY, elDimensionsZ); - elDimensionsX.addEventListener('change', dimensionsChangeFunction); - elDimensionsY.addEventListener('change', dimensionsChangeFunction); - elDimensionsZ.addEventListener('change', dimensionsChangeFunction); - - elParentID.addEventListener('change', createEmitTextPropertyUpdateFunction('parentID')); - elParentJointIndex.addEventListener('change', createEmitNumberPropertyUpdateFunction('parentJointIndex', 0)); - - var registrationChangeFunction = createEmitVec3PropertyUpdateFunction( - 'registrationPoint', elRegistrationX, elRegistrationY, elRegistrationZ); - elRegistrationX.addEventListener('change', registrationChangeFunction); - elRegistrationY.addEventListener('change', registrationChangeFunction); - elRegistrationZ.addEventListener('change', registrationChangeFunction); - - var rotationChangeFunction = createEmitVec3PropertyUpdateFunction( - 'rotation', elRotationX, elRotationY, elRotationZ); - elRotationX.addEventListener('change', rotationChangeFunction); - elRotationY.addEventListener('change', rotationChangeFunction); - elRotationZ.addEventListener('change', rotationChangeFunction); - - var velocityChangeFunction = createEmitVec3PropertyUpdateFunction( - 'velocity', elLinearVelocityX, elLinearVelocityY, elLinearVelocityZ); - elLinearVelocityX.addEventListener('change', velocityChangeFunction); - elLinearVelocityY.addEventListener('change', velocityChangeFunction); - elLinearVelocityZ.addEventListener('change', velocityChangeFunction); - elLinearDamping.addEventListener('change', createEmitNumberPropertyUpdateFunction('damping')); - - var angularVelocityChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier( - 'angularVelocity', elAngularVelocityX, elAngularVelocityY, elAngularVelocityZ, DEGREES_TO_RADIANS); - elAngularVelocityX.addEventListener('change', angularVelocityChangeFunction); - elAngularVelocityY.addEventListener('change', angularVelocityChangeFunction); - elAngularVelocityZ.addEventListener('change', angularVelocityChangeFunction); - elAngularDamping.addEventListener('change', createEmitNumberPropertyUpdateFunction('angularDamping')); - - elRestitution.addEventListener('change', createEmitNumberPropertyUpdateFunction('restitution')); - elFriction.addEventListener('change', createEmitNumberPropertyUpdateFunction('friction')); - - var gravityChangeFunction = createEmitVec3PropertyUpdateFunction( - 'gravity', elGravityX, elGravityY, elGravityZ); - elGravityX.addEventListener('change', gravityChangeFunction); - elGravityY.addEventListener('change', gravityChangeFunction); - elGravityZ.addEventListener('change', gravityChangeFunction); - - var accelerationChangeFunction = createEmitVec3PropertyUpdateFunction( - 'acceleration', elAccelerationX, elAccelerationY, elAccelerationZ); - elAccelerationX.addEventListener('change', accelerationChangeFunction); - elAccelerationY.addEventListener('change', accelerationChangeFunction); - elAccelerationZ.addEventListener('change', accelerationChangeFunction); - - elDensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('density')); - elCollisionless.addEventListener('change', createEmitCheckedPropertyUpdateFunction('collisionless')); - elDynamic.addEventListener('change', createEmitCheckedPropertyUpdateFunction('dynamic')); - - elCollideDynamic.addEventListener('change', function() { - updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideDynamic, 'dynamic'); + + // Server Script Status + let serverScriptProperty = properties["serverScripts"]; + let elServerScript = serverScriptProperty.elInput; + let serverScriptElementID = serverScriptProperty.elementID; + let serverScriptStatusElementID = serverScriptElementID + "-status"; + let elDiv = document.createElement('div'); + elDiv.className = "property"; + elDiv.setAttribute("id", "div-" + serverScriptStatusElementID); + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", serverScriptStatusElementID); + elLabel.innerText = "Server Script Status"; + createAppTooltip.registerTooltipElement(elLabel, "serverScriptsStatus"); + let elServerScriptStatus = document.createElement('span'); + elServerScriptStatus.setAttribute("id", serverScriptStatusElementID); + elDiv.appendChild(elLabel); + elDiv.appendChild(elServerScriptStatus); + elServerScript.parentNode.appendChild(elDiv); + + // Server Script Error + elDiv = document.createElement('div'); + elDiv.className = "property"; + let elServerScriptError = document.createElement('textarea'); + elServerScriptError.setAttribute("id", serverScriptElementID + "-error"); + elDiv.appendChild(elServerScriptError); + elServerScript.parentNode.appendChild(elDiv); + + let elScript = getPropertyInputElement("script"); + elScript.parentNode.className = "property url refresh"; + elServerScript.parentNode.className = "property url refresh"; + + // User Data + let userDataProperty = properties["userData"]; + let elUserData = userDataProperty.elInput; + let userDataElementID = userDataProperty.elementID; + elDiv = elUserData.parentNode; + let elStaticUserData = document.createElement('div'); + elStaticUserData.setAttribute("id", userDataElementID + "-static"); + let elUserDataEditor = document.createElement('div'); + elUserDataEditor.setAttribute("id", userDataElementID + "-editor"); + let elUserDataSaved = document.createElement('span'); + elUserDataSaved.setAttribute("id", userDataElementID + "-saved"); + elUserDataSaved.innerText = "Saved!"; + elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved); + elDiv.insertBefore(elStaticUserData, elUserData); + elDiv.insertBefore(elUserDataEditor, elUserData); + + // Material Data + let materialDataProperty = properties["materialData"]; + let elMaterialData = materialDataProperty.elInput; + let materialDataElementID = materialDataProperty.elementID; + elDiv = elMaterialData.parentNode; + let elStaticMaterialData = document.createElement('div'); + elStaticMaterialData.setAttribute("id", materialDataElementID + "-static"); + let elMaterialDataEditor = document.createElement('div'); + elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor"); + let elMaterialDataSaved = document.createElement('span'); + elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved"); + elMaterialDataSaved.innerText = "Saved!"; + elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved); + elDiv.insertBefore(elStaticMaterialData, elMaterialData); + elDiv.insertBefore(elMaterialDataEditor, elMaterialData); + + // Special Property Callbacks + let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace"); + let elParentMaterialNameNumber = getPropertyInputElement("submeshToReplace"); + let elParentMaterialNameCheckbox = getPropertyInputElement("selectSubmesh"); + elParentMaterialNameString.addEventListener('change', function () { + updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value, false); }); - - elCollideKinematic.addEventListener('change', function() { - updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideKinematic, 'kinematic'); + elParentMaterialNameNumber.addEventListener('change', function () { + updateProperty("parentMaterialName", this.value, false); }); - - elCollideStatic.addEventListener('change', function() { - updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideStatic, 'static'); - }); - elCollideMyAvatar.addEventListener('change', function() { - updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideMyAvatar, 'myAvatar'); - }); - elCollideOtherAvatar.addEventListener('change', function() { - updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideOtherAvatar, 'otherAvatar'); - }); - - - elGrabbable.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('grab', 'grabbable')); - elTriggerable.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('grab', 'triggerable')); - elGrabFollowsController.addEventListener('change', - createEmitGroupCheckedPropertyUpdateFunction('grab', 'grabFollowsController')); - - elCloneable.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneable')); - elCloneableDynamic.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneDynamic')); - elCloneableAvatarEntity.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneAvatarEntity')); - elCloneableLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneLifetime')); - elCloneableLimit.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneLimit')); - - elCollisionSoundURL.addEventListener('change', createEmitTextPropertyUpdateFunction('collisionSoundURL')); - - elLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifetime')); - elScriptURL.addEventListener('change', createEmitTextPropertyUpdateFunction('script')); - elScriptTimestamp.addEventListener('change', createEmitNumberPropertyUpdateFunction('scriptTimestamp')); - elServerScripts.addEventListener('change', createEmitTextPropertyUpdateFunction('serverScripts')); - elServerScripts.addEventListener('change', function() { - // invalidate the current status (so that same-same updates can still be observed visually) - elServerScriptStatus.innerText = PENDING_SCRIPT_STATUS; - }); - - elClearUserData.addEventListener("click", function() { - deleteJSONEditor(); - elUserData.value = ""; - showUserDataTextArea(); - showNewJSONEditorButton(); - hideSaveUserDataButton(); - updateProperty('userData', elUserData.value); - }); - - elSaveUserData.addEventListener("click", function() { - saveJSONUserData(true); - }); - - elUserData.addEventListener('change', createEmitTextPropertyUpdateFunction('userData')); - - elNewJSONEditor.addEventListener('click', function() { - deleteJSONEditor(); - createJSONEditor(); - var data = {}; - setEditorJSON(data); - hideUserDataTextArea(); - hideNewJSONEditorButton(); - showSaveUserDataButton(); - }); - - elClearMaterialData.addEventListener("click", function() { - deleteJSONMaterialEditor(); - elMaterialData.value = ""; - showMaterialDataTextArea(); - showNewJSONMaterialEditorButton(); - hideSaveMaterialDataButton(); - updateProperty('materialData', elMaterialData.value); - }); - - elSaveMaterialData.addEventListener("click", function() { - saveJSONMaterialData(true); - }); - - elMaterialData.addEventListener('change', createEmitTextPropertyUpdateFunction('materialData')); - - elNewJSONMaterialEditor.addEventListener('click', function() { - deleteJSONMaterialEditor(); - createJSONMaterialEditor(); - var data = {}; - setMaterialEditorJSON(data); - hideMaterialDataTextArea(); - hideNewJSONMaterialEditorButton(); - showSaveMaterialDataButton(); - }); - - var colorChangeFunction = createEmitColorPropertyUpdateFunction( - 'color', elColorRed, elColorGreen, elColorBlue); - elColorRed.addEventListener('change', colorChangeFunction); - elColorGreen.addEventListener('change', colorChangeFunction); - elColorBlue.addEventListener('change', colorChangeFunction); - colorPickers['#property-color-control2'] = $('#property-color-control2').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-color-control2').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-color-control2'].colpickSetColor({ - "r": elColorRed.value, - "g": elColorGreen.value, - "b": elColorBlue.value}); - }, - onHide: function(colpick) { - $('#property-color-control2').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); - } - }); - - elLightSpotLight.addEventListener('change', createEmitCheckedPropertyUpdateFunction('isSpotlight')); - - var lightColorChangeFunction = createEmitColorPropertyUpdateFunction( - 'color', elLightColorRed, elLightColorGreen, elLightColorBlue); - elLightColorRed.addEventListener('change', lightColorChangeFunction); - elLightColorGreen.addEventListener('change', lightColorChangeFunction); - elLightColorBlue.addEventListener('change', lightColorChangeFunction); - colorPickers['#property-light-color'] = $('#property-light-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-light-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-light-color'].colpickSetColor({ - "r": elLightColorRed.value, - "g": elLightColorGreen.value, - "b": elLightColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-light-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); - } - }); - - elLightIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('intensity', 1)); - elLightFalloffRadius.addEventListener('change', createEmitNumberPropertyUpdateFunction('falloffRadius', 1)); - elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent', 2)); - elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff', 2)); - - elShape.addEventListener('change', createEmitTextPropertyUpdateFunction('shape')); - - elCanCastShadow.addEventListener('change', createEmitCheckedPropertyUpdateFunction('canCastShadow')); - - elImageURL.addEventListener('change', createImageURLUpdateFunction('textures')); - - elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl')); - elWebDPI.addEventListener('change', createEmitNumberPropertyUpdateFunction('dpi', 0)); - - elModelURL.addEventListener('change', createEmitTextPropertyUpdateFunction('modelURL')); - elShapeType.addEventListener('change', createEmitTextPropertyUpdateFunction('shapeType')); - elCompoundShapeURL.addEventListener('change', createEmitTextPropertyUpdateFunction('compoundShapeURL')); - - elModelAnimationURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('animation', 'url')); - elModelAnimationPlaying.addEventListener('change',createEmitGroupCheckedPropertyUpdateFunction('animation', 'running')); - elModelAnimationFPS.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'fps')); - elModelAnimationFrame.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('animation', 'currentFrame')); - elModelAnimationFirstFrame.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('animation', 'firstFrame')); - elModelAnimationLastFrame.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('animation', 'lastFrame')); - elModelAnimationLoop.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'loop')); - elModelAnimationHold.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'hold')); - elModelAnimationAllowTranslation.addEventListener('change', - createEmitGroupCheckedPropertyUpdateFunction('animation', 'allowTranslation')); - - elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures')); - - elMaterialURL.addEventListener('change', createEmitTextPropertyUpdateFunction('materialURL')); - //elMaterialMappingMode.addEventListener('change', createEmitTextPropertyUpdateFunction('materialMappingMode')); - elPriority.addEventListener('change', createEmitNumberPropertyUpdateFunction('priority', 0)); - - elParentMaterialNameString.addEventListener('change', function () { updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); }); - elParentMaterialNameNumber.addEventListener('change', function () { updateProperty("parentMaterialName", this.value); }); elParentMaterialNameCheckbox.addEventListener('change', function () { if (this.checked) { - updateProperty("parentMaterialName", elParentMaterialNameNumber.value); + updateProperty("parentMaterialName", elParentMaterialNameNumber.value, false); showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString); } else { - updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + elParentMaterialNameString.value); + updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + elParentMaterialNameString.value, false); showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); } }); + + getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction('textures', false)); + + // Collapsible sections + let elCollapsible = document.getElementsByClassName("section-header"); - var materialMappingPosChangeFunction = createEmitVec2PropertyUpdateFunction('materialMappingPos', elMaterialMappingPosX, elMaterialMappingPosY); - elMaterialMappingPosX.addEventListener('change', materialMappingPosChangeFunction); - elMaterialMappingPosY.addEventListener('change', materialMappingPosChangeFunction); - var materialMappingScaleChangeFunction = createEmitVec2PropertyUpdateFunction('materialMappingScale', elMaterialMappingScaleX, elMaterialMappingScaleY); - elMaterialMappingScaleX.addEventListener('change', materialMappingScaleChangeFunction); - elMaterialMappingScaleY.addEventListener('change', materialMappingScaleChangeFunction); - elMaterialMappingRot.addEventListener('change', createEmitNumberPropertyUpdateFunction('materialMappingRot', 2)); + let toggleCollapsedEvent = function(event) { + let element = event.target.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"; + }; - elTextText.addEventListener('change', createEmitTextPropertyUpdateFunction('text')); - elTextFaceCamera.addEventListener('change', createEmitCheckedPropertyUpdateFunction('faceCamera')); - elTextLineHeight.addEventListener('change', createEmitNumberPropertyUpdateFunction('lineHeight')); - var textTextColorChangeFunction = createEmitColorPropertyUpdateFunction( - 'textColor', elTextTextColorRed, elTextTextColorGreen, elTextTextColorBlue); - elTextTextColorRed.addEventListener('change', textTextColorChangeFunction); - elTextTextColorGreen.addEventListener('change', textTextColorChangeFunction); - elTextTextColorBlue.addEventListener('change', textTextColorChangeFunction); - colorPickers['#property-text-text-color'] = $('#property-text-text-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-text-text-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-text-text-color'].colpickSetColor({ - "r": elTextTextColorRed.value, - "g": elTextTextColorGreen.value, - "b": elTextTextColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-text-text-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).attr('active', 'false'); - emitColorPropertyUpdate('textColor', rgb.r, rgb.g, rgb.b); + for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { + let curCollapsibleElement = elCollapsible[collapseIndex]; + curCollapsibleElement.getElementsByTagName('span')[0].addEventListener("click", toggleCollapsedEvent, true); + } + + // Textarea scrollbars + let elTextareas = document.getElementsByTagName("TEXTAREA"); + + let textareaOnChangeEvent = function(event) { + setTextareaScrolling(event.target); + }; + + for (let textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) { + let curTextAreaElement = elTextareas[textAreaIndex]; + setTextareaScrolling(curTextAreaElement); + curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false); + curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false); + /* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize + event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ + curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); + } + + // Dropdowns + // For each dropdown the following replacement is created in place of the original dropdown... + // Structure created: + // <dl dropped="true/false"> + // <dt name="?" id="?" value="?"><span>display text</span><span>carat</span></dt> + // <dd> + // <ul> + // <li value="??>display text</li> + // <li>...</li> + // </ul> + // </dd> + // </dl> + let elDropdowns = document.getElementsByTagName("select"); + for (let dropDownIndex = 0; dropDownIndex < elDropdowns.length; ++dropDownIndex) { + let elDropdown = elDropdowns[dropDownIndex]; + let options = elDropdown.getElementsByTagName("option"); + let selectedOption = 0; + for (let optionIndex = 0; optionIndex < options.length; ++optionIndex) { + if (options[optionIndex].getAttribute("selected") === "selected") { + selectedOption = optionIndex; + break; + } } - }); + let div = elDropdown.parentNode; - var textBackgroundColorChangeFunction = createEmitColorPropertyUpdateFunction( - 'backgroundColor', elTextBackgroundColorRed, elTextBackgroundColorGreen, elTextBackgroundColorBlue); + let dl = document.createElement("dl"); + div.appendChild(dl); - elTextBackgroundColorRed.addEventListener('change', textBackgroundColorChangeFunction); - elTextBackgroundColorGreen.addEventListener('change', textBackgroundColorChangeFunction); - elTextBackgroundColorBlue.addEventListener('change', textBackgroundColorChangeFunction); - colorPickers['#property-text-background-color'] = $('#property-text-background-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-text-background-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-text-background-color'].colpickSetColor({ - "r": elTextBackgroundColorRed.value, - "g": elTextBackgroundColorGreen.value, - "b": elTextBackgroundColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-text-background-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('backgroundColor', rgb.r, rgb.g, rgb.b); + let dt = document.createElement("dt"); + dt.name = elDropdown.name; + dt.id = elDropdown.id; + dt.addEventListener("click", toggleDropdown, true); + dl.appendChild(dt); + + let span = document.createElement("span"); + span.setAttribute("value", options[selectedOption].value); + span.textContent = options[selectedOption].firstChild.textContent; + dt.appendChild(span); + + let spanCaratDown = document.createElement("span"); + spanCaratDown.textContent = "5"; // caratDn + dt.appendChild(spanCaratDown); + + let dd = document.createElement("dd"); + dl.appendChild(dd); + + let ul = document.createElement("ul"); + dd.appendChild(ul); + + for (let listOptionIndex = 0; listOptionIndex < options.length; ++listOptionIndex) { + let li = document.createElement("li"); + li.setAttribute("value", options[listOptionIndex].value); + li.textContent = options[listOptionIndex].firstChild.textContent; + li.addEventListener("click", setDropdownValue); + ul.appendChild(li); } - }); - - // Key light - var keyLightModeChanged = createZoneComponentModeChangedFunction('keyLightMode', - elZoneKeyLightModeInherit, elZoneKeyLightModeDisabled, elZoneKeyLightModeEnabled); - - elZoneKeyLightModeInherit.addEventListener('change', keyLightModeChanged); - elZoneKeyLightModeDisabled.addEventListener('change', keyLightModeChanged); - elZoneKeyLightModeEnabled.addEventListener('change', keyLightModeChanged); - - colorPickers['#property-zone-key-light-color'] = $('#property-zone-key-light-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-zone-key-light-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-zone-key-light-color'].colpickSetColor({ - "r": elZoneKeyLightColorRed.value, - "g": elZoneKeyLightColorGreen.value, - "b": elZoneKeyLightColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-zone-key-light-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'keyLight'); - } - }); - var zoneKeyLightColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('keyLight', 'color', - elZoneKeyLightColorRed, elZoneKeyLightColorGreen, elZoneKeyLightColorBlue); - - elZoneKeyLightColorRed.addEventListener('change', zoneKeyLightColorChangeFunction); - elZoneKeyLightColorGreen.addEventListener('change', zoneKeyLightColorChangeFunction); - elZoneKeyLightColorBlue.addEventListener('change', zoneKeyLightColorChangeFunction); - elZoneKeyLightIntensity.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('keyLight', 'intensity')); - - var zoneKeyLightDirectionChangeFunction = createEmitGroupVec3PropertyUpdateFunction('keyLight', 'direction', - elZoneKeyLightDirectionX, elZoneKeyLightDirectionY); - - elZoneKeyLightDirectionX.addEventListener('change', zoneKeyLightDirectionChangeFunction); - elZoneKeyLightDirectionY.addEventListener('change', zoneKeyLightDirectionChangeFunction); - - elZoneKeyLightCastShadows.addEventListener('change', - createEmitGroupCheckedPropertyUpdateFunction('keyLight', 'castShadows')); - - // Skybox - var skyboxModeChanged = createZoneComponentModeChangedFunction('skyboxMode', - elZoneSkyboxModeInherit, elZoneSkyboxModeDisabled, elZoneSkyboxModeEnabled); - - elZoneSkyboxModeInherit.addEventListener('change', skyboxModeChanged); - elZoneSkyboxModeDisabled.addEventListener('change', skyboxModeChanged); - elZoneSkyboxModeEnabled.addEventListener('change', skyboxModeChanged); - - // Ambient light - elCopySkyboxURLToAmbientURL.addEventListener("click", function () { - document.getElementById("property-zone-key-ambient-url").value = properties.skybox.url; - properties.ambientLight.ambientURL = properties.skybox.url; - updateProperties(properties); - }); - - var ambientLightModeChanged = createZoneComponentModeChangedFunction('ambientLightMode', - elZoneAmbientLightModeInherit, elZoneAmbientLightModeDisabled, elZoneAmbientLightModeEnabled); - - elZoneAmbientLightModeInherit.addEventListener('change', ambientLightModeChanged); - elZoneAmbientLightModeDisabled.addEventListener('change', ambientLightModeChanged); - elZoneAmbientLightModeEnabled.addEventListener('change', ambientLightModeChanged); - - elZoneAmbientLightIntensity.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('ambientLight', 'ambientIntensity')); - - elZoneAmbientLightURL.addEventListener('change', - createEmitGroupTextPropertyUpdateFunction('ambientLight', 'ambientURL')); - - // Haze - var hazeModeChanged = createZoneComponentModeChangedFunction('hazeMode', - elZoneHazeModeInherit, elZoneHazeModeDisabled, elZoneHazeModeEnabled); - - elZoneHazeModeInherit.addEventListener('change', hazeModeChanged); - elZoneHazeModeDisabled.addEventListener('change', hazeModeChanged); - elZoneHazeModeEnabled.addEventListener('change', hazeModeChanged); - - elZoneHazeRange.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeRange')); - - colorPickers['#property-zone-haze-color'] = $('#property-zone-haze-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-zone-haze-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-zone-haze-color'].colpickSetColor({ - "r": elZoneHazeColorRed.value, - "g": elZoneHazeColorGreen.value, - "b": elZoneHazeColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-zone-haze-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('hazeColor', rgb.r, rgb.g, rgb.b, 'haze'); - } - }); - var zoneHazeColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('haze', 'hazeColor', - elZoneHazeColorRed, - elZoneHazeColorGreen, - elZoneHazeColorBlue); - - elZoneHazeColorRed.addEventListener('change', zoneHazeColorChangeFunction); - elZoneHazeColorGreen.addEventListener('change', zoneHazeColorChangeFunction); - elZoneHazeColorBlue.addEventListener('change', zoneHazeColorChangeFunction); - - colorPickers['#property-zone-haze-glare-color'] = $('#property-zone-haze-glare-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-zone-haze-glare-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-zone-haze-glare-color'].colpickSetColor({ - "r": elZoneHazeGlareColorRed.value, - "g": elZoneHazeGlareColorGreen.value, - "b": elZoneHazeGlareColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-zone-haze-glare-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('hazeGlareColor', rgb.r, rgb.g, rgb.b, 'haze'); - } - }); - var zoneHazeGlareColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('haze', 'hazeGlareColor', - elZoneHazeGlareColorRed, - elZoneHazeGlareColorGreen, - elZoneHazeGlareColorBlue); - - elZoneHazeGlareColorRed.addEventListener('change', zoneHazeGlareColorChangeFunction); - elZoneHazeGlareColorGreen.addEventListener('change', zoneHazeGlareColorChangeFunction); - elZoneHazeGlareColorBlue.addEventListener('change', zoneHazeGlareColorChangeFunction); - - elZoneHazeEnableGlare.addEventListener('change', - createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeEnableGlare')); - elZoneHazeGlareAngle.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeGlareAngle')); - - elZoneHazeAltitudeEffect.addEventListener('change', - createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeAltitudeEffect')); - elZoneHazeCeiling.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeCeiling')); - elZoneHazeBaseRef.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBaseRef')); - - elZoneHazeBackgroundBlend.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBackgroundBlend')); - - // Bloom - var bloomModeChanged = createZoneComponentModeChangedFunction('bloomMode', - elZoneBloomModeInherit, elZoneBloomModeDisabled, elZoneBloomModeEnabled); - - elZoneBloomModeInherit.addEventListener('change', bloomModeChanged); - elZoneBloomModeDisabled.addEventListener('change', bloomModeChanged); - elZoneBloomModeEnabled.addEventListener('change', bloomModeChanged); - - elZoneBloomIntensity.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('bloom', 'bloomIntensity')); - elZoneBloomThreshold.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('bloom', 'bloomThreshold')); - elZoneBloomSize.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('bloom', 'bloomSize')); - - var zoneSkyboxColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('skybox', 'color', - elZoneSkyboxColorRed, elZoneSkyboxColorGreen, elZoneSkyboxColorBlue); - elZoneSkyboxColorRed.addEventListener('change', zoneSkyboxColorChangeFunction); - elZoneSkyboxColorGreen.addEventListener('change', zoneSkyboxColorChangeFunction); - elZoneSkyboxColorBlue.addEventListener('change', zoneSkyboxColorChangeFunction); - colorPickers['#property-zone-skybox-color'] = $('#property-zone-skybox-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-zone-skybox-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-zone-skybox-color'].colpickSetColor({ - "r": elZoneSkyboxColorRed.value, - "g": elZoneSkyboxColorGreen.value, - "b": elZoneSkyboxColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-zone-skybox-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'skybox'); - } - }); - - elZoneSkyboxURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('skybox', 'url')); - - elZoneFlyingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('flyingAllowed')); - elZoneGhostingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ghostingAllowed')); - elZoneFilterURL.addEventListener('change', createEmitTextPropertyUpdateFunction('filterURL')); - - var voxelVolumeSizeChangeFunction = createEmitVec3PropertyUpdateFunction( - 'voxelVolumeSize', elVoxelVolumeSizeX, elVoxelVolumeSizeY, elVoxelVolumeSizeZ); - elVoxelVolumeSizeX.addEventListener('change', voxelVolumeSizeChangeFunction); - elVoxelVolumeSizeY.addEventListener('change', voxelVolumeSizeChangeFunction); - elVoxelVolumeSizeZ.addEventListener('change', voxelVolumeSizeChangeFunction); - elVoxelSurfaceStyle.addEventListener('change', createEmitTextPropertyUpdateFunction('voxelSurfaceStyle')); - elXTextureURL.addEventListener('change', createEmitTextPropertyUpdateFunction('xTextureURL')); - elYTextureURL.addEventListener('change', createEmitTextPropertyUpdateFunction('yTextureURL')); - elZTextureURL.addEventListener('change', createEmitTextPropertyUpdateFunction('zTextureURL')); - - elMoveSelectionToGrid.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveSelectionToGrid" - })); - }); - elMoveAllToGrid.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveAllToGrid" - })); - }); - elResetToNaturalDimensions.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "resetToNaturalDimensions" - })); - }); - elRescaleDimensionsButton.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "rescaleDimensions", - percentage: parseFloat(elRescaleDimensionsPct.value) - })); - }); - elReloadScriptsButton.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadClientScripts" - })); - }); - elReloadServerScriptsButton.addEventListener("click", function() { - // invalidate the current status (so that same-same updates can still be observed visually) - elServerScriptStatus.innerText = PENDING_SCRIPT_STATUS; - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadServerScripts" - })); - }); - + + let propertyID = elDropdown.getAttribute("propertyID"); + let property = properties[propertyID]; + property.elInput = dt; + dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property.name, property.isParticleProperty)); + } + + elDropdowns = document.getElementsByTagName("select"); + while (elDropdowns.length > 0) { + let el = elDropdowns[0]; + el.parentNode.removeChild(el); + elDropdowns = document.getElementsByTagName("select"); + } + document.addEventListener("keydown", function (keyDown) { if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) { if (keyDown.shiftKey) { @@ -2147,156 +3370,29 @@ function loaded() { } } }); + window.onblur = function() { // Fake a change event - var ev = document.createEvent("HTMLEvents"); + let ev = document.createEvent("HTMLEvents"); ev.initEvent("change", true, true); document.activeElement.dispatchEvent(ev); }; - + // For input and textarea elements, select all of the text on focus - var els = document.querySelectorAll("input, textarea"); - for (var i = 0; i < els.length; i++) { + let els = document.querySelectorAll("input, textarea"); + for (let i = 0; i < els.length; ++i) { els[i].onfocus = function (e) { e.target.select(); }; } + + bindAllNonJSONEditorElements(); - bindAllNonJSONEditorElements(); + showGroupsForType("None"); + resetProperties(); + disableProperties(); }); - // Collapsible sections - var elCollapsible = document.getElementsByClassName("section-header"); - - var toggleCollapsedEvent = function(event) { - var element = event.target.parentNode.parentNode; - var 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"; - }; - - for (var collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { - var curCollapsibleElement = elCollapsible[collapseIndex]; - curCollapsibleElement.getElementsByTagName('span')[0].addEventListener("click", toggleCollapsedEvent, true); - } - - - // Textarea scrollbars - var elTextareas = document.getElementsByTagName("TEXTAREA"); - - var textareaOnChangeEvent = function(event) { - setTextareaScrolling(event.target); - }; - - for (var textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) { - var curTextAreaElement = elTextareas[textAreaIndex]; - setTextareaScrolling(curTextAreaElement); - curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false); - curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false); - /* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize - event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ - curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); - } - - // Dropdowns - // For each dropdown the following replacement is created in place of the original dropdown... - // Structure created: - // <dl dropped="true/false"> - // <dt name="?" id="?" value="?"><span>display text</span><span>carat</span></dt> - // <dd> - // <ul> - // <li value="??>display text</li> - // <li>...</li> - // </ul> - // </dd> - // </dl> - - function setDropdownText(dropdown) { - var lis = dropdown.parentNode.getElementsByTagName("li"); - var text = ""; - for (var i = 0; i < lis.length; i++) { - if (String(lis[i].getAttribute("value")) === String(dropdown.value)) { - text = lis[i].textContent; - } - } - dropdown.firstChild.textContent = text; - } - - function toggleDropdown(event) { - var element = event.target; - if (element.nodeName !== "DT") { - element = element.parentNode; - } - element = element.parentNode; - var isDropped = element.getAttribute("dropped"); - element.setAttribute("dropped", isDropped !== "true" ? "true" : "false"); - } - - function setDropdownValue(event) { - var dt = event.target.parentNode.parentNode.previousSibling; - dt.value = event.target.getAttribute("value"); - dt.firstChild.textContent = event.target.textContent; - - dt.parentNode.setAttribute("dropped", "false"); - - var evt = document.createEvent("HTMLEvents"); - evt.initEvent("change", true, true); - dt.dispatchEvent(evt); - } - - var elDropdowns = document.getElementsByTagName("select"); - for (var dropDownIndex = 0; dropDownIndex < elDropdowns.length; ++dropDownIndex) { - var options = elDropdowns[dropDownIndex].getElementsByTagName("option"); - var selectedOption = 0; - for (var optionIndex = 0; optionIndex < options.length; ++optionIndex) { - if (options[optionIndex].getAttribute("selected") === "selected") { - selectedOption = optionIndex; - // TODO: Shouldn't there be a break here? - } - } - var div = elDropdowns[dropDownIndex].parentNode; - - var dl = document.createElement("dl"); - div.appendChild(dl); - - var dt = document.createElement("dt"); - dt.name = elDropdowns[dropDownIndex].name; - dt.id = elDropdowns[dropDownIndex].id; - dt.addEventListener("click", toggleDropdown, true); - dl.appendChild(dt); - - var span = document.createElement("span"); - span.setAttribute("value", options[selectedOption].value); - span.textContent = options[selectedOption].firstChild.textContent; - dt.appendChild(span); - - var spanCaratDown = document.createElement("span"); - spanCaratDown.textContent = "5"; // caratDn - dt.appendChild(spanCaratDown); - - var dd = document.createElement("dd"); - dl.appendChild(dd); - - var ul = document.createElement("ul"); - dd.appendChild(ul); - - for (var listOptionIndex = 0; listOptionIndex < options.length; ++listOptionIndex) { - var li = document.createElement("li"); - li.setAttribute("value", options[listOptionIndex].value); - li.textContent = options[listOptionIndex].firstChild.textContent; - li.addEventListener("click", setDropdownValue); - ul.appendChild(li); - } - } - - elDropdowns = document.getElementsByTagName("select"); - while (elDropdowns.length > 0) { - var el = elDropdowns[0]; - el.parentNode.removeChild(el); - elDropdowns = document.getElementsByTagName("select"); - } - augmentSpinButtons(); // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked diff --git a/scripts/system/html/js/listView.js b/scripts/system/html/js/listView.js index 10dc37efba..62163ffe16 100644 --- a/scripts/system/html/js/listView.js +++ b/scripts/system/html/js/listView.js @@ -38,7 +38,7 @@ function ListView(elTableBody, elTableScroll, elTableHeaderRow, createRowFunctio this.lastRowShiftScrollTop = 0; this.initialize(); -}; +} ListView.prototype = { getNumRows: function() { @@ -152,6 +152,30 @@ ListView.prototype = { this.refresh(); } }, + + /** + * Scrolls firstRowIndex with least effort, also tries to make the window include the other selections in case lastRowIndex is set. + * In the case that firstRowIndex and lastRowIndex are already within the visible bounds then nothing will happen. + * @param {number} firstRowIndex - The row that will be scrolled to. + * @param {number} lastRowIndex - The last index of the bound. + */ + scrollToRow: function (firstRowIndex, lastRowIndex) { + lastRowIndex = lastRowIndex ? lastRowIndex : firstRowIndex; + let boundingTop = firstRowIndex * this.rowHeight; + let boundingBottom = (lastRowIndex * this.rowHeight) + this.rowHeight; + if ((boundingBottom - boundingTop) > this.elTableScroll.clientHeight) { + boundingBottom = boundingTop + this.elTableScroll.clientHeight; + } + + let currentVisibleAreaTop = this.elTableScroll.scrollTop; + let currentVisibleAreaBottom = currentVisibleAreaTop + this.elTableScroll.clientHeight; + + if (boundingTop < currentVisibleAreaTop) { + this.elTableScroll.scrollTop = boundingTop; + } else if (boundingBottom > currentVisibleAreaBottom) { + this.elTableScroll.scrollTop = boundingBottom - (this.elTableScroll.clientHeight); + } + }, refresh: function() { // block refreshing before rows are initialized diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 24a96023da..28451a14cb 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -27,6 +27,7 @@ var xmlHttpRequest = null; var isPreparing = false; // Explicitly track download request status. + var limitedCommerce = false; var commerceMode = false; var userIsLoggedIn = false; var walletNeedsSetup = false; @@ -59,7 +60,7 @@ ); // Footer. - var isInitialHiFiPage = location.href === marketplaceBaseURL + "/marketplace?"; + var isInitialHiFiPage = location.href === (marketplaceBaseURL + "/marketplace?"); $("body").append( '<div id="marketplace-navigation">' + (!isInitialHiFiPage ? '<input id="back-button" type="button" class="white" value="< Back" />' : '') + @@ -92,7 +93,7 @@ window.location = "https://clara.io/library?gameCheck=true&public=true"; }); $('#exploreHifiMarketplace').on('click', function () { - window.location = marketplaceBaseURL + "/marketplace"; + window.location = marketplaceBaseURL + "/marketplace?"; }); } @@ -169,7 +170,7 @@ var span = document.createElement('span'); span.style = "margin:10px;color:#1b6420;font-size:15px;"; - span.innerHTML = "to purchase items from the Marketplace."; + span.innerHTML = "to get items from the Marketplace."; var xButton = document.createElement('a'); xButton.id = "xButton"; @@ -195,40 +196,6 @@ } } - function maybeAddPurchasesButton() { - if (userIsLoggedIn) { - // Why isn't this an id?! This really shouldn't be a class on the website, but it is. - var navbarBrandElement = document.getElementsByClassName('navbar-brand')[0]; - var purchasesElement = document.createElement('a'); - var dropDownElement = document.getElementById('user-dropdown'); - - $('#user-dropdown').find('.username')[0].style = "max-width:80px;white-space:nowrap;overflow:hidden;" + - "text-overflow:ellipsis;display:inline-block;position:relative;top:4px;"; - $('#user-dropdown').find('.caret')[0].style = "position:relative;top:-3px;"; - - purchasesElement.id = "purchasesButton"; - purchasesElement.setAttribute('href', "#"); - purchasesElement.innerHTML = ""; - if (messagesWaiting) { - purchasesElement.innerHTML += "<span style='width:10px;height:10px;background-color:red;border-radius:50%;display:inline-block;'></span> "; - } - purchasesElement.innerHTML += "My Purchases"; - // FRONTEND WEBDEV RANT: The username dropdown should REALLY not be programmed to be on the same - // line as the search bar, overlaid on top of the search bar, floated right, and then relatively bumped up using "top:-50px". - $('.navbar-brand').css('margin-right', '10px'); - purchasesElement.style = "height:100%;margin-top:18px;font-weight:bold;float:right;margin-right:" + (dropDownElement.offsetWidth + 30) + - "px;position:relative;z-index:999;"; - navbarBrandElement.parentNode.insertAdjacentElement('beforeend', purchasesElement); - $('#purchasesButton').on('click', function () { - EventBridge.emitWebEvent(JSON.stringify({ - type: "PURCHASES", - referrerURL: window.location.href, - hasUpdates: messagesWaiting - })); - }); - } - } - function changeDropdownMenu() { var logInOrOutButton = document.createElement('a'); logInOrOutButton.id = "logInOrOutButton"; @@ -283,6 +250,7 @@ $(this).attr('href', '#'); } cost = $(this).closest('.col-xs-3').find('.item-cost').text(); + var costInt = parseInt(cost, 10); $(this).closest('.col-xs-3').prev().attr("class", 'col-xs-6'); $(this).closest('.col-xs-3').attr("class", 'col-xs-6'); @@ -312,11 +280,12 @@ var getString = "GET"; // Protection against the button getting stuck in the "BUY"/"GET" state. // That happens when the browser gets two MOUSEENTER events before getting a - // MOUSELEAVE event. - if ($this.text() === buyString || $this.text() === getString) { - return; - } - if ($this.text() === 'invalidated') { + // MOUSELEAVE event. Also, if not available for sale, just return. + if ($this.text() === buyString || + $this.text() === getString || + $this.text() === 'invalidated' || + $this.text() === 'sold out' || + $this.text() === 'not for sale' ) { return; } $this.data('initialHtml', $this.html()); @@ -337,7 +306,10 @@ $('.grid-item').find('#price-or-edit').find('a').on('click', function () { - if ($(this).closest('.grid-item').find('.price').text() === 'invalidated') { + var price = $(this).closest('.grid-item').find('.price').text(); + if (price === 'invalidated' || + price === 'sold out' || + price === 'not for sale') { return false; } buyButtonClicked($(this).closest('.grid-item').attr('data-item-id'), @@ -398,7 +370,6 @@ // Try this here in case it works (it will if the user just pressed the "back" button, // since that doesn't trigger another AJAX request. injectBuyButtonOnMainPage(); - maybeAddPurchasesButton(); } } @@ -419,7 +390,12 @@ var href = purchaseButton.attr('href'); purchaseButton.attr('href', '#'); + var cost = $('.item-cost').text(); + var costInt = parseInt(cost, 10); var availability = $.trim($('.item-availability').text()); + if (limitedCommerce && (costInt > 0)) { + availability = ''; + } if (availability === 'available') { purchaseButton.css({ "background": "linear-gradient(#00b4ef, #0093C5)", @@ -436,14 +412,13 @@ }); } - var cost = $('.item-cost').text(); var type = $('.item-type').text(); var isUpdating = window.location.href.indexOf('edition=') > -1; var urlParams = new URLSearchParams(window.location.search); if (isUpdating) { purchaseButton.html('UPDATE FOR FREE'); } else if (availability !== 'available') { - purchaseButton.html('UNAVAILABLE (' + availability + ')'); + purchaseButton.html('UNAVAILABLE ' + (availability ? ('(' + availability + ')') : '')); } else if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { purchaseButton.html('PURCHASE <span class="hifi-glyph hifi-glyph-hfc" style="filter:invert(1);background-size:20px;' + 'width:20px;height:20px;position:relative;top:5px;"></span> ' + cost); @@ -461,7 +436,6 @@ type); } }); - maybeAddPurchasesButton(); } } @@ -742,6 +716,7 @@ cancelClaraDownload(); } else if (message.type === "marketplaces") { if (message.action === "commerceSetting") { + limitedCommerce = !!message.data.limitedCommerce; commerceMode = !!message.data.commerceMode; userIsLoggedIn = !!message.data.userIsLoggedIn; walletNeedsSetup = !!message.data.walletNeedsSetup; diff --git a/scripts/system/particle_explorer/underscore-min.js b/scripts/system/html/js/underscore-min.js similarity index 100% rename from scripts/system/particle_explorer/underscore-min.js rename to scripts/system/html/js/underscore-min.js diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index 4f73d8e598..e2db032d8c 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -16,16 +16,17 @@ Script.include("/~/system/libraries/Xform.js"); Script.include("/~/system/libraries/globals.js"); var DEBUG = false; - var MIN_LOADING_PROGRESS = 3.6; - var TOTAL_LOADING_PROGRESS = 3.8; - var EPSILON = 0.01; + var TOTAL_LOADING_PROGRESS = 3.7; + var EPSILON = 0.05; + var TEXTURE_EPSILON = 0.01; var isVisible = false; var VOLUME = 0.4; - var tune = SoundCache.getSound("http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/crystals_and_voices.wav"); + var tune = SoundCache.getSound(Script.resolvePath("/~/system/assets/sounds/crystals_and_voices.mp3")); var sample = null; var MAX_LEFT_MARGIN = 1.9; var INNER_CIRCLE_WIDTH = 4.7; var DEFAULT_Z_OFFSET = 5.45; + var LOADING_IMAGE_WIDTH_PIXELS = 1024; var previousCameraMode = Camera.mode; var renderViewTask = Render.getConfig("RenderMainView"); @@ -63,9 +64,9 @@ var loadingSphereID = Overlays.addOverlay("model", { name: "Loading-Sphere", - position: Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0.0, y: -1.0, z: 0.0}), Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.95, z: 0})), - orientation: Quat.multiply(Quat.fromVec3Degrees({x: 0, y: 180, z: 0}), MyAvatar.orientation), - url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/black-sphere.fbx", + position: Vec3.sum(Vec3.sum(MyAvatar.position, { x: 0.0, y: -1.0, z: 0.0 }), Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.95, z: 0 })), + orientation: Quat.multiply(Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), MyAvatar.orientation), + url: Script.resolvePath("/~/system/assets/models/black-sphere.fbx"), dimensions: DEFAULT_DIMENSIONS, alpha: 1, visible: isVisible, @@ -76,12 +77,12 @@ }); var anchorOverlay = Overlays.addOverlay("cube", { - dimensions: {x: 0.2, y: 0.2, z: 0.2}, + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, visible: false, grabbable: false, ignoreRayIntersection: true, - localPosition: {x: 0.0, y: getAnchorLocalYOffset(), z: DEFAULT_Z_OFFSET }, - orientation: Quat.multiply(Quat.fromVec3Degrees({x: 0, y: 180, z: 0}), MyAvatar.orientation), + localPosition: { x: 0.0, y: getAnchorLocalYOffset(), z: DEFAULT_Z_OFFSET }, + orientation: Quat.multiply(Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), MyAvatar.orientation), solid: true, drawInFront: true, parentID: loadingSphereID @@ -113,7 +114,6 @@ backgroundAlpha: 1, lineHeight: 0.13, visible: isVisible, - backgroundAlpha: 0, ignoreRayIntersection: true, drawInFront: true, grabbable: false, @@ -125,7 +125,7 @@ var domainToolTip = Overlays.addOverlay("text3d", { name: "Loading-Tooltip", - localPosition: { x: 0.0 , y: -1.6, z: 0.0 }, + localPosition: { x: 0.0, y: -1.6, z: 0.0 }, text: toolTip, textAlpha: 1, backgroundAlpha: 0.00393, @@ -140,14 +140,14 @@ var loadingToTheSpotText = Overlays.addOverlay("text3d", { name: "Loading-Destination-Card-Text", - localPosition: { x: 0.0 , y: -1.687, z: -0.3 }, + localPosition: { x: 0.0, y: -1.687, z: -0.3 }, text: "Go To TheSpot", textAlpha: 1, backgroundAlpha: 0.00393, lineHeight: 0.10, visible: isVisible, ignoreRayIntersection: true, - dimensions: {x: 1, y: 0.17}, + dimensions: { x: 1, y: 0.17 }, drawInFront: true, grabbable: false, localOrientation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), @@ -156,7 +156,7 @@ var loadingToTheSpotID = Overlays.addOverlay("image3d", { name: "Loading-Destination-Card-GoTo-Image", - localPosition: { x: 0.0 , y: -1.75, z: -0.3 }, + localPosition: { x: 0.0, y: -1.75, z: -0.3 }, url: Script.resourcesPath() + "images/interstitialPage/button.png", alpha: 1, visible: isVisible, @@ -170,7 +170,7 @@ var loadingToTheSpotHoverID = Overlays.addOverlay("image3d", { name: "Loading-Destination-Card-GoTo-Image-Hover", - localPosition: { x: 0.0 , y: -1.75, z: -0.3 }, + localPosition: { x: 0.0, y: -1.75, z: -0.3 }, url: Script.resourcesPath() + "images/interstitialPage/button_hover.png", alpha: 1, visible: false, @@ -182,27 +182,29 @@ parentID: anchorOverlay }); - var loadingBarPlacard = Overlays.addOverlay("image3d", { - name: "Loading-Bar-Placard", - localPosition: { x: 0.0, y: -0.99, z: 0.3 }, - url: Script.resourcesPath() + "images/loadingBar_placard.png", - alpha: 1, - dimensions: { x: 4, y: 2.8}, - visible: isVisible, - emissive: true, - ignoreRayIntersection: false, - drawInFront: true, - grabbable: false, - localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), - parentID: anchorOverlay - }); var loadingBarProgress = Overlays.addOverlay("image3d", { name: "Loading-Bar-Progress", - localPosition: { x: 0.0, y: -0.90, z: 0.0 }, + localPosition: { x: 0.0, y: -0.86, z: 0.0 }, url: Script.resourcesPath() + "images/loadingBar_progress.png", alpha: 1, - dimensions: {x: 3.8, y: 2.8}, + dimensions: { x: TOTAL_LOADING_PROGRESS, y: 0.3}, + visible: isVisible, + emissive: true, + ignoreRayIntersection: false, + drawInFront: true, + grabbable: false, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), + parentID: anchorOverlay, + keepAspectRatio: false + }); + + var loadingBarPlacard = Overlays.addOverlay("image3d", { + name: "Loading-Bar-Placard", + localPosition: { x: 0.0, y: -0.99, z: 0.4 }, + url: Script.resourcesPath() + "images/loadingBar_placard.png", + alpha: 1, + dimensions: { x: 4, y: 2.8 }, visible: isVisible, emissive: true, ignoreRayIntersection: false, @@ -212,7 +214,7 @@ parentID: anchorOverlay }); - var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update + var TARGET_UPDATE_HZ = 30; var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; var lastInterval = Date.now(); var currentDomain = "no domain"; @@ -245,15 +247,7 @@ } function resetValues() { - var properties = { - localPosition: { x: 1.85, y: -0.935, z: 0.0 }, - dimensions: { - x: 0.1, - y: 2.8 - } - }; - - Overlays.editOverlay(loadingBarProgress, properties); + updateProgressBar(0.0); } function startInterstitialPage() { @@ -263,11 +257,12 @@ target = 0; textureMemSizeStabilityCount = 0; textureMemSizeAtLastCheck = 0; - currentProgress = 0.1; + currentProgress = 0.0; connectionToDomainFailed = false; previousCameraMode = Camera.mode; Camera.mode = "first person"; - timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); + updateProgressBar(0.0); + timer = Script.setTimeout(update, 2000); } } @@ -348,12 +343,12 @@ } } - var THE_PLACE = (HifiAbout.buildVersion === "dev") ? "hifi://TheSpot-dev": "hifi://TheSpot"; + var THE_PLACE = (HifiAbout.buildVersion === "dev") ? "hifi://TheSpot-dev" : "hifi://TheSpot"; function clickedOnOverlay(overlayID, event) { if (loadingToTheSpotHoverID === overlayID) { location.handleLookupString(THE_PLACE); - Overlays.editOverlay(loadingToTheSpotHoverID, {visible: false}); - Overlays.editOverlay(loadingToTheSpotID, {visible: true}); + Overlays.editOverlay(loadingToTheSpotHoverID, { visible: false }); + Overlays.editOverlay(loadingToTheSpotID, { visible: true }); } } @@ -362,8 +357,8 @@ return; } if (overlayID === loadingToTheSpotID) { - Overlays.editOverlay(loadingToTheSpotID, {visible: false}); - Overlays.editOverlay(loadingToTheSpotHoverID, {visible: true}); + Overlays.editOverlay(loadingToTheSpotID, { visible: false }); + Overlays.editOverlay(loadingToTheSpotHoverID, { visible: true }); } } @@ -372,18 +367,18 @@ return; } if (overlayID === loadingToTheSpotHoverID) { - Overlays.editOverlay(loadingToTheSpotHoverID, {visible: false}); - Overlays.editOverlay(loadingToTheSpotID, {visible: true}); + Overlays.editOverlay(loadingToTheSpotHoverID, { visible: false }); + Overlays.editOverlay(loadingToTheSpotID, { visible: true }); } } - var currentProgress = 0.1; + var currentProgress = 0.0; function updateOverlays(physicsEnabled) { if (isInterstitialOverlaysVisible !== !physicsEnabled && !physicsEnabled === true) { - // visible changed to true. - isInterstitialOverlaysVisible = !physicsEnabled; + // visible changed to true. + isInterstitialOverlaysVisible = !physicsEnabled; } var properties = { @@ -400,7 +395,6 @@ }; var loadingBarProperties = { - dimensions: { x: 0.0, y: 2.8 }, visible: !physicsEnabled }; @@ -434,8 +428,8 @@ } if (isInterstitialOverlaysVisible !== !physicsEnabled && !physicsEnabled === false) { - // visible changed to false. - isInterstitialOverlaysVisible = !physicsEnabled; + // visible changed to false. + isInterstitialOverlaysVisible = !physicsEnabled; } } @@ -453,12 +447,40 @@ function sleep(milliseconds) { var start = new Date().getTime(); for (var i = 0; i < 1e7; i++) { - if ((new Date().getTime() - start) > milliseconds){ + if ((new Date().getTime() - start) > milliseconds) { break; } } } + function updateProgressBar(progress) { + var progressPercentage = progress / TOTAL_LOADING_PROGRESS; + var subImageWidth = progressPercentage * LOADING_IMAGE_WIDTH_PIXELS; + + var start = TOTAL_LOADING_PROGRESS / 2; + var end = 0; + var xLocalPosition = (progressPercentage * (end - start)) + start; + var properties = { + localPosition: { x: xLocalPosition, y: -0.93, z: 0.0 }, + dimensions: { + x: progress, + y: 0.3 + }, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 180.0, z: 0.0 }), + subImage: { + x: 0.0, + y: 0.0, + width: subImageWidth, + height: 128 + } + }; + + Overlays.editOverlay(loadingBarProgress, properties); + } + + var MAX_TEXTURE_STABILITY_COUNT = 30; + var INTERVAL_PROGRESS = 0.04; + var INTERVAL_PROGRESS_PHYSICS_ENABLED = 0.09; function update() { var renderStats = Render.getConfig("Stats"); var physicsEnabled = Window.isPhysicsEnabled(); @@ -472,7 +494,7 @@ target = progress; } - if (currentProgress >= (TOTAL_LOADING_PROGRESS * 0.4)) { + if (currentProgress >= ((TOTAL_LOADING_PROGRESS * 0.4) - TEXTURE_EPSILON)) { var textureResourceGPUMemSize = renderStats.textureResourceGPUMemSize; var texturePopulatedGPUMemSize = renderStats.textureResourcePopulatedGPUMemSize; @@ -484,10 +506,9 @@ textureMemSizeAtLastCheck = textureResourceGPUMemSize; - if (textureMemSizeStabilityCount >= 20) { + if (textureMemSizeStabilityCount >= MAX_TEXTURE_STABILITY_COUNT) { if (textureResourceGPUMemSize > 0) { - // print((texturePopulatedGPUMemSize / textureResourceGPUMemSize)); var gpuPercantage = (TOTAL_LOADING_PROGRESS * 0.6) * (texturePopulatedGPUMemSize / textureResourceGPUMemSize); var totalProgress = progress + gpuPercantage; if (totalProgress >= target) { @@ -501,21 +522,14 @@ target = TOTAL_LOADING_PROGRESS; } - currentProgress = lerp(currentProgress, target, 0.2); - var properties = { - localPosition: { x: (1.85 - (currentProgress / 2) - (-0.029 * (currentProgress / TOTAL_LOADING_PROGRESS))), y: -0.935, z: 0.0 }, - dimensions: { - x: currentProgress, - y: 2.8 - } - }; + currentProgress = lerp(currentProgress, target, (physicsEnabled ? INTERVAL_PROGRESS_PHYSICS_ENABLED : INTERVAL_PROGRESS)); - Overlays.editOverlay(loadingBarProgress, properties); + updateProgressBar(currentProgress); if (errorConnectingToDomain) { updateOverlays(errorConnectingToDomain); // setting hover id to invisible - Overlays.editOverlay(loadingToTheSpotHoverID, {visible: false}); + Overlays.editOverlay(loadingToTheSpotHoverID, { visible: false }); endAudio(); currentDomain = "no domain"; timer = null; @@ -531,7 +545,7 @@ } else if ((physicsEnabled && (currentProgress >= (TOTAL_LOADING_PROGRESS - EPSILON)))) { updateOverlays((physicsEnabled || connectionToDomainFailed)); // setting hover id to invisible - Overlays.editOverlay(loadingToTheSpotHoverID, {visible: false}); + Overlays.editOverlay(loadingToTheSpotHoverID, { visible: false }); endAudio(); currentDomain = "no domain"; timer = null; @@ -539,25 +553,23 @@ } timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); } - var whiteColor = {red: 255, green: 255, blue: 255}; - var greyColor = {red: 125, green: 125, blue: 125}; + var whiteColor = { red: 255, green: 255, blue: 255 }; + var greyColor = { red: 125, green: 125, blue: 125 }; + Overlays.mouseReleaseOnOverlay.connect(clickedOnOverlay); Overlays.hoverEnterOverlay.connect(onEnterOverlay); - Overlays.hoverLeaveOverlay.connect(onLeaveOverlay); - location.hostChanged.connect(domainChanged); - location.lookupResultsFinished.connect(function() { - Script.setTimeout(function() { - connectionToDomainFailed = !location.isConnected; - }, 1200); - }); Window.redirectErrorStateChanged.connect(toggleInterstitialPage); MyAvatar.sensorToWorldScaleChanged.connect(scaleInterstitialPage); MyAvatar.sessionUUIDChanged.connect(function() { var avatarSessionUUID = MyAvatar.sessionUUID; - Overlays.editOverlay(loadingSphereID, { parentID: avatarSessionUUID }); + Overlays.editOverlay(loadingSphereID, { + position: Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0.0, y: -1.0, z: 0.0}), Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.95, z: 0})), + orientation: Quat.multiply(Quat.fromVec3Degrees({x: 0, y: 180, z: 0}), MyAvatar.orientation), + parentID: avatarSessionUUID + }); }); var toggle = true; diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 30e952723f..585820d32f 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -15,7 +15,7 @@ var PROFILING_ENABLED = false; var profileIndent = ''; const PROFILE_NOOP = function(_name, fn, args) { fn.apply(this, args); -} ; +}; PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) { console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin"); var previousIndent = profileIndent; @@ -98,7 +98,11 @@ EntityListTool = function(shouldUseEditTabletApp) { that.setVisible(!visible); }; - selectionManager.addEventListener(function() { + selectionManager.addEventListener(function(isSelectionUpdate, caller) { + if (caller === that) { + // ignore events that we emitted from the entity list itself + return; + } var selectedIDs = []; for (var i = 0; i < selectionManager.selections.length; i++) { @@ -224,7 +228,7 @@ EntityListTool = function(shouldUseEditTabletApp) { for (var i = 0; i < ids.length; i++) { entityIDs.push(ids[i]); } - selectionManager.setSelections(entityIDs); + selectionManager.setSelections(entityIDs, that); if (data.focus) { cameraManager.enable(); cameraManager.focus(selectionManager.worldPosition, @@ -245,7 +249,7 @@ EntityListTool = function(shouldUseEditTabletApp) { Window.saveAsync("Select Where to Save", "", "*.json"); } } else if (data.type === "pal") { - var sessionIds = {}; // Collect the sessionsIds of all selected entitities, w/o duplicates. + var sessionIds = {}; // Collect the sessionsIds of all selected entities, w/o duplicates. selectionManager.selections.forEach(function (id) { var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy; if (lastEditedBy) { @@ -271,6 +275,19 @@ EntityListTool = function(shouldUseEditTabletApp) { filterInView = data.filterInView === true; } else if (data.type === "radius") { searchRadius = data.radius; + } else if (data.type === "cut") { + SelectionManager.cutSelectedEntities(); + } else if (data.type === "copy") { + SelectionManager.copySelectedEntities(); + } else if (data.type === "paste") { + SelectionManager.pasteEntities(); + } else if (data.type === "duplicate") { + SelectionManager.duplicateSelection(); + that.sendUpdate(); + } else if (data.type === "rename") { + Entities.editEntity(data.entityID, {name: data.name}); + // make sure that the name also gets updated in the properties window + SelectionManager._update(); } }; diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 843d3e986f..3bb36d632e 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -40,7 +40,7 @@ SelectionManager = (function() { Messages.messageReceived.connect(handleEntitySelectionToolUpdates); } - // FUNCTION: HANDLE ENTITY SELECTION TOOL UDPATES + // FUNCTION: HANDLE ENTITY SELECTION TOOL UPDATES function handleEntitySelectionToolUpdates(channel, message, sender) { if (channel !== 'entityToolUpdates') { return; @@ -63,7 +63,7 @@ SelectionManager = (function() { if (wantDebug) { print("setting selection to " + messageParsed.entityID); } - that.setSelections([messageParsed.entityID]); + that.setSelections([messageParsed.entityID], that); } } else if (messageParsed.method === "clearSelection") { if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) { @@ -136,7 +136,7 @@ SelectionManager = (function() { return that.selections.length > 0; }; - that.setSelections = function(entityIDs) { + that.setSelections = function(entityIDs, caller) { that.selections = []; for (var i = 0; i < entityIDs.length; i++) { var entityID = entityIDs[i]; @@ -144,10 +144,10 @@ SelectionManager = (function() { Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); } - that._update(true); + that._update(true, caller); }; - that.addEntity = function(entityID, toggleSelection) { + that.addEntity = function(entityID, toggleSelection, caller) { if (entityID) { var idx = -1; for (var i = 0; i < that.selections.length; i++) { @@ -165,7 +165,7 @@ SelectionManager = (function() { } } - that._update(true); + that._update(true, caller); }; function removeEntityByID(entityID) { @@ -176,21 +176,21 @@ SelectionManager = (function() { } } - that.removeEntity = function (entityID) { + that.removeEntity = function (entityID, caller) { removeEntityByID(entityID); - that._update(true); + that._update(true, caller); }; - that.removeEntities = function(entityIDs) { + that.removeEntities = function(entityIDs, caller) { for (var i = 0, length = entityIDs.length; i < length; i++) { removeEntityByID(entityIDs[i]); } - that._update(true); + that._update(true, caller); }; - that.clearSelections = function() { + that.clearSelections = function(caller) { that.selections = []; - that._update(true); + that._update(true, caller); }; that.addChildrenEntities = function(parentEntityID, entityList) { @@ -353,12 +353,12 @@ SelectionManager = (function() { } return createdEntityIDs; - } + }; that.cutSelectedEntities = function() { - copySelectedEntities(); + that.copySelectedEntities(); deleteSelectedEntities(); - } + }; that.copySelectedEntities = function() { var entityProperties = Entities.getMultipleEntityProperties(that.selections); @@ -434,7 +434,7 @@ SelectionManager = (function() { z: brn.z + entityClipboard.dimensions.z / 2 }; } - } + }; that.pasteEntities = function() { var dimensions = entityClipboard.dimensions; @@ -442,7 +442,7 @@ SelectionManager = (function() { var pastePosition = getPositionToCreateEntity(maxDimension); var deltaPosition = Vec3.subtract(pastePosition, entityClipboard.position); - var copiedProperties = [] + var copiedProperties = []; var ids = []; entityClipboard.entities.forEach(function(originalProperties) { var properties = deepCopy(originalProperties); @@ -475,9 +475,9 @@ SelectionManager = (function() { redo(copiedProperties); undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties); - } + }; - that._update = function(selectionUpdated) { + that._update = function(selectionUpdated, caller) { var properties = null; if (that.selections.length === 0) { that.localDimensions = null; @@ -542,7 +542,7 @@ SelectionManager = (function() { for (var j = 0; j < listeners.length; j++) { try { - listeners[j](selectionUpdated === true); + listeners[j](selectionUpdated === true, caller); } catch (e) { print("ERROR: entitySelectionTool.update got exception: " + JSON.stringify(e)); } @@ -985,7 +985,7 @@ SelectionDisplay = (function() { that.pressedHand = NO_HAND; that.triggered = function() { return that.triggeredHand !== NO_HAND; - } + }; function pointingAtDesktopWindowOrTablet(hand) { var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtDesktopWindowRight) || @@ -1032,7 +1032,7 @@ SelectionDisplay = (function() { that.disableTriggerMapping = function() { that.triggerClickMapping.disable(); that.triggerPressMapping.disable(); - } + }; Script.scriptEnding.connect(that.disableTriggerMapping); // FUNCTION DEF(s): Intersection Check Helpers @@ -1234,7 +1234,7 @@ SelectionDisplay = (function() { if (wantDebug) { print(" Trigger SelectionManager::update"); } - SelectionManager._update(); + SelectionManager._update(false, that); if (wantDebug) { print("=============== eST::MouseMoveEvent END ======================="); @@ -1299,7 +1299,7 @@ SelectionDisplay = (function() { lastMouseEvent.isControl = event.isControl; lastMouseEvent.isAlt = event.isAlt; activeTool.onMove(lastMouseEvent); - SelectionManager._update(); + SelectionManager._update(false, this); } }; @@ -1315,7 +1315,7 @@ SelectionDisplay = (function() { lastMouseEvent.isControl = event.isControl; lastMouseEvent.isAlt = event.isAlt; activeTool.onMove(lastMouseEvent); - SelectionManager._update(); + SelectionManager._update(false, this); } }; @@ -2179,7 +2179,7 @@ SelectionDisplay = (function() { } } - SelectionManager._update(); + SelectionManager._update(false, this); } }); } @@ -2301,7 +2301,7 @@ SelectionDisplay = (function() { previousPickRay = pickRay; - SelectionManager._update(); + SelectionManager._update(false, this); } }); } @@ -2488,7 +2488,7 @@ SelectionDisplay = (function() { previousPickRay = pickRay; - SelectionManager._update(); + SelectionManager._update(false, this); } }); } @@ -2599,7 +2599,7 @@ SelectionDisplay = (function() { previousPickRay = pickRay; - SelectionManager._update(); + SelectionManager._update(false, this); } }); } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index cca535a064..74c1e4baf0 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -10,7 +10,7 @@ /* global Tablet, Script, HMD, UserActivityLogger, Entities, Account, Wallet, ContextOverlay, Settings, Camera, Vec3, Quat, MyAvatar, Clipboard, Menu, Grid, Uuid, GlobalServices, openLoginWindow, getConnectionData, Overlays, SoundCache, - DesktopPreviewProvider */ + DesktopPreviewProvider, ResourceRequestObserver */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ var selectionDisplay = null; // for gridTool.js to ignore @@ -23,7 +23,7 @@ Script.include("/~/system/libraries/connectionUtils.js"); var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; var MARKETPLACE_ITEM_TESTER_QML_PATH = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"; -var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; +var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; // HRS FIXME "hifi/commerce/purchases/Purchases.qml"; var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); @@ -49,6 +49,42 @@ var NO_BUTTON = 0; // QMessageBox::NoButton var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't write to \nthe domain's Asset Server."; + +var resourceRequestEvents = []; +function signalResourceRequestEvent(data) { + // Once we can tie resource request events to specific resources, + // we will have to update the "0" in here. + var resourceData = "from: " + data.extra + ": " + data.url.toString().replace("__NONE__,", ""); + + if (resourceObjectsInTest[0].resourceDataArray.indexOf(resourceData) === -1) { + resourceObjectsInTest[0].resourceDataArray.push(resourceData); + + resourceObjectsInTest[0].resourceAccessEventText += "[" + data.date.toISOString() + "] " + + resourceData + "\n"; + + ui.tablet.sendToQml({ + method: "resourceRequestEvent", + data: data, + resourceAccessEventText: resourceObjectsInTest[0].resourceAccessEventText + }); + } +} + +function onResourceRequestEvent(data) { + // Once we can tie resource request events to specific resources, + // we will have to update the "0" in here. + if (resourceObjectsInTest[0] && resourceObjectsInTest[0].currentlyRecordingResources) { + var resourceRequestEvent = { + "date": new Date(), + "url": data.url, + "callerId": data.callerId, + "extra": data.extra + }; + resourceRequestEvents.push(resourceRequestEvent); + signalResourceRequestEvent(resourceRequestEvent); + } +} + function onMessageBoxClosed(id, button) { if (id === messageBox && button === CANCEL_BUTTON) { isDownloadBeingCancelled = true; @@ -79,10 +115,10 @@ function setTabletVisibleInSecondaryCamera(visibleInSecondaryCam) { tabletShouldBeVisibleInSecondaryCamera = Overlays.getProperty(HMD.tabletID, "isVisibleInSecondaryCamera"); } - Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); - Overlays.editOverlay(HMD.homeButtonID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); - Overlays.editOverlay(HMD.homeButtonHighlightID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); - Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); + Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera: visibleInSecondaryCam }); + Overlays.editOverlay(HMD.homeButtonID, { isVisibleInSecondaryCamera: visibleInSecondaryCam }); + Overlays.editOverlay(HMD.homeButtonHighlightID, { isVisibleInSecondaryCamera: visibleInSecondaryCam }); + Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera: visibleInSecondaryCam }); } function openWallet() { @@ -101,7 +137,7 @@ function setupWallet(referrer) { } function onMarketplaceOpen(referrer) { - var cta = referrer, match; + var match; if (Account.loggedIn && walletNeedsSetup()) { if (referrer === MARKETPLACE_URL_INITIAL) { setupWallet('marketplace cta'); @@ -182,7 +218,7 @@ function onUsernameChanged() { } function walletNeedsSetup() { - return Wallet.walletStatus === 1; + return WalletScriptingInterface.walletStatus === 1; } function sendCommerceSettings() { @@ -194,289 +230,11 @@ function sendCommerceSettings() { userIsLoggedIn: Account.loggedIn, walletNeedsSetup: walletNeedsSetup(), metaverseServerURL: Account.metaverseServerURL, - messagesWaiting: shouldShowDot + limitedCommerce: WalletScriptingInterface.limitedCommerce } }); } -// BEGIN AVATAR SELECTOR LOGIC -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 }; - -var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. - -function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with. - overlays[key] = this; - this.key = key; - this.selected = false; - 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) { - var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR; - function scale(component) { - return component; - } - return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; -} -// 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) }); - 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; - } - - this.editOverlay({ color: color(selected, this.hovering) }); - this.selected = selected; -}; -// Class methods: -var selectedId = false; -ExtendedOverlay.isSelected = function (id) { - return selectedId === 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; - } - }); -}; - -function addAvatarNode(id) { - return new ExtendedOverlay(id, "sphere", { - drawInFront: true, - solid: true, - alpha: 0.8, - color: color(false, false), - ignoreRayIntersection: false - }); -} - -var pingPong = true; -function updateOverlays() { - var eye = Camera.position; - AvatarList.getAvatarIdentifiers().forEach(function (id) { - if (!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. - 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), - position: target, - dimensions: 0.032 * distance - }); - }); - pingPong = !pingPong; - ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) - if (overlay.ping === pingPong) { - overlay.deleteOverlay(); - } - }); -} -function removeOverlays() { - selectedId = false; - lastHoveringId = 0; - ExtendedOverlay.some(function (overlay) { - overlay.deleteOverlay(); - }); -} - -// -// Clicks. -// -function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { - if (selectedId === id) { - var message = { - method: 'updateSelectedRecipientUsername', - userName: username === "" ? "unknown username" : username - }; - ui.tablet.sendToQml(message); - } -} -function handleClick(pickRay) { - ExtendedOverlay.applyPickRay(pickRay, function (overlay) { - var nextSelectedStatus = !overlay.selected; - var avatarId = overlay.key; - selectedId = nextSelectedStatus ? avatarId : false; - if (nextSelectedStatus) { - Users.requestUsernameFromID(avatarId); - } - var message = { - method: 'selectRecipient', - id: avatarId, - isSelected: nextSelectedStatus, - displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', - userName: '' - }; - ui.tablet.sendToQml(message); - - ExtendedOverlay.some(function (overlay) { - var id = overlay.key; - var selected = ExtendedOverlay.isSelected(id); - overlay.select(selected); - }); - - 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)); -// END AVATAR SELECTOR LOGIC - var grid = new Grid(); function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original @@ -522,13 +280,19 @@ function getPositionToCreateEntity(extra) { return position; } -function rezEntity(itemHref, itemType) { +function defaultFor(arg, val) { + return typeof arg !== 'undefined' ? arg : val; +} + +var CERT_ID_URLPARAM_LENGTH = 15; // length of "certificate_id=" +function rezEntity(itemHref, itemType, marketplaceItemTesterId) { var isWearable = itemType === "wearable"; - var success = Clipboard.importEntities(itemHref); + var success = Clipboard.importEntities(itemHref, true, marketplaceItemTesterId); var wearableLocalPosition = null; var wearableLocalRotation = null; var wearableLocalDimensions = null; var wearableDimensions = null; + marketplaceItemTesterId = defaultFor(marketplaceItemTesterId, -1); if (itemType === "contentSet") { console.log("Item is a content set; codepath shouldn't go here."); @@ -543,7 +307,7 @@ function rezEntity(itemHref, itemType) { } var certPos = itemHref.search("certificate_id="); // TODO how do I parse a URL from here? if (certPos >= 0) { - certPos += 15; // length of "certificate_id=" + certPos += CERT_ID_URLPARAM_LENGTH; var certURLEncoded = itemHref.substring(certPos); var certB64Encoded = decodeURIComponent(certURLEncoded); for (var key in wearableTransforms) { @@ -552,7 +316,7 @@ function rezEntity(itemHref, itemType) { if (certificateTransforms) { for (var certID in certificateTransforms) { if (certificateTransforms.hasOwnProperty(certID) && - certID == certB64Encoded) { + certID === certB64Encoded) { var certificateTransform = certificateTransforms[certID]; wearableLocalPosition = certificateTransform.localPosition; wearableLocalRotation = certificateTransform.localRotation; @@ -595,8 +359,10 @@ function rezEntity(itemHref, itemType) { targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z); var targetPosition = getPositionToCreateEntity(); - var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. - var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. + // Distance to move entities parallel to targetDirection. + var deltaParallel = HALF_TREE_SCALE; + // Distance to move entities perpendicular to targetDirection. + var deltaPerpendicular = Vec3.ZERO; for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", "registrationPoint", "rotation", "parentID"]); @@ -624,7 +390,8 @@ function rezEntity(itemHref, itemType) { } if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { - for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) { + for (var editEntityIndex = 0, + numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) { if (Uuid.isNull(entityParentIDs[editEntityIndex])) { Entities.editEntity(pastedEntityIDs[editEntityIndex], { position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex]) @@ -727,79 +494,6 @@ function onWebEventReceived(message) { }); } } -var sendAssetRecipient; -var sendAssetParticleEffectUpdateTimer; -var particleEffectTimestamp; -var sendAssetParticleEffect; -var SEND_ASSET_PARTICLE_TIMER_UPDATE = 250; -var SEND_ASSET_PARTICLE_EMITTING_DURATION = 3000; -var SEND_ASSET_PARTICLE_LIFETIME_SECONDS = 8; -var SEND_ASSET_PARTICLE_PROPERTIES = { - accelerationSpread: { x: 0, y: 0, z: 0 }, - alpha: 1, - alphaFinish: 1, - alphaSpread: 0, - alphaStart: 1, - azimuthFinish: 0, - azimuthStart: -6, - color: { red: 255, green: 222, blue: 255 }, - colorFinish: { red: 255, green: 229, blue: 225 }, - colorSpread: { red: 0, green: 0, blue: 0 }, - colorStart: { red: 243, green: 255, blue: 255 }, - emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate - emitDimensions: { x: 0, y: 0, z: 0 }, - emitOrientation: { x: 0, y: 0, z: 0 }, - emitRate: 4, - emitSpeed: 2.1, - emitterShouldTrail: true, - isEmitting: 1, - lifespan: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate - lifetime: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, - maxParticles: 20, - name: 'asset-particles', - particleRadius: 0.2, - polarFinish: 0, - polarStart: 0, - radiusFinish: 0.05, - radiusSpread: 0, - radiusStart: 0.2, - speedSpread: 0, - textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png", - type: 'ParticleEffect' -}; - -function updateSendAssetParticleEffect() { - var timestampNow = Date.now(); - if ((timestampNow - particleEffectTimestamp) > (SEND_ASSET_PARTICLE_LIFETIME_SECONDS * 1000)) { - deleteSendAssetParticleEffect(); - return; - } else if ((timestampNow - particleEffectTimestamp) > SEND_ASSET_PARTICLE_EMITTING_DURATION) { - Entities.editEntity(sendAssetParticleEffect, { - isEmitting: 0 - }); - } else if (sendAssetParticleEffect) { - var recipientPosition = AvatarList.getAvatar(sendAssetRecipient).position; - var distance = Vec3.distance(recipientPosition, MyAvatar.position); - var accel = Vec3.subtract(recipientPosition, MyAvatar.position); - accel.y -= 3.0; - var life = Math.sqrt(2 * distance / Vec3.length(accel)); - Entities.editEntity(sendAssetParticleEffect, { - emitAcceleration: accel, - lifespan: life - }); - } -} - -function deleteSendAssetParticleEffect() { - if (sendAssetParticleEffectUpdateTimer) { - Script.clearInterval(sendAssetParticleEffectUpdateTimer); - sendAssetParticleEffectUpdateTimer = null; - } - if (sendAssetParticleEffect) { - sendAssetParticleEffect = Entities.deleteEntity(sendAssetParticleEffect); - } - sendAssetRecipient = null; -} var savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview"); var UI_FADE_TIMEOUT_MS = 150; @@ -816,7 +510,8 @@ var resourceObjectsInTest = []; function signalNewResourceObjectInTest(resourceObject) { ui.tablet.sendToQml({ method: "newResourceObjectInTest", - resourceObject: resourceObject }); + resourceObject: resourceObject + }); } var onQmlMessageReceived = function onQmlMessageReceived(message) { @@ -825,24 +520,21 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { } switch (message.method) { case 'gotoBank': - ui.close(); + ui.close(); if (Account.metaverseServerURL.indexOf("staging") >= 0) { Window.location = "hifi://hifiqa-master-metaverse-staging"; // So that we can test in staging. } else { Window.location = "hifi://BankOfHighFidelity"; } - break; - case 'purchases_openWallet': - case 'checkout_openWallet': - case 'checkout_setUpClicked': - openWallet(); break; - case 'purchases_walletNotSetUp': + case 'checkout_openRecentActivity': + ui.open(MARKETPLACE_WALLET_QML_PATH); wireQmlEventBridge(true); ui.tablet.sendToQml({ - method: 'updateWalletReferrer', - referrer: "purchases" + method: 'checkout_openRecentActivity' }); + break; + case 'checkout_setUpClicked': openWallet(); break; case 'checkout_walletNotSetUp': @@ -865,23 +557,21 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { case 'checkout_itemLinkClicked': openMarketplace(message.itemId); break; - case 'checkout_continueShopping': + case 'checkout_continue': openMarketplace(); break; - case 'purchases_itemInfoClicked': - var itemId = message.itemId; - if (itemId && itemId !== "") { - openMarketplace(itemId); - } - break; case 'checkout_rezClicked': case 'purchases_rezClicked': case 'tester_rezClicked': - rezEntity(message.itemHref, message.itemType); + rezEntity(message.itemHref, message.itemType, message.itemId); break; case 'tester_newResourceObject': var resourceObject = message.resourceObject; - resourceObjectsInTest[resourceObject.id] = resourceObject; + resourceObjectsInTest = []; // REMOVE THIS once we support specific referrers + resourceObject.currentlyRecordingResources = false; + resourceObject.resourceAccessEventText = ""; + resourceObjectsInTest[resourceObject.resourceObjectId] = resourceObject; + resourceObjectsInTest[resourceObject.resourceObjectId].resourceDataArray = []; signalNewResourceObjectInTest(resourceObject); break; case 'tester_updateResourceObjectAssetType': @@ -890,8 +580,14 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { case 'tester_deleteResourceObject': delete resourceObjectsInTest[message.objectId]; break; + case 'tester_updateResourceRecordingStatus': + resourceObjectsInTest[message.objectId].currentlyRecordingResources = message.status; + if (message.status) { + resourceObjectsInTest[message.objectId].resourceDataArray = []; + resourceObjectsInTest[message.objectId].resourceAccessEventText = ""; + } + break; case 'header_marketplaceImageClicked': - case 'purchases_backClicked': openMarketplace(message.referrerURL); break; case 'purchases_goToMarketplaceClicked': @@ -899,9 +595,6 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { break; case 'updateItemClicked': openMarketplace(message.upgradeUrl + "?edition=" + message.itemEdition); - break; - case 'giftAsset': - break; case 'passphrasePopup_cancelClicked': case 'needsLogIn_cancelClicked': @@ -921,13 +614,9 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { case 'maybeEnableHmdPreview': maybeEnableHMDPreview(); break; - case 'purchases_openGoTo': + case 'checkout_openGoTo': ui.open("hifi/tablet/TabletAddressDialog.qml"); break; - case 'purchases_itemCertificateClicked': - contextOverlayEntity = ""; - setCertificateInfo(contextOverlayEntity, message.itemCertificateId); - break; case 'inspectionCertificate_closeClicked': ui.close(); break; @@ -946,99 +635,45 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { method: 'purchases_showMyItems' }); break; - case 'refreshConnections': - // Guard to prevent this code from being executed while sending money -- - // we only want to execute this while sending non-HFC gifts - if (!onWalletScreen) { - print('Refreshing Connections...'); - getConnectionData(false); - } - break; - case 'enable_ChooseRecipientNearbyMode': - // Guard to prevent this code from being executed while sending money -- - // we only want to execute this while sending non-HFC gifts - if (!onWalletScreen) { - if (!isUpdateOverlaysWired) { - Script.update.connect(updateOverlays); - isUpdateOverlaysWired = true; - } - } - break; - case 'disable_ChooseRecipientNearbyMode': - // Guard to prevent this code from being executed while sending money -- - // we only want to execute this while sending non-HFC gifts - if (!onWalletScreen) { - if (isUpdateOverlaysWired) { - Script.update.disconnect(updateOverlays); - isUpdateOverlaysWired = false; - } - removeOverlays(); - } - break; - case 'purchases_availableUpdatesReceived': - shouldShowDot = message.numUpdates > 0; - ui.messagesWaiting(shouldShowDot && !ui.isOpen); - break; - case 'purchases_updateWearables': - var currentlyWornWearables = []; - var ATTACHMENT_SEARCH_RADIUS = 100; // meters (just in case) - - var nearbyEntities = Entities.findEntitiesByType('Model', MyAvatar.position, ATTACHMENT_SEARCH_RADIUS); - - for (var i = 0; i < nearbyEntities.length; i++) { - var currentProperties = Entities.getEntityProperties( - nearbyEntities[i], ['certificateID', 'editionNumber', 'parentID'] - ); - if (currentProperties.parentID === MyAvatar.sessionUUID) { - currentlyWornWearables.push({ - entityID: nearbyEntities[i], - entityCertID: currentProperties.certificateID, - entityEdition: currentProperties.editionNumber - }); - } - } - - ui.tablet.sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables }); - break; - case 'sendAsset_sendPublicly': - if (message.assetName !== "") { - deleteSendAssetParticleEffect(); - sendAssetRecipient = message.recipient; - var props = SEND_ASSET_PARTICLE_PROPERTIES; - props.parentID = MyAvatar.sessionUUID; - props.position = MyAvatar.position; - props.position.y += 0.2; - if (message.effectImage) { - props.textures = message.effectImage; - } - sendAssetParticleEffect = Entities.addEntity(props, true); - particleEffectTimestamp = Date.now(); - updateSendAssetParticleEffect(); - sendAssetParticleEffectUpdateTimer = Script.setInterval(updateSendAssetParticleEffect, - SEND_ASSET_PARTICLE_TIMER_UPDATE); - } - break; case 'http.request': // Handled elsewhere, don't log. break; - case 'goToPurchases_fromWalletHome': // HRS FIXME What's this about? + // All of these are handled by wallet.js + case 'purchases_updateWearables': + case 'enable_ChooseRecipientNearbyMode': + case 'disable_ChooseRecipientNearbyMode': + case 'sendAsset_sendPublicly': + case 'refreshConnections': + case 'transactionHistory_goToBank': + case 'purchases_walletNotSetUp': + case 'purchases_openGoTo': + case 'purchases_itemInfoClicked': + case 'purchases_itemCertificateClicked': + case 'clearShouldShowDotHistory': + case 'giftAsset': break; default: - print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); + print('Unrecognized message from Checkout.qml: ' + JSON.stringify(message)); } }; function pushResourceObjectsInTest() { - var maxObjectId = -1; - for (var objectId in resourceObjectsInTest) { - signalNewResourceObjectInTest(resourceObjectsInTest[objectId]); - maxObjectId = (maxObjectId < objectId) ? parseInt(objectId) : maxObjectId; + var maxResourceObjectId = -1; + var length = resourceObjectsInTest.length; + for (var i = 0; i < length; i++) { + if (i in resourceObjectsInTest) { + signalNewResourceObjectInTest(resourceObjectsInTest[i]); + var resourceObjectId = resourceObjectsInTest[i].resourceObjectId; + maxResourceObjectId = (maxResourceObjectId < resourceObjectId) ? parseInt(resourceObjectId) : maxResourceObjectId; + } } // N.B. Thinking about removing the following sendToQml? Be sure // that the marketplace item tester QML has heard from us, at least // so that it can indicate to the user that all of the resoruce // objects in test have been transmitted to it. - ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: maxObjectId + 1 }); + //ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: maxResourceObjectId + 1 }); + // Since, for now, we only support 1 object in test, always send id: 0 + ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: 0 }); } // Function Name: onTabletScreenChanged() @@ -1087,11 +722,18 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { referrerURL: referrerURL, filterText: filterText }); + referrerURL = ""; + filterText = ""; } + var wasIsOpen = ui.isOpen; ui.isOpen = (onMarketplaceScreen || onCommerceScreen) && !onWalletScreen; ui.buttonActive(ui.isOpen); + if (wasIsOpen !== ui.isOpen && Keyboard.raised) { + Keyboard.raised = false; + } + if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { ContextOverlay.isInMarketplaceInspectionMode = true; } else { @@ -1103,15 +745,7 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { } if (onCommerceScreen) { - if (!isWired) { - Users.usernameFromIDReply.connect(usernameFromIDReply); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - triggerMapping.enable(); - triggerPressMapping.enable(); - } - isWired = true; - Wallet.refreshWalletStatus(); + WalletScriptingInterface.refreshWalletStatus(); } else { if (onMarketplaceScreen) { onMarketplaceOpen('marketplace cta'); @@ -1133,44 +767,11 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { "\nNew screen URL: " + url + "\nCurrent app open status: " + ui.isOpen + "\n"); }; -function notificationDataProcessPage(data) { - return data.data.updates; -} - -var shouldShowDot = false; -function notificationPollCallback(updatesArray) { - shouldShowDot = shouldShowDot || updatesArray.length > 0; - ui.messagesWaiting(shouldShowDot && !ui.isOpen); - - if (updatesArray.length > 0) { - var message; - if (!ui.notificationInitialCallbackMade) { - message = updatesArray.length + " of your purchased items " + - (updatesArray.length === 1 ? "has an update " : "have updates ") + - "available. Open MARKET to update."; - ui.notificationDisplayBanner(message); - - ui.notificationPollCaresAboutSince = true; - } else { - for (var i = 0; i < updatesArray.length; i++) { - message = "Update available for \"" + - updatesArray[i].base_item_title + "\"." + - "Open MARKET to update."; - ui.notificationDisplayBanner(message); - } - } - } -} - -function isReturnedDataEmpty(data) { - var historyArray = data.data.updates; - return historyArray.length === 0; -} - var BUTTON_NAME = "MARKET"; var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace"; -var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. +// Append "?" if necessary to signal injected script that it's the initial page. +var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + (MARKETPLACE_URL.indexOf("?") > -1 ? "" : "?"); var ui; function startup() { ui = new AppUi({ @@ -1179,53 +780,31 @@ function startup() { inject: MARKETPLACES_INJECT_SCRIPT_URL, home: MARKETPLACE_URL_INITIAL, onScreenChanged: onTabletScreenChanged, - onMessage: onQmlMessageReceived, - notificationPollEndpoint: "/api/v1/commerce/available_updates?per_page=10", - notificationPollTimeoutMs: 300000, - notificationDataProcessPage: notificationDataProcessPage, - notificationPollCallback: notificationPollCallback, - notificationPollStopPaginatingConditionMet: isReturnedDataEmpty, - notificationPollCaresAboutSince: false // Changes to true after first poll + onMessage: onQmlMessageReceived }); ContextOverlay.contextOverlayClicked.connect(openInspectionCertificateQML); Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); GlobalServices.myUsernameChanged.connect(onUsernameChanged); ui.tablet.webEventReceived.connect(onWebEventReceived); - Wallet.walletStatusChanged.connect(sendCommerceSettings); + WalletScriptingInterface.walletStatusChanged.connect(sendCommerceSettings); Window.messageBoxClosed.connect(onMessageBoxClosed); + ResourceRequestObserver.resourceRequestEvent.connect(onResourceRequestEvent); - Wallet.refreshWalletStatus(); + WalletScriptingInterface.refreshWalletStatus(); } -var isWired = false; -var isUpdateOverlaysWired = false; function off() { - if (isWired) { - Users.usernameFromIDReply.disconnect(usernameFromIDReply); - Controller.mousePressEvent.disconnect(handleMouseEvent); - Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); - triggerMapping.disable(); - triggerPressMapping.disable(); - - isWired = false; - } - - if (isUpdateOverlaysWired) { - Script.update.disconnect(updateOverlays); - isUpdateOverlaysWired = false; - } - removeOverlays(); } function shutdown() { maybeEnableHMDPreview(); - deleteSendAssetParticleEffect(); Window.messageBoxClosed.disconnect(onMessageBoxClosed); - Wallet.walletStatusChanged.disconnect(sendCommerceSettings); + WalletScriptingInterface.walletStatusChanged.disconnect(sendCommerceSettings); ui.tablet.webEventReceived.disconnect(onWebEventReceived); GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged); ContextOverlay.contextOverlayClicked.disconnect(openInspectionCertificateQML); + ResourceRequestObserver.resourceRequestEvent.disconnect(onResourceRequestEvent); off(); } diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 36fe264274..9558b99310 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -634,7 +634,7 @@ Window.notifyEditError = onEditError; Window.notify = onNotify; Tablet.tabletNotification.connect(tabletNotification); - Wallet.walletNotSetup.connect(walletNotSetup); + WalletScriptingInterface.walletNotSetup.connect(walletNotSetup); Messages.subscribe(NOTIFICATIONS_MESSAGE_CHANNEL); Messages.messageReceived.connect(onMessageReceived); diff --git a/scripts/system/pal.js b/scripts/system/pal.js index a2ebae1a33..341ce9ebc8 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -844,7 +844,7 @@ function notificationPollCallback(connectionsArray) { newOnlineUsers++; storedOnlineUsers[user.username] = user; - if (!ui.isOpen && ui.notificationInitialCallbackMade) { + if (!ui.isOpen && ui.notificationInitialCallbackMade[0]) { message = user.username + " is available in " + user.location.root.name + ". Open PEOPLE to join them."; ui.notificationDisplayBanner(message); @@ -868,7 +868,7 @@ function notificationPollCallback(connectionsArray) { shouldShowDot: shouldShowDot }); - if (newOnlineUsers > 0 && !ui.notificationInitialCallbackMade) { + if (newOnlineUsers > 0 && !ui.notificationInitialCallbackMade[0]) { message = newOnlineUsers + " of your connections " + (newOnlineUsers === 1 ? "is" : "are") + " available online. Open PEOPLE to join them."; ui.notificationDisplayBanner(message); @@ -889,12 +889,12 @@ function startup() { onOpened: palOpened, onClosed: off, onMessage: fromQml, - notificationPollEndpoint: "/api/v1/users?filter=connections&status=online&per_page=10", - notificationPollTimeoutMs: 60000, - notificationDataProcessPage: notificationDataProcessPage, - notificationPollCallback: notificationPollCallback, - notificationPollStopPaginatingConditionMet: isReturnedDataEmpty, - notificationPollCaresAboutSince: false + notificationPollEndpoint: ["/api/v1/users?filter=connections&status=online&per_page=10"], + notificationPollTimeoutMs: [60000], + notificationDataProcessPage: [notificationDataProcessPage], + notificationPollCallback: [notificationPollCallback], + notificationPollStopPaginatingConditionMet: [isReturnedDataEmpty], + notificationPollCaresAboutSince: [false] }); Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); diff --git a/scripts/system/particle_explorer/hifi-entity-ui.js b/scripts/system/particle_explorer/hifi-entity-ui.js deleted file mode 100644 index 62a0aadc86..0000000000 --- a/scripts/system/particle_explorer/hifi-entity-ui.js +++ /dev/null @@ -1,709 +0,0 @@ -/* global window, document, print, alert, console,setTimeout, clearTimeout, _ $ */ -/* eslint no-console: 0 */ - -/** -UI Builder V1.0 - -Created by Matti 'Menithal' Lahtinen -24/5/2017 -Copyright 2017 High Fidelity, Inc. - -This can eventually be expanded to all of Edit, for now, starting -with Particles Only. - -This is created for the sole purpose of streamliming the bridge, and to simplify -the logic between an inputfield in WebView and Entities in High Fidelity. - -We also do not need anything as heavy as jquery or any other platform, -as we are mostly only building for QT (while, all the other JS frameworks usually do alot of polyfilling) - -Available Types: - - JSONInputField - Accepts JSON input, once one presses Save, it will be propegated. - Button- A Button that listens for a custom event as defined by callback - Boolean - Creates a checkbox that the user can either check or uncheck - SliderFloat - Creates a slider (with input) that has Float values from min to max. - Default is min 0, max 1 - SliderInteger - Creates a slider (with input) that has a Integer value from min to max. - Default is min 1, max 10000 - SliderRadian - Creates a slider (with input) that has Float values in degrees, - that are converted to radians. default is min 0, max Math.PI. - Texture - Creates a Image with an url input field that points to texture. - If image cannot form, show "cannot find image" - VecQuaternion - Creates a 3D Vector field that converts to quaternions. - Checkbox exists to show quaternions instead. - Color - Create field color button, that when pressed, opens the color picker. - Vector - Create a 3D Vector field that has one to one correspondence. - -The script will use this structure to build a UI that is connected The -id fields within High Fidelity - -This should make editing, and everything related much more simpler to maintain, -and If there is any changes to either the Entities or properties of - -**/ - -var RADIANS_PER_DEGREE = Math.PI / 180; -var DEBOUNCE_TIMEOUT = 125; - -var roundFloat = function (input, round) { - round = round ? round : 1000; - var sanitizedInput; - if (typeof input === "string") { - sanitizedInput = parseFloat(input); - } else { - sanitizedInput = input; - } - return Math.round(sanitizedInput * round) / round; -}; - -function HifiEntityUI(parent) { - this.parent = parent; - - var self = this; - this.sendPackage = {}; - this.settingsUpdateLock = false; - this.webBridgeSync = function(id, val) { - if (!this.settingsUpdateLock) { - this.sendPackage[id] = val; - this.webBridgeSyncDebounce(); - } - }; - this.webBridgeSyncDebounce = _.debounce(function () { - if (self.EventBridge) { - self.submitChanges(self.sendPackage); - self.sendPackage = {}; - } - }, DEBOUNCE_TIMEOUT); -} - -HifiEntityUI.prototype = { - setOnSelect: function (callback) { - this.onSelect = callback; - }, - submitChanges: function (structure) { - var message = { - messageType: "settings_update", - updatedSettings: structure - }; - this.EventBridge.emitWebEvent(JSON.stringify(message)); - }, - setUI: function (structure) { - this.structure = structure; - }, - disableFields: function () { - var fields = document.getElementsByTagName("input"); - for (var i = 0; i < fields.length; i++) { - if (fields[i].getAttribute("type") !== "button") { - fields[i].value = ""; - } - - fields[i].setAttribute("disabled", true); - } - var textures = document.getElementsByTagName("img"); - for (i = 0; i < textures.length; i++) { - textures[i].src = ""; - } - - textures = document.getElementsByClassName("with-texture"); - for (i = 0; i < textures.length; i++) { - textures[i].classList.remove("with-textures"); - textures[i].classList.add("no-texture"); - } - - var textareas = document.getElementsByTagName("textarea"); - for (var x = 0; x < textareas.length; x++) { - textareas[x].remove(); - } - }, - getSettings: function () { - var self = this; - var json = {}; - var keys = Object.keys(self.builtRows); - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - var el = self.builtRows[key]; - if (el.className.indexOf("checkbox") !== -1) { - json[key] = document.getElementById(key) - .checked ? true : false; - } else if (el.className.indexOf("vector-section") !== -1) { - var vector = {}; - if (el.className.indexOf("rgb") !== -1) { - var red = document.getElementById(key + "-red"); - var blue = document.getElementById(key + "-blue"); - var green = document.getElementById(key + "-green"); - vector.red = red.value; - vector.blue = blue.value; - vector.green = green.value; - } else if (el.className.indexOf("pyr") !== -1) { - var p = document.getElementById(key + "-Pitch"); - var y = document.getElementById(key + "-Yaw"); - var r = document.getElementById(key + "-Roll"); - vector.x = p.value; - vector.y = y.value; - vector.z = r.value; - } else { - var x = document.getElementById(key + "-x"); - var ey = document.getElementById(key + "-y"); - var z = document.getElementById(key + "-z"); - vector.x = x.value; - vector.y = ey.value; - vector.z = z.value; - } - json[key] = vector; - } else if (el.className.indexOf("radian") !== -1) { - json[key] = document.getElementById(key).value * RADIANS_PER_DEGREE; - } else if (el.className.length > 0) { - json[key] = document.getElementById(key).value; - } - } - - - return json; - }, - fillFields: function (currentProperties) { - var self = this; - var fields = document.getElementsByTagName("input"); - - if (!currentProperties.locked) { - for (var i = 0; i < fields.length; i++) { - fields[i].removeAttribute("disabled"); - if (fields[i].hasAttribute("data-max")) { - // Reset Max to original max - fields[i].setAttribute("max", fields[i].getAttribute("data-max")); - } - } - } - - if (self.onSelect) { - self.onSelect(); - } - var keys = Object.keys(currentProperties); - - - for (var e in keys) { - if (keys.hasOwnProperty(e)) { - var value = keys[e]; - - var property = currentProperties[value]; - var field = self.builtRows[value]; - if (field) { - var el = document.getElementById(value); - - if (field.className.indexOf("radian") !== -1) { - el.value = property / RADIANS_PER_DEGREE; - el.onchange({ - target: el - }); - } else if (field.className.indexOf("range") !== -1 || field.className.indexOf("texture") !== -1) { - el.value = property; - el.onchange({ - target: el - }); - } else if (field.className.indexOf("checkbox") !== -1) { - if (property) { - el.setAttribute("checked", property); - } else { - el.removeAttribute("checked"); - } - } else if (field.className.indexOf("vector-section") !== -1) { - if (field.className.indexOf("rgb") !== -1) { - var red = document.getElementById(value + "-red"); - var blue = document.getElementById(value + "-blue"); - var green = document.getElementById(value + "-green"); - red.value = parseInt(property.red); - blue.value = parseInt(property.blue); - green.value = parseInt(property.green); - - red.oninput({ - target: red - }); - } else if (field.className.indexOf("xyz") !== -1) { - var x = document.getElementById(value + "-x"); - var y = document.getElementById(value + "-y"); - var z = document.getElementById(value + "-z"); - - x.value = roundFloat(property.x, 100); - y.value = roundFloat(property.y, 100); - z.value = roundFloat(property.z, 100); - } else if (field.className.indexOf("pyr") !== -1) { - var pitch = document.getElementById(value + "-Pitch"); - var yaw = document.getElementById(value + "-Yaw"); - var roll = document.getElementById(value + "-Roll"); - - pitch.value = roundFloat(property.x, 100); - yaw.value = roundFloat(property.y, 100); - roll.value = roundFloat(property.z, 100); - - } - } - } - } - } - }, - connect: function (EventBridge) { - this.EventBridge = EventBridge; - - var self = this; - - EventBridge.emitWebEvent(JSON.stringify({ - messageType: 'page_loaded' - })); - - EventBridge.scriptEventReceived.connect(function (data) { - data = JSON.parse(data); - - if (data.messageType === 'particle_settings') { - self.settingsUpdateLock = true; - self.fillFields(data.currentProperties); - self.settingsUpdateLock = false; - // Do expected property match with structure; - } else if (data.messageType === 'particle_close') { - self.disableFields(); - } - }); - }, - build: function () { - var self = this; - var sections = Object.keys(this.structure); - this.builtRows = {}; - sections.forEach(function (section, index) { - var properties = self.structure[section]; - self.addSection(self.parent, section, properties, index); - }); - }, - addSection: function (parent, section, properties, index) { - var self = this; - - var sectionDivHeader = document.createElement("fieldset"); - var title = document.createElement("legend"); - var dropDown = document.createElement("span"); - - dropDown.className = "arrow"; - sectionDivHeader.className = "major"; - title.className = "section-header"; - title.id = section + "-section"; - title.innerHTML = section; - title.appendChild(dropDown); - sectionDivHeader.appendChild(title); - - var collapsed = index !== 0; - - dropDown.innerHTML = collapsed ? "L" : "M"; - sectionDivHeader.setAttribute("collapsed", collapsed); - parent.appendChild(sectionDivHeader); - - var sectionDivBody = document.createElement("div"); - sectionDivBody.className = "property-group"; - - var animationWrapper = document.createElement("div"); - animationWrapper.className = "section-wrap"; - - for (var property in properties) { - if (properties.hasOwnProperty(property)) { - var builtRow = self.addElement(animationWrapper, properties[property]); - var id = properties[property].id; - if (id) { - self.builtRows[id] = builtRow; - } - } - } - sectionDivBody.appendChild(animationWrapper); - sectionDivHeader.appendChild(sectionDivBody); - _.defer(function () { - var height = (animationWrapper.clientHeight) + "px"; - if (collapsed) { - sectionDivBody.classList.remove("visible"); - sectionDivBody.style.maxHeight = "0px"; - } else { - sectionDivBody.classList.add("visible"); - sectionDivBody.style.maxHeight = height; - } - - title.onclick = function () { - collapsed = !collapsed; - if (collapsed) { - sectionDivBody.classList.remove("visible"); - sectionDivBody.style.maxHeight = "0px"; - } else { - sectionDivBody.classList.add("visible"); - sectionDivBody.style.maxHeight = (animationWrapper.clientHeight) + "px"; - } - // sectionDivBody.style.display = collapsed ? "none": "block"; - dropDown.innerHTML = collapsed ? "L" : "M"; - title.setAttribute("collapsed", collapsed); - }; - }); - }, - addLabel: function (parent, group) { - var label = document.createElement("label"); - label.innerHTML = group.name; - parent.appendChild(label); - if (group.unit) { - var span = document.createElement("span"); - span.innerHTML = group.unit; - span.className = "unit"; - label.appendChild(span); - } - return label; - }, - addVector: function (parent, group, labels, domArray) { - var self = this; - var inputs = labels ? labels : ["x", "y", "z"]; - domArray = domArray ? domArray : []; - parent.id = group.id; - for (var index in inputs) { - var element = document.createElement("input"); - - element.setAttribute("type", "number"); - element.className = inputs[index]; - element.id = group.id + "-" + inputs[index]; - - if (group.defaultRange) { - if (group.defaultRange.min) { - element.setAttribute("min", group.defaultRange.min); - } - if (group.defaultRange.max) { - element.setAttribute("max", group.defaultRange.max); - } - if (group.defaultRange.step) { - element.setAttribute("step", group.defaultRange.step); - } - } - if (group.oninput) { - element.oninput = group.oninput; - } else { - element.oninput = function (event) { - self.webBridgeSync(group.id, { - x: domArray[0].value, - y: domArray[1].value, - z: domArray[2].value - }); - }; - } - element.onchange = element.oninput; - domArray.push(element); - } - - this.addLabel(parent, group); - var className = ""; - for (var i = 0; i < inputs.length; i++) { - className += inputs[i].charAt(0) - .toLowerCase(); - } - parent.className += " property vector-section " + className; - - // Add Tuple and the rest - var tupleContainer = document.createElement("div"); - tupleContainer.className = "tuple"; - for (var domIndex in domArray) { - var container = domArray[domIndex]; - var div = document.createElement("div"); - var label = document.createElement("label"); - label.innerHTML = inputs[domIndex] + ":"; - label.setAttribute("for", container.id); - div.appendChild(container); - div.appendChild(label); - tupleContainer.appendChild(div); - } - parent.appendChild(tupleContainer); - }, - addVectorQuaternion: function (parent, group) { - this.addVector(parent, group, ["Pitch", "Yaw", "Roll"]); - }, - addColorPicker: function (parent, group) { - var self = this; - var $colPickContainer = $('<div>', { - id: group.id, - class: "color-picker" - }); - var updateColors = function (red, green, blue) { - $colPickContainer.css('background-color', "rgb(" + - red + "," + - green + "," + - blue + ")"); - }; - - var inputs = ["red", "green", "blue"]; - var domArray = []; - group.oninput = function (event) { - $colPickContainer.colpickSetColor( - { - r: domArray[0].value, - g: domArray[1].value, - b: domArray[2].value - }, - true); - }; - group.defaultRange = { - min: 0, - max: 255, - step: 1 - }; - - parent.appendChild($colPickContainer[0]); - self.addVector(parent, group, inputs, domArray); - - updateColors(domArray[0].value, domArray[1].value, domArray[2].value); - - // Could probably write a custom one for this to completely write out jquery, - // but for now, using the same as earlier. - - /* Color Picker Logic Here */ - - - $colPickContainer.colpick({ - colorScheme: (group.layoutColorScheme === undefined ? 'dark' : group.layoutColorScheme), - layout: (group.layoutType === undefined ? 'hex' : group.layoutType), - submit: (group.useSubmitButton === undefined ? true : group.useSubmitButton), - color: { - r: domArray[0].value, - g: domArray[1].value, - b: domArray[2].value - }, - onChange: function (hsb, hex, rgb, el) { - updateColors(rgb.r, rgb.g, rgb.b); - - domArray[0].value = rgb.r; - domArray[1].value = rgb.g; - domArray[2].value = rgb.b; - self.webBridgeSync(group.id, { - red: rgb.r, - green: rgb.g, - blue: rgb.b - }); - }, - onSubmit: function (hsb, hex, rgb, el) { - $(el) - .css('background-color', '#' + hex); - $(el) - .colpickHide(); - domArray[0].value = rgb.r; - domArray[1].value = rgb.g; - domArray[2].value = rgb.b; - self.webBridgeSync(group.id, { - red: rgb.r, - green: rgb.g, - blue: rgb.b - }); - } - }); - }, - addTextureField: function (parent, group) { - var self = this; - this.addLabel(parent, group); - parent.className += " property texture"; - var textureImage = document.createElement("div"); - var textureUrl = document.createElement("input"); - textureUrl.setAttribute("type", "text"); - textureUrl.id = group.id; - textureImage.className = "texture-image no-texture"; - var image = document.createElement("img"); - var imageLoad = _.debounce(function (url) { - if (url.slice(0, 5).toLowerCase() === "atp:/") { - image.src = ""; - image.style.display = "none"; - textureImage.classList.remove("with-texture"); - textureImage.classList.remove("no-texture"); - textureImage.classList.add("no-preview"); - } else if (url.length > 0) { - textureImage.classList.remove("no-texture"); - textureImage.classList.remove("no-preview"); - textureImage.classList.add("with-texture"); - image.src = url; - image.style.display = "block"; - } else { - image.src = ""; - image.style.display = "none"; - textureImage.classList.remove("with-texture"); - textureImage.classList.remove("no-preview"); - textureImage.classList.add("no-texture"); - } - }, DEBOUNCE_TIMEOUT * 2); - - textureUrl.oninput = function (event) { - // Add throttle - var url = event.target.value; - imageLoad(url); - self.webBridgeSync(group.id, url); - }; - textureUrl.onchange = textureUrl.oninput; - textureImage.appendChild(image); - parent.appendChild(textureImage); - parent.appendChild(textureUrl); - }, - addSlider: function (parent, group) { - var self = this; - this.addLabel(parent, group); - parent.className += " property range"; - var container = document.createElement("div"); - container.className = "slider-wrapper"; - var slider = document.createElement("input"); - slider.setAttribute("type", "range"); - - var inputField = document.createElement("input"); - inputField.setAttribute("type", "number"); - - container.appendChild(slider); - container.appendChild(inputField); - parent.appendChild(container); - - if (group.type === "SliderInteger") { - inputField.setAttribute("min", group.min !== undefined ? group.min : 0); - inputField.setAttribute("step", 1); - - slider.setAttribute("min", group.min !== undefined ? group.min : 0); - slider.setAttribute("max", group.max !== undefined ? group.max : 10000); - slider.setAttribute("data-max", group.max !== undefined ? group.max : 10000); - slider.setAttribute("step", 1); - - inputField.oninput = function (event) { - // TODO: Remove this functionality? Alan finds it confusing - if (parseInt(event.target.value) > parseInt(slider.getAttribute("max")) && group.max !== 1) { - slider.setAttribute("max", event.target.value); - } - slider.value = event.target.value; - self.webBridgeSync(group.id, slider.value); - }; - inputField.onchange = inputField.oninput; - slider.oninput = function (event) { - inputField.value = event.target.value; - self.webBridgeSync(group.id, inputField.value); - }; - - inputField.id = group.id; - } else if (group.type === "SliderRadian") { - slider.setAttribute("min", group.min !== undefined ? group.min : 0); - slider.setAttribute("max", group.max !== undefined ? group.max : 180); - slider.setAttribute("step", 1); - parent.className += " radian"; - inputField.setAttribute("min", (group.min !== undefined ? group.min : 0)); - inputField.setAttribute("max", (group.max !== undefined ? group.max : 180)); - - inputField.oninput = function (event) { - slider.value = event.target.value; - self.webBridgeSync(group.id, slider.value * RADIANS_PER_DEGREE); - }; - inputField.onchange = inputField.oninput; - - inputField.id = group.id; - slider.oninput = function (event) { - if (event.target.value > 0) { - inputField.value = Math.floor(event.target.value); - } else { - inputField.value = Math.ceil(event.target.value); - } - self.webBridgeSync(group.id, inputField.value * RADIANS_PER_DEGREE); - }; - var degrees = document.createElement("label"); - degrees.innerHTML = "°"; - degrees.style.fontSize = "1.4rem"; - degrees.style.display = "inline"; - degrees.style.verticalAlign = "top"; - degrees.style.paddingLeft = "0.4rem"; - container.appendChild(degrees); - - } else { - // Must then be Float - inputField.setAttribute("min", group.min !== undefined ? group.min : 0); - slider.setAttribute("step", 0.01); - - slider.setAttribute("min", group.min !== undefined ? group.min : 0); - slider.setAttribute("max", group.max !== undefined ? group.max : 1); - slider.setAttribute("data-max", group.max !== undefined ? group.max : 1); - slider.setAttribute("step", 0.01); - - inputField.oninput = function (event) { - // TODO: Remove this functionality? Alan finds it confusing - if (parseFloat(event.target.value) > parseFloat(slider.getAttribute("max")) && group.max !== 1) { - slider.setAttribute("max", event.target.value); - } - - slider.value = event.target.value; - self.webBridgeSync(group.id, slider.value); - // bind web sock update here. - }; - inputField.onchange = inputField.oninput; - slider.oninput = function (event) { - inputField.value = event.target.value; - self.webBridgeSync(group.id, inputField.value); - }; - - inputField.id = group.id; - } - - // UpdateBinding - }, - addCheckBox: function (parent, group) { - var checkBox = document.createElement("input"); - checkBox.setAttribute("type", "checkbox"); - var self = this; - checkBox.onchange = function (event) { - self.webBridgeSync(group.id, event.target.checked); - }; - checkBox.id = group.id; - parent.appendChild(checkBox); - var label = this.addLabel(parent, group); - label.setAttribute("for", checkBox.id); - parent.className += " property checkbox"; - }, - addElement: function (parent, group) { - var self = this; - var property = document.createElement("div"); - property.id = group.id; - - var row = document.createElement("div"); - switch (group.type) { - case "Button": - var button = document.createElement("input"); - button.setAttribute("type", "button"); - button.id = group.id; - if (group.disabled) { - button.disabled = group.disabled; - } - button.className = group.class; - button.value = group.name; - - button.onclick = group.callback; - parent.appendChild(button); - break; - case "Row": - var hr = document.createElement("hr"); - hr.className = "splitter"; - if (group.id) { - hr.id = group.id; - } - parent.appendChild(hr); - break; - case "Boolean": - self.addCheckBox(row, group); - parent.appendChild(row); - break; - case "SliderFloat": - case "SliderInteger": - case "SliderRadian": - self.addSlider(row, group); - parent.appendChild(row); - break; - case "Texture": - self.addTextureField(row, group); - parent.appendChild(row); - break; - case "Color": - self.addColorPicker(row, group); - parent.appendChild(row); - break; - case "Vector": - self.addVector(row, group); - parent.appendChild(row); - break; - case "VectorQuaternion": - self.addVectorQuaternion(row, group); - parent.appendChild(row); - break; - default: - console.log("not defined"); - } - return row; - } -}; \ No newline at end of file diff --git a/scripts/system/particle_explorer/particle-style.css b/scripts/system/particle_explorer/particle-style.css deleted file mode 100644 index cde325f6c6..0000000000 --- a/scripts/system/particle_explorer/particle-style.css +++ /dev/null @@ -1,140 +0,0 @@ -/* -// particle-style.css -// -// Created by Matti 'Menithal' Lahtinen on 21 May 2017 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -*/ - - -.property-group { - max-height: 0; - -webkit-transition: max-height 0.15s ease-out; - transition: max-height 0.15s ease-out; - overflow: hidden; -} -.property-group.visible { - transition: max-height 0.25s ease-in; -} -.section-wrap { - width: 100%; -} -.property { - padding: 0.4rem 0; - margin: 0; -} -.property.checkbox { - margin: 0; -} -.property.range label{ - display: block; -} - -input[type="button"] { - margin: 0.4rem; - min-width: 6rem; -} -input[type="text"] { - margin: 0; -} -.property.range input[type=number]{ - margin-left: 0.8rem; - width: 5.4rem; - height: 1.8rem; -} -input[type=range] { - -webkit-appearance: none; - background: #2e2e2e; - height: 1.8rem; - border-radius: 1rem; -} -input[type=range]::-webkit-slider-thumb { - -webkit-appearance:none; - width: 0.6rem; - height: 1.8rem; - padding:0; - margin: 0; - background-color: #696969; - border-radius: 1rem; -} -input[type=range]::-webkit-slider-thumb:hover { - background-color: white; -} -input[type=range]:focus { /*#252525*/ - outline: none; -} -.tuple label { - text-transform: capitalize; -} -.slider-wrapper { - display: table; - padding: 0.4rem 0; -} -hr.splitter{ - width: 100%; - padding: 0.2rem 0 0 0; - margin: 0; - position: relative; - clear: both; -} -hr.splitter:last-of-type{ - padding:0; -} -#rem { - height: 1rem; - width: 1rem; -} -.property { - min-height: 2rem; -} -.property.vector-section{ - - width: 24rem; -} - -.property.texture { - display: block; -} -.property.texture input{ - margin: 0.4rem 0; -} -.texture-image img{ - padding: 0; - margin: 0; - width: 100%; - height: 100%; - display: none; -} -.texture-image { - display: block; - position: relative; - background-repeat: no-repeat; - background-position: center; - background-size: 100% 100%; - margin-top: 0.4rem; - height:128px; - width: 128px; - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAABhNJREFUeNrsnVFy4joQRVsSCwAqBMwqsrRsIavMEkICoeAf2+8j1R5ZGDBgpLzoUDVVmTT2dc8It/paOpi3t7faOSciImVZyn6/l6qqRETEWivj8VistYPFd7ud1HUtIiLGGBmPx5JKX0RkMplIzvmPnHNijBERaS7Ef1lrB40bY1oXgH5a/ZH+8P7+LlVVycfHR/MGa60URdGcYOi4MUaKomhGaGx9EZHlcplMP2X+Ly8vPwOgLEtxzklVVVJVVfOznqAsy9YFXhuvqqq5AF/Lj+srtr7+LpV+yvz1mNF+vxcRkdVqJdZaeXp6ap1ws9m0TjibzVoj6lJ8vV6fjJdlKev1ujViU+j7t8tc8p9Op1KWpYw06L9JL0Av0r9l+jXl3nhd11JV1VE8tn5YM3PI3xjzoxVOGvyDU7zQj6s/0tGmI++amtNV087F9Wf/FnVPzRtCXz8RdV1nlb/efUbaJy4Wi0FqzjU1yRgjs9ls0Jp3jb6IyPPzczL9lPkvFot/dwCtB/om/x9oyJoXxps65NW8mPpdNTeX/JtBEtYE/+AUL/Tj6g/qA3TVnD41a6g++Bp9rYOp9FPnH80HOBcvy1I2m81D++BL+o/2AX5r/vgA+AD4AOif8AH8EdpVcy71sX3jWp/8W2AKff/TkUv+Oufr9AF0YuKc66xJ18T7eNP3nP9WfZ0EzufzJPqp8y+KQuq67vYBdETqCDpVU/rEw5oUnr+rD46h73/qUuinzh8fAP22D6AjxznXcqq6akrf+KmaFB6vf4+t7/sAelfIJf/GB9jtdmKMkdVq1dQM3zg4VVNU/NY+1Bgjh8Oh6YM1+dj6X19fzXwgp/wbH0DFtS7oyf0RdKqmhPFr+1RdseKfP7a+Px/IKX98APTbPoDOJrv60L417d54TH3V8lfS5pT/yfUA6/X6qOZcqkm3xrUm6X9CTH3fB0ihnzr/Ix9A/3T1qbfWpGvjMfX9T0UK/dT54wOg/88H8EfGPTVr6D740frhLDmn/Hv5AH1qku9t31KTzh3/aP1LPsBfzr+XDxCO0K6ack/N6qp5MfUv+QB/Of/ePsCQfWmfc6EfV3/kjzZrrRwOh9YtKHSm/LjOH3yrMTzej4c1y//51PHoP0a/tR7AOSdFURw9rz5VU049zw7jl2qWrosP++BY+iI/+wJS6afMv9kXoA6gvimsieHzZr/m6MTp3PPuc3G9SP95OPpx9JtOgT4cHwA+QCJ9+ADwAeADsC+AfQHo/4b1APAB4APAB4APAB8APgB9OD4AfAD4AFFqEnwA+AD4APgA6P86HwA+AHyAZhIBHwA+AHwA+AD04X/eB4APAB8APgB8APgA8AHow/P0AeADwAeADwAfAD4AfAD68Px8APgA8AHgA8AHgA8AH0DO70/v6lHvjaOfVn8U/iLcXx5OUML96X49vRTX3/nPw9FPo9+sB5hMJuKck+VyeVRTrLWtdfNdcf95eldNCuOfn5+tSYy/Pz+2voi0fICc8p/P5z93gJAPEN4+wufN4evaePj99eH+ePTj6p/1Abp60kt9Ksf/v46HDwAfAD6A/6gUPgD7AtgXwPP4DNcDwAeADwAfAD4AfAD4ADyPz289AHyA+Pqp84cPIPAB8AHwAfAB8AHgA7Q+HfAB4APAB4APAB+APjw3HwA+AHwA+ADwAeADwAegD8/TB4APAB8APgB8APgA8AHow/PzAeADwAeADwAfAD4AfACJ//316KfVH/mjLeb31+vx/kWhH0+/tR7AOSdFUUT9/nq9oK4+OJa+iLT25+eUf7MvIOQDxPr+en2F++PRj6PfdAr04fgA8AES6cMHgA8AH4B9AewLQP83rAeADwAfAD4AfAD4APAB6MPxAeADwAeIUpPgA8AHwAfAB0D/1/kA8AHgAzSTCPgA8AHgA8AHoA//8z4AfAD4APAB4APAB4APQB+epw8AHwA+AHwA+ADwAeAD0Ifn5wPAB4APAB8APgB8gBz5AOb19bX2TYLpdNpqQ7bbbctJGjJeVZVst9vWLSu2/vf3t+Sc/yicFIRr0C7Fu76f/lw8XBePflr9/wYAqWwWUSLcO54AAAAASUVORK5CYII='); -} - -.texture-image.no-texture { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAB81JREFUeNrsnTGPm0oXht97FWm2Ch2pTEeHpUihsyvTuXO67Ta/IPkr+Qfp3MWdO7Zad0SKZDo6XIWOrTzV9xVXZ8SygGHXG4/t96lW68GGw8vMmZlzDv98+/btfyBXy780wXXzTv74/fs3rXFFfPz4kT0AoQAoAJqAAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQCgAQgEQCoBQAIQCIBQAoQAIBUAoAHLmvDv3C7i7u4PjOMiyDOv1+mC75XKJoiga2wRBAN/34TgOHMdBWZYoigJpmiLPcwrARhzHAQD4vg/P81pvlLRrwvM8zGYz00ZrbY5xHAe+7yPPc9zf36MsSwrAVmazGX78+DHoGM/zsFgsAAB5nmOz2ZgeQimF8XiMMAxNu+VyaQRCH8Ai8jyH4zgIw7D3MUopzOdzAECaplitVk+GB601kiTBz58/obWG4ziIoohOoI38+vULABCGYWd3X2U6nUIphbIsEcdxa7uiKPDw8GCGGtd1KQDbKMsSWZZBKYXJZNLrGN/3zdN/iDRNTdcvx1EAFqGUwmazeeIQduG6LpRSAIAsy3r9hrRjD2BxL5AkiXEI+8wetNa9PXtp13eIoQBOQJIkxmHrcgjlJkov8JKpJwVgIVpr47CFYdh6g/f7/ZM5/9CehgKwmDRNURQFlFKYTqeNN/rx8dH0AH2faBn7KYAzQKZ1QRCYZd0qf/78MX+PRqNe3ymO5W63owBsR9bwZShoGirEq++zeBQEweBZAwVwYh4eHqC1RhAErQ6jOHVdK3yu65qhJE1TDgHn5BDKTW6auxdFYdYOgiDAYrF40k4phTAM8fnzZyilUBRF54rhOfIOF06SJMYPaPt8v99jOp3C8zx4nget9bPZQ5ZlF3fzL0IAZVke9OLv7+/Njl/brCHLMozHY4xGI3z48MH0EEVRIMuyi40H+EdqBbNS6HXBSqGEAiAUAAVAE1AAhAIgFAChAAgFQCgAQgGQq+Eom0GLxeJgGHYVSdCUhM02yrI0qV5hGGIymaAsy9b0LNd1cXt7CwDYbDa98wOA/zKLVquVSQGr/nYTbe2iKDIh53JtZVmiLEvsdjtst9tn5z7EDmfXA3QFXdaTMbvYbrdm568tgkdueJ7njbt3QwJA+8YJ1tsFQQDXdXFzc2N2E0Uwk8kEX758eXbMEDtY2QOsVqtn//v69SsAYL1eH9xK7dNGgjuiKMJ4PH4WmSN7+QBMFu/3798bn1oAzz47NvVrqmYgz2azRpv1scNV+wDVaN969y6JIEmSWBmyJenlIgZbcgvOzgmUqJxqkmY18ldCvGwkz/MntQcogBcgETrVMV98Aptvfh1JTKEAXsBms4HWGp7nYT6fw3Ec5Hlufbi253lQSkFr3VqmhgLoQVmW2G63ZigQx8/2my/FKCR17WLWAV7LfD5vzOFLkqS1W0/T1HT9RVFY5/jNZjMz3ouvorVGHMet9QheYoer7AGq478Y2LaiDTc3N3Bd90megSwG2YQVPcDQ+a/ccK01ttutWSWsetl/i7bfq16TzP1lGFgul0exw9X2AJLGJV3joRXCl3rnXbUDhmQKl2WJ9XoNrbV1vdXZCUCWWqvVQGR8HFIgqmuaKUiCSJcA+nrzWmvzdA/ZN6EAKlTz/eXmA3iSuXOoNEzfBRsA+PTpU+PnUjxSfnvo9/ZNR6cAakjFj2rqd3VtQJ6u1z5h1e+SdYbqdK5aWHLImC0OoFQgpRN4YPoD/LfRVC8C2TQlkhVC3/dfVDG0/l1xHCOKIvi+b572atJoURSdtYnbfAHxV0aj0TP/oY8dzqYH6OscHXK26tO+rqcujmNTIKqtJkDfc0vTFMvl8smu436/R57niOO4NSbh0HfLkFHtpYbY4dgwOfRKYXIooQAIBUAB0AQUAKEACAVAKABCARAKgFAA5Gp4s93AKIrw/v17ExsnFEWB/X6P3W6HLMtaN0+GJkwOad+W2FlPLq3GHFSRdq85h2PYyGoByG6cvJOnHiEryZJSg7e+s1ZNmOyzSza0ffWYJsIwbMzk7Tp+6Dm81kZWC0BoCnSU7dowDE2K12q1alT60EDJYwVWKqUQRdHgPf9jnfMQG52dDyA5fLKnLlGztiB5Bn1eP3fuNvr31IaWZM9jhHIdEwk5G1Jk4hxtdPJZQJZlJrLWlnBpx3FMmrnrup3RReduIyumgXJxtryRUxw4mQXIO4Yv0UZWCMDWN3I2vX7u0mxk1RtDmp6yoQmTbe27kjK7iOMYt7e3CIIA2+22VyLIWyZ5Hrsnsmol0Jac+fo51QtSXJKNrOgBuvLsTrUOUO8FxAP3ff/gTXiLc3irt5aevAdQSpmpja0vZqq+fm4ymfz18i5vaaOTC0DSvapv8rQRmRY6joPxeHwxNjqpAGSpUwx8ikKJQ5AyNFKb4BJsdBIfwPM8BEFgFjXSNG3debMJSUv7GyuWf8tGby6Aaq2c+qvaJce/a3p2ioTJQ73A3d3di6aBbef8WhtZKQDJ6K1fTJ7neHx8PFjWTcbbvvPePm8QbVtc6ft/+UwKUdfbDT3n19roGDA59EphciihAAgFQAHQBBQAoQAIBUAoAEIBEAqAUACEAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQC4CkxgiceKEPQC5Iv4/APgB2O7x8IXXAAAAAElFTkSuQmCC'); -} - -.texture-image.no-preview { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAA8sSURBVHhe7Z3rbxXFG8d7B9SWthRabLmIYlHkIEXKJdXYBEXxHtEXprwxxsR3/jG+8PLCaDDGeAkmKsTEoCUVKoVCA6WNtLS2UEUKBSy0tKW/D+eZM9nu7tmz55z+mC2Zz4tl9tk5c2bnO/PMM2dnS+6nn36aYzFH7vvvv6+SFhMoAY4fPy7nljvG448/zjFPTiymsAIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjmLhQgPz+/pKRk3rx56jzaRHFf0ObNmxctWkTi7Nmzp0+fFqNm+/btRUVFP/30kzp3UFtbu27duqVLl+bl3e5Y169f7+rqam1tvXnzpmSIFNHdF1RTU7M6TkNDQ0FBgbImWLVqFZfUSQKyvfzyy88991x1dfXU1NSFCxdGRkbuueeeurq6pqam0tJSlS96RNcFSQvSo9V5IC+88MIDDzwwOjr6448/fvTRR19++eVnn322Z8+ev//+u7i4+M0331ywYIHKGjGiK8Aff/zBMRaL5ebmiiUZjz322MqVK/Ez33333ZkzZxgBYh8eHt67d++lS5do/W3btokxakRXANxIf38/3mPNmjXKlARxpkeOHKGtxaIZHx9vaWkhwfTg9WZRILoCgIQG0r7JKC8vlxm7s7NTLC6YyW/cuFFYWIiPUqYoEWkB+vr6cOJLlizBwyiTB2l9vA0xj1hcTE9PDw4OkiA6EkukiLQAcOzYMY4bN26UUy8LFy7k+O+//8qpL1euXOF43333yWmkiLoATKqEQwSmlZWVyjQTIiWOwZG+npYjSNQFwIG0tbWRqK+vF4sL1r0qlZzJyUmOYXLeeaIuAHR3d+PfmQbE27hgguUY3LgS/0RzHMwBAei/R48ezcvL8x0EOCiOxEJy6osoJ1JFjTkgAHR0dExMTBDLexe0EvsTKQUMgsWLF3OUWChqzA0BGARoQBN7wyHWa6Ojo1x6+OGHlWkmaEOoeuvWrXPnzilTlJgbAgBeiEEQi8W8Pf3kyZMct27d6v0JGsf15JNPkmA5lmyhYJY5IwAenNmYBW1RUZEyJSBMYiYoLi7etWtXWVmZsubkkHPHjh2EsCjX3NysrBFjzggANDSeRJ04wEF9//33rLYqKip27979yiuvNDY2Pvvss2+//TZ+ieBn//79V69eVbkjRv6WLVv4hxW/nEcB+iyuo6ura3x8XJnicIqToV8zGpgSlDXO2NhYZ2cnV+WnIVZtTLxEn+fPn9+3b180p9+qqiqOd9ub8ihH67M8xuPT65mf1YXocXe+KY+PGhoa6unp4Rjl1tfcbQLMOawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGyfy3oNdff72mpkadJLh27Vpvb29LS8vExIRYdu7c6dpLOz09ffPmTXLypadOnVLWnJzGxsZYLKZOPHR0dDQ3N7/33nv5+fkff/yx7/PFBQsWvPPOO5T/4YcfLly4sKmpaXBw8Ntvv5Wr7777bsAOUbINDw+Th5IpX1kTyGcPHz7c2tqqTHG4NW7wzz//9N2tHczs/BY0NjZ2PQFVLy4uXr9+/UsvvaQuJxgfH1eZ4tkKCwsrKiq2b9/u3XbozOkEzaamps6ePUueZHvcsOfl5ZFHtkH4oorzQOFU7MqVKzS0S6fy8nKxeDvckiVLOGbza2u22yW/+eYbOo46ie9Te/XVV5ctW7Z8+fK//vpLWXNyfvjhB2ctaaaGhoYNGzZs3bq1q6tLWeP88ssvdCh14oFLDz30EA3tuxFRhBGRkvHJJ5+olB8XLlxg6NCs/f39ypRo93/++Wfp0qWMP+fuCnna7N2TGp5ZngMQ48iRIyQefPBBsfhy69atgwcPjo6OlpSU+G42SQaicv80tPfBJBbslBwsQDBDQ0McpVk1CMBAx2HyFa79jUhFfeRTmTH7k7DsEky5DxBPffHiRRKytS0kNMTAwAAN4d0tigX7+fPnfaeHkEjlxbFoEIAvlTFRXV0tRhBnNTIy4hwT6TL7Asgz2zBvBUlO/K+chkQc1IoVK+RUI5YzZ87IaWZIX3buMpIJAP+Jroxv5zQgOmW52WL2BZDtyv/995+cJkMeHHJX6T42wcPgZ5gJ1HkCsWTjf4C+TCuXlpZqFyctLl6etpZpIH5F6eScAjNglgVg+n3iiSdIuHoiI/f2S19xamtrN23a9NprrzEVt7W1uSKWtWvXPu2HuhzfHkF/pFfef//9ypSTQxoLPi3lw3dV3Ez4UnU5/nicJpZuBAigvTzfyyU9DWQfAkG2UdCLL76oPeC99947f/58Et3d3cQMYhTk0b8TejGhfXt7uzpPgCfxuhf49ddfVSonp6enhyhr1apVeHyxkOYYxv8QJauUA9yaXpEQCKEH8zAJThGA1pd7lLamM0mCPNhl73vGZDsCGK10FgGffvnyZZYqP//8s7qcgCY7EUemMvz+F198ceDAAaZiyaA5duwYixov6nIcaWhpdEHSfIucBqCKm4m8hSDIBhHp3URoMgHEr9wefHoaYChw71qbjMlWgK+//pp1o/DBBx98/vnnLBfp3epyAmI4ujDs3bv3t99+I/J5/vnnfd++4/7pj17U5TjohzsuKysTL8yRNM5HwqpgVHEzce7KoYlpUynZO83qaYAOxzGbFYCQrQAsXOkXgrc7+4IYuA5WwgHvvaSEVuMoKy859vb23r6QNbQ+zof2Je2cAAQ9DYhCWU4AMPtRUBhko2B9fX1aiwAnEu3IakCOYfxPSFgN4HnwP7h7xHA6GT0NyFScZQgEZgRgimYyKCwsrKurU6Y0weHIbwO0FEfGX5bxuBPp8kR0jAPX22d8EY2Oa6qqqiJt3gVlzKFDhzjGYjFaUCzpgs/BGzQ2NnJkWg7pAMMg8Y/8Wul1Mn19fUiONtl3fzAmAP0XN8IgcM0EGzZs2JkElSOBTAMsLDiGnwBUWR74XpUjvuxiJS/TgK8AdBpUz34CAGMCgPy27hoEdC5Zr3lRORIQ8krYMzExMTAwIMaUqLI8iE/XyCCgj+NnxKLRoWf2/gcyfyBDGDNv3jw6csCP70C0QPvSUq6tzgKelK5EUxJZElazlFMX/PB6efkIJXsD0IKCgsrKSuclmpi1t6S9uBy6lJzMy1My5ae892DExdn/R8wYd+fu6DmHFcAwVgDDWAEMYwUwjBXAMFYAw1gBDGMFMIwVwDBp/xSxZs2aqqqqsbGxw4cPK1PiD2W0t7cne0K9ePHitWvXXr9+Xf4aKFRWVj7yyCMkKIfSxKgpLS1lpT4yMqIrxinGU6dOBf95OGH16tXV1dWuSmrkmbs6iTM5OXnjxo2enh7560Oap+O7MZz7AVzIF6kTPwI+m+FPEbT1+vXrN2/eXFJSokzxfXAYH330UXXuYd26dWRw/uoZi8WwgPPZukYKdO5vJI0FDdR5IL6V1KxYseL2FzvYuHFjQ0NDU1OTa7uRXFUnftTU1EieZKh8yUlPALott3T58mXSiC9GkJ/mA/aDyo1JNsjPz6fdr169OjU15SxnVqioqCgrK/NW0oXefrF///4DBw5QN2r1zDPPFBcXqxyhOXnypBTlReVITnoCyP20tLS4Gq6/v58hvGjRIudfi9HIrqnR0VG9jWfZsmXz58/nnoeGhiQt9llBVxIXFCCA3n7R3d3d0dFBY3EXRUVF4hjTAq8oRXlROZKTtgATExN9fX0DAwMyGsQ+PT0te3V8b1iMztqIpbe3l6JkNIh9VtCVpEGdlUyJPOjnI3J6Z0hDALkZbozuL63pbG6vReMSQFqcEcOACPhUZoj/kUrKPonwhcvTlTDbimeRNASQt1mkp9N5uUPn+y2Dg4M4Ge7f1eOQTR4taf+zcuVKfI6UI5sbli9f7pyfs0GaWwpnmLoqGYxswwr/dHNWSEMA7o37kfdecK+4b+luchUv5NudnS0iiEU/Rmfg5+XlBb/QEZ7gSjoh0CpPwOy1adMmQrVz58653tgJAz1MFTQT79+w8xJWACZSvobeoWN2r9MXAWSfmkb8u8v/UIjuaOk6igCkrYMrqXnqqad2JyAA3bZtG8N037593n2VKamvr1cFzaS2tlblSE5YAeQenLvPpJc57w0ng0thYaL3u0mLcGN6Bwf+p7CwkOmRfiqWixcv4rsIqLP3QmEqqRkeHqZWQK8njMH1U+233nor5FLDCcs3KcpFypckIOz2dLkHhiqrG7EAlZYmlqAb6Oksaoj65W+6iWOhG+pdU1IOGjjLQSGGF5nlD1BmTMhKCq2trXpcAkOT5RuV37Fjx1dffaWs4Whvb3f9DbvwhBoBdE8aiASr5y0O5B0j519MlVvSDt21/iooKBCPxFEVEYcGwhhmwAYgrUwiZSV9YUQeOnQI31VVVZXWe4NZEkoAqT3tyIrRibwQ6Ww4Qho6mvgTmoNG4ZZ0/EO70/cZ7+rzDojc+VTGe3VBur+3kvq/MInnCgINqD+JDLxQxqQWIDc3VzoyHYSB5uT333/HfUtDS2agCYhqWN8CpxKwyiVpI/XhmUhQJBkyQz7rrWRbWxvu3lXJZMhw0RW+A6QWQLoz9+DyoYI3hmFlzxHN+CAJp/+RAMk5SWqyjIXE/ySrJOsyjikLp+OzaiEKohxl+v+TWgCpt2+rgTfOu3TpEoENrQ/OcBP/w0RHyMGUKxYnrAbod84IyheCa/K4YH4KrqSvAK6i6urq3njjDcbu6dOnXTVUOWZCf1KX48opqweZOwNIEQVp/6PXTS7w77SyDHC9C5NeT0RBorOz0+V/5PcWL5OTk0hFkEq2EydOKKsHJlWVcoCjl8KTVVJUd1XStyjmp4MHD6qTBLt27VIpB3v27NEDZUMcSbugbrhBdeJHij9dTDyAvFQrWaMQXyLS+Pj4tWvX9PAn/kV5hgJhJXYxMgLIQDm+u3SBeZgOKJM2/YuhwJSoN+SWlJTQiJTphTZlzRlQSXBWkjUwsan6cBy+iLD9+PHjzc3Nzv22RLQqhwfEphBukx6mTH6wEEn2kOru/NPFc4gMn4hZZhcrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYdS2FIsp7AgwSk7O/wCqCi/+JioQYgAAAABJRU5ErkJggg=='); -} - -#properties-list > fieldset { - margin-top: 0px; -} - -#main-header { - margin-bottom: 21px; -} - -.section-wrap { - padding: 21px 0px; -} \ No newline at end of file diff --git a/scripts/system/particle_explorer/particleExplorer.html b/scripts/system/particle_explorer/particleExplorer.html deleted file mode 100644 index ab4c249cc3..0000000000 --- a/scripts/system/particle_explorer/particleExplorer.html +++ /dev/null @@ -1,43 +0,0 @@ -<!-- -// particleExplorer.hml -// -// -// Created by James B. Pollack @imgntn on 9/26/2015 -// Copyright 2015 High Fidelity, Inc. -// - -// Reworked by Menithal on 20/5/2017 -// Using a custom built system for High Fidelity -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - --> -<html> -<head> - <link rel="stylesheet" type="text/css" href="../html/css/colpick.css"> - <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script> - <script type="text/javascript" src="../html/js/eventBridgeLoader.js"></script> - <!----> - <script type="text/javascript" src="../html/js/jquery-2.1.4.min.js"></script> - <script type="text/javascript" src="../html/js/colpick.js"></script> - - <script type="text/javascript" src="underscore-min.js"></script> - <script type="text/javascript" src="hifi-entity-ui.js?v1"></script> - - <link rel="stylesheet" type="text/css" href="../html/css/hifi-style.css"> - <link rel="stylesheet" type="text/css" href="../html/css/edit-style.css"> - <link rel="stylesheet" type="text/css" href="../html/css/colpick.css"> - <link rel="stylesheet" type="text/css" href="particle-style.css"> -</head> -<body> - <div id="properties-list"> - <div class="section-header" id="main-header"> - <label> Particle Explorer </label> - </div> - <!-- This will be filled by the script! --> - </div> - <div id="rem"></div> - <script type="text/javascript" src="particleExplorer.js"></script> -</body> -</html> diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js deleted file mode 100644 index f1b7c8600f..0000000000 --- a/scripts/system/particle_explorer/particleExplorer.js +++ /dev/null @@ -1,485 +0,0 @@ -// -// particleExplorer.js -// -// Created by James B. Pollack @imgntn on 9/26/2015 -// Copyright 2017 High Fidelity, Inc. -// -// Reworked by Menithal on 20/5/2017 -// Reworked by Daniela Fontes and Artur Gomes (Mimicry) on 12/18/2017 -// -// Web app side of the App - contains GUI. -// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -/* global HifiEntityUI, openEventBridge, console, EventBridge, document, window */ -/* eslint no-console: 0, no-global-assign: 0 */ - -(function () { - - var root = document.getElementById("properties-list"); - - window.onload = function () { - var ui = new HifiEntityUI(root); - var textarea = document.createElement("textarea"); - var properties = ""; - var menuStructure = { - General: [ - { - type: "Row", - id: "export-import-field" - }, - { - id: "show-properties-button", - name: "Show Properties", - type: "Button", - class: "blue", - disabled: true, - callback: function (event) { - var insertZone = document.getElementById("export-import-field"); - var json = ui.getSettings(); - properties = JSON.stringify(json); - textarea.value = properties; - if (!insertZone.contains(textarea)) { - insertZone.appendChild(textarea); - insertZone.parentNode.parentNode.style.maxHeight = - insertZone.parentNode.clientHeight + "px"; - document.getElementById("export-properties-button").removeAttribute("disabled"); - textarea.onchange = function (e) { - if (e.target.value !== properties) { - document.getElementById("import-properties-button").removeAttribute("disabled"); - } - }; - textarea.oninput = textarea.onchange; - document.getElementById("show-properties-button").value = "Hide Properties"; - } else { - textarea.onchange = function () {}; - textarea.oninput = textarea.onchange; - textarea.value = ""; - textarea.remove(); - insertZone.parentNode.parentNode.style.maxHeight = - insertZone.parentNode.clientHeight + "px"; - document.getElementById("export-properties-button").setAttribute("disabled", true); - document.getElementById("import-properties-button").setAttribute("disabled", true); - document.getElementById("show-properties-button").value = "Show Properties"; - } - } - }, - { - id: "import-properties-button", - name: "Import", - type: "Button", - class: "blue", - disabled: true, - callback: function (event) { - ui.fillFields(JSON.parse(textarea.value)); - ui.submitChanges(JSON.parse(textarea.value)); - } - }, - { - id: "export-properties-button", - name: "Export", - type: "Button", - class: "red", - disabled: true, - callback: function (event) { - textarea.select(); - try { - var success = document.execCommand('copy'); - if (!success) { - throw "Not success :("; - } - } catch (e) { - print("couldnt copy field"); - } - } - }, - { - type: "Row" - }, - { - id: "isEmitting", - name: "Is Emitting", - type: "Boolean" - }, - { - type: "Row" - }, - { - id: "lifespan", - name: "Lifespan", - type: "SliderFloat", - min: 0.01, - max: 10 - }, - { - type: "Row" - }, - { - id: "maxParticles", - name: "Max Particles", - type: "SliderInteger", - min: 1, - max: 10000 - }, - { - type: "Row" - }, - { - id: "textures", - name: "Textures", - type: "Texture" - }, - { - type: "Row" - } - ], - Emit: [ - { - id: "emitRate", - name: "Emit Rate", - type: "SliderInteger", - max: 1000, - min: 1 - }, - { - type: "Row" - }, - { - id: "emitSpeed", - name: "Emit Speed", - type: "SliderFloat", - max: 5 - }, - { - id: "speedSpread", - name: "Speed Spread", - type: "SliderFloat", - max: 5 - }, - { - type: "Row" - }, - { - id: "emitDimensions", - name: "Emit Dimension", - type: "Vector", - defaultRange: { - min: 0, - step: 0.01 - } - }, - { - type: "Row" - }, - { - id: "emitOrientation", - unit: "deg", - name: "Emit Orientation", - type: "VectorQuaternion", - defaultRange: { - min: 0, - step: 0.01 - } - }, - { - type: "Row" - }, - { - id: "emitterShouldTrail", - name: "Emitter Should Trail", - type: "Boolean" - }, - { - type: "Row" - } - ], - Radius: [ - { - id: "particleRadius", - name: "Particle Radius", - type: "SliderFloat", - max: 4.0 - }, - { - type: "Row" - }, - { - id: "radiusSpread", - name: "Radius Spread", - type: "SliderFloat", - max: 4.0 - }, - { - type: "Row" - }, - { - id: "radiusStart", - name: "Radius Start", - type: "SliderFloat", - max: 4.0 - }, - { - type: "Row" - }, - { - id: "radiusFinish", - name: "Radius Finish", - type: "SliderFloat", - max: 4.0 - }, - { - type: "Row" - } - ], - Color: [ - { - id: "color", - name: "Color", - type: "Color", - defaultColor: { - red: 255, - green: 255, - blue: 255 - }, - layoutType: "hex", - layoutColorScheme: "dark", - useSubmitButton: false - }, - { - type: "Row" - }, - { - id: "colorSpread", - name: "Color Spread", - type: "Color", - defaultColor: { - red: 0, - green: 0, - blue: 0 - }, - layoutType: "hex", - layoutColorScheme: "dark", - useSubmitButton: false - }, - { - type: "Row" - }, - { - id: "colorStart", - name: "Color Start", - type: "Color", - defaultColor: { - red: 255, - green: 255, - blue: 255 - }, - layoutType: "hex", - layoutColorScheme: "dark", - useSubmitButton: false - }, - { - type: "Row" - }, - { - id: "colorFinish", - name: "Color Finish", - type: "Color", - defaultColor: { - red: 255, - green: 255, - blue: 255 - }, - layoutType: "hex", - layoutColorScheme: "dark", - useSubmitButton: false - }, - { - type: "Row" - } - ], - Acceleration: [ - { - id: "emitAcceleration", - name: "Emit Acceleration", - type: "Vector", - defaultRange: { - step: 0.01 - } - }, - { - type: "Row" - }, - { - id: "accelerationSpread", - name: "Acceleration Spread", - type: "Vector", - defaultRange: { - step: 0.01 - } - }, - { - type: "Row" - } - ], - Alpha: [ - { - id: "alpha", - name: "Alpha", - type: "SliderFloat", - max: 1.0 - }, - { - type: "Row" - }, - { - id: "alphaSpread", - name: "Alpha Spread", - type: "SliderFloat", - max: 1.0 - }, - { - type: "Row" - }, - { - id: "alphaStart", - name: "Alpha Start", - type: "SliderFloat", - max: 1.0 - }, - { - type: "Row" - }, - { - id: "alphaFinish", - name: "Alpha Finish", - type: "SliderFloat", - max: 1.0 - }, - { - type: "Row" - } - ], - Spin: [ - { - id: "particleSpin", - name: "Particle Spin", - type: "SliderRadian", - min: -360.0, - max: 360.0 - }, - { - type: "Row" - }, - { - id: "spinSpread", - name: "Spin Spread", - type: "SliderRadian", - max: 360.0 - }, - { - type: "Row" - }, - { - id: "spinStart", - name: "Spin Start", - type: "SliderRadian", - min: -360.0, - max: 360.0 - }, - { - type: "Row" - }, - { - id: "spinFinish", - name: "Spin Finish", - type: "SliderRadian", - min: -360.0, - max: 360.0 - }, - { - type: "Row" - }, - { - id: "rotateWithEntity", - name: "Rotate with Entity", - type: "Boolean" - }, - { - type: "Row" - } - ], - Polar: [ - { - id: "polarStart", - name: "Polar Start", - unit: "deg", - type: "SliderRadian" - }, - { - type: "Row" - }, - { - id: "polarFinish", - name: "Polar Finish", - unit: "deg", - type: "SliderRadian" - }, - { - type: "Row" - } - ], - Azimuth: [ - { - id: "azimuthStart", - name: "Azimuth Start", - unit: "deg", - type: "SliderRadian", - min: -180, - max: 0 - }, - { - type: "Row" - }, - { - id: "azimuthFinish", - name: "Azimuth Finish", - unit: "deg", - type: "SliderRadian" - }, - { - type: "Row" - } - ] - }; - ui.setUI(menuStructure); - ui.setOnSelect(function () { - document.getElementById("show-properties-button").removeAttribute("disabled"); - document.getElementById("export-properties-button").setAttribute("disabled", true); - document.getElementById("import-properties-button").setAttribute("disabled", true); - }); - ui.build(); - var overrideLoad = false; - if (openEventBridge === undefined) { - overrideLoad = true, - openEventBridge = function (callback) { - callback({ - emitWebEvent: function () {}, - submitChanges: function () {}, - scriptEventReceived: { - connect: function () { - - } - } - }); - }; - } - openEventBridge(function (EventBridge) { - ui.connect(EventBridge); - }); - if (overrideLoad) { - openEventBridge(); - } - }; -})(); diff --git a/scripts/system/particle_explorer/particleExplorerTool.js b/scripts/system/particle_explorer/particleExplorerTool.js deleted file mode 100644 index a3be004329..0000000000 --- a/scripts/system/particle_explorer/particleExplorerTool.js +++ /dev/null @@ -1,144 +0,0 @@ -// -// particleExplorerTool.js -// -// Created by Eric Levin on 2/15/16 -// Copyright 2016 High Fidelity, Inc. -// Adds particleExplorer tool to the edit panel when a user selects a particle entity from the edit tool window -// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -/* global ParticleExplorerTool */ - - -var PARTICLE_EXPLORER_HTML_URL = Script.resolvePath('particleExplorer.html'); - -ParticleExplorerTool = function(createToolsWindow) { - var that = {}; - that.activeParticleEntity = 0; - that.updatedActiveParticleProperties = {}; - - that.createWebView = function() { - that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - that.webView.setVisible = function(value) {}; - that.webView.webEventReceived.connect(that.webEventReceived); - createToolsWindow.webEventReceived.addListener(this, that.webEventReceived); - }; - - function emitScriptEvent(data) { - var messageData = JSON.stringify(data); - that.webView.emitScriptEvent(messageData); - createToolsWindow.emitScriptEvent(messageData); - } - - that.destroyWebView = function() { - if (!that.webView) { - return; - } - that.activeParticleEntity = 0; - that.updatedActiveParticleProperties = {}; - - emitScriptEvent({ - messageType: "particle_close" - }); - }; - - function sendParticleProperties(properties) { - emitScriptEvent({ - messageType: "particle_settings", - currentProperties: properties - }); - } - - function sendActiveParticleProperties() { - var properties = Entities.getEntityProperties(that.activeParticleEntity); - if (properties.emitOrientation) { - properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation); - } - // Update uninitialized variables - if (isNaN(properties.alphaStart)) { - properties.alphaStart = properties.alpha; - } - if (isNaN(properties.alphaFinish)) { - properties.alphaFinish = properties.alpha; - } - if (isNaN(properties.radiusStart)) { - properties.radiusStart = properties.particleRadius; - } - if (isNaN(properties.radiusFinish)) { - properties.radiusFinish = properties.particleRadius; - } - if (isNaN(properties.colorStart.red)) { - properties.colorStart = properties.color; - } - if (isNaN(properties.colorFinish.red)) { - properties.colorFinish = properties.color; - } - if (isNaN(properties.spinStart)) { - properties.spinStart = properties.particleSpin; - } - if (isNaN(properties.spinFinish)) { - properties.spinFinish = properties.particleSpin; - } - sendParticleProperties(properties); - } - - function sendUpdatedActiveParticleProperties() { - sendParticleProperties(that.updatedActiveParticleProperties); - that.updatedActiveParticleProperties = {}; - } - - that.webEventReceived = function(message) { - var data = JSON.parse(message); - if (data.messageType === "settings_update") { - var updatedSettings = data.updatedSettings; - - var optionalProps = ["alphaStart", "alphaFinish", "radiusStart", "radiusFinish", "colorStart", "colorFinish", "spinStart", "spinFinish"]; - var fallbackProps = ["alpha", "particleRadius", "color", "particleSpin"]; - for (var i = 0; i < optionalProps.length; i++) { - var fallbackProp = fallbackProps[Math.floor(i / 2)]; - var optionalValue = updatedSettings[optionalProps[i]]; - var fallbackValue = updatedSettings[fallbackProp]; - if (optionalValue && fallbackValue) { - delete updatedSettings[optionalProps[i]]; - } - } - - if (updatedSettings.emitOrientation) { - updatedSettings.emitOrientation = Quat.fromVec3Degrees(updatedSettings.emitOrientation); - } - - Entities.editEntity(that.activeParticleEntity, updatedSettings); - - var entityProps = Entities.getEntityProperties(that.activeParticleEntity, optionalProps); - - var needsUpdate = false; - for (var i = 0; i < optionalProps.length; i++) { - var fallbackProp = fallbackProps[Math.floor(i / 2)]; - var fallbackValue = updatedSettings[fallbackProp]; - if (fallbackValue) { - var optionalProp = optionalProps[i]; - if ((fallbackProp !== "color" && isNaN(entityProps[optionalProp])) || (fallbackProp === "color" && isNaN(entityProps[optionalProp].red))) { - that.updatedActiveParticleProperties[optionalProp] = fallbackValue; - needsUpdate = true; - } - } - } - - if (needsUpdate) { - sendUpdatedActiveParticleProperties(); - } - - } else if (data.messageType === "page_loaded") { - sendActiveParticleProperties(); - } - }; - - that.setActiveParticleEntity = function(id) { - that.activeParticleEntity = id; - sendActiveParticleProperties(); - }; - - return that; -}; diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 6d8ba3a927..94117fd9ea 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -37,7 +37,7 @@ function notificationPollCallback(userStoriesArray) { // pingPong = !pingPong; var totalNewStories = 0; - var shouldNotifyIndividually = !ui.isOpen && ui.notificationInitialCallbackMade; + var shouldNotifyIndividually = !ui.isOpen && ui.notificationInitialCallbackMade[0]; userStoriesArray.forEach(function (story) { if (story.audience !== "for_connections" && story.audience !== "for_feed") { @@ -91,7 +91,7 @@ function notificationPollCallback(userStoriesArray) { shouldShowDot = totalNewStories > 0 || (totalStories > 0 && shouldShowDot); ui.messagesWaiting(shouldShowDot && !ui.isOpen); - if (totalStories > 0 && !ui.isOpen && !ui.notificationInitialCallbackMade) { + if (totalStories > 0 && !ui.isOpen && !ui.notificationInitialCallbackMade[0]) { message = "There " + (totalStories === 1 ? "is " : "are ") + totalStories + " event" + (totalStories === 1 ? "" : "s") + " to know about. " + "Open GOTO to see " + (totalStories === 1 ? "it" : "them") + "."; @@ -122,12 +122,12 @@ function startup() { sortOrder: 8, onOpened: gotoOpened, home: GOTO_QML_SOURCE, - notificationPollEndpoint: endpoint, - notificationPollTimeoutMs: 60000, - notificationDataProcessPage: notificationDataProcessPage, - notificationPollCallback: notificationPollCallback, - notificationPollStopPaginatingConditionMet: isReturnedDataEmpty, - notificationPollCaresAboutSince: false + notificationPollEndpoint: [endpoint], + notificationPollTimeoutMs: [60000], + notificationDataProcessPage: [notificationDataProcessPage], + notificationPollCallback: [notificationPollCallback], + notificationPollStopPaginatingConditionMet: [isReturnedDataEmpty], + notificationPollCaresAboutSince: [false] }); } diff --git a/server-console/src/main.js b/server-console/src/main.js index f731bacaa6..dc3fbd4333 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -874,10 +874,6 @@ function onContentLoaded() { hasShownUpdateNotification = true; } }); - notifier.on('click', function(notifierObject, options) { - log.debug("Got click", options.url); - shell.openExternal(options.url); - }); } deleteOldFiles(logPath, DELETE_LOG_FILES_OLDER_THAN_X_SECONDS, LOG_FILE_REGEX); diff --git a/server-console/src/modules/hf-notifications.js b/server-console/src/modules/hf-notifications.js index b1f337bbc3..8a812625b4 100644 --- a/server-console/src/modules/hf-notifications.js +++ b/server-console/src/modules/hf-notifications.js @@ -5,6 +5,8 @@ const process = require('process'); const hfApp = require('./hf-app'); const path = require('path'); const AccountInfo = require('./hf-acctinfo').AccountInfo; +const url = require('url'); +const shell = require('electron').shell; const GetBuildInfo = hfApp.getBuildInfo; const buildInfo = GetBuildInfo(); const osType = os.type(); @@ -154,8 +156,13 @@ function HifiNotifications(config, menuNotificationCallback) { var _menuNotificationCallback = menuNotificationCallback; notifier.on('click', function (notifierObject, options) { - StartInterface(options.url); - _menuNotificationCallback(options.notificationType, false); + const optUrl = url.parse(options.url); + if ((optUrl.protocol === "hifi:") || (optUrl.protocol === "hifiapp:")) { + StartInterface(options.url); + _menuNotificationCallback(options.notificationType, false); + } else { + shell.openExternal(options.url); + } }); } diff --git a/tests-manual/gpu/src/TestFbx.cpp b/tests-manual/gpu/src/TestFbx.cpp index 538bb0a973..9253f8bc91 100644 --- a/tests-manual/gpu/src/TestFbx.cpp +++ b/tests-manual/gpu/src/TestFbx.cpp @@ -100,12 +100,12 @@ bool TestFbx::isReady() const { void TestFbx::parseFbx(const QByteArray& fbxData) { QVariantHash mapping; - FBXGeometry* fbx = readFBX(fbxData, mapping); + HFMModel* hfmModel = readFBX(fbxData, mapping); size_t totalVertexCount = 0; size_t totalIndexCount = 0; size_t totalPartCount = 0; size_t highestIndex = 0; - for (const auto& mesh : fbx->meshes) { + for (const auto& mesh : hfmModel->meshes) { size_t vertexCount = mesh.vertices.size(); totalVertexCount += mesh.vertices.size(); highestIndex = std::max(highestIndex, vertexCount); @@ -123,7 +123,7 @@ void TestFbx::parseFbx(const QByteArray& fbxData) { std::vector<DrawElementsIndirectCommand> parts; parts.reserve(totalPartCount); _partCount = totalPartCount; - for (const auto& mesh : fbx->meshes) { + for (const auto& mesh : hfmModel->meshes) { baseVertex = vertices.size(); vec3 color; @@ -133,7 +133,7 @@ void TestFbx::parseFbx(const QByteArray& fbxData) { partIndirect.firstIndex = (uint)indices.size(); partIndirect.baseInstance = (uint)parts.size(); _partTransforms.push_back(mesh.modelTransform); - auto material = fbx->materials[part.materialID]; + auto material = hfmModel->materials[part.materialID]; color = material.diffuseColor; for (auto index : part.quadTrianglesIndices) { indices.push_back(index); @@ -163,7 +163,7 @@ void TestFbx::parseFbx(const QByteArray& fbxData) { _vertexBuffer->append(vertices); _indexBuffer->append(indices); _indirectBuffer->append(parts); - delete fbx; + delete hfmModel; } void TestFbx::renderTest(size_t testId, RenderArgs* args) { diff --git a/tests-manual/gpu/src/TestFbx.h b/tests-manual/gpu/src/TestFbx.h index 391fff1091..8056af21ec 100644 --- a/tests-manual/gpu/src/TestFbx.h +++ b/tests-manual/gpu/src/TestFbx.h @@ -11,7 +11,7 @@ #include <render/ShapePipeline.h> -class FBXGeometry; +class HFMModel; class TestFbx : public GpuTestBase { size_t _partCount { 0 }; diff --git a/tests-manual/qml/qml/MacQml.qml b/tests-manual/qml/qml/MacQml.qml new file mode 100644 index 0000000000..bb7e3a0dff --- /dev/null +++ b/tests-manual/qml/qml/MacQml.qml @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Controls 1.2 +import QtWebEngine 1.5 + +Item { + width: 640 + height: 480 + + Rectangle { + width: 5 + height: 5 + color: "red" + ColorAnimation on color { loops: Animation.Infinite; from: "red"; to: "yellow"; duration: 1000 } + } + + + WebEngineView { + id: root + url: "https://google.com/" + x: 6; y: 6; + width: parent.width * 0.8 + height: parent.height * 0.8 + + } +} diff --git a/tests-manual/qml/src/MacQml.cpp b/tests-manual/qml/src/MacQml.cpp new file mode 100644 index 0000000000..9c5f91041e --- /dev/null +++ b/tests-manual/qml/src/MacQml.cpp @@ -0,0 +1,60 @@ +#include "MacQml.h" + +#include <cmath> + +#include <QtQuick/QQuickItem> + +#include <SharedUtil.h> + +using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; + +void MacQml::update() { + auto rootItem =_surface->getRootItem(); + float now = sinf(secTimestampNow()); + rootItem->setProperty("level", fabs(now)); + rootItem->setProperty("muted", now > 0.0f); + rootItem->setProperty("statsValue", rand()); + + // Fetch any new textures + TextureAndFence newTextureAndFence; + if (_surface->fetchTexture(newTextureAndFence)) { + if (_texture != 0) { + auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _discardLamdba(_texture, readFence); + } + _texture = newTextureAndFence.first; + _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); + } +} + +void MacQml::init() { + Parent::init(); + _glf.glGenFramebuffers(1, &_fbo); + _surface.reset(new hifi::qml::OffscreenSurface()); + //QUrl url =getTestResource("qml/main.qml"); + QUrl url = getTestResource("qml/MacQml.qml"); + hifi::qml::QmlContextObjectCallback callback =[](QQmlContext* context, QQuickItem* item) { + }; + _surface->load(url, callback); + _surface->resize(_window->size()); + _surface->resume(); + +} + +void MacQml::draw() { + auto size = _window->geometry().size(); + if (_texture) { + _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo); + _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); + _glf.glBlitFramebuffer( + // src coordinates + 0, 0, size.width(), size.height(), + // dst coordinates + 0, 0, size.width(), size.height(), + // blit mask and filter + GL_COLOR_BUFFER_BIT, GL_NEAREST); + _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + } +} diff --git a/tests-manual/qml/src/MacQml.h b/tests-manual/qml/src/MacQml.h new file mode 100644 index 0000000000..50f71cb72e --- /dev/null +++ b/tests-manual/qml/src/MacQml.h @@ -0,0 +1,16 @@ +#include "TestCase.h" + +#include <qml/OffscreenSurface.h> + +class MacQml : public TestCase { + using Parent = TestCase; +public: + GLuint _texture{ 0 }; + QmlPtr _surface; + GLuint _fbo{ 0 }; + + MacQml(const QWindow* window) : Parent(window) {} + void update() override; + void init() override; + void draw() override; +}; diff --git a/tests-manual/qml/src/StressWeb.cpp b/tests-manual/qml/src/StressWeb.cpp new file mode 100644 index 0000000000..71293feb9a --- /dev/null +++ b/tests-manual/qml/src/StressWeb.cpp @@ -0,0 +1,131 @@ +#include "StressWeb.h" + +#include <QtQuick/QQuickItem> + +#include <SharedUtil.h> + +using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; + +static const int DEFAULT_MAX_FPS = 10; +static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" }; +static const char* URL_PROPERTY{ "url" }; + +QString StressWeb::getSourceUrl(bool video) { + static const std::vector<QString> SOURCE_URLS{ + "https://www.reddit.com/wiki/random", + "https://en.wikipedia.org/wiki/Wikipedia:Random", + "https://slashdot.org/", + }; + + static const std::vector<QString> VIDEO_SOURCE_URLS{ + "https://www.youtube.com/watch?v=gDXwhHm4GhM", + "https://www.youtube.com/watch?v=Ch_hoYPPeGc", + }; + + const auto& sourceUrls = video ? VIDEO_SOURCE_URLS : SOURCE_URLS; + auto index = rand() % sourceUrls.size(); + return sourceUrls[index]; +} + + + +void StressWeb::buildSurface(QmlInfo& qmlInfo, bool video) { + ++_surfaceCount; + auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f)); + auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs); + qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow(); + qmlInfo.texture = 0; + qmlInfo.surface.reset(new hifi::qml::OffscreenSurface()); + qmlInfo.surface->load(getTestResource(CONTROL_URL), [video](QQmlContext* context, QQuickItem* item) { + item->setProperty(URL_PROPERTY, getSourceUrl(video)); + }); + qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS); + qmlInfo.surface->resize(_qmlSize); + qmlInfo.surface->resume(); +} + +void StressWeb::destroySurface(QmlInfo& qmlInfo) { + auto& surface = qmlInfo.surface; + auto& currentTexture = qmlInfo.texture; + if (currentTexture) { + auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _discardLamdba(currentTexture, readFence); + } + auto webView = surface->getRootItem(); + if (webView) { + // stop loading + QMetaObject::invokeMethod(webView, "stop"); + webView->setProperty(URL_PROPERTY, "about:blank"); + } + surface->pause(); + surface.reset(); +} + +void StressWeb::update() { + auto now = usecTimestampNow(); + // Fetch any new textures + for (size_t x = 0; x < DIVISIONS_X; ++x) { + for (size_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface) { + if (now < _createStopTime && randFloat() > 0.99f) { + buildSurface(qmlInfo, x == 0 && y == 0); + } else { + continue; + } + } + + if (now > qmlInfo.lifetime) { + destroySurface(qmlInfo); + continue; + } + + auto& surface = qmlInfo.surface; + auto& currentTexture = qmlInfo.texture; + + TextureAndFence newTextureAndFence; + if (surface->fetchTexture(newTextureAndFence)) { + if (currentTexture != 0) { + auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _discardLamdba(currentTexture, readFence); + } + currentTexture = newTextureAndFence.first; + _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); + } + } + } +} + +void StressWeb::init() { + Parent::init(); + _createStopTime = usecTimestampNow() + (3000u * USECS_PER_SECOND); + _glf.glGenFramebuffers(1, &_fbo); +} + +void StressWeb::draw() { + auto size = _window->geometry().size(); + auto incrementX = size.width() / DIVISIONS_X; + auto incrementY = size.height() / DIVISIONS_Y; + + for (uint32_t x = 0; x < DIVISIONS_X; ++x) { + for (uint32_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface || !qmlInfo.texture) { + continue; + } + _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo); + _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, qmlInfo.texture, 0); + _glf.glBlitFramebuffer( + // src coordinates + 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, + // dst coordinates + incrementX * x, incrementY * y, incrementX * (x + 1), incrementY * (y + 1), + // blit mask and filter + GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + } + _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); +} diff --git a/tests-manual/qml/src/StressWeb.h b/tests-manual/qml/src/StressWeb.h new file mode 100644 index 0000000000..a68e34d0c1 --- /dev/null +++ b/tests-manual/qml/src/StressWeb.h @@ -0,0 +1,34 @@ +#include "TestCase.h" + +#include <array> + +#include <qml/OffscreenSurface.h> + +#define DIVISIONS_X 5 +#define DIVISIONS_Y 5 + +class StressWeb : public TestCase { + using Parent = TestCase; +public: + using QmlPtr = QSharedPointer<hifi::qml::OffscreenSurface>; + + struct QmlInfo { + QmlPtr surface; + GLuint texture{ 0 }; + uint64_t lifetime{ 0 }; + }; + + size_t _surfaceCount{ 0 }; + uint64_t _createStopTime{ 0 }; + const QSize _qmlSize{ 640, 480 }; + std::array<std::array<QmlInfo, DIVISIONS_Y>, DIVISIONS_X> _surfaces; + GLuint _fbo{ 0 }; + + StressWeb(const QWindow* window) : Parent(window) {} + static QString getSourceUrl(bool video); + void buildSurface(QmlInfo& qmlInfo, bool video); + void destroySurface(QmlInfo& qmlInfo); + void update() override; + void init() override; + void draw() override; +}; diff --git a/tests-manual/qml/src/TestCase.cpp b/tests-manual/qml/src/TestCase.cpp new file mode 100644 index 0000000000..534de71e51 --- /dev/null +++ b/tests-manual/qml/src/TestCase.cpp @@ -0,0 +1,25 @@ +#include "TestCase.h" + +#include <QtCore/QLoggingCategory> +#include <QtCore/QDir> + +void TestCase::destroy() { +} +void TestCase::update() { +} + +void TestCase::init() { + _glf.initializeOpenGLFunctions(); + _discardLamdba = hifi::qml::OffscreenSurface::getDiscardLambda(); +} + +QUrl TestCase::getTestResource(const QString& relativePath) { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../")) + "/"; + qDebug() << "Resources Path: " << dir; + } + return QUrl::fromLocalFile(dir + relativePath); +} diff --git a/tests-manual/qml/src/TestCase.h b/tests-manual/qml/src/TestCase.h new file mode 100644 index 0000000000..191eecb408 --- /dev/null +++ b/tests-manual/qml/src/TestCase.h @@ -0,0 +1,23 @@ +#pragma once + +#include <functional> +#include <QtGui/QWindow> +#include <QtGui/QOpenGLFunctions_4_1_Core> +#include <qml/OffscreenSurface.h> + +class TestCase { +public: + using QmlPtr = QSharedPointer<hifi::qml::OffscreenSurface>; + using Builder = std::function<TestCase*(const QWindow*)>; + TestCase(const QWindow* window) : _window(window) {} + virtual void init(); + virtual void destroy(); + virtual void update(); + virtual void draw() = 0; + static QUrl getTestResource(const QString& relativePath); + +protected: + QOpenGLFunctions_4_1_Core _glf; + const QWindow* _window; + std::function<void(uint32_t, void*)> _discardLamdba; +}; diff --git a/tests-manual/qml/src/main.cpp b/tests-manual/qml/src/main.cpp index d70bb52dde..1d98ebf8c8 100644 --- a/tests-manual/qml/src/main.cpp +++ b/tests-manual/qml/src/main.cpp @@ -43,6 +43,11 @@ #include <qml/OffscreenSurface.h> #include <unordered_set> #include <array> +#include <gl/GLHelpers.h> +#include <gl/Context.h> + +#include "TestCase.h" +#include "MacQml.h" namespace gl { extern void initModuleGl(); @@ -67,53 +72,37 @@ QUrl getTestResource(const QString& relativePath) { return QUrl::fromLocalFile(dir + relativePath); } -#define DIVISIONS_X 5 -#define DIVISIONS_Y 5 - using QmlPtr = QSharedPointer<hifi::qml::OffscreenSurface>; using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; -struct QmlInfo { - QmlPtr surface; - GLuint texture{ 0 }; - uint64_t lifetime{ 0 }; -}; - class TestWindow : public QWindow { public: - TestWindow(); + TestWindow(const TestCase::Builder& caseBuilder); private: QOpenGLContext _glContext; OffscreenGLCanvas _sharedContext; - std::array<std::array<QmlInfo, DIVISIONS_Y>, DIVISIONS_X> _surfaces; + TestCase* _testCase{ nullptr }; QOpenGLFunctions_4_1_Core _glf; - std::function<void(uint32_t, void*)> _discardLamdba; QSize _size; - size_t _surfaceCount{ 0 }; - GLuint _fbo{ 0 }; - const QSize _qmlSize{ 640, 480 }; bool _aboutToQuit{ false }; - uint64_t _createStopTime; void initGl(); - void updateSurfaces(); - void buildSurface(QmlInfo& qmlInfo, bool allowVideo); - void destroySurface(QmlInfo& qmlInfo); void resizeWindow(const QSize& size); void draw(); void resizeEvent(QResizeEvent* ev) override; }; -TestWindow::TestWindow() { +TestWindow::TestWindow(const TestCase::Builder& builder) { Setting::init(); + + _testCase = builder(this); setSurfaceType(QSurface::OpenGLSurface); qmlRegisterType<QTestItem>("Hifi", 1, 0, "TestItem"); show(); - _createStopTime = usecTimestampNow() + (3000u * USECS_PER_SECOND); resize(QSize(800, 600)); @@ -129,162 +118,84 @@ TestWindow::TestWindow() { }); } +Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); +Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); +OffscreenGLCanvas* _chromiumShareContext{ nullptr}; void TestWindow::initGl() { _glContext.setFormat(format()); + + auto globalShareContext = qt_gl_global_share_context(); + if (globalShareContext) { + _glContext.setShareContext(globalShareContext); + globalShareContext->makeCurrent(this); + gl::Context::setupDebugLogging(globalShareContext); + globalShareContext->doneCurrent(); + } + if (!_glContext.create() || !_glContext.makeCurrent(this)) { qFatal("Unable to intialize Window GL context"); } + gl::Context::setupDebugLogging(&_glContext); gl::initModuleGl(); _glf.initializeOpenGLFunctions(); - _glf.glGenFramebuffers(1, &_fbo); if (!_sharedContext.create(&_glContext) || !_sharedContext.makeCurrent()) { qFatal("Unable to intialize Shared GL context"); } hifi::qml::OffscreenSurface::setSharedContext(_sharedContext.getContext()); - _discardLamdba = hifi::qml::OffscreenSurface::getDiscardLambda(); + + if (!globalShareContext) { + _chromiumShareContext = new OffscreenGLCanvas(); + _chromiumShareContext->setObjectName("ChromiumShareContext"); + _chromiumShareContext->create(&_glContext); + if (!_chromiumShareContext->makeCurrent()) { + qFatal("Unable to make chromium shared context current"); + } + + qt_gl_set_global_share_context(_chromiumShareContext->getContext()); + _chromiumShareContext->doneCurrent(); + } + + // Restore the GL widget context + if (!_glContext.makeCurrent(this)) { + qFatal("Unable to make window context current"); + } + + _testCase->init(); } void TestWindow::resizeWindow(const QSize& size) { _size = size; } -static const int DEFAULT_MAX_FPS = 10; -static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" }; -static const char* URL_PROPERTY{ "url" }; - -QString getSourceUrl(bool video) { - static const std::vector<QString> SOURCE_URLS{ - "https://www.reddit.com/wiki/random", - "https://en.wikipedia.org/wiki/Wikipedia:Random", - "https://slashdot.org/", - }; - - static const std::vector<QString> VIDEO_SOURCE_URLS{ - "https://www.youtube.com/watch?v=gDXwhHm4GhM", - "https://www.youtube.com/watch?v=Ch_hoYPPeGc", - }; - - const auto& sourceUrls = video ? VIDEO_SOURCE_URLS : SOURCE_URLS; - auto index = rand() % sourceUrls.size(); - return sourceUrls[index]; -} - -void TestWindow::buildSurface(QmlInfo& qmlInfo, bool video) { - ++_surfaceCount; - auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f)); - auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs); - qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow(); - qmlInfo.texture = 0; - qmlInfo.surface.reset(new hifi::qml::OffscreenSurface()); - qmlInfo.surface->load(getTestResource(CONTROL_URL), [video](QQmlContext* context, QQuickItem* item) { - item->setProperty(URL_PROPERTY, getSourceUrl(video)); - }); - qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS); - qmlInfo.surface->resize(_qmlSize); - qmlInfo.surface->resume(); -} - -void TestWindow::destroySurface(QmlInfo& qmlInfo) { - auto& surface = qmlInfo.surface; - auto& currentTexture = qmlInfo.texture; - if (currentTexture) { - auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - glFlush(); - _discardLamdba(currentTexture, readFence); - } - auto webView = surface->getRootItem(); - if (webView) { - // stop loading - QMetaObject::invokeMethod(webView, "stop"); - webView->setProperty(URL_PROPERTY, "about:blank"); - } - surface->pause(); - surface.reset(); -} - -void TestWindow::updateSurfaces() { - auto now = usecTimestampNow(); - // Fetch any new textures - for (size_t x = 0; x < DIVISIONS_X; ++x) { - for (size_t y = 0; y < DIVISIONS_Y; ++y) { - auto& qmlInfo = _surfaces[x][y]; - if (!qmlInfo.surface) { - if (now < _createStopTime && randFloat() > 0.99f) { - buildSurface(qmlInfo, x == 0 && y == 0); - } else { - continue; - } - } - - if (now > qmlInfo.lifetime) { - destroySurface(qmlInfo); - continue; - } - - auto& surface = qmlInfo.surface; - auto& currentTexture = qmlInfo.texture; - - TextureAndFence newTextureAndFence; - if (surface->fetchTexture(newTextureAndFence)) { - if (currentTexture != 0) { - auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - glFlush(); - _discardLamdba(currentTexture, readFence); - } - currentTexture = newTextureAndFence.first; - _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); - } - } - } -} - void TestWindow::draw() { if (_aboutToQuit) { return; } - + // Attempting to draw before we're visible and have a valid size will // produce GL errors. if (!isVisible() || _size.width() <= 0 || _size.height() <= 0) { return; } - + static std::once_flag once; std::call_once(once, [&] { initGl(); }); - + if (!_glContext.makeCurrent(this)) { return; } - - updateSurfaces(); - - auto size = this->geometry().size(); - auto incrementX = size.width() / DIVISIONS_X; - auto incrementY = size.height() / DIVISIONS_Y; + + _testCase->update(); + + auto size = geometry().size(); _glf.glViewport(0, 0, size.width(), size.height()); _glf.glClearColor(1, 0, 0, 1); _glf.glClear(GL_COLOR_BUFFER_BIT); - for (uint32_t x = 0; x < DIVISIONS_X; ++x) { - for (uint32_t y = 0; y < DIVISIONS_Y; ++y) { - auto& qmlInfo = _surfaces[x][y]; - if (!qmlInfo.surface || !qmlInfo.texture) { - continue; - } - _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo); - _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, qmlInfo.texture, 0); - _glf.glBlitFramebuffer( - // src coordinates - 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, - // dst coordinates - incrementX * x, incrementY * y, incrementX * (x + 1), incrementY * (y + 1), - // blit mask and filter - GL_COLOR_BUFFER_BIT, GL_NEAREST); - } - } - _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + _testCase->draw(); + _glContext.swapBuffers(this); } @@ -292,19 +203,15 @@ void TestWindow::resizeEvent(QResizeEvent* ev) { resizeWindow(ev->size()); } -int main(int argc, char** argv) { - QSurfaceFormat format; - format.setDepthBufferSize(24); - format.setStencilBufferSize(8); +int main(int argc, char** argv) { + auto format = getDefaultOpenGLSurfaceFormat(); format.setVersion(4, 1); - format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); - format.setOption(QSurfaceFormat::DebugContext); QSurfaceFormat::setDefaultFormat(format); - // setFormat(format); QGuiApplication app(argc, argv); - TestWindow window; + TestCase::Builder builder = [](const QWindow* window)->TestCase*{ return new MacQml(window); }; + TestWindow window(builder); return app.exec(); } diff --git a/tests-manual/ui/qml/ControlsGalleryWindow.qml b/tests-manual/ui/qml/ControlsGalleryWindow.qml new file mode 100644 index 0000000000..32fd62da36 --- /dev/null +++ b/tests-manual/ui/qml/ControlsGalleryWindow.qml @@ -0,0 +1,14 @@ +import QtQuick 2.0 +import QtQuick.Window 2.3 +import QtQuick.Controls 1.4 +import '../../../scripts/developer/tests' as Tests + +ApplicationWindow { + width: 640 + height: 480 + visible: true + + Tests.ControlsGallery { + anchors.fill: parent + } +} diff --git a/tests-manual/ui/qml/Palettes.qml b/tests-manual/ui/qml/Palettes.qml deleted file mode 100644 index 2bdf6eba8b..0000000000 --- a/tests-manual/ui/qml/Palettes.qml +++ /dev/null @@ -1,150 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 - -Rectangle { - color: "teal" - height: 512 - width: 192 - SystemPalette { id: sp; colorGroup: SystemPalette.Active } - SystemPalette { id: spi; colorGroup: SystemPalette.Inactive } - SystemPalette { id: spd; colorGroup: SystemPalette.Disabled } - - Column { - anchors.margins: 8 - anchors.fill: parent - spacing: 8 - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "base" } - Rectangle { height: parent.height; width: 16; color: sp.base } - Rectangle { height: parent.height; width: 16; color: spi.base } - Rectangle { height: parent.height; width: 16; color: spd.base } - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "alternateBase" } - Rectangle { height: parent.height; width: 16; color: sp.alternateBase } - Rectangle { height: parent.height; width: 16; color: spi.alternateBase } - Rectangle { height: parent.height; width: 16; color: spd.alternateBase } - } - Item { - height: 16 - width:parent.width - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "dark" } - Rectangle { height: parent.height; width: 16; color: sp.dark } - Rectangle { height: parent.height; width: 16; color: spi.dark } - Rectangle { height: parent.height; width: 16; color: spd.dark } - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "mid" } - Rectangle { height: parent.height; width: 16; color: sp.mid } - Rectangle { height: parent.height; width: 16; color: spi.mid } - Rectangle { height: parent.height; width: 16; color: spd.mid } - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "mid light" } - Rectangle { height: parent.height; width: 16; color: sp.midlight } - Rectangle { height: parent.height; width: 16; color: spi.midlight } - Rectangle { height: parent.height; width: 16; color: spd.midlight } - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "light" } - Rectangle { height: parent.height; width: 16; color: sp.light} - Rectangle { height: parent.height; width: 16; color: spi.light} - Rectangle { height: parent.height; width: 16; color: spd.light} - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "shadow" } - Rectangle { height: parent.height; width: 16; color: sp.shadow} - Rectangle { height: parent.height; width: 16; color: spi.shadow} - Rectangle { height: parent.height; width: 16; color: spd.shadow} - } - Item { - height: 16 - width:parent.width - } - - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "text" } - Rectangle { height: parent.height; width: 16; color: sp.text } - Rectangle { height: parent.height; width: 16; color: spi.text } - Rectangle { height: parent.height; width: 16; color: spd.text } - } - Item { - height: 16 - width:parent.width - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "window" } - Rectangle { height: parent.height; width: 16; color: sp.window } - Rectangle { height: parent.height; width: 16; color: spi.window } - Rectangle { height: parent.height; width: 16; color: spd.window } - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "window text" } - Rectangle { height: parent.height; width: 16; color: sp.windowText } - Rectangle { height: parent.height; width: 16; color: spi.windowText } - Rectangle { height: parent.height; width: 16; color: spd.windowText } - } - Item { - height: 16 - width:parent.width - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "button" } - Rectangle { height: parent.height; width: 16; color: sp.button } - Rectangle { height: parent.height; width: 16; color: spi.button } - Rectangle { height: parent.height; width: 16; color: spd.button } - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "buttonText" } - Rectangle { height: parent.height; width: 16; color: sp.buttonText } - Rectangle { height: parent.height; width: 16; color: spi.buttonText } - Rectangle { height: parent.height; width: 16; color: spd.buttonText } - } - Item { - height: 16 - width:parent.width - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "highlight" } - Rectangle { height: parent.height; width: 16; color: sp.highlight } - Rectangle { height: parent.height; width: 16; color: spi.highlight } - Rectangle { height: parent.height; width: 16; color: spd.highlight } - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "highlighted text" } - Rectangle { height: parent.height; width: 16; color: sp.highlightedText} - Rectangle { height: parent.height; width: 16; color: spi.highlightedText} - Rectangle { height: parent.height; width: 16; color: spd.highlightedText} - } - } -} diff --git a/tests-manual/ui/qml/ScrollingGraph.qml b/tests-manual/ui/qml/ScrollingGraph.qml deleted file mode 100644 index 55523a23f4..0000000000 --- a/tests-manual/ui/qml/ScrollingGraph.qml +++ /dev/null @@ -1,111 +0,0 @@ -import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 - -Rectangle { - id: root - property int size: 64 - width: size - height: size - color: 'black' - property int controlId: 0 - property real value: 0.5 - property int scrollWidth: 1 - property real min: 0.0 - property real max: 1.0 - property bool log: false - property real range: max - min - property color lineColor: 'yellow' - property bool bar: false - property real lastHeight: -1 - property string label: "" - - function update() { - value = Controller.getValue(controlId); - if (log) { - var log = Math.log(10) / Math.log(Math.abs(value)); - var sign = Math.sign(value); - value = log * sign; - } - canvas.requestPaint(); - } - - function drawHeight() { - if (value < min) { - return 0; - } - if (value > max) { - return height; - } - return ((value - min) / range) * height; - } - - Timer { - interval: 50; running: true; repeat: true - onTriggered: root.update() - } - - Canvas { - id: canvas - anchors.fill: parent - antialiasing: false - - Text { - anchors.top: parent.top - text: root.label - color: 'white' - } - - Text { - anchors.right: parent.right - anchors.top: parent.top - text: root.max - color: 'white' - } - - Text { - anchors.right: parent.right - anchors.bottom: parent.bottom - text: root.min - color: 'white' - } - - function scroll() { - var ctx = canvas.getContext('2d'); - var image = ctx.getImageData(0, 0, canvas.width, canvas.height); - ctx.beginPath(); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(image, -root.scrollWidth, 0, canvas.width, canvas.height) - ctx.restore() - } - - onPaint: { - scroll(); - var ctx = canvas.getContext('2d'); - ctx.save(); - var currentHeight = root.drawHeight(); - if (root.lastHeight == -1) { - root.lastHeight = currentHeight - } - -// var x = canvas.width - root.drawWidth; -// var y = canvas.height - drawHeight; -// ctx.fillStyle = root.color -// ctx.fillRect(x, y, root.drawWidth, root.bar ? drawHeight : 1) -// ctx.fill(); -// ctx.restore() - - - ctx.beginPath(); - ctx.lineWidth = 1 - ctx.strokeStyle = root.lineColor - ctx.moveTo(canvas.width - root.scrollWidth, root.lastHeight).lineTo(canvas.width, currentHeight) - ctx.stroke() - ctx.restore() - root.lastHeight = currentHeight - } - } -} - - diff --git a/tests-manual/ui/qml/StubMenu.qml b/tests-manual/ui/qml/StubMenu.qml deleted file mode 100644 index fd0298988a..0000000000 --- a/tests-manual/ui/qml/StubMenu.qml +++ /dev/null @@ -1,730 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 - -import "../../../interface/resources/qml/hifi" - -Menu { - property var menuOption: MenuOption {} - Item { - Action { - id: login; - text: menuOption.login - } - Action { - id: update; - text: "Update"; - enabled: false - } - Action { - id: crashReporter; - text: "Crash Reporter..."; - enabled: false - } - Action { - id: help; - text: menuOption.help - onTriggered: Application.showHelp() - } - Action { - id: aboutApp; - text: menuOption.aboutApp - } - Action { - id: quit; - text: menuOption.quit - } - - ExclusiveGroup { id: renderResolutionGroup } - Action { - id: renderResolutionOne; - exclusiveGroup: renderResolutionGroup; - text: menuOption.renderResolutionOne; - checkable: true; - checked: true - } - Action { - id: renderResolutionTwoThird; - exclusiveGroup: renderResolutionGroup; - text: menuOption.renderResolutionTwoThird; - checkable: true - } - Action { - id: renderResolutionHalf; - exclusiveGroup: renderResolutionGroup; - text: menuOption.renderResolutionHalf; - checkable: true - } - Action { - id: renderResolutionThird; - exclusiveGroup: renderResolutionGroup; - text: menuOption.renderResolutionThird; - checkable: true - } - Action { - id: renderResolutionQuarter; - exclusiveGroup: renderResolutionGroup; - text: menuOption.renderResolutionQuarter; - checkable: true - } - - ExclusiveGroup { id: ambientLightGroup } - Action { - id: renderAmbientLightGlobal; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLightGlobal; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight0; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight0; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight1; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight1; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight2; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight2; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight3; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight3; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight4; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight4; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight5; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight5; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight6; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight6; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight7; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight7; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight8; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight8; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight9; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight9; - checkable: true; - checked: true - } - Action { - id: preferences - shortcut: StandardKey.Preferences - text: menuOption.preferences - onTriggered: dialogsManager.editPreferences() - } - - } - - Menu { - title: "File" - MenuItem { - action: login - } - MenuItem { - action: update - } - MenuItem { - action: help - } - MenuItem { - action: crashReporter - } - MenuItem { - action: aboutApp - } - MenuItem { - action: quit - } - } - - Menu { - title: "Edit" - MenuItem { - text: "Undo" } - MenuItem { - text: "Redo" } - MenuItem { - text: menuOption.runningScripts - } - MenuItem { - text: menuOption.loadScript - } - MenuItem { - text: menuOption.loadScriptURL - } - MenuItem { - text: menuOption.stopAllScripts - } - MenuItem { - text: menuOption.reloadAllScripts - } - MenuItem { - text: menuOption.scriptEditor - } - MenuItem { - text: menuOption.console_ - } - MenuItem { - text: menuOption.reloadContent - } - MenuItem { - text: menuOption.packageModel - } - } - - Menu { - title: "Audio" - MenuItem { - text: menuOption.muteAudio; - checkable: true - } - MenuItem { - text: menuOption.audioTools; - checkable: true - } - } - Menu { - title: "Avatar" - // Avatar > Attachments... - MenuItem { - text: menuOption.attachments - } - Menu { - title: "Size" - // Avatar > Size > Increase - MenuItem { - text: menuOption.increaseAvatarSize - } - // Avatar > Size > Decrease - MenuItem { - text: menuOption.decreaseAvatarSize - } - // Avatar > Size > Reset - MenuItem { - text: menuOption.resetAvatarSize - } - } - // Avatar > Reset Sensors - MenuItem { - text: menuOption.resetSensors - } - } - Menu { - title: "Display" - } - Menu { - title: "View" - ExclusiveGroup { - id: cameraModeGroup - } - - MenuItem { - text: menuOption.firstPerson; - checkable: true; - exclusiveGroup: cameraModeGroup - } - MenuItem { - text: menuOption.thirdPerson; - checkable: true; - exclusiveGroup: cameraModeGroup - } - MenuItem { - text: menuOption.fullscreenMirror; - checkable: true; - exclusiveGroup: cameraModeGroup - } - MenuItem { - text: menuOption.independentMode; - checkable: true; - exclusiveGroup: cameraModeGroup - } - MenuItem { - text: menuOption.cameraEntityMode; - checkable: true; - exclusiveGroup: cameraModeGroup - } - MenuSeparator{} - MenuItem { - text: menuOption.miniMirror; - checkable: true - } - } - Menu { - title: "Navigate" - MenuItem { - text: "Home" } - MenuItem { - text: menuOption.addressBar - } - MenuItem { - text: "Directory" } - MenuItem { - text: menuOption.copyAddress - } - MenuItem { - text: menuOption.copyPath - } - } - Menu { - title: "Settings" - MenuItem { - text: "Advanced Menus" } - MenuItem { - text: "Developer Menus" } - MenuItem { - text: menuOption.preferences - } - MenuItem { - text: "Avatar..." } - MenuItem { - text: "Audio..." } - MenuItem { - text: "LOD..." } - MenuItem { - text: menuOption.inputMenu - } - } - Menu { - title: "Developer" - Menu { - title: "Render" - MenuItem { - text: menuOption.atmosphere; - checkable: true - } - MenuItem { - text: menuOption.worldAxes; - checkable: true - } - MenuItem { - text: menuOption.debugAmbientOcclusion; - checkable: true - } - MenuItem { - text: menuOption.antialiasing; - checkable: true - } - MenuItem { - text: menuOption.stars; - checkable: true - } - Menu { - title: menuOption.renderAmbientLight - MenuItem { - action: renderAmbientLightGlobal; } - MenuItem { - action: renderAmbientLight0; } - MenuItem { - action: renderAmbientLight1; } - MenuItem { - action: renderAmbientLight2; } - MenuItem { - action: renderAmbientLight3; } - MenuItem { - action: renderAmbientLight4; } - MenuItem { - action: renderAmbientLight5; } - MenuItem { - action: renderAmbientLight6; } - MenuItem { - action: renderAmbientLight7; } - MenuItem { - action: renderAmbientLight8; } - MenuItem { - action: renderAmbientLight9; } - } - MenuItem { - text: menuOption.throttleFPSIfNotFocus; - checkable: true - } - Menu { - title: menuOption.renderResolution - MenuItem { - action: renderResolutionOne - } - MenuItem { - action: renderResolutionTwoThird - } - MenuItem { - action: renderResolutionHalf - } - MenuItem { - action: renderResolutionThird - } - MenuItem { - action: renderResolutionQuarter - } - } - MenuItem { - text: menuOption.lodTools - } - } - Menu { - title: "Assets" - MenuItem { - text: menuOption.uploadAsset - } - MenuItem { - text: menuOption.assetMigration - } - } - Menu { - title: "Avatar" - Menu { - title: "Face Tracking" - MenuItem { - text: menuOption.noFaceTracking; - checkable: true - } - MenuItem { - text: menuOption.faceshift; - checkable: true - } - MenuItem { - text: menuOption.useCamera; - checkable: true - } - MenuSeparator{} - MenuItem { - text: menuOption.binaryEyelidControl; - checkable: true - } - MenuItem { - text: menuOption.coupleEyelids; - checkable: true - } - MenuItem { - text: menuOption.useAudioForMouth; - checkable: true - } - MenuItem { - text: menuOption.velocityFilter; - checkable: true - } - MenuItem { - text: menuOption.calibrateCamera - } - MenuSeparator{} - MenuItem { - text: menuOption.muteFaceTracking; - checkable: true - } - MenuItem { - text: menuOption.autoMuteAudio; - checkable: true - } - } - Menu { - title: "Eye Tracking" - MenuItem { - text: menuOption.sMIEyeTracking; - checkable: true - } - Menu { - title: "Calibrate" - MenuItem { - text: menuOption.onePointCalibration - } - MenuItem { - text: menuOption.threePointCalibration - } - MenuItem { - text: menuOption.fivePointCalibration - } - } - MenuItem { - text: menuOption.simulateEyeTracking; - checkable: true - } - } - MenuItem { - text: menuOption.avatarReceiveStats; - checkable: true - } - MenuItem { - text: menuOption.renderBoundingCollisionShapes; - checkable: true - } - MenuItem { - text: menuOption.renderLookAtVectors; - checkable: true - } - MenuItem { - text: menuOption.renderLookAtTargets; - checkable: true - } - MenuItem { - text: menuOption.renderFocusIndicator; - checkable: true - } - MenuItem { - text: menuOption.showWhosLookingAtMe; - checkable: true - } - MenuItem { - text: menuOption.fixGaze; - checkable: true - } - MenuItem { - text: menuOption.animDebugDrawDefaultPose; - checkable: true - } - MenuItem { - text: menuOption.animDebugDrawAnimPose; - checkable: true - } - MenuItem { - text: menuOption.animDebugDrawPosition; - checkable: true - } - MenuItem { - text: menuOption.meshVisible; - checkable: true - } - MenuItem { - text: menuOption.disableEyelidAdjustment; - checkable: true - } - MenuItem { - text: menuOption.turnWithHead; - checkable: true - } - MenuItem { - text: menuOption.keyboardMotorControl; - checkable: true - } - MenuItem { - text: menuOption.scriptedMotorControl; - checkable: true - } - MenuItem { - text: menuOption.enableCharacterController; - checkable: true - } - } - Menu { - title: "Hands" - MenuItem { - text: menuOption.displayHandTargets; - checkable: true - } - MenuItem { - text: menuOption.lowVelocityFilter; - checkable: true - } - Menu { - title: "Leap Motion" - MenuItem { - text: menuOption.leapMotionOnHMD; - checkable: true - } - } - } - Menu { - title: "Entities" - MenuItem { - text: menuOption.octreeStats - } - MenuItem { - text: menuOption.showRealtimeEntityStats; - checkable: true - } - } - Menu { - title: "Network" - MenuItem { - text: menuOption.reloadContent - } - MenuItem { - text: menuOption.disableNackPackets; - checkable: true - } - MenuItem { - text: menuOption.disableActivityLogger; - checkable: true - } - MenuItem { - text: menuOption.cachesSize - } - MenuItem { - text: menuOption.diskCacheEditor - } - MenuItem { - text: menuOption.showDSConnectTable - } - MenuItem { - text: menuOption.bandwidthDetails - } - } - Menu { - title: "Timing" - Menu { - title: "Performance Timer" - MenuItem { - text: menuOption.displayDebugTimingDetails; - checkable: true - } - MenuItem { - text: menuOption.onlyDisplayTopTen; - checkable: true - } - MenuItem { - text: menuOption.expandUpdateTiming; - checkable: true - } - MenuItem { - text: menuOption.expandMyAvatarTiming; - checkable: true - } - MenuItem { - text: menuOption.expandMyAvatarSimulateTiming; - checkable: true - } - MenuItem { - text: menuOption.expandOtherAvatarTiming; - checkable: true - } - MenuItem { - text: menuOption.expandPaintGLTiming; - checkable: true - } - } - MenuItem { - text: menuOption.frameTimer; - checkable: true - } - MenuItem { - text: menuOption.runTimingTests - } - MenuItem { - text: menuOption.pipelineWarnings; - checkable: true - } - MenuItem { - text: menuOption.logExtraTimings; - checkable: true - } - MenuItem { - text: menuOption.suppressShortTimings; - checkable: true - } - } - Menu { - title: "Audio" - MenuItem { - text: menuOption.audioNoiseReduction; - checkable: true - } - MenuItem { - text: menuOption.echoServerAudio; - checkable: true - } - MenuItem { - text: menuOption.echoLocalAudio; - checkable: true - } - MenuItem { - text: menuOption.muteEnvironment - } - Menu { - title: "Audio" - MenuItem { - text: menuOption.audioScope; - checkable: true - } - MenuItem { - text: menuOption.audioScopePause; - checkable: true - } - Menu { - title: "Display Frames" - ExclusiveGroup { - id: audioScopeFramesGroup - } - MenuItem { - exclusiveGroup: audioScopeFramesGroup; - text: menuOption.audioScopeFiveFrames; - checkable: true - } - MenuItem { - exclusiveGroup: audioScopeFramesGroup; - text: menuOption.audioScopeTwentyFrames; - checkable: true - } - MenuItem { - exclusiveGroup: audioScopeFramesGroup; - text: menuOption.audioScopeFiftyFrames; - checkable: true - } - } - MenuItem { - text: menuOption.audioNetworkStats - } - } - } - Menu { - title: "Physics" - MenuItem { - text: menuOption.physicsShowOwned; - checkable: true - } - MenuItem { - text: menuOption.physicsShowHulls; - checkable: true - } - } - MenuItem { - text: menuOption.displayCrashOptions; - checkable: true - } - MenuItem { - text: menuOption.crashInterface - } - MenuItem { - text: menuOption.log - } - MenuItem { - text: menuOption.stats; - checkable: true - } - } -} diff --git a/tests-manual/ui/qml/Stubs.qml b/tests-manual/ui/qml/Stubs.qml deleted file mode 100644 index 8c1465d54c..0000000000 --- a/tests-manual/ui/qml/Stubs.qml +++ /dev/null @@ -1,346 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 - -// Stubs for the global service objects set by Interface.cpp when creating the UI -// This is useful for testing inside Qt creator where these services don't actually exist. -Item { - - Item { - objectName: "offscreenFlags" - property bool navigationFocused: false - } - - Item { - objectName: "urlHandler" - function fixupUrl(url) { return url; } - function canHandleUrl(url) { return false; } - function handleUrl(url) { return true; } - } - - Item { - objectName: "Account" - function isLoggedIn() { return true; } - function getUsername() { return "Jherico"; } - } - - Item { - objectName: "GL" - property string vendor: "" - } - - Item { - objectName: "ApplicationCompositor" - property bool reticleOverDesktop: true - } - - Item { - objectName: "Controller" - function getRecommendedOverlayRect() { - return Qt.rect(0, 0, 1920, 1080); - } - } - - Item { - objectName: "Preferences" - // List of categories obtained by logging categories as they are added in Interface in Preferences::addPreference(). - property var categories: [ - "Avatar Basics", "Snapshots", "Scripts", "Privacy", "Level of Detail Tuning", "Avatar Tuning", "Avatar Camera", - "Audio", "Octree", "HMD", "Sixense Controllers", "Graphics" - ] - } - - Item { - objectName: "ScriptDiscoveryService" - //property var scriptsModelFilter: scriptsModel - signal scriptCountChanged() - property var _runningScripts:[ - { name: "wireFrameTest.js", url: "foo/wireframetest.js", path: "foo/wireframetest.js", local: true }, - { name: "edit.js", url: "foo/edit.js", path: "foo/edit.js", local: false }, - { name: "listAllScripts.js", url: "foo/listAllScripts.js", path: "foo/listAllScripts.js", local: false }, - { name: "users.js", url: "foo/users.js", path: "foo/users.js", local: false }, - ] - - function getRunning() { - return _runningScripts; - } - } - - Item { - objectName: "HMD" - property bool active: false - } - - Item { - id: menuHelper - objectName: "MenuHelper" - - Component { - id: modelMaker - ListModel { } - } - - function toModel(menu, parent) { - if (!parent) { parent = menuHelper } - var result = modelMaker.createObject(parent); - if (menu.type !== MenuItemType.Menu) { - console.warn("Not a menu: " + menu); - return result; - } - - var items = menu.items; - for (var i = 0; i < items.length; ++i) { - var item = items[i]; - switch (item.type) { - case 2: - result.append({"name": item.title, "item": item}) - break; - case 1: - result.append({"name": item.text, "item": item}) - break; - case 0: - result.append({"name": "", "item": item}) - break; - } - } - return result; - } - - } - - Item { - objectName: "Desktop" - - property string _OFFSCREEN_ROOT_OBJECT_NAME: "desktopRoot"; - property string _OFFSCREEN_DIALOG_OBJECT_NAME: "topLevelWindow"; - - - function findChild(item, name) { - for (var i = 0; i < item.children.length; ++i) { - if (item.children[i].objectName === name) { - return item.children[i]; - } - } - return null; - } - - function findParent(item, name) { - while (item) { - if (item.objectName === name) { - return item; - } - item = item.parent; - } - return null; - } - - function findDialog(item) { - item = findParent(item, _OFFSCREEN_DIALOG_OBJECT_NAME); - return item; - } - - function closeDialog(item) { - item = findDialog(item); - if (item) { - item.visible = false - } else { - console.warn("Could not find top level dialog") - } - } - - function getDesktop(item) { - while (item) { - if (item.desktopRoot) { - break; - } - item = item.parent; - } - return item - } - - function raise(item) { - var desktop = getDesktop(item); - if (desktop) { - desktop.raise(item); - } - } - } - - Menu { - id: root - objectName: "rootMenu" - - Menu { - title: "Audio" - } - - Menu { - title: "Avatar" - } - - Menu { - title: "Display" - ExclusiveGroup { id: displayMode } - Menu { - title: "More Stuff" - - Menu { title: "Empty" } - - MenuItem { - text: "Do Nothing" - onTriggered: console.log("Nothing") - } - } - MenuItem { - text: "Oculus" - exclusiveGroup: displayMode - checkable: true - } - MenuItem { - text: "OpenVR" - exclusiveGroup: displayMode - checkable: true - } - MenuItem { - text: "OSVR" - exclusiveGroup: displayMode - checkable: true - } - MenuItem { - text: "2D Screen" - exclusiveGroup: displayMode - checkable: true - checked: true - } - MenuItem { - text: "3D Screen (Active)" - exclusiveGroup: displayMode - checkable: true - } - MenuItem { - text: "3D Screen (Passive)" - exclusiveGroup: displayMode - checkable: true - } - } - - Menu { - title: "View" - Menu { - title: "Camera Mode" - ExclusiveGroup { id: cameraMode } - MenuItem { - exclusiveGroup: cameraMode - text: "First Person"; - onTriggered: console.log(text + " checked " + checked) - checkable: true - checked: true - } - MenuItem { - exclusiveGroup: cameraMode - text: "Third Person"; - onTriggered: console.log(text) - checkable: true - } - MenuItem { - exclusiveGroup: cameraMode - text: "Independent Mode"; - onTriggered: console.log(text) - checkable: true - } - MenuItem { - exclusiveGroup: cameraMode - text: "Entity Mode"; - onTriggered: console.log(text) - enabled: false - checkable: true - } - MenuItem { - exclusiveGroup: cameraMode - text: "Fullscreen Mirror"; - onTriggered: console.log(text) - checkable: true - } - } - } - - Menu { - title: "Edit" - - MenuItem { - text: "Undo" - shortcut: "Ctrl+Z" - enabled: false - onTriggered: console.log(text) - } - - MenuItem { - text: "Redo" - shortcut: "Ctrl+Shift+Z" - enabled: false - onTriggered: console.log(text) - } - - MenuSeparator { } - - MenuItem { - text: "Cut" - shortcut: "Ctrl+X" - onTriggered: console.log(text) - } - - MenuItem { - text: "Copy" - shortcut: "Ctrl+C" - onTriggered: console.log(text) - } - - MenuItem { - text: "Paste" - shortcut: "Ctrl+V" - visible: false - onTriggered: console.log("Paste") - } - } - - Menu { - title: "Navigate" - } - - Menu { - title: "Market" - } - - Menu { - title: "Settings" - } - - Menu { - title: "Developer" - } - - Menu { - title: "Quit" - } - - Menu { - title: "File" - - Action { - id: login - text: "Login" - } - - Action { - id: quit - text: "Quit" - shortcut: "Ctrl+Q" - onTriggered: Qt.quit(); - } - - MenuItem { action: quit } - MenuItem { action: login } - } - } - -} - diff --git a/tests-manual/ui/qml/TestControllers.qml b/tests-manual/ui/qml/TestControllers.qml deleted file mode 100644 index e9a7fb49e5..0000000000 --- a/tests-manual/ui/qml/TestControllers.qml +++ /dev/null @@ -1,160 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 - -import "controller" -import "controls" as HifiControls -import "styles" - -HifiControls.VrDialog { - id: root - HifiConstants { id: hifi } - title: "Controller Test" - resizable: true - contentImplicitWidth: clientArea.implicitWidth - contentImplicitHeight: clientArea.implicitHeight - backgroundColor: "beige" - - property var actions: Controller.Actions - property var standard: Controller.Standard - property var hydra: null - property var testMapping: null - property bool testMappingEnabled: false - property var xbox: null - - function buildMapping() { - testMapping = Controller.newMapping(); - testMapping.fromQml(standard.RY).invert().toQml(actions.Pitch); - testMapping.fromQml(function(){ - return Math.sin(Date.now() / 250); - }).toQml(actions.Yaw); - //testMapping.makeAxis(standard.LB, standard.RB).to(actions.Yaw); - // Step yaw takes a number of degrees - testMapping.fromQml(standard.LB).pulse(0.10).invert().scale(40.0).toQml(actions.StepYaw); - testMapping.fromQml(standard.RB).pulse(0.10).scale(15.0).toQml(actions.StepYaw); - testMapping.fromQml(standard.RX).scale(15.0).toQml(actions.StepYaw); - } - - function toggleMapping() { - testMapping.enable(!testMappingEnabled); - testMappingEnabled = !testMappingEnabled; - } - - Component.onCompleted: { - var xboxRegex = /^GamePad/; - var hydraRegex = /^Hydra/; - for (var prop in Controller.Hardware) { - if(xboxRegex.test(prop)) { - root.xbox = Controller.Hardware[prop] - print("found xbox") - continue - } - if (hydraRegex.test(prop)) { - root.hydra = Controller.Hardware[prop] - print("found hydra") - continue - } - } - } - - Column { - id: clientArea - spacing: 12 - x: root.clientX - y: root.clientY - - Row { - spacing: 8 - - Button { - text: !root.testMapping ? "Build Mapping" : (root.testMappingEnabled ? "Disable Mapping" : "Enable Mapping") - onClicked: { - - if (!root.testMapping) { - root.buildMapping() - } else { - root.toggleMapping(); - } - } - } - } - - Row { - Standard { device: root.standard; label: "Standard"; width: 180 } - } - - Row { - spacing: 8 - Xbox { device: root.xbox; label: "XBox"; width: 180 } - Hydra { device: root.hydra; width: 180 } - } - - Row { - spacing: 4 - ScrollingGraph { - controlId: Controller.Actions.Yaw - label: "Yaw" - min: -2.0 - max: 2.0 - size: 64 - } - - ScrollingGraph { - controlId: Controller.Actions.YawLeft - label: "Yaw Left" - min: -2.0 - max: 2.0 - size: 64 - } - - ScrollingGraph { - controlId: Controller.Actions.YawRight - label: "Yaw Right" - min: -2.0 - max: 2.0 - size: 64 - } - - ScrollingGraph { - controlId: Controller.Actions.StepYaw - label: "StepYaw" - min: -20.0 - max: 20.0 - size: 64 - } - } - - Row { - ScrollingGraph { - controlId: Controller.Actions.TranslateZ - label: "TranslateZ" - min: -2.0 - max: 2.0 - size: 64 - } - - ScrollingGraph { - controlId: Controller.Actions.Forward - label: "Forward" - min: -2.0 - max: 2.0 - size: 64 - } - - ScrollingGraph { - controlId: Controller.Actions.Backward - label: "Backward" - min: -2.0 - max: 2.0 - size: 64 - } - - } - } -} // dialog - - - - - diff --git a/tests-manual/ui/qml/TestDialog.qml b/tests-manual/ui/qml/TestDialog.qml deleted file mode 100644 index e6675b7282..0000000000 --- a/tests-manual/ui/qml/TestDialog.qml +++ /dev/null @@ -1,94 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.3 -import "controls" - -VrDialog { - title: "Test Dialog" - id: testDialog - objectName: "TestDialog" - width: 512 - height: 512 - animationDuration: 200 - - onEnabledChanged: { - if (enabled) { - edit.forceActiveFocus(); - } - } - - Item { - id: clientArea - // The client area - anchors.fill: parent - anchors.margins: parent.margins - anchors.topMargin: parent.topMargin - - Rectangle { - property int d: 100 - id: square - objectName: "testRect" - width: d - height: d - anchors.centerIn: parent - color: "red" - NumberAnimation on rotation { from: 0; to: 360; duration: 2000; loops: Animation.Infinite; } - } - - - TextEdit { - id: edit - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.right: parent.right - anchors.rightMargin: 12 - clip: true - text: "test edit" - anchors.top: parent.top - anchors.topMargin: 12 - } - - Button { - x: 128 - y: 192 - text: "Test" - anchors.bottom: parent.bottom - anchors.bottomMargin: 12 - anchors.right: parent.right - anchors.rightMargin: 12 - onClicked: { - console.log("Click"); - - if (square.visible) { - square.visible = false - } else { - square.visible = true - } - } - } - - Button { - id: customButton2 - y: 192 - text: "Move" - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 12 - onClicked: { - onClicked: testDialog.x == 0 ? testDialog.x = 200 : testDialog.x = 0 - } - } - - Keys.onPressed: { - console.log("Key " + event.key); - switch (event.key) { - case Qt.Key_Q: - if (Qt.ControlModifier == event.modifiers) { - event.accepted = true; - break; - } - } - } - } -} diff --git a/tests-manual/ui/qml/TestMenu.qml b/tests-manual/ui/qml/TestMenu.qml deleted file mode 100644 index fe8a26e234..0000000000 --- a/tests-manual/ui/qml/TestMenu.qml +++ /dev/null @@ -1,10 +0,0 @@ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import Hifi 1.0 - -// Currently for testing a pure QML replacement menu -Item { - Menu { - objectName: "rootMenu"; - } -} diff --git a/tests-manual/ui/qml/TestRoot.qml b/tests-manual/ui/qml/TestRoot.qml deleted file mode 100644 index bd38c696bf..0000000000 --- a/tests-manual/ui/qml/TestRoot.qml +++ /dev/null @@ -1,43 +0,0 @@ -import Hifi 1.0 -import QtQuick 2.3 -import QtQuick.Controls 1.3 -// Import local folder last so that our own control customizations override -// the built in ones -import "controls" - -Root { - id: root - anchors.fill: parent - onParentChanged: { - forceActiveFocus(); - } - Button { - id: messageBox - anchors.right: createDialog.left - anchors.rightMargin: 24 - anchors.bottom: parent.bottom - anchors.bottomMargin: 24 - text: "Message" - onClicked: { - console.log("Foo") - root.information("a") - console.log("Bar") - } - } - Button { - id: createDialog - anchors.right: parent.right - anchors.rightMargin: 24 - anchors.bottom: parent.bottom - anchors.bottomMargin: 24 - text: "Create" - onClicked: { - root.loadChild("MenuTest.qml"); - } - } - - Keys.onPressed: { - console.log("Key press root") - } -} - diff --git a/tests-manual/ui/qml/controlDemo/ButtonPage.qml b/tests-manual/ui/qml/controlDemo/ButtonPage.qml deleted file mode 100644 index 0ed7e2d6ad..0000000000 --- a/tests-manual/ui/qml/controlDemo/ButtonPage.qml +++ /dev/null @@ -1,128 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Quick Controls module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -import QtQuick 2.2 -import QtQuick.Layouts 1.1 -import QtQuick.Controls 1.2 - -ScrollView { - id: page - - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - - Item { - id: content - - width: Math.max(page.viewport.width, grid.implicitWidth + 2 * grid.rowSpacing) - height: Math.max(page.viewport.height, grid.implicitHeight + 2 * grid.columnSpacing) - - GridLayout { - id: grid - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: grid.rowSpacing - anchors.rightMargin: grid.rowSpacing - anchors.topMargin: grid.columnSpacing - - columns: page.width < page.height ? 1 : 2 - - GroupBox { - title: "Button" - Layout.fillWidth: true - Layout.columnSpan: grid.columns - RowLayout { - anchors.fill: parent - Button { text: "OK"; isDefault: true } - Button { text: "Cancel" } - Item { Layout.fillWidth: true } - Button { - text: "Attach" - menu: Menu { - MenuItem { text: "Image" } - MenuItem { text: "Document" } - } - } - } - } - - GroupBox { - title: "CheckBox" - Layout.fillWidth: true - ColumnLayout { - anchors.fill: parent - CheckBox { text: "E-mail"; checked: true } - CheckBox { text: "Calendar"; checked: true } - CheckBox { text: "Contacts" } - } - } - - GroupBox { - title: "RadioButton" - Layout.fillWidth: true - ColumnLayout { - anchors.fill: parent - ExclusiveGroup { id: radioGroup } - RadioButton { text: "Portrait"; exclusiveGroup: radioGroup } - RadioButton { text: "Landscape"; exclusiveGroup: radioGroup } - RadioButton { text: "Automatic"; exclusiveGroup: radioGroup; checked: true } - } - } - - GroupBox { - title: "Switch" - Layout.fillWidth: true - Layout.columnSpan: grid.columns - ColumnLayout { - anchors.fill: parent - RowLayout { - Label { text: "Wi-Fi"; Layout.fillWidth: true } - Switch { checked: true } - } - RowLayout { - Label { text: "Bluetooth"; Layout.fillWidth: true } - Switch { checked: false } - } - } - } - } - } -} diff --git a/tests-manual/ui/qml/controlDemo/InputPage.qml b/tests-manual/ui/qml/controlDemo/InputPage.qml deleted file mode 100644 index cb1878d023..0000000000 --- a/tests-manual/ui/qml/controlDemo/InputPage.qml +++ /dev/null @@ -1,114 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Quick Controls module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -import QtQuick 2.2 -import QtQuick.Layouts 1.1 -import QtQuick.Controls 1.2 - -ScrollView { - id: page - - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - - Item { - id: content - - width: Math.max(page.viewport.width, column.implicitWidth + 2 * column.spacing) - height: Math.max(page.viewport.height, column.implicitHeight + 2 * column.spacing) - - ColumnLayout { - id: column - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: column.spacing - - GroupBox { - title: "TextField" - Layout.fillWidth: true - ColumnLayout { - anchors.fill: parent - TextField { placeholderText: "..."; Layout.fillWidth: true; z: 1 } - TextField { placeholderText: "Password"; echoMode: TextInput.Password; Layout.fillWidth: true } - } - } - - GroupBox { - title: "ComboBox" - Layout.fillWidth: true - ColumnLayout { - anchors.fill: parent - ComboBox { - model: Qt.fontFamilies() - Layout.fillWidth: true - } - ComboBox { - editable: true - model: ListModel { - id: listModel - ListElement { text: "Apple" } - ListElement { text: "Banana" } - ListElement { text: "Coconut" } - ListElement { text: "Orange" } - } - onAccepted: { - if (find(currentText) === -1) { - listModel.append({text: editText}) - currentIndex = find(editText) - } - } - Layout.fillWidth: true - } - } - } - - GroupBox { - title: "SpinBox" - Layout.fillWidth: true - ColumnLayout { - anchors.fill: parent - SpinBox { value: 99; Layout.fillWidth: true; z: 1 } - SpinBox { decimals: 2; Layout.fillWidth: true } - } - } - } - } -} diff --git a/tests-manual/ui/qml/controlDemo/ProgressPage.qml b/tests-manual/ui/qml/controlDemo/ProgressPage.qml deleted file mode 100644 index a1fa596f79..0000000000 --- a/tests-manual/ui/qml/controlDemo/ProgressPage.qml +++ /dev/null @@ -1,90 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Quick Controls module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -import QtQuick 2.2 -import QtQuick.Layouts 1.1 -import QtQuick.Controls 1.2 - -ScrollView { - id: page - - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - - Item { - id: content - - width: Math.max(page.viewport.width, column.implicitWidth + 2 * column.spacing) - height: Math.max(page.viewport.height, column.implicitHeight + 2 * column.spacing) - - ColumnLayout { - id: column - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: column.spacing - - GroupBox { - title: "ProgressBar" - Layout.fillWidth: true - ColumnLayout { - anchors.fill: parent - ProgressBar { indeterminate: true; Layout.fillWidth: true } - ProgressBar { value: slider.value; Layout.fillWidth: true } - } - } - - GroupBox { - title: "Slider" - Layout.fillWidth: true - ColumnLayout { - anchors.fill: parent - Slider { id: slider; value: 0.5; Layout.fillWidth: true } - } - } - - GroupBox { - title: "BusyIndicator" - Layout.fillWidth: true - BusyIndicator { running: true } - } - } - } -} diff --git a/tests-manual/ui/qml/controlDemo/main.qml b/tests-manual/ui/qml/controlDemo/main.qml deleted file mode 100644 index 168b9fb291..0000000000 --- a/tests-manual/ui/qml/controlDemo/main.qml +++ /dev/null @@ -1,161 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Quick Controls module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -import QtQuick 2.2 -import QtQuick.Layouts 1.1 -import QtQuick.Dialogs 1.1 -import QtQuick.Controls 1.2 -import "qml/UI.js" as UI -import "qml" -//import "/Users/bdavis/Git/hifi/interface/resources/qml" - -Item { - anchors.fill: parent - visible: true - //title: "Qt Quick Controls Gallery" - - MessageDialog { - id: aboutDialog - icon: StandardIcon.Information - title: "About" - text: "Qt Quick Controls Gallery" - informativeText: "This example demonstrates most of the available Qt Quick Controls." - } - - Action { - id: copyAction - text: "&Copy" - shortcut: StandardKey.Copy - iconName: "edit-copy" - enabled: (!!activeFocusItem && !!activeFocusItem["copy"]) - onTriggered: activeFocusItem.copy() - } - - Action { - id: cutAction - text: "Cu&t" - shortcut: StandardKey.Cut - iconName: "edit-cut" - enabled: (!!activeFocusItem && !!activeFocusItem["cut"]) - onTriggered: activeFocusItem.cut() - } - - Action { - id: pasteAction - text: "&Paste" - shortcut: StandardKey.Paste - iconName: "edit-paste" - enabled: (!!activeFocusItem && !!activeFocusItem["paste"]) - onTriggered: activeFocusItem.paste() - } - -// toolBar: ToolBar { -// RowLayout { -// anchors.fill: parent -// anchors.margins: spacing -// Label { -// text: UI.label -// } -// Item { Layout.fillWidth: true } -// CheckBox { -// id: enabler -// text: "Enabled" -// checked: true -// } -// } -// } - -// menuBar: MenuBar { -// Menu { -// title: "&File" -// MenuItem { -// text: "E&xit" -// shortcut: StandardKey.Quit -// onTriggered: Qt.quit() -// } -// } -// Menu { -// title: "&Edit" -// visible: tabView.currentIndex == 2 -// MenuItem { action: cutAction } -// MenuItem { action: copyAction } -// MenuItem { action: pasteAction } -// } -// Menu { -// title: "&Help" -// MenuItem { -// text: "About..." -// onTriggered: aboutDialog.open() -// } -// } -// } - - TabView { - id: tabView - - anchors.fill: parent - anchors.margins: UI.margin - tabPosition: UI.tabPosition - - Layout.minimumWidth: 360 - Layout.minimumHeight: 360 - Layout.preferredWidth: 480 - Layout.preferredHeight: 640 - - Tab { - title: "Buttons" - ButtonPage { - enabled: enabler.checked - } - } - Tab { - title: "Progress" - ProgressPage { - enabled: enabler.checked - } - } - Tab { - title: "Input" - InputPage { - enabled: enabler.checked - } - } - } -} diff --git a/tests-manual/ui/qml/main.qml b/tests-manual/ui/qml/main.qml deleted file mode 100644 index 607bd624a1..0000000000 --- a/tests-manual/ui/qml/main.qml +++ /dev/null @@ -1,461 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Dialogs 1.2 as OriginalDialogs -import Qt.labs.settings 1.0 - -import "../../../interface/resources/qml" -//import "../../../interface/resources/qml/windows" -import "../../../interface/resources/qml/windows" -import "../../../interface/resources/qml/dialogs" -import "../../../interface/resources/qml/hifi" -import "../../../interface/resources/qml/hifi/dialogs" -import "../../../interface/resources/qml/styles-uit" - -ApplicationWindow { - id: appWindow - objectName: "MainWindow" - visible: true - width: 1280 - height: 800 - title: qsTr("Scratch App") - toolBar: Row { - id: testButtons - anchors { margins: 8; left: parent.left; top: parent.top } - spacing: 8 - property int count: 0 - - property var tabs: []; - property var urls: []; - property var toolbar; - property var lastButton; - - Button { - text: HMD.active ? "Disable HMD" : "Enable HMD" - onClicked: HMD.active = !HMD.active - } - - Button { - text: desktop.hmdHandMouseActive ? "Disable HMD HandMouse" : "Enable HMD HandMouse" - onClicked: desktop.hmdHandMouseActive = !desktop.hmdHandMouseActive - } - - // Window visibility - Button { - text: "toggle desktop" - onClicked: desktop.togglePinned() - } - - Button { - text: "Create Toolbar" - onClicked: testButtons.toolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system"); - } - - Button { - text: "Toggle Toolbar Direction" - onClicked: testButtons.toolbar.horizontal = !testButtons.toolbar.horizontal - } - - Button { - readonly property var icons: [ - "edit-01.svg", - "model-01.svg", - "cube-01.svg", - "sphere-01.svg", - "light-01.svg", - "text-01.svg", - "web-01.svg", - "zone-01.svg", - "particle-01.svg", - ] - property int iconIndex: 0 - readonly property string toolIconUrl: "../../../../../scripts/system/assets/images/tools/" - text: "Create Button" - onClicked: { - var name = icons[iconIndex]; - var url = toolIconUrl + name; - iconIndex = (iconIndex + 1) % icons.length; - var button = testButtons.lastButton = testButtons.toolbar.addButton({ - imageURL: url, - objectName: name, - subImage: { - y: 50, - }, - alpha: 0.9 - }); - - button.clicked.connect(function(){ - console.log("Clicked on button " + button.imageURL + " alpha " + button.alpha) - }); - } - } - - Button { - text: "Toggle Button Visible" - onClicked: testButtons.lastButton.visible = !testButtons.lastButton.visible - } - - // Error alerts - /* - Button { - // Message without title. - text: "Show Error" - onClicked: { - var messageBox = desktop.messageBox({ - text: "Diagnostic cycle will be complete in 30 seconds", - icon: hifi.icons.critical, - }); - messageBox.selected.connect(function(button) { - console.log("You clicked " + button) - }) - } - } - Button { - // detailedText is not currently used anywhere in Interface but it is easier to leave in and style good enough. - text: "Show Long Error" - onClicked: { - desktop.messageBox({ - informativeText: "Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds ", - text: "Baloney", - icon: hifi.icons.warning, - detailedText: "sakjd;laskj dksa;dl jka;lsd j;lkjas ;dlkaj s;dlakjd ;alkjda; slkjda; lkjda;lksjd ;alksjd; alksjd ;alksjd; alksjd; alksdjas;ldkjas;lkdja ;kj ;lkasjd; lkj as;dlka jsd;lka jsd;laksjd a" - }); - } - } - */ - - // query - /* - // There is no such desktop.queryBox() function; may need to update test to cover QueryDialog.qml? - Button { - text: "Show Query" - onClicked: { - var queryBox = desktop.queryBox({ - text: "Have you stopped beating your wife?", - placeholderText: "Are you sure?", - // icon: hifi.icons.critical, - }); - queryBox.selected.connect(function(result) { - console.log("User responded with " + result); - }); - - queryBox.canceled.connect(function() { - console.log("User cancelled query box "); - }) - } - } - */ - - // Browser - /* - Button { - text: "Open Browser" - onClicked: builder.createObject(desktop); - property var builder: Component { - Browser {} - } - } - */ - - - // file dialog - /* - - Button { - text: "Open Directory" - property var builder: Component { - FileDialog { selectDirectory: true } - } - - onClicked: { - var fileDialog = builder.createObject(desktop); - fileDialog.canceled.connect(function(){ - console.log("Cancelled") - }) - fileDialog.selectedFile.connect(function(file){ - console.log("Selected " + file) - }) - } - } - Button { - text: "Open File" - property var builder: Component { - FileDialog { - title: "Open File" - filter: "All Files (*.*)" - //filter: "HTML files (*.html);;Other(*.png)" - } - } - - onClicked: { - var fileDialog = builder.createObject(desktop); - fileDialog.canceled.connect(function(){ - console.log("Cancelled") - }) - fileDialog.selectedFile.connect(function(file){ - console.log("Selected " + file) - }) - } - } - */ - - // tabs - /* - Button { - text: "Add Tab" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.addWebTab({ source: "Foo" }); - desktop.toolWindow.showTabForUrl("Foo", true); - } - } - - Button { - text: "Add Tab 2" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.addWebTab({ source: "Foo 2" }); - desktop.toolWindow.showTabForUrl("Foo 2", true); - } - } - - Button { - text: "Add Tab 3" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.addWebTab({ source: "Foo 3" }); - desktop.toolWindow.showTabForUrl("Foo 3", true); - } - } - - Button { - text: "Destroy Tab" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.removeTabForUrl("Foo"); - } - } - */ - - // Hifi specific stuff - /* - Button { - // Shows the dialog with preferences sections but not each section's preference items - // because Preferences.preferencesByCategory() method is not stubbed out. - text: "Settings > General..." - property var builder: Component { - GeneralPreferencesDialog { } - } - onClicked: { - var runningScripts = builder.createObject(desktop); - } - } - - Button { - text: "Running Scripts" - property var builder: Component { - RunningScripts { } - } - onClicked: { - var runningScripts = builder.createObject(desktop); - } - } - - Button { - text: "Attachments" - property var builder: Component { - AttachmentsDialog { } - } - onClicked: { - var attachmentsDialog = builder.createObject(desktop); - } - } - Button { - // Replicates message box that pops up after selecting new avatar. Includes title. - text: "Confirm Avatar" - onClicked: { - var messageBox = desktop.messageBox({ - title: "Set Avatar", - text: "Would you like to use 'Albert' for your avatar?", - icon: hifi.icons.question, // Test question icon - //icon: hifi.icons.information, // Test informaton icon - //icon: hifi.icons.warning, // Test warning icon - //icon: hifi.icons.critical, // Test critical icon - //icon: hifi.icons.none, // Test no icon - buttons: OriginalDialogs.StandardButton.Ok + OriginalDialogs.StandardButton.Cancel, - defaultButton: OriginalDialogs.StandardButton.Ok - }); - messageBox.selected.connect(function(button) { - console.log("You clicked " + button) - }) - } - } - */ - // bookmarks - /* - Button { - text: "Bookmark Location" - onClicked: { - desktop.inputDialog({ - title: "Bookmark Location", - icon: hifi.icons.placemark, - label: "Name" - }); - } - } - Button { - text: "Delete Bookmark" - onClicked: { - desktop.inputDialog({ - title: "Delete Bookmark", - icon: hifi.icons.placemark, - label: "Select the bookmark to delete", - items: ["Bookmark A", "Bookmark B", "Bookmark C"] - }); - } - } - Button { - text: "Duplicate Bookmark" - onClicked: { - desktop.messageBox({ - title: "Duplicate Bookmark", - icon: hifi.icons.warning, - text: "The bookmark name you entered alread exists in yoru list.", - informativeText: "Would you like to overwrite it?", - buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No, - defaultButton: OriginalDialogs.StandardButton.Yes - }); - } - } - */ - - } - - - HifiConstants { id: hifi } - - Desktop { - id: desktop - anchors.fill: parent - - //rootMenu: StubMenu { id: rootMenu } - //Component.onCompleted: offscreenWindow = appWindow - - /* - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton - onClicked: desktop.popupMenu(Qt.vector2d(mouseX, mouseY)); - } - */ - - Browser { - url: "http://s3.amazonaws.com/DreamingContent/testUiDelegates.html" - } - - - Window { - id: blue - closable: true - visible: true - resizable: true - destroyOnHidden: false - title: "Blue" - - width: 100; height: 100 - x: 1280 / 2; y: 720 / 2 - Settings { - category: "TestWindow.Blue" - property alias x: blue.x - property alias y: blue.y - property alias width: blue.width - property alias height: blue.height - } - - Rectangle { - anchors.fill: parent - visible: true - color: "blue" - Component.onDestruction: console.log("Blue destroyed") - } - } - - Window { - id: green - closable: true - visible: true - resizable: true - title: "Green" - destroyOnHidden: false - - width: 100; height: 100 - x: 1280 / 2; y: 720 / 2 - Settings { - category: "TestWindow.Green" - property alias x: green.x - property alias y: green.y - property alias width: green.width - property alias height: green.height - } - - Rectangle { - anchors.fill: parent - visible: true - color: "green" - Component.onDestruction: console.log("Green destroyed") - } - } - -/* - Rectangle { width: 100; height: 100; x: 100; y: 100; color: "#00f" } - - Window { - id: green - alwaysOnTop: true - frame: HiddenFrame{} - hideBackground: true - closable: true - visible: true - resizable: false - x: 1280 / 2; y: 720 / 2 - width: 100; height: 100 - Rectangle { - color: "#0f0" - width: green.width; - height: green.height; - } - } - */ - -/* - Window { - id: yellow - closable: true - visible: true - resizable: true - x: 100; y: 100 - width: 100; height: 100 - Rectangle { - anchors.fill: parent - visible: true - color: "yellow" - } - } -*/ - } - - Action { - id: openBrowserAction - text: "Open Browser" - shortcut: "Ctrl+Shift+X" - onTriggered: { - builder.createObject(desktop); - } - property var builder: Component { - ModelBrowserDialog{} - } - } -} - - - - diff --git a/tests-manual/ui/qmlscratch.pro b/tests-manual/ui/qmlscratch.pro index 5c9b91ee52..3287180d26 100644 --- a/tests-manual/ui/qmlscratch.pro +++ b/tests-manual/ui/qmlscratch.pro @@ -4,34 +4,10 @@ QT += gui qml quick xml webengine widgets CONFIG += c++11 -SOURCES += src/main.cpp \ - ../../libraries/ui/src/FileDialogHelper.cpp - -HEADERS += \ - ../../libraries/ui/src/FileDialogHelper.h +SOURCES += src/main.cpp # Additional import path used to resolve QML modules in Qt Creator's code model QML_IMPORT_PATH = ../../interface/resources/qml - DISTFILES += \ - qml/*.qml \ - ../../interface/resources/QtWebEngine/UIDelegates/original/*.qml \ - ../../interface/resources/QtWebEngine/UIDelegates/*.qml \ - ../../interface/resources/qml/*.qml \ - ../../interface/resources/qml/controls/*.qml \ - ../../interface/resources/qml/controls-uit/*.qml \ - ../../interface/resources/qml/dialogs/*.qml \ - ../../interface/resources/qml/dialogs/fileDialog/*.qml \ - ../../interface/resources/qml/dialogs/preferences/*.qml \ - ../../interface/resources/qml/dialogs/messageDialog/*.qml \ - ../../interface/resources/qml/desktop/*.qml \ - ../../interface/resources/qml/menus/*.qml \ - ../../interface/resources/qml/styles/*.qml \ - ../../interface/resources/qml/styles-uit/*.qml \ - ../../interface/resources/qml/windows/*.qml \ - ../../interface/resources/qml/hifi/*.qml \ - ../../interface/resources/qml/hifi/toolbars/*.qml \ - ../../interface/resources/qml/hifi/dialogs/*.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/*.qml \ - ../../interface/resources/qml/hifi/overlays/*.qml + qml/*.qml diff --git a/tests-manual/ui/src/main.cpp b/tests-manual/ui/src/main.cpp index 312b5f3823..a5061f4d01 100644 --- a/tests-manual/ui/src/main.cpp +++ b/tests-manual/ui/src/main.cpp @@ -3,88 +3,31 @@ #include <QtWebEngine> #include <QFileSystemModel> -#include "../../../libraries/ui/src/FileDialogHelper.h" - - -class Preference : public QObject { - Q_OBJECT - Q_PROPERTY(QString category READ getCategory() CONSTANT) - Q_PROPERTY(QString name READ getName() CONSTANT) - Q_PROPERTY(Type type READ getType() CONSTANT) - Q_ENUMS(Type) -public: - enum Type { - Editable, - Browsable, - Spinner, - Checkbox, - }; - - Preference(QObject* parent = nullptr) : QObject(parent) {} - - Preference(const QString& category, const QString& name, QObject* parent = nullptr) - : QObject(parent), _category(category), _name(name) { } - const QString& getCategory() const { return _category; } - const QString& getName() const { return _name; } - virtual Type getType() { return Editable; } - -protected: - const QString _category; - const QString _name; -}; - -class Reticle : public QObject { - Q_OBJECT - Q_PROPERTY(QPoint position READ getPosition CONSTANT) -public: - - Reticle(QObject* parent) : QObject(parent) { - } - - QPoint getPosition() { - if (!_window) { - return QPoint(0, 0); - } - return _window->mapFromGlobal(QCursor::pos()); - } - - void setWindow(QWindow* window) { - _window = window; - } - -private: - QWindow* _window{nullptr}; -}; - QString getRelativeDir(const QString& relativePath = ".") { - QDir path(__FILE__); path.cdUp(); + QDir path(__FILE__); + path.cdUp(); + path.cdUp(); auto result = path.absoluteFilePath(relativePath); result = path.cleanPath(result) + "/"; return result; } -QString getTestQmlDir() { - return getRelativeDir("../qml"); +QString getResourcesDir() { + return getRelativeDir("../../interface/resources"); } -QString getInterfaceQmlDir() { - return getRelativeDir("/"); +QString getQmlDir() { + return getRelativeDir("../../interface/resources/qml"); } - -void setChild(QQmlApplicationEngine& engine, const char* name) { - for (auto obj : engine.rootObjects()) { - auto child = obj->findChild<QObject*>(QString(name)); - if (child) { - engine.rootContext()->setContextProperty(name, child); - return; - } - } - qWarning() << "Could not find object named " << name; +QString getScriptsDir() { + return getRelativeDir("../../scripts"); } void addImportPath(QQmlApplicationEngine& engine, const QString& relativePath, bool insert = false) { QString resolvedPath = getRelativeDir(relativePath); + + qDebug() << "adding import path: " << QDir::toNativeSeparators(resolvedPath); engine.addImportPath(resolvedPath); } @@ -93,44 +36,24 @@ int main(int argc, char *argv[]) { app.setOrganizationName("Some Company"); app.setOrganizationDomain("somecompany.com"); app.setApplicationName("Amazing Application"); - QDir::setCurrent(getRelativeDir("..")); - QtWebEngine::initialize(); - qmlRegisterType<Preference>("Hifi", 1, 0, "Preference"); + auto scriptsDir = getScriptsDir(); + auto resourcesDir = getResourcesDir(); QQmlApplicationEngine engine; + addImportPath(engine, "."); addImportPath(engine, "qml"); - addImportPath(engine, "../../interface/resources/qml"); - addImportPath(engine, "../../interface/resources"); - engine.load(QUrl(QStringLiteral("qml/Stubs.qml"))); + addImportPath(engine, resourcesDir); + addImportPath(engine, resourcesDir + "/qml"); + addImportPath(engine, scriptsDir); + addImportPath(engine, scriptsDir + "/developer/tests"); - setChild(engine, "offscreenFlags"); - setChild(engine, "Account"); - setChild(engine, "ApplicationCompositor"); - setChild(engine, "Controller"); - setChild(engine, "Desktop"); - setChild(engine, "ScriptDiscoveryService"); - setChild(engine, "HMD"); - setChild(engine, "GL"); - setChild(engine, "MenuHelper"); - setChild(engine, "Preferences"); - setChild(engine, "urlHandler"); - engine.rootContext()->setContextProperty("DebugQML", true); - engine.rootContext()->setContextProperty("fileDialogHelper", new FileDialogHelper()); + QFontDatabase::addApplicationFont(resourcesDir + "/fonts/FiraSans-Regular.ttf"); + QFontDatabase::addApplicationFont(resourcesDir + "/fonts/FiraSans-SemiBold.ttf"); + QFontDatabase::addApplicationFont(resourcesDir + "/fonts/hifi-glyphs.ttf"); - //engine.load(QUrl(QStringLiteral("qrc:/qml/gallery/main.qml"))); - engine.load(QUrl(QStringLiteral("qml/main.qml"))); - for (QObject* rootObject : engine.rootObjects()) { - if (rootObject->objectName() == "MainWindow") { - Reticle* reticle = new Reticle(rootObject); - reticle->setWindow((QWindow*)rootObject); - engine.rootContext()->setContextProperty("Reticle", reticle); - engine.rootContext()->setContextProperty("Window", rootObject); - break; - } - } + auto url = getRelativeDir(".") + "qml/ControlsGalleryWindow.qml"; + + engine.load(url); return app.exec(); } - -#include "main.moc" - diff --git a/tests/animation/src/AnimInverseKinematicsTests.cpp b/tests/animation/src/AnimInverseKinematicsTests.cpp index f5d3597f56..910770bb0d 100644 --- a/tests/animation/src/AnimInverseKinematicsTests.cpp +++ b/tests/animation/src/AnimInverseKinematicsTests.cpp @@ -28,8 +28,8 @@ const glm::quat identity = glm::quat(); const glm::quat quaterTurnAroundZ = glm::angleAxis(0.5f * PI, zAxis); -void makeTestFBXJoints(FBXGeometry& geometry) { - FBXJoint joint; +void makeTestFBXJoints(HFMModel& hfmModel) { + HFMJoint joint; joint.isFree = false; joint.freeLineage.clear(); joint.parentIndex = -1; @@ -60,29 +60,29 @@ void makeTestFBXJoints(FBXGeometry& geometry) { joint.name = "A"; joint.parentIndex = -1; joint.translation = origin; - geometry.joints.push_back(joint); + hfmModel.joints.push_back(joint); joint.name = "B"; joint.parentIndex = 0; joint.translation = xAxis; - geometry.joints.push_back(joint); + hfmModel.joints.push_back(joint); joint.name = "C"; joint.parentIndex = 1; joint.translation = xAxis; - geometry.joints.push_back(joint); + hfmModel.joints.push_back(joint); joint.name = "D"; joint.parentIndex = 2; joint.translation = xAxis; - geometry.joints.push_back(joint); + hfmModel.joints.push_back(joint); // compute each joint's transform - for (int i = 1; i < (int)geometry.joints.size(); ++i) { - FBXJoint& j = geometry.joints[i]; + for (int i = 1; i < (int)hfmModel.joints.size(); ++i) { + HFMJoint& j = hfmModel.joints[i]; int parentIndex = j.parentIndex; // World = ParentWorld * T * (Roff * Rp) * Rpre * R * Rpost * (Rp-1 * Soff * Sp * S * Sp-1) - j.transform = geometry.joints[parentIndex].transform * + j.transform = hfmModel.joints[parentIndex].transform * glm::translate(j.translation) * j.preTransform * glm::mat4_cast(j.preRotation * j.rotation * j.postRotation) * @@ -96,12 +96,12 @@ void AnimInverseKinematicsTests::testSingleChain() { AnimContext context(false, false, false, glm::mat4(), glm::mat4()); - FBXGeometry geometry; - makeTestFBXJoints(geometry); + HFMModel hfmModel; + makeTestFBXJoints(hfmModel); // create a skeleton and doll AnimPose offset; - AnimSkeleton::Pointer skeletonPtr = std::make_shared<AnimSkeleton>(geometry); + AnimSkeleton::Pointer skeletonPtr = std::make_shared<AnimSkeleton>(hfmModel); AnimInverseKinematics ikDoll("doll"); ikDoll.setSkeleton(skeletonPtr); @@ -119,7 +119,7 @@ void AnimInverseKinematicsTests::testSingleChain() { poses.push_back(pose); pose.trans() = xAxis; - for (int i = 1; i < (int)geometry.joints.size(); ++i) { + for (int i = 1; i < (int)hfmModel.joints.size(); ++i) { poses.push_back(pose); } ikDoll.loadPoses(poses); diff --git a/tools/auto-tester/CMakeLists.txt b/tools/auto-tester/CMakeLists.txt index 06930ab0fe..c8c0a336d2 100644 --- a/tools/auto-tester/CMakeLists.txt +++ b/tools/auto-tester/CMakeLists.txt @@ -60,7 +60,7 @@ if (WIN32) add_custom_command( TARGET ${TARGET_NAME} POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy_directory D:/GitHub/vcpkg/installed/x64-windows/bin "$<TARGET_FILE_DIR:${TARGET_NAME}>" + COMMAND "${CMAKE_COMMAND}" -E copy_directory "$ENV{VCPKG_ROOT}/installed/x64-windows/bin" "$<TARGET_FILE_DIR:${TARGET_NAME}>" ) endif () \ No newline at end of file diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index 674cf6f8e8..01ec04f254 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -340,7 +340,7 @@ void TestRunner::runInterfaceWithTestScript() { QString testScript = QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js"; - QString commandLine = exeFile + " --url " + url + " --no-updater --no-login" + " --testScript " + testScript + + QString commandLine = exeFile + " --url " + url + " --no-updater --no-login-suggestion" + " --testScript " + testScript + " quitWhenFinished --testResultsLocation " + snapshotFolder; interfaceWorker->setCommandLine(commandLine); diff --git a/tools/skeleton-dump/src/SkeletonDumpApp.cpp b/tools/skeleton-dump/src/SkeletonDumpApp.cpp index e9d8243e38..10b13aef36 100644 --- a/tools/skeleton-dump/src/SkeletonDumpApp.cpp +++ b/tools/skeleton-dump/src/SkeletonDumpApp.cpp @@ -54,8 +54,8 @@ SkeletonDumpApp::SkeletonDumpApp(int argc, char* argv[]) : QCoreApplication(argc return; } QByteArray blob = file.readAll(); - std::unique_ptr<FBXGeometry> fbxGeometry(readFBX(blob, QVariantHash())); - std::unique_ptr<AnimSkeleton> skeleton(new AnimSkeleton(*fbxGeometry)); + std::unique_ptr<HFMModel> geometry(readFBX(blob, QVariantHash())); + std::unique_ptr<AnimSkeleton> skeleton(new AnimSkeleton(*geometry)); skeleton->dump(verbose); } diff --git a/tools/vhacd-util/src/VHACDUtil.cpp b/tools/vhacd-util/src/VHACDUtil.cpp index a52e948f01..8de9c39da9 100644 --- a/tools/vhacd-util/src/VHACDUtil.cpp +++ b/tools/vhacd-util/src/VHACDUtil.cpp @@ -19,16 +19,16 @@ // FBXReader jumbles the order of the meshes by reading them back out of a hashtable. This will put // them back in the order in which they appeared in the file. -bool FBXGeometryLessThan(const FBXMesh& e1, const FBXMesh& e2) { +bool HFMModelLessThan(const HFMMesh& e1, const HFMMesh& e2) { return e1.meshIndex < e2.meshIndex; } -void reSortFBXGeometryMeshes(FBXGeometry& geometry) { - qSort(geometry.meshes.begin(), geometry.meshes.end(), FBXGeometryLessThan); +void reSortHFMModelMeshes(HFMModel& hfmModel) { + qSort(hfmModel.meshes.begin(), hfmModel.meshes.end(), HFMModelLessThan); } // Read all the meshes from provided FBX file -bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { +bool vhacd::VHACDUtil::loadFBX(const QString filename, HFMModel& result) { if (_verbose) { qDebug() << "reading FBX file =" << filename << "..."; } @@ -41,19 +41,19 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { } try { QByteArray fbxContents = fbx.readAll(); - FBXGeometry::Pointer geom; + HFMModel::Pointer hfmModel; if (filename.toLower().endsWith(".obj")) { bool combineParts = false; - geom = OBJReader().readOBJ(fbxContents, QVariantHash(), combineParts); + hfmModel = OBJReader().readOBJ(fbxContents, QVariantHash(), combineParts); } else if (filename.toLower().endsWith(".fbx")) { - geom.reset(readFBX(fbxContents, QVariantHash(), filename)); + hfmModel.reset(readFBX(fbxContents, QVariantHash(), filename)); } else { qWarning() << "file has unknown extension" << filename; return false; } - result = *geom; + result = *hfmModel; - reSortFBXGeometryMeshes(result); + reSortHFMModelMeshes(result); } catch (const QString& error) { qWarning() << "error reading" << filename << ":" << error; return false; @@ -63,7 +63,7 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { } -void getTrianglesInMeshPart(const FBXMeshPart &meshPart, std::vector<int>& triangleIndices) { +void getTrianglesInMeshPart(const HFMMeshPart &meshPart, std::vector<int>& triangleIndices) { // append triangle indices triangleIndices.reserve(triangleIndices.size() + (size_t)meshPart.triangleIndices.size()); for (auto index : meshPart.triangleIndices) { @@ -88,12 +88,12 @@ void getTrianglesInMeshPart(const FBXMeshPart &meshPart, std::vector<int>& trian } } -void vhacd::VHACDUtil::fattenMesh(const FBXMesh& mesh, const glm::mat4& geometryOffset, FBXMesh& result) const { +void vhacd::VHACDUtil::fattenMesh(const HFMMesh& mesh, const glm::mat4& modelOffset, HFMMesh& result) const { // this is used to make meshes generated from a highfield collidable. each triangle // is converted into a tetrahedron and made into its own mesh-part. std::vector<int> triangleIndices; - foreach (const FBXMeshPart &meshPart, mesh.parts) { + foreach (const HFMMeshPart &meshPart, mesh.parts) { getTrianglesInMeshPart(meshPart, triangleIndices); } @@ -104,7 +104,7 @@ void vhacd::VHACDUtil::fattenMesh(const FBXMesh& mesh, const glm::mat4& geometry int indexStartOffset = result.vertices.size(); // new mesh gets the transformed points from the original - glm::mat4 totalTransform = geometryOffset * mesh.modelTransform; + glm::mat4 totalTransform = modelOffset * mesh.modelTransform; for (int i = 0; i < mesh.vertices.size(); i++) { // apply the source mesh's transform to the points glm::vec4 v = totalTransform * glm::vec4(mesh.vertices[i], 1.0f); @@ -145,7 +145,7 @@ void vhacd::VHACDUtil::fattenMesh(const FBXMesh& mesh, const glm::mat4& geometry int index3 = result.vertices.size(); result.vertices << p3; // add the new point to the result mesh - FBXMeshPart newMeshPart; + HFMMeshPart newMeshPart; setMeshPartDefaults(newMeshPart, "unknown"); newMeshPart.triangleIndices << index0 << index1 << index2; newMeshPart.triangleIndices << index0 << index3 << index1; @@ -155,7 +155,7 @@ void vhacd::VHACDUtil::fattenMesh(const FBXMesh& mesh, const glm::mat4& geometry } } -AABox getAABoxForMeshPart(const FBXMesh& mesh, const FBXMeshPart &meshPart) { +AABox getAABoxForMeshPart(const HFMMesh& mesh, const HFMMeshPart &meshPart) { AABox aaBox; const int TRIANGLE_STRIDE = 3; for (int i = 0; i < meshPart.triangleIndices.size(); i += TRIANGLE_STRIDE) { @@ -242,7 +242,7 @@ bool isClosedManifold(const std::vector<int>& triangleIndices) { return true; } -void vhacd::VHACDUtil::getConvexResults(VHACD::IVHACD* convexifier, FBXMesh& resultMesh) const { +void vhacd::VHACDUtil::getConvexResults(VHACD::IVHACD* convexifier, HFMMesh& resultMesh) const { // Number of hulls for this input meshPart uint32_t numHulls = convexifier->GetNConvexHulls(); if (_verbose) { @@ -256,8 +256,8 @@ void vhacd::VHACDUtil::getConvexResults(VHACD::IVHACD* convexifier, FBXMesh& res VHACD::IVHACD::ConvexHull hull; convexifier->GetConvexHull(j, hull); - resultMesh.parts.append(FBXMeshPart()); - FBXMeshPart& resultMeshPart = resultMesh.parts.last(); + resultMesh.parts.append(HFMMeshPart()); + HFMMeshPart& resultMeshPart = resultMesh.parts.last(); int hullIndexStart = resultMesh.vertices.size(); resultMesh.vertices.reserve(hullIndexStart + hull.m_nPoints); @@ -288,17 +288,17 @@ float computeDt(uint64_t start) { return (float)(usecTimestampNow() - start) / (float)USECS_PER_SECOND; } -bool vhacd::VHACDUtil::computeVHACD(FBXGeometry& geometry, +bool vhacd::VHACDUtil::computeVHACD(HFMModel& hfmModel, VHACD::IVHACD::Parameters params, - FBXGeometry& result, + HFMModel& result, float minimumMeshSize, float maximumMeshSize) { if (_verbose) { - qDebug() << "meshes =" << geometry.meshes.size(); + qDebug() << "meshes =" << hfmModel.meshes.size(); } // count the mesh-parts int numParts = 0; - foreach (const FBXMesh& mesh, geometry.meshes) { + foreach (const HFMMesh& mesh, hfmModel.meshes) { numParts += mesh.parts.size(); } if (_verbose) { @@ -308,15 +308,15 @@ bool vhacd::VHACDUtil::computeVHACD(FBXGeometry& geometry, VHACD::IVHACD * convexifier = VHACD::CreateVHACD(); result.meshExtents.reset(); - result.meshes.append(FBXMesh()); - FBXMesh &resultMesh = result.meshes.last(); + result.meshes.append(HFMMesh()); + HFMMesh &resultMesh = result.meshes.last(); const uint32_t POINT_STRIDE = 3; const uint32_t TRIANGLE_STRIDE = 3; int meshIndex = 0; int validPartsFound = 0; - foreach (const FBXMesh& mesh, geometry.meshes) { + foreach (const HFMMesh& mesh, hfmModel.meshes) { // find duplicate points int numDupes = 0; @@ -337,7 +337,7 @@ bool vhacd::VHACDUtil::computeVHACD(FBXGeometry& geometry, // each mesh has its own transform to move it to model-space std::vector<glm::vec3> vertices; - glm::mat4 totalTransform = geometry.offset * mesh.modelTransform; + glm::mat4 totalTransform = hfmModel.offset * mesh.modelTransform; foreach (glm::vec3 vertex, mesh.vertices) { vertices.push_back(glm::vec3(totalTransform * glm::vec4(vertex, 1.0f))); } @@ -354,7 +354,7 @@ bool vhacd::VHACDUtil::computeVHACD(FBXGeometry& geometry, int partIndex = 0; std::vector<int> triangleIndices; - foreach (const FBXMeshPart &meshPart, mesh.parts) { + foreach (const HFMMeshPart &meshPart, mesh.parts) { triangleIndices.clear(); getTrianglesInMeshPart(meshPart, triangleIndices); @@ -421,7 +421,7 @@ bool vhacd::VHACDUtil::computeVHACD(FBXGeometry& geometry, triangleIndices.clear(); for (auto index : openParts) { - const FBXMeshPart &meshPart = mesh.parts[index]; + const HFMMeshPart &meshPart = mesh.parts[index]; getTrianglesInMeshPart(meshPart, triangleIndices); } diff --git a/tools/vhacd-util/src/VHACDUtil.h b/tools/vhacd-util/src/VHACDUtil.h index 35ec3ef56b..dd8f606756 100644 --- a/tools/vhacd-util/src/VHACDUtil.h +++ b/tools/vhacd-util/src/VHACDUtil.h @@ -27,16 +27,16 @@ namespace vhacd { public: void setVerbose(bool verbose) { _verbose = verbose; } - bool loadFBX(const QString filename, FBXGeometry& result); + bool loadFBX(const QString filename, HFMModel& result); - void fattenMesh(const FBXMesh& mesh, const glm::mat4& gometryOffset, FBXMesh& result) const; + void fattenMesh(const HFMMesh& mesh, const glm::mat4& modelOffset, HFMMesh& result) const; - bool computeVHACD(FBXGeometry& geometry, + bool computeVHACD(HFMModel& hfmModel, VHACD::IVHACD::Parameters params, - FBXGeometry& result, + HFMModel& result, float minimumMeshSize, float maximumMeshSize); - void getConvexResults(VHACD::IVHACD* convexifier, FBXMesh& resultMesh) const; + void getConvexResults(VHACD::IVHACD* convexifier, HFMMesh& resultMesh) const; ~VHACDUtil(); @@ -55,6 +55,6 @@ namespace vhacd { }; } -AABox getAABoxForMeshPart(const FBXMeshPart &meshPart); +AABox getAABoxForMeshPart(const HFMMeshPart &meshPart); #endif //hifi_VHACDUtil_h diff --git a/tools/vhacd-util/src/VHACDUtilApp.cpp b/tools/vhacd-util/src/VHACDUtilApp.cpp index c263dce609..3d675f8baf 100644 --- a/tools/vhacd-util/src/VHACDUtilApp.cpp +++ b/tools/vhacd-util/src/VHACDUtilApp.cpp @@ -36,7 +36,7 @@ QString formatFloat(double n) { } -bool VHACDUtilApp::writeOBJ(QString outFileName, FBXGeometry& geometry, bool outputCentimeters, int whichMeshPart) { +bool VHACDUtilApp::writeOBJ(QString outFileName, HFMModel& hfmModel, bool outputCentimeters, int whichMeshPart) { QFile file(outFileName); if (!file.open(QIODevice::WriteOnly)) { qWarning() << "unable to write to" << outFileName; @@ -56,9 +56,9 @@ bool VHACDUtilApp::writeOBJ(QString outFileName, FBXGeometry& geometry, bool out int vertexIndexOffset = 0; - foreach (const FBXMesh& mesh, geometry.meshes) { + foreach (const HFMMesh& mesh, hfmModel.meshes) { bool verticesHaveBeenOutput = false; - foreach (const FBXMeshPart &meshPart, mesh.parts) { + foreach (const HFMMeshPart &meshPart, mesh.parts) { if (whichMeshPart >= 0 && nth != (unsigned int) whichMeshPart) { nth++; continue; @@ -297,7 +297,7 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : } // load the mesh - FBXGeometry fbx; + HFMModel fbx; auto begin = std::chrono::high_resolution_clock::now(); if (!vUtil.loadFBX(inputFilename, fbx)){ _returnCode = VHACD_RETURN_CODE_FAILURE_TO_READ; @@ -315,8 +315,8 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : QVector<QString> infileExtensions = {"fbx", "obj"}; QString baseFileName = fileNameWithoutExtension(outputFilename, infileExtensions); int count = 0; - foreach (const FBXMesh& mesh, fbx.meshes) { - foreach (const FBXMeshPart &meshPart, mesh.parts) { + foreach (const HFMMesh& mesh, fbx.meshes) { + foreach (const HFMMeshPart &meshPart, mesh.parts) { QString outputFileName = baseFileName + "-" + QString::number(count) + ".obj"; writeOBJ(outputFileName, fbx, outputCentimeters, count); count++; @@ -358,7 +358,7 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : } begin = std::chrono::high_resolution_clock::now(); - FBXGeometry result; + HFMModel result; bool success = vUtil.computeVHACD(fbx, params, result, minimumMeshSize, maximumMeshSize); end = std::chrono::high_resolution_clock::now(); @@ -377,9 +377,9 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : int totalVertices = 0; int totalTriangles = 0; - foreach (const FBXMesh& mesh, result.meshes) { + foreach (const HFMMesh& mesh, result.meshes) { totalVertices += mesh.vertices.size(); - foreach (const FBXMeshPart &meshPart, mesh.parts) { + foreach (const HFMMeshPart &meshPart, mesh.parts) { totalTriangles += meshPart.triangleIndices.size() / 3; // each quad was made into two triangles totalTriangles += 2 * meshPart.quadIndices.size() / 4; @@ -398,17 +398,17 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : } if (fattenFaces) { - FBXGeometry newFbx; - FBXMesh result; + HFMModel newFbx; + HFMMesh result; // count the mesh-parts unsigned int meshCount = 0; - foreach (const FBXMesh& mesh, fbx.meshes) { + foreach (const HFMMesh& mesh, fbx.meshes) { meshCount += mesh.parts.size(); } result.modelTransform = glm::mat4(); // Identity matrix - foreach (const FBXMesh& mesh, fbx.meshes) { + foreach (const HFMMesh& mesh, fbx.meshes) { vUtil.fattenMesh(mesh, fbx.offset, result); } diff --git a/tools/vhacd-util/src/VHACDUtilApp.h b/tools/vhacd-util/src/VHACDUtilApp.h index 0d75275802..7dadad20b3 100644 --- a/tools/vhacd-util/src/VHACDUtilApp.h +++ b/tools/vhacd-util/src/VHACDUtilApp.h @@ -28,7 +28,7 @@ public: VHACDUtilApp(int argc, char* argv[]); ~VHACDUtilApp(); - bool writeOBJ(QString outFileName, FBXGeometry& geometry, bool outputCentimeters, int whichMeshPart = -1); + bool writeOBJ(QString outFileName, HFMModel& hfmModel, bool outputCentimeters, int whichMeshPart = -1); int getReturnCode() const { return _returnCode; } diff --git a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml index 033039b87d..753771987a 100644 --- a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml +++ b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml @@ -16,8 +16,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 -import "qrc:////qml//styles-uit" as HifiStylesUit -import "qrc:////qml//controls-uit" as HifiControlsUit +import stylesUit 1.0 as HifiStylesUit +import controlsUit 1.0 as HifiControlsUit import "qrc:////qml//controls" as HifiControls import "qrc:////qml//hifi" as Hifi