Merge branch 'master' into feature/platform

This commit is contained in:
amer cerkic 2019-05-16 10:03:51 -07:00
commit 6595f86985
117 changed files with 6960 additions and 560 deletions

View file

@ -51,6 +51,10 @@ Install Python 3:
sudo apt-get install python3.6
```
Install node, required to build the jsdoc documentation
```bash
sudo apt-get install nodejs
```
### Get code and checkout the tag you need

View file

@ -53,6 +53,10 @@ Open `%HIFI_DIR%\build\hifi.sln` using Visual Studio.
Change the Solution Configuration (menu ribbon under the menu bar, next to the green play button) from "Debug" to "Release" for best performance.
Create another environment variable (see Step #4)
* Set "Variable name": `PreferredToolArchitecture`
* Set "Variable value": `x64`
Run from the menu bar `Build > Build Solution`.
### Step 7. Testing Interface

View file

@ -17,6 +17,7 @@
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonValue>
#include <shared/QtHelpers.h>
#include <LogHandler.h>
#include <NetworkAccessManager.h>
@ -269,6 +270,13 @@ void AudioMixer::sendStatsPacket() {
return;
}
#ifdef DEBUG_EVENT_QUEUE
QJsonObject qtStats;
_slavePool.queueStats(qtStats);
statsObject["audio_thread_event_queue"] = qtStats;
#endif
// general stats
statsObject["useDynamicJitterBuffers"] = _numStaticJitterFrames == DISABLE_STATIC_JITTER_FRAMES;

View file

@ -113,6 +113,19 @@ void AudioMixerSlavePool::each(std::function<void(AudioMixerSlave& slave)> funct
}
}
#ifdef DEBUG_EVENT_QUEUE
void AudioMixerSlavePool::queueStats(QJsonObject& stats) {
unsigned i = 0;
for (auto& slave : _slaves) {
int queueSize = ::hifi::qt::getEventQueueSize(slave.get());
QString queueName = QString("audio_thread_event_queue_%1").arg(i);
stats[queueName] = queueSize;
i++;
}
}
#endif // DEBUG_EVENT_QUEUE
void AudioMixerSlavePool::setNumThreads(int numThreads) {
// clamp to allowed size
{

View file

@ -17,7 +17,7 @@
#include <vector>
#include <QThread>
#include <shared/QtHelpers.h>
#include <TBBHelpers.h>
#include "AudioMixerSlave.h"
@ -72,6 +72,10 @@ public:
// iterate over all slaves
void each(std::function<void(AudioMixerSlave& slave)> functor);
#ifdef DEBUG_EVENT_QUEUE
void queueStats(QJsonObject& stats);
#endif
void setNumThreads(int numThreads);
int numThreads() { return _numThreads; }

View file

@ -24,6 +24,7 @@
#include <QtCore/QTimer>
#include <QtCore/QThread>
#include <QtCore/QJsonDocument>
#include <shared/QtHelpers.h>
#include <AABox.h>
#include <AvatarLogging.h>
@ -753,6 +754,13 @@ void AvatarMixer::sendStatsPacket() {
statsObject["trailing_mix_ratio"] = _trailingMixRatio;
statsObject["throttling_ratio"] = _throttlingRatio;
#ifdef DEBUG_EVENT_QUEUE
QJsonObject qtStats;
_slavePool.queueStats(qtStats);
statsObject["avatar_thread_event_queue"] = qtStats;
#endif
// this things all occur on the frequency of the tight loop
int tightLoopFrames = _numTightLoopFrames;
int tenTimesPerFrame = tightLoopFrames * 10;

View file

@ -117,6 +117,19 @@ void AvatarMixerSlavePool::each(std::function<void(AvatarMixerSlave& slave)> fun
}
}
#ifdef DEBUG_EVENT_QUEUE
void AvatarMixerSlavePool::queueStats(QJsonObject& stats) {
unsigned i = 0;
for (auto& slave : _slaves) {
int queueSize = ::hifi::qt::getEventQueueSize(slave.get());
QString queueName = QString("avatar_thread_event_queue_%1").arg(i);
stats[queueName] = queueSize;
i++;
}
}
#endif // DEBUG_EVENT_QUEUE
void AvatarMixerSlavePool::setNumThreads(int numThreads) {
// clamp to allowed size
{

View file

@ -20,9 +20,11 @@
#include <TBBHelpers.h>
#include <NodeList.h>
#include <shared/QtHelpers.h>
#include "AvatarMixerSlave.h"
class AvatarMixerSlavePool;
class AvatarMixerSlaveThread : public QThread, public AvatarMixerSlave {
@ -72,6 +74,10 @@ public:
// iterate over all slaves
void each(std::function<void(AvatarMixerSlave& slave)> functor);
#ifdef DEBUG_EVENT_QUEUE
void AvatarMixerSlavePool::queueStats(QJsonObject& stats);
#endif
void setNumThreads(int numThreads);
int numThreads() const { return _numThreads; }

View file

@ -27,6 +27,12 @@
#include "ClientTraitsHandler.h"
#include "AvatarLogging.h"
MixerAvatar::~MixerAvatar() {
if (_challengeTimeout) {
_challengeTimeout->deleteLater();
}
}
void MixerAvatar::fetchAvatarFST() {
_verifyState = nonCertified;
@ -229,6 +235,7 @@ void MixerAvatar::processCertifyEvents() {
QJsonDocument responseJson = QJsonDocument::fromJson(_dynamicMarketResponse.toUtf8());
QString ownerPublicKey;
bool ownerValid = false;
_pendingEvent = false;
if (responseJson["status"].toString() == "success") {
QJsonValue jsonData = responseJson["data"];
if (jsonData.isObject()) {
@ -251,6 +258,7 @@ void MixerAvatar::processCertifyEvents() {
}
sendOwnerChallenge();
_verifyState = challengeClient;
_pendingEvent = true;
} else {
_verifyState = error;
}
@ -259,7 +267,6 @@ void MixerAvatar::processCertifyEvents() {
"message:" << responseJson["message"].toString();
_verifyState = error;
}
_pendingEvent = false;
break;
}
@ -295,6 +302,7 @@ void MixerAvatar::processCertifyEvents() {
}
case requestingOwner:
case challengeClient:
{ // Qt networking done on this thread:
QCoreApplication::processEvents();
break;
@ -324,12 +332,21 @@ void MixerAvatar::sendOwnerChallenge() {
nonceHash.addData(nonce);
_challengeNonceHash = nonceHash.result();
static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s
_challengeTimeout.setInterval(CHALLENGE_TIMEOUT_MS);
_challengeTimeout.connect(&_challengeTimeout, &QTimer::timeout, [this]() {
_verifyState = verificationFailed;
_needsIdentityUpdate = true;
static constexpr int CHALLENGE_TIMEOUT_MS = 5 * 1000; // 5 s
if (_challengeTimeout) {
_challengeTimeout->deleteLater();
}
_challengeTimeout = new QTimer();
_challengeTimeout->setInterval(CHALLENGE_TIMEOUT_MS);
_challengeTimeout->setSingleShot(true);
_challengeTimeout->connect(_challengeTimeout, &QTimer::timeout, this, [this]() {
if (_verifyState == challengeClient) {
_pendingEvent = false;
_verifyState = verificationFailed;
_needsIdentityUpdate = true;
}
});
_challengeTimeout->start();
}
void MixerAvatar::handleChallengeResponse(ReceivedMessage* response) {
@ -337,7 +354,6 @@ void MixerAvatar::handleChallengeResponse(ReceivedMessage* response) {
QByteArray encryptedNonce;
QMutexLocker certifyLocker(&_avatarCertifyLock);
if (_verifyState == challengeClient) {
_challengeTimeout.stop();
_challengeResponse = response->readAll();
_verifyState = challengeResponse;
_pendingEvent = true;

View file

@ -21,6 +21,7 @@ class ResourceRequest;
class MixerAvatar : public AvatarData {
public:
~MixerAvatar();
bool getNeedsHeroCheck() const { return _needsHeroCheck; }
void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; }
@ -53,7 +54,7 @@ private:
QString _ownerPublicKey;
QByteArray _challengeNonceHash;
QByteArray _challengeResponse;
QTimer _challengeTimeout;
QTimer* _challengeTimeout { nullptr };
bool _needsIdentityUpdate { false };
bool generateFSTHash();

View file

@ -372,7 +372,7 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
// Record explicitly filtered-in entity so that extra entities can be flagged.
entityNodeData->insertSentFilteredEntity(entityID);
}
OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData);
OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData, entityNode->getCanGetAndSetPrivateUserData());
if (appendEntityState != OctreeElement::COMPLETED) {
if (appendEntityState == OctreeElement::PARTIAL) {

View file

@ -7,7 +7,8 @@
#
# Construct a default QT location from a root path, a version and an architecture
function(calculate_default_qt_dir _RESULT_NAME)
function(calculate_default_qt_dir _QT_VERSION _RESULT_NAME)
if (ANDROID)
set(QT_DEFAULT_ARCH "android_armv7")
elseif(UWP)
@ -27,22 +28,22 @@ function(calculate_default_qt_dir _RESULT_NAME)
endif()
set_from_env(QT_ROOT QT_ROOT ${QT_DEFAULT_ROOT})
set_from_env(QT_VERSION QT_VERSION "5.10.1")
set_from_env(QT_ARCH QT_ARCH ${QT_DEFAULT_ARCH})
set(${_RESULT_NAME} "${QT_ROOT}/${QT_VERSION}/${QT_ARCH}" PARENT_SCOPE)
set(${_RESULT_NAME} "${QT_ROOT}/${_QT_VERSION}/${QT_ARCH}" PARENT_SCOPE)
endfunction()
# Sets the QT_CMAKE_PREFIX_PATH and QT_DIR variables
# Also enables CMAKE_AUTOMOC and CMAKE_AUTORCC
macro(setup_qt)
set_from_env(QT_VERSION QT_VERSION "5.10.1")
# if QT_CMAKE_PREFIX_PATH was not specified before hand,
# try to use the environment variable
if (NOT QT_CMAKE_PREFIX_PATH)
set(QT_CMAKE_PREFIX_PATH "$ENV{QT_CMAKE_PREFIX_PATH}")
endif()
if (("QT_CMAKE_PREFIX_PATH" STREQUAL "") OR (NOT EXISTS "${QT_CMAKE_PREFIX_PATH}"))
calculate_default_qt_dir(QT_DIR)
calculate_default_qt_dir(${QT_VERSION} QT_DIR)
set(QT_CMAKE_PREFIX_PATH "${QT_DIR}/lib/cmake")
else()
# figure out where the qt dir is

View file

@ -1,5 +1,5 @@
{
"version": 2.2,
"version": 2.3,
"settings": [
{
"name": "metaverse",
@ -224,7 +224,7 @@
"name": "standard_permissions",
"type": "table",
"label": "Domain-Wide User Permissions",
"help": "Indicate which types of users can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
"help": "Indicate which types of users can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
"caption": "Standard Permissions",
"can_add_new_rows": false,
"groups": [
@ -233,8 +233,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
"span": 10
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
"span": 11
}
],
"columns": [
@ -311,6 +311,13 @@
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_get_and_set_private_user_data",
"label": "Can Get and Set Private User Data",
"type": "checkbox",
"editable": true,
"default": false
}
],
"non-deletable-row-key": "permissions_id",
@ -337,6 +344,7 @@
"id_can_rez_tmp": true,
"id_can_rez_tmp_certified": true,
"id_can_write_to_asset_server": true,
"id_can_get_and_set_private_user_data": true,
"permissions_id": "localhost"
},
{
@ -361,8 +369,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 10
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 11
}
],
"columns": [
@ -464,6 +472,13 @@
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_get_and_set_private_user_data",
"label": "Can Get and Set Private User Data",
"type": "checkbox",
"editable": true,
"default": false
}
]
},
@ -482,8 +497,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users in specific groups can replace entire content sets by wiping existing domain content.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 10
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users in specific groups can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 11
}
],
"columns": [
@ -582,6 +597,13 @@
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_get_and_set_private_user_data",
"label": "Can Get and Set Private User Data",
"type": "checkbox",
"editable": true,
"default": false
}
]
},
@ -596,8 +618,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
"span": 10
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
"span": 11
}
],
"columns": [
@ -674,6 +696,13 @@
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_get_and_set_private_user_data",
"label": "Can Get and Set Private User Data",
"type": "checkbox",
"editable": true,
"default": false
}
]
},
@ -688,8 +717,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide IP Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users from specific IPs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific IPs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users from specific IPs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users from specific IPs can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users from specific IPs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users from specific IPs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users from specific IPs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users from specific IPs can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific IP will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). IP address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 10
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide IP Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users from specific IPs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific IPs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users from specific IPs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users from specific IPs can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users from specific IPs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users from specific IPs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users from specific IPs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users from specific IPs can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</li></ul><p>Note that permissions assigned to a specific IP will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). IP address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 11
}
],
"columns": [
@ -766,6 +795,13 @@
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_get_and_set_private_user_data",
"label": "Can Get and Set Private User Data",
"type": "checkbox",
"editable": true,
"default": false
}
]
},
@ -780,8 +816,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide MAC Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific MACs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific MACs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific MACs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific MACs can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific MACs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific MACs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific MACs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific MACs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific MACs can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific MAC will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). MAC address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 10
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide MAC Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific MACs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific MACs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific MACs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific MACs can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific MACs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific MACs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific MACs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific MACs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific MACs can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</li></ul><p>Note that permissions assigned to a specific MAC will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). MAC address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 11
}
],
"columns": [
@ -858,7 +894,14 @@
"type": "checkbox",
"editable": true,
"default": false
}
},
{
"name": "id_can_get_and_set_private_user_data",
"label": "Can Get and Set Private User Data",
"type": "checkbox",
"editable": true,
"default": false
}
]
},
{
@ -872,8 +915,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide Machine Fingerprint Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific Machine Fingerprints can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific Machine Fingerprints can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific Machine Fingerprints can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific Machine Fingerprints can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific Machine Fingerprints can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific Machine Fingerprints can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific Machine Fingerprints can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific Machine Fingerprint will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). Machine Fingerprint address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 10
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide Machine Fingerprint Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific Machine Fingerprints can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific Machine Fingerprints can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific Machine Fingerprints can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific Machine Fingerprints can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific Machine Fingerprints can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific Machine Fingerprints can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific Machine Fingerprints can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</li></ul><p>Note that permissions assigned to a specific Machine Fingerprint will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). Machine Fingerprint address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 11
}
],
"columns": [
@ -950,6 +993,13 @@
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_get_and_set_private_user_data",
"label": "Can Get and Set Private User Data",
"type": "checkbox",
"editable": true,
"default": false
}
]
},

View file

@ -282,6 +282,7 @@ void DomainGatekeeper::updateNodePermissions() {
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryCertifiedEntities;
userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer;
userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent;
userPerms.permissions |= NodePermissions::Permission::canGetAndSetPrivateUserData;
} else {
// at this point we don't have a sending socket for packets from this node - assume it is the active socket
// or the public socket if we haven't activated a socket for the node yet
@ -374,6 +375,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryCertifiedEntities;
userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer;
userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent;
userPerms.permissions |= NodePermissions::Permission::canGetAndSetPrivateUserData;
newNode->setPermissions(userPerms);
return newNode;
}

View file

@ -22,6 +22,7 @@
#include <QtCore/QThread>
#include <QtCore/QUrl>
#include <QtCore/QUrlQuery>
#include <QSaveFile>
#include <AccountManager.h>
#include <Assignment.h>
@ -441,6 +442,12 @@ void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilena
}
}
if (oldVersion < 2.3) {
unpackPermissions();
_standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canGetAndSetPrivateUserData);
packPermissions();
}
// write the current description version to our settings
*versionVariant = _descriptionVersion;
@ -1706,28 +1713,44 @@ void DomainServerSettingsManager::sortPermissions() {
}
void DomainServerSettingsManager::persistToFile() {
sortPermissions();
// make sure we have the dir the settings file is supposed to live in
QFileInfo settingsFileInfo(_configMap.getUserConfigFilename());
if (!settingsFileInfo.dir().exists()) {
settingsFileInfo.dir().mkpath(".");
}
QFile settingsFile(_configMap.getUserConfigFilename());
if (settingsFile.open(QIODevice::WriteOnly)) {
// take a read lock so we can grab the config and write it to file
QReadLocker locker(&_settingsLock);
settingsFile.write(QJsonDocument::fromVariant(_configMap.getConfig()).toJson());
} else {
qCritical("Could not write to JSON settings file. Unable to persist settings.");
// failed to write, reload whatever the current config state is
// with a write lock since we're about to overwrite the config map
QString settingsFilename = _configMap.getUserConfigFilename();
QDir settingsDir = QFileInfo(settingsFilename).dir();
if (!settingsDir.exists() && !settingsDir.mkpath(".")) {
// If the path already exists when the `mkpath` method is
// called, it will return true. It will only return false if the
// path doesn't exist after the call returns.
qCritical("Could not create the settings file parent directory. Unable to persist settings.");
QWriteLocker locker(&_settingsLock);
_configMap.loadConfig();
return;
}
QSaveFile settingsFile(settingsFilename);
if (!settingsFile.open(QIODevice::WriteOnly)) {
qCritical("Could not open the JSON settings file. Unable to persist settings.");
QWriteLocker locker(&_settingsLock);
_configMap.loadConfig();
return;
}
sortPermissions();
QVariantMap conf;
{
QReadLocker locker(&_settingsLock);
conf = _configMap.getConfig();
}
QByteArray json = QJsonDocument::fromVariant(conf).toJson();
if (settingsFile.write(json) == -1) {
qCritical("Could not write to JSON settings file. Unable to persist settings.");
QWriteLocker locker(&_settingsLock);
_configMap.loadConfig();
return;
}
if (!settingsFile.commit()) {
qCritical("Could not commit writes to JSON settings file. Unable to persist settings.");
QWriteLocker locker(&_settingsLock);
_configMap.loadConfig();
return; // defend against future code
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View file

@ -16,6 +16,7 @@ import TabletScriptingInterface 1.0
Item {
id: root;
visible: AvatarInputs.showAudioTools || AvatarInputs.showBubbleTools
objectName: "AvatarInputsBar"
property int modality: Qt.NonModal
readonly property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled;
@ -58,6 +59,6 @@ Item {
BubbleIcon {
dragTarget: parent
visible: !root.hmdActive;
visible: !root.hmdActive && AvatarInputs.showBubbleTools;
}
}

View file

@ -147,6 +147,17 @@ Item {
"Parabolas:\t" + root.parabolaPicksUpdated.x + "/" + root.parabolaPicksUpdated.y + "/" + root.parabolaPicksUpdated.z + "\n " +
"Colliders:\t" + root.collisionPicksUpdated.x + "/" + root.collisionPicksUpdated.y + "/" + root.collisionPicksUpdated.z
}
StatText {
visible: { root.eventQueueDebuggingOn && root.expanded }
text: { if (root.eventQueueDebuggingOn) {
return "Event Queue Depth\n " +
"Main:\t" + root.mainThreadQueueDepth + "\n" +
"NodeList:\t" + root.nodeListThreadQueueDepth;
} else {
return "";
}
}
}
}
}

View file

@ -0,0 +1,227 @@
//
// AvatarApp.qml
//
// Created by Zach Fox on 2019-05-02
// Copyright 2019 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 "../simplifiedConstants" as SimplifiedConstants
import "./components" as AvatarAppComponents
import stylesUit 1.0 as HifiStylesUit
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
Rectangle {
id: root
property bool inventoryReceived: false
property bool isDebuggingFirstUseTutorial: false
property bool keyboardRaised: false
property int numUpdatesAvailable: 0
property string avatarPreviewUrl: ""
onAvatarPreviewUrlChanged: {
sendToScript({
"source": "AvatarApp.qml",
"method": "updateAvatarThumbnailURL",
"data": {
"avatarThumbnailURL": root.avatarPreviewUrl
}
});
}
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
color: simplifiedUI.colors.darkBackground
Component.onCompleted: {
Commerce.getLoginStatus();
}
Connections {
target: MyAvatar
onSkeletonModelURLChanged: {
root.updatePreviewUrl();
}
}
Connections {
target: Commerce
onLoginStatusResult: {
if (isLoggedIn) {
Commerce.getWalletStatus();
} else {
// Show some error to the user
}
}
onWalletStatusResult: {
if (walletStatus === 5) {
getInventory();
} else {
// Show some error to the user
}
}
onInventoryResult: {
avatarAppInventoryModel.handlePage(result.status !== "success" && result.message, result);
root.updatePreviewUrl();
}
}
Image {
id: accent
source: "../images/accent.svg"
anchors.top: parent.top
anchors.right: parent.right
width: 60
height: 103
transform: Scale {
yScale: -1
origin.x: accent.width / 2
origin.y: accent.height / 2
}
}
AvatarAppComponents.DisplayNameHeader {
id: displayNameHeader
previewUrl: avatarPreviewUrl
loading: !inventoryContentsList.visible
anchors.top: parent.top
anchors.topMargin: 30
anchors.left: parent.left
anchors.leftMargin: 24
anchors.right: parent.right
anchors.rightMargin: 24
}
Item {
id: avatarInfoTextContainer
width: parent.implicitWidth
height: childrenRect.height
anchors.top: displayNameHeader.bottom
anchors.topMargin: 30
anchors.left: parent.left
anchors.leftMargin: 24
anchors.right: parent.right
anchors.rightMargin: 24
HifiStylesUit.GraphikRegular {
id: yourAvatarsTitle
text: "Your Avatars"
anchors.top: parent.top
verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignLeft
color: simplifiedUI.colors.text.white
size: 22
}
HifiStylesUit.GraphikRegular {
id: yourAvatarsSubtitle
text: "These are the avatars that you've created and uploaded via the Avatar Creator."
width: parent.width
wrapMode: Text.WordWrap
anchors.top: yourAvatarsTitle.bottom
anchors.topMargin: 6
verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignLeft
color: simplifiedUI.colors.text.darkGrey
size: 14
}
}
HifiModels.PSFListModel {
id: avatarAppInventoryModel
itemsPerPage: 4
listModelName: 'inventory'
listView: inventoryContentsList
getPage: function () {
var editionFilter = "";
var primaryFilter = "avatar";
var titleFilter = "";
Commerce.inventory(
editionFilter,
primaryFilter,
titleFilter,
avatarAppInventoryModel.currentPageToRetrieve,
avatarAppInventoryModel.itemsPerPage
);
}
processPage: function(data) {
inventoryReceived = true;
data.assets.forEach(function (item) {
if (item.status.length > 1) { console.warn("Unrecognized inventory status", item); }
item.status = item.status[0];
});
return data.assets;
}
}
Item {
anchors.top: avatarInfoTextContainer.bottom
anchors.topMargin: 16
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
AnimatedImage {
visible: !inventoryContentsList.visible
anchors.centerIn: parent
width: 72
height: width
source: "../images/loading.gif"
}
ListView {
id: inventoryContentsList
visible: avatarAppInventoryModel.count !== 0
interactive: contentItem.height > height
clip: true
model: avatarAppInventoryModel
anchors.fill: parent
width: parent.width
delegate: AvatarAppComponents.AvatarAppListDelegate {
id: avatarAppListDelegate
itemName: title
itemPreviewImageUrl: preview
itemHref: download_url
standaloneOptimized: model.standalone_optimized
standaloneIncompatible: model.standalone_incompatible
}
}
}
function getInventory() {
avatarAppInventoryModel.getFirstPage();
}
function updatePreviewUrl() {
var previewUrl = "";
var downloadUrl = "";
for (var i = 0; i < avatarAppInventoryModel.count; ++i) {
downloadUrl = avatarAppInventoryModel.get(i).download_url;
previewUrl = avatarAppInventoryModel.get(i).preview;
if (MyAvatar.skeletonModelURL === downloadUrl) {
avatarPreviewUrl = previewUrl;
return;
}
}
}
function fromScript(message) {
switch (message.method) {
default:
console.log('AvatarApp.qml: Unrecognized message from JS');
break;
}
}
signal sendToScript(var message);
}

View file

@ -0,0 +1,124 @@
//
// AvatarAppListDelegate.qml
//
// Created by Zach Fox on 2019-05-09
// Copyright 2019 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 "../../simplifiedConstants" as SimplifiedConstants
import "../../simplifiedControls" as SimplifiedControls
import stylesUit 1.0 as HifiStylesUit
import QtGraphicalEffects 1.0
Rectangle {
id: root
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
property string itemName
property string itemPreviewImageUrl
property string itemHref
property bool standaloneOptimized
property bool standaloneIncompatible
property bool isCurrentItem
property bool isHovering: mouseArea.containsMouse || wearButton.hovered || wearButton.down
height: 102
width: parent.width
color: root.isHovering ? simplifiedUI.colors.darkBackgroundHighlight : "transparent"
Rectangle {
id: borderMask
width: root.isHovering ? itemPreviewImage.width + 4 : itemPreviewImage.width - 4
height: width
radius: width
anchors.centerIn: itemPreviewImage
color: "#FFFFFF"
Behavior on width {
enabled: true
SmoothedAnimation { velocity: 80 }
}
}
Image {
id: itemPreviewImage
source: root.itemPreviewImageUrl
anchors.left: parent.left
anchors.leftMargin: 20
anchors.verticalCenter: parent.verticalCenter
height: 60
width: height
fillMode: Image.PreserveAspectCrop
mipmap: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: mask
}
Rectangle {
id: mask
width: itemPreviewImage.width
height: itemPreviewImage.height
radius: itemPreviewImage.width / 2
visible: false
}
}
HifiStylesUit.GraphikRegular {
id: avatarName
text: root.itemName
anchors.left: itemPreviewImage.right
anchors.leftMargin: 20
anchors.right: root.isHovering ? wearButton.left : parent.right
anchors.rightMargin: 20
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
height: parent.height
size: 20
color: simplifiedUI.colors.text.almostWhite
}
SimplifiedControls.Button {
id: wearButton
visible: MyAvatar.skeletonModelURL !== root.itemHref && root.isHovering
anchors.right: parent.right
anchors.rightMargin: 24
anchors.verticalCenter: parent.verticalCenter
width: 165
height: 32
text: "WEAR"
onClicked: {
MyAvatar.useFullAvatarURL(root.itemHref);
}
}
SimplifiedControls.CheckBox {
id: wornCheckBox
enabled: false
visible: MyAvatar.skeletonModelURL === root.itemHref
anchors.right: parent.right
anchors.rightMargin: 24
anchors.verticalCenter: parent.verticalCenter
width: 14
height: 14
checked: true
}
MouseArea {
z: -1
id: mouseArea
anchors.fill: parent
hoverEnabled: true
}
}

View file

@ -0,0 +1,108 @@
//
// DisplayNameHeader.qml
//
// Created by Wayne Chen on 2019-05-03
// Copyright 2019 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 "../../simplifiedConstants" as SimplifiedConstants
import "../../simplifiedControls" as SimplifiedControls
import stylesUit 1.0 as HifiStylesUit
import controlsUit 1.0 as HifiControlsUit
import QtGraphicalEffects 1.0
Item {
id: root
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
height: itemPreviewImage.height
property string previewUrl: ""
property bool loading: true
AnimatedImage {
visible: root.loading
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
height: 72
width: height
source: "../../images/loading.gif"
}
Image {
id: itemPreviewImage
visible: !root.loading
source: root.previewUrl
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
height: 100
width: height
fillMode: Image.PreserveAspectCrop
layer.enabled: true
layer.effect: OpacityMask {
maskSource: mask
}
mipmap: true
Rectangle {
id: mask
width: itemPreviewImage.width
height: width
radius: width / 2
visible: false
}
}
Item {
id: displayNameContainer
height: itemPreviewImage.height
anchors.right: parent.right
anchors.left: itemPreviewImage.right
anchors.leftMargin: 21
anchors.verticalCenter: parent.verticalCenter
HifiStylesUit.GraphikRegular {
id: displayNameLabel
text: "Display Name"
color: simplifiedUI.colors.text.lightGrey
size: 16
anchors.top: parent.top
anchors.right: parent.right
anchors.bottom: parent.verticalCenter
anchors.left: parent.left
verticalAlignment: Text.AlignBottom
}
Item {
id: myDisplayNameContainer
width: parent.width
height: 42
anchors.top: parent.verticalCenter
anchors.right: parent.right
anchors.left: parent.left
SimplifiedControls.TextField {
id: myDisplayNameText
text: MyAvatar.sessionDisplayName === "" ? MyAvatar.displayName : MyAvatar.sessionDisplayName
maximumLength: 256
clip: true
anchors.fill: parent
onEditingFinished: {
if (MyAvatar.displayName !== text) {
MyAvatar.displayName = text;
}
myDisplayNameText.focus = false;
}
onFocusChanged: {
myDisplayNameText.autoScroll = focus;
}
}
}
}
}

View file

@ -0,0 +1,4 @@
<svg width="94" height="175" viewBox="0 0 94 175" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 175L93.4322 175L73.3124 166.208L0 175Z" fill="#009036"/>
<path d="M73.3124 166.208L93.4322 175L93.4322 -1.34999e-05L73.3124 166.208Z" fill="#FF42A7"/>
</svg>

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View file

@ -0,0 +1,192 @@
//
// InputDeviceButton.qml
//
// Created by Zach Fox on 2019-05-02
// Based off of MicBarApplication.qml by Zach Pomerantz and Wayne Chen
// Copyright 2019 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 QtGraphicalEffects 1.0
import stylesUit 1.0
import TabletScriptingInterface 1.0
import "../simplifiedConstants" as SimplifiedConstants
Rectangle {
id: micBar
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
readonly property var level: AudioScriptingInterface.inputLevel
readonly property var clipping: AudioScriptingInterface.clipping
property var muted: AudioScriptingInterface.muted
property var pushToTalk: AudioScriptingInterface.pushToTalk
property var pushingToTalk: AudioScriptingInterface.pushingToTalk
readonly property var userSpeakingLevel: 0.4
property bool gated: false
readonly property string unmutedIcon: "images/mic-unmute-i.svg"
readonly property string mutedIcon: "images/mic-mute-i.svg"
readonly property string pushToTalkIcon: "images/mic-ptt-i.svg"
readonly property string clippingIcon: "images/mic-clip-i.svg"
readonly property string gatedIcon: "images/mic-gate-i.svg"
Connections {
target: AudioScriptingInterface
onNoiseGateOpened: {
gated = false;
}
onNoiseGateClosed: {
gated = false;
}
}
height: 30
width: 34
opacity: 0.7
onLevelChanged: {
var rectOpacity = (muted && (level >= userSpeakingLevel)) ? 1.0 : 0.7;
if (pushToTalk && !pushingToTalk) {
rectOpacity = (mouseArea.containsMouse) ? 1.0 : 0.7;
} else if (mouseArea.containsMouse && rectOpacity != 1.0) {
rectOpacity = 1.0;
}
micBar.opacity = rectOpacity;
}
color: "#00000000"
MouseArea {
id: mouseArea
anchors {
left: icon.left
right: bar.right
top: icon.top
bottom: icon.bottom
}
hoverEnabled: true
scrollGestureEnabled: false
onClicked: {
if (pushToTalk) {
return;
}
AudioScriptingInterface.muted = !muted;
Tablet.playSound(TabletEnums.ButtonClick);
muted = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding
}
onContainsMouseChanged: {
if (containsMouse) {
Tablet.playSound(TabletEnums.ButtonHover);
}
}
}
QtObject {
id: colors
readonly property string unmutedColor: simplifiedUI.colors.controls.inputVolumeButton.text.noisy
readonly property string gatedColor: "#00BDFF"
readonly property string mutedColor: simplifiedUI.colors.controls.inputVolumeButton.text.muted
readonly property string gutter: "#575757"
readonly property string greenStart: "#39A38F"
readonly property string greenEnd: "#1FC6A6"
readonly property string yellow: "#C0C000"
readonly property string fill: "#55000000"
readonly property string icon: (muted || clipping) ? mutedColor : gated ? gatedColor : unmutedColor
}
Item {
id: icon
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
width: parent.width - bar.width - bar.anchors.leftMargin
height: parent.height
Item {
anchors.fill: parent
opacity: mouseArea.containsMouse ? 1.0 : 0.7
Image {
id: image
source: (pushToTalk) ? pushToTalkIcon : muted ? mutedIcon :
clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon
anchors.fill: parent
fillMode: Image.PreserveAspectFit
}
ColorOverlay {
id: imageOverlay
anchors { fill: image }
source: image
color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : colors.icon
}
}
}
Item {
id: bar
anchors {
left: icon.right
leftMargin: 0
verticalCenter: icon.verticalCenter
}
width: 4
height: parent.height
Rectangle { // base
id: baseBar
radius: 4
anchors { fill: parent }
color: colors.gutter
}
Rectangle { // mask
id: mask
height: micBar.muted ? parent.height : parent.height * level
color: micBar.muted ? colors.mutedColor : "white"
width: parent.width
radius: 5
anchors {
bottom: parent.bottom
bottomMargin: 0
left: parent.left
leftMargin: 0
}
}
LinearGradient {
anchors { fill: mask }
visible: mask.visible && !micBar.muted
source: mask
start: Qt.point(0, 0)
end: Qt.point(0, bar.height)
rotation: 180
gradient: Gradient {
GradientStop {
position: 0.0
color: colors.greenStart
}
GradientStop {
position: 0.5
color: colors.greenEnd
}
GradientStop {
position: 1.0
color: colors.yellow
}
}
}
}
}

View file

@ -0,0 +1,10 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.3383 4.13446L23.8713 5.60152C21.9374 3.46761 19.2033 2.06723 16.0691 2.06723C13.0683 2.06723 10.4009 3.40093 8.46707 5.40147L7 3.9344C9.26728 1.53375 12.5348 0 16.0691 0C19.7368 0 23.071 1.60044 25.3383 4.13446ZM21.9376 7.53584L20.4705 9.0029C19.4703 7.66921 17.8698 6.86899 16.0693 6.86899C14.4022 6.86899 12.9351 7.66921 11.8682 8.80285L10.4011 7.33578C11.8015 5.80203 13.802 4.80176 16.0693 4.80176C18.4033 4.80176 20.5372 5.86871 21.9376 7.53584ZM17.9575 30.1572C17.9575 31.1771 17.1307 32.0039 16.1108 32.0039C15.0909 32.0039 14.2642 31.1771 14.2642 30.1572C14.2642 29.1373 15.0909 28.3105 16.1108 28.3105C17.1307 28.3105 17.9575 29.1373 17.9575 30.1572ZM18.3632 11.0801H14.1597L15.0116 25.8539H17.4867L18.3632 11.0801Z" fill="#EA4C5F"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.1999 4.72587C17.5049 4.72587 18.5628 3.66795 18.5628 2.36294C18.5628 1.05792 17.5049 0 16.1999 0C14.8948 0 13.8369 1.05792 13.8369 2.36294C13.8369 3.66795 14.8948 4.72587 16.1999 4.72587ZM18.6667 11.8004V14.7337H13.7334V11.8004C13.7334 10.467 14.8667 9.40039 16.2 9.40039C17.6 9.40039 18.6667 10.467 18.6667 11.8004ZM13.7334 20.1332V17.2666H18.6667V20.1332C18.6667 21.4665 17.5333 22.5332 16.2 22.5332C14.8667 22.5332 13.7334 21.4665 13.7334 20.1332ZM23.6665 20.6V17.0667C23.6665 16.4 23.0665 15.9334 22.4665 15.9334C21.7998 15.9334 21.3332 16.4667 21.3332 17.1333V20.6C21.3332 23.0666 19.0665 25.0666 16.3332 25.0666C13.5999 25.0666 11.3333 23.0666 11.3333 20.6V17.0667C11.3333 16.4 10.8666 15.8667 10.2666 15.8667C9.59999 15.8 9 16.2667 9 16.9333V20.6C9 23.9999 11.6666 26.7999 15.1333 27.3332V29.5998H12.2666C11.6 29.5998 11.0666 30.1332 11.0666 30.7998C11.0666 31.4665 11.6 31.9998 12.2666 31.9998H20.4665C21.1332 31.9998 21.6665 31.4665 21.6665 30.7998C21.6665 30.1332 21.1332 29.5998 20.4665 29.5998H17.5332V27.3332C20.9998 26.7332 23.6665 23.9999 23.6665 20.6Z" fill="#00B4EF" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.6441 4.13328L24.1775 5.59992C22.2442 3.46662 19.5109 2.06664 16.3776 2.06664C13.3776 2.06664 10.711 3.39995 8.77768 5.39993L7.31104 3.93328C9.57767 1.53331 12.8443 0 16.3776 0C20.0442 0 23.3775 1.59998 25.6441 4.13328ZM20.7777 9.00072L22.2444 7.53408C20.8444 5.86743 18.7111 4.80078 16.3778 4.80078C14.1111 4.80078 12.1112 5.80077 10.7112 7.33408L12.1778 8.80073C13.2445 7.66741 14.7111 6.86742 16.3778 6.86742C18.1777 6.86742 19.7777 7.66741 20.7777 9.00072ZM18.8803 12.0758V11.7496C18.8803 10.4445 17.7763 9.40039 16.4775 9.40039C15.1787 9.40039 14.0747 10.4445 14.0747 11.7496V16.2521L18.8803 12.0758ZM14.543 21.5129L12.6113 23.2103C13.4959 24.2599 14.9141 24.9311 16.4774 24.9311C19.14 24.9311 21.348 22.9735 21.348 20.559V17.1658C21.348 16.5132 21.8026 15.9912 22.452 15.9912C23.0364 15.9912 23.6209 16.448 23.6209 17.1005V20.559C23.6209 23.887 21.0233 26.6277 17.6464 27.1498V29.3684H20.5038C21.1532 29.3684 21.6727 29.8905 21.6727 30.543C21.6727 31.1956 21.1532 31.7176 20.5038 31.7176H12.5161C11.8667 31.7176 11.3471 31.1956 11.3471 30.543C11.3471 29.8905 11.8667 29.3684 12.5161 29.3684H15.3085V27.1498C13.5328 26.915 11.9589 26.0045 10.8771 24.7343L8.9443 26.4327C8.48972 26.8242 7.77537 26.759 7.38573 26.3022L7.25585 26.1717C6.8662 25.7149 6.93114 24.9971 7.38573 24.6056L23.8806 9.98853C24.3352 9.597 25.0495 9.66226 25.4392 10.119L25.5691 10.2495C25.9587 10.7716 25.8938 11.4241 25.5041 11.8809L18.8803 17.7015V20.1017C18.8803 21.4068 17.7763 22.4509 16.4775 22.4509C15.6689 22.4509 14.9744 22.0838 14.543 21.5129ZM10.5679 15.9919C11.1523 15.9919 11.6069 16.5139 11.6069 17.1664V18.4715L9.33398 20.4944V17.0359C9.39892 16.4486 9.91845 15.9266 10.5679 15.9919Z" fill="#EA4C5F"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.6441 4.13328L24.1775 5.59992C22.2442 3.46662 19.5109 2.06664 16.3776 2.06664C13.3776 2.06664 10.711 3.39995 8.77768 5.39993L7.31104 3.93328C9.57767 1.53331 12.8443 0 16.3776 0C20.0442 0 23.3775 1.59998 25.6441 4.13328ZM20.7777 9.00072L22.2444 7.53408C20.8444 5.86743 18.7111 4.80078 16.3778 4.80078C14.1111 4.80078 12.1112 5.80077 10.7112 7.33408L12.1778 8.80073C13.2445 7.66741 14.7111 6.86742 16.3778 6.86742C18.1777 6.86742 19.7777 7.66741 20.7777 9.00072ZM18.8803 12.0758V11.7496C18.8803 10.4445 17.7763 9.40039 16.4775 9.40039C15.1787 9.40039 14.0747 10.4445 14.0747 11.7496V16.2521L18.8803 12.0758ZM14.543 21.5129L12.6113 23.2103C13.4959 24.2599 14.9141 24.9311 16.4774 24.9311C19.14 24.9311 21.348 22.9735 21.348 20.559V17.1658C21.348 16.5132 21.8026 15.9912 22.452 15.9912C23.0364 15.9912 23.6209 16.448 23.6209 17.1005V20.559C23.6209 23.887 21.0233 26.6277 17.6464 27.1498V29.3684H20.5038C21.1532 29.3684 21.6727 29.8905 21.6727 30.543C21.6727 31.1956 21.1532 31.7176 20.5038 31.7176H12.5161C11.8667 31.7176 11.3471 31.1956 11.3471 30.543C11.3471 29.8905 11.8667 29.3684 12.5161 29.3684H15.3085V27.1498C13.5328 26.915 11.9589 26.0045 10.8771 24.7343L8.9443 26.4327C8.48972 26.8242 7.77537 26.759 7.38573 26.3022L7.25585 26.1717C6.8662 25.7149 6.93114 24.9971 7.38573 24.6056L23.8806 9.98853C24.3352 9.597 25.0495 9.66226 25.4392 10.119L25.5691 10.2495C25.9587 10.7716 25.8938 11.4241 25.5041 11.8809L18.8803 17.7015V20.1017C18.8803 21.4068 17.7763 22.4509 16.4775 22.4509C15.6689 22.4509 14.9744 22.0838 14.543 21.5129ZM10.5679 15.9919C11.1523 15.9919 11.6069 16.5139 11.6069 17.1664V18.4715L9.33398 20.4944V17.0359C9.39893 16.4486 9.91845 15.9266 10.5679 15.9919Z" fill="#EA4C5F"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1 @@
<svg id="Art" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><defs><style>.cls-1{fill-rule:evenodd;}</style></defs><title>mic-ptt-a</title><path class="cls-1" d="M34.52,10.09l-1.85,1.85a13.19,13.19,0,0,0-9.82-4.46,13.35,13.35,0,0,0-9.58,4.2L11.42,9.84a15.66,15.66,0,0,1,23.1.25Zm-6.13,6.13,1.85-1.85a9.62,9.62,0,0,0-14.53-.25L17.55,16a7.34,7.34,0,0,1,5.3-2.44A6.85,6.85,0,0,1,28.39,16.22ZM23,16a3.58,3.58,0,0,1,1.51.3,3.68,3.68,0,0,1,1.26.88,3.88,3.88,0,0,1,.84,1.3A3.94,3.94,0,0,1,26.9,20v5.07a1.91,1.91,0,0,1,.48-.05A3.93,3.93,0,0,1,30,26.07a3.38,3.38,0,0,1,1.5-.32,3.27,3.27,0,0,1,2.77,1.36,2.75,2.75,0,0,1,.85-.1,3.35,3.35,0,0,1,1.33.25,3.18,3.18,0,0,1,1.12.76,3.23,3.23,0,0,1,.73,1.13,3.32,3.32,0,0,1,.24,1.32v3.31a12.27,12.27,0,0,1-.43,3.41l-1.36,5.65a2.67,2.67,0,0,1-1,1.55A2.89,2.89,0,0,1,34,45H23a4.47,4.47,0,0,1-1.76-.43,3.88,3.88,0,0,1-1.36-1.12L14.1,35.7a3.72,3.72,0,0,1-.8-2.35,3.64,3.64,0,0,1,.28-1.5,3.75,3.75,0,0,1,.84-1.27,3.9,3.9,0,0,1,2.77-1.18,4.5,4.5,0,0,1,2,.54V19.88a4.06,4.06,0,0,1,1.13-2.78,3.74,3.74,0,0,1,1.25-.83A3.85,3.85,0,0,1,23,16Zm0,2a1.89,1.89,0,0,0-.74.12,2,2,0,0,0-1.06,1,1.92,1.92,0,0,0-.15.74V35.21l-2.32-3.06a2,2,0,0,0-.7-.59,1.88,1.88,0,0,0-.9-.2,1.85,1.85,0,0,0-.74.15,2,2,0,0,0-.63.43,2,2,0,0,0-.4.63,1.9,1.9,0,0,0-.13.74,2,2,0,0,0,.38,1.17l5.86,7.79a1.79,1.79,0,0,0,.68.57A1.74,1.74,0,0,0,23,43H34a1.23,1.23,0,0,0,.59-.15.88.88,0,0,0,.24-.23.71.71,0,0,0,.13-.31l1.37-5.6a12,12,0,0,0,.37-3V30.43a1.7,1.7,0,0,0-.43-1.07,1.31,1.31,0,0,0-.47-.37,1.35,1.35,0,0,0-.59-.11,1.46,1.46,0,0,0-.55.11,1.23,1.23,0,0,0-.46.32,1.64,1.64,0,0,0-.43,1.07h-.48v-1a1.52,1.52,0,0,0-.12-.66,1.61,1.61,0,0,0-.37-.56,1.63,1.63,0,0,0-1.22-.54,2,2,0,0,0-1.23.54,1.77,1.77,0,0,0-.36.53,1.57,1.57,0,0,0-.11.64v1h-.49V29a2.22,2.22,0,0,0-.58-1.44,1.71,1.71,0,0,0-.62-.44A1.88,1.88,0,0,0,27.4,27a2,2,0,0,0-.74.13,1.85,1.85,0,0,0-.63.41,2,2,0,0,0-.53,1.36v1.5h-.57V20a2,2,0,0,0-.54-1.44,1.75,1.75,0,0,0-.63-.44A1.73,1.73,0,0,0,23,18Z"/></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Art" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M16.7,13.2c-0.3,0.3-0.7,0.6-1,0.9l1.8,1.9c1.4-1.5,3.3-2.4,5.3-2.4c2.2,0,4.2,0.9,5.5,2.7l1.8-1.8
C26.8,10.3,20.8,9.8,16.7,13.2z M32.7,11.9l1.9-1.9c-0.3-0.3-0.6-0.7-1-1c-6.3-5.9-16.2-5.6-22.1,0.7l1.9,1.8
c2.5-2.6,5.9-4.2,9.6-4.2C26.6,7.5,30.2,9.1,32.7,11.9z M38.3,29.1c-0.2-0.4-0.4-0.8-0.7-1.1c-0.3-0.3-0.7-0.6-1.1-0.8
C36,27.1,35.6,27,35.1,27c-0.3,0-0.6,0-0.8,0.1c-0.6-0.9-1.7-1.4-2.8-1.4c-0.5,0-1,0.1-1.5,0.3c-0.7-0.7-1.6-1-2.6-1.1
c-0.2,0-0.3,0-0.5,0.1V20c0-0.5-0.1-1-0.3-1.5c-0.2-0.5-0.5-0.9-0.8-1.3c-0.4-0.4-0.8-0.7-1.3-0.9C24,16.1,23.5,16,23,16
c-0.5,0-1,0.1-1.4,0.3c-0.5,0.2-0.9,0.5-1.3,0.8c-0.7,0.7-1.1,1.7-1.1,2.8v10.1c-0.6-0.3-1.3-0.5-2-0.5c-1,0-2,0.4-2.8,1.2
c-0.4,0.4-0.6,0.8-0.8,1.3c-0.2,0.5-0.3,1-0.3,1.5c0,0.9,0.3,1.7,0.8,2.4l5.8,7.8c0.4,0.5,0.8,0.9,1.4,1.1c0.6,0.3,1.2,0.4,1.8,0.4
h11c0.6,0,1.2-0.2,1.8-0.6c0.5-0.4,0.9-0.9,1-1.6l1.4-5.6c0.3-1.1,0.4-2.3,0.4-3.4v-3.3C38.6,30,38.5,29.6,38.3,29.1z M36.7,33.7
c0,1-0.1,2-0.4,3L35,42.3c0,0.1-0.1,0.2-0.1,0.3c-0.1,0.1-0.1,0.2-0.2,0.2C34.4,42.9,34.2,43,34,43H23c-0.3,0-0.6,0-0.8-0.2
c-0.3-0.1-0.5-0.3-0.7-0.6l-5.9-7.8c-0.2-0.3-0.4-0.7-0.4-1.2c0-0.3,0-0.5,0.1-0.7c0.1-0.2,0.2-0.4,0.4-0.6c0.2-0.2,0.4-0.3,0.6-0.4
c0.2-0.1,0.5-0.2,0.7-0.2c0.3,0,0.6,0.1,0.9,0.2c0.3,0.1,0.5,0.3,0.7,0.6l2.3,3.1V19.9c0-0.3,0.1-0.5,0.2-0.7c0.2-0.5,0.6-0.8,1.1-1
C22.5,18,22.7,18,23,18c0.3,0,0.5,0,0.8,0.1c0.2,0.1,0.5,0.2,0.6,0.4c0.4,0.4,0.6,0.9,0.5,1.4v10.4h0.6v-1.5c0-0.5,0.2-1,0.5-1.4
c0.2-0.2,0.4-0.3,0.6-0.4c0.2-0.1,0.5-0.1,0.7-0.1c0.3,0,0.5,0,0.8,0.1c0.2,0.1,0.4,0.2,0.6,0.4c0.4,0.4,0.6,0.9,0.6,1.4v1.3h0.5v-1
c0-0.2,0-0.4,0.1-0.6c0.1-0.2,0.2-0.4,0.4-0.5c0.3-0.3,0.8-0.5,1.2-0.5c0.5,0,0.9,0.2,1.2,0.5c0.2,0.2,0.3,0.3,0.4,0.6
c0.1,0.2,0.1,0.4,0.1,0.7v1h0.5c0-0.4,0.2-0.8,0.4-1.1c0.1-0.1,0.3-0.3,0.5-0.3c0.2-0.1,0.4-0.1,0.6-0.1c0.2,0,0.4,0,0.6,0.1
c0.2,0.1,0.3,0.2,0.5,0.4c0.3,0.3,0.4,0.7,0.4,1.1V33.7z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8664 5.59992L25.3331 4.13328C23.0664 1.59998 19.7332 0 16.0665 0C12.5333 0 9.26664 1.53331 7 3.93328L8.46665 5.39993C10.4 3.39995 13.0666 2.06664 16.0665 2.06664C19.1998 2.06664 21.9331 3.46662 23.8664 5.59992ZM20.4664 8.99975L21.9331 7.5331C20.5331 5.86646 18.3998 4.7998 16.0665 4.7998C13.7999 4.7998 11.7999 5.79979 10.3999 7.3331L11.8665 8.79975C12.9332 7.66643 14.3998 6.86644 16.0665 6.86644C17.8665 6.86644 19.4664 7.66643 20.4664 8.99975ZM18.5334 11.8004V14.7337H13.6001V11.8004C13.6001 10.467 14.7334 9.40039 16.0667 9.40039C17.4667 9.40039 18.5334 10.467 18.5334 11.8004ZM13.6001 17.2666V20.1332C13.6001 21.4665 14.7334 22.5332 16.0667 22.5332C17.4 22.5332 18.5334 21.4665 18.5334 20.1332V17.2666H13.6001ZM23.5332 17.0667V20.6C23.5332 23.9999 20.8665 26.7332 17.3999 27.3332V29.5998H20.3332C20.9999 29.5998 21.5332 30.1332 21.5332 30.7998C21.5332 31.4665 20.9999 31.9998 20.3332 31.9998H12.1333C11.4667 31.9998 10.9333 31.4665 10.9333 30.7998C10.9333 30.1332 11.4667 29.5998 12.1333 29.5998H14.9999V27.3332C11.5333 26.7999 8.8667 23.9999 8.8667 20.6V16.9333C8.8667 16.2667 9.46669 15.8 10.1333 15.8667C10.7333 15.8667 11.2 16.4 11.2 17.0667V20.6C11.2 23.0666 13.4666 25.0666 16.1999 25.0666C18.9332 25.0666 21.1999 23.0666 21.1999 20.6V17.1333C21.1999 16.4667 21.6665 15.9334 22.3332 15.9334C22.9332 15.9334 23.5332 16.4 23.5332 17.0667Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8664 5.59992L25.3331 4.13328C23.0664 1.59998 19.7332 0 16.0665 0C12.5333 0 9.26664 1.53331 7 3.93328L8.46665 5.39993C10.4 3.39995 13.0666 2.06664 16.0665 2.06664C19.1998 2.06664 21.9331 3.46662 23.8664 5.59992ZM20.4664 8.99975L21.9331 7.5331C20.5331 5.86646 18.3998 4.7998 16.0665 4.7998C13.7999 4.7998 11.7999 5.79979 10.3999 7.3331L11.8665 8.79975C12.9332 7.66643 14.3998 6.86644 16.0665 6.86644C17.8665 6.86644 19.4664 7.66643 20.4664 8.99975ZM18.5334 11.8004V14.7337H13.6001V11.8004C13.6001 10.467 14.7334 9.40039 16.0667 9.40039C17.4667 9.40039 18.5334 10.467 18.5334 11.8004ZM13.6001 17.2666V20.1332C13.6001 21.4665 14.7334 22.5332 16.0667 22.5332C17.4 22.5332 18.5334 21.4665 18.5334 20.1332V17.2666H13.6001ZM23.5332 17.0667V20.6C23.5332 23.9999 20.8665 26.7332 17.3999 27.3332V29.5998H20.3332C20.9999 29.5998 21.5332 30.1332 21.5332 30.7998C21.5332 31.4665 20.9999 31.9998 20.3332 31.9998H12.1333C11.4667 31.9998 10.9333 31.4665 10.9333 30.7998C10.9333 30.1332 11.4667 29.5998 12.1333 29.5998H14.9999V27.3332C11.5333 26.7999 8.8667 23.9999 8.8667 20.6V16.9333C8.8667 16.2667 9.46669 15.8 10.1333 15.8667C10.7333 15.8667 11.2 16.4 11.2 17.0667V20.6C11.2 23.0666 13.4666 25.0666 16.1999 25.0666C18.9332 25.0666 21.1999 23.0666 21.1999 20.6V17.1333C21.1999 16.4667 21.6665 15.9334 22.3332 15.9334C22.9332 15.9334 23.5332 16.4 23.5332 17.0667Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,147 @@
//
// SettingsApp.qml
//
// Created by Zach Fox on 2019-05-02
// Copyright 2019 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 "../simplifiedConstants" as SimplifiedConstants
import stylesUit 1.0 as HifiStylesUit
import "./audio" as AudioSettings
import "./general" as GeneralSettings
import "./vr" as VrSettings
Rectangle {
property string activeTabView: "generalTabView"
id: root
color: simplifiedUI.colors.darkBackground
anchors.fill: parent
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
Rectangle {
id: tabContainer
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: 64
color: simplifiedUI.colors.highlightOnDark
ListModel {
id: tabListModel
ListElement {
tabTitle: "General"
tabViewName: "generalTabView"
}
ListElement {
tabTitle: "Audio"
tabViewName: "audioTabView"
}
ListElement {
tabTitle: "VR"
tabViewName: "vrTabView"
}
}
Component {
id: highlightBar
Rectangle {
color: simplifiedUI.colors.darkBackground
}
}
ListView {
id: tabListView
anchors.fill: parent
contentHeight: parent.height
contentWidth: childrenRect.width
orientation: ListView.Horizontal
model: tabListModel
highlight: highlightBar
interactive: contentItem.width > width
delegate: Item {
width: tabTitleText.paintedWidth + 64
height: parent.height
HifiStylesUit.GraphikRegular {
id: tabTitleText
color: simplifiedUI.colors.text.white
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: model.tabTitle
size: 24
}
MouseArea {
anchors.fill: parent
onClicked: {
tabListView.currentIndex = index;
root.activeTabView = model.tabViewName;
}
}
}
}
}
Item {
id: tabViewContainers
anchors.top: tabContainer.bottom
anchors.left: parent.left
anchors.leftMargin: 26
anchors.right: parent.right
anchors.rightMargin: 26
anchors.bottom: parent.bottom
GeneralSettings.General {
id: generalTabViewContainer
visible: activeTabView === "generalTabView"
anchors.fill: parent
onSendNameTagInfo: {
sendToScript(message);
}
}
AudioSettings.Audio {
id: audioTabViewContainer
visible: activeTabView === "audioTabView"
anchors.fill: parent
}
VrSettings.VR {
id: vrTabViewContainer
visible: activeTabView === "vrTabView"
anchors.fill: parent
}
}
Image {
source: "../images/accent.svg"
anchors.right: parent.right
anchors.bottom: parent.bottom
width: 94
height: 175
}
function fromScript(message) {
switch (message.method) {
default:
console.log('SettingsApp.qml: Unrecognized message from JS');
break;
}
}
signal sendToScript(var message);
}

View file

@ -0,0 +1,363 @@
//
// Audio.qml
//
// Created by Zach Fox on 2019-05-06
// Copyright 2019 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 "../../simplifiedConstants" as SimplifiedConstants
import "../../simplifiedControls" as SimplifiedControls
import stylesUit 1.0 as HifiStylesUit
import QtQuick.Layouts 1.3
Flickable {
id: root
contentWidth: parent.width
contentHeight: audioColumnLayout.height
topMargin: 16
bottomMargin: 16
clip: true
function changePeakValuesEnabled(enabled) {
if (!enabled) {
AudioScriptingInterface.devices.input.peakValuesEnabled = true;
}
}
onVisibleChanged: {
AudioScriptingInterface.devices.input.peakValuesEnabled = visible;
if (visible) {
root.contentX = 0;
root.contentY = -root.topMargin;
AudioScriptingInterface.devices.input.peakValuesEnabledChanged.connect(changePeakValuesEnabled);
} else {
AudioScriptingInterface.devices.input.peakValuesEnabledChanged.disconnect(changePeakValuesEnabled);
}
}
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
ColumnLayout {
id: audioColumnLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: simplifiedUI.margins.settings.spacingBetweenSettings
ColumnLayout {
id: volumeControlsContainer
Layout.preferredWidth: parent.width
spacing: 0
HifiStylesUit.GraphikRegular {
id: volumeControlsTitle
text: "Volume Controls"
Layout.maximumWidth: parent.width
height: paintedHeight
size: 22
color: simplifiedUI.colors.text.white
}
SimplifiedControls.Slider {
id: peopleVolume
anchors.left: parent.left
anchors.right: parent.right
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
height: 30
labelText: "People Volume"
from: -60
to: 10
defaultValue: 0.0
value: AudioScriptingInterface.getAvatarGain()
live: true
onValueChanged: {
if (AudioScriptingInterface.getAvatarGain() != peopleVolume.value) {
AudioScriptingInterface.setAvatarGain(peopleVolume.value);
}
}
}
SimplifiedControls.Slider {
id: environmentVolume
anchors.left: parent.left
anchors.right: parent.right
Layout.topMargin: 2
height: 30
labelText: "Environment Volume"
from: -60
to: 10
defaultValue: 0.0
value: AudioScriptingInterface.getInjectorGain()
live: true
onValueChanged: {
if (AudioScriptingInterface.getInjectorGain() != environmentVolume.value) {
AudioScriptingInterface.setInjectorGain(environmentVolume.value);
}
}
}
SimplifiedControls.Slider {
id: systemSoundVolume
anchors.left: parent.left
anchors.right: parent.right
Layout.topMargin: 2
height: 30
labelText: "System Sound Volume"
from: -60
to: 10
defaultValue: 0.0
value: AudioScriptingInterface.getSystemInjectorGain()
live: true
onValueChanged: {
if (AudioScriptingInterface.getSystemInjectorGain() != systemSoundVolume.value) {
AudioScriptingInterface.setSystemInjectorGain(systemSoundVolume.value);
}
}
}
}
ColumnLayout {
id: micControlsContainer
Layout.preferredWidth: parent.width
spacing: 0
HifiStylesUit.GraphikRegular {
id: micControlsTitle
text: "Default Mute Controls"
Layout.maximumWidth: parent.width
height: paintedHeight
size: 22
color: simplifiedUI.colors.text.white
}
ColumnLayout {
id: micControlsSwitchGroup
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
SimplifiedControls.Switch {
id: muteMicrophoneSwitch
width: parent.width
height: 18
labelTextOn: "Mute Microphone"
checked: AudioScriptingInterface.mutedDesktop
onClicked: {
AudioScriptingInterface.mutedDesktop = !AudioScriptingInterface.mutedDesktop;
}
}
SimplifiedControls.Switch {
id: pushToTalkSwitch
width: parent.width
height: 18
labelTextOn: "Push to Talk - Press and Hold \"T\" to Talk"
checked: AudioScriptingInterface.pushToTalkDesktop
onClicked: {
AudioScriptingInterface.pushToTalkDesktop = !AudioScriptingInterface.pushToTalkDesktop;
}
}
}
}
ColumnLayout {
id: inputDeviceContainer
Layout.preferredWidth: parent.width
spacing: 0
HifiStylesUit.GraphikRegular {
id: inputDeviceTitle
text: "Which input device?"
Layout.maximumWidth: parent.width
height: paintedHeight
size: 22
color: simplifiedUI.colors.text.white
}
ButtonGroup { id: inputDeviceButtonGroup }
ListView {
id: inputDeviceListView
anchors.left: parent.left
anchors.right: parent.right
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
interactive: false
height: contentItem.height
spacing: 4
clip: true
model: AudioScriptingInterface.devices.input
delegate: Item {
width: parent.width
height: inputDeviceCheckbox.height
SimplifiedControls.RadioButton {
id: inputDeviceCheckbox
anchors.left: parent.left
width: parent.width - inputLevel.width
checked: selectedDesktop
text: model.devicename
ButtonGroup.group: inputDeviceButtonGroup
onClicked: {
AudioScriptingInterface.setStereoInput(false); // the next selected audio device might not support stereo
AudioScriptingInterface.setInputDevice(model.info, false); // `false` argument for Desktop mode setting
}
}
SimplifiedControls.InputPeak {
id: inputLevel
showMuted: AudioScriptingInterface.mutedDesktop
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
peak: model.peak
visible: AudioScriptingInterface.devices.input.peakValuesAvailable
}
}
}
SimplifiedControls.Button {
property bool audioLoopedBack: AudioScriptingInterface.getLocalEcho()
function startAudioLoopback() {
if (!audioLoopedBack) {
audioLoopedBack = true;
AudioScriptingInterface.setLocalEcho(true);
}
}
function stopAudioLoopback() {
if (audioLoopedBack) {
audioLoopedBack = false;
AudioScriptingInterface.setLocalEcho(false);
}
}
Timer {
id: loopbackTimer
interval: 8000
running: false
repeat: false
onTriggered: {
stopAudioLoopback();
}
}
id: testYourMicButton
enabled: !HMD.active
anchors.left: parent.left
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
width: 160
height: 32
text: audioLoopedBack ? "STOP TESTING" : "TEST YOUR MIC"
onClicked: {
if (audioLoopedBack) {
loopbackTimer.stop();
stopAudioLoopback();
} else {
loopbackTimer.restart();
startAudioLoopback();
}
}
}
}
ColumnLayout {
id: outputDeviceContainer
Layout.preferredWidth: parent.width
spacing: 0
HifiStylesUit.GraphikRegular {
id: outputDeviceTitle
text: "Which output device?"
Layout.maximumWidth: parent.width
height: paintedHeight
size: 22
color: simplifiedUI.colors.text.white
}
ButtonGroup { id: outputDeviceButtonGroup }
ListView {
id: outputDeviceListView
anchors.left: parent.left
anchors.right: parent.right
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
interactive: false
height: contentItem.height
spacing: 4
clip: true
model: AudioScriptingInterface.devices.output
delegate: Item {
width: parent.width
height: outputDeviceCheckbox.height
SimplifiedControls.RadioButton {
id: outputDeviceCheckbox
anchors.left: parent.left
width: parent.width
checked: selectedDesktop
text: model.devicename
ButtonGroup.group: outputDeviceButtonGroup
onClicked: {
AudioScriptingInterface.setOutputDevice(model.info, false); // `false` argument for Desktop mode setting
}
}
}
}
SimplifiedControls.Button {
property var sound: null
property var sample: null
property bool isPlaying: false
function createSampleSound() {
sound = ApplicationInterface.getSampleSound();
sample = null;
}
function playSound() {
// FIXME: MyAvatar is not properly exposed to QML; MyAvatar.qmlPosition is a stopgap
// FIXME: AudioScriptingInterface.playSystemSound should not require position
if (sample === null && !isPlaying) {
sample = AudioScriptingInterface.playSystemSound(sound, MyAvatar.qmlPosition);
isPlaying = true;
sample.finished.connect(reset);
}
}
function stopSound() {
if (sample && isPlaying) {
sample.stop();
}
}
function reset() {
sample.finished.disconnect(reset);
isPlaying = false;
sample = null;
}
Component.onCompleted: createSampleSound();
Component.onDestruction: stopSound();
onVisibleChanged: {
if (!visible) {
stopSound();
}
}
id: testYourSoundButton
enabled: !HMD.active
anchors.left: parent.left
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
width: 160
height: 32
text: isPlaying ? "STOP TESTING" : "TEST YOUR SOUND"
onClicked: {
isPlaying ? stopSound() : playSound();
}
}
}
}
}

View file

@ -0,0 +1,210 @@
//
// General.qml
//
// Created by Zach Fox on 2019-05-06
// Copyright 2019 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 "../../simplifiedConstants" as SimplifiedConstants
import "../../simplifiedControls" as SimplifiedControls
import stylesUit 1.0 as HifiStylesUit
import QtQuick.Layouts 1.3
Flickable {
property string avatarNametagMode: Settings.getValue("simplifiedNametag/avatarNametagMode", "on")
id: root
contentWidth: parent.width
contentHeight: generalColumnLayout.height
topMargin: 16
bottomMargin: 16
clip: true
onAvatarNametagModeChanged: {
sendNameTagInfo({method: 'handleAvatarNametagMode', avatarNametagMode: root.avatarNametagMode, source: "SettingsApp.qml"});
}
onVisibleChanged: {
if (visible) {
root.contentX = 0;
root.contentY = -root.topMargin;
}
}
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
ColumnLayout {
id: generalColumnLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: simplifiedUI.margins.settings.spacingBetweenSettings
ColumnLayout {
id: avatarNameTagsContainer
Layout.preferredWidth: parent.width
spacing: 0
HifiStylesUit.GraphikRegular {
id: avatarNameTagsTitle
text: "Avatar Name Tags"
Layout.maximumWidth: parent.width
height: paintedHeight
size: 22
color: simplifiedUI.colors.text.white
}
ColumnLayout {
id: avatarNameTagsRadioButtonGroup
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
SimplifiedControls.RadioButton {
id: avatarNameTagsOff
text: "Off"
checked: root.avatarNametagMode === "off"
onClicked: {
root.avatarNametagMode = "off"
}
}
SimplifiedControls.RadioButton {
id: avatarNameTagsAlwaysOn
text: "Always On"
checked: root.avatarNametagMode === "alwaysOn"
onClicked: {
root.avatarNametagMode = "alwaysOn"
}
}
SimplifiedControls.RadioButton {
id: avatarNameTagsClickToView
text: "Click to View"
checked: root.avatarNametagMode === "on"
onClicked: {
root.avatarNametagMode = "on"
}
}
}
}
ColumnLayout {
id: performanceContainer
Layout.preferredWidth: parent.width
spacing: 0
HifiStylesUit.GraphikRegular {
id: performanceTitle
text: "Performance"
Layout.maximumWidth: parent.width
height: paintedHeight
size: 22
color: simplifiedUI.colors.text.white
}
ColumnLayout {
id: performanceRadioButtonGroup
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
SimplifiedControls.RadioButton {
id: performanceLow
text: "Eco"
}
SimplifiedControls.RadioButton {
id: performanceMedium
text: "Interactive"
}
SimplifiedControls.RadioButton {
id: performanceHigh
text: "Realtime"
}
}
}
ColumnLayout {
id: cameraContainer
Layout.preferredWidth: parent.width
spacing: 0
HifiStylesUit.GraphikRegular {
id: cameraTitle
text: "Camera View"
Layout.maximumWidth: parent.width
height: paintedHeight
size: 22
color: simplifiedUI.colors.text.white
}
ColumnLayout {
id: cameraRadioButtonGroup
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
SimplifiedControls.RadioButton {
id: firstPerson
text: "First Person View"
checked: Camera.mode === "first person"
onClicked: {
Camera.mode = "first person"
}
}
SimplifiedControls.RadioButton {
id: thirdPerson
text: "Third Person View"
checked: Camera.mode === "third person"
onClicked: {
Camera.mode = "third person"
}
}
Connections {
target: Camera
onModeUpdated: {
if (Camera.mode === "first person") {
firstPerson.checked = true
} else if (Camera.mode === "third person") {
thirdPerson.checked = true
}
}
}
}
}
HifiStylesUit.GraphikRegular {
id: logoutText
text: (AccountServices.username === "Unknown user" ? "Log In" : "Logout " + AccountServices.username)
wrapMode: Text.Wrap
width: paintedWidth
height: paintedHeight
size: 22
color: simplifiedUI.colors.text.lightBlue
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
parent.color = simplifiedUI.colors.text.lightBlueHover;
}
onExited: {
parent.color = simplifiedUI.colors.text.lightBlue;
}
onClicked: {
if (Account.loggedIn) {
AccountServices.logOut();
} else {
DialogsManager.showLoginDialog();
}
}
}
}
}
signal sendNameTagInfo(var message);
}

View file

@ -0,0 +1,376 @@
//
// VR.qml
//
// Created by Zach Fox on 2019-05-08
// Copyright 2019 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 "../../simplifiedConstants" as SimplifiedConstants
import "../../simplifiedControls" as SimplifiedControls
import stylesUit 1.0 as HifiStylesUit
import QtQuick.Layouts 1.3
Flickable {
id: root
contentWidth: parent.width
contentHeight: vrColumnLayout.height
topMargin: 16
bottomMargin: 16
clip: true
function changePeakValuesEnabled(enabled) {
if (!enabled) {
AudioScriptingInterface.devices.input.peakValuesEnabled = true;
}
}
onVisibleChanged: {
AudioScriptingInterface.devices.input.peakValuesEnabled = visible;
if (visible) {
root.contentX = 0;
root.contentY = -root.topMargin;
AudioScriptingInterface.devices.input.peakValuesEnabledChanged.connect(changePeakValuesEnabled);
} else {
AudioScriptingInterface.devices.input.peakValuesEnabledChanged.disconnect(changePeakValuesEnabled);
}
}
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
ColumnLayout {
id: vrColumnLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: simplifiedUI.margins.settings.spacingBetweenSettings
ColumnLayout {
id: controlsContainer
Layout.preferredWidth: parent.width
spacing: 0
HifiStylesUit.GraphikRegular {
id: controlsTitle
text: "VR Movement Controls"
Layout.maximumWidth: parent.width
height: paintedHeight
size: 22
color: simplifiedUI.colors.text.white
}
ColumnLayout {
id: controlsRadioButtonGroup
width: parent.width
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
ButtonGroup { id: controlsButtonGroup }
SimplifiedControls.RadioButton {
id: controlsDefault
text: "Default"
ButtonGroup.group: controlsButtonGroup
checked: MyAvatar.getControlScheme() === 0
onClicked: {
MyAvatar.setControlScheme(0);
}
}
SimplifiedControls.RadioButton {
id: controlsAnalog
text: "Analog"
ButtonGroup.group: controlsButtonGroup
checked: MyAvatar.getControlScheme() === 1
onClicked: {
MyAvatar.setControlScheme(1);
}
}
Item {
id: controlsAdvancedContainer
Layout.minimumWidth: parent.width
Layout.minimumHeight: 14
SimplifiedControls.RadioButton {
id: controlsAdvanced
text: "Advanced"
ButtonGroup.group: controlsButtonGroup
checked: MyAvatar.getControlScheme() === 2
onClicked: {
MyAvatar.setControlScheme(2);
}
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
height: 14
}
SimplifiedControls.Slider {
id: controlsAdvancedMovementSpeed
anchors.top: parent.top
anchors.topMargin: 4 // For perfect alignment
anchors.bottom: parent.bottom
anchors.right: parent.right
width: 300
height: 14
labelText: "Movement Speed"
labelTextColor: simplifiedUI.colors.text.darkGrey
from: 3
to: 30
defaultValue: 6
value: MyAvatar.analogPlusWalkSpeed
live: true
onValueChanged: {
if (MyAvatar.analogPlusWalkSpeed != controlsAdvancedMovementSpeed.value) {
MyAvatar.analogPlusWalkSpeed = controlsAdvancedMovementSpeed.value;
}
}
}
}
}
}
ColumnLayout {
id: micControlsContainer
Layout.preferredWidth: parent.width
spacing: 0
HifiStylesUit.GraphikRegular {
id: micControlsTitle
text: "Default Mute Controls"
Layout.maximumWidth: parent.width
height: paintedHeight
size: 22
color: simplifiedUI.colors.text.white
}
ColumnLayout {
id: micControlsSwitchGroup
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
SimplifiedControls.Switch {
id: muteMicrophoneSwitch
width: parent.width
height: 18
labelTextOn: "Mute Microphone"
checked: AudioScriptingInterface.mutedHMD
onClicked: {
AudioScriptingInterface.mutedHMD = !AudioScriptingInterface.mutedHMD;
}
}
SimplifiedControls.Switch {
id: pushToTalkSwitch
width: parent.width
height: 18
labelTextOn: "Push to Talk - Press and Hold Grip Triggers to Talk"
checked: AudioScriptingInterface.pushToTalkHMD
onClicked: {
AudioScriptingInterface.pushToTalkHMD = !AudioScriptingInterface.pushToTalkHMD;
}
}
}
}
ColumnLayout {
id: inputDeviceContainer
Layout.preferredWidth: parent.width
spacing: 0
HifiStylesUit.GraphikRegular {
id: inputDeviceTitle
text: "Which input device?"
Layout.maximumWidth: parent.width
height: paintedHeight
size: 22
color: simplifiedUI.colors.text.white
}
ButtonGroup { id: inputDeviceButtonGroup }
ListView {
id: inputDeviceListView
anchors.left: parent.left
anchors.right: parent.right
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
interactive: false
height: contentItem.height
spacing: 4
clip: true
model: AudioScriptingInterface.devices.input
delegate: Item {
width: parent.width
height: inputDeviceCheckbox.height
SimplifiedControls.RadioButton {
id: inputDeviceCheckbox
anchors.left: parent.left
width: parent.width - inputLevel.width
checked: selectedHMD
text: model.devicename
ButtonGroup.group: inputDeviceButtonGroup
onClicked: {
AudioScriptingInterface.setStereoInput(false); // the next selected audio device might not support stereo
AudioScriptingInterface.setInputDevice(model.info, true); // `true` argument for HMD mode setting
}
}
SimplifiedControls.InputPeak {
id: inputLevel
showMuted: AudioScriptingInterface.mutedHMD
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
peak: model.peak
visible: AudioScriptingInterface.devices.input.peakValuesAvailable
}
}
}
SimplifiedControls.Button {
property bool audioLoopedBack: AudioScriptingInterface.getLocalEcho()
function startAudioLoopback() {
if (!audioLoopedBack) {
audioLoopedBack = true;
AudioScriptingInterface.setLocalEcho(true);
}
}
function stopAudioLoopback() {
if (audioLoopedBack) {
audioLoopedBack = false;
AudioScriptingInterface.setLocalEcho(false);
}
}
Timer {
id: loopbackTimer
interval: 8000
running: false
repeat: false
onTriggered: {
stopAudioLoopback();
}
}
id: testYourMicButton
enabled: HMD.active
anchors.left: parent.left
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
width: 160
height: 32
text: audioLoopedBack ? "STOP TESTING" : "TEST YOUR MIC"
onClicked: {
if (audioLoopedBack) {
loopbackTimer.stop();
stopAudioLoopback();
} else {
loopbackTimer.restart();
startAudioLoopback();
}
}
}
}
ColumnLayout {
id: outputDeviceContainer
Layout.preferredWidth: parent.width
spacing: 0
HifiStylesUit.GraphikRegular {
id: outputDeviceTitle
text: "Which output device?"
Layout.maximumWidth: parent.width
height: paintedHeight
size: 22
color: simplifiedUI.colors.text.white
}
ButtonGroup { id: outputDeviceButtonGroup }
ListView {
id: outputDeviceListView
anchors.left: parent.left
anchors.right: parent.right
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
interactive: false
height: contentItem.height
spacing: 4
clip: true
model: AudioScriptingInterface.devices.output
delegate: Item {
width: parent.width
height: outputDeviceCheckbox.height
SimplifiedControls.RadioButton {
id: outputDeviceCheckbox
anchors.left: parent.left
width: parent.width
checked: selectedDesktop
text: model.devicename
ButtonGroup.group: outputDeviceButtonGroup
onClicked: {
AudioScriptingInterface.setOutputDevice(model.info, true); // `false` argument for Desktop mode setting
}
}
}
}
SimplifiedControls.Button {
property var sound: null
property var sample: null
property bool isPlaying: false
function createSampleSound() {
sound = ApplicationInterface.getSampleSound();
sample = null;
}
function playSound() {
// FIXME: MyAvatar is not properly exposed to QML; MyAvatar.qmlPosition is a stopgap
// FIXME: AudioScriptingInterface.playSystemSound should not require position
if (sample === null && !isPlaying) {
sample = AudioScriptingInterface.playSystemSound(sound, MyAvatar.qmlPosition);
isPlaying = true;
sample.finished.connect(reset);
}
}
function stopSound() {
if (sample && isPlaying) {
sample.stop();
}
}
function reset() {
sample.finished.disconnect(reset);
isPlaying = false;
sample = null;
}
Component.onCompleted: createSampleSound();
Component.onDestruction: stopSound();
onVisibleChanged: {
if (!visible) {
stopSound();
}
}
id: testYourSoundButton
enabled: HMD.active
anchors.left: parent.left
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
width: 160
height: 32
text: isPlaying ? "STOP TESTING" : "TEST YOUR SOUND"
onClicked: {
isPlaying ? stopSound() : playSound()
}
}
}
}
}

View file

@ -0,0 +1,223 @@
//
// SimplifiedConstants.qml
//
// Created by Zach Fox on 2019-05-02
// Copyright 2019 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
QtObject {
readonly property QtObject colors: QtObject {
readonly property QtObject text: QtObject {
readonly property color almostWhite: "#FAFAFA"
readonly property color lightGrey: "#CCCCCC"
readonly property color darkGrey: "#8F8F8F"
readonly property color white: "#FFFFFF"
readonly property color lightBlue: "#00B4EF"
readonly property color lightBlueHover: "#3dcfff"
}
readonly property QtObject controls: QtObject {
readonly property QtObject radioButton: QtObject {
readonly property QtObject background: QtObject {
readonly property color startColor: "#828282"
readonly property real startPosition: 0.15
readonly property color endColor: "#6A6A6A"
readonly property real endPosition: 1.0
readonly property real disabledOpacity: 0.5
}
readonly property QtObject hover: QtObject {
readonly property color outerBorderColor: "#00B4EF"
readonly property color innerColor: "#00B4EF"
readonly property color innerBorderColor: "#36CDFF"
readonly property real innerOpacity: 0.5
}
readonly property QtObject active: QtObject {
readonly property color color: "#00B4EF"
}
readonly property QtObject checked: QtObject {
readonly property color innerColor: "#00B4EF"
readonly property color innerBorderColor: "#36CDFF"
}
}
readonly property QtObject slider: QtObject {
readonly property QtObject background: QtObject {
readonly property color empty: "#252525"
readonly property QtObject filled: QtObject {
readonly property color start: "#0093C5"
readonly property color finish: "#00B4EF"
}
}
readonly property QtObject handle: QtObject {
readonly property color disabledBorder: "#2A2A2A"
readonly property color enabledBorder: "#00B4EF"
readonly property QtObject disabled: QtObject {
readonly property color start: "#2A2A2A"
readonly property color finish: "#2A2A2A"
}
readonly property QtObject normal: QtObject {
readonly property color start: "#828282"
readonly property color finish: "#6A6A6A"
}
readonly property QtObject hover: QtObject {
readonly property color start: "#48C7F4"
readonly property color finish: "#48C7F4"
}
readonly property QtObject pressed: QtObject {
readonly property color start: "#48C7F4"
readonly property color finish: "#48C7F4"
readonly property color border: "#00B4EF"
}
}
}
readonly property QtObject simplifiedSwitch: QtObject {
readonly property QtObject background: QtObject {
readonly property color disabled: "#616161"
readonly property color off: "#616161"
readonly property color hover: "#616161"
readonly property color pressed: "#616161"
readonly property color on: "#ffffff"
}
readonly property QtObject handle: QtObject {
readonly property color disabled: "#616161"
readonly property color off: "#E6E6E6"
readonly property color hover: "#48C7F4"
readonly property color active: "#48C7F4"
readonly property color activeBorder: "#00B4EF"
readonly property color on: "#00B4EF"
readonly property color checkedBorder: "#36CDFF"
}
readonly property QtObject text: QtObject {
readonly property color off: "#8F8F8F"
readonly property color on: "#ffffff"
}
}
readonly property QtObject button: QtObject {
readonly property QtObject background: QtObject {
readonly property color disabled: "#191919"
readonly property color enabled: "#191919"
readonly property color hover: "#00B4EF"
readonly property color active: "#00B4EF"
}
readonly property QtObject border: QtObject {
readonly property color disabled: "#8F8F8F"
readonly property color enabled: "#FFFFFF"
readonly property color hover: "#FFFFFF"
readonly property color active: "#FFFFFF"
}
readonly property QtObject text: QtObject {
readonly property color disabled: "#8F8F8F"
readonly property color enabled: "#FFFFFF"
}
}
readonly property QtObject outputVolumeButton: QtObject {
readonly property QtObject text: QtObject {
readonly property color muted: "#b20012"
readonly property color noisy: "#FFFFFF"
}
}
readonly property QtObject inputVolumeButton: QtObject {
readonly property QtObject text: QtObject {
readonly property color muted: "#b20012"
readonly property color noisy: "#FFFFFF"
}
}
readonly property QtObject checkBox: QtObject {
readonly property QtObject background: QtObject {
readonly property color disabled: "#464646"
readonly property color active: "#00B4EF"
readonly property color enabled: "#767676"
}
readonly property QtObject border: QtObject {
readonly property color hover: "#00B4EF"
}
readonly property QtObject innerBox: QtObject {
readonly property color border: "#36CDFF"
readonly property color background: "#00B4EF"
}
}
readonly property QtObject textField: QtObject {
readonly property color normal: Qt.rgba(1, 1, 1, 0.3)
readonly property color hover: "#FFFFFF"
readonly property color focus: "#FFFFFF"
}
}
readonly property color darkSeparator: "#595959"
readonly property color darkBackground: "#1A1A1A"
readonly property color darkBackgroundHighlight: "#575757"
readonly property color highlightOnDark: Qt.rgba(1, 1, 1, 0.2)
readonly property color white: "#FFFFFF"
}
readonly property QtObject glyphs: QtObject {
readonly property string gear: "@"
readonly property string editPencil: "\ue00d"
readonly property string playback_play: "\ue01d"
readonly property string stop_square: "\ue01e"
readonly property string hmd: "b"
readonly property string screen: "c"
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 muted: "H"
readonly property string pencil: "\ue00d"
}
readonly property QtObject margins: QtObject {
readonly property QtObject controls: QtObject {
readonly property QtObject radioButton: QtObject {
readonly property int labelLeftMargin: 6
}
}
readonly property QtObject settings: QtObject {
property real subtitleTopMargin: 2
property real settingsGroupTopMargin: 10
property real spacingBetweenSettings: 48
}
}
readonly property QtObject sizes: QtObject {
readonly property QtObject controls: QtObject {
readonly property QtObject slider: QtObject {
readonly property int height: 16
readonly property int labelTextSize: 14
readonly property int backgroundHeight: 8
}
readonly property QtObject radioButton: QtObject {
readonly property int outerBorderWidth: 1
readonly property int innerBorderWidth: 1
}
readonly property QtObject simplifiedSwitch: QtObject {
readonly property int switchBackgroundHeight: 8
readonly property int switchBackgroundWidth: 30
readonly property int switchHandleInnerWidth: 12
readonly property int switchHandleOuterWidth: 16
readonly property int switchHandleBorderSize: 1
readonly property int labelTextSize: 14
readonly property int labelGlyphSize: 32
}
readonly property QtObject button: QtObject {
readonly property int borderWidth: 1
readonly property int textPadding: 16
readonly property int width: 160
readonly property int height: 32
readonly property int textSize: 14
}
readonly property QtObject textField: QtObject {
readonly property int editPencilPadding: 6
}
}
}
}

View file

@ -0,0 +1,106 @@
//
// Button.qml
//
// Created by Zach Fox on 2019-05-08
// Copyright 2019 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 as Original
import stylesUit 1.0 as HifiStylesUit
import "../simplifiedConstants" as SimplifiedConstants
import TabletScriptingInterface 1.0
Original.Button {
id: root
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
hoverEnabled: true
width: simplifiedUI.sizes.controls.button.width
height: simplifiedUI.sizes.controls.button.height
onHoveredChanged: {
if (hovered && enabled) {
Tablet.playSound(TabletEnums.ButtonHover);
}
}
onFocusChanged: {
if (focus && enabled) {
Tablet.playSound(TabletEnums.ButtonHover);
}
}
onClicked: {
if (enabled) {
Tablet.playSound(TabletEnums.ButtonClick);
}
}
background: Rectangle {
implicitWidth: root.width
implicitHeight: root.height
color: {
if (root.enabled) {
if (root.hovered) {
simplifiedUI.colors.controls.button.background.enabled
} else if (root.down) {
simplifiedUI.colors.controls.button.background.active
} else {
simplifiedUI.colors.controls.button.background.enabled
}
} else {
simplifiedUI.colors.controls.button.background.disabled
}
}
border.width: simplifiedUI.sizes.controls.button.borderWidth
border.color: root.enabled ? simplifiedUI.colors.controls.button.border.enabled : simplifiedUI.colors.controls.button.border.disabled
Item {
clip: true
visible: root.enabled
anchors.centerIn: parent
width: parent.width - parent.border.width * 2
height: parent.height - parent.border.width * 2
Rectangle {
z: -1
clip: true
width: root.down ? parent.width * 1.5 : (root.hovered ? parent.width * 9 / 10 : 0)
height: parent.height
anchors.horizontalCenter: parent.horizontalCenter
anchors.horizontalCenterOffset: -14
color: simplifiedUI.colors.controls.button.background.active
Behavior on width {
enabled: true
SmoothedAnimation { velocity: 400 }
}
transform: Matrix4x4 {
property real a: Math.PI / 4
matrix: Qt.matrix4x4(1, Math.tan(a), 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1)
}
}
}
}
contentItem: HifiStylesUit.GraphikMedium {
id: buttonText
topPadding: -2 // Necessary for proper alignment using Graphik Medium
wrapMode: Text.Wrap
color: enabled ? simplifiedUI.colors.controls.button.text.enabled : simplifiedUI.colors.controls.button.text.disabled
size: simplifiedUI.sizes.controls.button.textSize
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: root.text
}
}

View file

@ -0,0 +1,89 @@
//
// CheckBox.qml
//
// Created by Zach Fox on 2019-05-14
// Copyright 2019 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 as Original
import stylesUit 1.0 as HifiStylesUit
import "../simplifiedConstants" as SimplifiedConstants
import TabletScriptingInterface 1.0
Original.CheckBox {
id: root
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
property int colorScheme: hifi.colorSchemes.light
property string color: hifi.colors.lightGrayText
property alias checkBoxSize: checkBoxIndicator.width
property alias checkBoxRadius: checkBoxIndicator.radius
property alias checkSize: innerBox.width
property alias checkRadius: innerBox.radius
property bool wrapLabel: true
property alias labelFontFamily: checkBoxLabel.font.family
property alias labelFontSize: checkBoxLabel.font.pixelSize
property alias labelFontWeight: checkBoxLabel.font.weight
focusPolicy: Qt.ClickFocus
hoverEnabled: true
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
}
onHoveredChanged: {
if (hovered) {
Tablet.playSound(TabletEnums.ButtonHover);
}
}
indicator: Rectangle {
id: checkBoxIndicator
width: 14
height: width
radius: 4
y: parent.height / 2 - height / 2
color: root.enabled ?
(root.pressed ? simplifiedUI.colors.controls.checkBox.background.active : simplifiedUI.colors.controls.checkBox.background.enabled) :
simplifiedUI.colors.controls.checkBox.background.disabled
border.width: root.hovered ? 2 : 0
border.color: simplifiedUI.colors.controls.checkBox.border.hover
Rectangle {
id: innerBox
visible: root.hovered || root.checked
opacity: root.hovered ? 0.3 : 1.0
anchors.centerIn: parent
width: checkBoxIndicator.width - 4
height: width
radius: 2
color: simplifiedUI.colors.controls.checkBox.innerBox.background
border.width: 1
border.color: simplifiedUI.colors.controls.checkBox.innerBox.border
}
}
contentItem: Text {
id: checkBoxLabel
text: root.text
color: root.color
font.family: "Graphik"
font.pixelSize: 14
font.weight: Font.DemiBold
x: 2
verticalAlignment: Text.AlignVCenter
wrapMode: root.wrapLabel ? Text.Wrap : Text.NoWrap
elide: root.wrapLabel ? Text.ElideNone : Text.ElideRight
leftPadding: root.indicator.width + root.spacing
}
}

View file

@ -0,0 +1,101 @@
//
// InputPeak.qml
//
// Created by Zach Pomerantz on 6/20/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.10
import QtGraphicalEffects 1.0
Item {
property var peak
property alias showMuted: status.visible
width: 70
height: 8
QtObject {
id: colors
readonly property string unmuted: "#FFF"
readonly property string muted: "#E2334D"
readonly property string gutter: "#575757"
readonly property string greenStart: "#39A38F"
readonly property string greenEnd: "#1FC6A6"
readonly property string yellow: "#C0C000"
readonly property string red: colors.muted
readonly property string fill: "#55000000"
}
Text {
id: status
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
visible: false
color: colors.muted
text: "MUTED"
font.pointSize: 10
}
Item {
id: bar
width: parent.width
height: parent.height
anchors { fill: parent }
visible: !status.visible
Rectangle { // base
radius: 4
anchors { fill: parent }
color: colors.gutter
}
Rectangle { // mask
id: mask
width: parent.width * peak
radius: 5
anchors {
bottom: parent.bottom
bottomMargin: 0
top: parent.top
topMargin: 0
left: parent.left
leftMargin: 0
}
}
LinearGradient {
anchors { fill: mask }
source: mask
start: Qt.point(0, 0)
end: Qt.point(bar.width, 0)
gradient: Gradient {
GradientStop {
position: 0
color: colors.greenStart
}
GradientStop {
position: 0.5
color: colors.greenEnd
}
GradientStop {
position: 1
color: colors.yellow
}
}
}
}
}

View file

@ -0,0 +1,103 @@
//
// RadioButton.qml
//
// Created by Zach Fox on 2019-05-06
// Copyright 2019 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 TabletScriptingInterface 1.0
import "../simplifiedConstants" as SimplifiedConstants
RadioButton {
id: root
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
padding: 0
property alias labelTextColor: radioButtonLabel.color
property alias labelFontSize: radioButtonLabel.font.pixelSize
property int radioButtonRadius: 14
property int labelLeftMargin: simplifiedUI.margins.controls.radioButton.labelLeftMargin
property bool wrapLabel: true
readonly property int checkSize: Math.max(root.radioButtonRadius - 8, 10)
focusPolicy: Qt.ClickFocus
hoverEnabled: true
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
}
onHoveredChanged: {
if (hovered) {
Tablet.playSound(TabletEnums.ButtonHover);
}
}
indicator: Rectangle {
id: radioButtonIndicator
implicitWidth: root.radioButtonRadius
implicitHeight: root.radioButtonRadius
radius: root.radioButtonRadius
y: parent.height / 2 - height / 2
border.width: root.hovered ? simplifiedUI.sizes.controls.radioButton.outerBorderWidth : 0
border.color: simplifiedUI.colors.controls.radioButton.hover.outerBorderColor
opacity: root.disabled ? 0.5 : 1.0
gradient: Gradient {
GradientStop {
position: simplifiedUI.colors.controls.radioButton.background.startPosition
color: simplifiedUI.colors.controls.radioButton.background.startColor
}
GradientStop {
position: simplifiedUI.colors.controls.radioButton.background.endPosition
color: simplifiedUI.colors.controls.radioButton.background.endColor
}
}
Rectangle {
id: innerBox
visible: root.checked || root.hovered
anchors.centerIn: parent
width: root.checkSize
height: width
radius: checkSize / 2
border.width: simplifiedUI.sizes.controls.radioButton.innerBorderWidth
border.color: root.hovered ? simplifiedUI.colors.controls.radioButton.hover.innerBorderColor : simplifiedUI.colors.controls.radioButton.checked.innerBorderColor
color: root.hovered ? simplifiedUI.colors.controls.radioButton.hover.innerColor : simplifiedUI.colors.controls.radioButton.hover.innerColor
opacity: root.hovered ? simplifiedUI.colors.controls.radioButton.hover.innerOpacity : 1.0
}
Rectangle {
id: pressedBox
visible: root.pressed
width: parent.width
height: parent.height
radius: parent.radius
anchors.centerIn: parent
color: simplifiedUI.colors.controls.radioButton.active.color
}
}
contentItem: Text {
id: radioButtonLabel
height: root.radioButtonRadius
font.pixelSize: 14
font.family: "Graphik"
font.weight: Font.Normal
text: root.text
color: simplifiedUI.colors.text.white
x: 2
wrapMode: root.wrapLabel ? Text.Wrap : Text.NoWrap
elide: root.wrapLabel ? Text.ElideNone : Text.ElideRight
enabled: root.enabled
verticalAlignment: Text.AlignVCenter
leftPadding: radioButtonIndicator.width + root.labelLeftMargin
}
}

View file

@ -0,0 +1,126 @@
//
// Slider.qml
//
// Created by Zach Fox on 2019-05-06
// Copyright 2019 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 stylesUit 1.0 as HifiStylesUit
import TabletScriptingInterface 1.0
import "../simplifiedConstants" as SimplifiedConstants
Item {
id: root
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
property alias labelText: sliderText.text
property alias labelTextSize: sliderText.size
property alias labelTextColor: sliderText.color
property alias value: sliderControl.value
property alias from: sliderControl.from
property alias to: sliderControl.to
property alias live: sliderControl.live
property alias stepSize: sliderControl.stepSize
property alias snapMode: sliderControl.snapMode
property real defaultValue: 0.0
HifiStylesUit.GraphikRegular {
id: sliderText
text: ""
anchors.right: sliderControl.left
anchors.rightMargin: 8
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: -4 // Necessary for vertical alignment
anchors.bottom: parent.bottom
horizontalAlignment: Text.AlignRight
visible: sliderText.text != ""
color: simplifiedUI.colors.text.white
size: simplifiedUI.sizes.controls.slider.labelTextSize
}
Slider {
id: sliderControl
height: simplifiedUI.sizes.controls.slider.height
width: root.width * 0.6
enabled: root.enabled
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
onPressedChanged: {
if (pressed) {
Tablet.playSound(TabletEnums.ButtonClick);
}
}
onHoveredChanged: {
if (hovered) {
Tablet.playSound(TabletEnums.ButtonHover);
}
}
background: Rectangle {
id: sliderBackground
width: sliderControl.width - sliderHandle.width
height: simplifiedUI.sizes.controls.slider.backgroundHeight
x: sliderHandle.width / 2
y: sliderControl.height / 2 - height / 2
radius: height / 2
color: simplifiedUI.colors.controls.slider.background.empty
Rectangle {
width: sliderControl.visualPosition * sliderBackground.width
height: parent.height
radius: height / 2
gradient: Gradient {
GradientStop { position: 0.0; color: simplifiedUI.colors.controls.slider.background.filled.start }
GradientStop { position: 1.0; color: simplifiedUI.colors.controls.slider.background.filled.finish }
}
}
}
handle: Rectangle {
id: sliderHandle
width: sliderControl.height
height: width
x: sliderControl.visualPosition * sliderBackground.width
y: sliderControl.height / 2 - height / 2
radius: height / 2
color: "#000000"
border.width: 1
border.color: sliderControl.hovered || sliderControl.pressed ? simplifiedUI.colors.controls.slider.handle.enabledBorder : simplifiedUI.colors.controls.slider.handle.disabledBorder
Rectangle {
visible: root.enabled
height: sliderControl.pressed ? parent.height : parent.height - 4
width: height
radius: height / 2
anchors.centerIn: parent
gradient: Gradient {
GradientStop {
position: 0.2
color: sliderControl.enabled ? (sliderControl.hovered ? simplifiedUI.colors.controls.slider.handle.hover.start :
(sliderControl.pressed
? (simplifiedUI.colors.controls.slider.handle.pressed.start)
: (simplifiedUI.colors.controls.slider.handle.normal.start))) : simplifiedUI.colors.controls.slider.handle.disabled.start
}
GradientStop {
position: 1.0
color: sliderControl.enabled ? (sliderControl.hovered ? simplifiedUI.colors.controls.slider.handle.hover.finish :
(sliderControl.pressed
? (simplifiedUI.colors.controls.slider.handle.pressed.finish)
: (simplifiedUI.colors.controls.slider.handle.normal.finish))) : simplifiedUI.colors.controls.slider.handle.disabled.finish
}
}
}
}
}
}

View file

@ -0,0 +1,234 @@
//
// Switch.qml
//
// Created by Zach Fox on 2019-05-08
// Copyright 2019 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 as Original
import stylesUit 1.0 as HifiStylesUit
import TabletScriptingInterface 1.0
import "../simplifiedConstants" as SimplifiedConstants
Item {
id: root
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
property alias switchWidth: switchBackground.width
property alias labelTextOff: labelOff.text
property alias labelTextOffSize: labelOff.size
property alias labelGlyphOffText: labelGlyphOff.text
property alias labelGlyphOffSize: labelGlyphOff.size
property alias labelTextOn: labelOn.text
property alias labelTextOnSize: labelOn.size
property alias labelGlyphOnText: labelGlyphOn.text
property alias labelGlyphOnSize: labelGlyphOn.size
property alias checked: originalSwitch.checked
property string backgroundOnColor: "#252525"
signal clicked
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
}
Original.Switch {
id: originalSwitch
enabled: root.enabled
focusPolicy: Qt.ClickFocus
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: labelOff.text === "" ? undefined : parent.horizontalCenter
anchors.left: labelOff.text === "" ? parent.left : undefined
anchors.leftMargin: (outerSwitchHandle.width - innerSwitchHandle.width) / 2
width: simplifiedUI.sizes.controls.simplifiedSwitch.switchBackgroundWidth
hoverEnabled: true
function changeColor() {
if (!originalSwitch.enabled) {
innerSwitchHandle.color = simplifiedUI.colors.controls.simplifiedSwitch.handle.disabled;
return;
}
if (originalSwitch.checked) {
if (originalSwitch.hovered) {
innerSwitchHandle.color = simplifiedUI.colors.controls.simplifiedSwitch.handle.hover;
} else {
innerSwitchHandle.color = simplifiedUI.colors.controls.simplifiedSwitch.handle.on;
}
} else {
if (originalSwitch.hovered) {
innerSwitchHandle.color = simplifiedUI.colors.controls.simplifiedSwitch.handle.hover;
} else {
innerSwitchHandle.color = simplifiedUI.colors.controls.simplifiedSwitch.handle.off;
}
}
}
onCheckedChanged: {
root.checkedChanged();
Tablet.playSound(TabletEnums.ButtonClick);
originalSwitch.changeColor();
}
onClicked: {
root.clicked();
originalSwitch.changeColor();
}
onHoveredChanged: {
if (hovered) {
Tablet.playSound(TabletEnums.ButtonHover);
}
originalSwitch.changeColor();
}
background: Rectangle {
id: switchBackground
anchors.verticalCenter: parent.verticalCenter
color: originalSwitch.checked ? simplifiedUI.colors.controls.simplifiedSwitch.background.on : simplifiedUI.colors.controls.simplifiedSwitch.background.off
width: originalSwitch.width
height: simplifiedUI.sizes.controls.simplifiedSwitch.switchBackgroundHeight
radius: height/2
}
indicator: Item {
anchors.verticalCenter: parent.verticalCenter
width: simplifiedUI.sizes.controls.simplifiedSwitch.switchHandleOuterWidth
height: width
x: originalSwitch.visualPosition * switchBackground.width - (innerSwitchHandle.width * (originalSwitch.checked ? 1 : 0)) - ((outerSwitchHandle.width - innerSwitchHandle.width) / 2)
Behavior on x {
enabled: !originalSwitch.down
SmoothedAnimation { velocity: 200 }
}
Rectangle {
id: outerSwitchHandle
visible: originalSwitch.hovered
anchors.centerIn: parent
width: simplifiedUI.sizes.controls.simplifiedSwitch.switchHandleOuterWidth
height: width
radius: width/2
color: "transparent"
border.width: simplifiedUI.sizes.controls.simplifiedSwitch.switchHandleBorderSize
border.color: simplifiedUI.colors.controls.simplifiedSwitch.handle.activeBorder
}
Rectangle {
id: innerSwitchHandle
anchors.centerIn: parent
width: simplifiedUI.sizes.controls.simplifiedSwitch.switchHandleInnerWidth
height: width
radius: width/2
color: simplifiedUI.colors.controls.simplifiedSwitch.handle.off
border.width: originalSwitch.pressed || originalSwitch.checked ? simplifiedUI.sizes.controls.simplifiedSwitch.switchHandleBorderSize : 0
border.color: originalSwitch.pressed ? simplifiedUI.colors.controls.simplifiedSwitch.handle.activeBorder : simplifiedUI.colors.controls.simplifiedSwitch.handle.checkedBorder
Component.onCompleted: {
originalSwitch.changeColor();
}
}
}
}
// OFF Label
Item {
anchors.right: originalSwitch.left
anchors.rightMargin: 10
anchors.top: parent.top
anchors.bottom: parent.bottom
HifiStylesUit.GraphikRegular {
id: labelOff
text: ""
size: simplifiedUI.sizes.controls.simplifiedSwitch.labelTextSize
color: originalSwitch.checked ? simplifiedUI.colors.controls.simplifiedSwitch.text.off : simplifiedUI.colors.controls.simplifiedSwitch.text.on
anchors.top: parent.top
anchors.topMargin: -2 // Necessary for text alignment
anchors.bottom: parent.bottom
anchors.right: parent.right
width: paintedWidth
verticalAlignment: Text.AlignVCenter
}
HifiStylesUit.HiFiGlyphs {
id: labelGlyphOff
text: ""
size: simplifiedUI.sizes.controls.simplifiedSwitch.labelGlyphSize
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: {
if (labelOn.text === "" && labelGlyphOn.text === "") {
originalSwitch.checked = !originalSwitch.checked;
} else {
originalSwitch.checked = false;
}
root.clicked();
}
}
}
// ON Label
Item {
anchors.left: originalSwitch.right
anchors.leftMargin: 10
anchors.top: parent.top
anchors.bottom: parent.bottom
HifiStylesUit.GraphikRegular {
id: labelOn
text: ""
size: simplifiedUI.sizes.controls.simplifiedSwitch.labelTextSize
color: originalSwitch.checked ? simplifiedUI.colors.controls.simplifiedSwitch.text.on : simplifiedUI.colors.controls.simplifiedSwitch.text.off
anchors.top: parent.top
anchors.topMargin: -2 // Necessary for text alignment
anchors.left: parent.left
width: paintedWidth
height: parent.height
verticalAlignment: Text.AlignVCenter
}
HifiStylesUit.HiFiGlyphs {
id: labelGlyphOn
text: ""
size: simplifiedUI.sizes.controls.simplifiedSwitch.labelGlyphSize
color: labelOn.color
anchors.top: parent.top
anchors.topMargin: 2
anchors.left: labelOn.right
anchors.leftMargin: 4
}
MouseArea {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: labelOn.left
anchors.right: labelGlyphOn.right
onClicked: {
if (labelOff.text === "" && labelGlyphOff.text === "") {
originalSwitch.checked = !originalSwitch.checked;
} else {
originalSwitch.checked = true;
}
root.clicked();
}
}
}
}

View file

@ -0,0 +1,75 @@
//
// TextField.qml
//
// Created by Zach Fox on 2019-05-06
// Copyright 2019 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 TabletScriptingInterface 1.0
import "../simplifiedConstants" as SimplifiedConstants
import stylesUit 1.0 as HifiStylesUit
TextField {
id: root
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
color: simplifiedUI.colors.text.white
font.family: "Graphik Medium"
font.pixelSize: 22
selectionColor: simplifiedUI.colors.text.white
selectedTextColor: simplifiedUI.colors.text.darkGrey
verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignLeft
autoScroll: false
hoverEnabled: true
leftPadding: 0
rightPadding: editPencil.implicitWidth + simplifiedUI.sizes.controls.textField.editPencilPadding
onFocusChanged: {
if (focus) {
Tablet.playSound(TabletEnums.ButtonClick);
}
}
onHoveredChanged: {
if (hovered) {
Tablet.playSound(TabletEnums.ButtonHover);
}
}
background: Item {
anchors.fill: parent
Rectangle {
id: bottomRectangle
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: root.focus ? simplifiedUI.colors.controls.textField.focus :
(root.hovered ? simplifiedUI.colors.controls.textField.hover : simplifiedUI.colors.controls.textField.normal)
}
HifiStylesUit.HiFiGlyphs {
id: editPencil
text: simplifiedUI.glyphs.pencil
// Text Size
size: root.font.pixelSize * 1.5
// Anchors
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
// Style
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: bottomRectangle.color
}
}
}

View file

@ -0,0 +1,352 @@
//
// SimplifiedTopBar.qml
//
// Created by Zach Fox on 2019-05-02
// Copyright 2019 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 "../simplifiedConstants" as SimplifiedConstants
import "../inputDeviceButton" as InputDeviceButton
import stylesUit 1.0 as HifiStylesUit
import TabletScriptingInterface 1.0
import QtGraphicalEffects 1.0
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
Rectangle {
id: root
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
color: simplifiedUI.colors.darkBackground
anchors.fill: parent
property bool inventoryFullyReceived: false
Component.onCompleted: {
Commerce.getLoginStatus();
}
Connections {
target: MyAvatar
onSkeletonModelURLChanged: {
root.updatePreviewUrl();
}
}
Connections {
target: Commerce
onLoginStatusResult: {
if (inventoryFullyReceived) {
return;
}
if (isLoggedIn) {
Commerce.getWalletStatus();
} else {
// Show some error to the user
}
}
onWalletStatusResult: {
if (inventoryFullyReceived) {
return;
}
if (walletStatus === 5) {
topBarInventoryModel.getFirstPage();
} else {
// Show some error to the user
}
}
onInventoryResult: {
if (inventoryFullyReceived) {
return;
}
topBarInventoryModel.handlePage(result.status !== "success" && result.message, result);
root.updatePreviewUrl();
// I _should_ be able to do `if (currentPageToRetrieve > -1)` here, but the
// inventory endpoint doesn't return `response.total_pages`, so the PSFListModel doesn't
// know when to automatically stop fetching new pages.
// This will result in fetching one extra page than is necessary, but that's not a big deal.
if (result.data.assets.length > 0) {
topBarInventoryModel.getNextPage();
} else {
inventoryFullyReceived = true;
}
}
}
HifiModels.PSFListModel {
id: topBarInventoryModel
itemsPerPage: 8
listModelName: 'inventory'
getPage: function () {
var editionFilter = "";
var primaryFilter = "avatar";
var titleFilter = "";
Commerce.inventory(
editionFilter,
primaryFilter,
titleFilter,
topBarInventoryModel.currentPageToRetrieve,
topBarInventoryModel.itemsPerPage
);
}
processPage: function(data) {
data.assets.forEach(function (item) {
if (item.status.length > 1) { console.warn("Unrecognized inventory status", item); }
item.status = item.status[0];
});
return data.assets;
}
}
Item {
id: avatarButtonContainer
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 16
width: 48
height: width
AnimatedImage {
visible: avatarButtonImage.source === ""
anchors.centerIn: parent
width: parent.width - 10
height: width
source: "../images/loading.gif"
}
Image {
id: avatarButtonImage
visible: source !== ""
source: ""
anchors.centerIn: parent
width: parent.width - 10
height: width
mipmap: true
fillMode: Image.PreserveAspectCrop
layer.enabled: true
layer.effect: OpacityMask {
maskSource: mask
}
MouseArea {
id: avatarButtonImageMouseArea
anchors.fill: parent
hoverEnabled: enabled
onEntered: {
Tablet.playSound(TabletEnums.ButtonHover);
}
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
if (Account.loggedIn) {
sendToScript({
"source": "SimplifiedTopBar.qml",
"method": "toggleAvatarApp"
});
} else {
DialogsManager.showLoginDialog();
}
}
}
}
Rectangle {
z: -1
id: borderMask
width: avatarButtonImageMouseArea.containsMouse ? avatarButtonImage.width + 4 : avatarButtonImage.width - 4
height: width
radius: width
anchors.centerIn: avatarButtonImage
color: "#FFFFFF"
Behavior on width {
enabled: true
SmoothedAnimation { velocity: 80 }
}
}
Rectangle {
id: mask
anchors.fill: avatarButtonImage
radius: avatarButtonImage.width
visible: false
}
}
InputDeviceButton.InputDeviceButton {
id: inputDeviceButton
anchors.verticalCenter: parent.verticalCenter
anchors.left: avatarButtonContainer.right
anchors.leftMargin: 8
}
Item {
id: outputDeviceButtonContainer
anchors.verticalCenter: parent.verticalCenter
anchors.left: inputDeviceButton.right
anchors.leftMargin: 24
width: 20
height: width
HifiStylesUit.HiFiGlyphs {
property bool outputMuted: false
id: outputDeviceButton
text: (outputDeviceButton.outputMuted ? simplifiedUI.glyphs.vol_0 : simplifiedUI.glyphs.vol_3)
color: (outputDeviceButton.outputMuted ? simplifiedUI.colors.controls.outputVolumeButton.text.muted : simplifiedUI.colors.controls.outputVolumeButton.text.noisy)
opacity: outputDeviceButtonMouseArea.containsMouse ? 1.0 : 0.7
size: 32
anchors.centerIn: parent
width: parent.width
height: parent.height
horizontalAlignment: Text.AlignHCenter
MouseArea {
id: outputDeviceButtonMouseArea
anchors.fill: parent
hoverEnabled: true
onEntered: {
Tablet.playSound(TabletEnums.ButtonHover);
}
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
outputDeviceButton.outputMuted = !outputDeviceButton.outputMuted;
sendToScript({
"source": "SimplifiedTopBar.qml",
"method": "setOutputMuted",
"data": {
"outputMuted": outputDeviceButton.outputMuted
}
});
}
}
}
}
Item {
id: hmdButtonContainer
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: settingsButtonContainer.left
anchors.rightMargin: 8
width: height
HifiStylesUit.HiFiGlyphs {
id: hmdGlyph
text: HMD.active ? simplifiedUI.glyphs.hmd : simplifiedUI.glyphs.screen
color: simplifiedUI.colors.text.white
opacity: hmdGlyphMouseArea.containsMouse ? 1.0 : 0.7
size: 40
anchors.centerIn: parent
width: 35
height: parent.height
horizontalAlignment: Text.AlignHCenter
MouseArea {
id: hmdGlyphMouseArea
anchors.fill: parent
hoverEnabled: true
onEntered: {
Tablet.playSound(TabletEnums.ButtonHover);
}
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
// TODO: actually do this right and change the display plugin
HMD.active = !HMD.active;
}
}
}
}
Item {
id: settingsButtonContainer
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.rightMargin: 16
width: height
HifiStylesUit.HiFiGlyphs {
id: settingsGlyph
text: simplifiedUI.glyphs.gear
color: simplifiedUI.colors.text.white
opacity: settingsGlyphMouseArea.containsMouse ? 1.0 : 0.7
size: 40
anchors.centerIn: parent
width: 35
height: parent.height
horizontalAlignment: Text.AlignHCenter
MouseArea {
id: settingsGlyphMouseArea
anchors.fill: parent
hoverEnabled: true
onEntered: {
Tablet.playSound(TabletEnums.ButtonHover);
}
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
sendToScript({
"source": "SimplifiedTopBar.qml",
"method": "toggleSettingsApp"
});
}
}
}
}
function updatePreviewUrl() {
var previewUrl = "";
var downloadUrl = "";
for (var i = 0; i < topBarInventoryModel.count; ++i) {
downloadUrl = topBarInventoryModel.get(i).download_url;
previewUrl = topBarInventoryModel.get(i).preview;
if (MyAvatar.skeletonModelURL === downloadUrl) {
avatarButtonImage.source = previewUrl;
return;
}
}
}
function fromScript(message) {
if (message.source !== "simplifiedUI.js") {
return;
}
switch (message.method) {
case "updateAvatarThumbnailURL":
avatarButtonImage.source = message.data.avatarThumbnailURL;
break;
case "updateOutputMuted":
outputDeviceButton.outputMuted = message.data.outputMuted;
break;
default:
console.log('SimplifiedTopBar.qml: Unrecognized message from JS');
break;
}
}
signal sendToScript(var message);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View file

@ -85,23 +85,11 @@ TabBar {
NewEntityButton {
icon: "icons/create-icons/21-cube-01.svg"
text: "CUBE"
text: "SHAPE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newCubeButton" }
});
editTabView.currentIndex = 2
}
}
NewEntityButton {
icon: "icons/create-icons/22-sphere-01.svg"
text: "SPHERE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newSphereButton" }
params: { buttonName: "newShapeButton" }
});
editTabView.currentIndex = 2
}

View file

@ -91,23 +91,11 @@ TabBar {
NewEntityButton {
icon: "icons/create-icons/21-cube-01.svg"
text: "CUBE"
text: "SHAPE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newCubeButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/22-sphere-01.svg"
text: "SPHERE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newSphereButton" }
params: { buttonName: "newShapeButton" }
});
editTabView.currentIndex = tabIndex.properties
}

View file

@ -0,0 +1,21 @@
//
// GraphikMedium.qml
//
// Created by Wayne Chen on 3 May 2019
// Copyright 2019 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: "Graphik"
font.weight: Font.Medium
}

View file

@ -0,0 +1,20 @@
//
// GraphikRegular.qml
//
// Created by Wayne Chen on 2 May 2019
// Copyright 2019 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: "Graphik"
}

View file

@ -0,0 +1,21 @@
//
// GraphikSemiBold.qml
//
// Created by Wayne Chen on 2 May 2019
// Copyright 2019 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: "Graphik"
font.weight: Font.DemiBold
}

View file

@ -5,12 +5,16 @@ FiraSansRegular 1.0 FiraSansRegular.qml
FiraSansSemiBold 1.0 FiraSansSemiBold.qml
HifiConstants 1.0 HifiConstants.qml
HiFiGlyphs 1.0 HiFiGlyphs.qml
GraphikMedium 1.0 GraphikMedium.qml
GraphikRegular 1.0 GraphikRegular.qml
GraphikSemiBold 1.0 GraphikSemiBold.qml
IconButton 1.0 IconButton.qml
InfoItem 1.0 InfoItem.qml
InputLabel 1.0 InputLabel.qml
ListItem 1.0 ListItem.qml
Logs 1.0 Logs.qml
OverlayTitle 1.0 OverlayTitle.qml
Rawline 1.0 Rawline.qml
RalewayBold 1.0 RalewayBold.qml
RalewayLight 1.0 RalewayLight.qml
RalewayRegular 1.0 RalewayRegular.qml
@ -19,4 +23,4 @@ SectionName 1.0 SectionName.qml
Separator 1.0 Separator.qml
ShortcutText 1.0 ShortcutText.qml
TabName 1.0 TabName.qml
TextFieldInput 1.0 TextFieldInput.qml
TextFieldInput 1.0 TextFieldInput.qml

View file

@ -250,17 +250,6 @@
#if defined(Q_OS_WIN)
#include <VersionHelpers.h>
#ifdef DEBUG_EVENT_QUEUE
// This is a HACK that uses private headers included with the qt source distrubution.
// To use this feature you need to add these directores to your include path:
// E:/Qt/5.10.1/Src/qtbase/include/QtCore/5.10.1/QtCore
// E:/Qt/5.10.1/Src/qtbase/include/QtCore/5.10.1
#define QT_BOOTSTRAPPED
#include <private/qthread_p.h>
#include <private/qobject_p.h>
#undef QT_BOOTSTRAPPED
#endif
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
// FIXME seems to be broken.
extern "C" {
@ -1123,6 +1112,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Bold.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-SemiBold.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Cairo-SemiBold.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Graphik-SemiBold.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Graphik-Regular.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Graphik-Medium.ttf");
_window->setWindowTitle("High Fidelity Interface");
Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us
@ -3081,6 +3073,7 @@ void Application::initializeUi() {
QmlContextCallback commerceCallback = [](QQmlContext* context) {
context->setContextProperty("Commerce", DependencyManager::get<QmlCommerce>().data());
};
OffscreenQmlSurface::addWhitelistContextHandler({
QUrl{ "hifi/commerce/checkout/Checkout.qml" },
QUrl{ "hifi/commerce/common/CommerceLightbox.qml" },
@ -3106,6 +3099,8 @@ void Application::initializeUi() {
QUrl{ "hifi/dialogs/security/SecurityImageSelection.qml" },
QUrl{ "hifi/tablet/TabletMenu.qml" },
QUrl{ "hifi/commerce/marketplace/Marketplace.qml" },
QUrl{ "hifi/simplifiedUI/avatarApp/AvatarApp.qml" },
QUrl{ "hifi/simplifiedUI/topBar/SimplifiedTopBar.qml" },
}, commerceCallback);
QmlContextCallback marketplaceCallback = [](QQmlContext* context) {
@ -4029,24 +4024,6 @@ bool Application::handleFileOpenEvent(QFileOpenEvent* fileEvent) {
return false;
}
#ifdef DEBUG_EVENT_QUEUE
static int getEventQueueSize(QThread* thread) {
auto threadData = QThreadData::get2(thread);
QMutexLocker locker(&threadData->postEventList.mutex);
return threadData->postEventList.size();
}
static void dumpEventQueue(QThread* thread) {
auto threadData = QThreadData::get2(thread);
QMutexLocker locker(&threadData->postEventList.mutex);
qDebug() << "Event list, size =" << threadData->postEventList.size();
for (auto& postEvent : threadData->postEventList) {
QEvent::Type type = (postEvent.event ? postEvent.event->type() : QEvent::None);
qDebug() << " " << type;
}
}
#endif // DEBUG_EVENT_QUEUE
bool Application::notify(QObject * object, QEvent * event) {
if (thread() == QThread::currentThread()) {
PROFILE_RANGE_IF_LONGER(app, "notify", 2)
@ -4082,14 +4059,16 @@ bool Application::event(QEvent* event) {
case ApplicationEvent::Idle:
idle();
#ifdef DEBUG_EVENT_QUEUE
#ifdef DEBUG_EVENT_QUEUE_DEPTH
// The event queue may very well grow beyond 400, so
// this code should only be enabled on local builds
{
int count = getEventQueueSize(QThread::currentThread());
int count = ::hifi::qt::getEventQueueSize(QThread::currentThread());
if (count > 400) {
dumpEventQueue(QThread::currentThread());
::hifi::qt::dumpEventQueue(QThread::currentThread());
}
}
#endif // DEBUG_EVENT_QUEUE
#endif // DEBUG_EVENT_QUEUE_DEPTH
_pendingIdleEvent.store(false);

View file

@ -351,10 +351,18 @@ float LODManager::getHMDLODTargetFPS() const {
}
float LODManager::getLODTargetFPS() const {
auto refreshRateFPS = qApp->getRefreshRateManager().getActiveRefreshRate();
auto lodTargetFPS = getDesktopLODTargetFPS();
if (qApp->isHMDMode()) {
return getHMDLODTargetFPS();
lodTargetFPS = getHMDLODTargetFPS();
}
// if RefreshRate is slower than LOD target then it becomes the true LOD target
if (lodTargetFPS > refreshRateFPS) {
return refreshRateFPS;
} else {
return lodTargetFPS;
}
return getDesktopLODTargetFPS();
}
void LODManager::setWorldDetailQuality(float quality) {

View file

@ -370,6 +370,8 @@ class MyAvatar : public Avatar {
Q_PROPERTY(QUuid SELF_ID READ getSelfID CONSTANT)
Q_PROPERTY(float walkSpeed READ getWalkSpeed WRITE setWalkSpeed);
Q_PROPERTY(float analogPlusWalkSpeed READ getAnalogPlusWalkSpeed WRITE setAnalogPlusWalkSpeed);
Q_PROPERTY(float analogPlusSprintSpeed READ getAnalogPlusSprintSpeed WRITE setAnalogPlusSprintSpeed);
Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed);
Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed);
Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState);

View file

@ -317,13 +317,13 @@ void Ledger::accountSuccess(QNetworkReply* reply) {
const QByteArray locker = data["locker"].toString().toUtf8();
bool isOverride = wallet->wasSoftReset();
wallet->setSalt(salt);
wallet->setIv(iv);
wallet->setCKey(ckey);
if (!locker.isEmpty()) {
wallet->setWallet(locker);
wallet->setPassphrase("ACCOUNT"); // We only locker wallets that have been converted to account-based auth.
}
wallet->setSalt(salt);
QString keyStatus = "ok";
QStringList localPublicKeys = wallet->listPublicKeys();

View file

@ -313,6 +313,8 @@ Wallet::Wallet() {
walletScriptingInterface->setWalletStatus(status);
});
connect(ledger.data(), &Ledger::accountResult, this, &Wallet::sendChallengeOwnershipResponses);
auto accountManager = DependencyManager::get<AccountManager>();
connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
getWalletStatus();
@ -823,88 +825,101 @@ bool Wallet::changePassphrase(const QString& newPassphrase) {
}
void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
_pendingChallenges.push_back(packet);
sendChallengeOwnershipResponses();
}
void Wallet::sendChallengeOwnershipResponses() {
if (_pendingChallenges.size() == 0 || getSalt().length() == 0) {
return;
}
auto nodeList = DependencyManager::get<NodeList>();
// With EC keys, we receive a nonce from the metaverse server, which is signed
// here with the private key and returned. Verification is done at server.
bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest;
int status;
int idByteArraySize;
int textByteArraySize;
int challengingNodeUUIDByteArraySize;
packet->readPrimitive(&idByteArraySize);
packet->readPrimitive(&textByteArraySize); // returns a cast char*, size
if (challengeOriginatedFromClient) {
packet->readPrimitive(&challengingNodeUUIDByteArraySize);
}
// "encryptedText" is now a series of random bytes, a nonce
QByteArray id = packet->read(idByteArraySize);
QByteArray text = packet->read(textByteArraySize);
QByteArray challengingNodeUUID;
if (challengeOriginatedFromClient) {
challengingNodeUUID = packet->read(challengingNodeUUIDByteArraySize);
}
EC_KEY* ec = readKeys(keyFilePath());
QString sig;
if (ec) {
ERR_clear_error();
sig = signWithKey(text, ""); // base64 signature, QByteArray cast (on return) to QString FIXME should pass ec as string so we can tell which key to sign with
status = 1;
} else {
qCDebug(commerce) << "During entity ownership challenge, creating the EC-signed nonce failed.";
status = -1;
for (const auto& packet: _pendingChallenges) {
// With EC keys, we receive a nonce from the metaverse server, which is signed
// here with the private key and returned. Verification is done at server.
QString sig;
bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest;
int status;
int idByteArraySize;
int textByteArraySize;
int challengingNodeUUIDByteArraySize;
packet->readPrimitive(&idByteArraySize);
packet->readPrimitive(&textByteArraySize); // returns a cast char*, size
if (challengeOriginatedFromClient) {
packet->readPrimitive(&challengingNodeUUIDByteArraySize);
}
// "encryptedText" is now a series of random bytes, a nonce
QByteArray id = packet->read(idByteArraySize);
QByteArray text = packet->read(textByteArraySize);
QByteArray challengingNodeUUID;
if (challengeOriginatedFromClient) {
challengingNodeUUID = packet->read(challengingNodeUUIDByteArraySize);
}
if (ec) {
ERR_clear_error();
sig = signWithKey(text, ""); // base64 signature, QByteArray cast (on return) to QString FIXME should pass ec as string so we can tell which key to sign with
status = 1;
} else {
qCDebug(commerce) << "During entity ownership challenge, creating the EC-signed nonce failed.";
status = -1;
}
QByteArray textByteArray;
if (status > -1) {
textByteArray = sig.toUtf8();
}
textByteArraySize = textByteArray.size();
int idSize = id.size();
// setup the packet
Node& sendingNode = *nodeList->nodeWithLocalID(packet->getSourceID());
if (challengeOriginatedFromClient) {
auto textPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
idSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
true);
textPacket->writePrimitive(idSize);
textPacket->writePrimitive(textByteArraySize);
textPacket->writePrimitive(challengingNodeUUIDByteArraySize);
textPacket->write(id);
textPacket->write(textByteArray);
textPacket->write(challengingNodeUUID);
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for id" << id;
nodeList->sendPacket(std::move(textPacket), sendingNode);
} else {
auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, idSize + textByteArraySize + 2 * sizeof(int), true);
textPacket->writePrimitive(idSize);
textPacket->writePrimitive(textByteArraySize);
textPacket->write(id);
textPacket->write(textByteArray);
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for id" << id;
nodeList->sendPacket(std::move(textPacket), sendingNode);
}
if (status == -1) {
qCDebug(commerce) << "During entity ownership challenge, signing the text failed.";
long error = ERR_get_error();
if (error != 0) {
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "EC error:" << error_str;
}
}
}
EC_KEY_free(ec);
QByteArray textByteArray;
if (status > -1) {
textByteArray = sig.toUtf8();
}
textByteArraySize = textByteArray.size();
int idSize = id.size();
// setup the packet
if (challengeOriginatedFromClient) {
auto textPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
idSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
true);
textPacket->writePrimitive(idSize);
textPacket->writePrimitive(textByteArraySize);
textPacket->writePrimitive(challengingNodeUUIDByteArraySize);
textPacket->write(id);
textPacket->write(textByteArray);
textPacket->write(challengingNodeUUID);
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for id" << id;
nodeList->sendPacket(std::move(textPacket), *sendingNode);
} else {
auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, idSize + textByteArraySize + 2 * sizeof(int), true);
textPacket->writePrimitive(idSize);
textPacket->writePrimitive(textByteArraySize);
textPacket->write(id);
textPacket->write(textByteArray);
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for id" << id;
nodeList->sendPacket(std::move(textPacket), *sendingNode);
}
if (status == -1) {
qCDebug(commerce) << "During entity ownership challenge, signing the text failed.";
long error = ERR_get_error();
if (error != 0) {
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "EC error:" << error_str;
}
}
_pendingChallenges.clear();
}
void Wallet::account() {

View file

@ -94,6 +94,7 @@ signals:
private slots:
void handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void sendChallengeOwnershipResponses();
private:
friend class Ledger;
@ -104,6 +105,7 @@ private:
QByteArray _ckey;
QString* _passphrase { nullptr };
bool _isOverridingServer { false };
std::vector<QSharedPointer<ReceivedMessage>> _pendingChallenges;
bool writeWallet(const QString& newPassphrase = QString(""));
void updateImageProvider();

View file

@ -22,6 +22,29 @@
#include <DependencyManager.h>
#include <OffscreenUi.h>
/**jsdoc
* The possible docking locations of an <code>InteractiveWindow</code>.
* @typedef {object} InteractiveWindow.DockAreas
* @property {InteractiveWindow.DockArea} TOP - Dock to the top edge of the Interface window.
* @property {InteractiveWindow.DockArea} BOTTOM - Dock to the bottom edge of the Interface window.
* @property {InteractiveWindow.DockArea} LEFT - Dock to the left edge of the Interface window.
* @property {InteractiveWindow.DockArea} RIGHT - Dock to the right edge of the Interface window.
*/
/**jsdoc
* A docking location of an <code>InteractiveWindow</code>.
* <table>
* <thead>
* <tr><th>Value</th><th>Name</p><th>Description</th>
* </thead>
* <tbody>
* <tr><td><code>0</code></td><td>TOP</td><td>Dock to the top edge of the Interface window.</td></tr>
* <tr><td><code>1</code></td><td>BOTTOM</td><td>Dock to the bottom edge of the Interface window.</td></tr>
* <tr><td><code>2</code></td><td>LEFT</td><td>Dock to the left edge of the Interface window.</td></tr>
* <tr><td><code>3</code></td><td>RIGHT</td><td>Dock to the right edge of the Interface window.</td></tr>
* <tbody>
* </table>
* @typedef {number} InteractiveWindow.DockArea
*/
static const QVariantMap DOCK_AREA {
{ "TOP", DockArea::TOP },
{ "BOTTOM", DockArea::BOTTOM },
@ -38,6 +61,29 @@ int DesktopScriptingInterface::getHeight() {
return size.height();
}
/**jsdoc
* The possible display modes for an <code>InteractiveWindow</code>.
* @typedef {object} InteractiveWindow.PresentationModes
* @property {InteractiveWindow.PresentationMode} VIRTUAL - The window is displayed inside Interface: in the desktop window in
* desktop mode or on the HUD surface in HMD mode.
* @property {InteractiveWindow.PresentationMode} NATIVE - The window is displayed separately from the Interface window, as its
* own separate window.
*/
/**jsdoc
* A display mode for an <code>InteractiveWindow</code>.
* <table>
* <thead>
* <tr><th>Value</th><th>Name</p><th>Description</th>
* </thead>
* <tbody>
* <tr><td><code>0</code></td><td>VIRTUAL</td><td>The window is displayed inside Interface: in the desktop window in
* desktop mode or on the HUD surface in HMD mode.</td></tr>
* <tr><td><code>1</code></td><td>NATIVE</td><td>The window is displayed separately from the Interface window, as its
* own separate window.</td></tr>
* <tbody>
* </table>
* @typedef {number} InteractiveWindow.PresentationMode
*/
QVariantMap DesktopScriptingInterface::getPresentationMode() {
static QVariantMap presentationModes {
{ "VIRTUAL", Virtual },

View file

@ -20,16 +20,28 @@
#include "ui/InteractiveWindow.h"
/**jsdoc
* The <code>Desktop</code> API provides the dimensions of the computer screen, sets the opacity of the HUD surface, and
* enables QML and HTML windows to be shown inside or outside of Interface.
*
* @namespace Desktop
*
* @hifi-interface
* @hifi-client-entity
* @hifi-avatar
*
* @property {number} width
* @property {number} height
* @property {number} ALWAYS_ON_TOP - InteractiveWindow flag for always showing a window on top
* @property {number} CLOSE_BUTTON_HIDES - InteractiveWindow flag for hiding the window instead of closing on window close by user
* @property {number} width - The width of the computer screen including task bar and system menu, in pixels.
* <em>Read-only.</em>
* @property {number} height - The height of the computer screen including task bar and system menu, in pixels.
* <em>Read-only.</em>
* @property {InteractiveWindow.Flags} ALWAYS_ON_TOP - A flag value that makes an {@link InteractiveWindow} always display on
* top. <em>Read-only.</em>
* @property {InteractiveWindow.Flags} CLOSE_BUTTON_HIDES - A flag value that makes an {@link InteractiveWindow} hide instead
* of closing when the user clicks the "close" button.<em> Read-only.</em>
* @property {InteractiveWindow.PresentationModes} PresentationMode - The possible display options for an
* {@link InteractiveWindow}: display inside Interface or in a separate desktop window. <em>Read-only.</em>
* @property {InteractiveWindow.DockAreas} DockArea - The possible docking locations of an {@link InteractiveWindow}: top,
* bottom, left, or right of the Interface window.
* <em>Read-only.</em>
*/
class DesktopScriptingInterface : public QObject, public Dependency {
Q_OBJECT
@ -41,10 +53,44 @@ class DesktopScriptingInterface : public QObject, public Dependency {
Q_PROPERTY(int ALWAYS_ON_TOP READ flagAlwaysOnTop CONSTANT FINAL)
Q_PROPERTY(int CLOSE_BUTTON_HIDES READ flagCloseButtonHides CONSTANT FINAL)
public:
public:
/**jsdoc
* Sets the opacity of the HUD surface.
* @function Desktop.setHUDAlpha
* @param {number} alpha - The opacity, <code>0.0 &ndash; 1.0</code>.
*/
Q_INVOKABLE void setHUDAlpha(float alpha);
/**jsdoc
* Opens a QML window within Interface: in the Interface window in desktop mode or on the HUD surface in HMD mode. If a
* window of the specified name already exists, it is shown, otherwise a new window is created from the QML.
* @function Desktop.show
* @param {string} url - The QML file that specifies the window content.
* @param {string} name - A unique name for the window.
* @example <caption>Open the general settings dialog.</caption>
* Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog");
*/
Q_INVOKABLE void show(const QString& path, const QString& title);
/**jsdoc
* Creates a new window that can be displayed either within Interface or as a separate desktop window.
* @function Desktop.createWindow
* @param {string} url - The QML file that specifies the window content. The QML file can use a <code>WebView</code>
* control (defined by "WebView.qml" included in the Interface install) to embed an HTML web page (complete with
* <code>EventBridge</code> object).
* @param {InteractiveWindow.Properties} [properties] - Initial window properties.
* @returns {InteractiveWindow} A new window object.
* @example <caption>Open a dialog in its own window separate from Interface.</caption>
* var nativeWindow = Desktop.createWindow(Script.resourcesPath() + 'qml/OverlayWindowTest.qml', {
* title: "Native Window",
* presentationMode: Desktop.PresentationMode.NATIVE,
* size: { x: 500, y: 400 }
* });
*
* Script.scriptEnding.connect(function () {
* nativeWindow.close();
* });
*/
Q_INVOKABLE InteractiveWindowPointer createWindow(const QString& sourceUrl, const QVariantMap& properties = QVariantMap());
int getWidth();

View file

@ -13,69 +13,90 @@
class QScriptValue;
/**jsdoc
* The <code>PlatformInfo</code> API provides information about the computer and controllers being used.
*
* @namespace PlatformInfo
*
* @hifi-interface
* @hifi-client-entity
* @hifi-avatar
*/
class PlatformInfoScriptingInterface : public QObject {
Q_OBJECT
public slots:
/**jsdoc
* @function PlatformInfo.getInstance
* @deprecated This function is deprecated and will be removed.
*/
static PlatformInfoScriptingInterface* getInstance();
/**jsdoc
* Returns the Operating Sytem type
* @function Test.getOperatingSystemType
* @returns {string} "WINDOWS", "MACOS" or "UNKNOWN"
*/
* Gets the operating system type.
* @function PlatformInfo.getOperatingSystemType
* @returns {string} <code>"WINDOWS"</code>, <code>"MACOS"</code>, or <code>"UNKNOWN"</code>.
*/
QString getOperatingSystemType();
/**jsdoc
* Returns the CPU brand
*function PlatformInfo.getCPUBrand()
* @returns {string} brand of CPU
*/
* Gets information on the CPU.
* @function PlatformInfo.getCPUBrand
* @returns {string} Information on the CPU.
* @example <caption>Report the CPU being used.</caption>
* print("CPU: " + PlatformInfo.getCPUBrand());
* // Example: Intel(R) Core(TM) i7-7820HK CPU @ 2.90GHz
*/
QString getCPUBrand();
/**jsdoc
* Returns the number of logical CPU cores
*function PlatformInfo.getNumLogicalCores()
* @returns {int} number of logical CPU cores
*/
* Gets the number of logical CPU cores.
* @function PlatformInfo.getNumLogicalCores
* @returns {number} The number of logical CPU cores.
*/
unsigned int getNumLogicalCores();
/**jsdoc
* Returns the total system memory in megabyte
*function PlatformInfo.getTotalSystemMemory()
* @returns {int} size of memory in megabytes
*/
* Returns the total system memory in megabytes.
* @function PlatformInfo.getTotalSystemMemoryMB
* @returns {number} The total system memory in megabytes.
*/
int getTotalSystemMemoryMB();
/**jsdoc
* Returns the graphics card type
* @function Test.getGraphicsCardType
* @returns {string} graphics card type
*/
* Gets the graphics card type.
* @function PlatformInfo.getGraphicsCardType
* @returns {string} The graphics card type.
*/
QString getGraphicsCardType();
/**jsdoc
* Returns true if Oculus Rift is connected (looks for hand controllers)
* @function Window.hasRift
* @returns {boolean} <code>true</code> if running on Windows, otherwise <code>false</code>.*/
* Checks whether Oculus Touch controllers are connected.
* @function PlatformInfo.hasRiftControllers
* @returns {boolean} <code>true</code> if Oculus Touch controllers are connected, <code>false</code> if they aren't.
*/
bool hasRiftControllers();
/**jsdoc
* Returns true if HTC Vive is connected (looks for hand controllers)
* @function Window.hasRift
* @returns {boolean} <code>true</code> if running on Windows, otherwise <code>false</code>.*/
* Checks whether Vive controllers are connected.
* @function PlatformInfo.hasViveControllers
* @returns {boolean} <code>true</code> if Vive controllers are connected, <code>false</code> if they aren't.
*/
bool hasViveControllers();
/**jsdoc
* Returns true if device supports 3d HTML
* @function Window.has3DHTML
* @returns {boolean} <code>true</code> if device supports 3d HTML, otherwise <code>false</code>.*/
* Checks whether HTML on 3D surfaces (e.g., Web entities) is supported.
* @function PlatformInfo.has3DHTML
* @returns {boolean} <code>true</code> if the current display supports HTML on 3D surfaces, <code>false</code> if it
* doesn't.
*/
bool has3DHTML();
/**jsdoc
* Returns true if device is standalone
* @function Window.hasRift
* @returns {boolean} <code>true</code> if device is a standalone device, otherwise <code>false</code>.*/
* Checks whether Interface is running on a stand-alone HMD device (CPU incorporated into the HMD display).
* @function PlatformInfo.isStandalone
* @returns {boolean} <code>true</code> if Interface is running on a stand-alone device, <code>false</code> if it isn't.
*/
bool isStandalone();
};

View file

@ -23,9 +23,9 @@
#include <DependencyManager.h>
/**jsdoc
* The Window API provides various facilities not covered elsewhere: window dimensions, window focus, normal or entity camera
* view, clipboard, announcements, user connections, common dialog boxes, snapshots, file import, domain changes, domain
* physics.
* The <code>Window</code> API provides various facilities not covered elsewhere, including: window dimensions, window focus,
* camera view, announcements, user connections, common dialog boxes, snapshots, file import, domain navigation, domain changes,
* domain physics, OS clipboard, build number.
*
* @namespace Window
*
@ -37,13 +37,13 @@
* chrome), in pixels. <em>Read-only.</em>
* @property {number} innerHeight - The height of the drawable area of the Interface window (i.e., without borders or other
* chrome), in pixels. <em>Read-only.</em>
* @property {object} location - Provides facilities for working with your current metaverse location. See {@link location}.
* @property {number} x - The x display coordinate of the top left corner of the drawable area of the Interface window.
* <em>Read-only.</em>
* @property {number} y - The y display coordinate of the top left corner of the drawable area of the Interface window.
* <em>Read-only.</em>
* @property {boolean} interstitialModeEnabled=true - <code>true</code> if the interstitial graphics are displayed when the
* @property {boolean} interstitialModeEnabled=false - <code>true</code> if the interstitial graphics are displayed when a
* domain is loading, otherwise <code>false</code>.
* @property {location} location - Provides facilities for working with your current metaverse location.
*/
class WindowScriptingInterface : public QObject, public Dependency {
@ -65,27 +65,27 @@ public:
public slots:
/**jsdoc
* Check if the Interface window has focus.
* Checks whether the Interface window has focus.
* @function Window.hasFocus
* @returns {boolean} <code>true</code> if the Interface window has focus, otherwise <code>false</code>.
* @returns {boolean} <code>true</code> if the Interface window has focus, <code>false</code> if it doesn't.
*/
QScriptValue hasFocus();
/**jsdoc
* Make the Interface window have focus. On Windows, if Interface doesn't already have focus, the task bar icon flashes to
* Makes the Interface window have focus. On Windows, if Interface doesn't already have focus, the task bar icon flashes to
* indicate that Interface wants attention but focus isn't taken away from the application that the user is using.
* @function Window.setFocus
*/
void setFocus();
/**jsdoc
* Raise the Interface window if it is minimized. If raised, the window gains focus.
* Raises the Interface window if it is minimized. If raised, the window gains focus.
* @function Window.raise
*/
void raise();
/**jsdoc
* Display a dialog with the specified message and an "OK" button. The dialog is non-modal; the script continues without
* Displays a dialog with the specified message and an "OK" button. The dialog is non-modal; the script continues without
* waiting for a user response.
* @function Window.alert
* @param {string} [message=""] - The message to display.
@ -96,8 +96,7 @@ public slots:
void alert(const QString& message = "");
/**jsdoc
* Prompt the user to confirm something. Displays a modal dialog with a message plus "Yes" and "No" buttons.
* responds.
* Prompts the user to confirm something. Displays a modal dialog with a message plus "Yes" and "No" buttons.
* @function Window.confirm
* @param {string} [message=""] - The question to display.
* @returns {boolean} <code>true</code> if the user selects "Yes", otherwise <code>false</code>.
@ -108,7 +107,7 @@ public slots:
QScriptValue confirm(const QString& message = "");
/**jsdoc
* Prompt the user to enter some text. Displays a modal dialog with a message and a text box, plus "OK" and "Cancel"
* Prompts the user to enter some text. Displays a modal dialog with a message and a text box, plus "OK" and "Cancel"
* buttons.
* @function Window.prompt
* @param {string} message - The question to display.
@ -125,7 +124,7 @@ public slots:
QScriptValue prompt(const QString& message, const QString& defaultText);
/**jsdoc
* Prompt the user to enter some text. Displays a non-modal dialog with a message and a text box, plus "OK" and "Cancel"
* Prompts the user to enter some text. Displays a non-modal dialog with a message and a text box, plus "OK" and "Cancel"
* buttons. A {@link Window.promptTextChanged|promptTextChanged} signal is emitted when the user OKs the dialog; no signal
* is emitted if the user cancels the dialog.
* @function Window.promptAsync
@ -143,7 +142,7 @@ public slots:
void promptAsync(const QString& message = "", const QString& defaultText = "");
/**jsdoc
* Prompt the user to choose a directory. Displays a modal dialog that navigates the directory tree.
* Prompts the user to choose a directory. Displays a modal dialog that navigates the directory tree.
* @function Window.browseDir
* @param {string} [title=""] - The title to display at the top of the dialog.
* @param {string} [directory=""] - The initial directory to start browsing at.
@ -155,7 +154,7 @@ public slots:
QScriptValue browseDir(const QString& title = "", const QString& directory = "");
/**jsdoc
* Prompt the user to choose a directory. Displays a non-modal dialog that navigates the directory tree. A
* Prompts the user to choose a directory. Displays a non-modal dialog that navigates the directory tree. A
* {@link Window.browseDirChanged|browseDirChanged} signal is emitted when a directory is chosen; no signal is emitted if
* the user cancels the dialog.
* @function Window.browseDirAsync
@ -173,7 +172,7 @@ public slots:
void browseDirAsync(const QString& title = "", const QString& directory = "");
/**jsdoc
* Prompt the user to choose a file. Displays a modal dialog that navigates the directory tree.
* Prompts the user to choose a file. Displays a modal dialog that navigates the directory tree.
* @function Window.browse
* @param {string} [title=""] - The title to display at the top of the dialog.
* @param {string} [directory=""] - The initial directory to start browsing at.
@ -187,7 +186,7 @@ public slots:
QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
/**jsdoc
* Prompt the user to choose a file. Displays a non-modal dialog that navigates the directory tree. A
* Prompts the user to choose a file. Displays a non-modal dialog that navigates the directory tree. A
* {@link Window.browseChanged|browseChanged} signal is emitted when a file is chosen; no signal is emitted if the user
* cancels the dialog.
* @function Window.browseAsync
@ -207,7 +206,7 @@ public slots:
void browseAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
/**jsdoc
* Prompt the user to specify the path and name of a file to save to. Displays a model dialog that navigates the directory
* Prompts the user to specify the path and name of a file to save to. Displays a model dialog that navigates the directory
* tree and allows the user to type in a file name.
* @function Window.save
* @param {string} [title=""] - The title to display at the top of the dialog.
@ -223,7 +222,7 @@ public slots:
QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
/**jsdoc
* Prompt the user to specify the path and name of a file to save to. Displays a non-model dialog that navigates the
* Prompts the user to specify the path and name of a file to save to. Displays a non-model dialog that navigates the
* directory tree and allows the user to type in a file name. A {@link Window.saveFileChanged|saveFileChanged} signal is
* emitted when a file is specified; no signal is emitted if the user cancels the dialog.
* @function Window.saveAsync
@ -243,7 +242,7 @@ public slots:
void saveAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
/**jsdoc
* Prompt the user to choose an Asset Server item. Displays a modal dialog that navigates the tree of assets on the Asset
* Prompts the user to choose an Asset Server item. Displays a modal dialog that navigates the tree of assets on the Asset
* Server.
* @function Window.browseAssets
* @param {string} [title=""] - The title to display at the top of the dialog.
@ -258,8 +257,8 @@ public slots:
QScriptValue browseAssets(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
/**jsdoc
* Prompt the user to choose an Asset Server item. Displays a non-modal dialog that navigates the tree of assets on the
* Asset Server. A {@link Window.assetsDirChanged|assetsDirChanged} signal is emitted when an asset is chosen; no signal is
* Prompts the user to choose an Asset Server item. Displays a non-modal dialog that navigates the tree of assets on the
* Asset Server. An {@link Window.assetsDirChanged|assetsDirChanged} signal is emitted when an asset is chosen; no signal is
* emitted if the user cancels the dialog.
* @function Window.browseAssetsAsync
* @param {string} [title=""] - The title to display at the top of the dialog.
@ -278,7 +277,7 @@ public slots:
void browseAssetsAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
/**jsdoc
* Open the Asset Browser dialog. If a file to upload is specified, the user is prompted to enter the folder and name to
* Opens the Asset Browser dialog. If a file to upload is specified, the user is prompted to enter the folder and name to
* map the file to on the asset server.
* @function Window.showAssetServer
* @param {string} [uploadFile=""] - The path and name of a file to upload to the asset server.
@ -290,14 +289,14 @@ public slots:
void showAssetServer(const QString& upload = "");
/**jsdoc
* Get Interface's build number.
* Gets Interface's build number.
* @function Window.checkVersion
* @returns {string} Interface's build number.
*/
QString checkVersion();
/**jsdoc
* Get the signature for Interface's protocol version.
* Gets the signature for Interface's protocol version.
* @function Window.protocolSignature
* @returns {string} A string uniquely identifying the version of the metaverse protocol that Interface is using.
*/
@ -317,25 +316,19 @@ public slots:
* are emitted. The path to store the snapshots and the length of the animated GIF to capture are specified in Settings >
* General > Snapshots.
*
* If user has supplied a specific filename for the snapshot:
* If the user's requested filename has a suffix that's contained within SUPPORTED_IMAGE_FORMATS,
* DON'T append ".jpg" to the filename. QT will save the image in the format associated with the
* filename's suffix.
* If you want lossless Snapshots, supply a `.png` filename. Otherwise, use `.jpeg` or `.jpg`.
* Otherwise, ".jpg" is appended to the user's requested filename so that the image is saved in JPG format.
* If the user hasn't supplied a specific filename for the snapshot:
* Save the snapshot in JPG format according to FILENAME_PATH_FORMAT
* @function Window.takeSnapshot
* @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken}
* signal.
* @param {boolean} [includeAnimated=false] - If <code>true</code>, a moving image is captured as an animated GIF in addition
* to a still image.
* @param {number} [aspectRatio=0] - The width/height ratio of the snapshot required. If the value is <code>0</code> the
* @param {number} [aspectRatio=0] - The width/height ratio of the snapshot required. If the value is <code>0</code>, the
* full resolution is used (window dimensions in desktop mode; HMD display dimensions in HMD mode), otherwise one of the
* dimensions is adjusted in order to match the aspect ratio.
* @param {string} [filename=""] - If this parameter is not given, the image will be saved as "hifi-snap-by-&lt;user name&gt-YYYY-MM-DD_HH-MM-SS".
* If this parameter is <code>""</code> then the image will be saved as ".jpg".
* Otherwise, the image will be saved to this filename, with an appended ".jpg".
* @param {string} [filename=""] - If a filename is not provided, the image is saved as "hifi-snap-by-&lt;user
* name&gt-on-YYYY-MM-DD_HH-MM-SS".<br />
* Still images are saved in JPEG or PNG format according to the extension provided &mdash; <code>".jpg"</code>,
* <code>".jpeg"</code>, or <code>".png"</code> &mdash; or if not provided then in JPEG format with an extension of
* <code>".jpg"</code>. Animated images are saved in GIF format.
*
* @example <caption>Using the snapshot function and signals.</caption>
* function onStillSnapshotTaken(path, notify) {
@ -368,9 +361,11 @@ public slots:
* @function Window.takeSecondaryCameraSnapshot
* @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken}
* signal.
* @param {string} [filename=""] - If this parameter is not given, the image will be saved as "hifi-snap-by-&lt;user name&gt;-YYYY-MM-DD_HH-MM-SS".
* If this parameter is <code>""</code> then the image will be saved as ".jpg".
* Otherwise, the image will be saved to this filename, with an appended ".jpg".
* @param {string} [filename=""] - If a filename is not provided, the image is saved as "hifi-snap-by-&lt;user
* name&gt-on-YYYY-MM-DD_HH-MM-SS".<br />
* Images are saved in JPEG or PNG format according to the extension provided &mdash; <code>".jpg"</code>,
* <code>".jpeg"</code>, or <code>".png"</code> &mdash; or if not provided then in JPEG format with an extension of
* <code>".jpg"</code>.
*/
void takeSecondaryCameraSnapshot(const bool& notify = true, const QString& filename = QString());
@ -380,17 +375,19 @@ public slots:
* @function Window.takeSecondaryCamera360Snapshot
* @param {Vec3} cameraPosition - The position of the camera for the snapshot.
* @param {boolean} [cubemapOutputFormat=false] - If <code>true</code> then the snapshot is saved as a cube map image,
* otherwise is saved as an equirectangular image.
* otherwise it is saved as an equirectangular image.
* @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken}
* signal.
* @param {string} [filename=""] - If this parameter is not supplied, the image will be saved as "hifi-snap-by-&lt;user name&gt;-YYYY-MM-DD_HH-MM-SS".
* If this parameter is <code>""</code> then the image will be saved as ".jpg".
* Otherwise, the image will be saved to this filename, with an appended ".jpg".
* @param {string} [filename=""] - If a filename is not provided, the image is saved as "hifi-snap-by-&lt;user
* name&gt-on-YYYY-MM-DD_HH-MM-SS".<br />
* Images are saved in JPEG or PNG format according to the extension provided &mdash; <code>".jpg"</code>,
* <code>".jpeg"</code>, or <code>".png"</code> &mdash; or if not provided then in JPEG format with an extension of
* <code>".jpg"</code>.
*/
void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat = false, const bool& notify = true, const QString& filename = QString());
/**jsdoc
* Emit a {@link Window.connectionAdded|connectionAdded} or a {@link Window.connectionError|connectionError} signal that
* Emits a {@link Window.connectionAdded|connectionAdded} or a {@link Window.connectionError|connectionError} signal that
* indicates whether or not a user connection was successfully made using the Web API.
* @function Window.makeConnection
* @param {boolean} success - If <code>true</code> then {@link Window.connectionAdded|connectionAdded} is emitted, otherwise
@ -400,7 +397,7 @@ public slots:
void makeConnection(bool success, const QString& userNameOrError);
/**jsdoc
* Display a notification message. Notifications are displayed in panels by the default script, nofications.js. An
* Displays a notification message. Notifications are displayed in panels by the default script, nofications.js. An
* {@link Window.announcement|announcement} signal is emitted when this function is called.
* @function Window.displayAnnouncement
* @param {string} message - The announcement message.
@ -416,7 +413,7 @@ public slots:
void displayAnnouncement(const QString& message);
/**jsdoc
* Prepare a snapshot ready for sharing. A {@link Window.snapshotShared|snapshotShared} signal is emitted when the snapshot
* Prepares a snapshot ready for sharing. A {@link Window.snapshotShared|snapshotShared} signal is emitted when the snapshot
* has been prepared.
* @function Window.shareSnapshot
* @param {string} path - The path and name of the image file to share.
@ -425,7 +422,7 @@ public slots:
void shareSnapshot(const QString& path, const QUrl& href = QUrl(""));
/**jsdoc
* Check to see if physics is active for you in the domain you're visiting - there is a delay between your arrival at a
* Checks to see if physics is active for you in the domain you're visiting - there is a delay between your arrival at a
* domain and physics becoming active for you in that domain.
* @function Window.isPhysicsEnabled
* @returns {boolean} <code>true</code> if physics is currently active for you, otherwise <code>false</code>.
@ -448,39 +445,16 @@ public slots:
bool isPhysicsEnabled();
/**jsdoc
* Set what to show on the PC display: normal view or entity camera view. The entity camera is configured using
* Sets what to show on the PC display. For entity camera view, the entity camera is configured using
* {@link Camera.setCameraEntity} and {@link Camera|Camera.mode}.
* @function Window.setDisplayTexture
* @param {Window.DisplayTexture} texture - The view to display.
* @returns {boolean} <code>true</code> if the display texture was successfully set, otherwise <code>false</code>.
*/
// See spectatorCamera.js for Valid parameter values.
/**jsdoc
* <p>The views that may be displayed on the PC display.</p>
* <table>
* <thead>
* <tr>
* <th>Value</th>
* <th>View Displayed</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td><code>""</code></td>
* <td>Normal view.</td>
* </tr>
* <tr>
* <td><code>"resource://spectatorCameraFrame"</code></td>
* <td>Entity camera view.</td>
* </tr>
* </tbody>
* </table>
* @typedef {string} Window.DisplayTexture
*/
bool setDisplayTexture(const QString& name);
/**jsdoc
* Check if a 2D point is within the desktop window if in desktop mode, or the drawable area of the HUD overlay if in HMD
* Checks if a 2D point is within the desktop window if in desktop mode, or the drawable area of the HUD overlay if in HMD
* mode.
* @function Window.isPointOnDesktopWindow
* @param {Vec2} point - The point to check.
@ -489,7 +463,7 @@ public slots:
bool isPointOnDesktopWindow(QVariant point);
/**jsdoc
* Get the size of the drawable area of the Interface window if in desktop mode or the HMD rendering surface if in HMD mode.
* Gets the size of the drawable area of the Interface window if in desktop mode or the HMD rendering surface if in HMD mode.
* @function Window.getDeviceSize
* @returns {Vec2} The width and height of the Interface window or HMD rendering surface, in pixels.
*/
@ -503,7 +477,7 @@ public slots:
int getLastDomainConnectionError() const;
/**jsdoc
* Open a non-modal message box that can have a variety of button combinations. See also,
* Opens a non-modal message box that can have a variety of button combinations. See also,
* {@link Window.updateMessageBox|updateMessageBox} and {@link Window.closeMessageBox|closeMessageBox}.
* @function Window.openMessageBox
* @param {string} title - The title to display for the message box.
@ -535,17 +509,21 @@ public slots:
int openMessageBox(QString title, QString text, int buttons, int defaultButton);
/**jsdoc
* Open a URL in the Interface window or other application, depending on the URL's scheme. The following schemes are supported:
* <code>hifi</code> (navigate to the URL in Interface), <code>hifiapp<code> (open a system app in Interface). Other schemes will either be handled by the OS
* (e.g. <code>http</code>, <code>https</code>, <code>mailto</code>) or will create a confirmation dialog asking the user to confirm that they want to try to open
* the URL.
* Opens a URL in the Interface window or other application, depending on the URL's scheme. The following schemes are
* supported:<br />
* <ul>
* <li><code>hifi</code>: Navigate to the URL in Interface.</li>
* <li><code>hifiapp</code>: Open a system app in Interface.</li>
* </ul>
* Other schemes will either be handled by the OS (e.g. <code>http</code>, <code>https</code>, or <code>mailto</code>) or
* will display a dialog asking the user to confirm that they want to try to open the URL.
* @function Window.openUrl
* @param {string} url - The URL to open.
*/
void openUrl(const QUrl& url);
/**jsdoc
* Open an Android activity and optionally return back to the scene when the activity is completed. <em>Android only.</em>
* Opens an Android activity and optionally return back to the scene when the activity is completed. <em>Android only.</em>
* @function Window.openAndroidActivity
* @param {string} activityName - The name of the activity to open: one of <code>"Home"</code>, <code>"Login"</code>, or
* <code>"Privacy Policy"</code>.
@ -555,7 +533,7 @@ public slots:
void openAndroidActivity(const QString& activityName, const bool backToScene);
/**jsdoc
* Update the content of a message box that was opened with {@link Window.openMessageBox|openMessageBox}.
* Updates the content of a message box that was opened with {@link Window.openMessageBox|openMessageBox}.
* @function Window.updateMessageBox
* @param {number} id - The ID of the message box.
* @param {string} title - The title to display for the message box.
@ -567,12 +545,17 @@ public slots:
void updateMessageBox(int id, QString title, QString text, int buttons, int defaultButton);
/**jsdoc
* Close a message box that was opened with {@link Window.openMessageBox|openMessageBox}.
* Closes a message box that was opened with {@link Window.openMessageBox|openMessageBox}.
* @function Window.closeMessageBox
* @param {number} id - The ID of the message box.
*/
void closeMessageBox(int id);
/**jsdoc
* @function Window.domainLoadingProgress
* @returns {number} Progress.
* @deprecated This function is deprecated and will be removed.
*/
float domainLoadingProgress();
private slots:

View file

@ -20,6 +20,7 @@
static AvatarInputs* INSTANCE{ nullptr };
Setting::Handle<bool> showAudioToolsSetting { QStringList { "AvatarInputs", "showAudioTools" }, true };
Setting::Handle<bool> showBubbleToolsSetting{ QStringList { "AvatarInputs", "showBubbleTools" }, true };
AvatarInputs* AvatarInputs::getInstance() {
if (!INSTANCE) {
@ -88,6 +89,15 @@ void AvatarInputs::setShowAudioTools(bool showAudioTools) {
emit showAudioToolsChanged(_showAudioTools);
}
void AvatarInputs::setShowBubbleTools(bool showBubbleTools) {
if (_showBubbleTools == showBubbleTools)
return;
_showBubbleTools = showBubbleTools;
showBubbleToolsSetting.set(_showAudioTools);
emit showBubbleToolsChanged(_showBubbleTools);
}
bool AvatarInputs::getIgnoreRadiusEnabled() const {
return DependencyManager::get<NodeList>()->getIgnoreRadiusEnabled();
}

View file

@ -35,6 +35,7 @@ class AvatarInputs : public QObject {
* @property {boolean} cameraMuted <em>Read-only.</em>
* @property {boolean} isHMD <em>Read-only.</em>
* @property {boolean} showAudioTools
* @property {boolean} showBubbleTools
*/
AI_PROPERTY(bool, cameraEnabled, false)
@ -42,6 +43,7 @@ class AvatarInputs : public QObject {
AI_PROPERTY(bool, isHMD, false)
Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged)
Q_PROPERTY(bool showBubbleTools READ showBubbleTools WRITE setShowBubbleTools NOTIFY showBubbleToolsChanged)
Q_PROPERTY(bool ignoreRadiusEnabled READ getIgnoreRadiusEnabled NOTIFY ignoreRadiusEnabledChanged)
//Q_PROPERTY(bool enteredIgnoreRadius READ getEnteredIgnoreRadius NOTIFY enteredIgnoreRadiusChanged)
@ -58,6 +60,7 @@ public:
AvatarInputs(QObject* parent = nullptr);
void update();
bool showAudioTools() const { return _showAudioTools; }
bool showBubbleTools() const { return _showBubbleTools; }
bool getIgnoreRadiusEnabled() const;
//bool getEnteredIgnoreRadius() const;
@ -69,6 +72,12 @@ public slots:
*/
void setShowAudioTools(bool showAudioTools);
/**jsdoc
* @function AvatarInputs.setShowBubbleTools
* @param {boolean} showBubbleTools
*/
void setShowBubbleTools(bool showBubbleTools);
signals:
/**jsdoc
@ -97,6 +106,13 @@ signals:
*/
void showAudioToolsChanged(bool show);
/**jsdoc
* @function AvatarInputs.showBubbleToolsChanged
* @param {boolean} show
* @returns {Signal}
*/
void showBubbleToolsChanged(bool show);
/**jsdoc
* @function AvatarInputs.avatarEnteredIgnoreRadius
* @param {QUuid} avatarID
@ -142,6 +158,7 @@ private:
void onAvatarLeftIgnoreRadius();
float _trailingAudioLoudness{ 0 };
bool _showAudioTools { false };
bool _showBubbleTools{ false };
};
#endif // hifi_AvatarInputs_h

View file

@ -68,8 +68,24 @@ void interactiveWindowPointerFromScriptValue(const QScriptValue& object, Interac
}
}
/**jsdoc
* A set of properties used when creating an <code>InteractiveWindow</code>.
* @typedef {object} InteractiveWindow.Properties
* @property {string} [title="InteractiveWindow] - The title of the window.
* @property {Vec2} [position] - The initial position of the window, in pixels.
* @property {Vec2} [size] - The initial size of the window, in pixels
* @property {boolean} [visible=true] - <code>true</code> to make the window visible when created, <code>false</code> to make
* it invisible.
* @property {InteractiveWindow.PresentationMode} [presentationMode=Desktop.PresentationMode.VIRTUAL] -
* <code>Desktop.PresentationMode.VIRTUAL</code> to display the window inside Interface, <code>.NATIVE</code> to display it
* as its own separate window.
* @property {InteractiveWindow.PresentationWindowInfo} [presentationWindowInfo] - Controls how a <code>NATIVE</code> window is
* displayed. If used, the window is docked to the specified edge of the Interface window, otherwise the window is
* displayed as its own separate window.
* @property {InteractiveWindow.Flags} [flags=0] - Window behavior flags, set at window creation. Possible flag values are
* provided as {@link Desktop|Desktop.ALWAYS_ON_TOP} and {@link Desktop|Desktop.CLOSE_BUTTON_HIDES}.
*/
InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties) {
bool docked = false;
InteractiveWindowPresentationMode presentationMode = InteractiveWindowPresentationMode::Native;
if (properties.contains(PRESENTATION_MODE_PROPERTY)) {
@ -95,6 +111,11 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
auto quickView = _dockWidget->getQuickView();
Application::setupQmlSurface(quickView->rootContext(), true);
/**jsdoc
* Configures how a <code>NATIVE</code> window is displayed.
* @typedef {object} InteractiveWindow.PresentationWindowInfo
* @property {InteractiveWindow.DockArea} dockArea - The edge of the Interface window to dock to.
*/
if (nativeWindowInfo.contains(DOCK_AREA_PROPERTY)) {
DockArea dockedArea = (DockArea) nativeWindowInfo[DOCK_AREA_PROPERTY].toInt();
switch (dockedArea) {
@ -124,12 +145,12 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
QObject::connect(quickView.get(), &QQuickView::statusChanged, [&, this] (QQuickView::Status status) {
if (status == QQuickView::Ready) {
QQuickItem* rootItem = _dockWidget->getRootItem();
_dockWidget->getQuickView()->rootContext()->setContextProperty(EVENT_BRIDGE_PROPERTY, this);
QObject::connect(rootItem, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
}
});
_dockWidget->setSource(QUrl(sourceUrl));
mainWindow->addDockWidget(dockArea, _dockWidget.get());
_dockedWindow = docked;
} else {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
// Build the event bridge and wrapper on the main thread
@ -188,10 +209,10 @@ InteractiveWindow::~InteractiveWindow() {
void InteractiveWindow::sendToQml(const QVariant& message) {
// Forward messages received from the script on to QML
if (_dockedWindow) {
if (_dockWidget) {
QQuickItem* rootItem = _dockWidget->getRootItem();
if (rootItem) {
QMetaObject::invokeMethod(_qmlWindow, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
QMetaObject::invokeMethod(rootItem, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
}
} else {
QMetaObject::invokeMethod(_qmlWindow, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));

View file

@ -25,6 +25,21 @@
namespace InteractiveWindowEnums {
Q_NAMESPACE
/**jsdoc
* A set of flags controlling <code>InteractiveWindow</code> behavior. The value is constructed by using the
* <code>|</code> (bitwise OR) operator on the individual flag values.<br />
* <table>
* <thead>
* <tr><th>Flag Name</th><th>Value</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td>ALWAYS_ON_TOP</td><td><code>1</code></td><td>The window always displays on top.</td></tr>
* <tr><td>CLOSE_BUTTON_HIDES</td><td><code>2</code></td><td>The window hides instead of closing when the user clicks
* the "close" button.</td></tr>
* </tbody>
* </table>
* @typedef {number} InteractiveWindow.Flags
*/
enum InteractiveWindowFlags : uint8_t {
AlwaysOnTop = 1 << 0,
CloseButtonHides = 1 << 1
@ -49,18 +64,25 @@ namespace InteractiveWindowEnums {
using namespace InteractiveWindowEnums;
/**jsdoc
* An <code>InteractiveWindow</code> can display either inside Interface or in its own window separate from the Interface
* window. The window content is defined by a QML file, which can optionally include a <code>WebView</code> control that embeds
* an HTML web page. (The <code>WebView</code> control is defined by a "WebView.qml" file included in the Interface install.)
*
* <p>Create using {@link Desktop.createWindow}.</p>
*
* @class InteractiveWindow
*
* @hifi-interface
* @hifi-client-entity
* @hifi-avatar
*
* @property {string} title
* @property {Vec2} position
* @property {Vec2} size
* @property {boolean} visible
* @property {Desktop.PresentationMode} presentationMode
*
* @property {string} title - The title of the window.
* @property {Vec2} position - The position of the window, in pixels.
* @property {Vec2} size - The size of the window, in pixels.
* @property {boolean} visible - <code>true</code> if the window is visible, <code>false</code> if it isn't.
* @property {InteractiveWindow.PresentationMode} presentationMode - The presentation mode of the window:
* <code>Desktop.PresentationMode.VIRTUAL</code> to display the window inside Interface, <code>.NATIVE</code> to display it
* as its own separate window.
*/
class DockWidget;
@ -99,36 +121,87 @@ private:
public slots:
/**jsdoc
* Sends a message to the QML page. To receive the message, the QML page must implement a function:
* <pre class="prettyprint"><code>function fromScript(message) {
* ...
* }</code></pre>
* @function InteractiveWindow.sendToQml
* @param {object} message
* @param {string|object} message - The message to send to the QML page.
* @example <caption>Send and receive messages with a QML window.</caption>
* // JavaScript file.
*
* var qmlWindow = Desktop.createWindow(Script.resolvePath("QMLWindow.qml"), {
* title: "QML Window",
* size: { x: 400, y: 300 }
* });
*
* qmlWindow.fromQml.connect(function (message) {
* print("Message received: " + message);
* });
*
* Script.setTimeout(function () {
* qmlWindow.sendToQml("Hello world!");
* }, 2000);
*
* Script.scriptEnding.connect(function () {
* qmlWindow.close();
* });
* @example
* // QML file, "QMLWindow.qml".
*
* import QtQuick 2.5
* import QtQuick.Controls 1.4
*
* Rectangle {
*
* function fromScript(message) {
* text.text = message;
* sendToScript("Hello back!");
* }
*
* Label {
* id: text
* anchors.centerIn: parent
* text: "..."
* }
* }
*/
// Scripts can use this to send a message to the QML object
void sendToQml(const QVariant& message);
/**jsdoc
* Sends a message to an embedded HTML web page. To receive the message, the HTML page's script must connect to the
* <code>EventBridge</code> that is automatically provided to the script:
* <pre class="prettyprint"><code>EventBridge.scriptEventReceived.connect(function(message) {
* ...
* });</code></pre>
* @function InteractiveWindow.emitScriptEvent
* @param {object} message
* @param {string|object} message - The message to send to the embedded HTML web page.
*/
// QmlWindow content may include WebView requiring EventBridge.
void emitScriptEvent(const QVariant& scriptMessage);
/**jsdoc
* @function InteractiveWindow.emitWebEvent
* @param {object} message
* @param {object|string} message - The message.
* @deprecated This function is deprecated and will be removed from the API.
*/
void emitWebEvent(const QVariant& webMessage);
/**jsdoc
* Closes the window. It can then no longer be used.
* @function InteractiveWindow.close
*/
Q_INVOKABLE void close();
/**jsdoc
* Makes the window visible and raises it to the top.
* @function InteractiveWindow.show
*/
Q_INVOKABLE void show();
/**jsdoc
* Raises the window to the top.
* @function InteractiveWindow.raise
*/
Q_INVOKABLE void raise();
@ -136,44 +209,52 @@ public slots:
signals:
/**jsdoc
* Triggered when the window is made visible or invisible, or is closed.
* @function InteractiveWindow.visibleChanged
* @returns {Signal}
*/
void visibleChanged();
/**jsdoc
* Triggered when the window's position changes.
* @function InteractiveWindow.positionChanged
* @returns {Signal}
*/
void positionChanged();
/**jsdoc
* Triggered when the window's size changes.
* @function InteractiveWindow.sizeChanged
* @returns {Signal}
*/
void sizeChanged();
/**jsdoc
* Triggered when the window's presentation mode changes.
* @function InteractiveWindow.presentationModeChanged
* @returns {Signal}
*/
void presentationModeChanged();
/**jsdoc
* Triggered when window's title changes.
* @function InteractiveWindow.titleChanged
* @returns {Signal}
*/
void titleChanged();
/**jsdoc
* Triggered when the window is closed.
* @function InteractiveWindow.closed
* @returns {Signal}
*/
void closed();
/**jsdoc
* Triggered when a message from the QML page is received. The QML page can send a message (string or object) by calling:
* <pre class="prettyprint"><code>sendToScript(message);</code></pre>
* @function InteractiveWindow.fromQml
* @param {object} message
* @param {string|object} message - The message received.
* @returns {Signal}
*/
// Scripts can connect to this signal to receive messages from the QML object
@ -181,15 +262,18 @@ signals:
/**jsdoc
* @function InteractiveWindow.scriptEventReceived
* @param {object} message
* @param {object} message - The message.
* @returns {Signal}
* @deprecated This signal is deprecated and will be removed from the API.
*/
// InteractiveWindow content may include WebView requiring EventBridge.
void scriptEventReceived(const QVariant& message);
/**jsdoc
* Triggered when a message from an embedded HTML web page is received. The HTML web page can send a message by calling:
* <pre class="prettyprint"><code>EventBridge.emitWebEvent(message);</code></pre>
* @function InteractiveWindow.webEventReceived
* @param {object} message
* @param {string|object} message - The message received.
* @returns {Signal}
*/
void webEventReceived(const QVariant& message);
@ -199,11 +283,11 @@ protected slots:
* @function InteractiveWindow.qmlToScript
* @param {object} message
* @returns {Signal}
* @deprecated This signal is deprecated and will be removed from the API.
*/
void qmlToScript(const QVariant& message);
private:
bool _dockedWindow { false };
QPointer<QObject> _qmlWindow;
std::shared_ptr<DockWidget> _dockWidget { nullptr };
};

View file

@ -180,6 +180,11 @@ void Stats::updateStats(bool force) {
STAT_UPDATE_FLOAT(mbpsIn, nodeList->getInboundKbps() / 1000.0f, 0.01f);
STAT_UPDATE_FLOAT(mbpsOut, nodeList->getOutboundKbps() / 1000.0f, 0.01f);
#ifdef DEBUG_EVENT_QUEUE
STAT_UPDATE(mainThreadQueueDepth, ::hifi::qt::getEventQueueSize(QThread::currentThread()));
STAT_UPDATE(nodeListThreadQueueDepth, ::hifi::qt::getEventQueueSize(nodeList->thread()));
#endif
SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer);
SharedNodePointer assetServerNode = nodeList->soloNodeOfType(NodeType::AssetServer);

View file

@ -14,6 +14,7 @@
#include <OffscreenQmlElement.h>
#include <AudioIOStats.h>
#include <render/Args.h>
#include <shared/QtHelpers.h>
#define STATS_PROPERTY(type, name, initialValue) \
Q_PROPERTY(type name READ name NOTIFY name##Changed) \
@ -179,6 +180,7 @@ private: \
* @property {Vec3} rayPicksUpdated - <em>Read-only.</em>
* @property {Vec3} parabolaPicksUpdated - <em>Read-only.</em>
* @property {Vec3} collisionPicksUpdated - <em>Read-only.</em>
* @property {bool} eventQueueDebuggingOn - <em>Read-only.</em>
*/
// Properties from x onwards are QQuickItem properties.
@ -312,6 +314,14 @@ class Stats : public QQuickItem {
STATS_PROPERTY(QVector3D, parabolaPicksUpdated, QVector3D(0, 0, 0))
STATS_PROPERTY(QVector3D, collisionPicksUpdated, QVector3D(0, 0, 0))
#ifdef DEBUG_EVENT_QUEUE
STATS_PROPERTY(bool, eventQueueDebuggingOn, true)
STATS_PROPERTY(int, mainThreadQueueDepth, -1);
STATS_PROPERTY(int, nodeListThreadQueueDepth, -1);
#else
STATS_PROPERTY(bool, eventQueueDebuggingOn, false)
#endif // DEBUG_EVENT_QUEUE
public:
static Stats* getInstance();
@ -1357,6 +1367,27 @@ signals:
*/
void collisionPicksUpdatedChanged();
/**jsdoc
* Triggered when the value of the <code>eventQueueDebuggingOn</code> property changes.
* @function Stats.eventQueueDebuggingOn
* @returns {Signal}
*/
void eventQueueDebuggingOnChanged();
/**jsdoc
* Triggered when the value of the <code>nodeListThreadQueueDepth</code> property changes.
* @function Stats.nodeListThreadQueueDepth
* @returns {Signal}
*/
void nodeListThreadQueueDepthChanged();
/**jsdoc
* Triggered when the value of the <code>nodeListThreadQueueDepth</code> property changes.
* @function Stats.nodeListThreadQueueDepth
* @returns {Signal}
*/
void mainThreadQueueDepthChanged();
private:
int _recentMaxPackets{ 0 } ; // recent max incoming voxel packets to process
bool _resetRecentMaxPacketsSoon{ true };

View file

@ -30,7 +30,7 @@ int RefreshRateController::getRefreshRateLimitPeriod() const {
return durationNanosecondsToHz(_refreshRateLimitPeriod);
}
void RefreshRateController::sleepThreadIfNeeded(QThread* thread, bool isHmd) {
std::chrono::nanoseconds RefreshRateController::sleepThreadIfNeeded(QThread* thread, bool isHmd) {
if (!isHmd) {
static const std::chrono::nanoseconds EPSILON = std::chrono::milliseconds(1);
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(_endTime - _startTime);
@ -39,5 +39,7 @@ void RefreshRateController::sleepThreadIfNeeded(QThread* thread, bool isHmd) {
if (sleepDuration.count() > 0) {
thread->msleep(std::chrono::duration_cast<std::chrono::milliseconds>(sleepDuration).count());
}
return sleepDuration;
}
return std::chrono::nanoseconds(0);
}

View file

@ -29,7 +29,7 @@ public:
void clockStartTime() { _startTime = std::chrono::high_resolution_clock::now(); }
void clockEndTime() { _endTime = std::chrono::high_resolution_clock::now(); }
void sleepThreadIfNeeded(QThread* thread, bool isHmd);
std::chrono::nanoseconds sleepThreadIfNeeded(QThread* thread, bool isHmd);
private:
std::chrono::time_point<std::chrono::high_resolution_clock> _startTime { std::chrono::high_resolution_clock::now() };
std::chrono::time_point<std::chrono::high_resolution_clock> _endTime { std::chrono::high_resolution_clock::now() };

View file

@ -361,6 +361,12 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
const uint32_t QUAD_STRIDE = 4;
ShapeType type = getShapeType();
auto model = getModel();
if (!model) {
type = SHAPE_TYPE_NONE;
}
if (type == SHAPE_TYPE_COMPOUND) {
updateModelBounds();
@ -442,10 +448,6 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
// to the visual model and apply them to the collision model (without regard for the
// collision model's extents).
auto model = getModel();
// assert we never fall in here when model not fully loaded
assert(model && model->isLoaded());
glm::vec3 dimensions = getScaledDimensions();
glm::vec3 scaleToFit = dimensions / model->getHFMModel().getUnscaledMeshExtents().size();
// multiply each point by scale before handing the point-set off to the physics engine.
@ -461,7 +463,6 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
adjustShapeInfoByRegistration(shapeInfo);
} else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) {
updateModelBounds();
auto model = getModel();
// assert we never fall in here when model not fully loaded
assert(model && model->isLoaded());
model->updateGeometry();

View file

@ -110,6 +110,10 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
EntityPropertyFlags requestedProperties = propertiesCopy.getChangedProperties();
if (!nodeList->getThisNodeCanGetAndSetPrivateUserData() && requestedProperties.getHasProperty(PROP_PRIVATE_USER_DATA)) {
requestedProperties -= PROP_PRIVATE_USER_DATA;
}
while (encodeResult == OctreeElement::PARTIAL) {
encodeResult = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut, requestedProperties, didntFitProperties);

View file

@ -86,6 +86,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_NAME;
requestedProperties += PROP_LOCKED;
requestedProperties += PROP_USER_DATA;
requestedProperties += PROP_PRIVATE_USER_DATA;
requestedProperties += PROP_HREF;
requestedProperties += PROP_DESCRIPTION;
requestedProperties += PROP_POSITION;
@ -154,7 +155,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
}
OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData) const {
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
const bool destinationNodeCanGetAndSetPrivateUserData) const {
// ALL this fits...
// object ID [16 bytes]
@ -198,6 +200,11 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
requestedProperties = entityTreeElementExtraEncodeData->entities.value(getEntityItemID());
}
QString privateUserData = "";
if (destinationNodeCanGetAndSetPrivateUserData) {
privateUserData = getPrivateUserData();
}
EntityPropertyFlags propertiesDidntFit = requestedProperties;
LevelDetails entityLevel = packetData->startLevel();
@ -266,8 +273,8 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, _simulationOwner.toByteArray());
// convert AVATAR_SELF_ID to actual sessionUUID.
QUuid actualParentID = getParentID();
auto nodeList = DependencyManager::get<NodeList>();
if (actualParentID == AVATAR_SELF_ID) {
auto nodeList = DependencyManager::get<NodeList>();
actualParentID = nodeList->getSessionUUID();
}
APPEND_ENTITY_PROPERTY(PROP_PARENT_ID, actualParentID);
@ -276,6 +283,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_NAME, getName());
APPEND_ENTITY_PROPERTY(PROP_LOCKED, getLocked());
APPEND_ENTITY_PROPERTY(PROP_USER_DATA, getUserData());
APPEND_ENTITY_PROPERTY(PROP_PRIVATE_USER_DATA, privateUserData);
APPEND_ENTITY_PROPERTY(PROP_HREF, getHref());
APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription());
APPEND_ENTITY_PROPERTY(PROP_POSITION, getLocalPosition());
@ -812,6 +820,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_NAME, QString, setName);
READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked);
READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData);
READ_ENTITY_PROPERTY(PROP_PRIVATE_USER_DATA, QString, setPrivateUserData);
READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref);
READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription);
{ // When we own the simulation we don't accept updates to the entity's transform/velocities
@ -1331,6 +1340,7 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire
COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(locked, getLocked);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(userData, getUserData);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(privateUserData, getPrivateUserData);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(description, getDescription);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getLocalPosition);
@ -1479,6 +1489,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(privateUserData, setPrivateUserData);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, setPosition);
@ -3121,6 +3132,20 @@ void EntityItem::setUserData(const QString& value) {
});
}
QString EntityItem::getPrivateUserData() const {
QString result;
withReadLock([&] {
result = _privateUserData;
});
return result;
}
void EntityItem::setPrivateUserData(const QString& value) {
withWriteLock([&] {
_privateUserData = value;
});
}
// Certifiable Properties
#define DEFINE_PROPERTY_GETTER(type, accessor, var) \
type EntityItem::get##accessor() const { \

View file

@ -134,7 +134,8 @@ public:
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
virtual OctreeElement::AppendState appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData) const;
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
const bool destinationNodeCanGetAndSetPrivateUserData = false) const;
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
@ -332,6 +333,9 @@ public:
QString getUserData() const;
virtual void setUserData(const QString& value); // FIXME: This is suspicious
QString getPrivateUserData() const;
void setPrivateUserData(const QString& value);
// FIXME not thread safe?
const SimulationOwner& getSimulationOwner() const { return _simulationOwner; }
void setSimulationOwner(const QUuid& id, uint8_t priority);
@ -644,6 +648,7 @@ protected:
bool _dynamic { ENTITY_ITEM_DEFAULT_DYNAMIC };
bool _locked { ENTITY_ITEM_DEFAULT_LOCKED };
QString _userData { ENTITY_ITEM_DEFAULT_USER_DATA };
QString _privateUserData{ ENTITY_ITEM_DEFAULT_PRIVATE_USER_DATA };
SimulationOwner _simulationOwner;
bool _shouldHighlight { false };
QString _name { ENTITY_ITEM_DEFAULT_NAME };

View file

@ -487,6 +487,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_NAME, name);
CHECK_PROPERTY_CHANGE(PROP_LOCKED, locked);
CHECK_PROPERTY_CHANGE(PROP_USER_DATA, userData);
CHECK_PROPERTY_CHANGE(PROP_PRIVATE_USER_DATA, privateUserData);
CHECK_PROPERTY_CHANGE(PROP_HREF, href);
CHECK_PROPERTY_CHANGE(PROP_DESCRIPTION, description);
CHECK_PROPERTY_CHANGE(PROP_POSITION, position);
@ -818,6 +819,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* which you can manipulate the properties of, and use <code>JSON.stringify()</code> to convert the object into a string to
* put in the property.
*
* @property {string} privateUserData="" - Like userData, but only accessible by Entity Server Scripts, AC scripts, and users
* who are given "Can Get and Set Private User Data" permissions from the ACL matrix on the Domain Settings page.
*
* @property {string} script="" - The URL of the client entity script, if any, that is attached to the entity.
* @property {number} scriptTimestamp=0 - Intended to be used to indicate when the client entity script was loaded. Should be
* an integer number of milliseconds since midnight GMT on January 1, 1970 (e.g., as supplied by <code>Date.now()</code>.
@ -1591,6 +1595,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NAME, name);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCKED, locked);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USER_DATA, userData);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PRIVATE_USER_DATA, privateUserData);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_HREF, href);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DESCRIPTION, description);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_POSITION, position);
@ -1999,6 +2004,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(name, QString, setName);
COPY_PROPERTY_FROM_QSCRIPTVALUE(locked, bool, setLocked);
COPY_PROPERTY_FROM_QSCRIPTVALUE(userData, QString, setUserData);
COPY_PROPERTY_FROM_QSCRIPTVALUE(privateUserData, QString, setPrivateUserData);
COPY_PROPERTY_FROM_QSCRIPTVALUE(href, QString, setHref);
COPY_PROPERTY_FROM_QSCRIPTVALUE(description, QString, setDescription);
COPY_PROPERTY_FROM_QSCRIPTVALUE(position, vec3, setPosition);
@ -2288,6 +2294,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
COPY_PROPERTY_IF_CHANGED(name);
COPY_PROPERTY_IF_CHANGED(locked);
COPY_PROPERTY_IF_CHANGED(userData);
COPY_PROPERTY_IF_CHANGED(privateUserData);
COPY_PROPERTY_IF_CHANGED(href);
COPY_PROPERTY_IF_CHANGED(description);
COPY_PROPERTY_IF_CHANGED(position);
@ -2573,6 +2580,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
ADD_PROPERTY_TO_MAP(PROP_NAME, Name, name, QString);
ADD_PROPERTY_TO_MAP(PROP_LOCKED, Locked, locked, bool);
ADD_PROPERTY_TO_MAP(PROP_USER_DATA, UserData, userData, QString);
ADD_PROPERTY_TO_MAP(PROP_PRIVATE_USER_DATA, PrivateUserData, privateUserData, QString);
ADD_PROPERTY_TO_MAP(PROP_HREF, Href, href, QString);
ADD_PROPERTY_TO_MAP(PROP_DESCRIPTION, Description, description, QString);
ADD_PROPERTY_TO_MAP(PROP_POSITION, Position, position, vec3);
@ -3047,6 +3055,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName());
APPEND_ENTITY_PROPERTY(PROP_LOCKED, properties.getLocked());
APPEND_ENTITY_PROPERTY(PROP_USER_DATA, properties.getUserData());
APPEND_ENTITY_PROPERTY(PROP_PRIVATE_USER_DATA, properties.getPrivateUserData());
APPEND_ENTITY_PROPERTY(PROP_HREF, properties.getHref());
APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, properties.getDescription());
APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition());
@ -3530,6 +3539,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCKED, bool, setLocked);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_USER_DATA, QString, setUserData);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PRIVATE_USER_DATA, QString, setPrivateUserData);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HREF, QString, setHref);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DESCRIPTION, QString, setDescription);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, vec3, setPosition);
@ -3941,6 +3951,7 @@ void EntityItemProperties::markAllChanged() {
_nameChanged = true;
_lockedChanged = true;
_userDataChanged = true;
_privateUserDataChanged = true;
_hrefChanged = true;
_descriptionChanged = true;
_positionChanged = true;
@ -4303,6 +4314,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
if (userDataChanged()) {
out += "userData";
}
if (privateUserDataChanged()) {
out += "privateUserData";
}
if (hrefChanged()) {
out += "href";
}

View file

@ -180,6 +180,7 @@ public:
DEFINE_PROPERTY_REF(PROP_NAME, Name, name, QString, ENTITY_ITEM_DEFAULT_NAME);
DEFINE_PROPERTY(PROP_LOCKED, Locked, locked, bool, ENTITY_ITEM_DEFAULT_LOCKED);
DEFINE_PROPERTY_REF(PROP_USER_DATA, UserData, userData, QString, ENTITY_ITEM_DEFAULT_USER_DATA);
DEFINE_PROPERTY_REF(PROP_PRIVATE_USER_DATA, PrivateUserData, privateUserData, QString, ENTITY_ITEM_DEFAULT_PRIVATE_USER_DATA);
DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString, "");
DEFINE_PROPERTY_REF(PROP_DESCRIPTION, Description, description, QString, "");
DEFINE_PROPERTY_REF_WITH_SETTER(PROP_POSITION, Position, position, glm::vec3, ENTITY_ITEM_ZERO_VEC3);
@ -607,6 +608,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Locked, locked, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Textures, textures, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, UserData, userData, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, PrivateUserData, privateUserData, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, SimulationOwner, simulationOwner, SimulationOwner());
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Text, text, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, LineHeight, lineHeight, "");

View file

@ -28,6 +28,7 @@ const QVector<glm::vec3> ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC = QVector<glm::vec3
const bool ENTITY_ITEM_DEFAULT_LOCKED = false;
const QString ENTITY_ITEM_DEFAULT_USER_DATA = QString("");
const QString ENTITY_ITEM_DEFAULT_PRIVATE_USER_DATA = QString("");
const QUuid ENTITY_ITEM_DEFAULT_SIMULATOR_ID = QUuid();
// Certifiable Properties

View file

@ -26,6 +26,7 @@ enum EntityPropertyList {
PROP_NAME,
PROP_LOCKED,
PROP_USER_DATA,
PROP_PRIVATE_USER_DATA,
PROP_HREF,
PROP_DESCRIPTION,
PROP_POSITION,

View file

@ -56,6 +56,7 @@ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership
connect(nodeList.data(), &NodeList::canRezCertifiedChanged, this, &EntityScriptingInterface::canRezCertifiedChanged);
connect(nodeList.data(), &NodeList::canRezTmpCertifiedChanged, this, &EntityScriptingInterface::canRezTmpCertifiedChanged);
connect(nodeList.data(), &NodeList::canWriteAssetsChanged, this, &EntityScriptingInterface::canWriteAssetsChanged);
connect(nodeList.data(), &NodeList::canGetAndSetPrivateUserDataChanged, this, &EntityScriptingInterface::canGetAndSetPrivateUserDataChanged);
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::EntityScriptCallMethod, this, "handleEntityScriptCallMethodPacket");
@ -107,6 +108,11 @@ bool EntityScriptingInterface::canReplaceContent() {
return nodeList->getThisNodeCanReplaceContent();
}
bool EntityScriptingInterface::canGetAndSetPrivateUserData() {
auto nodeList = DependencyManager::get<NodeList>();
return nodeList->getThisNodeCanGetAndSetPrivateUserData();
}
void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) {
if (_entityTree) {
disconnect(_entityTree.get(), &EntityTree::addingEntityPointer, this, &EntityScriptingInterface::onAddingEntity);

View file

@ -236,6 +236,14 @@ public slots:
*/
Q_INVOKABLE bool canReplaceContent();
/**jsdoc
* Check whether or not you can get and set private user data.
* @function Entities.canGetAndSetPrivateUserData
* @returns {boolean} <code>true</code> if the domain server will allow the user to get and set private user data,
* otherwise <code>false</code>.
*/
Q_INVOKABLE bool canGetAndSetPrivateUserData();
/**jsdoc
* <p>How an entity is sent over the wire.</p>
* <table>
@ -1861,6 +1869,15 @@ signals:
*/
void canWriteAssetsChanged(bool canWriteAssets);
/**jsdoc
* Triggered when your ability to get and set private user data changes.
* @function Entities.canGetAndSetPrivateUserDataChanged
* @param {boolean} canGetAndSetPrivateUserData - <code>true</code> if you can change the <code>privateUserData</code> property of an entity,
* otherwise <code>false</code>.
* @returns {Signal}
*/
void canGetAndSetPrivateUserDataChanged(bool canGetAndSetPrivateUserData);
/**jsdoc
* Triggered when a mouse button is clicked while the mouse cursor is on an entity, or a controller trigger is fully

View file

@ -1286,6 +1286,14 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList<Q
}
}
if (properties.privateUserDataChanged()) {
int index = changedProperties.indexOf("privateUserData");
if (index >= 0) {
QString changeHint = properties.getPrivateUserData();
changedProperties[index] = QString("privateUserData:") + changeHint;
}
}
if (properties.parentJointIndexChanged()) {
int index = changedProperties.indexOf("parentJointIndex");
if (index >= 0) {
@ -1772,6 +1780,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
bool suppressDisallowedClientScript = false;
bool suppressDisallowedServerScript = false;
bool suppressDisallowedPrivateUserData = false;
bool isPhysics = message.getType() == PacketType::EntityPhysics;
_totalEditMessages++;
@ -1860,7 +1869,22 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
}
}
}
}
if (!properties.getPrivateUserData().isEmpty() && validEditPacket && !senderNode->getCanGetAndSetPrivateUserData()) {
if (wantEditLogging()) {
qCDebug(entities) << "User [" << senderNode->getUUID()
<< "] is attempting to set private user data but user isn't allowed; edit rejected...";
}
// If this was an add, we also want to tell the client that sent this edit that the entity was not added.
if (isAdd) {
QWriteLocker locker(&_recentlyDeletedEntitiesLock);
_recentlyDeletedEntityItemIDs.insert(usecTimestampNow(), entityItemID);
validEditPacket = false;
} else {
suppressDisallowedPrivateUserData = true;
}
}
if (!isClone) {
@ -1915,6 +1939,11 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
properties.setServerScripts(existingEntity->getServerScripts());
}
if (suppressDisallowedPrivateUserData) {
bumpTimestamp(properties);
properties.setPrivateUserData(existingEntity->getPrivateUserData());
}
// if the EntityItem exists, then update it
startLogging = usecTimestampNow();
if (wantEditLogging()) {

View file

@ -294,9 +294,7 @@ void ModelEntityItem::setModelURL(const QString& url) {
withWriteLock([&] {
if (_modelURL != url) {
_modelURL = url;
if (_shapeType == SHAPE_TYPE_STATIC_MESH) {
_flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
}
_flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
}
});
}
@ -329,11 +327,8 @@ const Transform ModelEntityItem::getTransform(bool& success, int depth) const {
void ModelEntityItem::setCompoundShapeURL(const QString& url) {
withWriteLock([&] {
if (_compoundShapeURL.get() != url) {
ShapeType oldType = computeTrueShapeType();
_compoundShapeURL.set(url);
if (oldType != computeTrueShapeType()) {
_flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
}
_flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
}
});
}

View file

@ -37,6 +37,28 @@
#include "FBXSerializer.h"
#define GLTF_GET_INDICIES(accCount) int index1 = (indices[n + 0] * accCount); int index2 = (indices[n + 1] * accCount); int index3 = (indices[n + 2] * accCount);
#define GLTF_APPEND_ARRAY_1(newArray, oldArray) GLTF_GET_INDICIES(1) \
newArray.append(oldArray[index1]); \
newArray.append(oldArray[index2]); \
newArray.append(oldArray[index3]);
#define GLTF_APPEND_ARRAY_2(newArray, oldArray) GLTF_GET_INDICIES(2) \
newArray.append(oldArray[index1]); newArray.append(oldArray[index1 + 1]); \
newArray.append(oldArray[index2]); newArray.append(oldArray[index2 + 1]); \
newArray.append(oldArray[index3]); newArray.append(oldArray[index3 + 1]);
#define GLTF_APPEND_ARRAY_3(newArray, oldArray) GLTF_GET_INDICIES(3) \
newArray.append(oldArray[index1]); newArray.append(oldArray[index1 + 1]); newArray.append(oldArray[index1 + 2]); \
newArray.append(oldArray[index2]); newArray.append(oldArray[index2 + 1]); newArray.append(oldArray[index2 + 2]); \
newArray.append(oldArray[index3]); newArray.append(oldArray[index3 + 1]); newArray.append(oldArray[index3 + 2]);
#define GLTF_APPEND_ARRAY_4(newArray, oldArray) GLTF_GET_INDICIES(4) \
newArray.append(oldArray[index1]); newArray.append(oldArray[index1 + 1]); newArray.append(oldArray[index1 + 2]); newArray.append(oldArray[index1 + 3]); \
newArray.append(oldArray[index2]); newArray.append(oldArray[index2 + 1]); newArray.append(oldArray[index2 + 2]); newArray.append(oldArray[index2 + 3]); \
newArray.append(oldArray[index3]); newArray.append(oldArray[index3 + 1]); newArray.append(oldArray[index3 + 2]); newArray.append(oldArray[index3 + 3]);
bool GLTFSerializer::getStringVal(const QJsonObject& object, const QString& fieldname,
QString& value, QMap<QString, bool>& defined) {
bool _defined = (object.contains(fieldname) && object[fieldname].isString());
@ -261,6 +283,41 @@ bool GLTFSerializer::setAsset(const QJsonObject& object) {
return isAssetDefined;
}
GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseIndices GLTFSerializer::createAccessorSparseIndices(const QJsonObject& object) {
GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseIndices accessorSparseIndices;
getIntVal(object, "bufferView", accessorSparseIndices.bufferView, accessorSparseIndices.defined);
getIntVal(object, "byteOffset", accessorSparseIndices.byteOffset, accessorSparseIndices.defined);
getIntVal(object, "componentType", accessorSparseIndices.componentType, accessorSparseIndices.defined);
return accessorSparseIndices;
}
GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseValues GLTFSerializer::createAccessorSparseValues(const QJsonObject& object) {
GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseValues accessorSparseValues;
getIntVal(object, "bufferView", accessorSparseValues.bufferView, accessorSparseValues.defined);
getIntVal(object, "byteOffset", accessorSparseValues.byteOffset, accessorSparseValues.defined);
return accessorSparseValues;
}
GLTFAccessor::GLTFAccessorSparse GLTFSerializer::createAccessorSparse(const QJsonObject& object) {
GLTFAccessor::GLTFAccessorSparse accessorSparse;
getIntVal(object, "count", accessorSparse.count, accessorSparse.defined);
QJsonObject sparseIndicesObject;
if (getObjectVal(object, "indices", sparseIndicesObject, accessorSparse.defined)) {
accessorSparse.indices = createAccessorSparseIndices(sparseIndicesObject);
}
QJsonObject sparseValuesObject;
if (getObjectVal(object, "values", sparseValuesObject, accessorSparse.defined)) {
accessorSparse.values = createAccessorSparseValues(sparseValuesObject);
}
return accessorSparse;
}
bool GLTFSerializer::addAccessor(const QJsonObject& object) {
GLTFAccessor accessor;
@ -273,6 +330,12 @@ bool GLTFSerializer::addAccessor(const QJsonObject& object) {
if (getStringVal(object, "type", type, accessor.defined)) {
accessor.type = getAccessorType(type);
}
QJsonObject sparseObject;
if (getObjectVal(object, "sparse", sparseObject, accessor.defined)) {
accessor.sparse = createAccessorSparse(sparseObject);
}
getDoubleArrayVal(object, "max", accessor.max, accessor.defined);
getDoubleArrayVal(object, "min", accessor.min, accessor.defined);
@ -749,32 +812,16 @@ glm::mat4 GLTFSerializer::getModelTransform(const GLTFNode& node) {
void GLTFSerializer::getSkinInverseBindMatrices(std::vector<std::vector<float>>& inverseBindMatrixValues) {
for (auto &skin : _file.skins) {
GLTFAccessor& indicesAccessor = _file.accessors[skin.inverseBindMatrices];
GLTFBufferView& indicesBufferview = _file.bufferviews[indicesAccessor.bufferView];
GLTFBuffer& indicesBuffer = _file.buffers[indicesBufferview.buffer];
int accBoffset = indicesAccessor.defined["byteOffset"] ? indicesAccessor.byteOffset : 0;
QVector<float> matrices;
addArrayOfType(indicesBuffer.blob,
indicesBufferview.byteOffset + accBoffset,
indicesAccessor.count,
matrices,
indicesAccessor.type,
indicesAccessor.componentType);
addArrayFromAccessor(indicesAccessor, matrices);
inverseBindMatrixValues.push_back(matrices.toStdVector());
}
}
void GLTFSerializer::generateTargetData(int index, float weight, QVector<glm::vec3>& returnVector) {
GLTFAccessor& accessor = _file.accessors[index];
GLTFBufferView& bufferview = _file.bufferviews[accessor.bufferView];
GLTFBuffer& buffer = _file.buffers[bufferview.buffer];
int accBoffset = accessor.defined["byteOffset"] ? accessor.byteOffset : 0;
QVector<float> storedValues;
addArrayOfType(buffer.blob,
bufferview.byteOffset + accBoffset,
accessor.count,
storedValues,
accessor.type,
accessor.componentType);
addArrayFromAccessor(accessor, storedValues);
for (int n = 0; n < storedValues.size(); n = n + 3) {
returnVector.push_back(glm::vec3(weight * storedValues[n], weight * storedValues[n + 1], weight * storedValues[n + 2]));
}
@ -783,7 +830,7 @@ void GLTFSerializer::generateTargetData(int index, float weight, QVector<glm::ve
bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& mapping, const hifi::URL& url) {
int numNodes = _file.nodes.size();
// Build dependencies
//Build dependencies
QVector<int> parents;
QVector<int> sortedNodes;
parents.fill(-1, numNodes);
@ -813,7 +860,7 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
nodecount++;
}
// since parent indices must exist in the sorted list before any of their children, sortedNodes might not be initialized in the correct order
// therefore we need to re-initialize the order in which nodes will be parsed
QVector<bool> hasBeenSorted;
@ -868,7 +915,7 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
joint.translation = extractTranslation(joint.transform);
joint.rotation = glmExtractRotation(joint.transform);
glm::vec3 scale = extractScale(joint.transform);
joint.postTransform = glm::scale(glm::mat4(), scale);
joint.postTransform = glm::scale(glm::mat4(), scale);
joint.name = node.name;
joint.isSkeletonJoint = false;
@ -915,12 +962,18 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
}
// Build materials
//Build materials
QVector<QString> materialIDs;
QString unknown = "Default";
int ukcount = 0;
foreach(auto material, _file.materials) {
QString mid = (material.defined["name"]) ? material.name : unknown + ukcount++;
if (!material.defined["name"]) {
QString name = unknown + QString::number(ukcount++);
material.name = name;
material.defined.insert("name", true);
}
QString mid = material.name;
materialIDs.push_back(mid);
}
@ -929,6 +982,7 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
hfmModel.materials[matid] = HFMMaterial();
HFMMaterial& hfmMaterial = hfmModel.materials[matid];
hfmMaterial._material = std::make_shared<graphics::Material>();
hfmMaterial.name = hfmMaterial.materialID = matid;
setHFMMaterial(hfmMaterial, _file.materials[i]);
}
@ -939,56 +993,76 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
auto& node = _file.nodes[nodeIndex];
if (node.defined["mesh"]) {
foreach(auto &primitive, _file.meshes[node.mesh].primitives) {
hfmModel.meshes.append(HFMMesh());
HFMMesh& mesh = hfmModel.meshes[hfmModel.meshes.size() - 1];
if (!hfmModel.hasSkeletonJoints) {
hfmModel.meshes.append(HFMMesh());
HFMMesh& mesh = hfmModel.meshes[hfmModel.meshes.size() - 1];
if (!hfmModel.hasSkeletonJoints) {
HFMCluster cluster;
cluster.jointIndex = nodecount;
cluster.inverseBindMatrix = glm::mat4();
cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix);
mesh.clusters.append(cluster);
} else { // skinned model
for (int j = 0; j < numNodes; j++) {
HFMCluster cluster;
cluster.jointIndex = nodecount;
cluster.inverseBindMatrix = glm::mat4();
cluster.jointIndex = j;
cluster.inverseBindMatrix = jointInverseBindTransforms[j];
cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix);
mesh.clusters.append(cluster);
} else { // skinned model
for (int j = 0; j < numNodes; j++) {
HFMCluster cluster;
cluster.jointIndex = j;
cluster.inverseBindMatrix = jointInverseBindTransforms[j];
cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix);
mesh.clusters.append(cluster);
}
}
HFMCluster root;
root.jointIndex = 0;
root.inverseBindMatrix = jointInverseBindTransforms[root.jointIndex];
root.inverseBindTransform = Transform(root.inverseBindMatrix);
mesh.clusters.append(root);
QList<QString> meshAttributes;
foreach(auto &primitive, _file.meshes[node.mesh].primitives) {
QList<QString> keys = primitive.attributes.values.keys();
foreach (auto &key, keys) {
if (!meshAttributes.contains(key)) {
meshAttributes.push_back(key);
}
}
HFMCluster root;
root.jointIndex = 0;
root.inverseBindMatrix = jointInverseBindTransforms[root.jointIndex];
root.inverseBindTransform = Transform(root.inverseBindMatrix);
mesh.clusters.append(root);
}
foreach(auto &primitive, _file.meshes[node.mesh].primitives) {
HFMMeshPart part = HFMMeshPart();
int indicesAccessorIdx = primitive.indices;
GLTFAccessor& indicesAccessor = _file.accessors[indicesAccessorIdx];
GLTFBufferView& indicesBufferview = _file.bufferviews[indicesAccessor.bufferView];
GLTFBuffer& indicesBuffer = _file.buffers[indicesBufferview.buffer];
int indicesAccBoffset = indicesAccessor.defined["byteOffset"] ? indicesAccessor.byteOffset : 0;
// Buffers
QVector<int> indices;
QVector<float> vertices;
int verticesStride = 3;
QVector<float> normals;
int normalStride = 3;
QVector<float> tangents;
int tangentStride = 4;
QVector<float> texcoords;
int texCoordStride = 2;
QVector<float> texcoords2;
int texCoord2Stride = 2;
QVector<float> colors;
int colorStride = 3;
QVector<uint16_t> joints;
int jointStride = 4;
QVector<float> weights;
int weightStride = 4;
QVector<int> raw_indices;
QVector<glm::vec3> raw_vertices;
QVector<glm::vec3> raw_normals;
bool success = addArrayOfType(indicesBuffer.blob,
indicesBufferview.byteOffset + indicesAccBoffset,
indicesAccessor.count,
part.triangleIndices,
indicesAccessor.type,
indicesAccessor.componentType);
bool success = addArrayFromAccessor(indicesAccessor, indices);
if (!success) {
qWarning(modelformat) << "There was a problem reading glTF INDICES data for model " << _url;
continue;
}
// Increment the triangle indices by the current mesh vertex count so each mesh part can all reference the same buffers within the mesh
int prevMeshVerticesCount = mesh.vertices.count();
QList<QString> keys = primitive.attributes.values.keys();
QVector<uint16_t> clusterJoints;
QVector<float> clusterWeights;
@ -997,147 +1071,391 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
int accessorIdx = primitive.attributes.values[key];
GLTFAccessor& accessor = _file.accessors[accessorIdx];
GLTFBufferView& bufferview = _file.bufferviews[accessor.bufferView];
GLTFBuffer& buffer = _file.buffers[bufferview.buffer];
int accBoffset = accessor.defined["byteOffset"] ? accessor.byteOffset : 0;
if (key == "POSITION") {
QVector<float> vertices;
success = addArrayOfType(buffer.blob,
bufferview.byteOffset + accBoffset,
accessor.count, vertices,
accessor.type,
accessor.componentType);
if (accessor.type != GLTFAccessorType::VEC3) {
qWarning(modelformat) << "Invalid accessor type on glTF POSITION data for model " << _url;
continue;
}
success = addArrayFromAccessor(accessor, vertices);
if (!success) {
qWarning(modelformat) << "There was a problem reading glTF POSITION data for model " << _url;
continue;
}
for (int n = 0; n < vertices.size(); n = n + 3) {
mesh.vertices.push_back(glm::vec3(vertices[n], vertices[n + 1], vertices[n + 2]));
}
} else if (key == "NORMAL") {
QVector<float> normals;
success = addArrayOfType(buffer.blob,
bufferview.byteOffset + accBoffset,
accessor.count,
normals,
accessor.type,
accessor.componentType);
if (accessor.type != GLTFAccessorType::VEC3) {
qWarning(modelformat) << "Invalid accessor type on glTF NORMAL data for model " << _url;
continue;
}
success = addArrayFromAccessor(accessor, normals);
if (!success) {
qWarning(modelformat) << "There was a problem reading glTF NORMAL data for model " << _url;
continue;
}
for (int n = 0; n < normals.size(); n = n + 3) {
mesh.normals.push_back(glm::vec3(normals[n], normals[n + 1], normals[n + 2]));
}
} else if (key == "COLOR_0") {
QVector<float> colors;
success = addArrayOfType(buffer.blob,
bufferview.byteOffset + accBoffset,
accessor.count,
colors,
accessor.type,
accessor.componentType);
if (!success) {
qWarning(modelformat) << "There was a problem reading glTF COLOR_0 data for model " << _url;
} else if (key == "TANGENT") {
if (accessor.type == GLTFAccessorType::VEC4) {
tangentStride = 4;
} else if (accessor.type == GLTFAccessorType::VEC3) {
tangentStride = 3;
} else {
qWarning(modelformat) << "Invalid accessor type on glTF TANGENT data for model " << _url;
continue;
}
int stride = (accessor.type == GLTFAccessorType::VEC4) ? 4 : 3;
for (int n = 0; n < colors.size() - 3; n += stride) {
mesh.colors.push_back(glm::vec3(colors[n], colors[n + 1], colors[n + 2]));
}
} else if (key == "TANGENT") {
QVector<float> tangents;
success = addArrayOfType(buffer.blob,
bufferview.byteOffset + accBoffset,
accessor.count,
tangents,
accessor.type,
accessor.componentType);
success = addArrayFromAccessor(accessor, tangents);
if (!success) {
qWarning(modelformat) << "There was a problem reading glTF TANGENT data for model " << _url;
tangentStride = 0;
continue;
}
// tangents can be a vec3 or a vec4 which includes a w component (of -1 or 1)
int stride = (accessor.type == GLTFAccessorType::VEC4) ? 4 : 3;
for (int n = 0; n < tangents.size() - 3; n += stride) {
float tanW = stride == 4 ? tangents[n + 3] : 1;
mesh.tangents.push_back(glm::vec3(tanW * tangents[n], tangents[n + 1], tanW * tangents[n + 2]));
}
} else if (key == "TEXCOORD_0") {
QVector<float> texcoords;
success = addArrayOfType(buffer.blob,
bufferview.byteOffset + accBoffset,
accessor.count,
texcoords,
accessor.type,
accessor.componentType);
success = addArrayFromAccessor(accessor, texcoords);
if (!success) {
qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_0 data for model " << _url;
continue;
}
for (int n = 0; n < texcoords.size(); n = n + 2) {
mesh.texCoords.push_back(glm::vec2(texcoords[n], texcoords[n + 1]));
if (accessor.type != GLTFAccessorType::VEC2) {
qWarning(modelformat) << "Invalid accessor type on glTF TEXCOORD_0 data for model " << _url;
continue;
}
} else if (key == "TEXCOORD_1") {
QVector<float> texcoords;
success = addArrayOfType(buffer.blob,
bufferview.byteOffset + accBoffset,
accessor.count,
texcoords,
accessor.type,
accessor.componentType);
success = addArrayFromAccessor(accessor, texcoords2);
if (!success) {
qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_1 data for model " << _url;
continue;
}
for (int n = 0; n < texcoords.size(); n = n + 2) {
mesh.texCoords1.push_back(glm::vec2(texcoords[n], texcoords[n + 1]));
if (accessor.type != GLTFAccessorType::VEC2) {
qWarning(modelformat) << "Invalid accessor type on glTF TEXCOORD_1 data for model " << _url;
continue;
}
} else if (key == "COLOR_0") {
if (accessor.type == GLTFAccessorType::VEC4) {
colorStride = 4;
} else if (accessor.type == GLTFAccessorType::VEC3) {
colorStride = 3;
} else {
qWarning(modelformat) << "Invalid accessor type on glTF COLOR_0 data for model " << _url;
continue;
}
success = addArrayFromAccessor(accessor, colors);
if (!success) {
qWarning(modelformat) << "There was a problem reading glTF COLOR_0 data for model " << _url;
continue;
}
} else if (key == "JOINTS_0") {
QVector<uint16_t> joints;
success = addArrayOfType(buffer.blob,
bufferview.byteOffset + accBoffset,
accessor.count,
joints,
accessor.type,
accessor.componentType);
if (accessor.type == GLTFAccessorType::VEC4) {
jointStride = 4;
} else if (accessor.type == GLTFAccessorType::VEC3) {
jointStride = 3;
} else if (accessor.type == GLTFAccessorType::VEC2) {
jointStride = 2;
} else if (accessor.type == GLTFAccessorType::SCALAR) {
jointStride = 1;
} else {
qWarning(modelformat) << "Invalid accessor type on glTF JOINTS_0 data for model " << _url;
continue;
}
success = addArrayFromAccessor(accessor, joints);
if (!success) {
qWarning(modelformat) << "There was a problem reading glTF JOINTS_0 data for model " << _url;
continue;
}
for (int n = 0; n < joints.size(); n++) {
clusterJoints.push_back(joints[n]);
}
} else if (key == "WEIGHTS_0") {
QVector<float> weights;
success = addArrayOfType(buffer.blob,
bufferview.byteOffset + accBoffset,
accessor.count,
weights,
accessor.type,
accessor.componentType);
if (accessor.type == GLTFAccessorType::VEC4) {
weightStride = 4;
} else if (accessor.type == GLTFAccessorType::VEC3) {
weightStride = 3;
} else if (accessor.type == GLTFAccessorType::VEC2) {
weightStride = 2;
} else if (accessor.type == GLTFAccessorType::SCALAR) {
weightStride = 1;
} else {
qWarning(modelformat) << "Invalid accessor type on glTF WEIGHTS_0 data for model " << _url;
continue;
}
success = addArrayFromAccessor(accessor, weights);
if (!success) {
qWarning(modelformat) << "There was a problem reading glTF WEIGHTS_0 data for model " << _url;
continue;
}
for (int n = 0; n < weights.size(); n++) {
clusterWeights.push_back(weights[n]);
}
}
// Validation stage
if (indices.count() == 0) {
qWarning(modelformat) << "Missing indices for model " << _url;
continue;
}
if (vertices.count() == 0) {
qWarning(modelformat) << "Missing vertices for model " << _url;
continue;
}
int partVerticesCount = vertices.size() / 3;
// generate the normals if they don't exist
if (normals.size() == 0) {
QVector<int> newIndices;
QVector<float> newVertices;
QVector<float> newNormals;
QVector<float> newTexcoords;
QVector<float> newTexcoords2;
QVector<float> newColors;
QVector<uint16_t> newJoints;
QVector<float> newWeights;
for (int n = 0; n < indices.size(); n = n + 3) {
int v1_index = (indices[n + 0] * 3);
int v2_index = (indices[n + 1] * 3);
int v3_index = (indices[n + 2] * 3);
glm::vec3 v1 = glm::vec3(vertices[v1_index], vertices[v1_index + 1], vertices[v1_index + 2]);
glm::vec3 v2 = glm::vec3(vertices[v2_index], vertices[v2_index + 1], vertices[v2_index + 2]);
glm::vec3 v3 = glm::vec3(vertices[v3_index], vertices[v3_index + 1], vertices[v3_index + 2]);
newVertices.append(v1.x);
newVertices.append(v1.y);
newVertices.append(v1.z);
newVertices.append(v2.x);
newVertices.append(v2.y);
newVertices.append(v2.z);
newVertices.append(v3.x);
newVertices.append(v3.y);
newVertices.append(v3.z);
glm::vec3 norm = glm::normalize(glm::cross(v2 - v1, v3 - v1));
newNormals.append(norm.x);
newNormals.append(norm.y);
newNormals.append(norm.z);
newNormals.append(norm.x);
newNormals.append(norm.y);
newNormals.append(norm.z);
newNormals.append(norm.x);
newNormals.append(norm.y);
newNormals.append(norm.z);
if (texcoords.size() == partVerticesCount * texCoordStride) {
GLTF_APPEND_ARRAY_2(newTexcoords, texcoords)
}
if (texcoords2.size() == partVerticesCount * texCoord2Stride) {
GLTF_APPEND_ARRAY_2(newTexcoords2, texcoords2)
}
if (colors.size() == partVerticesCount * colorStride) {
if (colorStride == 4) {
GLTF_APPEND_ARRAY_4(newColors, colors)
} else {
GLTF_APPEND_ARRAY_3(newColors, colors)
}
}
if (joints.size() == partVerticesCount * jointStride) {
if (jointStride == 4) {
GLTF_APPEND_ARRAY_4(newJoints, joints)
} else if (jointStride == 3) {
GLTF_APPEND_ARRAY_3(newJoints, joints)
} else if (jointStride == 2) {
GLTF_APPEND_ARRAY_2(newJoints, joints)
} else {
GLTF_APPEND_ARRAY_1(newJoints, joints)
}
}
if (weights.size() == partVerticesCount * weightStride) {
if (weightStride == 4) {
GLTF_APPEND_ARRAY_4(newWeights, weights)
} else if (weightStride == 3) {
GLTF_APPEND_ARRAY_3(newWeights, weights)
} else if (weightStride == 2) {
GLTF_APPEND_ARRAY_2(newWeights, weights)
} else {
GLTF_APPEND_ARRAY_1(newWeights, weights)
}
}
newIndices.append(n);
newIndices.append(n + 1);
newIndices.append(n + 2);
}
vertices = newVertices;
normals = newNormals;
tangents = QVector<float>();
texcoords = newTexcoords;
texcoords2 = newTexcoords2;
colors = newColors;
joints = newJoints;
weights = newWeights;
indices = newIndices;
partVerticesCount = vertices.size() / 3;
}
QVector<int> validatedIndices;
for (int n = 0; n < indices.count(); n++) {
if (indices[n] < partVerticesCount) {
validatedIndices.push_back(indices[n] + prevMeshVerticesCount);
} else {
validatedIndices = QVector<int>();
break;
}
}
if (validatedIndices.size() == 0) {
qWarning(modelformat) << "Indices out of range for model " << _url;
continue;
}
part.triangleIndices.append(validatedIndices);
for (int n = 0; n < vertices.size(); n = n + verticesStride) {
mesh.vertices.push_back(glm::vec3(vertices[n], vertices[n + 1], vertices[n + 2]));
}
for (int n = 0; n < normals.size(); n = n + normalStride) {
mesh.normals.push_back(glm::vec3(normals[n], normals[n + 1], normals[n + 2]));
}
// TODO: add correct tangent generation
if (tangents.size() == partVerticesCount * tangentStride) {
for (int n = 0; n < tangents.size(); n += tangentStride) {
float tanW = tangentStride == 4 ? tangents[n + 3] : 1;
mesh.tangents.push_back(glm::vec3(tanW * tangents[n], tangents[n + 1], tanW * tangents[n + 2]));
}
} else {
if (meshAttributes.contains("TANGENT")) {
for (int i = 0; i < partVerticesCount; i++) {
mesh.tangents.push_back(glm::vec3(0.0f, 0.0f, 0.0f));
}
}
}
if (texcoords.size() == partVerticesCount * texCoordStride) {
for (int n = 0; n < texcoords.size(); n = n + 2) {
mesh.texCoords.push_back(glm::vec2(texcoords[n], texcoords[n + 1]));
}
} else {
if (meshAttributes.contains("TEXCOORD_0")) {
for (int i = 0; i < partVerticesCount; i++) {
mesh.texCoords.push_back(glm::vec2(0.0f, 0.0f));
}
}
}
if (texcoords2.size() == partVerticesCount * texCoord2Stride) {
for (int n = 0; n < texcoords2.size(); n = n + 2) {
mesh.texCoords1.push_back(glm::vec2(texcoords2[n], texcoords2[n + 1]));
}
} else {
if (meshAttributes.contains("TEXCOORD_1")) {
for (int i = 0; i < partVerticesCount; i++) {
mesh.texCoords1.push_back(glm::vec2(0.0f, 0.0f));
}
}
}
if (colors.size() == partVerticesCount * colorStride) {
for (int n = 0; n < colors.size(); n += colorStride) {
mesh.colors.push_back(glm::vec3(colors[n], colors[n + 1], colors[n + 2]));
}
} else {
if (meshAttributes.contains("COLOR_0")) {
for (int i = 0; i < partVerticesCount; i++) {
mesh.colors.push_back(glm::vec3(1.0f, 1.0f, 1.0f));
}
}
}
if (joints.size() == partVerticesCount * jointStride) {
for (int n = 0; n < joints.size(); n += jointStride) {
clusterJoints.push_back(joints[n]);
if (jointStride > 1) {
clusterJoints.push_back(joints[n + 1]);
if (jointStride > 2) {
clusterJoints.push_back(joints[n + 2]);
if (jointStride > 3) {
clusterJoints.push_back(joints[n + 3]);
} else {
clusterJoints.push_back(0);
}
} else {
clusterJoints.push_back(0);
clusterJoints.push_back(0);
}
} else {
clusterJoints.push_back(0);
clusterJoints.push_back(0);
clusterJoints.push_back(0);
}
}
} else {
if (meshAttributes.contains("JOINTS_0")) {
for (int i = 0; i < partVerticesCount; i++) {
for (int j = 0; j < 4; j++) {
clusterJoints.push_back(0);
}
}
}
}
if (weights.size() == partVerticesCount * weightStride) {
for (int n = 0; n < weights.size(); n += weightStride) {
clusterWeights.push_back(weights[n]);
if (weightStride > 1) {
clusterWeights.push_back(weights[n + 1]);
if (weightStride > 2) {
clusterWeights.push_back(weights[n + 2]);
if (weightStride > 3) {
clusterWeights.push_back(weights[n + 3]);
} else {
clusterWeights.push_back(0.0f);
}
} else {
clusterWeights.push_back(0.0f);
clusterWeights.push_back(0.0f);
}
} else {
clusterWeights.push_back(0.0f);
clusterWeights.push_back(0.0f);
clusterWeights.push_back(0.0f);
}
}
} else {
if (meshAttributes.contains("WEIGHTS_0")) {
for (int i = 0; i < partVerticesCount; i++) {
clusterWeights.push_back(1.0f);
for (int j = 1; j < 4; j++) {
clusterWeights.push_back(0.0f);
}
}
}
}
// Build weights (adapted from FBXSerializer.cpp)
if (hfmModel.hasSkeletonJoints) {
int numClusterIndices = clusterJoints.size();
int prevMeshClusterIndexCount = mesh.clusterIndices.count();
int prevMeshClusterWeightCount = mesh.clusterWeights.count();
const int WEIGHTS_PER_VERTEX = 4;
const float ALMOST_HALF = 0.499f;
int numVertices = mesh.vertices.size();
mesh.clusterIndices.fill(mesh.clusters.size() - 1, numClusterIndices);
mesh.clusterWeights.fill(0, numClusterIndices);
int numVertices = mesh.vertices.size() - prevMeshVerticesCount;
// Append new cluster indices and weights for this mesh part
for (int i = 0; i < numVertices * WEIGHTS_PER_VERTEX; i++) {
mesh.clusterIndices.push_back(mesh.clusters.size() - 1);
mesh.clusterWeights.push_back(0);
}
for (int c = 0; c < clusterJoints.size(); c++) {
mesh.clusterIndices[c] = originalToNewNodeIndexMap[_file.skins[node.skin].joints[clusterJoints[c]]];
mesh.clusterIndices[prevMeshClusterIndexCount + c] =
originalToNewNodeIndexMap[_file.skins[node.skin].joints[clusterJoints[c]]];
}
// normalize and compress to 16-bits
@ -1151,10 +1469,10 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
if (totalWeight > 0.0f) {
float weightScalingFactor = (float)(UINT16_MAX) / totalWeight;
for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) {
mesh.clusterWeights[k] = (uint16_t)(weightScalingFactor * clusterWeights[k] + ALMOST_HALF);
mesh.clusterWeights[prevMeshClusterWeightCount + k] = (uint16_t)(weightScalingFactor * clusterWeights[k] + ALMOST_HALF);
}
} else {
mesh.clusterWeights[j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF);
mesh.clusterWeights[prevMeshClusterWeightCount + j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF);
}
}
}
@ -1259,6 +1577,20 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
mesh.meshIndex = hfmModel.meshes.size();
}
mesh.meshExtents.reset();
foreach(const glm::vec3& vertex, mesh.vertices) {
mesh.meshExtents.addPoint(vertex);
hfmModel.meshExtents.addPoint(vertex);
}
// Add epsilon to mesh extents to compensate for planar meshes
mesh.meshExtents.minimum -= glm::vec3(EPSILON, EPSILON, EPSILON);
mesh.meshExtents.maximum += glm::vec3(EPSILON, EPSILON, EPSILON);
hfmModel.meshExtents.minimum -= glm::vec3(EPSILON, EPSILON, EPSILON);
hfmModel.meshExtents.maximum += glm::vec3(EPSILON, EPSILON, EPSILON);
mesh.meshIndex = hfmModel.meshes.size();
}
nodecount++;
}
@ -1412,10 +1744,6 @@ HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) {
void GLTFSerializer::setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& material) {
if (material.defined["name"]) {
fbxmat.name = fbxmat.materialID = material.name;
}
if (material.defined["emissiveFactor"] && material.emissiveFactor.size() == 3) {
glm::vec3 emissive = glm::vec3(material.emissiveFactor[0],
material.emissiveFactor[1],
@ -1552,7 +1880,74 @@ bool GLTFSerializer::addArrayOfType(const hifi::ByteArray& bin, int byteOffset,
return false;
}
void GLTFSerializer::retriangulate(const QVector<int>& inIndices, const QVector<glm::vec3>& in_vertices,
template <typename T>
bool GLTFSerializer::addArrayFromAccessor(GLTFAccessor& accessor, QVector<T>& outarray) {
bool success = true;
if (accessor.defined["bufferView"]) {
GLTFBufferView& bufferview = _file.bufferviews[accessor.bufferView];
GLTFBuffer& buffer = _file.buffers[bufferview.buffer];
int accBoffset = accessor.defined["byteOffset"] ? accessor.byteOffset : 0;
success = addArrayOfType(buffer.blob, bufferview.byteOffset + accBoffset, accessor.count, outarray, accessor.type,
accessor.componentType);
} else {
for (int i = 0; i < accessor.count; i++) {
T value;
memset(&value, 0, sizeof(T)); // Make sure the dummy array is initalised to zero.
outarray.push_back(value);
}
}
if (success) {
if (accessor.defined["sparse"]) {
QVector<int> out_sparse_indices_array;
GLTFBufferView& sparseIndicesBufferview = _file.bufferviews[accessor.sparse.indices.bufferView];
GLTFBuffer& sparseIndicesBuffer = _file.buffers[sparseIndicesBufferview.buffer];
int accSIBoffset = accessor.sparse.indices.defined["byteOffset"] ? accessor.sparse.indices.byteOffset : 0;
success = addArrayOfType(sparseIndicesBuffer.blob, sparseIndicesBufferview.byteOffset + accSIBoffset,
accessor.sparse.count, out_sparse_indices_array, GLTFAccessorType::SCALAR,
accessor.sparse.indices.componentType);
if (success) {
QVector<T> out_sparse_values_array;
GLTFBufferView& sparseValuesBufferview = _file.bufferviews[accessor.sparse.values.bufferView];
GLTFBuffer& sparseValuesBuffer = _file.buffers[sparseValuesBufferview.buffer];
int accSVBoffset = accessor.sparse.values.defined["byteOffset"] ? accessor.sparse.values.byteOffset : 0;
success = addArrayOfType(sparseValuesBuffer.blob, sparseValuesBufferview.byteOffset + accSVBoffset,
accessor.sparse.count, out_sparse_values_array, accessor.type, accessor.componentType);
if (success) {
for (int i = 0; i < accessor.sparse.count; i++) {
if ((i * 3) + 2 < out_sparse_values_array.size()) {
if ((out_sparse_indices_array[i] * 3) + 2 < outarray.length()) {
for (int j = 0; j < 3; j++) {
outarray[(out_sparse_indices_array[i] * 3) + j] = out_sparse_values_array[(i * 3) + j];
}
} else {
success = false;
break;
}
} else {
success = false;
break;
}
}
}
}
}
}
return success;
}
void GLTFSerializer::retriangulate(const QVector<int>& inIndices, const QVector<glm::vec3>& in_vertices,
const QVector<glm::vec3>& in_normals, QVector<int>& outIndices,
QVector<glm::vec3>& out_vertices, QVector<glm::vec3>& out_normals) {
for (int i = 0; i < inIndices.size(); i = i + 3) {
@ -1717,7 +2112,7 @@ void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) {
qCDebug(modelformat) << "---------------- Joints ----------------";
foreach(HFMJoint joint, hfmModel.joints) {
foreach (HFMJoint joint, hfmModel.joints) {
qCDebug(modelformat) << "\n";
qCDebug(modelformat) << " shapeInfo.avgPoint =" << joint.shapeInfo.avgPoint;
qCDebug(modelformat) << " shapeInfo.debugLines =" << joint.shapeInfo.debugLines;

View file

@ -481,6 +481,49 @@ namespace GLTFAccessorComponentType {
};
}
struct GLTFAccessor {
struct GLTFAccessorSparse {
struct GLTFAccessorSparseIndices {
int bufferView;
int byteOffset{ 0 };
int componentType;
QMap<QString, bool> defined;
void dump() {
if (defined["bufferView"]) {
qCDebug(modelformat) << "bufferView: " << bufferView;
}
if (defined["byteOffset"]) {
qCDebug(modelformat) << "byteOffset: " << byteOffset;
}
if (defined["componentType"]) {
qCDebug(modelformat) << "componentType: " << componentType;
}
}
};
struct GLTFAccessorSparseValues {
int bufferView;
int byteOffset{ 0 };
QMap<QString, bool> defined;
void dump() {
if (defined["bufferView"]) {
qCDebug(modelformat) << "bufferView: " << bufferView;
}
if (defined["byteOffset"]) {
qCDebug(modelformat) << "byteOffset: " << byteOffset;
}
}
};
int count;
GLTFAccessorSparseIndices indices;
GLTFAccessorSparseValues values;
QMap<QString, bool> defined;
void dump() {
}
};
int bufferView;
int byteOffset { 0 };
int componentType; //required
@ -489,6 +532,7 @@ struct GLTFAccessor {
bool normalized{ false };
QVector<double> max;
QVector<double> min;
GLTFAccessorSparse sparse;
QMap<QString, bool> defined;
void dump() {
if (defined["bufferView"]) {
@ -521,6 +565,10 @@ struct GLTFAccessor {
qCDebug(modelformat) << m;
}
}
if (defined["sparse"]) {
qCDebug(modelformat) << "sparse: ";
sparse.dump();
}
}
};
@ -763,6 +811,11 @@ private:
int& outidx, QMap<QString, bool>& defined);
bool setAsset(const QJsonObject& object);
GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseIndices createAccessorSparseIndices(const QJsonObject& object);
GLTFAccessor::GLTFAccessorSparse::GLTFAccessorSparseValues createAccessorSparseValues(const QJsonObject& object);
GLTFAccessor::GLTFAccessorSparse createAccessorSparse(const QJsonObject& object);
bool addAccessor(const QJsonObject& object);
bool addAnimation(const QJsonObject& object);
bool addBufferView(const QJsonObject& object);
@ -782,11 +835,14 @@ private:
template<typename T, typename L>
bool readArray(const hifi::ByteArray& bin, int byteOffset, int count,
QVector<L>& outarray, int accessorType);
template<typename T>
bool addArrayOfType(const hifi::ByteArray& bin, int byteOffset, int count,
QVector<T>& outarray, int accessorType, int componentType);
template <typename T>
bool addArrayFromAccessor(GLTFAccessor& accessor, QVector<T>& outarray);
void retriangulate(const QVector<int>& in_indices, const QVector<glm::vec3>& in_vertices,
const QVector<glm::vec3>& in_normals, QVector<int>& out_indices,
QVector<glm::vec3>& out_vertices, QVector<glm::vec3>& out_normals);

View file

@ -64,6 +64,32 @@ const std::string TextureCache::KTX_DIRNAME{ "ktx_cache" };
#endif
const std::string TextureCache::KTX_EXT { "ktx" };
/**jsdoc
* <p>The views that may be visible on the PC display.</p>
* <table>
* <thead>
* <tr>
* <th>Value</th>
* <th>View Displayed</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td><code>""</code></td>
* <td>Normal view.</td>
* </tr>
* <tr>
* <td><code>"resource://hmdPreviewFrame"</code></td>
* <td>HMD preview.</td>
* </tr>
* <tr>
* <td><code>"resource://spectatorCameraFrame"</code></td>
* <td>Entity camera view.</td>
* </tr>
* </tbody>
* </table>
* @typedef {string} Window.DisplayTexture
*/
static const QString RESOURCE_SCHEME = "resource";
static const QUrl SPECTATOR_CAMERA_FRAME_URL("resource://spectatorCameraFrame");
static const QUrl HMD_PREVIEW_FRAME_URL("resource://hmdPreviewFrame");

View file

@ -32,11 +32,11 @@ const QString INDEX_PATH = "/";
const QString GET_PLACE = "/api/v1/places/%1";
/**jsdoc
* The location API provides facilities related to your current location in the metaverse.
* The <code>location</code> API provides facilities related to your current location in the metaverse.
*
* <h5>Getter/Setter</h5>
* <h3>Getter/Setter</h3>
* <p>You can get and set your current metaverse address by directly reading a string value from and writing a string value to
* the <code>location</code> object. This is an alternative to using the <code>location.href</code> property or this object's
* the <code>location</code> object. This is an alternative to using the <code>location.href</code> property or other object
* functions.</p>
*
* @namespace location
@ -186,19 +186,19 @@ public:
public slots:
/**jsdoc
* Go to a specified metaverse address.
* Takes you to a specified metaverse address.
* @function location.handleLookupString
* @param {string} address - The address to go to: a <code>"hifi://"<code> address, an IP address (e.g.,
* <code>"127.0.0.1"</code> or <code>"localhost"</code>), a domain name, a named path on a domain (starts with
* <code>"/"</code>), a position or position and orientation, or a user (starts with <code>"@"</code>).
* @param {boolean} fromSuggestions=false - Set to <code>true</code> if the address is obtained from the "Goto" dialog.
* @param {boolean} [fromSuggestions=false] - Set to <code>true</code> if the address is obtained from the "Goto" dialog.
* Helps ensure that user's location history is correctly maintained.
*/
void handleLookupString(const QString& lookupString, bool fromSuggestions = false);
/**jsdoc
* Go to a position and orientation resulting from a lookup for a named path in the domain (set in the domain server's
* settings).
* Takes you to a position and orientation resulting from a lookup for a named path in the domain (set in the domain
* server's settings).
* @function location.goToViewpointForPath
* @param {string} path - The position and orientation corresponding to the named path.
* @param {string} namedPath - The named path that was looked up on the server.
@ -212,29 +212,29 @@ public slots:
{ return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString); }
/**jsdoc
* Go back to the previous location in your navigation history, if there is one.
* Takes you back to the previous location in your navigation history, if there is one.
* @function location.goBack
*/
void goBack();
/**jsdoc
* Go forward to the next location in your navigation history, if there is one.
* Takes you forward to the next location in your navigation history, if there is one.
* @function location.goForward
*/
void goForward();
/**jsdoc
* Go to the local Sandbox server that's running on the same PC as Interface.
* Takes you to the local Sandbox server that's running on the same PC as Interface.
* @function location.goToLocalSandbox
* @param {string} path="" - The position and orientation to go to (e.g., <code>"/0,0,0"</code>).
* @param {location.LookupTrigger} trigger=StartupFromSettings - The reason for the function call. Helps ensure that user's
* @param {string} [path=""] - The position and orientation to go to (e.g., <code>"/0,0,0"</code>).
* @param {location.LookupTrigger} [trigger=StartupFromSettings] - The reason for the function call. Helps ensure that user's
* location history is correctly maintained.
*/
void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) {
handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); }
/**jsdoc
* Go to the default "welcome" metaverse address.
* Takes you to the default "welcome" metaverse address.
* @function location.goToEntry
* @param {location.LookupTrigger} trigger=StartupFromSettings - The reason for the function call. Helps ensure that user's
* location history is correctly maintained.
@ -242,28 +242,29 @@ public slots:
void goToEntry(LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(DEFAULT_HIFI_ADDRESS, trigger); }
/**jsdoc
* Go to the specified user's location.
* Takes you to the specified user's location.
* @function location.goToUser
* @param {string} username - The user's username.
* @param {boolean} matchOrientation=true - If <code>true</code> then go to a location just in front of the user and turn to face
* them, otherwise go to the user's exact location and orientation.
* @param {boolean} [matchOrientation=true] - If <code>true</code> then go to a location just in front of the user and turn
* to face them, otherwise go to the user's exact location and orientation.
*/
void goToUser(const QString& username, bool shouldMatchOrientation = true);
/**jsdoc
* Go to the last address tried. This will be the last URL tried from location.handleLookupString
* @function location.goToLastAddress
*/
* Takes you to the last address tried. This will be the last URL tried from <code>location.handleLookupString</code>.
* @function location.goToLastAddress
*/
void goToLastAddress() { handleUrl(_lastVisitedURL, LookupTrigger::AttemptedRefresh); }
/**jsdoc
* Returns if going back is possible.
* @function location.canGoBack
*/
* Checks if going back to the previous location is possible.
* @function location.canGoBack
* @returns <code>true</code> if going back is possible, <code>false</code> if it isn't.
*/
bool canGoBack() const;
/**jsdoc
* Refresh the current address, e.g., after connecting to a domain in order to position the user to the desired location.
* Refreshes the current address, e.g., after connecting to a domain in order to position the user to the desired location.
* @function location.refreshPreviousLookup
* @deprecated This function is deprecated and will be removed.
*/
@ -272,27 +273,27 @@ public slots:
void refreshPreviousLookup();
/**jsdoc
* Update your current metaverse location in Interface's {@link Settings} file as your last-known address. This can be used
* Updates your current metaverse location in Interface's {@link Settings} file as your last-known address. This can be used
* to ensure that you start up at that address if you exit Interface without a later address automatically being saved.
* @function location.storeCurrentAddress
*/
void storeCurrentAddress();
/**jsdoc
* Copy your current metaverse address (i.e., <code>location.href</code> property value) to the OS clipboard.
* Copies your current metaverse address (i.e., <code>location.href</code> property value) to the OS clipboard.
* @function location.copyAddress
*/
void copyAddress();
/**jsdoc
* Copy your current metaverse location and orientation (i.e., <code>location.pathname</code> property value) to the OS
* Copies your current metaverse location and orientation (i.e., <code>location.pathname</code> property value) to the OS
* clipboard.
* @function location.copyPath
*/
void copyPath();
/**jsdoc
* Retrieve and remember the place name for the given domain ID if the place name is not already known.
* Retrieves and remembers the place name for the given domain ID if the place name is not already known.
* @function location.lookupShareableNameForDomainID
* @param {Uuid} domainID - The UUID of the domain.
* @deprecated This function is deprecated and will be removed.

View file

@ -567,9 +567,19 @@ bool DomainHandler::checkInPacketTimeout() {
}
if (_checkInPacketsSinceLastReply > MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
auto nodeList = DependencyManager::get<NodeList>();
// we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS
// so emit our signal that says that
qCDebug(networking_ice) << "Limit of silent domain checkins reached";
#ifdef DEBUG_EVENT_QUEUE
int nodeListQueueSize = ::hifi::qt::getEventQueueSize(nodeList->thread());
qCDebug(networking) << "Limit of silent domain checkins reached (network qt queue: " << nodeListQueueSize << ")";
#else // DEBUG_EVENT_QUEUE
qCDebug(networking) << "Limit of silent domain checkins reached";
#endif // DEBUG_EVENT_QUEUE
emit limitOfSilentDomainCheckInsReached();
return true;
} else {

View file

@ -197,6 +197,10 @@ void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) {
newPermissions.can(NodePermissions::Permission::canReplaceDomainContent)) {
emit canReplaceContentChanged(_permissions.can(NodePermissions::Permission::canReplaceDomainContent));
}
if (originalPermissions.can(NodePermissions::Permission::canGetAndSetPrivateUserData) !=
newPermissions.can(NodePermissions::Permission::canGetAndSetPrivateUserData)) {
emit canGetAndSetPrivateUserDataChanged(_permissions.can(NodePermissions::Permission::canGetAndSetPrivateUserData));
}
}
void LimitedNodeList::setSocketLocalPort(quint16 socketLocalPort) {

View file

@ -124,6 +124,7 @@ public:
bool getThisNodeCanWriteAssets() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); }
bool getThisNodeCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); }
bool getThisNodeCanReplaceContent() const { return _permissions.can(NodePermissions::Permission::canReplaceDomainContent); }
bool getThisNodeCanGetAndSetPrivateUserData() const { return _permissions.can(NodePermissions::Permission::canGetAndSetPrivateUserData); }
quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); }
Q_INVOKABLE void setSocketLocalPort(quint16 socketLocalPort);
@ -368,6 +369,7 @@ signals:
void canWriteAssetsChanged(bool canWriteAssets);
void canKickChanged(bool canKick);
void canReplaceContentChanged(bool canReplaceContent);
void canGetAndSetPrivateUserDataChanged(bool canGetAndSetPrivateUserData);
protected slots:
void connectedForLocalSocketTest();

View file

@ -83,6 +83,7 @@ public:
bool getCanWriteToAssetServer() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); }
bool getCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); }
bool getCanReplaceContent() const { return _permissions.can(NodePermissions::Permission::canReplaceDomainContent); }
bool getCanGetAndSetPrivateUserData() const { return _permissions.can(NodePermissions::Permission::canGetAndSetPrivateUserData); }
using NodesIgnoredPair = std::pair<std::vector<QUuid>, bool>;

View file

@ -67,6 +67,7 @@ NodePermissions::NodePermissions(QMap<QString, QVariant> perms) {
Permission::canConnectPastMaxCapacity : Permission::none;
permissions |= perms["id_can_kick"].toBool() ? Permission::canKick : Permission::none;
permissions |= perms["id_can_replace_content"].toBool() ? Permission::canReplaceDomainContent : Permission::none;
permissions |= perms["id_can_get_and_set_private_user_data"].toBool() ? Permission::canGetAndSetPrivateUserData : Permission::none;
}
QVariant NodePermissions::toVariant(QHash<QUuid, GroupRank> groupRanks) {
@ -94,6 +95,7 @@ QVariant NodePermissions::toVariant(QHash<QUuid, GroupRank> groupRanks) {
values["id_can_connect_past_max_capacity"] = can(Permission::canConnectPastMaxCapacity);
values["id_can_kick"] = can(Permission::canKick);
values["id_can_replace_content"] = can(Permission::canReplaceDomainContent);
values["id_can_get_and_set_private_user_data"] = can(Permission::canGetAndSetPrivateUserData);
return QVariant(values);
}
@ -166,6 +168,9 @@ QDebug operator<<(QDebug debug, const NodePermissions& perms) {
if (perms.can(NodePermissions::Permission::canReplaceDomainContent)) {
debug << " can_replace_content";
}
if (perms.can(NodePermissions::Permission::canGetAndSetPrivateUserData)) {
debug << " get-and-set-private-user-data";
}
debug.nospace() << "]";
return debug.nospace();
}

View file

@ -75,7 +75,8 @@ public:
canKick = 64,
canReplaceDomainContent = 128,
canRezPermanentCertifiedEntities = 256,
canRezTemporaryCertifiedEntities = 512
canRezTemporaryCertifiedEntities = 512,
canGetAndSetPrivateUserData = 1024
};
Q_DECLARE_FLAGS(Permissions, Permission)
Permissions permissions;

View file

@ -142,8 +142,10 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
if (wantExtraDebugging) {
qCDebug(networking) << "found it in _missingSet";
}
_stats._lost--;
_stats._recovered++;
if (_stats._lost > 0) {
_stats._lost--;
_stats._recovered++;
}
} else {
// this late seq num is not in our missing set. it is possibly a duplicate, or possibly a late
// packet that should have arrived before our first received packet. we'll count these

View file

@ -18,6 +18,7 @@
#include <QtCore/QTimer>
#include <LogHandler.h>
#include <shared/QtHelpers.h>
#include "NetworkLogging.h"
@ -94,6 +95,10 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy
void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject statsObject) {
auto nodeList = DependencyManager::get<NodeList>();
#ifdef DEBUG_EVENT_QUEUE
statsObject["nodelist_event_queue_size"] = ::hifi::qt::getEventQueueSize(nodeList->thread());
#endif
QJsonObject ioStats;
ioStats["inbound_kbps"] = nodeList->getInboundKbps();
ioStats["inbound_pps"] = nodeList->getInboundPPS();

View file

@ -270,6 +270,7 @@ enum class EntityVersion : PacketVersion {
DisableWebMedia,
ParticleShapeType,
ParticleShapeTypeDeadlockFix,
PrivateUserData,
// Add new versions above here
NUM_PACKET_TYPE,

View file

@ -249,12 +249,19 @@ void PhysicalEntitySimulation::buildMotionStatesForEntitiesThatNeedThem() {
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShapeByKey(requestItr->shapeHash));
if (shape) {
// shape is ready at last!
// But the entity's desired shape might have changed since last requested
// --> rebuild the ShapeInfo to verify hash
// but the entity's physics desired physics status may have changed since last requested
if (!entity->shouldBePhysical()) {
requestItr = _shapeRequests.erase(requestItr);
continue;
}
// rebuild the ShapeInfo to verify hash because entity's desired shape may have changed
// TODO? is there a better way to do this?
ShapeInfo shapeInfo;
entity->computeShapeInfo(shapeInfo);
if (shapeInfo.getHash() != requestItr->shapeHash) {
if (shapeInfo.getType() == SHAPE_TYPE_NONE) {
requestItr = _shapeRequests.erase(requestItr);
} else if (shapeInfo.getHash() != requestItr->shapeHash) {
// bummer, the hashes are different and we no longer want the shape we've received
ObjectMotionState::getShapeManager()->releaseShape(shape);
// try again

View file

@ -49,11 +49,15 @@ const btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
const btCollisionShape* shape = nullptr;
if (info.getType() == SHAPE_TYPE_STATIC_MESH) {
uint64_t hash = info.getHash();
// bump the request count to the caller knows we're
// starting or waiting on a thread.
++_workRequestCount;
const auto itr = std::find(_pendingMeshShapes.begin(), _pendingMeshShapes.end(), hash);
if (itr == _pendingMeshShapes.end()) {
// start a worker
_pendingMeshShapes.push_back(hash);
++_workRequestCount;
// try to recycle old deadWorker
ShapeFactory::Worker* worker = _deadWorker;
if (!worker) {

Some files were not shown because too many files have changed in this diff Show more