Merge branch 'master' of github.com:highfidelity/hifi into feat/hasScriptedBlendshapes

This commit is contained in:
Thijs Wenker 2018-04-24 00:36:28 +02:00
commit 3764ee4a06
361 changed files with 9413 additions and 5260 deletions

3
.gitignore vendored
View file

@ -91,3 +91,6 @@ interface/compiledResources
# GPUCache
interface/resources/GPUCache/*
# package lock file for JSDoc tool
tools/jsdoc/package-lock.json

View file

@ -11,11 +11,11 @@ Should you choose not to install Qt5 via a package manager that handles dependen
## Ubuntu 16.04 specific build guide
### Prepare environment
hifiqt5.10.1
Install qt:
```bash
wget http://debian.highfidelity.com/pool/h/hi/hifi-qt5.10.1_5.10.1_amd64.deb
sudo dpkg -i hifi-qt5.10.1_5.10.1_amd64.deb
wget http://debian.highfidelity.com/pool/h/hi/hifiqt5.10.1_5.10.1_amd64.deb
sudo dpkg -i hifiqt5.10.1_5.10.1_amd64.deb
```
Install build dependencies:

View file

@ -19,6 +19,7 @@ To produce an executable installer on Windows, the following are required:
- [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6
- [Inetc Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0
- [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0
- [nsisSlideshow Plug-in for Nullsoft](http://nsis.sourceforge.net/NsisSlideshow_plug-in) - 1.7
Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System.

View file

@ -380,7 +380,7 @@ void Agent::executeScript() {
using namespace recording;
static const FrameType AVATAR_FRAME_TYPE = Frame::registerFrameType(AvatarData::FRAME_NAME);
Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [this, scriptedAvatar](Frame::ConstPointer frame) {
Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [scriptedAvatar](Frame::ConstPointer frame) {
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
bool useFrameSkeleton = recordingInterface->getPlayerUseSkeletonModel();

View file

@ -61,13 +61,13 @@ static const ScriptBakeVersion CURRENT_SCRIPT_BAKE_VERSION = (ScriptBakeVersion)
BakedAssetType assetTypeForExtension(const QString& extension) {
auto extensionLower = extension.toLower();
if (BAKEABLE_MODEL_EXTENSIONS.contains(extensionLower)) {
return Model;
return BakedAssetType::Model;
} else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extensionLower.toLocal8Bit())) {
return Texture;
return BakedAssetType::Texture;
} else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extensionLower)) {
return Script;
return BakedAssetType::Script;
}
return Undefined;
return BakedAssetType::Undefined;
}
BakedAssetType assetTypeForFilename(const QString& filename) {
@ -82,11 +82,11 @@ BakedAssetType assetTypeForFilename(const QString& filename) {
QString bakedFilenameForAssetType(BakedAssetType type) {
switch (type) {
case Model:
case BakedAssetType::Model:
return BAKED_MODEL_SIMPLE_NAME;
case Texture:
case BakedAssetType::Texture:
return BAKED_TEXTURE_SIMPLE_NAME;
case Script:
case BakedAssetType::Script:
return BAKED_SCRIPT_SIMPLE_NAME;
default:
return "";
@ -95,11 +95,11 @@ QString bakedFilenameForAssetType(BakedAssetType type) {
BakeVersion currentBakeVersionForAssetType(BakedAssetType type) {
switch (type) {
case Model:
case BakedAssetType::Model:
return (BakeVersion)CURRENT_MODEL_BAKE_VERSION;
case Texture:
case BakedAssetType::Texture:
return (BakeVersion)CURRENT_TEXTURE_BAKE_VERSION;
case Script:
case BakedAssetType::Script:
return (BakeVersion)CURRENT_SCRIPT_BAKE_VERSION;
default:
return 0;
@ -222,7 +222,7 @@ bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetU
BakedAssetType type = assetTypeForFilename(path);
if (type == Undefined) {
if (type == BakedAssetType::Undefined) {
return false;
}
@ -241,7 +241,7 @@ bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetU
AssetMeta meta;
std::tie(loaded, meta) = readMetaFile(assetHash);
if (type == Texture && !loaded) {
if (type == BakedAssetType::Texture && !loaded) {
return false;
}
@ -901,7 +901,7 @@ void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, Sha
if (canWriteToAssetServer) {
qCDebug(asset_server) << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(message->getSourceID());
qCDebug(asset_server) << "Starting an UploadAssetTask for upload from" << message->getSourceID();
auto task = new UploadAssetTask(message, senderNode, _filesDirectory, _filesizeLimit);
_transferTaskPool.start(task);
@ -1546,7 +1546,7 @@ bool AssetServer::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool
auto it = _fileMappings.find(path);
if (it != _fileMappings.end()) {
auto type = assetTypeForFilename(path);
if (type == Undefined) {
if (type == BakedAssetType::Undefined) {
continue;
}
QString bakedFilename = bakedFilenameForAssetType(type);

View file

@ -27,7 +27,7 @@ using BakeVersion = int;
static const BakeVersion INITIAL_BAKE_VERSION = 0;
static const BakeVersion NEEDS_BAKING_BAKE_VERSION = -1;
enum BakedAssetType : int {
enum class BakedAssetType : int {
Model = 0,
Texture,
Script,

View file

@ -117,12 +117,13 @@ void AudioMixer::queueAudioPacket(QSharedPointer<ReceivedMessage> message, Share
void AudioMixer::queueReplicatedAudioPacket(QSharedPointer<ReceivedMessage> message) {
// make sure we have a replicated node for the original sender of the packet
auto nodeList = DependencyManager::get<NodeList>();
QUuid nodeID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
// Node ID is now part of user data, since replicated audio packets are non-sourced.
QUuid nodeID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
auto replicatedNode = nodeList->addOrUpdateNode(nodeID, NodeType::Agent,
message->getSenderSockAddr(), message->getSenderSockAddr(),
true, true);
Node::NULL_LOCAL_ID, true, true);
replicatedNode->setLastHeardMicrostamp(usecTimestampNow());
// construct a "fake" audio received message from the byte array and packet list information
@ -136,7 +137,7 @@ void AudioMixer::queueReplicatedAudioPacket(QSharedPointer<ReceivedMessage> mess
auto replicatedMessage = QSharedPointer<ReceivedMessage>::create(audioData, rewrittenType,
versionForPacketType(rewrittenType),
message->getSenderSockAddr(), nodeID);
message->getSenderSockAddr(), Node::NULL_LOCAL_ID);
getOrCreateClientData(replicatedNode.data())->queuePacket(replicatedMessage, replicatedNode);
}

View file

@ -376,6 +376,11 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU
return;
}
if (streamToAdd.getType() == PositionalAudioStream::Injector) {
// apply per-avatar gain to positional audio injectors, which wouldn't otherwise be affected by PAL sliders
hrtf.setGainAdjustment(listenerNodeData.hrtfForStream(sourceNodeID, QUuid()).getGainAdjustment());
}
hrtf.render(_bufferSamples, _mixSamples, HRTF_DATASET_INDEX, azimuth, distance, gain,
AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);

View file

@ -74,7 +74,7 @@ SharedNodePointer addOrUpdateReplicatedNode(const QUuid& nodeID, const HifiSockA
auto replicatedNode = DependencyManager::get<NodeList>()->addOrUpdateNode(nodeID, NodeType::Agent,
senderSockAddr,
senderSockAddr,
true, true);
Node::NULL_LOCAL_ID, true, true);
replicatedNode->setLastHeardMicrostamp(usecTimestampNow());
@ -112,8 +112,8 @@ void AvatarMixer::handleReplicatedPacket(QSharedPointer<ReceivedMessage> message
void AvatarMixer::handleReplicatedBulkAvatarPacket(QSharedPointer<ReceivedMessage> message) {
while (message->getBytesLeftToRead()) {
// first, grab the node ID for this replicated avatar
// Node ID is now part of user data, since ReplicatedBulkAvatarPacket is non-sourced.
auto nodeID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
// make sure we have an upstream replicated node that matches
auto replicatedNode = addOrUpdateReplicatedNode(nodeID, message->getSenderSockAddr());
@ -127,7 +127,7 @@ void AvatarMixer::handleReplicatedBulkAvatarPacket(QSharedPointer<ReceivedMessag
// construct a "fake" avatar data received message from the byte array and packet list information
auto replicatedMessage = QSharedPointer<ReceivedMessage>::create(avatarByteArray, PacketType::AvatarData,
versionForPacketType(PacketType::AvatarData),
message->getSenderSockAddr(), nodeID);
message->getSenderSockAddr(), Node::NULL_LOCAL_ID);
// queue up the replicated avatar data with the client data for the replicated node
auto start = usecTimestampNow();

View file

@ -48,8 +48,6 @@ private:
void preDistributionProcessing() override;
bool hasSomethingToSend(OctreeQueryNode* nodeData) override { return !_sendQueue.empty(); }
bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) override { return viewFrustumChanged || _traversal.finished(); }
void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) override {};
bool shouldTraverseAndSend(OctreeQueryNode* nodeData) override { return true; }
DiffTraversal _traversal;
EntityPriorityQueue _sendQueue;

View file

@ -304,23 +304,6 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
return numPackets;
}
void OctreeSendThread::preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) {
// If we're starting a full scene, then definitely we want to empty the elementBag
if (isFullScene) {
nodeData->elementBag.deleteAll();
}
// This is the start of "resending" the scene.
bool dontRestartSceneOnMove = false; // this is experimental
if (dontRestartSceneOnMove) {
if (nodeData->elementBag.isEmpty()) {
nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
}
} else {
nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
}
}
/// Version of octree element distributor that sends the deepest LOD level at once
int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged) {
OctreeServer::didPacketDistributor(this);
@ -366,16 +349,8 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
// the current view frustum for things to send.
if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) {
// if our view has changed, we need to reset these things...
if (viewFrustumChanged) {
if (nodeData->moveShouldDump() || nodeData->hasLodChanged()) {
nodeData->dumpOutOfView();
}
}
// track completed scenes and send out the stats packet accordingly
nodeData->stats.sceneCompleted();
nodeData->setLastRootTimestamp(_myServer->getOctree()->getRoot()->getLastChanged());
_myServer->getOctree()->releaseSceneEncodeData(&nodeData->extraEncodeData);
// TODO: add these to stats page
@ -389,111 +364,74 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
// TODO: add these to stats page
//::startSceneSleepTime = _usleepTime;
nodeData->sceneStart(usecTimestampNow() - CHANGE_FUDGE);
// start tracking our stats
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getOctree()->getRoot());
preStartNewScene(nodeData, isFullScene);
}
// If we have something in our elementBag, then turn them into packets and send them out...
if (shouldTraverseAndSend(nodeData)) {
quint64 start = usecTimestampNow();
quint64 start = usecTimestampNow();
_myServer->getOctree()->withReadLock([&]{
traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);
});
_myServer->getOctree()->withReadLock([&]{
traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);
});
// Here's where we can/should allow the server to send other data...
// send the environment packet
// TODO: should we turn this into a while loop to better handle sending multiple special packets
if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) {
int specialPacketsSent = 0;
int specialBytesSent = _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent);
nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed
_truePacketsSent += specialPacketsSent;
_trueBytesSent += specialBytesSent;
_packetsSentThisInterval += specialPacketsSent;
// Here's where we can/should allow the server to send other data...
// send the environment packet
// TODO: should we turn this into a while loop to better handle sending multiple special packets
if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) {
int specialPacketsSent = 0;
int specialBytesSent = _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent);
nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed
_truePacketsSent += specialPacketsSent;
_trueBytesSent += specialBytesSent;
_packetsSentThisInterval += specialPacketsSent;
_totalPackets += specialPacketsSent;
_totalBytes += specialBytesSent;
_totalPackets += specialPacketsSent;
_totalBytes += specialBytesSent;
_totalSpecialPackets += specialPacketsSent;
_totalSpecialBytes += specialBytesSent;
_totalSpecialPackets += specialPacketsSent;
_totalSpecialBytes += specialBytesSent;
}
// calculate max number of packets that can be sent during this interval
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
// Re-send packets that were nacked by the client
while (nodeData->hasNextNackedPacket() && _packetsSentThisInterval < maxPacketsPerInterval) {
const NLPacket* packet = nodeData->getNextNackedPacket();
if (packet) {
DependencyManager::get<NodeList>()->sendUnreliablePacket(*packet, *node);
int numBytes = packet->getDataSize();
_truePacketsSent++;
_trueBytesSent += numBytes;
_packetsSentThisInterval++;
_totalPackets++;
_totalBytes += numBytes;
_totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize();
}
}
// calculate max number of packets that can be sent during this interval
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
quint64 end = usecTimestampNow();
int elapsedmsec = (end - start) / USECS_PER_MSEC;
OctreeServer::trackLoopTime(elapsedmsec);
// Re-send packets that were nacked by the client
while (nodeData->hasNextNackedPacket() && _packetsSentThisInterval < maxPacketsPerInterval) {
const NLPacket* packet = nodeData->getNextNackedPacket();
if (packet) {
DependencyManager::get<NodeList>()->sendUnreliablePacket(*packet, *node);
int numBytes = packet->getDataSize();
_truePacketsSent++;
_trueBytesSent += numBytes;
_packetsSentThisInterval++;
// if we've sent everything, then we want to remember that we've sent all
// the octree elements from the current view frustum
if (!hasSomethingToSend(nodeData)) {
nodeData->setViewSent(true);
_totalPackets++;
_totalBytes += numBytes;
_totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize();
}
// If this was a full scene then make sure we really send out a stats packet at this point so that
// the clients will know the scene is stable
if (isFullScene) {
nodeData->stats.sceneCompleted();
handlePacketSend(node, nodeData, true);
}
quint64 end = usecTimestampNow();
int elapsedmsec = (end - start) / USECS_PER_MSEC;
OctreeServer::trackLoopTime(elapsedmsec);
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
// the octree elements from the current view frustum
if (!hasSomethingToSend(nodeData)) {
nodeData->updateLastKnownViewFrustum();
nodeData->setViewSent(true);
// If this was a full scene then make sure we really send out a stats packet at this point so that
// the clients will know the scene is stable
if (isFullScene) {
nodeData->stats.sceneCompleted();
handlePacketSend(node, nodeData, true);
}
}
} // end if bag wasn't empty, and so we sent stuff...
}
return _truePacketsSent;
}
bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) {
bool somethingToSend = false;
OctreeQueryNode* nodeData = static_cast<OctreeQueryNode*>(params.nodeData);
if (!nodeData->elementBag.isEmpty()) {
quint64 encodeStart = usecTimestampNow();
quint64 lockWaitStart = encodeStart;
_myServer->getOctree()->withReadLock([&]{
OctreeServer::trackTreeWaitTime((float)(usecTimestampNow() - lockWaitStart));
OctreeElementPointer subTree = nodeData->elementBag.extract();
if (subTree) {
// NOTE: this is where the tree "contents" are actually packed
nodeData->stats.encodeStarted();
_myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params);
nodeData->stats.encodeStopped();
somethingToSend = true;
}
});
OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart));
} else {
OctreeServer::trackTreeWaitTime(OctreeServer::SKIP_TIME);
OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME);
}
return somethingToSend;
}
void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) {
// calculate max number of packets that can be sent during this interval
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
@ -502,21 +440,12 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
int extraPackingAttempts = 0;
// init params once outside the while loop
int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
int boundaryLevelAdjust = boundaryLevelAdjustClient +
(viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
float octreeSizeScale = nodeData->getOctreeSizeScale();
EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP,
viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale,
isFullScene, nodeData);
EncodeBitstreamParams params(WANT_EXISTS_BITS, nodeData);
// Our trackSend() function is implemented by the server subclass, and will be called back as new entities/data elements are sent
params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
_myServer->trackSend(dataID, dataEdited, _nodeUuid);
};
nodeData->copyCurrentViewFrustum(params.viewFrustum);
if (viewFrustumChanged) {
nodeData->copyLastKnownViewFrustum(params.lastViewFrustum);
}
bool somethingToSend = true; // assume we have something
bool hadSomething = hasSomethingToSend(nodeData);
@ -536,8 +465,8 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
extraPackingAttempts++;
}
// If the bag had contents but is now empty then we know we've sent the entire scene.
bool completedScene = hadSomething && nodeData->elementBag.isEmpty();
// If we had something to send, but now we don't, then we know we've sent the entire scene.
bool completedScene = hadSomething;
if (completedScene || lastNodeDidntFit) {
// we probably want to flush what has accumulated in nodeData but:
// do we have more data to send? and is there room?

View file

@ -54,7 +54,7 @@ protected:
virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
bool viewFrustumChanged, bool isFullScene);
virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters);
virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) = 0;
OctreePacketData _packetData;
QWeakPointer<Node> _node;
@ -63,14 +63,12 @@ protected:
private:
/// Called before a packetDistributor pass to allow for pre-distribution processing
virtual void preDistributionProcessing() {};
virtual void preDistributionProcessing() = 0;
int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate = false);
int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged);
virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) { return !nodeData->elementBag.isEmpty(); }
virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) { return viewFrustumChanged || !hasSomethingToSend(nodeData); }
virtual void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene);
virtual bool shouldTraverseAndSend(OctreeQueryNode* nodeData) { return hasSomethingToSend(nodeData); }
virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) = 0;
virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) = 0;
int _truePacketsSent { 0 }; // available for debug stats
int _trueBytesSent { 0 }; // available for debug stats

View file

@ -876,10 +876,6 @@ void OctreeServer::parsePayload() {
}
}
OctreeServer::UniqueSendThread OctreeServer::newSendThread(const SharedNodePointer& node) {
return std::unique_ptr<OctreeSendThread>(new OctreeSendThread(this, node));
}
OctreeServer::UniqueSendThread OctreeServer::createSendThread(const SharedNodePointer& node) {
auto sendThread = newSendThread(node);

View file

@ -174,7 +174,7 @@ protected:
void beginRunning(QByteArray replaceData);
UniqueSendThread createSendThread(const SharedNodePointer& node);
virtual UniqueSendThread newSendThread(const SharedNodePointer& node);
virtual UniqueSendThread newSendThread(const SharedNodePointer& node) = 0;
int _argc;
const char** _argv;

View file

@ -1,25 +0,0 @@
# FindGVerb.cmake
#
# Try to find the Gverb library.
#
# You must provide a GVERB_ROOT_DIR which contains src and include directories
#
# Once done this will define
#
# GVERB_FOUND - system found Gverb
# GVERB_INCLUDE_DIRS - the Gverb include directory
#
# Copyright 2014 High Fidelity, Inc.
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
hifi_library_search_hints("gverb")
find_path(GVERB_INCLUDE_DIRS gverb.h PATH_SUFFIXES include HINTS ${GVERB_SEARCH_DIRS})
find_library(GVERB_LIBRARIES gverb PATH_SUFFIXES lib HINTS ${GVERB_SEARCH_DIRS})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Gverb DEFAULT_MSG GVERB_INCLUDE_DIRS GVERB_LIBRARIES)

View file

@ -87,6 +87,10 @@
;--------------------------------
;--------------------------------
;General
; hide install details since we show an image slideshow in their place
ShowInstDetails nevershow
; leverage the UAC NSIS plugin to promote uninstaller to elevated privileges
!include UAC.nsh
@ -446,6 +450,7 @@ SectionEnd
Page custom PostInstallOptionsPage ReadPostInstallOptions
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageInstallFilesPre
!define MUI_PAGE_CUSTOMFUNCTION_SHOW StartInstallSlideshow
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
@ -544,11 +549,33 @@ Var Express
${EndIf}
!macroend
!macro DownloadSlideshowImages
InitPluginsDir
Push $0
; figure out where to download installer slideshow images from
StrCpy $0 "http://cdn.highfidelity.com/installer/slideshow"
${If} $CampaignName == ""
StrCpy $0 "$0/default"
${Else}
StrCpy $0 "$0/$CampaignName"
${EndIf}
NSISdl::download_quiet $0/1.jpg "$PLUGINSDIR\1.jpg"
NSISdl::download_quiet $0/2.jpg "$PLUGINSDIR\2.jpg"
NSISdl::download_quiet $0/3.jpg "$PLUGINSDIR\3.jpg"
Pop $0
!macroend
Function OnUserAbort
!insertmacro GoogleAnalytics "Installer" "Abort" "User Abort" ""
FunctionEnd
Function PageWelcomePre
!insertmacro GoogleAnalytics "Installer" "Welcome" "" ""
!insertmacro DownloadSlideshowImages
FunctionEnd
Function PageLicensePre
!insertmacro GoogleAnalytics "Installer" "License" "" ""
@ -640,6 +667,56 @@ Function ChangeCustomLabel
Pop $R1
FunctionEnd
!macro AddImageToSlideshowFile ImageFilename
${If} ${FileExists} "$PLUGINSDIR\${ImageFilename}.jpg"
FileWrite $0 "= ${ImageFilename}.jpg,500,5000,$\"$\"$\r$\n"
StrCpy $1 "1"
${EndIf}
!macroend
Function StartInstallSlideshow
; create a slideshow file based on what files we have available
; stash $0 and $1
Push $0
Push $1
; start $1 as 0, indicating we have no images present
StrCpy $1 "0"
FileOpen $0 "$PLUGINSDIR\slideshow.dat" w
; write the language value to the slideshow file for english
FileWrite $0 "[1033]$\r$\n"
; for each of 1.jpg, 2.jpg, 3.jpg
; if the image is present add it to the dat file and set our flag
; to show we found at least one image
!insertmacro AddImageToSlideshowFile "1"
!insertmacro AddImageToSlideshowFile "2"
!insertmacro AddImageToSlideshowFile "3"
FileClose $0
; NOTE: something inside of nsisSlideshow::show isn't keeping the stack clean
; so we need to push things back BEFORE we call it
${If} $1 == "1"
Pop $1
Pop $0
; show the slideshow using the created data file
nsisSlideshow::show /NOUNLOAD "/auto=$PLUGINSDIR\slideshow.dat"
${Else}
Pop $1
Pop $0
; show the install details because we didn't end up with slideshow images to show
SetDetailsView show
${EndIf}
FunctionEnd
Function PostInstallOptionsPage
!insertmacro MaybeSkipPage
!insertmacro GoogleAnalytics "Installer" "Post Install Options" "" ""
@ -932,6 +1009,7 @@ FunctionEnd
;Installer Sections
Section "-Core installation"
;The following delete blocks are temporary and can be removed once users who had the initial installer have updated
;Delete any server-console files installed before it was placed in sub-folder
@ -983,11 +1061,13 @@ Section "-Core installation"
WriteRegStr HKLM "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR
;Write some information about this install to the installation folder
Push $0
FileOpen $0 "$INSTDIR\installer.ini" w
FileWrite $0 "type=@INSTALLER_TYPE@$\r$\n"
FileWrite $0 "campaign=$CampaignName$\r$\n"
FileWrite $0 "exepath=$EXEPATH$\r$\n"
FileClose $0
Pop $0
;Package the signed uninstaller produced by the inner loop
!ifndef INNER

View file

@ -14,6 +14,7 @@
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <random>
#include <AccountManager.h>
#include <Assignment.h>
@ -26,7 +27,7 @@ using SharedAssignmentPointer = QSharedPointer<Assignment>;
DomainGatekeeper::DomainGatekeeper(DomainServer* server) :
_server(server)
{
initLocalIDManagement();
}
void DomainGatekeeper::addPendingAssignedNode(const QUuid& nodeUUID, const QUuid& assignmentUUID,
@ -451,11 +452,12 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
return SharedNodePointer();
}
QUuid hintNodeID;
QUuid existingNodeID;
// in case this is a node that's failing to connect
// double check we don't have the same node whose sockets match exactly already in the list
limitedNodeList->eachNodeBreakable([&](const SharedNodePointer& node){
if (node->getPublicSocket() == nodeConnection.publicSockAddr && node->getLocalSocket() == nodeConnection.localSockAddr) {
// we have a node that already has these exact sockets - this can occur if a node
// is failing to connect to the domain
@ -465,15 +467,20 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
auto existingNodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
if (existingNodeData->getUsername() == username) {
hintNodeID = node->getUUID();
qDebug() << "Deleting existing connection from same sockaddr: " << node->getUUID();
existingNodeID = node->getUUID();
return false;
}
}
return true;
});
if (!existingNodeID.isNull()) {
limitedNodeList->killNodeWithUUID(existingNodeID);
}
// add the connecting node (or re-use the matched one from eachNodeBreakable above)
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID);
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
// set the edit rights for this user
newNode->setPermissions(userPerms);
@ -523,8 +530,10 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
Node::LocalID newLocalID = findOrCreateLocalID(nodeID);
SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeID, nodeConnection.nodeType,
nodeConnection.publicSockAddr, nodeConnection.localSockAddr);
nodeConnection.publicSockAddr, nodeConnection.localSockAddr,
newLocalID);
// So that we can send messages to this node at will - we need to activate the correct socket on this node now
newNode->activateMatchingOrNewSymmetricSocket(discoveredSocket);
@ -1014,3 +1023,31 @@ void DomainGatekeeper::refreshGroupsCache() {
_server->_settingsManager.debugDumpGroupsState();
#endif
}
void DomainGatekeeper::initLocalIDManagement() {
std::uniform_int_distribution<quint16> sixteenBitRand;
std::random_device randomDevice;
std::default_random_engine engine { randomDevice() };
_currentLocalID = sixteenBitRand(engine);
// Ensure increment is odd.
_idIncrement = sixteenBitRand(engine) | 1;
}
Node::LocalID DomainGatekeeper::findOrCreateLocalID(const QUuid& uuid) {
auto existingLocalIDIt = _uuidToLocalID.find(uuid);
if (existingLocalIDIt != _uuidToLocalID.end()) {
return existingLocalIDIt->second;
}
assert(_localIDs.size() < std::numeric_limits<LocalIDs::value_type>::max() - 2);
Node::LocalID newLocalID;
do {
newLocalID = _currentLocalID;
_currentLocalID += _idIncrement;
} while (newLocalID == Node::NULL_LOCAL_ID || _localIDs.find(newLocalID) != _localIDs.end());
_uuidToLocalID.emplace(uuid, newLocalID);
_localIDs.insert(newLocalID);
return newLocalID;
}

View file

@ -15,6 +15,7 @@
#define hifi_DomainGatekeeper_h
#include <unordered_map>
#include <unordered_set>
#include <QtCore/QObject>
#include <QtNetwork/QNetworkReply>
@ -41,6 +42,8 @@ public:
void removeICEPeer(const QUuid& peerUUID) { _icePeers.remove(peerUUID); }
Node::LocalID findOrCreateLocalID(const QUuid& uuid);
static void sendProtocolMismatchConnectionDenial(const HifiSockAddr& senderSockAddr);
public slots:
void processConnectRequestPacket(QSharedPointer<ReceivedMessage> message);
@ -120,6 +123,16 @@ private:
void getGroupMemberships(const QString& username);
// void getIsGroupMember(const QString& username, const QUuid groupID);
void getDomainOwnerFriendsList();
// Local ID management.
void initLocalIDManagement();
using UUIDToLocalID = std::unordered_map<QUuid, Node::LocalID> ;
using LocalIDs = std::unordered_set<Node::LocalID>;
LocalIDs _localIDs;
UUIDToLocalID _uuidToLocalID;
Node::LocalID _currentLocalID;
Node::LocalID _idIncrement;
};

View file

@ -593,8 +593,8 @@ bool DomainServer::isPacketVerified(const udt::Packet& packet) {
if (!PacketTypeEnum::getNonSourcedPackets().contains(headerType)) {
// this is a sourced packet - first check if we have a node that matches
QUuid sourceID = NLPacket::sourceIDInHeader(packet);
SharedNodePointer sourceNode = nodeList->nodeWithUUID(sourceID);
Node::LocalID localSourceID = NLPacket::sourceIDInHeader(packet);
SharedNodePointer sourceNode = nodeList->nodeWithLocalID(localSourceID);
if (sourceNode) {
// unverified DS packets (due to a lack of connection secret between DS + node)
@ -611,23 +611,13 @@ bool DomainServer::isPacketVerified(const udt::Packet& packet) {
// let the NodeList do its checks now (but pass it the sourceNode so it doesn't need to look it up again)
return nodeList->isPacketVerifiedWithSource(packet, sourceNode.data());
} else {
static const QString UNKNOWN_REGEX = "Packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unmatched IP for UUID";
static QString repeatedMessage
= LogHandler::getInstance().addRepeatedMessageRegex(UNKNOWN_REGEX);
qDebug() << "Packet of type" << headerType
<< "received from unmatched IP for UUID" << uuidStringWithoutCurlyBraces(sourceID);
HIFI_FDEBUG("Packet of type" << headerType
<< "received from unmatched IP for UUID" << uuidStringWithoutCurlyBraces(sourceNode->getUUID()));
return false;
}
} else {
static const QString UNKNOWN_REGEX = "Packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unknown node with UUID";
static QString repeatedMessage
= LogHandler::getInstance().addRepeatedMessageRegex(UNKNOWN_REGEX);
qDebug() << "Packet of type" << headerType
<< "received from unknown node with UUID" << uuidStringWithoutCurlyBraces(sourceID);
HIFI_FDEBUG("Packet of type" << headerType
<< "received from unknown node with Local ID" << localSourceID);
return false;
}
}
@ -691,6 +681,10 @@ void DomainServer::setupNodeListAndAssignments() {
}
}
// Create our own short session ID.
Node::LocalID serverSessionLocalID = _gatekeeper.findOrCreateLocalID(nodeList->getSessionUUID());
nodeList->setSessionLocalID(serverSessionLocalID);
if (isMetaverseDomain) {
// see if we think we're a temp domain (we have an API key) or a full domain
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
@ -1120,7 +1114,8 @@ void DomainServer::handleConnectedNode(SharedNodePointer newNode) {
}
void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr) {
const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NUM_BYTES_RFC4122_UUID + 2;
const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NLPacket::NUM_BYTES_LOCALID +
NUM_BYTES_RFC4122_UUID + NLPacket::NUM_BYTES_LOCALID + 4;
// setup the extended header for the domain list packets
// this data is at the beginning of each of the domain list packets
@ -1130,7 +1125,9 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
extendedHeaderStream << limitedNodeList->getSessionUUID();
extendedHeaderStream << limitedNodeList->getSessionLocalID();
extendedHeaderStream << node->getUUID();
extendedHeaderStream << node->getLocalID();
extendedHeaderStream << node->getPermissions();
auto domainListPackets = NLPacketList::create(PacketType::DomainList, extendedHeader);
@ -1242,31 +1239,16 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer<ReceivedMessage
auto it = find_if(_acSubnetWhitelist.begin(), _acSubnetWhitelist.end(), isHostAddressInSubnet);
if (it == _acSubnetWhitelist.end()) {
static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(
"Received an assignment connect request from a disallowed ip address: [^ ]+");
qDebug() << "Received an assignment connect request from a disallowed ip address:"
<< senderAddr.toString();
HIFI_FDEBUG("Received an assignment connect request from a disallowed ip address:"
<< senderAddr.toString());
return;
}
// Suppress these for Assignment::AgentType to once per 5 seconds
static QElapsedTimer noisyMessageTimer;
static bool wasNoisyTimerStarted = false;
if (!wasNoisyTimerStarted) {
noisyMessageTimer.start();
wasNoisyTimerStarted = true;
}
const qint64 NOISY_MESSAGE_INTERVAL_MSECS = 5 * 1000;
if (requestAssignment.getType() != Assignment::AgentType
|| noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) {
static QString repeatedMessage = LogHandler::getInstance().addOnlyOnceMessageRegex
("Received a request for assignment type [^ ]+ from [^ ]+");
static bool printedAssignmentTypeMessage = false;
if (!printedAssignmentTypeMessage && requestAssignment.getType() != Assignment::AgentType) {
printedAssignmentTypeMessage = true;
qDebug() << "Received a request for assignment type" << requestAssignment.getType()
<< "from" << message->getSenderSockAddr();
noisyMessageTimer.restart();
}
SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment);
@ -1300,13 +1282,11 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer<ReceivedMessage
_gatekeeper.addPendingAssignedNode(uniqueAssignment.getUUID(), assignmentToDeploy->getUUID(),
requestAssignment.getWalletUUID(), requestAssignment.getNodeVersion());
} else {
if (requestAssignment.getType() != Assignment::AgentType
|| noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) {
static QString repeatedMessage = LogHandler::getInstance().addOnlyOnceMessageRegex
("Unable to fulfill assignment request of type [^ ]+ from [^ ]+");
static bool printedAssignmentRequestMessage = false;
if (!printedAssignmentRequestMessage && requestAssignment.getType() != Assignment::AgentType) {
printedAssignmentRequestMessage = true;
qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType()
<< "from" << message->getSenderSockAddr();
noisyMessageTimer.restart();
}
}
}
@ -1552,10 +1532,12 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() {
callbackParameters.jsonCallbackReceiver = this;
callbackParameters.jsonCallbackMethod = "handleSuccessfulICEServerAddressUpdate";
static QString repeatedMessage = LogHandler::getInstance().addOnlyOnceMessageRegex
("Updating ice-server address in High Fidelity Metaverse API to [^ \n]+");
qDebug() << "Updating ice-server address in High Fidelity Metaverse API to"
<< (_iceServerSocket.isNull() ? "" : _iceServerSocket.getAddress().toString());
static bool printedIceServerMessage = false;
if (!printedIceServerMessage) {
printedIceServerMessage = true;
qDebug() << "Updating ice-server address in High Fidelity Metaverse API to"
<< (_iceServerSocket.isNull() ? "" : _iceServerSocket.getAddress().toString());
}
static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address";
@ -2864,7 +2846,7 @@ void DomainServer::updateReplicationNodes(ReplicationServerDirection direction)
// manually add the replication node to our node list
auto node = nodeList->addOrUpdateNode(QUuid::createUuid(), replicationServer.nodeType,
replicationServer.sockAddr, replicationServer.sockAddr,
false, direction == Upstream);
Node::NULL_LOCAL_ID, false, direction == Upstream);
node->setIsForcedNeverSilent(true);
qDebug() << "Adding" << (direction == Upstream ? "upstream" : "downstream")
@ -2926,7 +2908,7 @@ void DomainServer::updateReplicatedNodes() {
}
auto nodeList = DependencyManager::get<LimitedNodeList>();
nodeList->eachMatchingNode([this](const SharedNodePointer& otherNode) -> bool {
nodeList->eachMatchingNode([](const SharedNodePointer& otherNode) -> bool {
return otherNode->getType() == NodeType::Agent;
}, [this](const SharedNodePointer& otherNode) {
auto shouldReplicate = shouldReplicateNode(*otherNode);
@ -3164,13 +3146,12 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMes
// This packet has been matched to a source node and they're asking not to be in the domain anymore
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
const QUuid& nodeUUID = message->getSourceID();
qDebug() << "Received a disconnect request from node with UUID" << nodeUUID;
auto localID = message->getSourceID();
qDebug() << "Received a disconnect request from node with local ID" << localID;
// we want to check what type this node was before going to kill it so that we can avoid sending the RemovedNode
// packet to nodes that don't care about this type
auto nodeToKill = limitedNodeList->nodeWithUUID(nodeUUID);
auto nodeToKill = limitedNodeList->nodeWithLocalID(localID);
if (nodeToKill) {
handleKillNode(nodeToKill);
@ -3438,7 +3419,7 @@ void DomainServer::handleDomainContentReplacementFromURLRequest(QSharedPointer<R
}
void DomainServer::handleOctreeFileReplacementRequest(QSharedPointer<ReceivedMessage> message) {
auto node = DependencyManager::get<NodeList>()->nodeWithUUID(message->getSourceID());
auto node = DependencyManager::get<NodeList>()->nodeWithLocalID(message->getSourceID());
if (node->getCanReplaceContent()) {
handleOctreeFileReplacement(message->readAll());
}

View file

@ -180,8 +180,9 @@ else ()
add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM})
endif ()
if (BUILD_TOOLS AND NPM_EXECUTABLE)
# require JSDoc to be build before interface is deployed (Console Auto-complete)
# require JSDoc to be build before interface is deployed
add_dependencies(resources jsdoc)
endif()
@ -322,6 +323,13 @@ if (APPLE)
"${RESOURCES_DEV_DIR}/scripts"
)
# copy JSDoc files beside the executable
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${CMAKE_SOURCE_DIR}/tools/jsdoc/out"
"${RESOURCES_DEV_DIR}/jsdoc"
)
# call the fixup_interface macro to add required bundling commands for installation
fixup_interface()
@ -350,6 +358,13 @@ else()
"${RESOURCES_DEV_DIR}/serverless/tutorial.json"
)
# copy JSDoc files beside the executable
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${CMAKE_SOURCE_DIR}/tools/jsdoc/out"
"${INTERFACE_EXEC_DIR}/jsdoc"
)
# link target to external libraries
if (WIN32)
target_link_libraries(${TARGET_NAME} wsock32.lib Winmm.lib)

View file

@ -25,7 +25,6 @@
<glyph glyph-name="web" unicode="&#113;" d="M438 390c0 8-6 15-14 15l-333 0c-8 0-15-7-15-15l0-298c0-8 7-14 15-14l333 0c8 0 14 6 14 14z m-219-8l172 0c8 0 15-7 15-16 0-9-7-16-15-16l-172 0c-8 0-15 7-15 16 0 9 7 16 15 16z m-47 1c9 0 16-7 16-16 0-10-7-17-16-17-10 0-17 7-17 17 0 9 7 16 17 16z m-51 0c9 0 17-7 17-16 0-10-8-17-17-17-9 0-17 7-17 17 0 9 8 16 17 16z m291-276l-308 0 0 219 308 0z m-250 84l-15 0-20 60 13 0 14-45 15 45 13 0 15-45 14 45 13 0-20-60-14 0-14 41z m137 25l-46 0c0-5 2-8 6-11 3-2 8-4 12-4 8 0 13 3 17 7l7-8c-6-6-14-9-25-9-8 0-15 2-21 8-6 5-9 13-9 22 0 9 3 17 9 22 6 6 13 9 21 9 8 0 15-3 21-8 6-5 8-11 8-20z m-46 9l34 0c0 5-2 9-5 12-3 3-7 4-11 4-5 0-9-1-13-4-3-3-5-7-5-12z m101 27c8 0 14-3 20-9 6-5 9-12 9-22 0-9-3-16-9-22-5-6-12-8-19-8-8 0-15 3-21 9l0-9-12 0 0 83 12 0 0-34c5 8 12 12 20 12z m-20-31c0-6 2-10 5-14 4-4 8-5 13-5 5 0 9 1 13 5 3 4 5 8 5 14 0 6-2 10-5 14-4 4-8 6-13 6-5 0-9-2-13-6-3-4-5-8-5-14z"/>
<glyph glyph-name="web-2" unicode="&#114;" d="M438 390c0 8-6 15-14 15l-333 0c-8 0-15-7-15-15l0-298c0-8 7-14 15-14l333 0c8 0 14 6 14 14z m-219-8l172 0c8 0 15-7 15-16 0-9-7-16-15-16l-172 0c-8 0-15 7-15 16 0 9 7 16 15 16z m-47 1c9 0 16-7 16-16 0-10-7-17-16-17-10 0-17 7-17 17 0 9 7 16 17 16z m-51 0c9 0 17-7 17-16 0-10-8-17-17-17-9 0-17 7-17 17 0 9 8 16 17 16z m291-276l-308 0 0 219 308 0z"/>
<glyph glyph-name="edit" unicode="&#115;" d="M196 214c-27-27-54-54-81-81-6 6-13 13-19 19 27 27 54 54 81 81l-22 21c-31-31-61-62-92-92-7-7-11-13-12-22-3-25-7-50-11-76 3 0 4 0 6 0 24 5 48 10 72 15 5 1 10 4 13 7 32 32 64 64 96 96z m126 207c10 10 21 21 33 32 4 4 10 4 14 0 19-19 38-38 57-57 4-5 4-10 0-15-11-11-22-22-34-33-23 24-46 48-70 73z m23-181c-5-1-8 0-11 3-8 8-15 15-23 23 18 18 37 37 55 55 2 2 4 4 4 4-24 25-47 49-71 74-2-2-3-4-5-6-18-18-37-37-55-55-34 34-67 67-101 101-2 2-5 5-8 7-18 14-42 12-57-5-15-17-14-42 2-58 50-51 101-101 151-151 17-16 33-33 50-49 2-3 3-5 2-8-2-8-4-15-4-23-4-48 27-90 73-102 20-5 39-4 58 4-1 2-3 3-5 4-14 14-28 28-42 42-10 11-11 26-2 36 8 8 16 16 24 24 11 10 24 10 35 0 2-1 4-4 6-6 15-14 29-28 43-42 0 0 1 0 2 1 1 8 3 16 4 24 7 69-59 123-125 103z m-243 162c-7 0-14 6-14 14 0 8 6 14 14 14 8 0 15-6 15-14 0-7-7-14-15-14z m198-46c6-6 13-12 19-19-13-13-26-26-39-39-7 6-13 12-20 19 14 13 27 26 40 39z"/>
<glyph glyph-name="market" unicode="&#116;" d="M88 370c3 0 7 0 10 0 6 0 11-1 15-2 9-2 16-8 20-16 3-5 4-10 6-15 2-6 3-13 5-19 3-10 5-19 8-27 2-6 3-13 5-19 3-7 5-15 7-23 3-9 6-19 9-30 0-2 1-4 1-6 2-8 5-17 9-24 3-7 8-12 13-15 6-3 13-5 22-6 2 0 5 0 7 0l21 0 57 0c25 0 50 0 75 0 12 0 25 0 37 0 1 0 1 0 2 0 6 0 13 0 17 2 5 3 7 9 9 16l3 10c0 0 0 0 0 0 0 2 1 3 1 3 5 20 11 40 17 60 1 4 2 9 4 13 2 8 4 16 7 24 2 7 4 16 0 22-3 4-9 5-14 5-6 0-188-2-284-4l-4 0-5 22c0 3-1 6-2 9 0 3-1 5-1 7-1 2-1 3-1 4-1 4-2 7-3 11-2 6-6 12-11 17-5 5-11 8-17 9-3 1-6 1-10 1-5 1-9 1-14 1-19 0-38 0-59 0-1 0-3 0-4 0-3 0-7 0-10-1-3 0-6-2-8-4-3-5-4-11-2-17 2-5 6-6 9-7 4-1 8-1 13-1 5 0 10 0 16 0l16 0c3 0 5 0 8 0z m353-78l-26-92-6-4-2 0c-2 0-21 0-55 0-50 0-118-1-125-1l-1-1 0 0c-6 0-12 2-16 6-4 4-6 10-8 16-3 12-6 25-10 39-1 4-2 8-3 12-2 4-3 8-4 13l-4 12z m-194-164c-9 0-15-2-20-7-5-5-8-11-8-20 0-8 3-16 8-21 5-5 13-8 20-8 17 0 30 12 30 28 0 8-3 15-9 20-5 6-12 8-21 8z m139 0c-9 0-16-2-21-6-5-6-8-13-8-21 0-9 3-16 8-21 5-6 12-8 21-8 16 0 29 13 29 29 0 8-3 14-8 20-5 5-13 7-21 7z"/>
<glyph glyph-name="directory" unicode="&#117;" d="M432 451l-99-38c-2 0-3-1-4-1-3 1-5 2-8 2l-116 38c-8 3-15 6-30-1l-91-35c-17-5-32-16-32-31l0-303c0-15 14-27 32-27l99 38c3 1 6 2 9 4 1-1 3-2 4-2 3-1 5-2 7-3 0 0 1 0 1-1 0 0 1 0 1 0l116-38 0 0c8-1 12-1 21 3l90 34c13 6 32 16 32 31l0 304c0 14-14 26-32 26z m-351-371c-1 0-1 1-1 2l0 303c0 2 5 6 14 9l1 0 1 1 89 34c1-1 0-1 0-2l0-303c0-1-3-4-16-10z m356 42c-1-2-5-5-17-11l-90-34c0 1-1 1-1 2l0 304c0 1 5 5 14 8l1 0 1 1 91 34c0 0 1-1 1-1z"/>
<glyph glyph-name="menu" unicode="&#118;" d="M257 22c-60 0-119 22-164 67-44 44-68 102-68 164 0 62 24 120 68 163 90 91 237 91 327 0 44-43 68-101 68-163 0-62-24-120-68-164-45-45-104-67-163-67z m0 431c-52 0-103-20-142-59-38-38-58-88-58-141 0-54 20-104 58-142 78-78 205-78 283 0 38 38 59 88 59 142 0 53-21 103-59 141-39 39-90 59-141 59z m101-133l-203 0c-8 0-15 7-15 15 0 8 7 15 15 15l203 0c8 0 14-7 14-15 0-8-6-15-14-15z m0-84l-203 0c-8 0-15 7-15 15 0 8 7 15 15 15l203 0c8 0 14-7 14-15 0-8-6-15-14-15z m0-81l-203 0c-8 0-15 7-15 15 0 8 7 14 15 14l203 0c8 0 14-6 14-14 0-8-6-15-14-15z"/>
<glyph glyph-name="close" unicode="&#119;" d="M258 19c-59 0-118 23-163 68-44 43-68 101-68 163 0 62 24 120 68 164 90 90 237 90 327 0 44-44 68-102 68-164 0-62-24-120-68-163-45-45-104-68-164-68z m0 431c-51 0-102-19-141-58-38-38-59-88-59-142 0-53 21-103 59-141 78-78 205-78 283 0 38 38 58 88 58 141 0 54-20 104-58 142-39 39-90 58-142 58z m25-200l67 67c7 7 7 18 0 25-7 7-18 7-25 0l-67-67-66 67c-7 7-18 7-25 0-7-7-7-18 0-25l66-67-66-66c-7-7-7-18 0-25 7-7 18-7 25 0l66 66 67-66c7-7 18-7 25 0 7 7 7 18 0 25z"/>
@ -145,4 +144,14 @@
<glyph glyph-name="rez" unicode="&#57381;" d="M373 321c-2 5-6 8-11 8l-49 8 55 61c4 4 5 9 3 14-2 5-7 8-12 8 0 0 0 0 0 0l-114-1c-5-1-10-4-12-9l-54-136c-1-4-1-8 1-11 2-4 6-6 9-7l38-5-54-136c-2-6 0-13 6-16 2-1 4-2 7-2 3 0 7 2 10 5l175 206c3 4 3 9 2 13z"/>
<glyph glyph-name="keyboard-collapse" unicode="&#57387;" d="M373 249l-26 0 0 25 26 0z m-35 0l-27 0 0 25 27 0z m-36 0l-27 0 0 25 27 0z m-36 0l-26 0 0 25 26 0z m-35 0l-27 0 0 25 27 0z m-36 0l-27 0 0 25 27 0z m-36 0l-26 0 0 25 26 0z m224-1l18 0c7 0 13 6 13 13 0 7-6 13-13 13l-18 0m-262 0l-17 0c-7 0-13-6-13-13 0-7 6-13 13-13l17 0m252 39l-31 0 0 25 31 0z m-42 0l-31 0 0 25 31 0z m-41 0l-32 0 0 25 32 0z m-42 0l-32 0 0 25 32 0z m-42 0l-32 0 0 25 32 0z m-41 0l-32 0 0 25 32 0z m218-1l18 0c7 0 13 6 13 13 0 8-6 14-13 14l-18 0m-262 0l-17 0c-7 0-13-6-13-14 0-7 6-13 13-13l17 0m288-124l-315 0c-33 0-59 28-59 61l0 76c0 34 26 61 59 61l315 0c33 0 59-27 59-61l0-76c1-33-26-61-59-61z m-315 172c-18 0-33-16-33-34l0-77c0-19 15-34 33-34l315 0c18 0 33 15 33 34l0 77c0 19-15 34-33 34z m248-99l32 0 0-25-32 0z m-41 0l31 0 0-25-31 0z m-42 0l31 0 0-25-31 0z m-43 0l32 0 0-25-32 0z m-42 0l32 0 0-25-32 0z m-41 0l31 0 0-25-31 0z m250-26l18 0c7 0 13 6 13 14 0 7-6 13-13 13l-18 0m-262 0l-17 0c-7 0-13-6-13-13 0-8 6-13 13-13l17 0m81-82l50-50 53 54-107 0"/>
<glyph glyph-name="image" unicode="&#57386;" d="M257 428c52 0 104 0 156 0 24 0 37-13 37-37 1-90 1-179 0-269 0-25-13-38-39-38-103 0-207 0-311 0-26 0-39 13-39 40 0 88 0 176 0 263 0 28 13 41 41 41 51 0 103 0 155 0z m167-263c0 7 0 10 0 14 0 69 0 138 0 206 0 17 0 17-17 17-101 0-202 0-303 0-16 0-17-1-17-17 0-58 0-115 0-173 0-3 0-7 0-12 8 3 14 6 19 9 17 8 30 7 44-6 5-5 10-10 15-15 5-7 11-8 19-4 40 21 81 41 121 61 19 10 31 8 46-7 9-9 18-18 27-27 15-15 29-29 46-46z m-328-54c7 0 11-1 15-1 98 0 197 0 296 0 6 0 14 2 16 6 5 7 0 14-6 20-27 26-54 53-80 80-8 9-15 9-26 4-67-35-135-68-203-102-3-2-6-4-12-7z m-8 26c21 10 40 20 63 31-8 7-14 12-20 17-2 2-7 3-10 3-30-9-36-17-34-48 0 0 0-1 1-3z m134 169c1-25-21-46-46-46-25-1-46 20-47 46 0 25 21 46 47 47 25 0 46-21 46-47z m-46 22c-12 0-22-9-22-21 0-13 9-22 21-23 13 0 23 9 23 22 0 13-10 22-22 22z"/>
<glyph glyph-name="environments" unicode="&#57388;" d="M256 454c-110 0-199-89-199-199 0-110 89-199 199-199 0 0 0 0 0 0l2-1 0 1c109 1 197 90 197 199 0 110-89 199-199 199z m114-204l0 0 0 5c0 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 2l0 4 0 0c-1 22-4 43-10 63 21 5 36 11 46 15 15-26 24-56 24-87 0-30-8-59-23-85-8 4-23 11-47 16 6 21 9 42 10 64z m-19 102c-13 30-30 53-46 69 34-10 64-30 86-57-9-4-22-8-40-12z m-6-95c0-23-4-45-11-66-20 4-42 6-65 6l0 120c23 1 45 3 66 6 6-21 10-43 10-66z m-46-170c17 17 37 41 52 75 20-5 34-10 42-13-24-31-57-53-94-62z m-30 256l0 77c17-14 40-37 56-73-17-2-36-4-56-4z m0-248l0 77c20-1 39-3 56-5-16-35-40-59-56-72z m-83 252c17 35 40 59 56 73l0-77c-19 0-38 1-56 4z m-19-90c0 23 4 45 10 65 20-3 42-5 65-5l0-120c-23 0-45-1-65-4-6 20-10 42-10 64z m-47 106c22 28 52 48 86 58-15-16-33-39-46-70-18 4-31 8-40 12z m40-199c15-36 37-61 53-77-38 10-72 32-96 64 9 4 24 9 43 13z m82 8l0-77c-16 13-39 37-56 73 18 2 37 3 56 4z m-100 92l-1 0 0-4c0-1 0-1 0-2 0 0 0 0 0-1 0 0 0 0 0-1 0 0 0-1 0-1l0-5 1 0c0-21 4-42 10-62-23-5-39-10-49-15-14 25-21 53-21 82 0 30 8 60 23 86 10-4 25-10 47-14-6-20-10-41-10-63z"/>
<glyph glyph-name="wand" unicode="&#57389;" d="M438 107l-80 78c-5 5-12 7-18 6l-77 78c-5 5-14 5-19 0-5-5-6-14 0-19l77-79c0-6 2-12 7-17l80-78c5-4 10-6 15-6 6 0 12 2 16 6 8 9 8 22-1 31z m-185 209l-28 0 0 27c0 6-5 11-12 11-6 0-11-5-11-11l0-27-27 0c-6 0-11-5-11-12 0-6 5-11 11-11l27 0 0-28c0-6 5-11 11-11 7 0 12 5 12 11l0 28 28 0c6 0 11 5 11 11 0 7-5 12-11 12z m51-22c-6 0-10 4-10 10 0 5 4 10 10 10 15 0 31 0 45 0 6 0 10-5 10-10 0-6-4-10-10-10-14 0-30 0-45 0 0 0 0 0 0 0z m-184 0c0 0 0 0 0 0-12 0-26 0-40 0 0 0 0 0 0 0-5 0-10 4-10 10 0 5 5 10 10 10 14 0 28 0 41 0 5 0 10-5 10-10-1-6-5-10-11-10z m94-129c-5 0-10 4-10 9 0 14 0 26 0 40 0 5 5 9 10 9 0 0 0 0 0 0 6 0 10-5 10-10 0-13 0-25 0-38 0-6-4-10-10-10 0 0 0 0 0 0z m0 223c-5 0-10 4-10 9 0 14 0 26 0 40 0 5 5 9 10 9 6 0 10-5 10-10 0-13 0-25 0-38 0-6-4-10-10-10 0 0 0 0 0 0z m-68-26c-2 0-4 0-6 2-6 5-11 10-16 15-4 5-8 9-12 13-4 4-5 10-1 14 4 4 10 4 14 1 5-5 9-9 14-14 4-5 9-10 14-14 4-4 4-10 1-14-2-2-5-3-8-3z m-24-159c-3 0-5 1-7 3-4 3-4 10-1 14 5 5 9 10 14 14 5 4 9 8 12 12 4 4 10 5 14 1 4-4 5-10 1-14-4-5-9-10-13-14-5-4-9-8-13-12-2-2-4-4-7-4z m157 157c-3 0-5 1-6 2-5 3-6 10-2 14 4 6 10 11 15 16 5 4 10 8 13 12 3 5 10 6 14 2 4-3 5-9 2-14-5-6-10-10-15-15-5-4-10-8-13-13-2-2-5-4-8-4z"/>
<glyph glyph-name="market" unicode="&#116;" d="M428 332c-2 4-7 7-12 7-5 1-10 1-14 1-5 0-10 1-15 1-27 1-51 2-75 3-23 1-47 2-74 3-10 0-18 1-27 1-9 0-18 1-27 1l-1 0 0 1c-2 8-8 26-12 39-2 5-3 9-4 12-2 8-7 12-16 12l-48 0c-9 0-16-6-16-15 0-8 7-14 16-14l32 0c5 0 7-3 8-7l0 0c6-25 13-51 19-76 7-24 13-50 19-75 2-9 3-16 0-24-1-4-2-9-3-14-1-4-2-9-3-14-2-8-2-14 1-17 3-4 8-6 16-6l190 0c4 1 7 3 9 6 3 3 4 8 3 12-1 8-7 12-17 12l-170 0 1 1c0 1 1 4 1 6 2 7 5 17 4 20l0 1c1 6 5 10 12 10 8 0 16 0 24 0 43 2 87 3 128 5 8 1 12 4 15 10 8 15 29 65 36 86l0 0c3 4 3 8 0 12z m-45-224c0-16-13-30-29-30-16 0-29 13-29 30 0 15 13 30 29 30 16-1 29-15 29-30z m-177 1c0-16-14-29-29-29-16 0-29 14-29 29 0 17 13 30 29 30 16-1 29-14 29-30z"/>
<glyph glyph-name="wear" unicode="&#57390;" d="M451 180c3 1 5 4 6 7 1 3 1 7-1 10-2 3-4 5-8 6-3 1-6 1-10-1-6-4-13-5-19-6-3-1-6-1-7-1-1 0-1 0-1 0l-1 0-26 0 0 66c0 9-3 43-27 73-24 29-58 44-101 44-54 0-84-24-100-44-24-30-26-64-27-73l0-66-17 0c-2 0-4 0-8 1-8 1-17 3-27 7-7 3-14-1-17-7-2-7 1-14 8-17 14-6 27-8 33-9 3 0 6 0 8-1 1 0 1 0 2 0l299 0c1 0 2 0 3 0l0 0c2 1 6 1 10 2 6 1 18 3 28 9z m-295 80c0 15 6 39 20 58 18 22 45 34 80 34 35 0 62-12 80-34 15-19 21-43 22-58l0-6-202 0z m202-65l-202 0 0 33 202 0z"/>
<glyph glyph-name="certificate" unicode="&#57392;" d="M168 320l172 0c7 0 14 6 14 14 0 7-7 13-14 13l-172 0c-8 0-14-6-14-13 0-8 6-14 14-14z m0-55l63 0c7 0 13 6 13 13 0 8-6 13-13 13l-63 0c-8 0-14-5-14-13 0-7 6-13 14-13z m0-57l46 0c8 0 14 6 14 14 0 7-6 14-14 14l-46 0c-8 0-14-7-14-14 0-8 6-14 14-14z m-65 210l0-283 139 0c7 0 12 6 12 13 0 6-5 12-12 12l-114 0 0 233 256 0 0-83c0-7 6-12 13-12 6 0 12 5 12 12l0 108z m304-200c0 43-35 78-78 78-43 0-78-35-78-78 0-27 14-52 37-66l-3-69c0-5 2-9 6-11 5-3 10-2 13 1l27 20 26-21c2-2 5-3 8-3 2 0 4 0 5 1 4 2 7 7 7 12l-4 72c21 14 34 38 34 64z m-68-101c-4 4-10 4-14 1l-15-11 1 36c6-2 12-2 18-2 7 0 14 1 20 2l2-36z"/>
<glyph glyph-name="gift" unicode="&#57393;" d="M342 330c1 1 3 2 4 3 19 17 21 47 4 66-9 10-22 16-35 16-6 0-12-1-17-3-6 19-24 33-45 33-22 0-41-15-46-35-5 2-11 3-17 3-13 0-26-6-35-16-17-20-15-50 5-66 0 0 0 0 0-1l-82 0 0-84 27 0 0-166 298 0 0 166 27 0 0 84z m-45 50l0 1c1 1 2 2 4 3 5 5 10 6 14 6 6 0 12-3 16-8 4-4 6-9 5-15 0-6-3-11-7-15-2-2-3-3-4-3l0 0 0 0c-5-1-27-10-52-18 10 22 22 44 24 49z m-44 40c12 0 21-9 21-21 0-3 0-4 0-5l0 0 0 0c-2-5-11-24-21-48-10 23-19 42-21 47l0 1c0 1-1 3-1 5 0 12 10 21 22 21z m-79-40c4 5 10 8 16 8 4 0 9-1 14-5 2-2 3-3 4-4l0 0 0 0c2-5 11-23 22-47-24 7-44 13-49 15l-1 0c-1 1-2 2-4 3-9 8-10 21-2 30z m67-274l-110 0 0 140 110 0z m0 166l-137 0 0 32 137 0z m136-166l-111 0 0 140 111 0z m27 166l-138 0 0 32 138 0z"/>
<glyph glyph-name="update" unicode="&#57394;" d="M380 170c-7 0-13-6-13-13l0-12c0-5-4-9-9-9l-203 0c-5 0-9 4-9 9l0 114 33-27c6-4 14-4 19 2 4 5 4 13-2 18l-54 47c-3 2-5 3-8 3-4 0-7-1-9-3l-54-47c-6-5-6-13-1-18 4-6 13-6 18-1l32 26 0-114c0-19 16-35 35-35l203 0c19 0 35 16 35 35l0 12c0 7-6 13-13 13z m-248 153c7 0 13 5 13 13l0 12c0 5 4 8 9 8l203 0c5 0 9-4 9-8l0-115-33 27c-5 5-14 4-18-1-5-6-5-14 1-19l54-47c3-2 5-3 9-3 3 0 6 1 8 3l54 47c6 5 6 13 2 19-5 5-13 6-19 1l-32-27 0 115c0 19-16 34-35 34l-203 0c-19 0-35-15-35-34l0-12c0-8 6-13 13-13z"/>
<glyph glyph-name="uninstall" unicode="&#57395;" d="M83 227c-7 0-13-6-13-13l0-78c0-19 16-35 35-35l297 0c19 0 35 16 35 35l0 78c0 7-6 13-13 13-7 0-13-6-13-13l0-78c0-5-4-9-9-9l-297 0c-5 0-9 4-9 9l0 78c0 7-6 13-13 13z m191 47l50 50c5 5 5 14 0 19-5 5-13 5-19 0l-50-50-50 50c-5 5-13 5-19 0-5-5-5-14 0-19l50-50-50-50c-5-5-5-14 0-19 5-5 14-5 19 0l50 50 50-50c5-5 14-5 19 0 5 5 5 14 0 19z"/>
<glyph glyph-name="install" unicode="&#57391;" d="M83 227c-7 0-13-6-13-13l0-78c0-19 16-35 35-35l297 0c19 0 35 16 35 35l0 78c0 7-6 13-13 13-7 0-13-6-13-13l0-78c0-5-4-9-9-9l-297 0c-5 0-9 4-9 9l0 78c0 7-6 13-13 13z m170 171l0-155-33 27c-6 5-14 5-19-1-4-5-4-14 2-18l54-48c3-2 5-3 8-3 3 0 7 1 9 3l54 48c5 4 6 13 1 18-4 5-13 6-18 1l-32-27 0 154c0 8-6 13-13 13-7 0-13-5-13-12z"/>
<glyph glyph-name="ellipsis-vertical" unicode="&#57396;" d="M276 178c0-14-11-24-24-24-14 0-25 10-25 24 0 13 11 24 25 24 13 0 24-11 24-24z m0 78c0-14-11-24-24-24-14 0-25 10-25 24 0 13 11 24 25 24 13 0 24-11 24-24z m0 78c0-14-11-24-24-24-14 0-25 10-25 24 0 13 11 24 25 24 13 0 24-11 24-24z"/>
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View file

@ -12,7 +12,7 @@
<body>
<div class="container">
<h1>HiFi Glyphs</h1>
<p class="small">This font was created for use in<a href="http://highfidelity.io/">High Fidelity</a></p>
<p class="small">This font was created for use in <a href="http://highfidelity.io/">High Fidelity</a></p>
<h2>CSS mapping</h2>
<ul class="glyphs css-mapping">
<li>
@ -87,10 +87,6 @@
<div class="icon icon-edit"></div>
<input type="text" readonly="readonly" value="edit">
</li>
<li>
<div class="icon icon-market"></div>
<input type="text" readonly="readonly" value="market">
</li>
<li>
<div class="icon icon-directory"></div>
<input type="text" readonly="readonly" value="directory">
@ -567,6 +563,46 @@
<div class="icon icon-image"></div>
<input type="text" readonly="readonly" value="image">
</li>
<li>
<div class="icon icon-environments"></div>
<input type="text" readonly="readonly" value="environments">
</li>
<li>
<div class="icon icon-wand"></div>
<input type="text" readonly="readonly" value="wand">
</li>
<li>
<div class="icon icon-market"></div>
<input type="text" readonly="readonly" value="market">
</li>
<li>
<div class="icon icon-wear"></div>
<input type="text" readonly="readonly" value="wear">
</li>
<li>
<div class="icon icon-certificate"></div>
<input type="text" readonly="readonly" value="certificate">
</li>
<li>
<div class="icon icon-gift"></div>
<input type="text" readonly="readonly" value="gift">
</li>
<li>
<div class="icon icon-update"></div>
<input type="text" readonly="readonly" value="update">
</li>
<li>
<div class="icon icon-uninstall"></div>
<input type="text" readonly="readonly" value="uninstall">
</li>
<li>
<div class="icon icon-install"></div>
<input type="text" readonly="readonly" value="install">
</li>
<li>
<div class="icon icon-ellipsis-vertical"></div>
<input type="text" readonly="readonly" value="ellipsis-vertical">
</li>
</ul>
<h2>Character mapping</h2>
<ul class="glyphs character-mapping">
@ -642,10 +678,6 @@
<div data-icon="s" class="icon"></div>
<input type="text" readonly="readonly" value="s">
</li>
<li>
<div data-icon="t" class="icon"></div>
<input type="text" readonly="readonly" value="t">
</li>
<li>
<div data-icon="u" class="icon"></div>
<input type="text" readonly="readonly" value="u">
@ -1122,6 +1154,46 @@
<div data-icon="&#xe02a;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe02a;">
</li>
<li>
<div data-icon="&#xe02c;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe02c;">
</li>
<li>
<div data-icon="&#xe02d;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe02d;">
</li>
<li>
<div data-icon="t" class="icon"></div>
<input type="text" readonly="readonly" value="t">
</li>
<li>
<div data-icon="&#xe02e;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe02e;">
</li>
<li>
<div data-icon="&#xe030;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe030;">
</li>
<li>
<div data-icon="&#xe031;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe031;">
</li>
<li>
<div data-icon="&#xe032;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe032;">
</li>
<li>
<div data-icon="&#xe033;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe033;">
</li>
<li>
<div data-icon="&#xe02f;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe02f;">
</li>
<li>
<div data-icon="&#xe034;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe034;">
</li>
</ul>
</div>
<script>(function() {

View file

@ -92,9 +92,6 @@
.icon-edit:before {
content: "\73";
}
.icon-market:before {
content: "\74";
}
.icon-directory:before {
content: "\75";
}
@ -452,3 +449,33 @@
.icon-image:before {
content: "\e02a";
}
.icon-environments:before {
content: "\e02c";
}
.icon-wand:before {
content: "\e02d";
}
.icon-market:before {
content: "\74";
}
.icon-wear:before {
content: "\e02e";
}
.icon-certificate:before {
content: "\e030";
}
.icon-gift:before {
content: "\e031";
}
.icon-update:before {
content: "\e032";
}
.icon-uninstall:before {
content: "\e033";
}
.icon-install:before {
content: "\e02f";
}
.icon-ellipsis-vertical:before {
content: "\e034";
}

View file

@ -24,6 +24,7 @@ Slider {
property alias minimumValue: slider.from
property alias maximumValue: slider.to
property bool tickmarksEnabled: false
height: hifi.fontSizes.textFieldInput + 14 // Match height of TextField control.
y: sliderLabel.visible ? sliderLabel.height + sliderLabel.anchors.bottomMargin : 0

View file

@ -20,6 +20,7 @@ SpinBox {
property int colorScheme: hifi.colorSchemes.light
readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light
property string label: ""
property string suffix: ""
property string labelInside: ""
property color colorLabelInside: hifi.colors.white
property real controlHeight: height + (spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0)
@ -34,8 +35,11 @@ SpinBox {
property real realTo: 100.0
property real realStepSize: 1.0
signal editingFinished()
implicitHeight: height
implicitWidth: width
editable: true
padding: 0
leftPadding: 0
@ -68,16 +72,16 @@ SpinBox {
}
validator: DoubleValidator {
bottom: Math.min(spinBox.from, spinBox.to)*spinBox.factor
top: Math.max(spinBox.from, spinBox.to)*spinBox.factor
bottom: Math.min(spinBox.from, spinBox.to)
top: Math.max(spinBox.from, spinBox.to)
}
textFromValue: function(value, locale) {
return parseFloat(value*1.0/factor).toFixed(decimals);
return parseFloat(value/factor).toFixed(decimals);
}
valueFromText: function(text, locale) {
return Number.fromLocaleString(locale, text);
return Number.fromLocaleString(locale, text)*factor;
}
@ -88,12 +92,14 @@ SpinBox {
: (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText)
selectedTextColor: hifi.colors.black
selectionColor: hifi.colors.primaryHighlight
text: spinBox.textFromValue(spinBox.value, spinBox.locale)
text: spinBox.textFromValue(spinBox.value, spinBox.locale) + suffix
verticalAlignment: Qt.AlignVCenter
leftPadding: spinBoxLabelInside.visible ? 30 : hifi.dimensions.textPadding
//rightPadding: hifi.dimensions.spinnerSize
width: spinBox.width - hifi.dimensions.spinnerSize
onEditingFinished: spinBox.editingFinished()
}
up.indicator: Item {
x: spinBox.width - implicitWidth - 5
y: 1

View file

@ -8,8 +8,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import "../styles-uit"
import "../controls-uit" as HifiControls
@ -28,17 +29,11 @@ TextField {
property int roundedBorderRadius: 4
property bool error: false;
property bool hasClearButton: false;
property alias textColor: textField.color
property string leftPermanentGlyph: "";
property string centerPlaceholderGlyph: "";
placeholderText: textField.placeholderText
property bool rightAnchorSet: false;
anchors.onRightChanged: {
rightAnchorSet = true;
}
font.family: "Fira Sans"
font.pixelSize: hifi.fontSizes.textFieldInput
height: implicitHeight + 3 // Make surrounding box higher so that highlight is vertically centered.
@ -49,56 +44,42 @@ TextField {
// workaround for https://bugreports.qt.io/browse/QTBUG-49297
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Return:
case Qt.Key_Enter:
event.accepted = true;
case Qt.Key_Return:
case Qt.Key_Enter:
event.accepted = true;
// emit accepted signal manually
if (acceptableInput) {
accepted();
}
// emit accepted signal manually
if (acceptableInput) {
accepted();
}
}
}
Text {
id: placeholder
x: textField.leftPadding
y: textField.topPadding
width: textField.width - (textField.leftPadding + textField.rightPadding)
height: textField.height - (textField.topPadding + textField.bottomPadding)
text: textField.placeholderText
font: textField.font
color: textField.placeholderTextColor
verticalAlignment: textField.verticalAlignment
visible: !textField.length && !textField.preeditText && (!textField.activeFocus || textField.horizontalAlignment !== Qt.AlignHCenter)
elide: Text.ElideRight
}
color: {
if (isLightColorScheme) {
if (textField.activeFocus) {
hifi.colors.black
style: TextFieldStyle {
id: style;
textColor: {
if (isLightColorScheme) {
if (textField.activeFocus) {
hifi.colors.black
} else {
hifi.colors.lightGray
}
} else if (isFaintGrayColorScheme) {
if (textField.activeFocus) {
hifi.colors.black
} else {
hifi.colors.lightGray
}
} else {
hifi.colors.lightGray
}
} else if (isFaintGrayColorScheme) {
if (textField.activeFocus) {
hifi.colors.black
} else {
hifi.colors.lightGray
}
} else {
if (textField.activeFocus) {
hifi.colors.white
} else {
hifi.colors.lightGrayText
if (textField.activeFocus) {
hifi.colors.white
} else {
hifi.colors.lightGrayText
}
}
}
}
background: Rectangle {
color: {
background: Rectangle {
color: {
if (isLightColorScheme) {
if (textField.activeFocus) {
hifi.colors.white
@ -119,22 +100,22 @@ TextField {
}
}
}
border.color: textField.error ? hifi.colors.redHighlight :
border.color: textField.error ? hifi.colors.redHighlight :
(textField.activeFocus ? hifi.colors.primaryHighlight : (hasDefocusedBorder ? (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray) : color))
border.width: textField.activeFocus || hasRoundedBorder || textField.error ? 1 : 0
border.width: textField.activeFocus || hasRoundedBorder || textField.error ? 1 : 0
radius: isSearchField ? textField.height / 2 : (hasRoundedBorder ? roundedBorderRadius : 0)
HiFiGlyphs {
HiFiGlyphs {
text: textField.leftPermanentGlyph;
color: textColor;
size: hifi.fontSizes.textFieldSearchIcon;
anchors.left: parent.left;
anchors.verticalCenter: parent.verticalCenter;
anchors.leftMargin: hifi.dimensions.textPadding - 2;
visible: text;
}
color: textColor;
size: hifi.fontSizes.textFieldSearchIcon;
anchors.left: parent.left;
anchors.verticalCenter: parent.verticalCenter;
anchors.leftMargin: hifi.dimensions.textPadding - 2;
visible: text;
}
HiFiGlyphs {
HiFiGlyphs {
text: textField.centerPlaceholderGlyph;
color: textColor;
size: parent.height;
@ -144,57 +125,48 @@ TextField {
}
HiFiGlyphs {
text: hifi.glyphs.search
color: textColor
size: hifi.fontSizes.textFieldSearchIcon
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: hifi.dimensions.textPadding - 2
visible: isSearchField
}
text: hifi.glyphs.search
color: textColor
size: hifi.fontSizes.textFieldSearchIcon
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: hifi.dimensions.textPadding - 2
visible: isSearchField
}
HiFiGlyphs {
text: hifi.glyphs.error
color: textColor
size: 40
anchors.right: parent.right
anchors.rightMargin: hifi.dimensions.textPadding - 2
anchors.verticalCenter: parent.verticalCenter
visible: hasClearButton && textField.text !== "";
HiFiGlyphs {
text: hifi.glyphs.error
color: textColor
size: 40
anchors.right: parent.right
anchors.rightMargin: hifi.dimensions.textPadding - 2
anchors.verticalCenter: parent.verticalCenter
visible: hasClearButton && textField.text !== "";
MouseArea {
anchors.fill: parent;
onClicked: {
textField.text = "";
MouseArea {
anchors.fill: parent;
onClicked: {
textField.text = "";
}
}
}
}
placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray
selectedTextColor: hifi.colors.black
selectionColor: hifi.colors.primaryHighlight
padding.left: hasRoundedBorder ? textField.height / 2 : ((isSearchField || textField.leftPermanentGlyph !== "") ? textField.height - 2 : 0) + hifi.dimensions.textPadding
padding.right: (hasClearButton ? textField.height - 2 : 0) + hifi.dimensions.textPadding
}
property color placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray
selectedTextColor: hifi.colors.black
selectionColor: hifi.colors.primaryHighlight
leftPadding: hasRoundedBorder ? textField.height / 2 : ((isSearchField || textField.leftPermanentGlyph !== "") ? textField.height - 2 : 0) + hifi.dimensions.textPadding
rightPadding: (hasClearButton ? textField.height - 2 : 0) + hifi.dimensions.textPadding
HifiControls.Label {
id: textFieldLabel
text: textField.label
colorScheme: textField.colorScheme
anchors.left: parent.left
Binding on anchors.right {
when: rightAnchorSet
value: textField.right
}
Binding on wrapMode {
when: rightAnchorSet
value: Text.WordWrap
}
anchors.right: parent.right
anchors.bottom: parent.top
anchors.bottomMargin: 3
wrapMode: Text.WordWrap
visible: label != ""
}
}

View file

@ -18,11 +18,11 @@ Preference {
height: control.height + hifi.dimensions.controlInterlineHeight
Component.onCompleted: {
spinner.value = preference.value;
spinner.realValue = preference.value;
}
function save() {
preference.value = spinner.value;
preference.value = spinner.realValue;
preference.save();
}

View file

@ -21,7 +21,7 @@ Preference {
Component.onCompleted: {
slider.value = preference.value;
spinner.value = preference.value;
spinner.realValue = preference.value;
}
function save() {
@ -60,7 +60,7 @@ Preference {
maximumValue: MyAvatar.getDomainMaxScale()
stepSize: preference.step
onValueChanged: {
spinner.value = value
spinner.realValue = value
}
anchors {
right: spinner.left
@ -73,12 +73,12 @@ Preference {
SpinBox {
id: spinner
decimals: preference.decimals
value: preference.value
realValue: preference.value
minimumValue: MyAvatar.getDomainMinScale()
maximumValue: MyAvatar.getDomainMaxScale()
width: 100
onValueChanged: {
slider.value = value;
slider.value = realValue;
}
anchors {
right: button.left
@ -92,10 +92,10 @@ Preference {
id: button
onClicked: {
if (spinner.maximumValue >= 1) {
spinner.value = 1
spinner.realValue = 1
slider.value = 1
} else {
spinner.value = spinner.maximumValue
spinner.realValue = spinner.maximumValue
slider.value = spinner.maximumValue
}
}
@ -108,4 +108,4 @@ Preference {
colorScheme: hifi.colorSchemes.dark
}
}
}
}

View file

@ -30,6 +30,16 @@ Item {
color: "black"
opacity: 0.5
radius: popupRadius
MouseArea {
anchors.fill: parent;
hoverEnabled: true;
acceptedButtons: Qt.LeftButton;
propagateComposedEvents: false;
onClicked: {
letterbox.visible = false;
}
}
}
Rectangle {
id: textContainer;
@ -38,6 +48,14 @@ Item {
anchors.centerIn: parent
radius: popupRadius
color: "white"
// Prevent dismissing the popup by clicking on the textContainer
MouseArea {
anchors.fill: parent;
hoverEnabled: true;
propagateComposedEvents: false;
}
Item {
id: contentContainer
width: parent.width - 50
@ -135,11 +153,4 @@ Item {
}
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
letterbox.visible = false;
}
}
}

View file

@ -257,9 +257,14 @@ Rectangle {
lightboxPopup.bodyImageSource = msg.securityImageSource;
lightboxPopup.bodyText = lightboxPopup.securityPicBodyText;
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.button2text = "GO TO WALLET";
lightboxPopup.button2method = "sendToParent({method: 'checkout_openWallet'});";
lightboxPopup.button2method = function() {
lightboxPopup.visible = false;
sendToScript({method: 'checkout_openWallet'});
};
lightboxPopup.visible = true;
} else {
sendToScript(msg);
@ -623,10 +628,16 @@ Rectangle {
lightboxPopup.bodyText = "You will not be able to replace this domain's content with <b>" + root.itemName +
" </b>until the server owner gives you 'Replace Content' permissions.<br><br>Are you sure you want to purchase this content set?";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "Commerce.buy('" + root.itemId + "', " + root.itemPrice + ");" +
"root.visible = false; buyButton.enabled = false; loading.visible = true;";
lightboxPopup.button2method = function() {
Commerce.buy(root.itemId, root.itemPrice);
lightboxPopup.visible = false;
buyButton.enabled = false;
loading.visible = true;
};
lightboxPopup.visible = true;
} else {
buyButton.enabled = false;
@ -771,19 +782,30 @@ Rectangle {
"<a href='https://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/restoring-sandbox-content'>" +
"click here to open info on your desktop browser.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "Commerce.replaceContentSet('" + root.itemHref + "', '" + root.certificateId + "');" +
"root.visible = false;rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start();" +
"UserActivityLogger.commerceEntityRezzed('" + root.itemId + "', 'checkout', '" + root.itemType + "');";
lightboxPopup.button2method = function() {
Commerce.replaceContentSet(root.itemHref);
lightboxPopup.visible = false;
rezzedNotifContainer.visible = true;
rezzedNotifContainerTimer.start();
UserActivityLogger.commerceEntityRezzed(root.itemId, 'checkout', root.itemType);
};
lightboxPopup.visible = true;
} else if (root.itemType === "avatar") {
lightboxPopup.titleText = "Change Avatar";
lightboxPopup.bodyText = "This will change your current avatar to " + root.itemName + " while retaining your wearables.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "MyAvatar.useFullAvatarURL('" + root.itemHref + "'); root.visible = false;";
lightboxPopup.button2method = function() {
MyAvatar.useFullAvatarURL(root.itemHref);
lightboxPopup.visible = false;
};
lightboxPopup.visible = true;
} else if (root.itemType === "app") {
if (root.isInstalled) {
@ -822,9 +844,14 @@ Rectangle {
lightboxPopup.bodyText = "You don't have permission to rez certified items in this domain.<br><br>" +
"Use the <b>GOTO app</b> to visit another domain or <b>go to your own sandbox.</b>";
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.button2text = "OPEN GOTO";
lightboxPopup.button2method = "sendToParent({method: 'purchases_openGoTo'});";
lightboxPopup.button2method = function() {
sendToScript({method: 'purchases_openGoTo'});
lightboxPopup.visible = false;
};
lightboxPopup.visible = true;
}
}
@ -918,7 +945,9 @@ Rectangle {
lightboxPopup.bodyText = 'Your item is marked "pending" while your purchase is being confirmed.<br><br>' +
'Confirmations usually take about 90 seconds.';
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.visible = true;
}
}

View file

@ -26,10 +26,10 @@ Rectangle {
property string bodyText;
property string button1color: hifi.buttons.noneBorderlessGray;
property string button1text;
property string button1method;
property string button2color: hifi.buttons.noneBorderless;
property var button1method;
property string button2color: hifi.buttons.blue;
property string button2text;
property string button2method;
property var button2method;
property string buttonLayout: "leftright";
readonly property string securityPicBodyText: "When you see your Security Pic, your actions and data are securely making use of your " +
@ -72,7 +72,7 @@ Rectangle {
anchors.right: parent.right;
anchors.rightMargin: 30;
height: paintedHeight;
color: hifi.colors.baseGray;
color: hifi.colors.black;
size: 24;
verticalAlignment: Text.AlignTop;
wrapMode: Text.WordWrap;
@ -104,7 +104,7 @@ Rectangle {
anchors.right: parent.right;
anchors.rightMargin: 30;
height: paintedHeight;
color: hifi.colors.baseGray;
color: hifi.colors.black;
size: 20;
verticalAlignment: Text.AlignTop;
wrapMode: Text.WordWrap;
@ -129,15 +129,15 @@ Rectangle {
colorScheme: hifi.colorSchemes.light;
anchors.top: root.buttonLayout === "leftright" ? parent.top : parent.top;
anchors.left: parent.left;
anchors.leftMargin: 10;
anchors.leftMargin: root.buttonLayout === "leftright" ? 30 : 10;
anchors.right: root.buttonLayout === "leftright" ? undefined : parent.right;
anchors.rightMargin: root.buttonLayout === "leftright" ? undefined : 10;
width: root.buttonLayout === "leftright" ? (root.button2text ? parent.width/2 - anchors.leftMargin*2 : parent.width - anchors.leftMargin * 2) :
(undefined);
height: 50;
height: 40;
text: root.button1text;
onClicked: {
eval(button1method);
button1method();
}
}
@ -152,12 +152,12 @@ Rectangle {
anchors.left: root.buttonLayout === "leftright" ? undefined : parent.left;
anchors.leftMargin: root.buttonLayout === "leftright" ? undefined : 10;
anchors.right: parent.right;
anchors.rightMargin: 10;
anchors.rightMargin: root.buttonLayout === "leftright" ? 30 : 10;
width: root.buttonLayout === "leftright" ? parent.width/2 - anchors.rightMargin*2 : undefined;
height: 50;
height: 40;
text: root.button2text;
onClicked: {
eval(button2method);
button2method();
}
}
}
@ -174,10 +174,10 @@ Rectangle {
root.bodyText = "";
root.button1color = hifi.buttons.noneBorderlessGray;
root.button1text = "";
root.button1method = "";
root.button2color = hifi.buttons.noneBorderless;
root.button1method = function() {};
root.button2color = hifi.buttons.blue;
root.button2text = "";
root.button2method = "";
root.button2method = function() {};
root.buttonLayout = "leftright";
}
//

View file

@ -1,6 +1,6 @@
//
// ConnectionItem.qml
// qml/hifi/commerce/wallet/sendMoney
// qml/hifi/commerce/common/sendAsset
//
// ConnectionItem
//
@ -113,7 +113,7 @@ Item {
text: "CHOOSE";
onClicked: {
var msg = { method: 'chooseConnection', userName: root.userName, profilePicUrl: root.profilePicUrl };
sendToSendMoney(msg);
sendToParent(msg);
}
}
}
@ -121,7 +121,7 @@ Item {
//
// FUNCTION DEFINITIONS START
//
signal sendToSendMoney(var msg);
signal sendToParent(var msg);
//
// FUNCTION DEFINITIONS END
//

View file

@ -1,6 +1,6 @@
//
// RecipientDisplay.qml
// qml/hifi/commerce/wallet/sendMoney
// qml/hifi/commerce/common/sendAsset
//
// RecipientDisplay
//
@ -18,7 +18,7 @@ import QtGraphicalEffects 1.0
import "../../../../styles-uit"
import "../../../../controls-uit" as HifiControlsUit
import "../../../../controls" as HifiControls
import "../../common" as HifiCommerceCommon
import "../" as HifiCommerceCommon
Item {
HifiConstants { id: hifi; }

View file

@ -13,7 +13,6 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import QtGraphicalEffects 1.0
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import "../../../styles-uit"
@ -22,14 +21,11 @@ import "../../../controls" as HifiControls
import "../wallet" as HifiWallet
import TabletScriptingInterface 1.0
// references XXX from root context
Item {
HifiConstants { id: hifi; }
id: root;
property string purchaseStatus;
property bool purchaseStatusChanged;
property string itemName;
property string itemId;
property string itemPreviewImageUrl;
@ -46,16 +42,18 @@ Item {
property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar];
property bool showConfirmation: false;
property bool hasPermissionToRezThis;
property bool permissionExplanationCardVisible;
property bool cardBackVisible;
property bool isInstalled;
property string wornEntityID;
property string upgradeUrl;
property string upgradeTitle;
property bool updateAvailable: root.upgradeUrl !== "" && !root.isShowingMyItems;
property bool isShowingMyItems;
property string originalStatusText;
property string originalStatusColor;
height: (root.upgradeUrl === "" || root.isShowingMyItems) ? 110 : 150;
height: 102;
width: parent.width;
Connections {
@ -99,16 +97,6 @@ Item {
}
}
onPurchaseStatusChangedChanged: {
if (root.purchaseStatusChanged === true && root.purchaseStatus === "confirmed") {
root.originalStatusText = statusText.text;
root.originalStatusColor = statusText.color;
statusText.text = "CONFIRMED!";
statusText.color = hifi.colors.blueAccent;
confirmedTimer.start();
}
}
onShowConfirmationChanged: {
if (root.showConfirmation) {
rezzedNotifContainer.visible = true;
@ -118,35 +106,307 @@ Item {
}
}
Timer {
id: confirmedTimer;
interval: 3000;
onTriggered: {
statusText.text = root.originalStatusText;
statusText.color = root.originalStatusColor;
root.purchaseStatusChanged = false;
Rectangle {
id: background;
z: 10;
color: Qt.rgba(0, 0, 0, 0.25);
anchors.fill: parent;
}
Flipable {
id: flipable;
z: 50;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.top: parent.top;
height: root.height - 2;
front: mainContainer;
back: Rectangle {
anchors.fill: parent;
color: hifi.colors.white;
Item {
id: closeContextMenuContainer;
anchors.right: parent.right;
anchors.rightMargin: 8;
anchors.top: parent.top;
anchors.topMargin: 8;
width: 30;
height: width;
HiFiGlyphs {
id: closeContextMenuGlyph;
text: hifi.glyphs.close;
anchors.fill: parent;
size: 26;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
color: hifi.colors.black;
}
MouseArea {
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
root.sendToPurchases({ method: 'flipCard', closeAll: true });
}
onEntered: {
closeContextMenuGlyph.text = hifi.glyphs.closeInverted;
}
onExited: {
closeContextMenuGlyph.text = hifi.glyphs.close;
}
}
}
Rectangle {
id: contextCard;
z: 2;
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.right: closeContextMenuContainer.left;
anchors.rightMargin: 8;
color: hifi.colors.white;
Component {
id: contextCardButton;
Item {
property alias buttonGlyphText: buttonGlyph.text;
property alias buttonText: buttonText.text;
property string buttonColor: hifi.colors.black;
property string buttonColor_hover: hifi.colors.blueHighlight;
property alias enabled: buttonMouseArea.enabled;
property var buttonClicked;
HiFiGlyphs {
id: buttonGlyph;
anchors.top: parent.top;
anchors.topMargin: 4;
anchors.horizontalCenter: parent.horizontalCenter;
anchors.bottom: parent.verticalCenter;
width: parent.width;
size: 40;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
color: buttonMouseArea.enabled ? buttonColor : hifi.colors.lightGrayText;
}
RalewayRegular {
id: buttonText;
anchors.top: parent.verticalCenter;
anchors.topMargin: 4;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 12;
anchors.horizontalCenter: parent.horizontalCenter;
width: parent.width;
color: buttonMouseArea.enabled ? buttonColor : hifi.colors.lightGrayText;
size: 16;
wrapMode: Text.Wrap;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
MouseArea {
id: buttonMouseArea;
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
parent.buttonClicked();
}
onEntered: {
buttonGlyph.color = buttonColor_hover;
buttonText.color = buttonColor_hover;
}
onExited: {
buttonGlyph.color = buttonColor;
buttonText.color = buttonColor;
}
}
}
}
Loader {
id: giftButton;
visible: !root.isShowingMyItems;
sourceComponent: contextCardButton;
anchors.right: parent.right;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
width: 62;
onLoaded: {
item.enabled = (root.purchaseStatus === "confirmed");
item.buttonGlyphText = hifi.glyphs.gift;
item.buttonText = "Gift";
item.buttonClicked = function() {
sendToPurchases({ method: 'flipCard', closeAll: true });
sendToPurchases({
method: 'giftAsset',
itemName: root.itemName,
certId: root.certificateId,
itemType: root.itemType,
itemHref: root.itemHref,
isInstalled: root.isInstalled,
wornEntityID: root.wornEntityID,
effectImage: root.itemPreviewImageUrl
});
}
}
}
Loader {
id: marketplaceButton;
sourceComponent: contextCardButton;
anchors.right: giftButton.visible ? giftButton.left : parent.right;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
width: 100;
onLoaded: {
item.buttonGlyphText = hifi.glyphs.market;
item.buttonText = "View in Marketplace";
item.buttonClicked = function() {
sendToPurchases({ method: 'flipCard', closeAll: true });
sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId});
}
}
}
Loader {
id: certificateButton;
sourceComponent: contextCardButton;
anchors.right: marketplaceButton.left;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
width: 100;
onLoaded: {
item.buttonGlyphText = hifi.glyphs.certificate;
item.buttonText = "View Certificate";
item.buttonClicked = function() {
sendToPurchases({ method: 'flipCard', closeAll: true });
sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId});
}
}
}
Loader {
id: uninstallButton;
visible: root.isInstalled;
sourceComponent: contextCardButton;
anchors.right: certificateButton.left;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
width: 78;
onLoaded: {
item.buttonGlyphText = hifi.glyphs.uninstall;
item.buttonText = "Uninstall";
item.buttonClicked = function() {
sendToPurchases({ method: 'flipCard', closeAll: true });
Commerce.uninstallApp(root.itemHref);
}
}
}
Loader {
id: updateButton;
visible: root.updateAvailable;
sourceComponent: contextCardButton;
anchors.right: uninstallButton.visible ? uninstallButton.left : certificateButton.left;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
width: 84;
onLoaded: {
item.buttonGlyphText = hifi.glyphs.update;
item.buttonText = "Update";
item.buttonColor = "#E2334D";
item.buttonClicked = function() {
sendToPurchases({ method: 'flipCard', closeAll: true });
sendToPurchases({method: 'updateItemClicked', itemId: root.itemId, itemEdition: root.itemEdition, upgradeUrl: root.upgradeUrl});
}
}
}
}
Rectangle {
id: permissionExplanationCard;
z: 1;
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.right: closeContextMenuContainer.left;
anchors.rightMargin: 8;
color: hifi.colors.white;
RalewayRegular {
id: permissionExplanationText;
anchors.fill: parent;
text: {
if (root.itemType === "contentSet") {
"You do not have 'Replace Content' permissions in this domain. <a href='#replaceContentPermission'>Learn more</a>";
} else if (root.itemType === "entity") {
"You do not have 'Rez Certified' permissions in this domain. <a href='#rezCertifiedPermission'>Learn more</a>";
} else {
"Hey! You're not supposed to see this. How is it even possible that you're here? Are you a developer???"
}
}
size: 16;
color: hifi.colors.baseGray;
wrapMode: Text.Wrap;
verticalAlignment: Text.AlignVCenter;
onLinkActivated: {
sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType});
}
}
}
}
transform: Rotation {
id: rotation;
origin.x: flipable.width/2;
origin.y: flipable.height/2;
axis.x: 1;
axis.y: 0;
axis.z: 0;
angle: 0;
}
states: State {
name: "back";
PropertyChanges {
target: rotation;
angle: 180;
}
when: root.cardBackVisible;
}
transitions: Transition {
SmoothedAnimation {
target: rotation;
property: "angle";
velocity: 600;
}
}
}
Rectangle {
id: mainContainer;
z: 51;
// Style
color: hifi.colors.white;
// Size
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
anchors.verticalCenter: parent.verticalCenter;
height: root.height - 10;
// START "incorrect indentation to prevent insane diffs"
Item {
id: itemContainer;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.top: parent.top;
height: 100;
height: root.height - 2;
Image {
id: itemPreviewImage;
@ -154,8 +414,9 @@ Item {
anchors.left: parent.left;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
width: height;
width: height * 1.78;
fillMode: Image.PreserveAspectCrop;
mipmap: true;
MouseArea {
anchors.fill: parent;
@ -165,218 +426,53 @@ Item {
}
}
TextMetrics {
id: itemNameTextMetrics;
font: itemName.font;
text: itemName.text;
}
RalewaySemiBold {
RalewayRegular {
id: itemName;
anchors.top: itemPreviewImage.top;
anchors.top: parent.top;
anchors.topMargin: 4;
anchors.left: itemPreviewImage.right;
anchors.leftMargin: 8;
width: !noPermissionGlyph.visible ? (buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - anchors.leftMargin) :
Math.min(itemNameTextMetrics.tightBoundingRect.width + 2,
buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - anchors.leftMargin - noPermissionGlyph.width + 2);
anchors.leftMargin: 10;
anchors.right: contextMenuButtonContainer.left;
anchors.rightMargin: 4;
height: paintedHeight;
// Text size
size: 24;
size: 20;
// Style
color: hifi.colors.blueAccent;
color: hifi.colors.black;
text: root.itemName;
elide: Text.ElideRight;
// Alignment
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter;
MouseArea {
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId});
}
onEntered: {
itemName.color = hifi.colors.blueHighlight;
}
onExited: {
itemName.color = hifi.colors.blueAccent;
}
}
}
HiFiGlyphs {
id: noPermissionGlyph;
visible: !root.hasPermissionToRezThis;
anchors.verticalCenter: itemName.verticalCenter;
anchors.left: itemName.right;
anchors.leftMargin: itemName.truncated ? -10 : -2;
text: hifi.glyphs.info;
// Size
size: 40;
width: 32;
// Style
color: hifi.colors.redAccent;
MouseArea {
anchors.fill: parent;
hoverEnabled: true;
onEntered: {
noPermissionGlyph.color = hifi.colors.redHighlight;
}
onExited: {
noPermissionGlyph.color = hifi.colors.redAccent;
}
onClicked: {
root.sendToPurchases({ method: 'openPermissionExplanationCard' });
}
}
}
Rectangle {
id: permissionExplanationCard;
z: 995;
visible: root.permissionExplanationCardVisible;
anchors.fill: parent;
color: hifi.colors.white;
RalewayRegular {
id: permissionExplanationText;
text: {
if (root.itemType === "contentSet") {
"You do not have 'Replace Content' permissions in this domain. <a href='#replaceContentPermission'>Learn more</a>";
} else if (root.itemType === "entity") {
"You do not have 'Rez Certified' permissions in this domain. <a href='#rezCertifiedPermission'>Learn more</a>";
} else {
""
}
}
size: 16;
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.right: permissionExplanationGlyph.left;
color: hifi.colors.baseGray;
wrapMode: Text.WordWrap;
verticalAlignment: Text.AlignVCenter;
onLinkActivated: {
sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType});
}
}
// "Close" button
HiFiGlyphs {
id: permissionExplanationGlyph;
text: hifi.glyphs.close;
color: hifi.colors.baseGray;
size: 26;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.right: parent.right;
width: 77;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
MouseArea {
anchors.fill: parent;
hoverEnabled: true;
onEntered: {
parent.text = hifi.glyphs.closeInverted;
}
onExited: {
parent.text = hifi.glyphs.close;
}
onClicked: {
root.sendToPurchases({ method: 'openPermissionExplanationCard', closeAll: true });
}
}
}
}
Item {
id: certificateContainer;
anchors.top: itemName.bottom;
anchors.topMargin: 4;
anchors.left: itemName.left;
anchors.right: buttonContainer.left;
anchors.rightMargin: 2;
height: 24;
HiFiGlyphs {
id: certificateIcon;
text: hifi.glyphs.scriptNew;
// Size
size: 30;
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
width: 32;
// Style
color: hifi.colors.black;
}
RalewayRegular {
id: viewCertificateText;
text: "VIEW CERTIFICATE";
size: 13;
anchors.left: certificateIcon.right;
anchors.leftMargin: 4;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.right: parent.right;
color: hifi.colors.black;
}
MouseArea {
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId});
}
onEntered: {
certificateIcon.color = hifi.colors.lightGray;
viewCertificateText.color = hifi.colors.lightGray;
}
onExited: {
certificateIcon.color = hifi.colors.black;
viewCertificateText.color = hifi.colors.black;
}
}
}
Item {
id: editionContainer;
RalewayRegular {
id: editionNumberText;
visible: root.displayedItemCount > 1 && !statusContainer.visible;
anchors.left: itemName.left;
anchors.top: certificateContainer.bottom;
anchors.topMargin: 8;
anchors.bottom: parent.bottom;
anchors.right: buttonContainer.left;
anchors.rightMargin: 2;
RalewayRegular {
anchors.left: parent.left;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
width: paintedWidth;
text: "#" + root.itemEdition;
size: 13;
color: hifi.colors.black;
verticalAlignment: Text.AlignTop;
}
anchors.right: itemName.right;
anchors.top: itemName.bottom;
anchors.topMargin: 4;
anchors.bottom: buttonContainer.top;
anchors.bottomMargin: 4;
width: itemName.width;
text: "Edition #" + root.itemEdition;
size: 13;
color: hifi.colors.black;
verticalAlignment: Text.AlignVCenter;
}
Item {
id: statusContainer;
visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged || root.numberSold > -1;
visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.numberSold > -1;
anchors.left: itemName.left;
anchors.top: certificateContainer.bottom;
anchors.topMargin: 8;
anchors.bottom: parent.bottom;
anchors.right: buttonContainer.left;
anchors.rightMargin: 2;
anchors.right: itemName.right;
anchors.top: itemName.bottom;
anchors.topMargin: 4;
anchors.bottom: buttonContainer.top;
anchors.bottomMargin: 4;
RalewaySemiBold {
RalewayRegular {
id: statusText;
anchors.left: parent.left;
anchors.top: parent.top;
@ -393,7 +489,7 @@ Item {
""
}
}
size: 18;
size: 13;
color: {
if (root.purchaseStatus === "pending") {
hifi.colors.blueAccent
@ -418,10 +514,10 @@ Item {
}
}
// Size
size: 36;
size: 34;
// Anchors
anchors.top: parent.top;
anchors.topMargin: -8;
anchors.topMargin: -10;
anchors.left: statusText.right;
anchors.bottom: parent.bottom;
// Style
@ -468,6 +564,50 @@ Item {
}
}
Item {
id: contextMenuButtonContainer;
anchors.right: parent.right;
anchors.rightMargin: 8;
anchors.top: parent.top;
anchors.topMargin: 8;
width: 30;
height: width;
Rectangle {
visible: root.updateAvailable;
anchors.fill: parent;
radius: height;
border.width: 1;
border.color: "#E2334D";
}
HiFiGlyphs {
id: contextMenuGlyph;
text: hifi.glyphs.verticalEllipsis;
anchors.fill: parent;
size: 46;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
color: root.updateAvailable ? "#E2334D" : hifi.colors.black;
}
MouseArea {
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
contextCard.z = 1;
permissionExplanationCard.z = 0;
root.sendToPurchases({ method: 'flipCard' });
}
onEntered: {
contextMenuGlyph.color = root.updateAvailable ? hifi.colors.redHighlight : hifi.colors.blueHighlight;
}
onExited: {
contextMenuGlyph.color = root.updateAvailable ? "#E2334D" : hifi.colors.black;
}
}
}
Rectangle {
id: rezzedNotifContainer;
z: 998;
@ -489,62 +629,22 @@ Item {
horizontalAlignment: Text.AlignHCenter;
}
Timer {
id: rezzedNotifContainerTimer;
interval: 2000;
onTriggered: rezzedNotifContainer.visible = false
}
}
Rectangle {
id: appButtonContainer;
color: hifi.colors.white;
z: 994;
visible: root.isInstalled;
anchors.fill: buttonContainer;
HifiControlsUit.Button {
id: openAppButton;
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.light;
anchors.top: parent.top;
anchors.right: parent.right;
anchors.left: parent.left;
width: 92;
height: 44;
text: "OPEN"
onClicked: {
Commerce.openApp(root.itemHref);
}
}
HifiControlsUit.Button {
id: uninstallAppButton;
color: hifi.buttons.noneBorderless;
colorScheme: hifi.colorSchemes.light;
anchors.bottom: parent.bottom;
anchors.right: parent.right;
anchors.left: parent.left;
height: 44;
text: "UNINSTALL"
onClicked: {
Commerce.uninstallApp(root.itemHref);
}
Timer {
id: rezzedNotifContainerTimer;
interval: 2000;
onTriggered: rezzedNotifContainer.visible = false
}
}
Button {
id: buttonContainer;
property int color: hifi.buttons.blue;
property int colorScheme: hifi.colorSchemes.light;
anchors.top: parent.top;
anchors.topMargin: 4;
anchors.left: itemName.left;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
anchors.right: parent.right;
anchors.rightMargin: 4;
width: height;
anchors.bottomMargin: 8;
width: 160;
height: 40;
enabled: root.hasPermissionToRezThis &&
root.purchaseStatus !== "invalidated" &&
MyAvatar.skeletonModelURL !== root.itemHref;
@ -568,8 +668,12 @@ Item {
} else if (root.itemType === "avatar") {
sendToPurchases({method: 'showChangeAvatarLightbox', itemName: root.itemName, itemHref: root.itemHref});
} else if (root.itemType === "app") {
// "Run" and "Uninstall" buttons are separate.
Commerce.installApp(root.itemHref);
if (root.isInstalled) {
Commerce.openApp(root.itemHref);
} else {
// "Run" and "Uninstall" buttons are separate.
Commerce.installApp(root.itemHref);
}
} else {
sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, itemType: root.itemType});
root.showConfirmation = true;
@ -612,93 +716,76 @@ Item {
}
label: Item {
TextMetrics {
id: rezIconTextMetrics;
font: rezIcon.font;
text: rezIcon.text;
}
HiFiGlyphs {
id: rezIcon;
text: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)];
// Size
size: 60;
// Anchors
anchors.top: parent.top;
anchors.topMargin: 0;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.right: rezIconLabel.left;
anchors.rightMargin: 2;
anchors.verticalCenter: parent.verticalCenter;
size: 36;
horizontalAlignment: Text.AlignHCenter;
// Style
color: enabled ? hifi.buttons.textColor[control.color]
: hifi.buttons.disabledTextColor[control.colorScheme]
: hifi.buttons.disabledTextColor[control.colorScheme]
}
TextMetrics {
id: rezIconLabelTextMetrics;
font: rezIconLabel.font;
text: rezIconLabel.text;
}
RalewayBold {
id: rezIconLabel;
anchors.top: rezIcon.bottom;
anchors.topMargin: -4;
anchors.right: parent.right;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
font.capitalization: Font.AllUppercase
color: enabled ? hifi.buttons.textColor[control.color]
: hifi.buttons.disabledTextColor[control.colorScheme]
text: root.isInstalled ? "OPEN" : (MyAvatar.skeletonModelURL === root.itemHref ? "CURRENT" : (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)]);
anchors.verticalCenter: parent.verticalCenter;
width: rezIconLabelTextMetrics.width;
x: parent.width/2 - rezIconLabelTextMetrics.width/2 + rezIconTextMetrics.width/2;
size: 15;
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: MyAvatar.skeletonModelURL === root.itemHref ? "CURRENT" : (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)];
font.capitalization: Font.AllUppercase;
verticalAlignment: Text.AlignVCenter;
horizontalAlignment: Text.AlignHCenter;
color: enabled ? hifi.buttons.textColor[control.color]
: hifi.buttons.disabledTextColor[control.colorScheme]
}
}
}
}
}
// END "incorrect indentation to prevent insane diffs"
HiFiGlyphs {
id: noPermissionGlyph;
visible: !root.hasPermissionToRezThis;
anchors.verticalCenter: buttonContainer.verticalCenter;
anchors.left: buttonContainer.left;
anchors.right: buttonContainer.right;
anchors.rightMargin: -40;
text: hifi.glyphs.info;
// Size
size: 44;
// Style
color: hifi.colors.redAccent;
horizontalAlignment: Text.AlignRight;
MouseArea {
anchors.fill: parent;
hoverEnabled: true;
Rectangle {
id: upgradeAvailableContainer;
visible: root.upgradeUrl !== "" && !root.isShowingMyItems;
anchors.top: itemContainer.bottom;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
color: "#B5EAFF";
RalewayRegular {
id: updateAvailableText;
text: "UPDATE AVAILABLE";
size: 13;
anchors.left: parent.left;
anchors.leftMargin: 12;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
width: paintedWidth;
color: hifi.colors.black;
verticalAlignment: Text.AlignVCenter;
}
RalewaySemiBold {
id: updateNowText;
text: "<font color='#0093C5'><a href='#'>Update this item now</a></font>";
size: 13;
anchors.left: updateAvailableText.right;
anchors.leftMargin: 16;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
width: paintedWidth;
color: hifi.colors.black;
verticalAlignment: Text.AlignVCenter;
onLinkActivated: {
sendToPurchases({method: 'updateItemClicked', itemId: root.itemId, itemEdition: root.itemEdition, upgradeUrl: root.upgradeUrl});
onEntered: {
noPermissionGlyph.color = hifi.colors.redHighlight;
}
onExited: {
noPermissionGlyph.color = hifi.colors.redAccent;
}
onClicked: {
contextCard.z = 0;
permissionExplanationCard.z = 1;
root.sendToPurchases({ method: 'flipCard' });
}
}
}
}
DropShadow {
anchors.fill: mainContainer;
horizontalOffset: 0;
verticalOffset: 4;
radius: 4.0;
samples: 9
color: Qt.rgba(0, 0, 0, 0.25);
source: mainContainer;
}
//
// FUNCTION DEFINITIONS START
//

View file

@ -19,6 +19,7 @@ import "../../../controls" as HifiControls
import "../wallet" as HifiWallet
import "../common" as HifiCommerceCommon
import "../inspectionCertificate" as HifiInspectionCertificate
import "../common/sendAsset" as HifiSendAsset
// references XXX from root context
@ -31,7 +32,6 @@ Rectangle {
property bool securityImageResultReceived: false;
property bool purchasesReceived: false;
property bool punctuationMode: false;
property bool pendingInventoryReply: true;
property bool isShowingMyItems: false;
property bool isDebuggingFirstUseTutorial: false;
property int pendingItemCount: 0;
@ -114,12 +114,6 @@ Rectangle {
purchasesContentsList.positionViewAtIndex(currentIndex, ListView.Beginning);
}
if (root.pendingInventoryReply && root.pendingItemCount > 0) {
inventoryTimer.start();
}
root.pendingInventoryReply = false;
}
onAvailableUpdatesResult: {
@ -142,7 +136,7 @@ Rectangle {
HifiInspectionCertificate.InspectionCertificate {
id: inspectionCertificate;
z: 999;
z: 998;
visible: false;
anchors.fill: parent;
@ -155,6 +149,7 @@ Rectangle {
HifiCommerceCommon.CommerceLightbox {
id: lightboxPopup;
z: 999;
visible: false;
anchors.fill: parent;
@ -169,12 +164,33 @@ Rectangle {
}
}
HifiSendAsset.SendAsset {
id: sendAsset;
z: 998;
visible: root.activeView === "giftAsset";
anchors.fill: parent;
parentAppTitleBarHeight: 70;
parentAppNavBarHeight: 0;
Connections {
onSendSignalToParent: {
if (msg.method === 'sendAssetHome_back' || msg.method === 'closeSendAsset') {
root.activeView = "purchasesMain";
Commerce.inventory();
Commerce.getAvailableUpdates();
} else {
sendToScript(msg);
}
}
}
}
//
// TITLE BAR START
//
HifiCommerceCommon.EmulatedMarketplaceHeader {
id: titleBarContainer;
z: 998;
z: 997;
visible: !needsLogIn.visible;
// Size
width: parent.width;
@ -191,9 +207,14 @@ Rectangle {
lightboxPopup.bodyImageSource = msg.securityImageSource;
lightboxPopup.bodyText = lightboxPopup.securityPicBodyText;
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.button2text = "GO TO WALLET";
lightboxPopup.button2method = "sendToParent({method: 'purchases_openWallet'});";
lightboxPopup.button2method = function() {
sendToScript({method: 'purchases_openWallet'});
lightboxPopup.visible = false;
};
lightboxPopup.visible = true;
} else {
sendToScript(msg);
@ -308,7 +329,7 @@ Rectangle {
// FILTER BAR START
//
Item {
z: 997;
z: 996;
id: filterBarContainer;
// Size
height: 40;
@ -316,7 +337,7 @@ Rectangle {
anchors.left: parent.left;
anchors.leftMargin: 8;
anchors.right: parent.right;
anchors.rightMargin: 16;
anchors.rightMargin: 8;
anchors.top: parent.top;
anchors.topMargin: 4;
@ -341,6 +362,7 @@ Rectangle {
colorScheme: hifi.colorSchemes.faintGray;
anchors.top: parent.top;
anchors.right: parent.right;
anchors.rightMargin: 8;
anchors.left: myText.right;
anchors.leftMargin: 16;
textFieldHeight: 39;
@ -396,7 +418,7 @@ Rectangle {
//
HifiControlsUit.Separator {
z: 996;
z: 995;
id: separator;
colorScheme: 2;
anchors.left: parent.left;
@ -426,7 +448,6 @@ Rectangle {
snapMode: ListView.SnapToItem;
// Anchors
anchors.top: separator.bottom;
anchors.topMargin: 12;
anchors.left: parent.left;
anchors.bottom: updatesAvailableBanner.visible ? updatesAvailableBanner.top : parent.bottom;
width: parent.width;
@ -437,13 +458,13 @@ Rectangle {
itemHref: download_url;
certificateId: certificate_id;
purchaseStatus: status;
purchaseStatusChanged: statusChanged;
itemEdition: model.edition_number;
numberSold: model.number_sold;
limitedRun: model.limited_run;
displayedItemCount: model.displayedItemCount;
permissionExplanationCardVisible: model.permissionExplanationCardVisible;
cardBackVisible: model.cardBackVisible;
isInstalled: model.isInstalled;
wornEntityID: model.wornEntityID;
upgradeUrl: model.upgrade_url;
upgradeTitle: model.upgrade_title;
itemType: model.itemType;
@ -457,6 +478,11 @@ Rectangle {
sendToScript({method: 'purchases_itemInfoClicked', itemId: itemId});
} else if (msg.method === "purchases_rezClicked") {
sendToScript({method: 'purchases_rezClicked', itemHref: itemHref, itemType: itemType});
// Race condition - Wearable might not be rezzed by the time the "currently worn wearbles" model is created
if (itemType === "wearable") {
sendToScript({ method: 'purchases_updateWearables' });
}
} else if (msg.method === 'purchases_itemCertificateClicked') {
inspectionCertificate.visible = true;
inspectionCertificate.isLightbox = true;
@ -466,14 +492,18 @@ Rectangle {
lightboxPopup.bodyText = 'Your item is marked "invalidated" because this item has been suspended ' +
"from the Marketplace due to a claim against its author.";
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.visible = true;
} else if (msg.method === "showPendingLightbox") {
lightboxPopup.titleText = "Item Pending";
lightboxPopup.bodyText = 'Your item is marked "pending" while your purchase is being confirmed. ' +
"Usually, purchases take about 90 seconds to confirm.";
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.visible = true;
} else if (msg.method === "showReplaceContentLightbox") {
lightboxPopup.titleText = "Replace Content";
@ -483,17 +513,27 @@ Rectangle {
"<a href='https://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/restoring-sandbox-content'>" +
"click here to open info on your desktop browser.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "Commerce.replaceContentSet('" + msg.itemHref + "', '" + msg.certID + "'); root.visible = false;";
lightboxPopup.button2method = function() {
Commerce.replaceContentSet(msg.itemHref, msg.certID);
lightboxPopup.visible = false;
};
lightboxPopup.visible = true;
} else if (msg.method === "showChangeAvatarLightbox") {
lightboxPopup.titleText = "Change Avatar";
lightboxPopup.bodyText = "This will change your current avatar to " + msg.itemName + " while retaining your wearables.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "MyAvatar.useFullAvatarURL('" + msg.itemHref + "'); root.visible = false;";
lightboxPopup.button2method = function() {
MyAvatar.useFullAvatarURL(msg.itemHref);
lightboxPopup.visible = false;
};
lightboxPopup.visible = true;
} else if (msg.method === "showPermissionsExplanation") {
if (msg.itemType === "entity") {
@ -501,27 +541,86 @@ Rectangle {
lightboxPopup.bodyText = "You don't have permission to rez certified items in this domain.<br><br>" +
"Use the <b>GOTO app</b> to visit another domain or <b>go to your own sandbox.</b>";
lightboxPopup.button2text = "OPEN GOTO";
lightboxPopup.button2method = "sendToParent({method: 'purchases_openGoTo'});";
lightboxPopup.button2method = function() {
sendToScript({method: 'purchases_openGoTo'});
lightboxPopup.visible = false;
};
} else if (msg.itemType === "contentSet") {
lightboxPopup.titleText = "Replace Content Permission";
lightboxPopup.bodyText = "You do not have the permission 'Replace Content' in this <b>domain's server settings</b>. The domain owner " +
"must enable it for you before you can replace content sets in this domain.";
}
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.visible = true;
} else if (msg.method === "setFilterText") {
filterBar.text = msg.filterText;
} else if (msg.method === "openPermissionExplanationCard") {
} else if (msg.method === "flipCard") {
for (var i = 0; i < filteredPurchasesModel.count; i++) {
if (i !== index || msg.closeAll) {
filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", false);
filteredPurchasesModel.setProperty(i, "cardBackVisible", false);
} else {
filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", true);
filteredPurchasesModel.setProperty(i, "cardBackVisible", true);
}
}
} else if (msg.method === "updateItemClicked") {
sendToScript(msg);
} else if (msg.method === "giftAsset") {
sendAsset.assetName = msg.itemName;
sendAsset.assetCertID = msg.certId;
sendAsset.sendingPubliclyEffectImage = msg.effectImage;
if (msg.itemType === "avatar" && MyAvatar.skeletonModelURL === msg.itemHref) {
lightboxPopup.titleText = "Change Avatar to Default";
lightboxPopup.bodyText = "You are currently wearing the avatar that you are trying to gift.<br><br>" +
"If you proceed, your avatar will be changed to the default avatar.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = function() {
MyAvatar.skeletonModelURL = '';
root.activeView = "giftAsset";
lightboxPopup.visible = false;
};
lightboxPopup.visible = true;
} else if (msg.itemType === "app" && msg.isInstalled) {
lightboxPopup.titleText = "Uninstall App";
lightboxPopup.bodyText = "You are currently using the app that you are trying to gift.<br><br>" +
"If you proceed, the app will be uninstalled.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = function() {
Commerce.uninstallApp(msg.itemHref);
root.activeView = "giftAsset";
lightboxPopup.visible = false;
};
lightboxPopup.visible = true;
} else if (msg.itemType === "wearable" && msg.wornEntityID !== '') {
lightboxPopup.titleText = "Remove Wearable";
lightboxPopup.bodyText = "You are currently wearing the wearable that you are trying to send.<br><br>" +
"If you proceed, this wearable will be removed.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = function() {
Entities.deleteEntity(msg.wornEntityID);
filteredPurchasesModel.setProperty(index, 'wornEntityID', '');
root.activeView = "giftAsset";
lightboxPopup.visible = false;
};
lightboxPopup.visible = true;
} else {
root.activeView = "giftAsset";
}
}
}
}
@ -693,6 +792,7 @@ Rectangle {
HifiControlsUit.Keyboard {
id: keyboard;
z: 999;
raised: HMD.mounted && parent.keyboardRaised;
numeric: parent.punctuationMode;
anchors {
@ -702,26 +802,6 @@ Rectangle {
}
}
onVisibleChanged: {
if (!visible) {
inventoryTimer.stop();
}
}
Timer {
id: inventoryTimer;
interval: 4000; // Change this back to 90000 after demo
//interval: 90000;
onTriggered: {
if (root.activeView === "purchasesMain" && !root.pendingInventoryReply) {
console.log("Refreshing Purchases...");
root.pendingInventoryReply = true;
Commerce.inventory();
Commerce.getAvailableUpdates();
}
}
}
//
// FUNCTION DEFINITIONS START
//
@ -833,10 +913,12 @@ Rectangle {
continue;
}
filteredPurchasesModel.append(tempPurchasesModel.get(i));
filteredPurchasesModel.setProperty(i, 'permissionExplanationCardVisible', false);
filteredPurchasesModel.setProperty(i, 'cardBackVisible', false);
filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1));
filteredPurchasesModel.setProperty(i, 'wornEntityID', '');
}
sendToScript({ method: 'purchases_updateWearables' });
populateDisplayedItemCounts();
sortByDate();
}
@ -863,6 +945,19 @@ Rectangle {
}
}
}
function updateCurrentlyWornWearables(wearables) {
for (var i = 0; i < filteredPurchasesModel.count; i++) {
for (var j = 0; j < wearables.length; j++) {
if (filteredPurchasesModel.get(i).itemType === "wearable" &&
wearables[j].entityCertID === filteredPurchasesModel.get(i).certificate_id &&
wearables[j].entityEdition.toString() === filteredPurchasesModel.get(i).edition_number) {
filteredPurchasesModel.setProperty(i, 'wornEntityID', wearables[j].entityID);
break;
}
}
}
}
//
// Function Name: fromScript()
@ -890,6 +985,16 @@ Rectangle {
case 'purchases_showMyItems':
root.isShowingMyItems = true;
break;
case 'updateConnections':
sendAsset.updateConnections(message.connections);
break;
case 'selectRecipient':
case 'updateSelectedRecipientUsername':
sendAsset.fromScript(message);
break;
case 'updateWearables':
updateCurrentlyWornWearables(message.wornWearables);
break;
default:
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
}

View file

@ -143,7 +143,9 @@ Item {
lightboxPopup.bodyImageSource = titleBarSecurityImage.source;
lightboxPopup.bodyText = lightboxPopup.securityPicBodyText;
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.visible = true;
}
}

View file

@ -42,18 +42,6 @@ Item {
}
}
}
// This will cause a bug -- if you bring up security image selection in HUD mode while
// in HMD while having HMD preview enabled, then move, then finish passphrase selection,
// HMD preview will stay off.
// TODO: Fix this unlikely bug
onVisibleChanged: {
if (visible) {
sendSignalToWallet({method: 'disableHmdPreview'});
} else {
sendSignalToWallet({method: 'maybeEnableHmdPreview'});
}
}
// Security Image
Item {

View file

@ -18,7 +18,7 @@ import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
import "../common" as HifiCommerceCommon
import "./sendMoney"
import "../common/sendAsset"
Rectangle {
HifiConstants { id: hifi; }
@ -160,7 +160,9 @@ Rectangle {
lightboxPopup.bodyImageSource = titleBarSecurityImage.source;
lightboxPopup.bodyText = lightboxPopup.securityPicBodyText;
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.visible = true;
}
}
@ -341,7 +343,7 @@ Rectangle {
}
}
SendMoney {
SendAsset {
id: sendMoney;
z: 997;
visible: root.activeView === "sendMoney";
@ -350,7 +352,7 @@ Rectangle {
parentAppNavBarHeight: tabButtonsContainer.height;
Connections {
onSendSignalToWallet: {
onSendSignalToParent: {
sendToScript(msg);
}
}
@ -405,7 +407,7 @@ Rectangle {
//
Item {
id: tabButtonsContainer;
visible: !needsLogIn.visible && root.activeView !== "passphraseChange" && root.activeView !== "securityImageChange" && sendMoney.currentActiveView !== "sendMoneyStep";
visible: !needsLogIn.visible && root.activeView !== "passphraseChange" && root.activeView !== "securityImageChange" && sendMoney.currentActiveView !== "sendAssetStep";
property int numTabs: 5;
// Size
width: root.width;

View file

@ -206,9 +206,14 @@ Item {
"This step cannot be undone.";
lightboxPopup.button1color = hifi.buttons.red;
lightboxPopup.button1text = "YES, CREATE NEW WALLET";
lightboxPopup.button1method = "root.visible = false;proceed(true);";
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
proceed(true);
}
lightboxPopup.button2text = "CANCEL";
lightboxPopup.button2method = "root.visible = false;"
lightboxPopup.button2method = function() {
lightboxPopup.visible = false;
};
lightboxPopup.buttonLayout = "topbottom";
lightboxPopup.visible = true;
}
@ -241,7 +246,9 @@ Item {
"If you'd prefer to create a new wallet (not recommended - you will lose your money and past " +
"purchases), click 'Create New Wallet'.";
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.visible = true;
} else {
lightboxPopup.titleText = "You may have set up more than one wallet";
@ -251,7 +258,9 @@ Item {
"If you would prefer to use another wallet, click 'Locate Other Keys' to show us where " +
"you've stored the private keys for that wallet.";
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.visible = true;
}
}

View file

@ -76,7 +76,7 @@ Item {
UserActivityLogger.commerceWalletSetupProgress(timestamp, root.setupAttemptID,
Math.round((timestamp - root.startingTimestamp)/1000), currentStepNumber, root.setupStepNames[currentStepNumber - 1]);
if (root.activeView === "step_2" || root.activeView === "step_3") {
if (root.activeView === "step_3") {
sendSignalToWallet({method: 'disableHmdPreview'});
} else {
sendSignalToWallet({method: 'maybeEnableHmdPreview'});
@ -150,7 +150,9 @@ Item {
lightboxPopup.bodyImageSource = titleBarSecurityImage.source;
lightboxPopup.bodyText = lightboxPopup.securityPicBodyText;
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.visible = true;
}
}

View file

@ -181,11 +181,11 @@ Item {
minimumValue: 0.01
maximumValue: 10
realStepSize: 0.05;
value: attachment ? attachment.scale : 1.0
realValue: attachment ? attachment.scale : 1.0
colorScheme: hifi.colorSchemes.dark
onValueChanged: {
if (completed && attachment && attachment.scale !== value) {
attachment.scale = value;
onRealValueChanged: {
if (completed && attachment && attachment.scale !== realValue) {
attachment.scale = realValue;
updateAttachment();
}
}

View file

@ -51,7 +51,7 @@ Item {
id: xspinner
width: root.spinboxWidth
anchors { left: parent.left }
value: root.vector.x
realValue: root.vector.x
labelInside: "X:"
colorScheme: hifi.colorSchemes.dark
colorLabelInside: hifi.colors.redHighlight
@ -72,17 +72,17 @@ Item {
id: yspinner
width: root.spinboxWidth
anchors { horizontalCenter: parent.horizontalCenter }
value: root.vector.y
realValue: root.vector.y
labelInside: "Y:"
colorLabelInside: hifi.colors.greenHighlight
colorScheme: hifi.colorSchemes.dark
decimals: root.decimals
stepSize: root.stepSize
realStepSize: root.stepSize
maximumValue: root.maximumValue
minimumValue: root.minimumValue
onValueChanged: {
if (value !== vector.y) {
vector.y = value
onRealValueChanged: {
if (realValue !== vector.y) {
vector.y = realValue
root.valueChanged();
}
}
@ -93,17 +93,17 @@ Item {
id: zspinner
width: root.spinboxWidth
anchors { right: parent.right; }
value: root.vector.z
realValue: root.vector.z
labelInside: "Z:"
colorLabelInside: hifi.colors.primaryHighlight
colorScheme: hifi.colorSchemes.dark
decimals: root.decimals
stepSize: root.stepSize
realStepSize: root.stepSize
maximumValue: root.maximumValue
minimumValue: root.minimumValue
onValueChanged: {
if (value !== vector.z) {
vector.z = value
onRealValueChanged: {
if (realValue !== vector.z) {
vector.z = realValue
root.valueChanged();
}
}

View file

@ -178,8 +178,8 @@ Rectangle {
label: "Y Offset"
suffix: " cm"
minimumValue: -10
stepSize: 1
value: -5
realStepSize: 1
realValue: -5
colorScheme: hifi.colorSchemes.dark
onEditingFinished: {
@ -193,10 +193,10 @@ Rectangle {
width: 112
label: "Z Offset"
minimumValue: -10
stepSize: 1
realStepSize: 1
decimals: 1
suffix: " cm"
value: -5
realValue: -5
colorScheme: hifi.colorSchemes.dark
onEditingFinished: {
@ -288,7 +288,7 @@ Rectangle {
suffix: " cm"
label: "Y Offset"
minimumValue: -10
stepSize: 1
realStepSize: 1
colorScheme: hifi.colorSchemes.dark
onEditingFinished: {
@ -303,7 +303,7 @@ Rectangle {
label: "Z Offset"
suffix: " cm"
minimumValue: -10
stepSize: 1
realStepSize: 1
decimals: 1
colorScheme: hifi.colorSchemes.dark
@ -535,9 +535,9 @@ Rectangle {
suffix: " cm"
label: "Arm Circumference"
minimumValue: 0
stepSize: 1.0
realStepSize: 1.0
colorScheme: hifi.colorSchemes.dark
value: 33.0
realValue: 33.0
onEditingFinished: {
sendConfigurationSettings();
@ -550,10 +550,10 @@ Rectangle {
label: "Shoulder Width"
suffix: " cm"
minimumValue: 0
stepSize: 1.0
realStepSize: 1.0
decimals: 1
colorScheme: hifi.colorSchemes.dark
value: 48
realValue: 48
onEditingFinished: {
sendConfigurationSettings();
@ -659,13 +659,13 @@ Rectangle {
InputConfiguration.uncalibratePlugin(pluginName);
updateCalibrationButton();
} else {
calibrationTimer.interval = timeToCalibrate.value * 1000
openVrConfiguration.countDown = timeToCalibrate.value;
calibrationTimer.interval = timeToCalibrate.realValue * 1000
openVrConfiguration.countDown = timeToCalibrate.realValue;
var calibratingScreen = screen.createObject();
stack.push(calibratingScreen);
calibratingScreen.canceled.connect(cancelCalibration);
calibratingScreen.restart.connect(restartCalibration);
calibratingScreen.start(calibrationTimer.interval, timeToCalibrate.value);
calibratingScreen.start(calibrationTimer.interval, timeToCalibrate.realValue);
calibrationTimer.start();
}
}
@ -728,12 +728,12 @@ Rectangle {
anchors.leftMargin: leftMargin
minimumValue: 5
value: 5
realValue: 5
colorScheme: hifi.colorSchemes.dark
onEditingFinished: {
calibrationTimer.interval = value * 1000;
openVrConfiguration.countDown = value;
calibrationTimer.interval = realValue * 1000;
openVrConfiguration.countDown = realValue;
numberAnimation.duration = calibrationTimer.interval;
}
}
@ -910,8 +910,8 @@ Rectangle {
var desktopMode = settings["desktopMode"];
var hmdDesktopPosition = settings["hmdDesktopTracking"];
armCircumference.value = settings.armCircumference;
shoulderWidth.value = settings.shoulderWidth;
armCircumference.realValue = settings.armCircumference;
shoulderWidth.realValue = settings.shoulderWidth;
if (HmdHead) {
headBox.checked = true;
@ -1075,22 +1075,22 @@ Rectangle {
var headObject = {
"override": overrideHead,
"Y": headYOffset.value,
"Z": headZOffset.value
"Y": headYOffset.realValue,
"Z": headZOffset.realValue
}
var handObject = {
"override": overrideHandController,
"Y": handYOffset.value,
"Z": handZOffset.value
"Y": handYOffset.realValue,
"Z": handZOffset.realValue
}
var settingsObject = {
"bodyConfiguration": trackerConfiguration,
"headConfiguration": headObject,
"handConfiguration": handObject,
"armCircumference": armCircumference.value,
"shoulderWidth": shoulderWidth.value,
"armCircumference": armCircumference.realValue,
"shoulderWidth": shoulderWidth.realValue,
"desktopMode": viveInDesktop.checked,
"hmdDesktopTracking": hmdInDesktop.checked
}

View file

@ -14,8 +14,8 @@ FocusScope {
id: tabletMenu
objectName: "tabletMenu"
width: 480
height: 720
width: parent.width
height: parent.height
property var rootMenu: Menu { objectName:"rootMenu" }
property var point: Qt.point(50, 50);

View file

@ -16,6 +16,8 @@ import "."
Item {
id: root
anchors.fill: parent
width: parent.width
height: parent.height
objectName: "tabletMenuHandlerItem"
StackView {

View file

@ -40,10 +40,10 @@ FocusScope {
id: listView
x: 0
y: 0
width: 480
height: 720
contentWidth: 480
contentHeight: 720
width: parent.width
height: parent.height
contentWidth: parent.width
contentHeight: parent.height
objectName: "menuList"
topMargin: hifi.dimensions.menuPadding.y

View file

@ -344,5 +344,10 @@ QtObject {
readonly property string wand: "\ue02d"
readonly property string hat: "\ue02e"
readonly property string install: "\ue02f"
readonly property string certificate: "\ue030"
readonly property string gift: "\ue031"
readonly property string update: "\ue032"
readonly property string uninstall: "\ue033"
readonly property string verticalEllipsis: "\ue034"
}
}

View file

@ -1,5 +1,5 @@
//
// DefaultFrame.qml
// Decoration.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 High Fidelity, Inc.

View file

@ -490,7 +490,7 @@ public:
// Don't actually crash in debug builds, in case this apparent deadlock is simply from
// the developer actively debugging code
#ifdef NDEBUG
deadlockDetectionCrash();
deadlockDetectionCrash();
#endif
}
}
@ -688,6 +688,34 @@ private:
}
};
/**jsdoc
* <p>The <code>Controller.Hardware.Application</code> object has properties representing Interface's state. The property
* values are integer IDs, uniquely identifying each output. <em>Read-only.</em> These can be mapped to actions or functions or
* <code>Controller.Standard</code> items in a {@link RouteObject} mapping (e.g., using the {@link RouteObject#when} method).
* Each data value is either <code>1.0</code> for "true" or <code>0.0</code> for "false".</p>
* <table>
* <thead>
* <tr><th>Property</th><th>Type</th><th>Data</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>CameraFirstPerson</code></td><td>number</td><td>number</td><td>The camera is in first-person mode.
* </td></tr>
* <tr><td><code>CameraThirdPerson</code></td><td>number</td><td>number</td><td>The camera is in third-person mode.
* </td></tr>
* <tr><td><code>CameraFSM</code></td><td>number</td><td>number</td><td>The camera is in full screen mirror mode.</td></tr>
* <tr><td><code>CameraIndependent</code></td><td>number</td><td>number</td><td>The camera is in independent mode.</td></tr>
* <tr><td><code>CameraEntity</code></td><td>number</td><td>number</td><td>The camera is in entity mode.</td></tr>
* <tr><td><code>InHMD</code></td><td>number</td><td>number</td><td>The user is in HMD mode.</td></tr>
* <tr><td><code>AdvancedMovement</code></td><td>number</td><td>number</td><td>Advanced movement controls are enabled.
* </td></tr>
* <tr><td><code>SnapTurn</code></td><td>number</td><td>number</td><td>Snap turn is enabled.</td></tr>
* <tr><td><code>Grounded</code></td><td>number</td><td>number</td><td>The user's avatar is on the ground.</td></tr>
* <tr><td><code>NavigationFocused</code></td><td>number</td><td>number</td><td><em>Not used.</em></td></tr>
* </tbody>
* </table>
* @typedef Controller.Hardware-Application
*/
static const QString STATE_IN_HMD = "InHMD";
static const QString STATE_CAMERA_FULL_SCREEN_MIRROR = "CameraFSM";
static const QString STATE_CAMERA_FIRST_PERSON = "CameraFirstPerson";
@ -773,7 +801,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
steamClient->init();
}
DependencyManager::set<tracing::Tracer>();
PROFILE_SET_THREAD_NAME("Main Thread");
#if defined(Q_OS_WIN)
@ -958,10 +985,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
auto steamClient = PluginManager::getInstance()->getSteamClientPlugin();
setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning()));
setProperty(hifi::properties::CRASHED, _previousSessionCrashed);
{
const QString TEST_SCRIPT = "--testScript";
const QString TRACE_FILE = "--traceFile";
const QStringList args = arguments();
for (int i = 0; i < args.size() - 1; ++i) {
if (args.at(i) == TEST_SCRIPT) {
@ -969,10 +994,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
if (QFileInfo(testScriptPath).exists()) {
setProperty(hifi::properties::TEST, QUrl::fromLocalFile(testScriptPath));
}
} else if (args.at(i) == TRACE_FILE) {
QString traceFilePath = args.at(i + 1);
setProperty(hifi::properties::TRACING, traceFilePath);
DependencyManager::get<tracing::Tracer>()->startTracing();
}
}
}
@ -1018,6 +1039,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
auto nodeList = DependencyManager::get<NodeList>();
nodeList->startThread();
const char** constArgv = const_cast<const char**>(argv);
if (cmdOptionExists(argc, constArgv, "--disableWatchdog")) {
DISABLE_WATCHDOG = true;
}
// Set up a watchdog thread to intentionally crash the application on deadlocks
if (!DISABLE_WATCHDOG) {
(new DeadlockWatchdogThread())->start();
@ -1102,10 +1127,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
auto audioScriptingInterface = DependencyManager::get<AudioScriptingInterface>();
auto myAvatarPosition = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldPosition();
float distance = glm::distance(myAvatarPosition, position);
bool shouldMute = !audioClient->isMuted() && (distance < radius);
if (shouldMute) {
audioClient->toggleMute();
if (distance < radius) {
audioClient->setMuted(true);
audioScriptingInterface->environmentMuted();
}
});
@ -1227,7 +1251,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent);
connect(&_entityEditSender, &EntityEditPacketSender::addingEntityWithCertificate, this, &Application::addingEntityWithCertificate);
const char** constArgv = const_cast<const char**>(argv);
QString concurrentDownloadsStr = getCmdOption(argc, constArgv, "--concurrent-downloads");
bool success;
int concurrentDownloads = concurrentDownloadsStr.toInt(&success);
@ -1270,9 +1293,32 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// Make sure the window is set to the correct size by processing the pending events
QCoreApplication::processEvents();
_glWidget->createContext();
_glWidget->makeCurrent();
// Create the main thread context, the GPU backend, and the display plugins
initializeGL();
qCDebug(interfaceapp, "Initialized Display.");
// Create the rendering engine. This can be slow on some machines due to lots of
// GPU pipeline creation.
initializeRenderEngine();
qCDebug(interfaceapp, "Initialized Render Engine.");
// Initialize the user interface and menu system
// Needs to happen AFTER the render engine initialization to access its configuration
initializeUi();
init();
qCDebug(interfaceapp, "init() complete.");
// create thread for parsing of octree data independent of the main network and rendering threads
_octreeProcessor.initialize(_enableProcessOctreeThread);
connect(&_octreeProcessor, &OctreePacketProcessor::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
_entityEditSender.initialize(_enableProcessOctreeThread);
_idleLoopStdev.reset();
// update before the first render
update(0);
// Make sure we don't time out during slow operations at startup
updateHeartbeat();
@ -1508,7 +1554,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
if (state) {
if (action == controller::toInt(controller::Action::TOGGLE_MUTE)) {
DependencyManager::get<AudioClient>()->toggleMute();
auto audioClient = DependencyManager::get<AudioClient>();
audioClient->setMuted(!audioClient->isMuted());
} else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) {
cycleCamera();
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
@ -2434,48 +2481,47 @@ void Application::initializeGL() {
_isGLInitialized = true;
}
// Build a shared canvas / context for the Chromium processes
_glWidget->makeCurrent();
glClearColor(0.2f, 0.2f, 0.2f, 1);
glClear(GL_COLOR_BUFFER_BIT);
_glWidget->swapBuffers();
#if !defined(DISABLE_QML)
// Chromium rendering uses some GL functions that prevent nSight from capturing
// frames, so we only create the shared context if nsight is NOT active.
if (!nsightActive()) {
_chromiumShareContext = new OffscreenGLCanvas();
_chromiumShareContext->setObjectName("ChromiumShareContext");
_chromiumShareContext->create(_glWidget->qglContext());
_chromiumShareContext->makeCurrent();
if (!_chromiumShareContext->makeCurrent()) {
qCWarning(interfaceapp, "Unable to make chromium shared context current");
}
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
} else {
qCWarning(interfaceapp) << "nSIGHT detected, disabling chrome rendering";
// Build an offscreen GL context for the main thread.
_offscreenContext = new OffscreenGLCanvas();
_offscreenContext->setObjectName("MainThreadContext");
_offscreenContext->create(_glWidget->qglContext());
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
#endif
_offscreenContext->doneCurrent();
_offscreenContext->setThreadContext();
// Build a shared canvas / context for the QML rendering
_glWidget->makeCurrent();
_qmlShareContext = new OffscreenGLCanvas();
_qmlShareContext->setObjectName("QmlShareContext");
_qmlShareContext->create(_glWidget->qglContext());
if (!_qmlShareContext->makeCurrent()) {
qCWarning(interfaceapp, "Unable to make QML shared context current");
// Move the GL widget context to the render event handler thread
_renderEventHandler = new RenderEventHandler(_glWidget->qglContext());
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
OffscreenQmlSurface::setSharedContext(_qmlShareContext->getContext());
_qmlShareContext->doneCurrent();
// Create the GPU backend
// Requires the window context, because that's what's used in the actual rendering
// and the GPU backend will make things like the VAO which cannot be shared across
// contexts
_glWidget->makeCurrent();
gpu::Context::init<gpu::gl::GLBackend>();
qApp->setProperty(hifi::properties::gl::MAKE_PROGRAM_CALLBACK,
QVariant::fromValue((void*)(&gpu::gl::GLBackend::makeProgram)));
_gpuContext = std::make_shared<gpu::Context>();
// The gpu context can make child contexts for transfers, so
// we need to restore primary rendering context
_glWidget->makeCurrent();
_gpuContext = std::make_shared<gpu::Context>();
initDisplay();
qCDebug(interfaceapp, "Initialized Display.");
// Restore the default main thread context
_offscreenContext->makeCurrent();
updateDisplayMode();
}
void Application::initializeRenderEngine() {
_offscreenContext->makeCurrent();
// FIXME: on low end systems os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread.
DeadlockWatchdogThread::withPause([&] {
@ -2492,66 +2538,44 @@ void Application::initializeGL() {
// Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success.
DependencyManager::get<GeometryCache>()->initializeShapePipelines();
});
_offscreenContext = new OffscreenGLCanvas();
_offscreenContext->setObjectName("MainThreadContext");
_offscreenContext->create(_glWidget->qglContext());
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
_offscreenContext->doneCurrent();
_offscreenContext->setThreadContext();
_renderEventHandler = new RenderEventHandler(_glWidget->qglContext());
// The UI can't be created until the primary OpenGL
// context is created, because it needs to share
// texture resources
// Needs to happen AFTER the render engine initialization to access its configuration
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
initializeUi();
qCDebug(interfaceapp, "Initialized Offscreen UI.");
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
init();
qCDebug(interfaceapp, "init() complete.");
// create thread for parsing of octree data independent of the main network and rendering threads
_octreeProcessor.initialize(_enableProcessOctreeThread);
connect(&_octreeProcessor, &OctreePacketProcessor::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
_entityEditSender.initialize(_enableProcessOctreeThread);
_idleLoopStdev.reset();
// Restore the primary GL content for the main thread
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
// update before the first render
update(0);
}
extern void setupPreferences();
void Application::initializeUi() {
// Build a shared canvas / context for the Chromium processes
#if !defined(DISABLE_QML)
// Chromium rendering uses some GL functions that prevent nSight from capturing
// frames, so we only create the shared context if nsight is NOT active.
if (!nsightActive()) {
_chromiumShareContext = new OffscreenGLCanvas();
_chromiumShareContext->setObjectName("ChromiumShareContext");
_chromiumShareContext->create(_offscreenContext->getContext());
if (!_chromiumShareContext->makeCurrent()) {
qCWarning(interfaceapp, "Unable to make chromium shared context current");
}
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
_chromiumShareContext->doneCurrent();
// Restore the GL widget context
_offscreenContext->makeCurrent();
} else {
qCWarning(interfaceapp) << "nSIGHT detected, disabling chrome rendering";
}
#endif
// Build a shared canvas / context for the QML rendering
_qmlShareContext = new OffscreenGLCanvas();
_qmlShareContext->setObjectName("QmlShareContext");
_qmlShareContext->create(_offscreenContext->getContext());
if (!_qmlShareContext->makeCurrent()) {
qCWarning(interfaceapp, "Unable to make QML shared context current");
}
OffscreenQmlSurface::setSharedContext(_qmlShareContext->getContext());
_qmlShareContext->doneCurrent();
// Restore the GL widget context
_offscreenContext->makeCurrent();
// Make sure all QML surfaces share the main thread GL context
OffscreenQmlSurface::setSharedContext(_offscreenContext->getContext());
OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "OverlayWindowTest.qml" },
[](QQmlContext* context) {
qDebug() << "Whitelist OverlayWindow worked";
context->setContextProperty("OverlayWindowTestString", "TestWorked");
});
OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "hifi/audio/Audio.qml" },
[](QQmlContext* context) {
qDebug() << "QQQ" << __FUNCTION__ << "Whitelist Audio worked";
});
AddressBarDialog::registerType();
ErrorDialog::registerType();
@ -2567,6 +2591,7 @@ void Application::initializeUi() {
QUrl{ "hifi/commerce/common/EmulatedMarketplaceHeader.qml" },
QUrl{ "hifi/commerce/common/FirstUseTutorial.qml" },
QUrl{ "hifi/commerce/common/SortableListModel.qml" },
QUrl{ "hifi/commerce/common/sendAsset/SendAsset.qml" },
QUrl{ "hifi/commerce/inspectionCertificate/InspectionCertificate.qml" },
QUrl{ "hifi/commerce/purchases/PurchasedItem.qml" },
QUrl{ "hifi/commerce/purchases/Purchases.qml" },
@ -2579,7 +2604,6 @@ void Application::initializeUi() {
QUrl{ "hifi/commerce/wallet/SecurityImageChange.qml" },
QUrl{ "hifi/commerce/wallet/SecurityImageModel.qml" },
QUrl{ "hifi/commerce/wallet/SecurityImageSelection.qml" },
QUrl{ "hifi/commerce/wallet/sendMoney/SendMoney.qml" },
QUrl{ "hifi/commerce/wallet/Wallet.qml" },
QUrl{ "hifi/commerce/wallet/WalletHome.qml" },
QUrl{ "hifi/commerce/wallet/WalletSetup.qml" },
@ -2654,6 +2678,16 @@ void Application::initializeUi() {
auto offscreenSurfaceCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1);
offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2);
// Now that the menu is instantiated, ensure the display plugin menu is properly updated
updateDisplayMode();
flushMenuUpdates();
// The display plugins are created before the menu now, so we need to do this here to hide the menu bar
// now that it exists
if (_window && _window->isFullScreen()) {
setFullscreen(nullptr, true);
}
}
@ -3150,6 +3184,10 @@ void Application::loadServerlessDomain(QUrl domainURL) {
tmpTree->sendEntities(&_entityEditSender, getEntities()->getTree(), 0, 0, 0);
}
std::map<QString, QString> namedPaths = tmpTree->getNamedPaths();
nodeList->getDomainHandler().connectedToServerless(namedPaths);
_fullSceneReceivedCounter++;
}
@ -3461,7 +3499,8 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_M:
if (isMeta) {
DependencyManager::get<AudioClient>()->toggleMute();
auto audioClient = DependencyManager::get<AudioClient>();
audioClient->setMuted(!audioClient->isMuted());
}
break;
@ -4611,11 +4650,8 @@ QVector<EntityItemID> Application::pasteEntities(float x, float y, float z) {
return _entityClipboard->sendEntities(&_entityEditSender, getEntities()->getTree(), x, y, z);
}
void Application::initDisplay() {
}
void Application::init() {
_offscreenContext->makeCurrent();
// Make sure Login state is up to date
DependencyManager::get<DialogsManager>()->toggleLoginDialog();
if (!DISABLE_DEFERRED) {
@ -4821,7 +4857,7 @@ void Application::updateThreads(float deltaTime) {
void Application::toggleOverlays() {
auto menu = Menu::getInstance();
menu->setIsOptionChecked(MenuOption::Overlays, menu->isOptionChecked(MenuOption::Overlays));
menu->setIsOptionChecked(MenuOption::Overlays, !menu->isOptionChecked(MenuOption::Overlays));
}
void Application::setOverlaysVisible(bool visible) {
@ -5119,7 +5155,7 @@ void Application::update(float deltaTime) {
if (menu->isOptionChecked(MenuOption::AutoMuteAudio) && !audioClient->isMuted()) {
if (_lastFaceTrackerUpdate > 0
&& ((usecTimestampNow() - _lastFaceTrackerUpdate) > MUTE_MICROPHONE_AFTER_USECS)) {
audioClient->toggleMute();
audioClient->setMuted(true);
_lastFaceTrackerUpdate = 0;
}
} else {
@ -5260,12 +5296,12 @@ void Application::update(float deltaTime) {
QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
{
PROFILE_RANGE(simulation_physics, "Physics");
PerformanceTimer perfTimer("physics");
PROFILE_RANGE(simulation_physics, "Simulation");
PerformanceTimer perfTimer("simulation");
if (_physicsEnabled) {
{
PROFILE_RANGE(simulation_physics, "PreStep");
PerformanceTimer perfTimer("preStep)");
PROFILE_RANGE(simulation_physics, "PrePhysics");
PerformanceTimer perfTimer("prePhysics)");
{
const VectorOfMotionStates& motionStates = _entitySimulation->getObjectsToRemoveFromPhysics();
_physicsEngine->removeObjects(motionStates);
@ -5299,59 +5335,63 @@ void Application::update(float deltaTime) {
});
}
{
PROFILE_RANGE(simulation_physics, "Step");
PerformanceTimer perfTimer("step");
PROFILE_RANGE(simulation_physics, "StepPhysics");
PerformanceTimer perfTimer("stepPhysics");
getEntities()->getTree()->withWriteLock([&] {
_physicsEngine->stepSimulation();
});
}
{
PROFILE_RANGE(simulation_physics, "PostStep");
PerformanceTimer perfTimer("postStep");
if (_physicsEngine->hasOutgoingChanges()) {
// grab the collision events BEFORE handleOutgoingChanges() because at this point
// we have a better idea of which objects we own or should own.
auto& collisionEvents = _physicsEngine->getCollisionEvents();
{
PROFILE_RANGE(simulation_physics, "PostPhysics");
PerformanceTimer perfTimer("postPhysics");
// grab the collision events BEFORE handleChangedMotionStates() because at this point
// we have a better idea of which objects we own or should own.
auto& collisionEvents = _physicsEngine->getCollisionEvents();
getEntities()->getTree()->withWriteLock([&] {
PROFILE_RANGE(simulation_physics, "HandleChanges");
PerformanceTimer perfTimer("handleChanges");
getEntities()->getTree()->withWriteLock([&] {
PROFILE_RANGE(simulation_physics, "HandleChanges");
PerformanceTimer perfTimer("handleChanges");
const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates();
_entitySimulation->handleChangedMotionStates(outgoingChanges);
avatarManager->handleChangedMotionStates(outgoingChanges);
const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates();
_entitySimulation->handleChangedMotionStates(outgoingChanges);
avatarManager->handleChangedMotionStates(outgoingChanges);
const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates();
_entitySimulation->handleDeactivatedMotionStates(deactivations);
});
const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates();
_entitySimulation->handleDeactivatedMotionStates(deactivations);
});
if (!_aboutToQuit) {
// handleCollisionEvents() AFTER handleOutgoingChanges()
{
PROFILE_RANGE(simulation_physics, "CollisionEvents");
avatarManager->handleCollisionEvents(collisionEvents);
// Collision events (and their scripts) must not be handled when we're locked, above. (That would risk
// deadlock.)
_entitySimulation->handleCollisionEvents(collisionEvents);
if (!_aboutToQuit) {
// handleCollisionEvents() AFTER handleChangedMotionStates()
{
PROFILE_RANGE(simulation_physics, "CollisionEvents");
avatarManager->handleCollisionEvents(collisionEvents);
// Collision events (and their scripts) must not be handled when we're locked, above. (That would risk
// deadlock.)
_entitySimulation->handleCollisionEvents(collisionEvents);
}
}
{
PROFILE_RANGE(simulation_physics, "MyAvatar");
myAvatar->harvestResultsFromPhysicsSimulation(deltaTime);
}
if (PerformanceTimer::isActive() &&
Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) &&
Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsTiming)) {
_physicsEngine->harvestPerformanceStats();
}
// NOTE: the PhysicsEngine stats are written to stdout NOT to Qt log framework
_physicsEngine->dumpStatsIfNecessary();
}
if (!_aboutToQuit) {
// NOTE: the getEntities()->update() call below will wait for lock
// and will simulate entity motion (the EntityTree has been given an EntitySimulation).
// and will provide non-physical entity motion
getEntities()->update(true); // update the models...
}
{
PROFILE_RANGE(simulation_physics, "MyAvatar");
myAvatar->harvestResultsFromPhysicsSimulation(deltaTime);
}
if (PerformanceTimer::isActive() &&
Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) &&
Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsSimulationTiming)) {
_physicsEngine->harvestPerformanceStats();
}
// NOTE: the PhysicsEngine stats are written to stdout NOT to Qt log framework
_physicsEngine->dumpStatsIfNecessary();
}
}
} else {
@ -5907,6 +5947,9 @@ void Application::nodeActivated(SharedNodePointer node) {
}
getMyAvatar()->markIdentityDataChanged();
getMyAvatar()->resetLastSent();
// transmit a "sendAll" packet to the AvatarMixer we just connected to.
getMyAvatar()->sendAvatarDataPacket(true);
}
}
@ -6012,9 +6055,7 @@ bool Application::nearbyEntitiesAreReadyForPhysics() {
bool result = true;
foreach (EntityItemPointer entity, entities) {
if (entity->shouldBePhysical() && !entity->isReadyToComputeShape()) {
static QString repeatedMessage =
LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*");
qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName();
HIFI_FCDEBUG(interfaceapp(), "Physics disabled until entity loads: " << entity->getID() << entity->getName());
// don't break here because we want all the relevant entities to start their downloads
result = false;
}
@ -7504,21 +7545,34 @@ void Application::updateDisplayMode() {
qFatal("Attempted to switch display plugins from a non-main thread");
}
auto menu = Menu::getInstance();
auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
// Once time initialization code
static std::once_flag once;
std::call_once(once, [&] {
bool first = true;
// first sort the plugins into groupings: standard, advanced, developer
DisplayPluginList standard;
DisplayPluginList advanced;
DisplayPluginList developer;
foreach(auto displayPlugin, displayPlugins) {
displayPlugin->setContext(_gpuContext);
auto grouping = displayPlugin->getGrouping();
switch (grouping) {
QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged,
[this](const QSize& size) { resizeGL(); });
QObject::connect(displayPlugin.get(), &DisplayPlugin::resetSensorsRequested, this, &Application::requestReset);
}
});
// Once time initialization code that depends on the UI being available
auto menu = Menu::getInstance();
if (menu) {
static std::once_flag onceUi;
std::call_once(onceUi, [&] {
bool first = true;
// first sort the plugins into groupings: standard, advanced, developer
DisplayPluginList standard;
DisplayPluginList advanced;
DisplayPluginList developer;
foreach(auto displayPlugin, displayPlugins) {
displayPlugin->setContext(_gpuContext);
auto grouping = displayPlugin->getGrouping();
switch (grouping) {
case Plugin::ADVANCED:
advanced.push_back(displayPlugin);
break;
@ -7528,42 +7582,40 @@ void Application::updateDisplayMode() {
default:
standard.push_back(displayPlugin);
break;
}
}
}
// concatenate the groupings into a single list in the order: standard, advanced, developer
standard.insert(std::end(standard), std::begin(advanced), std::end(advanced));
standard.insert(std::end(standard), std::begin(developer), std::end(developer));
// concatenate the groupings into a single list in the order: standard, advanced, developer
standard.insert(std::end(standard), std::begin(advanced), std::end(advanced));
standard.insert(std::end(standard), std::begin(developer), std::end(developer));
foreach(auto displayPlugin, standard) {
addDisplayPluginToMenu(displayPlugin, first);
auto displayPluginName = displayPlugin->getName();
QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) {
resizeGL();
});
QObject::connect(displayPlugin.get(), &DisplayPlugin::resetSensorsRequested, this, &Application::requestReset);
first = false;
}
foreach(auto displayPlugin, standard) {
addDisplayPluginToMenu(displayPlugin, first);
first = false;
}
// after all plugins have been added to the menu, add a separator to the menu
auto menu = Menu::getInstance();
auto parent = menu->getMenu(MenuOption::OutputMenu);
parent->addSeparator();
});
// after all plugins have been added to the menu, add a separator to the menu
auto parent = menu->getMenu(MenuOption::OutputMenu);
parent->addSeparator();
});
}
// Default to the first item on the list, in case none of the menu items match
DisplayPluginPointer newDisplayPlugin = displayPlugins.at(0);
foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
QString name = displayPlugin->getName();
QAction* action = menu->getActionForOption(name);
// Menu might have been removed if the display plugin lost
if (!action) {
continue;
}
if (action->isChecked()) {
newDisplayPlugin = displayPlugin;
break;
if (menu) {
foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
QString name = displayPlugin->getName();
QAction* action = menu->getActionForOption(name);
// Menu might have been removed if the display plugin lost
if (!action) {
continue;
}
if (action->isChecked()) {
newDisplayPlugin = displayPlugin;
break;
}
}
}
@ -7571,8 +7623,13 @@ void Application::updateDisplayMode() {
return;
}
setDisplayPlugin(newDisplayPlugin);
}
void Application::setDisplayPlugin(DisplayPluginPointer newDisplayPlugin) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto desktop = offscreenUi->getDesktop();
auto menu = Menu::getInstance();
// Make the switch atomic from the perspective of other threads
{
@ -7593,6 +7650,8 @@ void Application::updateDisplayMode() {
bool active = newDisplayPlugin->activate();
if (!active) {
auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
// If the new plugin fails to activate, fallback to last display
qWarning() << "Failed to activate display: " << newDisplayPlugin->getName();
newDisplayPlugin = oldDisplayPlugin;
@ -7613,13 +7672,6 @@ void Application::updateDisplayMode() {
if (!active) {
qFatal("Failed to activate fallback plugin");
}
// We've changed the selection - it should be reflected in the menu
QAction* action = menu->getActionForOption(newDisplayPlugin->getName());
if (!action) {
qFatal("Failed to find activated plugin");
}
action->setChecked(true);
}
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
@ -7646,14 +7698,21 @@ void Application::updateDisplayMode() {
getMyAvatar()->reset(false);
// switch to first person if entering hmd and setting is checked
if (isHmd && menu->isOptionChecked(MenuOption::FirstPersonHMD)) {
menu->setIsOptionChecked(MenuOption::FirstPerson, true);
cameraMenuChanged();
}
if (menu) {
QAction* action = menu->getActionForOption(newDisplayPlugin->getName());
if (action) {
action->setChecked(true);
}
// Remove the mirror camera option from menu if in HMD mode
auto mirrorAction = menu->getActionForOption(MenuOption::FullscreenMirror);
mirrorAction->setVisible(!isHmd);
if (isHmd && menu->isOptionChecked(MenuOption::FirstPersonHMD)) {
menu->setIsOptionChecked(MenuOption::FirstPerson, true);
cameraMenuChanged();
}
// Remove the mirror camera option from menu if in HMD mode
auto mirrorAction = menu->getActionForOption(MenuOption::FullscreenMirror);
mirrorAction->setVisible(!isHmd);
}
Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin");
}
@ -7729,15 +7788,18 @@ void Application::unresponsiveApplication() {
}
void Application::setActiveDisplayPlugin(const QString& pluginName) {
auto menu = Menu::getInstance();
foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
DisplayPluginPointer newDisplayPlugin;
for (DisplayPluginPointer displayPlugin : PluginManager::getInstance()->getDisplayPlugins()) {
QString name = displayPlugin->getName();
QAction* action = menu->getActionForOption(name);
if (pluginName == name) {
action->setChecked(true);
newDisplayPlugin = displayPlugin;
break;
}
}
updateDisplayMode();
if (newDisplayPlugin) {
setDisplayPlugin(newDisplayPlugin);
}
}
void Application::handleLocalServerConnection() const {

View file

@ -145,6 +145,7 @@ public:
Q_INVOKABLE QString getUserAgent();
void initializeGL();
void initializeRenderEngine();
void initializeUi();
void updateCamera(RenderArgs& renderArgs, float deltaTime);
@ -437,6 +438,7 @@ private slots:
static void packetSent(quint64 length);
static void addingEntityWithCertificate(const QString& certificateID, const QString& placeName);
void updateDisplayMode();
void setDisplayPlugin(DisplayPluginPointer newPlugin);
void domainConnectionRefused(const QString& reasonMessage, int reason, const QString& extraInfo);
void addAssetToWorldCheckModelSize();
@ -449,7 +451,6 @@ private slots:
void switchDisplayMode();
private:
static void initDisplay();
void init();
bool handleKeyEventForFocusedEntityOrOverlay(QEvent* event);
bool handleFileOpenEvent(QFileOpenEvent* event);

View file

@ -31,10 +31,41 @@ using namespace crashpad;
static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL };
static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN };
static std::wstring gIPCPipe;
extern QString qAppFileName();
// crashpad::AnnotationList* crashpadAnnotations { nullptr };
#include <Windows.h>
LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) {
static const DWORD EXTERNAL_EXCEPTION_CODE{ 0xe06d7363 };
static const DWORD HEAP_CORRUPTION_CODE{ 0xc0000374 };
auto exceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode;
if (exceptionCode == EXTERNAL_EXCEPTION_CODE) {
return EXCEPTION_CONTINUE_SEARCH;
}
if (exceptionCode == HEAP_CORRUPTION_CODE) {
qCritical() << "VectoredExceptionHandler: Heap corruption:" << QString::number(exceptionCode, 16);
CrashpadClient client;
if (gIPCPipe.length()) {
bool rc = client.SetHandlerIPCPipe(gIPCPipe);
qCritical() << "SetHandlerIPCPipe = " << rc;
} else {
qCritical() << "No IPC Pipe was previously defined for crash handler.";
}
qCritical() << "Calling DumpAndCrash()";
client.DumpAndCrash(pExceptionInfo);
return EXCEPTION_CONTINUE_SEARCH;
}
return EXCEPTION_CONTINUE_SEARCH;
}
bool startCrashHandler() {
if (BACKTRACE_URL.empty() || BACKTRACE_TOKEN.empty()) {
return false;
@ -76,7 +107,12 @@ bool startCrashHandler() {
// Enable automated uploads.
database->GetSettings()->SetUploadsEnabled(true);
return client.StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true);
bool result = client.StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true);
gIPCPipe = client.GetHandlerIPCPipe();
AddVectoredExceptionHandler(0, vectoredExceptionHandler);
return result;
}
void setCrashAnnotation(std::string name, std::string value) {

View file

@ -87,10 +87,8 @@ EntityDynamicPointer InterfaceDynamicFactory::factoryBA(EntityItemPointer ownerE
if (dynamic) {
dynamic->deserialize(data);
if (dynamic->lifetimeIsOver()) {
static QString repeatedMessage =
LogHandler::getInstance().addRepeatedMessageRegex(".*factoryBA lifetimeIsOver during dynamic creation.*");
qDebug() << "InterfaceDynamicFactory::factoryBA lifetimeIsOver during dynamic creation --"
<< dynamic->getExpires() << "<" << usecTimestampNow();
HIFI_FDEBUG("InterfaceDynamicFactory::factoryBA lifetimeIsOver during dynamic creation --"
<< dynamic->getExpires() << "<" << usecTimestampNow());
return nullptr;
}
}

View file

@ -683,11 +683,12 @@ Menu::Menu() {
qApp, SLOT(enablePerfStats(bool)));
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::OnlyDisplayTopTen, 0, true);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandUpdateTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandSimulationTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandPhysicsTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMyAvatarTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMyAvatarSimulateTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandOtherAvatarTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandPaintGLTiming, 0, false);
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandPhysicsSimulationTiming, 0, false);
addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::FrameTimer);
addActionToQMenuAndActionHash(timingMenu, MenuOption::RunTimingTests, 0, qApp, SLOT(runTests()));
@ -749,32 +750,32 @@ Menu::Menu() {
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunction);
connect(action, &QAction::triggered, qApp, []() { crash::pureVirtualCall(); });
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunctionThreaded);
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::pureVirtualCall(); }); });
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::pureVirtualCall).join(); });
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashDoubleFree);
connect(action, &QAction::triggered, qApp, []() { crash::doubleFree(); });
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashDoubleFreeThreaded);
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::doubleFree(); }); });
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::doubleFree).join(); });
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashAbort);
connect(action, &QAction::triggered, qApp, []() { crash::doAbort(); });
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashAbortThreaded);
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::doAbort(); }); });
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::doAbort).join(); });
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNullDereference);
connect(action, &QAction::triggered, qApp, []() { crash::nullDeref(); });
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNullDereferenceThreaded);
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::nullDeref(); }); });
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::nullDeref).join(); });
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOutOfBoundsVectorAccess);
connect(action, &QAction::triggered, qApp, []() { crash::outOfBoundsVectorCrash(); });
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOutOfBoundsVectorAccessThreaded);
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::outOfBoundsVectorCrash(); }); });
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::outOfBoundsVectorCrash).join(); });
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFault);
connect(action, &QAction::triggered, qApp, []() { crash::newFault(); });
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFaultThreaded);
connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::newFault(); }); });
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::newFault).join(); });
// Developer > Log...
addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L,

View file

@ -105,7 +105,8 @@ namespace MenuOption {
const QString ExpandMyAvatarTiming = "Expand /myAvatar";
const QString ExpandOtherAvatarTiming = "Expand /otherAvatar";
const QString ExpandPaintGLTiming = "Expand /paintGL";
const QString ExpandPhysicsSimulationTiming = "Expand /physics";
const QString ExpandSimulationTiming = "Expand /simulation";
const QString ExpandPhysicsTiming = "Expand /physics";
const QString ExpandUpdateTiming = "Expand /update";
const QString FirstPerson = "First Person";
const QString FirstPersonHMD = "Enter First Person Mode in HMD";

View file

@ -56,8 +56,8 @@ Handler(buy)
Handler(receiveAt)
Handler(balance)
Handler(inventory)
Handler(transferHfcToNode)
Handler(transferHfcToUsername)
Handler(transferAssetToNode)
Handler(transferAssetToUsername)
Handler(alreadyOwned)
Handler(availableUpdates)
Handler(updateItem)
@ -173,7 +173,8 @@ QString userLink(const QString& username, const QString& placename) {
QString transactionString(const QJsonObject& valueObject) {
int sentCerts = valueObject["sent_certs"].toInt();
int receivedCerts = valueObject["received_certs"].toInt();
int sent = valueObject["sent_money"].toInt();
int sentMoney = valueObject["sent_money"].toInt();
int receivedMoney = valueObject["received_money"].toInt();
int dateInteger = valueObject["created_at"].toInt();
QString message = valueObject["message"].toString();
QDateTime createdAt(QDateTime::fromSecsSinceEpoch(dateInteger, Qt::UTC));
@ -181,7 +182,7 @@ QString transactionString(const QJsonObject& valueObject) {
if (sentCerts <= 0 && receivedCerts <= 0 && !KNOWN_USERS.contains(valueObject["sender_name"].toString())) {
// this is an hfc transfer.
if (sent > 0) {
if (sentMoney > 0) {
QString recipient = userLink(valueObject["recipient_name"].toString(), valueObject["place_name"].toString());
result += QString("Money sent to %1").arg(recipient);
} else {
@ -191,6 +192,18 @@ QString transactionString(const QJsonObject& valueObject) {
if (!message.isEmpty()) {
result += QString("<br>with memo: <i>\"%1\"</i>").arg(message);
}
} else if (sentMoney <= 0 && receivedMoney <= 0 && (sentCerts > 0 || receivedCerts > 0) && !KNOWN_USERS.contains(valueObject["sender_name"].toString())) {
// this is a non-HFC asset transfer.
if (sentCerts > 0) {
QString recipient = userLink(valueObject["recipient_name"].toString(), valueObject["place_name"].toString());
result += QString("Gift sent to %1").arg(recipient);
} else {
QString sender = userLink(valueObject["sender_name"].toString(), valueObject["place_name"].toString());
result += QString("Gift from %1").arg(sender);
}
if (!message.isEmpty()) {
result += QString("<br>with memo: <i>\"%1\"</i>").arg(message);
}
} else {
result += valueObject["message"].toString();
}
@ -354,27 +367,41 @@ void Ledger::certificateInfo(const QString& certificateId) {
send(endpoint, "certificateInfoSuccess", "certificateInfoFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::None, request);
}
void Ledger::transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage) {
void Ledger::transferAssetToNode(const QString& hfc_key, const QString& nodeID, const QString& certificateID, const int& amount, const QString& optionalMessage) {
QJsonObject transaction;
transaction["public_key"] = hfc_key;
transaction["node_id"] = nodeID;
transaction["quantity"] = amount;
transaction["message"] = optionalMessage;
transaction["place_name"] = DependencyManager::get<AddressManager>()->getPlaceName();
if (!certificateID.isEmpty()) {
transaction["certificate_id"] = certificateID;
}
QJsonDocument transactionDoc{ transaction };
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
signedSend("transaction", transactionString, hfc_key, "transfer_hfc_to_node", "transferHfcToNodeSuccess", "transferHfcToNodeFailure");
if (certificateID.isEmpty()) {
signedSend("transaction", transactionString, hfc_key, "transfer_hfc_to_node", "transferAssetToNodeSuccess", "transferAssetToNodeFailure");
} else {
signedSend("transaction", transactionString, hfc_key, "transfer_asset_to_node", "transferAssetToNodeSuccess", "transferAssetToNodeFailure");
}
}
void Ledger::transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage) {
void Ledger::transferAssetToUsername(const QString& hfc_key, const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage) {
QJsonObject transaction;
transaction["public_key"] = hfc_key;
transaction["username"] = username;
transaction["quantity"] = amount;
transaction["message"] = optionalMessage;
if (!certificateID.isEmpty()) {
transaction["certificate_id"] = certificateID;
}
QJsonDocument transactionDoc{ transaction };
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
signedSend("transaction", transactionString, hfc_key, "transfer_hfc_to_user", "transferHfcToUsernameSuccess", "transferHfcToUsernameFailure");
if (certificateID.isEmpty()) {
signedSend("transaction", transactionString, hfc_key, "transfer_hfc_to_user", "transferAssetToUsernameSuccess", "transferAssetToUsernameFailure");
} else {
signedSend("transaction", transactionString, hfc_key, "transfer_asset_to_user", "transferAssetToUsernameSuccess", "transferAssetToUsernameFailure");
}
}
void Ledger::alreadyOwned(const QString& marketplaceId) {

View file

@ -33,8 +33,8 @@ public:
void account();
void updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings = false, const bool controlledFailure = false);
void certificateInfo(const QString& certificateId);
void transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage);
void transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage);
void transferAssetToNode(const QString& hfc_key, const QString& nodeID, const QString& certificateID, const int& amount, const QString& optionalMessage);
void transferAssetToUsername(const QString& hfc_key, const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage);
void alreadyOwned(const QString& marketplaceId);
void getAvailableUpdates(const QString& itemId = "");
void updateItem(const QString& hfc_key, const QString& certificate_id);
@ -56,8 +56,8 @@ signals:
void accountResult(QJsonObject result);
void locationUpdateResult(QJsonObject result);
void certificateInfoResult(QJsonObject result);
void transferHfcToNodeResult(QJsonObject result);
void transferHfcToUsernameResult(QJsonObject result);
void transferAssetToNodeResult(QJsonObject result);
void transferAssetToUsernameResult(QJsonObject result);
void alreadyOwnedResult(QJsonObject result);
void availableUpdatesResult(QJsonObject result);
void updateItemResult(QJsonObject result);
@ -81,10 +81,10 @@ public slots:
void updateLocationFailure(QNetworkReply& reply);
void certificateInfoSuccess(QNetworkReply& reply);
void certificateInfoFailure(QNetworkReply& reply);
void transferHfcToNodeSuccess(QNetworkReply& reply);
void transferHfcToNodeFailure(QNetworkReply& reply);
void transferHfcToUsernameSuccess(QNetworkReply& reply);
void transferHfcToUsernameFailure(QNetworkReply& reply);
void transferAssetToNodeSuccess(QNetworkReply& reply);
void transferAssetToNodeFailure(QNetworkReply& reply);
void transferAssetToUsernameSuccess(QNetworkReply& reply);
void transferAssetToUsernameFailure(QNetworkReply& reply);
void alreadyOwnedSuccess(QNetworkReply& reply);
void alreadyOwnedFailure(QNetworkReply& reply);
void availableUpdatesSuccess(QNetworkReply& reply);

View file

@ -36,8 +36,8 @@ QmlCommerce::QmlCommerce() {
connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult);
connect(ledger.data(), &Ledger::alreadyOwnedResult, this, &QmlCommerce::alreadyOwnedResult);
connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus);
connect(ledger.data(), &Ledger::transferHfcToNodeResult, this, &QmlCommerce::transferHfcToNodeResult);
connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult);
connect(ledger.data(), &Ledger::transferAssetToNodeResult, this, &QmlCommerce::transferAssetToNodeResult);
connect(ledger.data(), &Ledger::transferAssetToUsernameResult, this, &QmlCommerce::transferAssetToUsernameResult);
connect(ledger.data(), &Ledger::availableUpdatesResult, this, &QmlCommerce::availableUpdatesResult);
connect(ledger.data(), &Ledger::updateItemResult, this, &QmlCommerce::updateItemResult);
@ -166,28 +166,28 @@ void QmlCommerce::certificateInfo(const QString& certificateId) {
ledger->certificateInfo(certificateId);
}
void QmlCommerce::transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage) {
void QmlCommerce::transferAssetToNode(const QString& nodeID, const QString& certificateID, const int& amount, const QString& optionalMessage) {
auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>();
QStringList keys = wallet->listPublicKeys();
if (keys.count() == 0) {
QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } };
return emit buyResult(result);
return emit transferAssetToNodeResult(result);
}
QString key = keys[0];
ledger->transferHfcToNode(key, nodeID, amount, optionalMessage);
ledger->transferAssetToNode(key, nodeID, certificateID, amount, optionalMessage);
}
void QmlCommerce::transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage) {
void QmlCommerce::transferAssetToUsername(const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage) {
auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>();
QStringList keys = wallet->listPublicKeys();
if (keys.count() == 0) {
QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } };
return emit buyResult(result);
return emit transferAssetToUsernameResult(result);
}
QString key = keys[0];
ledger->transferHfcToUsername(key, username, amount, optionalMessage);
ledger->transferAssetToUsername(key, username, certificateID, amount, optionalMessage);
}
void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID) {

View file

@ -48,8 +48,8 @@ signals:
void updateCertificateStatus(const QString& certID, uint certStatus);
void transferHfcToNodeResult(QJsonObject result);
void transferHfcToUsernameResult(QJsonObject result);
void transferAssetToNodeResult(QJsonObject result);
void transferAssetToUsernameResult(QJsonObject result);
void contentSetChanged(const QString& contentSetHref);
@ -81,8 +81,8 @@ protected:
Q_INVOKABLE void certificateInfo(const QString& certificateId);
Q_INVOKABLE void alreadyOwned(const QString& marketplaceId);
Q_INVOKABLE void transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void transferAssetToNode(const QString& nodeID, const QString& certificateID, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void transferAssetToUsername(const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID);

View file

@ -13,8 +13,9 @@
#include "Ledger.h"
#include "Wallet.h"
#include "Application.h"
#include "ui/ImageProvider.h"
#include "ui/SecurityImageProvider.h"
#include "scripting/HMDScriptingInterface.h"
#include <ui/TabletScriptingInterface.h>
#include <PathUtils.h>
#include <OffscreenUi.h>
@ -566,7 +567,6 @@ bool Wallet::generateKeyPair() {
}
QStringList Wallet::listPublicKeys() {
qCInfo(commerce) << "Enumerating public keys.";
return _publicKeys;
}
@ -607,11 +607,17 @@ QString Wallet::signWithKey(const QByteArray& text, const QString& key) {
}
void Wallet::updateImageProvider() {
// inform the image provider. Note it doesn't matter which one you inform, as the
// images are statics
auto engine = DependencyManager::get<OffscreenUi>()->getSurfaceContext()->engine();
auto imageProvider = reinterpret_cast<ImageProvider*>(engine->imageProvider(ImageProvider::PROVIDER_NAME));
imageProvider->setSecurityImage(_securityImage);
SecurityImageProvider* securityImageProvider;
// inform offscreenUI security image provider
QQmlEngine* engine = DependencyManager::get<OffscreenUi>()->getSurfaceContext()->engine();
securityImageProvider = reinterpret_cast<SecurityImageProvider*>(engine->imageProvider(SecurityImageProvider::PROVIDER_NAME));
securityImageProvider->setSecurityImage(_securityImage);
// inform tablet security image provider
QQmlEngine* tabletEngine = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system")->getTabletSurface()->getSurfaceContext()->engine();
securityImageProvider = reinterpret_cast<SecurityImageProvider*>(tabletEngine->imageProvider(SecurityImageProvider::PROVIDER_NAME));
securityImageProvider->setSecurityImage(_securityImage);
}
void Wallet::chooseSecurityImage(const QString& filename) {
@ -651,6 +657,7 @@ bool Wallet::getSecurityImage() {
// if already decrypted, don't do it again
if (_securityImage) {
updateImageProvider();
emit securityImageResult(true);
return true;
}

View file

@ -31,6 +31,8 @@
#include "UserActivityLogger.h"
#include "MainWindow.h"
#include "Profile.h"
#ifdef Q_OS_WIN
extern "C" {
typedef int(__stdcall * CHECKMINSPECPROC) ();
@ -40,6 +42,26 @@ extern "C" {
int main(int argc, const char* argv[]) {
setupHifiApplication(BuildInfo::INTERFACE_NAME);
// Early check for --traceFile argument
auto tracer = DependencyManager::set<tracing::Tracer>();
const char * traceFile = nullptr;
const QString traceFileFlag("--traceFile");
float traceDuration = 0.0f;
for (int a = 1; a < argc; ++a) {
if (traceFileFlag == argv[a] && argc > a + 1) {
traceFile = argv[a + 1];
if (argc > a + 2) {
traceDuration = atof(argv[a + 2]);
}
break;
}
}
if (traceFile != nullptr) {
tracer->startTracing();
}
PROFILE_SYNC_BEGIN(startup, "main startup", "");
#ifdef Q_OS_LINUX
QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
#endif
@ -235,7 +257,18 @@ int main(int argc, const char* argv[]) {
argvExtended.push_back("--ignore-gpu-blacklist");
int argcExtended = (int)argvExtended.size();
PROFILE_SYNC_END(startup, "main startup", "");
PROFILE_SYNC_BEGIN(startup, "app full ctor", "");
Application app(argcExtended, const_cast<char**>(argvExtended.data()), startupTime, runningMarkerExisted);
PROFILE_SYNC_END(startup, "app full ctor", "");
QTimer exitTimer;
if (traceDuration > 0.0f) {
exitTimer.setSingleShot(true);
QObject::connect(&exitTimer, &QTimer::timeout, &app, &Application::quit);
exitTimer.start(int(1000 * traceDuration));
}
#if 0
// If we failed the OpenGLVersion check, log it.
@ -273,6 +306,11 @@ int main(int argc, const char* argv[]) {
qCDebug(interfaceapp, "Created QT Application.");
exitCode = app.exec();
server.close();
if (traceFile != nullptr) {
tracer->stopTracing();
tracer->serialize(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/" + traceFile);
}
}
Application::shutdownPlugins();

View file

@ -71,7 +71,7 @@ void OctreePacketProcessor::processPacket(QSharedPointer<ReceivedMessage> messag
if (message->getVersion() != versionForPacketType(message->getType())) {
static QMultiMap<QUuid, PacketType> versionDebugSuppressMap;
const QUuid& senderUUID = message->getSourceID();
const QUuid& senderUUID = sendingNode->getUUID();
if (!versionDebugSuppressMap.contains(senderUUID, packetType)) {
qDebug() << "Was stats packet? " << wasStatsPacket;

View file

@ -50,110 +50,162 @@ float Audio::loudnessToLevel(float loudness) {
Audio::Audio() : _devices(_contextIsHMD) {
auto client = DependencyManager::get<AudioClient>().data();
connect(client, &AudioClient::muteToggled, this, &Audio::onMutedChanged);
connect(client, &AudioClient::noiseReductionChanged, this, &Audio::onNoiseReductionChanged);
connect(client, &AudioClient::muteToggled, this, &Audio::setMuted);
connect(client, &AudioClient::noiseReductionChanged, this, &Audio::enableNoiseReduction);
connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged);
connect(client, &AudioClient::inputVolumeChanged, this, &Audio::onInputVolumeChanged);
connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume);
connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
enableNoiseReduction(enableNoiseReductionSetting.get());
}
bool Audio::startRecording(const QString& filepath) {
auto client = DependencyManager::get<AudioClient>().data();
return client->startRecording(filepath);
return resultWithWriteLock<bool>([&] {
return client->startRecording(filepath);
});
}
bool Audio::getRecording() {
auto client = DependencyManager::get<AudioClient>().data();
return client->getRecording();
return resultWithReadLock<bool>([&] {
return client->getRecording();
});
}
void Audio::stopRecording() {
auto client = DependencyManager::get<AudioClient>().data();
client->stopRecording();
withWriteLock([&] {
client->stopRecording();
});
}
bool Audio::isMuted() const {
return resultWithReadLock<bool>([&] {
return _isMuted;
});
}
void Audio::setMuted(bool isMuted) {
if (_isMuted != isMuted) {
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "toggleMute");
bool changed = false;
withWriteLock([&] {
if (_isMuted != isMuted) {
_isMuted = isMuted;
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false));
changed = true;
}
});
if (changed) {
emit mutedChanged(isMuted);
}
}
void Audio::onMutedChanged() {
bool isMuted = DependencyManager::get<AudioClient>()->isMuted();
if (_isMuted != isMuted) {
_isMuted = isMuted;
emit mutedChanged(_isMuted);
}
bool Audio::noiseReductionEnabled() const {
return resultWithReadLock<bool>([&] {
return _enableNoiseReduction;
});
}
void Audio::enableNoiseReduction(bool enable) {
if (_enableNoiseReduction != enable) {
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setNoiseReduction", Q_ARG(bool, enable));
enableNoiseReductionSetting.set(enable);
bool changed = false;
withWriteLock([&] {
if (_enableNoiseReduction != enable) {
_enableNoiseReduction = enable;
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setNoiseReduction", Q_ARG(bool, enable), Q_ARG(bool, false));
enableNoiseReductionSetting.set(enable);
changed = true;
}
});
if (changed) {
emit noiseReductionChanged(enable);
}
}
void Audio::onNoiseReductionChanged() {
bool noiseReductionEnabled = DependencyManager::get<AudioClient>()->isNoiseReductionEnabled();
if (_enableNoiseReduction != noiseReductionEnabled) {
_enableNoiseReduction = noiseReductionEnabled;
emit noiseReductionChanged(_enableNoiseReduction);
}
float Audio::getInputVolume() const {
return resultWithReadLock<bool>([&] {
return _inputVolume;
});
}
void Audio::setInputVolume(float volume) {
// getInputVolume will not reflect changes synchronously, so clamp beforehand
volume = glm::clamp(volume, 0.0f, 1.0f);
if (_inputVolume != volume) {
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setInputVolume", Q_ARG(float, volume));
bool changed = false;
withWriteLock([&] {
if (_inputVolume != volume) {
_inputVolume = volume;
auto client = DependencyManager::get<AudioClient>().data();
QMetaObject::invokeMethod(client, "setInputVolume", Q_ARG(float, volume), Q_ARG(bool, false));
changed = true;
}
});
if (changed) {
emit inputVolumeChanged(volume);
}
}
void Audio::onInputVolumeChanged(float volume) {
if (_inputVolume != volume) {
_inputVolume = volume;
emit inputVolumeChanged(_inputVolume);
}
float Audio::getInputLevel() const {
return resultWithReadLock<float>([&] {
return _inputLevel;
});
}
void Audio::onInputLoudnessChanged(float loudness) {
float level = loudnessToLevel(loudness);
if (_inputLevel != level) {
_inputLevel = level;
emit inputLevelChanged(_inputLevel);
bool changed = false;
withWriteLock([&] {
if (_inputLevel != level) {
_inputLevel = level;
changed = true;
}
});
if (changed) {
emit inputLevelChanged(level);
}
}
QString Audio::getContext() const {
return _contextIsHMD ? Audio::HMD : Audio::DESKTOP;
return resultWithReadLock<QString>([&] {
return _contextIsHMD ? Audio::HMD : Audio::DESKTOP;
});
}
void Audio::onContextChanged() {
bool changed = false;
bool isHMD = qApp->isHMDMode();
if (_contextIsHMD != isHMD) {
_contextIsHMD = isHMD;
emit contextChanged(getContext());
withWriteLock([&] {
if (_contextIsHMD != isHMD) {
_contextIsHMD = isHMD;
changed = true;
}
});
if (changed) {
emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP);
}
}
void Audio::setReverb(bool enable) {
DependencyManager::get<AudioClient>()->setReverb(enable);
withWriteLock([&] {
DependencyManager::get<AudioClient>()->setReverb(enable);
});
}
void Audio::setReverbOptions(const AudioEffectOptions* options) {
DependencyManager::get<AudioClient>()->setReverbOptions(options);
withWriteLock([&] {
DependencyManager::get<AudioClient>()->setReverbOptions(options);
});
}
void Audio::setInputDevice(const QAudioDeviceInfo& device, bool isHMD) {
_devices.chooseInputDevice(device, isHMD);
withWriteLock([&] {
_devices.chooseInputDevice(device, isHMD);
});
}
void Audio::setOutputDevice(const QAudioDeviceInfo& device, bool isHMD) {
_devices.chooseOutputDevice(device, isHMD);
withWriteLock([&] {
_devices.chooseOutputDevice(device, isHMD);
});
}

View file

@ -17,10 +17,11 @@
#include "AudioEffectOptions.h"
#include "SettingHandle.h"
#include "AudioFileWav.h"
#include <shared/ReadWriteLockable.h>
namespace scripting {
class Audio : public AudioScriptingInterface {
class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
Q_OBJECT
SINGLETON_DEPENDENCY
@ -40,16 +41,13 @@ public:
virtual ~Audio() {}
bool isMuted() const { return _isMuted; }
bool noiseReductionEnabled() const { return _enableNoiseReduction; }
float getInputVolume() const { return _inputVolume; }
float getInputLevel() const { return _inputLevel; }
bool isMuted() const;
bool noiseReductionEnabled() const;
float getInputVolume() const;
float getInputLevel() const;
QString getContext() const;
void setMuted(bool muted);
void enableNoiseReduction(bool enable);
void showMicMeter(bool show);
void setInputVolume(float volume);
Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD);
Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD);
@ -72,9 +70,9 @@ public slots:
void onContextChanged();
private slots:
void onMutedChanged();
void onNoiseReductionChanged();
void onInputVolumeChanged(float volume);
void setMuted(bool muted);
void enableNoiseReduction(bool enable);
void setInputVolume(float volume);
void onInputLoudnessChanged(float loudness);
protected:

View file

@ -188,7 +188,7 @@ void AudioDeviceList::onDeviceChanged(const QAudioDeviceInfo& device, bool isHMD
for (auto i = 0; i < _devices.size(); ++i) {
std::shared_ptr<AudioDevice> device = _devices[i];
bool &isSelected = isHMD ? device->selectedHMD : device->selectedDesktop;
bool& isSelected = isHMD ? device->selectedHMD : device->selectedDesktop;
if (isSelected && device->info != selectedDevice) {
isSelected = false;
} else if (device->info == selectedDevice) {
@ -259,7 +259,7 @@ void AudioDeviceList::onDevicesChanged(const QList<QAudioDeviceInfo>& devices) {
foreach(const QAudioDeviceInfo& deviceInfo, devices) {
for (bool isHMD : {false, true}) {
auto &backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName;
auto& backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName;
if (deviceInfo.deviceName() == backupSelectedDeviceName) {
QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
selectedDevice = deviceInfo;
@ -278,7 +278,7 @@ void AudioDeviceList::onDevicesChanged(const QList<QAudioDeviceInfo>& devices) {
for (bool isHMD : {false, true}) {
QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
bool &isSelected = isHMD ? device.selectedHMD : device.selectedDesktop;
bool& isSelected = isHMD ? device.selectedHMD : device.selectedDesktop;
if (!selectedDevice.isNull()) {
isSelected = (device.info == selectedDevice);

View file

@ -24,11 +24,154 @@
#include <WheelEvent.h>
class ScriptEngine;
/**jsdoc
* The Controller API provides facilities to interact with computer and controller hardware.
*
* <h5>Functions:</h5>
*
* <p>Properties</p>
* <ul>
* <li>{@link Controller.getActions|getActions}</li>
* <li>{@link Controller.getHardware|getHardware}</li>
* <li>{@link Controller.getStandard|getStandard}</li>
* </ul>
*
* <p>Mappings</p>
* <ul>
* <li>{@link Controller.disableMapping|disableMapping}</li>
* <li>{@link Controller.enableMapping|enableMapping}</li>
* <li>{@link Controller.loadMapping|loadMapping}</li>
* <li>{@link Controller.newMapping|newMapping}</li>
* <li>{@link Controller.parseMapping|parseMapping}</li>
* </ul>
*
* <p>Input, Hardware, and Action Reflection</p>
* <ul>
* <li>{@link Controller.findAction|findAction}</li>
* <li>{@link Controller.findDevice|findDevice}</li>
* <li>{@link Controller.getActionNames|getActionNames}</li>
* <li>{@link Controller.getAllActions|getAllActions}</li>
* <li>{@link Controller.getAvailableInputs|getAvailableInputs}</li>
* <li>{@link Controller.getDeviceName|getDeviceName}</li>
* <li>{@link Controller.getDeviceNames|getDeviceNames}</li>
* </ul>
*
* <p>Input, Hardware, and Action Events</p>
* <ul>
* <li>{@link Controller.actionEvent|actionEvent}</li>
* <li>{@link Controller.hardwareChanged|hardwareChanged}</li>
* <li>{@link Controller.inputEvent|inputEvent}</li>
* </ul>
*
* <p>Mouse, Keyboard, and Touch Events</p>
* <ul>
* <li>{@link Controller.keyPressEvent|keyPressEvent}</li>
* <li>{@link Controller.keyReleaseEvent|keyReleaseEvent}</li>
* <li>{@link Controller.mouseDoublePressEvent|mouseDoublePressEvent}</li>
* <li>{@link Controller.mouseMoveEvent|mouseMoveEvent}</li>
* <li>{@link Controller.mousePressEvent|mousePressEvent}</li>
* <li>{@link Controller.mouseReleaseEvent|mouseReleaseEvent}</li>
* <li>{@link Controller.touchBeginEvent|touchBeginEvent}</li>
* <li>{@link Controller.touchEndEvent|touchEndEvent}</li>
* <li>{@link Controller.touchUpdateEvent|touchUpdateEvent}</li>
* <li>{@link Controller.wheelEvent|wheelEvent}</li>
* </ul>
*
* <p>Control Capturing</p>
* <ul>
* <li>{@link Controller.captureMouseEvents|captureMouseEvents}</li>
* <li>{@link Controller.captureTouchEvents|captureTouchEvents}</li>
* <li>{@link Controller.captureWheelEvents|captureWheelEvents}</li>
* <li>{@link Controller.releaseMouseEvents|releaseMouseEvents}</li>
* <li>{@link Controller.releaseTouchEvents|releaseTouchEvents}</li>
* <li>{@link Controller.releaseWheelEvents|releaseWheelEvents}</li>
* </ul>
*
* <p>Action Capturing</p>
* <ul>
* <li>{@link Controller.captureActionEvents|captureActionEvents}</li>
* <li>{@link Controller.captureEntityClickEvents|captureEntityClickEvents}</li>
* <li>{@link Controller.captureJoystick|captureJoystick}</li>
* <li>{@link Controller.captureKeyEvents|captureKeyEvents}</li>
* <li>{@link Controller.releaseActionEvents|releaseActionEvents}</li>
* <li>{@link Controller.releaseEntityClickEvents|releaseEntityClickEvents}</li>
* <li>{@link Controller.releaseJoystick|releaseJoystick}</li>
* <li>{@link Controller.releaseKeyEvents|releaseKeyEvents}</li>
* </ul>
*
* <p>Controller and Action Values</p>
* <ul>
* <li>{@link Controller.getValue|getValue}</li>
* <li>{@link Controller.getAxisValue|getAxisValue}</li>
* <li>{@link Controller.getPoseValue|getgetPoseValue}</li>
* <li>{@link Controller.getButtonValue|getButtonValue} for a particular device</li>
* <li>{@link Controller.getAxisValue(0)|getAxisValue} for a particular device</li>
* <li>{@link Controller.getPoseValue(0)|getPoseValue} for a particular device</li>
* <li>{@link Controller.getActionValue|getActionValue}</li>
* </ul>
*
* <p>Haptics</p>
* <ul>
* <li>{@link Controller.triggerHapticPulse|triggerHapticPulse}</li>
* <li>{@link Controller.triggerHapticPulseOnDevice|triggerHapticPulseOnDevice}</li>
* <li>{@link Controller.triggerShortHapticPulse|triggerShortHapticPulse}</li>
* <li>{@link Controller.triggerShortHapticPulseOnDevice|triggerShortHapticPulseOnDevice}</li>
* </ul>
*
* <p>Display Information</p>
* <ul>
* <li>{@link Controller.getViewportDimensions|getViewportDimensions}</li>
* <li>{@link Controller.getRecommendedHUDRect|getRecommendedHUDRect}</li>
* </ul>
*
* <p>Virtual Game Pad</p>
* <ul>
* <li>{@link Controller.setVPadEnabled|setVPadEnabled}</li>
* <li>{@link Controller.setVPadHidden|setVPadHidden}</li>
* <li>{@link Controller.setVPadExtraBottomMargin|setVPadExtraBottomMargin}</li>
* </ul>
*
* <p>Input Recordings</p>
* <ul>
* <li>{@link Controller.startInputRecording|startInputRecording}</li>
* <li>{@link Controller.stopInputRecording|stopInputRecording}</li>
* <li>{@link Controller.saveInputRecording|saveInputRecording}</li>
* <li>{@link Controller.getInputRecorderSaveDirectory|getInputRecorderSaveDirectory}</li>
* <li>{@link Controller.loadInputRecording|loadInputRecording}</li>
* <li>{@link Controller.startInputPlayback|startInputPlayback}</li>
* <li>{@link Controller.stopInputPlayback|stopInputPlayback}</li>
* </ul>
*
* @namespace Controller
*
* @property {Controller.Actions} Actions - Predefined actions on Interface and the user's avatar. These can be used as end
* points in a {@link RouteObject} mapping. A synonym for <code>Controller.Hardware.Actions</code>.
* <em>Read-only.</em><br />
* Default mappings are provided from the <code>Controller.Hardware.Keyboard</code> and <code>Controller.Standard</code> to
* actions in
* <a href="https://github.com/highfidelity/hifi/blob/master/interface/resources/controllers/keyboardMouse.json">
* keyboardMouse.json</a> and
* <a href="https://github.com/highfidelity/hifi/blob/master/interface/resources/controllers/standard.json">
* standard.json</a>, respectively.
*
* @property {Controller.Hardware} Hardware - Standard and hardware-specific controller and computer outputs, plus predefined
* actions on Interface and the user's avatar. The outputs can be mapped to <code>Actions</code> or functions in a
* {@link RouteObject} mapping. Additionally, hardware-specific controller outputs can be mapped to <code>Standard</code>
* controller outputs. <em>Read-only.</em>
*
* @property {Controller.Standard} Standard - Standard controller outputs that can be mapped to <code>Actions</code> or
* functions in a {@link RouteObject} mapping. <em>Read-only.</em><br />
* Each hardware device has a mapping from its outputs to <code>Controller.Standard</code> items, specified in a JSON file.
* For example, <a href="https://github.com/highfidelity/hifi/blob/master/interface/resources/controllers/leapmotion.json">
* leapmotion.json</a> and
* <a href="https://github.com/highfidelity/hifi/blob/master/interface/resources/controllers/vive.json">vive.json</a>.
*/
/// handles scripting of input controller commands from JS
class ControllerScriptingInterface : public controller::ScriptingInterface {
Q_OBJECT
public:
virtual ~ControllerScriptingInterface() {}
@ -53,40 +196,214 @@ public:
public slots:
/**jsdoc
* Disable default Interface actions for a particular key event.
* @function Controller.captureKeyEvents
* @param {KeyEvent} event - Details of the key event to be captured. The <code>key</code> property must be specified. The
* <code>text</code> property is ignored. The other properties default to <code>false</code>.
* @example <caption>Disable left and right strafing.</caption>
* var STRAFE_LEFT = { "key": 16777234, isShifted: true };
* var STRAFE_RIGHT = { "key": 16777236, isShifted: true };
*
* Controller.captureKeyEvents(STRAFE_LEFT);
* Controller.captureKeyEvents(STRAFE_RIGHT);
*
* Script.scriptEnding.connect(function () {
* Controller.releaseKeyEvents(STRAFE_LEFT);
* Controller.releaseKeyEvents(STRAFE_RIGHT);
* });
*/
virtual void captureKeyEvents(const KeyEvent& event);
/**jsdoc
* Re-enable default Interface actions for a particular key event that has been disabled using
* {@link Controller.captureKeyEvents|captureKeyEvents}.
* @function Controller.releaseKeyEvents
* @param {KeyEvent} event - Details of the key event to release from capture. The <code>key</code> property must be
* specified. The <code>text</code> property is ignored. The other properties default to <code>false</code>.
*/
virtual void releaseKeyEvents(const KeyEvent& event);
/**jsdoc
* Disable default Interface actions for a joystick.
* @function Controller.captureJoystick
* @param {number} joystickID - The integer ID of the joystick.
* @deprecated This function no longer has any effect.
*/
virtual void captureJoystick(int joystickIndex);
/**jsdoc
* Re-enable default Interface actions for a joystick that has been disabled using
* {@link Controller.captureJoystick|captureJoystick}.
* @function Controller.releaseJoystick
* @param {number} joystickID - The integer ID of the joystick.
* @deprecated This function no longer has any effect.
*/
virtual void releaseJoystick(int joystickIndex);
/**jsdoc
* Disable {@link Entities.mousePressOnEntity} and {@link Entities.mouseDoublePressOnEntity} events on entities.
* @function Controller.captureEntityClickEvents
* @example <caption>Disable entity click events for a short period.</caption>
* Entities.mousePressOnEntity.connect(function (entityID, event) {
* print("Clicked on entity: " + entityID);
* });
*
* Script.setTimeout(function () {
* Controller.captureEntityClickEvents();
* }, 5000);
*
* Script.setTimeout(function () {
* Controller.releaseEntityClickEvents();
* }, 10000);
*/
virtual void captureEntityClickEvents();
/**jsdoc
* Re-enable {@link Entities.mousePressOnEntity} and {@link Entities.mouseDoublePressOnEntity} events on entities that were
* disabled using {@link Controller.captureEntityClickEvents|captureEntityClickEvents}.
* @function Controller.releaseEntityClickEvents
*/
virtual void releaseEntityClickEvents();
/**jsdoc
* Get the dimensions of the Interface window's interior if in desktop mode or the HUD surface if in HMD mode.
* @function Controller.getViewportDimensions
* @returns {Vec2} The dimensions of the Interface window interior if in desktop mode or HUD surface if in HMD mode.
*/
virtual glm::vec2 getViewportDimensions() const;
/**jsdoc
* Get the recommended area to position UI on the HUD surface if in HMD mode or Interface's window interior if in desktop
* mode.
* @function Controller.getRecommendedHUDRect
* @returns {Rect} The recommended area in which to position UI.
*/
virtual QVariant getRecommendedHUDRect() const;
/**jsdoc
* Enables or disables the virtual game pad that is displayed on certain devices (e.g., Android).
* @function Controller.setVPadEnabled
* @param {boolean} enable - If <code>true</code> then the virtual game pad doesn't work, otherwise it does work provided
* that it is not hidden by {@link Controller.setVPadHidden|setVPadHidden}.
*
*/
virtual void setVPadEnabled(bool enable);
/**jsdoc
* Shows or hides the virtual game pad that is displayed on certain devices (e.g., Android).
* @function Controller.setVPadHidden
* @param {boolean} hidden - If <code>true</code> then the virtual game pad is hidden, otherwise it is shown.
*/
virtual void setVPadHidden(bool hidden); // Call it when a window should hide it
/**jsdoc
* Sets the amount of extra margin between the virtual game pad that is displayed on certain devices (e.g., Android) and
* the bottom of the display.
* @function Controller.setVPadExtraBottomMargin
* @param {number} margin - Integer number of pixels in the extra margin.
*/
virtual void setVPadExtraBottomMargin(int margin);
signals:
/**jsdoc
* Triggered when a keyboard key is pressed.
* @function Controller.keyPressEvent
* @param {KeyEvent} event - Details of the key press.
* @returns {Signal}
* @example <caption>Report the KeyEvent details for each key press.</caption>
* Controller.keyPressEvent.connect(function (event) {
* print(JSON.stringify(event));
* });
*/
void keyPressEvent(const KeyEvent& event);
/**jsdoc
* Triggered when a keyboard key is released from being pressed.
* @function Controller.keyReleaseEvent
* @param {KeyEvent} event - Details of the key release.
* @returns {Signal}
*/
void keyReleaseEvent(const KeyEvent& event);
/**jsdoc
* Triggered when the mouse moves.
* @function Controller.mouseMoveEvent
* @param {MouseEvent} event - Details of the mouse movement.
* @returns {Signal}
* @example <caption>Report the MouseEvent details for each mouse move.</caption>
* Controller.mouseMoveEvent.connect(function (event) {
* print(JSON.stringify(event));
* });
*/
void mouseMoveEvent(const MouseEvent& event);
/**jsdoc
* Triggered when a mouse button is pressed.
* @function Controller.mousePressEvent
* @param {MouseEvent} event - Details of the button press.
* @returns {Signal}
*/
void mousePressEvent(const MouseEvent& event);
/**jsdoc
* Triggered when a mouse button is double-pressed.
* @function Controller.mouseDoublePressEvent
* @param {MouseEvent} event - Details of the button double-press.
* @returns {Signal}
*/
void mouseDoublePressEvent(const MouseEvent& event);
/**jsdoc
* Triggered when a mouse button is released from being pressed.
* @function Controller.mouseReleaseEvent
* @param {MouseEvent} event - Details of the button release.
* @returns {Signal}
*/
void mouseReleaseEvent(const MouseEvent& event);
/**jsdoc
* Triggered when a touch event starts in the Interface window on a touch-enabled display or device.
* @function Controller.touchBeginEvent
* @param {TouchEvent} event - Details of the touch begin.
* @returns {Signal}
* @example <caption>Report the TouchEvent details when a touch event starts.</caption>
* Controller.touchBeginEvent.connect(function (event) {
* print(JSON.stringify(event));
* });
*/
void touchBeginEvent(const TouchEvent& event);
/**jsdoc
* Triggered when a touch event ends in the Interface window on a touch-enabled display or device.
* @function Controller.touchEndEvent
* @param {TouchEvent} event - Details of the touch end.
* @returns {Signal}
*/
void touchEndEvent(const TouchEvent& event);
/**jsdoc
* Triggered when a touch event update occurs in the Interface window on a touch-enabled display or device.
* @function Controller.touchUpdateEvent
* @param {TouchEvent} event - Details of the touch update.
* @returns {Signal}
*/
void touchUpdateEvent(const TouchEvent& event);
/**jsdoc
* Triggered when the mouse wheel is rotated.
* @function Controller.wheelEvent
* @param {WheelEvent} event - Details of the wheel movement.
* @returns {Signal}
* @example <caption>Report the WheelEvent details for each wheel rotation.</caption>
* Controller.wheelEvent.connect(function (event) {
* print(JSON.stringify(event));
* });
*/
void wheelEvent(const WheelEvent& event);
private:
QString sanatizeName(const QString& name); /// makes a name clean for inclusing in JavaScript
QMultiMap<int,KeyEvent> _capturedKeys;
QSet<int> _capturedJoysticks;
bool _captureEntityClicks;

View file

@ -24,6 +24,36 @@ class QScriptEngine;
#include <QReadWriteLock>
/**jsdoc
* The HMD API provides access to the HMD used in VR display mode.
*
* @namespace HMD
* @property {Vec3} position - The position of the HMD if currently in VR display mode, otherwise
* {@link Vec3(0)|Vec3.ZERO}. <em>Read-only.</em>
* @property {Quat} orientation - The orientation of the HMD if currently in VR display mode, otherwise
* {@link Quat(0)|Quat.IDENTITY}. <em>Read-only.</em>
* @property {boolean} active - <code>true</code> if the display mode is HMD, otherwise <code>false</code>. <em>Read-only.</em>
* @property {boolean} mounted - <code>true</code> if currently in VR display mode and the HMD is being worn, otherwise
* <code>false</code>. <em>Read-only.</em>
*
* @property {number} playerHeight - The real-world height of the user. <em>Read-only.</em> <em>Currently always returns a
* value of <code>1.755</code>.</em>
* @property {number} eyeHeight - The real-world height of the user's eyes. <em>Read-only.</em> <em>Currently always returns a
* value of <code>1.655</code>.</em>
* @property {number} ipd - The inter-pupillary distance (distance between eyes) of the user, used for rendering. Defaults to
* the human average of <code>0.064</code> unless set by the HMD. <em>Read-only.</em>
* @property {number} ipdScale=1.0 - A scale factor applied to the <code>ipd</code> property value.
*
* @property {boolean} showTablet - <code>true</code> if the tablet is being displayed, <code>false</code> otherwise.
* <em>Read-only.</em>
* @property {boolean} tabletContextualMode - <code>true</code> if the tablet has been opened in contextual mode, otherwise
* <code>false</code>. In contextual mode, the tablet has been opened at a specific world position and orientation rather
* than at a position and orientation relative to the user. <em>Read-only.</em>
* @property {Uuid} tabletID - The UUID of the tablet body model overlay.
* @property {Uuid} tabletScreenID - The UUID of the tablet's screen overlay.
* @property {Uuid} homeButtonID - The UUID of the tablet's "home" button overlay.
* @property {Uuid} homeButtonHighlightID - The UUID of the tablet's "home" button highlight overlay.
*/
class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency {
Q_OBJECT
Q_PROPERTY(glm::vec3 position READ getPosition)
@ -37,26 +67,217 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen
Q_PROPERTY(QUuid tabletScreenID READ getCurrentTabletScreenID WRITE setCurrentTabletScreenID)
public:
/**jsdoc
* Calculate the intersection of a ray with the HUD overlay.
* @function HMD.calculateRayUICollisionPoint
* @param {Vec3} position - The origin of the ray.
* @param {Vec3} direction - The direction of the ray.
* @returns {Vec3} The point of intersection with the HUD overlay if it intersects, otherwise {@link Vec3(0)|Vec3.ZERO}.
* @example <caption>Draw a square on the HUD overlay in the direction you're looking.</caption>
* var hudIntersection = HMD.calculateRayUICollisionPoint(MyAvatar.getHeadPosition(),
* Quat.getForward(MyAvatar.headOrientation));
* var hudPoint = HMD.overlayFromWorldPoint(hudIntersection);
*
* var DIMENSIONS = { x: 50, y: 50 };
* var square = Overlays.addOverlay("rectangle", {
* x: hudPoint.x - DIMENSIONS.x / 2,
* y: hudPoint.y - DIMENSIONS.y / 2,
* width: DIMENSIONS.x,
* height: DIMENSIONS.y,
* color: { red: 255, green: 0, blue: 0 }
* });
*
* Script.scriptEnding.connect(function () {
* Overlays.deleteOverlay(square);
* });
*/
Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const;
/**jsdoc
* Get the 2D HUD overlay coordinates of a 3D point on the HUD overlay.
* 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay.
* @function HMD.overlayFromWorldPoint
* @param {Vec3} position - The point on the HUD overlay in world coordinates.
* @returns {Vec2} The point on the HUD overlay in HUD coordinates.
* @example <caption>Draw a square on the HUD overlay in the direction you're looking.</caption>
* var hudIntersection = HMD.calculateRayUICollisionPoint(MyAvatar.getHeadPosition(),
* Quat.getForward(MyAvatar.headOrientation));
* var hudPoint = HMD.overlayFromWorldPoint(hudIntersection);
*
* var DIMENSIONS = { x: 50, y: 50 };
* var square = Overlays.addOverlay("rectangle", {
* x: hudPoint.x - DIMENSIONS.x / 2,
* y: hudPoint.y - DIMENSIONS.y / 2,
* width: DIMENSIONS.x,
* height: DIMENSIONS.y,
* color: { red: 255, green: 0, blue: 0 }
* });
*
* Script.scriptEnding.connect(function () {
* Overlays.deleteOverlay(square);
* });
*/
Q_INVOKABLE glm::vec2 overlayFromWorldPoint(const glm::vec3& position) const;
/**jsdoc
* Get the 3D world coordinates of a 2D point on the HUD overlay.
* 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay.
* @function HMD.worldPointFromOverlay
* @param {Vec2} coordinates - The point on the HUD overlay in HUD coordinates.
* @returns {Vec3} The point on the HUD overlay in world coordinates.
*/
Q_INVOKABLE glm::vec3 worldPointFromOverlay(const glm::vec2& overlay) const;
/**jsdoc
* Get the 2D point on the HUD overlay represented by given spherical coordinates.
* 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay.
* Spherical coordinates are polar coordinates in radians with <code>{ x: 0, y: 0 }</code> being the center of the HUD
* overlay.
* @function HMD.sphericalToOverlay
* @param {Vec2} sphericalPos - The point on the HUD overlay in spherical coordinates.
* @returns {Vec2} The point on the HUD overlay in HUD coordinates.
*/
Q_INVOKABLE glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const;
/**jsdoc
* Get the spherical coordinates of a 2D point on the HUD overlay.
* 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay.
* Spherical coordinates are polar coordinates in radians with <code>{ x: 0, y: 0 }</code> being the center of the HUD
* overlay.
* @function HMD.overlayToSpherical
* @param {Vec2} overlayPos - The point on the HUD overlay in HUD coordinates.
* @returns {Vec2} The point on the HUD overlay in spherical coordinates.
*/
Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const;
/**jsdoc
* Recenter the HMD HUD to the current HMD position and orientation.
* @function HMD.centerUI
*/
Q_INVOKABLE void centerUI();
/**jsdoc
* Get the name of the HMD audio input device.
* @function HMD.preferredAudioInput
* @returns {string} The name of the HMD audio input device if in HMD mode, otherwise an empty string.
*/
Q_INVOKABLE QString preferredAudioInput() const;
/**jsdoc
* Get the name of the HMD audio output device.
* @function HMD.preferredAudioOutput
* @returns {string} The name of the HMD audio output device if in HMD mode, otherwise an empty string.
*/
Q_INVOKABLE QString preferredAudioOutput() const;
/**jsdoc
* Check whether there is an HMD available.
* @function HMD.isHMDAvailable
* @param {string} [name=""] - The name of the HMD to check for, e.g., <code>"Oculus Rift"</code>. The name is the same as
* may be displayed in Interface's "Display" menu. If no name is specified then any HMD matches.
* @returns {boolean} <code>true</code> if an HMD of the specified <code>name</code> is available, otherwise
* <code>false</code>.
* @example <caption>Report on HMD availability.</caption>
* print("Is any HMD available: " + HMD.isHMDAvailable());
* print("Is an Oculus Rift HMD available: " + HMD.isHMDAvailable("Oculus Rift"));
* print("Is a Vive HMD available: " + HMD.isHMDAvailable("OpenVR (Vive)"));
*/
Q_INVOKABLE bool isHMDAvailable(const QString& name = "");
/**jsdoc
* Check whether there is an HMD head controller available.
* @function HMD.isHeadControllerAvailable
* @param {string} [name=""] - The name of the HMD head controller to check for, e.g., <code>"Oculus"</code>. If no name is
* specified then any HMD head controller matches.
* @returns {boolean} <code>true</code> if an HMD head controller of the specified <code>name</code> is available,
* otherwise <code>false</code>.
* @example <caption>Report HMD head controller availability.</caption>
* print("Is any HMD head controller available: " + HMD.isHeadControllerAvailable());
* print("Is an Oculus head controller available: " + HMD.isHeadControllerAvailable("Oculus"));
* print("Is a Vive head controller available: " + HMD.isHeadControllerAvailable("OpenVR"));
*/
Q_INVOKABLE bool isHeadControllerAvailable(const QString& name = "");
/**jsdoc
* Check whether there are HMD hand controllers available.
* @function HMD.isHandControllerAvailable
* @param {string} [name=""] - The name of the HMD hand controller to check for, e.g., <code>"Oculus"</code>. If no name is
* specified then any HMD hand controller matches.
* @returns {boolean} <code>true</code> if an HMD hand controller of the specified <code>name</code> is available,
* otherwise <code>false</code>.
* @example <caption>Report HMD hand controller availability.</caption>
* print("Are any HMD hand controllers available: " + HMD.isHandControllerAvailable());
* print("Are Oculus hand controllers available: " + HMD.isHandControllerAvailable("Oculus"));
* print("Are Vive hand controllers available: " + HMD.isHandControllerAvailable("OpenVR"));
*/
Q_INVOKABLE bool isHandControllerAvailable(const QString& name = "");
/**jsdoc
* Check whether there are specific HMD controllers available.
* @function HMD.isSubdeviceContainingNameAvailable
* @param {string} name - The name of the HMD controller to check for, e.g., <code>"OculusTouch"</code>.
* @returns {boolean} <code>true</code> if an HMD controller with a name containing the specified <code>name</code> is
* available, otherwise <code>false</code>.
* @example <caption>Report if particular Oculus controllers are available.</caption>
* print("Is an Oculus Touch controller available: " + HMD.isSubdeviceContainingNameAvailable("Touch"));
* print("Is an Oculus Remote controller available: " + HMD.isSubdeviceContainingNameAvailable("Remote"));
*/
Q_INVOKABLE bool isSubdeviceContainingNameAvailable(const QString& name);
/**jsdoc
* Signal that models of the HMD hand controllers being used should be displayed. The models are displayed at their actual,
* real-world locations.
* @function HMD.requestShowHandControllers
* @example <caption>Show your hand controllers for 10 seconds.</caption>
* HMD.requestShowHandControllers();
* Script.setTimeout(function () {
* HMD.requestHideHandControllers();
* }, 10000);
*/
Q_INVOKABLE void requestShowHandControllers();
/**jsdoc
* Signal that it is no longer necessary to display models of the HMD hand controllers being used. If no other scripts
* want the models displayed then they are no longer displayed.
* @function HMD.requestHideHandControllers
*/
Q_INVOKABLE void requestHideHandControllers();
/**jsdoc
* Check whether any script wants models of the HMD hand controllers displayed. Requests are made and canceled using
* {@link HMD.requestShowHandControllers|requestShowHandControllers} and
* {@link HMD.requestHideHandControllers|requestHideHandControllers}.
* @function HMD.shouldShowHandControllers
* @returns {boolean} <code>true</code> if any script is requesting that HMD hand controller models be displayed.
*/
Q_INVOKABLE bool shouldShowHandControllers() const;
/**jsdoc
* Causes the borders in HUD windows to be enlarged when the laser intersects them in HMD mode. By default, borders are not
* enlarged.
* @function HMD.activateHMDHandMouse
*/
Q_INVOKABLE void activateHMDHandMouse();
/**jsdoc
* Causes the border in HUD windows to no longer be enlarged when the laser intersects them in HMD mode. By default,
* borders are not enlarged.
* @function HMD.deactivateHMDHandMouse
*/
Q_INVOKABLE void deactivateHMDHandMouse();
/**jsdoc
* Suppress the activation of the HMD-provided keyboard, if any. Successful calls should be balanced with a call to
* {@link HMD.unspressKeyboard|unspressKeyboard} within a reasonable amount of time.
* @function HMD.suppressKeyboard
* @returns {boolean} <code>true</code> if the current HMD provides a keyboard and it was successfully suppressed (e.g., it
* isn't being displayed), otherwise <code>false</code>.
*/
/// Suppress the activation of any on-screen keyboard so that a script operation will
/// not be interrupted by a keyboard popup
/// Returns false if there is already an active keyboard displayed.
@ -65,21 +286,68 @@ public:
/// call to unsuppressKeyboard() within a reasonable amount of time
Q_INVOKABLE bool suppressKeyboard();
/**jsdoc
* Unsuppress the activation of the HMD-provided keyboard, if any.
* @function HMD.unsuppressKeyboard
*/
/// Enable the keyboard following a suppressKeyboard call
Q_INVOKABLE void unsuppressKeyboard();
/**jsdoc
* Check whether the HMD-provided keyboard, if any, is visible.
* @function HMD.isKeyboardVisible
* @returns {boolean} <code>true</code> if the current HMD provides a keyboard and it is visible, otherwise
* <code>false</code>.
*/
/// Query the display plugin to determine the current VR keyboard visibility
Q_INVOKABLE bool isKeyboardVisible();
// rotate the overlay UI sphere so that it is centered about the the current HMD position and orientation
Q_INVOKABLE void centerUI();
/**jsdoc
* Closes the tablet if it is open.
* @function HMD.closeTablet
*/
Q_INVOKABLE void closeTablet();
/**jsdoc
* Opens the tablet if the tablet is used in the current display mode and it isn't already showing, and sets the tablet to
* contextual mode if requested. In contextual mode, the page displayed on the tablet is wholly controlled by script (i.e.,
* the user cannot navigate to another).
* @function HMD.openTablet
* @param {boolean} [contextualMode=false] - If <code>true</code> then the tablet is opened at a specific position and
* orientation already set by the script, otherwise it opens at a position and orientation relative to the user. For
* contextual mode, set the world or local position and orientation of the <code>HMD.tabletID</code> overlay.
*/
Q_INVOKABLE void openTablet(bool contextualMode = false);
signals:
/**jsdoc
* Triggered when a request to show or hide models of the HMD hand controllers is made using
* {@link HMD.requestShowHandControllers|requestShowHandControllers} or
* {@link HMD.requestHideHandControllers|requestHideHandControllers}.
* @function HMD.shouldShowHandControllersChanged
* @returns {Signal}
* @example <caption>Report when showing of hand controllers changes.</caption>
* function onShouldShowHandControllersChanged() {
* print("Should show hand controllers: " + HMD.shouldShowHandControllers());
* }
* HMD.shouldShowHandControllersChanged.connect(onShouldShowHandControllersChanged);
*
* HMD.requestShowHandControllers();
* Script.setTimeout(function () {
* HMD.requestHideHandControllers();
* }, 10000);
*/
bool shouldShowHandControllersChanged();
/**jsdoc
* Triggered when the <code>HMD.mounted</code> property value changes.
* @function HMD.mountedChanged
* @returns {Signal}
* @example <caption>Report when there's a change in the HMD being worn.</caption>
* HMD.mountedChanged.connect(function () {
* print("Mounted changed. HMD is mounted: " + HMD.mounted);
* });
*/
void mountedChanged();
public:

View file

@ -531,8 +531,8 @@ private slots:
signals:
/**jsdoc
* Triggered when you change the domain you're visiting. <strong>Warning:</strong> Is not emitted if you go to domain that
* isn't running.
* Triggered when you change the domain you're visiting. <strong>Warning:</strong> Is not emitted if you go to a domain
* that isn't running.
* @function Window.domainChanged
* @param {string} domainURL - The domain's URL.
* @returns {Signal}

View file

@ -60,8 +60,8 @@ Stats::Stats(QQuickItem* parent) : QQuickItem(parent) {
bool Stats::includeTimingRecord(const QString& name) {
if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails)) {
if (name.startsWith("/idle/update/")) {
if (name.startsWith("/idle/update/physics/")) {
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsSimulationTiming);
if (name.startsWith("/idle/update/simulation/")) {
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandSimulationTiming);
} else if (name.startsWith("/idle/update/myAvatar/")) {
if (name.startsWith("/idle/update/myAvatar/simulate/")) {
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandMyAvatarSimulateTiming);
@ -75,8 +75,8 @@ bool Stats::includeTimingRecord(const QString& name) {
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming);
} else if (name.startsWith("/paintGL/")) {
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming);
} else if (name.startsWith("step/")) {
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsSimulationTiming);
} else if (name.startsWith("physics/")) {
return Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsTiming);
}
return true;
}
@ -479,7 +479,14 @@ void Stats::updateStats(bool force) {
float dt = (float)itr.value().getMovingAverage() / (float)USECS_PER_MSEC;
_gameUpdateStats = QString("/idle/update = %1 ms").arg(dt);
QVector<QString> categories = { "devices", "physics", "otherAvatars", "MyAvatar", "misc" };
QVector<QString> categories = {
"devices",
"MyAvatar",
"otherAvatars",
"pickManager",
"pointerManager",
"simulation"
};
for (int32_t j = 0; j < categories.size(); ++j) {
QString recordKey = "/idle/update/" + categories[j];
itr = allRecords.find(recordKey);

View file

@ -757,7 +757,7 @@ void AudioClient::Gate::flush() {
void AudioClient::handleNoisyMutePacket(QSharedPointer<ReceivedMessage> message) {
if (!_muted) {
toggleMute();
setMuted(true);
// have the audio scripting interface emit a signal to say we were muted by the mixer
emit mutedByMixer();
@ -1384,15 +1384,21 @@ void AudioClient::sendMuteEnvironmentPacket() {
}
}
void AudioClient::toggleMute() {
_muted = !_muted;
emit muteToggled();
void AudioClient::setMuted(bool muted, bool emitSignal) {
if (_muted != muted) {
_muted = muted;
if (emitSignal) {
emit muteToggled(_muted);
}
}
}
void AudioClient::setNoiseReduction(bool enable) {
void AudioClient::setNoiseReduction(bool enable, bool emitSignal) {
if (_isNoiseGateEnabled != enable) {
_isNoiseGateEnabled = enable;
emit noiseReductionChanged();
if (emitSignal) {
emit noiseReductionChanged(_isNoiseGateEnabled);
}
}
}
@ -2018,9 +2024,11 @@ void AudioClient::startThread() {
moveToNewNamedThread(this, "Audio Thread", [this] { start(); });
}
void AudioClient::setInputVolume(float volume) {
void AudioClient::setInputVolume(float volume, bool emitSignal) {
if (_audioInput && volume != (float)_audioInput->volume()) {
_audioInput->setVolume(volume);
emit inputVolumeChanged(_audioInput->volume());
if (emitSignal) {
emit inputVolumeChanged(_audioInput->volume());
}
}
}

View file

@ -189,13 +189,13 @@ public slots:
void reset();
void audioMixerKilled();
void toggleMute();
void setMuted(bool muted, bool emitSignal = true);
bool isMuted() { return _muted; }
virtual bool setIsStereoInput(bool stereo) override;
virtual bool isStereoInput() override { return _isStereoInput; }
void setNoiseReduction(bool isNoiseGateEnabled);
void setNoiseReduction(bool isNoiseGateEnabled, bool emitSignal = true);
bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; }
bool getLocalEcho() { return _shouldEchoLocally; }
@ -218,7 +218,7 @@ public slots:
bool switchAudioDevice(QAudio::Mode mode, const QString& deviceName);
float getInputVolume() const { return (_audioInput) ? (float)_audioInput->volume() : 0.0f; }
void setInputVolume(float volume);
void setInputVolume(float volume, bool emitSignal = true);
void setReverb(bool reverb);
void setReverbOptions(const AudioEffectOptions* options);
@ -229,8 +229,8 @@ public slots:
signals:
void inputVolumeChanged(float volume);
void muteToggled();
void noiseReductionChanged();
void muteToggled(bool muted);
void noiseReductionChanged(bool noiseReductionEnabled);
void mutedByMixer();
void inputReceived(const QByteArray& inputSamples);
void inputLoudnessChanged(float loudness);

View file

@ -39,9 +39,6 @@ AudioRingBufferTemplate<T>::AudioRingBufferTemplate(int numFrameSamples, int num
_nextOutput = _buffer;
_endOfLastWrite = _buffer;
}
static QString repeatedOverflowMessage = LogHandler::getInstance().addRepeatedMessageRegex(RING_BUFFER_OVERFLOW_DEBUG);
static QString repeatedDroppedMessage = LogHandler::getInstance().addRepeatedMessageRegex(DROPPED_SILENT_DEBUG);
}
template <class T>
@ -154,6 +151,11 @@ int AudioRingBufferTemplate<T>::appendData(char *data, int maxSize) {
return numReadSamples * SampleSize;
}
namespace {
int repeatedOverflowMessageID = 0;
std::once_flag messageIDFlag;
}
template <class T>
int AudioRingBufferTemplate<T>::writeData(const char* data, int maxSize) {
// only copy up to the number of samples we have capacity for
@ -167,7 +169,9 @@ int AudioRingBufferTemplate<T>::writeData(const char* data, int maxSize) {
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete);
_overflowCount++;
qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG);
std::call_once(messageIDFlag, [](int* id) { *id = LogHandler::getInstance().newRepeatedMessageID(); },
&repeatedOverflowMessageID);
HIFI_FCDEBUG_ID(audio(), repeatedOverflowMessageID, RING_BUFFER_OVERFLOW_DEBUG);
}
if (_endOfLastWrite + numWriteSamples > _buffer + _bufferLength) {
@ -224,7 +228,7 @@ int AudioRingBufferTemplate<T>::addSilentSamples(int silentSamples) {
if (numWriteSamples > samplesRoomFor) {
numWriteSamples = samplesRoomFor;
qCDebug(audio) << qPrintable(DROPPED_SILENT_DEBUG);
HIFI_FCDEBUG(audio(), DROPPED_SILENT_DEBUG);
}
if (_endOfLastWrite + numWriteSamples > _buffer + _bufferLength) {
@ -275,7 +279,10 @@ int AudioRingBufferTemplate<T>::writeSamples(ConstIterator source, int maxSample
int samplesToDelete = samplesToCopy - samplesRoomFor;
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete);
_overflowCount++;
qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG);
std::call_once(messageIDFlag, [](int* id) { *id = LogHandler::getInstance().newRepeatedMessageID(); },
&repeatedOverflowMessageID);
HIFI_FCDEBUG_ID(audio(), repeatedOverflowMessageID, RING_BUFFER_OVERFLOW_DEBUG);
}
Sample* bufferLast = _buffer + _bufferLength - 1;
@ -297,7 +304,10 @@ int AudioRingBufferTemplate<T>::writeSamplesWithFade(ConstIterator source, int m
int samplesToDelete = samplesToCopy - samplesRoomFor;
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete);
_overflowCount++;
qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG);
std::call_once(messageIDFlag, [](int* id) { *id = LogHandler::getInstance().newRepeatedMessageID(); },
&repeatedOverflowMessageID);
HIFI_FCDEBUG_ID(audio(), repeatedOverflowMessageID, RING_BUFFER_OVERFLOW_DEBUG);
}
Sample* bufferLast = _buffer + _bufferLength - 1;

View file

@ -120,8 +120,8 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
// parse sequence number and track it
quint16 sequence;
message.readPrimitive(&sequence);
SequenceNumberStats::ArrivalInfo arrivalInfo = _incomingSequenceNumberStats.sequenceNumberReceived(sequence,
message.getSourceID());
SequenceNumberStats::ArrivalInfo arrivalInfo =
_incomingSequenceNumberStats.sequenceNumberReceived(sequence, message.getSourceID());
QString codecInPacket = message.readString();
packetReceivedUpdateTimingStats();
@ -186,7 +186,7 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
_mismatchedAudioCodecCount = 0;
// inform others of the mismatch
auto sendingNode = DependencyManager::get<NodeList>()->nodeWithUUID(message.getSourceID());
auto sendingNode = DependencyManager::get<NodeList>()->nodeWithLocalID(message.getSourceID());
if (sendingNode) {
emit mismatchedAudioCodec(sendingNode, _selectedCodecName, codecInPacket);
qDebug(audio) << "Codec mismatch threshold exceeded, SelectedAudioFormat(" << _selectedCodecName << " ) sent";

View file

@ -16,6 +16,7 @@
#include <glm/detail/func_common.hpp>
#include <QtCore/QDataStream>
#include <QtCore/QLoggingCategory>
#include <LogHandler.h>
#include <Node.h>
@ -80,10 +81,7 @@ int PositionalAudioStream::parsePositionalData(const QByteArray& positionalByteA
// if the client sends us a bad position, flag it so that we don't consider this stream for mixing
if (glm::isnan(_position.x) || glm::isnan(_position.y) || glm::isnan(_position.z)) {
static const QString INVALID_POSITION_REGEX = "PositionalAudioStream unpacked invalid position for node";
static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(INVALID_POSITION_REGEX);
qDebug() << "PositionalAudioStream unpacked invalid position for node" << uuidStringWithoutCurlyBraces(getNodeID());
HIFI_FDEBUG("PositionalAudioStream unpacked invalid position for node" << uuidStringWithoutCurlyBraces(getNodeID()) );
_hasValidPosition = false;
} else {

View file

@ -58,6 +58,13 @@ void SkeletonModel::initJointStates() {
glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset);
_rig.initJointStates(geometry, modelOffset);
{
// initialize _jointData with proper values for default joints
QVector<JointData> defaultJointData;
_rig.copyJointsIntoJointData(defaultJointData);
_owningAvatar->setRawJointData(defaultJointData);
}
// Determine the default eye position for avatar scale = 1.0
int headJointIndex = geometry.headJointIndex;
if (0 > headJointIndex || headJointIndex >= _rig.getJointStateCount()) {

View file

@ -560,7 +560,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
const JointData& last = lastSentJointData[i];
if (!data.rotationIsDefaultPose) {
if (sendAll || last.rotationIsDefaultPose || last.rotation != data.rotation) {
bool mustSend = sendAll || last.rotationIsDefaultPose;
if (mustSend || last.rotation != data.rotation) {
bool largeEnoughRotation = true;
if (cullSmallChanges) {
@ -569,7 +570,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
largeEnoughRotation = fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT;
}
if (sendAll || !cullSmallChanges || largeEnoughRotation) {
if (mustSend || !cullSmallChanges || largeEnoughRotation) {
validity |= (1 << validityBit);
#ifdef WANT_DEBUG
rotationSentCount++;
@ -609,10 +610,12 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
float maxTranslationDimension = 0.0;
for (int i = 0; i < _jointData.size(); i++) {
const JointData& data = _jointData[i];
const JointData& last = lastSentJointData[i];
if (!data.translationIsDefaultPose) {
if (sendAll || lastSentJointData[i].translation != data.translation) {
if (sendAll || !cullSmallChanges || glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) {
bool mustSend = sendAll || last.translationIsDefaultPose;
if (mustSend || last.translation != data.translation) {
if (mustSend || !cullSmallChanges || glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) {
validity |= (1 << validityBit);
#ifdef WANT_DEBUG
translationSentCount++;
@ -670,6 +673,19 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
}
if (sentJointDataOut) {
// Mark default poses in lastSentJointData, so when they become non-default we send them.
for (int i = 0; i < _jointData.size(); i++) {
const JointData& data = _jointData[i];
JointData& local = localSentJointDataOut[i];
if (data.rotationIsDefaultPose) {
local.rotationIsDefaultPose = true;
}
if (data.translationIsDefaultPose) {
local.translationIsDefaultPose = true;
}
}
// Push new sent joint data to sentJointDataOut
sentJointDataOut->swap(localSentJointDataOut);
}
@ -1817,13 +1833,13 @@ void AvatarData::setJointMappingsFromNetworkReply() {
networkReply->deleteLater();
}
void AvatarData::sendAvatarDataPacket() {
void AvatarData::sendAvatarDataPacket(bool sendAll) {
auto nodeList = DependencyManager::get<NodeList>();
// about 2% of the time, we send a full update (meaning, we transmit all the joint data), even if nothing has changed.
// this is to guard against a joint moving once, the packet getting lost, and the joint never moving again.
bool cullSmallData = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO);
bool cullSmallData = !sendAll && (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO);
auto dataDetail = cullSmallData ? SendAllData : CullSmallData;
QByteArray avatarByteArray = toByteArrayStateful(dataDetail);

View file

@ -256,6 +256,11 @@ namespace AvatarDataPacket {
SixByteQuat rotation[numValidRotations]; // encodeded and compressed by packOrientationQuatToSixBytes()
uint8_t translationValidityBits[ceil(numJoints / 8)]; // one bit per joint, if true then a compressed translation follows.
SixByteTrans translation[numValidTranslations]; // encodeded and compressed by packFloatVec3ToSignedTwoByteFixed()
SixByteQuat leftHandControllerRotation;
SixByteTrans leftHandControllerTranslation;
SixByteQuat rightHandControllerRotation;
SixByteTrans rightHandControllerTranslation;
};
*/
size_t maxJointDataSize(size_t numJoints);
@ -709,11 +714,11 @@ signals:
void sessionUUIDChanged();
public slots:
void sendAvatarDataPacket();
void sendAvatarDataPacket(bool sendAll = false);
void sendIdentityPacket();
void setJointMappingsFromNetworkReply();
void setSessionUUID(const QUuid& sessionUUID) {
virtual void setSessionUUID(const QUuid& sessionUUID) {
if (sessionUUID != getID()) {
if (sessionUUID == QUuid()) {
setID(AVATAR_SELF_ID);

View file

@ -33,6 +33,282 @@ namespace controller {
return std::make_shared<ActionEndpoint>(input);
}
/**jsdoc
* <p>The <code>Controller.Actions</code> object has properties representing predefined actions on the user's avatar and
* Interface. The property values are integer IDs, uniquely identifying each action. <em>Read-only.</em> These can be used
* as end points in the routes of a {@link MappingObject}. The data routed to each action is either a number or a
* {@link Pose}.</p>
*
* <table>
* <thead>
* <tr><th>Property</th><th>Type</th><th>Data</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td colSpan=4><strong>Avatar Movement</strong></td>
* <tr><td><code>TranslateX</code></td><td>number</td><td>number</td><td>Move the user's avatar in the direction of its
* x-axis, if the camera isn't in independent or mirror modes.</td></tr>
* <tr><td><code>TranslateY</code></td><td>number</td><td>number</td><td>Move the user's avatar in the direction of its
* y-axis, if the camera isn't in independent or mirror modes.</td></tr>
* <tr><td><code>TranslateZ</code></td><td>number</td><td>number</td><td>Move the user's avatar in the direction of its
* z-axis, if the camera isn't in independent or mirror modes.</td></tr>
* <tr><td><code>Pitch</code></td><td>number</td><td>number</td><td>Rotate the user's avatar head and attached camera
* about its negative x-axis (i.e., positive values pitch down), if the camera isn't in HMD, independent, or mirror
* modes.</td></tr>
* <tr><td><code>Yaw</code></td><td>number</td><td>number</td><td>Rotate the user's avatar about its y-axis, if the
* camera isn't in independent or mirror modes.</td></tr>
* <tr><td><code>Roll</code></td><td>number</td><td>number</td><td>No action.</td></tr>
* <tr><td><code>StepTranslateX</code></td><td>number</td><td>number</td><td>No action.</td></tr>
* <tr><td><code>StepTranslateY</code></td><td>number</td><td>number</td><td>No action.</td></tr>
* <tr><td><code>StepTranslateZ</code></td><td>number</td><td>number</td><td>No action.</td></tr>
* <tr><td><code>StepPitch</code></td><td>number</td><td>number</td><td>No action.</td></tr>
* <tr><td><code>StepYaw</code></td><td>number</td><td>number</td><td>Rotate the user's avatar about its y-axis in a
* step increment, if the camera isn't in independent or mirror modes.</td></tr>
* <tr><td><code>StepRoll</code></td><td>number</td><td>number</td><td>No action.</td></tr>
*
* <tr><td colSpan=4><strong>Avatar Skeleton</strong></td>
* <tr><td><code>Hips</code></td><td>number</td><td>{@link Pose}</td><td>Set the hips pose of the user's avatar.
* </td></tr>
* <tr><td><code>Spine2</code></td><td>number</td><td>{@link Pose}</td><td>Set the spine2 pose of the user's avatar.
* </td></tr>
* <tr><td><code>Head</code></td><td>number</td><td>{@link Pose}</td><td>Set the head pose of the user's avatar.
* </td></tr>
* <tr><td><code>LeftArm</code></td><td>number</td><td>{@link Pose}</td><td>Set the left arm pose of the user's avatar.
* </td></tr>
* <tr><td><code>RightArm</code></td><td>number</td><td>{@link Pose}</td><td>Set the right arm pose of the user's
* avatar.</td></tr>
* <tr><td><code>LeftHand</code></td><td>number</td><td>{@link Pose}</td><td>Set the left hand pose of the user's
* avatar.</td></tr>
* <tr><td><code>LeftHandThumb1</code></td><td>number</td><td>{@link Pose}</td><td>Set the left thumb 1 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftHandThumb2</code></td><td>number</td><td>{@link Pose}</td><td>Set the left thumb 2 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftHandThumb3</code></td><td>number</td><td>{@link Pose}</td><td>Set the left thumb 3 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftHandThumb4</code></td><td>number</td><td>{@link Pose}</td><td>Set the left thumb 4 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftHandIndex1</code></td><td>number</td><td>{@link Pose}</td><td>Set the left index 1 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftHandIndex2</code></td><td>number</td><td>{@link Pose}</td><td>Set the left index 2 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftHandIndex3</code></td><td>number</td><td>{@link Pose}</td><td>Set the left index 3 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftHandIndex4</code></td><td>number</td><td>{@link Pose}</td><td>Set the left index 4 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftHandMiddle1</code></td><td>number</td><td>{@link Pose}</td><td>Set the left middle 1 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftHandMiddle2</code></td><td>number</td><td>{@link Pose}</td><td>Set the left middle 2 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftHandMiddle3</code></td><td>number</td><td>{@link Pose}</td><td>Set the left middle 3 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftHandMiddle4</code></td><td>number</td><td>{@link Pose}</td><td>Set the left middle 4 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftHandRing1</code></td><td>number</td><td>{@link Pose}</td><td>Set the left ring 1 finger joint pose
* of the user's avatar.</td></tr>
* <tr><td><code>LeftHandRing2</code></td><td>number</td><td>{@link Pose}</td><td>Set the left ring 2 finger joint pose
* of the user's avatar.</td></tr>
* <tr><td><code>LeftHandRing3</code></td><td>number</td><td>{@link Pose}</td><td>Set the left ring 3 finger joint pose
* of the user's avatar.</td></tr>
* <tr><td><code>LeftHandRing4</code></td><td>number</td><td>{@link Pose}</td><td>Set the left ring 4 finger joint pose
* of the user's avatar.</td></tr>
* <tr><td><code>LeftHandPinky1</code></td><td>number</td><td>{@link Pose}</td><td>Set the left pinky 1 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftHandPinky2</code></td><td>number</td><td>{@link Pose}</td><td>Set the left pinky 2 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftHandPinky3</code></td><td>number</td><td>{@link Pose}</td><td>Set the left pinky 3 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftHandPinky4</code></td><td>number</td><td>{@link Pose}</td><td>Set the left pinky 4 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHand</code></td><td>number</td><td>{@link Pose}</td><td>Set the right hand of the user's avatar.
* </td></tr>
* <tr><td><code>RightHandThumb1</code></td><td>number</td><td>{@link Pose}</td><td>Set the right thumb 1 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandThumb2</code></td><td>number</td><td>{@link Pose}</td><td>Set the right thumb 2 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandThumb3</code></td><td>number</td><td>{@link Pose}</td><td>Set the right thumb 3 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandThumb4</code></td><td>number</td><td>{@link Pose}</td><td>Set the right thumb 4 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandIndex1</code></td><td>number</td><td>{@link Pose}</td><td>Set the right index 1 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandIndex2</code></td><td>number</td><td>{@link Pose}</td><td>Set the right index 2 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandIndex3</code></td><td>number</td><td>{@link Pose}</td><td>Set the right index 3 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandIndex4</code></td><td>number</td><td>{@link Pose}</td><td>Set the right index 4 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandMiddle1</code></td><td>number</td><td>{@link Pose}</td><td>Set the right middle 1 finger
* joint pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandMiddle2</code></td><td>number</td><td>{@link Pose}</td><td>Set the right middle 2 finger
* joint pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandMiddle3</code></td><td>number</td><td>{@link Pose}</td><td>Set the right middle 3 finger
* joint pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandMiddle4</code></td><td>number</td><td>{@link Pose}</td><td>Set the right middle 4 finger
* joint pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandRing1</code></td><td>number</td><td>{@link Pose}</td><td>Set the right ring 1 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandRing2</code></td><td>number</td><td>{@link Pose}</td><td>Set the right ring 2 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandRing3</code></td><td>number</td><td>{@link Pose}</td><td>Set the right ring 3 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandRing4</code></td><td>number</td><td>{@link Pose}</td><td>Set the right ring 4 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandPinky1</code></td><td>number</td><td>{@link Pose}</td><td>Set the right pinky 1 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandPinky2</code></td><td>number</td><td>{@link Pose}</td><td>Set the right pinky 2 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandPinky3</code></td><td>number</td><td>{@link Pose}</td><td>Set the right pinky 3 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>RightHandPinky4</code></td><td>number</td><td>{@link Pose}</td><td>Set the right pinky 4 finger joint
* pose of the user's avatar.</td></tr>
* <tr><td><code>LeftFoot</code></td><td>number</td><td>{@link Pose}</td><td>Set the left foot pose of the user's
* avatar.</td></tr>
* <tr><td><code>RightFoot</code></td><td>number</td><td>{@link Pose}</td><td>Set the right foot pose of the user's
* avatar.</td></tr>
*
* <tr><td colSpan=4><strong><strong>Application</strong></td>
* <tr><td><code>BoomIn</code></td><td>number</td><td>number</td><td>Zoom camera in from third person toward first
* person view.</td></tr>
* <tr><td><code>BoomOut</code></td><td>number</td><td>number</td><td>Zoom camera out from first person to third
* person view.</td></tr>
* <tr><td><code>CycleCamera</code></td><td>number</td><td>number</td><td>Cycle the camera view from first person, to
* third person, to full screen mirror, then back to first person and repeat.</td></tr>
* <tr><td><code>ContextMenu</code></td><td>number</td><td>number</td><td>Show / hide the tablet.</td></tr>
* <tr><td><code>ToggleMute</code></td><td>number</td><td>number</td><td>Toggle the microphone mute.</td></tr>
* <tr><td><code>ToggleOverlay</code></td><td>number</td><td>number</td><td>Toggle the display of overlays.</td></tr>
* <tr><td><code>Sprint</code></td><td>number</td><td>number</td><td>Set avatar sprint mode.</td></tr>
* <tr><td><code>ReticleClick</code></td><td>number</td><td>number</td><td>Set mouse-pressed.</td></tr>
* <tr><td><code>ReticleX</code></td><td>number</td><td>number</td><td>Move the cursor left/right in the x direction.
* </td></tr>
* <tr><td><code>ReticleY</code></td><td>number</td><td>number</td><td>move the cursor up/down in the y direction.
* </td></tr>
* <tr><td><code>ReticleLeft</code></td><td>number</td><td>number</td><td>Move the cursor left.</td></tr>
* <tr><td><code>ReticleRight</code></td><td>number</td><td>number</td><td>Move the cursor right.</td></tr>
* <tr><td><code>ReticleUp</code></td><td>number</td><td>number</td><td>Move the cursor up.</td></tr>
* <tr><td><code>ReticleDown</code></td><td>number</td><td>number</td><td>Move the cursor down.</td></tr>
* <tr><td><code>UiNavLateral</code></td><td>number</td><td>number</td><td>Generate a keyboard left or right arrow key
* event.</td></tr>
* <tr><td><code>UiNavVertical</code></td><td>number</td><td>number</td><td>Generate a keyboard up or down arrow key
* event.</td></tr>
* <tr><td><code>UiNavGroup</code></td><td>number</td><td>number</td><td>Generate a keyboard tab or back-tab key event.
* </td></tr>
* <tr><td><code>UiNavSelect</code></td><td>number</td><td>number</td><td>Generate a keyboard Enter key event.
* </td></tr>
* <tr><td><code>UiNavBack</code></td><td>number</td><td>number</td><td>Generate a keyboard Esc key event.</td></tr>
* <tr><td><code>LeftHandClick</code></td><td>number</td><td>number</td><td><strong>Deprecated: </strong> No action.
* </td></tr>
* <tr><td><code>RightHandClick</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> No action.
* </td></tr>
* <tr><td><code>Shift</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> No action.</td></tr>
* <tr><td><code>PrimaryAction</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> No action.
* </td></tr>
* <tr><td><code>SecondaryAction</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> No action.
* </td></tr>
*
* <tr><td colSpan=4><strong>Aliases</strong></td>
* <tr><td><code>Backward</code></td><td>number</td><td>number</td><td>Alias for <code>TranslateZ</code> in the
* positive direction.</td></tr>
* <tr><td><code>Forward</code></td><td>number</td><td>number</td><td>Alias for <code>TranslateZ</code> in the negative
* direction.</td></tr>
* <tr><td><code>StrafeRight</code></td><td>number</td><td>number</td><td>Alias for <code>TranslateX</code> in the
* positive direction.</td></tr>
* <tr><td><code>StrafeLeft</code></td><td>number</td><td>number</td><td>Alias for <code>TranslateX</code> in the
* negative direction.</td></tr>
* <tr><td><code>Up</code></td><td>number</td><td>number</td><td>Alias for <code>TranslateY</code> in the positive
* direction.</td></tr>
* <tr><td><code>Down</code></td><td>number</td><td>number</td><td>Alias for <code>TranslateY</code> in the negative
* direction.</td></tr>
* <tr><td><code>PitchDown</code></td><td>number</td><td>number</td><td>Alias for <code>Pitch</code> in the positive
* direction.</td></tr>
* <tr><td><code>PitchUp</code></td><td>number</td><td>number</td><td>Alias for <code>Pitch</code> in the negative
* direction.</td></tr>
* <tr><td><code>YawLeft</code></td><td>number</td><td>number</td><td>Alias for <code>Yaw</code> in the positive
* direction.</td></tr>
* <tr><td><code>YawRight</code></td><td>number</td><td>number</td><td>Alias for <code>Yaw</code> in the negative
* direction.</td></tr>
*
* <tr><td colSpan=4><strong>Deprecated Aliases</strong></td>
* <tr><td><code>LEFT_HAND</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated:</strong> Use
* <code>LeftHand</code> instead.</td></tr>
* <tr><td><code>RIGHT_HAND</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated:</strong> Use
* <code>RightHand</code> instead.</td></tr>
* <tr><td><code>BOOM_IN</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>BoomIn</code> instead.</td></tr>
* <tr><td><code>BOOM_OUT</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>BoomOut</code> instead.</td></tr>
* <tr><td><code>CONTEXT_MENU</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>ContextMenu</code> instead.</td></tr>
* <tr><td><code>TOGGLE_MUTE</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>ToggleMute</code> instead.</td></tr>
* <tr><td><code>SPRINT</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>Sprint</code> instead.</td></tr>
* <tr><td><code>LONGITUDINAL_BACKWARD</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>Backward</code> instead.</td></tr>
* <tr><td><code>LONGITUDINAL_FORWARD</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>Forward</code> instead.</td></tr>
* <tr><td><code>LATERAL_LEFT</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>StrafeLeft</code> instead.</td></tr>
* <tr><td><code>LATERAL_RIGHT</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>StrafeRight</code> instead.</td></tr>
* <tr><td><code>VERTICAL_UP</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>Up</code> instead.</td></tr>
* <tr><td><code>VERTICAL_DOWN</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>Down</code> instead.</td></tr>
* <tr><td><code>PITCH_DOWN</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>PitchDown</code> instead.</td></tr>
* <tr><td><code>PITCH_UP</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>PitchUp</code> instead.</td></tr>
* <tr><td><code>YAW_LEFT</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>YawLeft</code> instead.</td></tr>
* <tr><td><code>YAW_RIGHT</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>YawRight</code> instead.</td></tr>
* <tr><td><code>LEFT_HAND_CLICK</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>LeftHandClick</code> instead.</td></tr>
* <tr><td><code>RIGHT_HAND_CLICK</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>RightHandClick</code> instead.</td></tr>
* <tr><td><code>SHIFT</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>Shift</code> instead.</td></tr>
* <tr><td><code>ACTION1</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>PrimaryAction</code> instead.</td></tr>
* <tr><td><code>ACTION2</code></td><td>number</td><td>number</td><td><strong>Deprecated:</strong> Use
* <code>SecondaryAction</code> instead.</td></tr>
*
* <tr><td colSpan=4><strong>Deprecated Trackers</strong></td>
* <tr><td><code>TrackedObject00</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* <tr><td><code>TrackedObject01</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* <tr><td><code>TrackedObject02</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* <tr><td><code>TrackedObject03</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* <tr><td><code>TrackedObject04</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* <tr><td><code>TrackedObject05</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* <tr><td><code>TrackedObject06</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* <tr><td><code>TrackedObject07</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* <tr><td><code>TrackedObject08</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* <tr><td><code>TrackedObject09</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* <tr><td><code>TrackedObject10</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* <tr><td><code>TrackedObject11</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* <tr><td><code>TrackedObject12</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* <tr><td><code>TrackedObject13</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* <tr><td><code>TrackedObject14</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* <tr><td><code>TrackedObject15</code></td><td>number</td><td>{@link Pose}</td><td><strong>Deprecated: </strong> No
* action.</td></tr>
* </tbody>
* </table>
* @typedef Controller.Actions
*/
// Device functions
Input::NamedVector ActionsDevice::getAvailableInputs() const {
static Input::NamedVector availableInputs {

View file

@ -31,12 +31,67 @@ namespace controller {
class Endpoint;
using EndpointPointer = std::shared_ptr<Endpoint>;
/**jsdoc
* <p>Some controller actions may be associated with one or both hands:</p>
* <table>
* <thead>
* <tr><th>Value</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>0</code></td><td>Left hand.</td></tr>
* <tr><td><code>1</code></td><td>Right hand.</td></tr>
* <tr><td><code>2</code></td><td>Both hands.</td></tr>
* </tbody>
* </table>
* @typedef {number} Controller.Hand
*/
enum Hand {
LEFT = 0,
RIGHT,
BOTH
};
/**jsdoc
* <p>The <code>Controller.Hardware</code> object has properties representing standard and hardware-specific controller and
* computer outputs, plus predefined actions on Interface and the user's avatar. <em>Read-only.</em> The outputs can be mapped
* to actions or functions in a {@link RouteObject} mapping. Additionally, hardware-specific controller outputs can be mapped
* to standard controller outputs.
*
* <p>Controllers typically implement a subset of the {@link Controller.Standard} controls, plus they may implement some extras.
* Some common controllers are included in the table. You can see the outputs provided by these and others by
* viewing their {@link Controller.MappingJSON|MappingJSON} files at
* <a href="https://github.com/highfidelity/hifi/tree/master/interface/resources/controllers">
* https://github.com/highfidelity/hifi/tree/master/interface/resources/controllers</a>.</p>
*
* <table>
* <thead>
* <tr><th>Property</th><th>Type</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>Controller.Hardware.Actions</code></td><td>object</td><td>Synonym for {@link Controller.Actions}.</td></tr>
* <tr><td><code>Controller.Hardware.Application</code></td><td>object</td><td>Interface state outputs. See
* {@link Controller.Hardware-Application}.</td></tr>
* <tr><td><code>Controller.Hardware.Keyboard</code></td><td>object</td><td>Keyboard, mouse, and touch pad outputs. See
* {@link Controller.Hardware-Keyboard}.</td></tr>
* <tr><td><code>Controller.Hardware.OculusTouch</code></td><td>object</td><td>Oculus Rift HMD outputs. See
* {@link Controller.Hardware-OculusTouch}.</td></tr>
* <tr><td><code>Controller.Hardware.Vive</code></td><td>object</td><td>Vive HMD outputs. See
* {@link Controller.Hardware-Vive}.</td></tr>
* </tbody>
* </table>
* @typedef Controller.Hardware
* @example <caption>List all the currently available <code>Controller.Hardware</code> properties.</caption>
* function printProperties(string, item) {
* print(string);
* for (var key in item) {
* if (item.hasOwnProperty(key)) {
* printProperties(string + "." + key, item[key]);
* }
* }
* }
*
* printProperties("Controller.Hardware", Controller.Hardware);
*/
// NOTE: If something inherits from both InputDevice and InputPlugin, InputPlugin must go first.
// e.g. class Example : public InputPlugin, public InputDevice
// instead of class Example : public InputDevice, public InputPlugin

View file

@ -30,6 +30,15 @@ namespace controller {
velocity == right.getVelocity() && angularVelocity == right.getAngularVelocity();
}
/**jsdoc
* The pose of a joint or other item relative to the world or a parent.
* @typedef {object} Pose
* @property {Vec3} translation - Translation.
* @property {Quat} rotation - Rotation.
* @property {Vec3} velocity - Velocity in m/s.
* @property {Vec3} angularVelocity - Angular velocity in rad/s.
* @property {boolean} valid - <code>true</code> if the pose is valid, otherwise <code>false</code>.
*/
QScriptValue Pose::toScriptValue(QScriptEngine* engine, const Pose& pose) {
QScriptValue obj = engine->newObject();
obj.setProperty("translation", vec3toScriptValue(engine, pose.translation));
@ -37,7 +46,6 @@ namespace controller {
obj.setProperty("velocity", vec3toScriptValue(engine, pose.velocity));
obj.setProperty("angularVelocity", vec3toScriptValue(engine, pose.angularVelocity));
obj.setProperty("valid", pose.valid);
return obj;
}

View file

@ -1,6 +1,6 @@
//
// AbstractControllerScriptingInterface.h
// libraries/script-engine/src
// ScriptingInterface.h
// libraries/controllers/src/controllers
//
// Created by Brad Hefta-Gaub on 12/17/13.
// Copyright 2013 High Fidelity, Inc.
@ -61,6 +61,8 @@ namespace controller {
/// handles scripting of input controller commands from JS
class ScriptingInterface : public QObject, public Dependency {
Q_OBJECT
// These properties have JSDoc in ControllerScriptingInterface.h.
Q_PROPERTY(QVariantMap Hardware READ getHardware CONSTANT FINAL)
Q_PROPERTY(QVariantMap Actions READ getActions CONSTANT FINAL)
Q_PROPERTY(QVariantMap Standard READ getStandard CONSTANT FINAL)
@ -69,42 +71,401 @@ namespace controller {
ScriptingInterface();
virtual ~ScriptingInterface() {};
/**jsdoc
* Get a list of all available actions.
* @function Controller.getAllActions
* @returns {Action[]} All available actions.
* @deprecated This function no longer works.
*/
// FIXME: This function causes a JavaScript crash: https://highfidelity.manuscript.com/f/cases/edit/13921
Q_INVOKABLE QVector<Action> getAllActions();
/**jsdoc
* Get a list of all available inputs for a hardware device.
* @function Controller.getAvailableInputs
* @param {number} deviceID - Integer ID of the hardware device.
* @returns {NamedPair[]} All available inputs for the device.
* @deprecated This function no longer works.
*/
// FIXME: This function causes a JavaScript crash: https://highfidelity.manuscript.com/f/cases/edit/13922
Q_INVOKABLE QVector<Input::NamedPair> getAvailableInputs(unsigned int device);
/**jsdoc
* Find the name of a particular controller from its device ID.
* @function Controller.getDeviceName
* @param {number} deviceID - The integer ID of the device.
* @returns {string} The name of the device if found, otherwise <code>"unknown"</code>.
* @example <caption>Get the name of the Oculus Touch controller from its ID.</caption>
* var deviceID = Controller.findDevice("OculusTouch");
* print("Device ID = " + deviceID);
*
* var deviceName = Controller.getDeviceName(deviceID);
* print("Device name = " + deviceName);
*/
Q_INVOKABLE QString getDeviceName(unsigned int device);
/**jsdoc
* Get the current value of an action.
* @function Controller.getActionValue
* @param {number} actionID - The integer ID of the action.
* @returns {number} The current value of the action.
* @example <caption>Periodically report the value of the "TranslateX" action.</caption>
* var actionID = Controller.findAction("TranslateX");
*
* function reportValue() {
* print(Controller.getActionValue(actionID));
* }
* reportTimer = Script.setInterval(reportValue, 1000);
*/
Q_INVOKABLE float getActionValue(int action);
/**jsdoc
* Find the ID of a specific controller from its device name.
* @function Controller.findDevice
* @param {string} deviceName - The name of the device to find.
* @returns {number} The integer ID of the device if available, otherwise <code>65535</code>.
* @example <caption>Get the ID of the Oculus Touch.</caption>
* var deviceID = Controller.findDevice("OculusTouch");
* print("Device ID = " + deviceID);
*/
Q_INVOKABLE int findDevice(QString name);
/**jsdoc
* Get the names of all currently available controller devices plus "Actions", "Application", and "Standard".
* @function Controller.getDeviceNames
* @returns {string[]} An array of device names.
* @example <caption>Get the names of all currently available controller devices.</caption>
* var deviceNames = Controller.getDeviceNames();
* print(JSON.stringify(deviceNames));
* // ["Standard","Keyboard","LeapMotion","OculusTouch","Application","Actions"] or similar.
*/
Q_INVOKABLE QVector<QString> getDeviceNames();
/**jsdoc
* Find the ID of an action from its name.
* @function Controller.findAction
* @param {string} actionName - The name of the action: one of the {@link Controller.Actions} property names.
* @returns {number} The integer ID of the action if found, otherwise <code>4095</code>. Note that this value is not
* the same as the value of the relevant {@link Controller.Actions} property.
* @example <caption>Get the ID of the "TranslateY" action. Compare with the property value.</caption>
* var actionID = Controller.findAction("TranslateY");
* print("Action ID = " + actionID); // 1
* print("Property value = " + Controller.Actions.TranslateY); // 537001728 or similar value.
*/
Q_INVOKABLE int findAction(QString actionName);
/**jsdoc
* Get the names of all actions available as properties of {@link Controller.Actions}.
* @function Controller.getActionNames
* @returns {string[]} An array of action names.
* @example <caption>Get the names of all actions.</caption>
* var actionNames = Controller.getActionNames();
* print("Action names: " + JSON.stringify(actionNames));
* // ["TranslateX","TranslateY","TranslateZ","Roll", ...
*/
Q_INVOKABLE QVector<QString> getActionNames() const;
/**jsdoc
* Get the value of a controller button or axis output. Note: Also gets the value of a controller axis output.
* @function Controller.getValue
* @param {number} source - The {@link Controller.Standard} or {@link Controller.Hardware} item.
* @returns {number} The current value of the controller item output if <code>source</code> is valid, otherwise
* <code>0</code>.
* @example <caption>Report the Standard and Vive right trigger values.</caption>
* var triggerValue = Controller.getValue(Controller.Standard.RT);
* print("Trigger value: " + triggerValue);
*
* if (Controller.Hardware.Vive) {
* triggerValue = Controller.getValue(Controller.Hardware.Vive.RT);
* print("Vive trigger value: " + triggerValue);
* } else {
* print("No Vive present");
* }
*/
Q_INVOKABLE float getValue(const int& source) const;
Q_INVOKABLE float getButtonValue(StandardButtonChannel source, uint16_t device = 0) const;
Q_INVOKABLE float getAxisValue(StandardAxisChannel source, uint16_t device = 0) const;
/**jsdoc
* Get the value of a controller axis output. Note: Also gets the value of a controller button output.
* @function Controller.getAxisValue
* @param {number} source - The {@link Controller.Standard} or {@link Controller.Hardware} item.
* @returns {number} The current value of the controller item output if <code>source</code> is valid, otherwise
* <code>0</code>.
*/
// TODO: getAxisValue() should use const int& parameter? Or others shouldn't?
Q_INVOKABLE float getAxisValue(int source) const;
/**jsdoc
* Get the value of a controller pose output.
* @function Controller.getPoseValue
* @param {number} source - The {@link Controller.Standard} or {@link Controller.Hardware} pose output.
* @returns {Pose} The current value of the controller pose output if <code>source</code> is a pose output, otherwise
* an invalid pose with <code>Pose.valid == false</code>.
* @exammple <caption>Report the right hand's pose.</caption>
* var pose = Controller.getPoseValue(Controller.Standard.RightHand);
* print("Pose: " + JSON.stringify(pose));
*/
Q_INVOKABLE Pose getPoseValue(const int& source) const;
/**jsdoc
* Get the value of a button on a particular device.
* @function Controller.getButtonValue
* @param {StandardButtonChannel} source - The button to get the value of.
* @param {number} [device=0] - The ID of the hardware device to get the value from. The default value of
* <code>0</code> corresponds to <code>Standard</code>.
* @returns {number} The current value of the button if the parameters are valid, otherwise <code>0</code>.
* @deprecated This function no longer works.
*/
// FIXME: This function causes a JavaScript crash: https://highfidelity.manuscript.com/f/cases/edit/14139
Q_INVOKABLE float getButtonValue(StandardButtonChannel source, uint16_t device = 0) const;
/**jsdoc
* Get the value of an axis control on a particular device.
* @function Controller.getAxisValue
* @variation 0
* @param {StandardAxisChannel} source - The axis to get the value of.
* @param {number} [device=0] - The ID of the hardware device to get the value from. The default value of
* <code>0</code> corresponds to <code>Standard</code>.
* @returns {number} The current value of the axis if the parameters are valid, otherwise <code>0</code>.
* @deprecated This function no longer works.
*/
Q_INVOKABLE float getAxisValue(StandardAxisChannel source, uint16_t device = 0) const;
/**jsdoc
* Get the value of an pose control on a particular device.
* @function Controller.getPoseValue
* @variation 0
* @param {StandardPoseChannel} source - The pose to get the value of.
* @param {number} [device=0] - The ID of the hardware device to get the value from. The default value of
* <code>0</code> corresponds to <code>Standard</code>.
* @returns {Pose} The current value of the controller pose output if the parameters are valid, otherwise an invalid
* pose with <code>Pose.valid == false</code>.
* @deprecated This function no longer works.
*/
Q_INVOKABLE Pose getPoseValue(StandardPoseChannel source, uint16_t device = 0) const;
/**jsdoc
* Triggers a haptic pulse on connected and enabled devices that have the capability.
* @function Controller.triggerHapticPulse
* @param {number} strength - The strength of the haptic pulse, <code>0.0</code> &ndash; <code>1.0</code>.
* @param {number} duration - The duration of the haptic pulse, in milliseconds.
* @param {Controller.Hand} hand=2 - The hand or hands to trigger the haptic pulse on.
* @example <caption>Trigger a haptic pulse on the right hand.</caption>
* var HAPTIC_STRENGTH = 0.5;
* var HAPTIC_DURATION = 10;
* var RIGHT_HAND = 1;
* Controller.triggerHapticPulse(HAPTIC_STRENGTH, HAPTIC_DURATION, RIGHT_HAND);
*/
Q_INVOKABLE bool triggerHapticPulse(float strength, float duration, controller::Hand hand = BOTH) const;
Q_INVOKABLE bool triggerShortHapticPulse(float strength, controller::Hand hand = BOTH) const;
Q_INVOKABLE bool triggerHapticPulseOnDevice(unsigned int device, float strength, float duration, controller::Hand hand = BOTH) const;
Q_INVOKABLE bool triggerShortHapticPulseOnDevice(unsigned int device, float strength, controller::Hand hand = BOTH) const;
/**jsdoc
* Triggers a 250ms haptic pulse on connected and enabled devices that have the capability.
* @function Controller.triggerShortHapticPulse
* @param {number} strength - The strength of the haptic pulse, <code>0.0</code> &ndash; <code>1.0</code>.
* @param {Controller.Hand} hand=2 - The hand or hands to trigger the haptic pulse on.
*/
Q_INVOKABLE bool triggerShortHapticPulse(float strength, controller::Hand hand = BOTH) const;
/**jsdoc
* Triggers a haptic pulse on a particular device if connected and enabled and it has the capability.
* @function Controller.triggerHapticPulseOnDevice
* @param {number} deviceID - The ID of the device to trigger the haptic pulse on.
* @param {number} strength - The strength of the haptic pulse, <code>0.0</code> &ndash; <code>1.0</code>.
* @param {number} duration - The duration of the haptic pulse, in milliseconds.
* @param {Controller.Hand} hand=2 - The hand or hands to trigger the haptic pulse on.
* @example <caption>Trigger a haptic pulse on an Oculus Touch controller.</caption>
* var HAPTIC_STRENGTH = 0.5;
* var deviceID = Controller.findDevice("OculusTouch");
* var HAPTIC_DURATION = 10;
* var RIGHT_HAND = 1;
* Controller.triggerHapticPulseOnDevice(deviceID, HAPTIC_STRENGTH, HAPTIC_DURATION, RIGHT_HAND);
*/
Q_INVOKABLE bool triggerHapticPulseOnDevice(unsigned int device, float strength, float duration,
controller::Hand hand = BOTH) const;
/**jsdoc
* Triggers a 250ms haptic pulse on a particular device if connected and enabled and it has the capability.
* @function Controller.triggerShortHapticPulseOnDevice
* @param {number} deviceID - The ID of the device to trigger the haptic pulse on.
* @param {number} strength - The strength of the haptic pulse, <code>0.0</code> &ndash; <code>1.0</code>.
* @param {Controller.Hand} hand=2 - The hand or hands to trigger the haptic pulse on.
*/
Q_INVOKABLE bool triggerShortHapticPulseOnDevice(unsigned int device, float strength, controller::Hand hand = BOTH)
const;
/**jsdoc
* Create a new controller mapping. Routes can then be added to the mapping using {@link MappingObject} methods and
* routed to <code>Standard</code> controls, <code>Actions</code>, or script functions using {@link RouteObject}
* methods. The mapping can then be enabled using {@link Controller.enableMapping|enableMapping} for it to take effect.
* @function Controller.newMapping
* @param {string} mappingName=Uuid.generate() - A unique name for the mapping. If not specified a new UUID generated
* by {@link Uuid.generate} is used.
* @returns {MappingObject} A controller mapping object.
* @example <caption>Create a simple mapping that makes the right trigger move your avatar up.</caption>
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
*
* mapping.from(Controller.Standard.RT).to(Controller.Actions.TranslateY);
* Controller.enableMapping(MAPPING_NAME);
*
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*/
Q_INVOKABLE QObject* newMapping(const QString& mappingName = QUuid::createUuid().toString());
/**jsdoc
* Enable or disable a controller mapping. When enabled, the routes in the mapping have effect.
* @function Controller.enableMapping
* @param {string} mappingName - The name of the mapping.
* @param {boolean} enable=true - If <code>true</code> then the mapping is enabled, otherwise it is disabled.
*/
Q_INVOKABLE void enableMapping(const QString& mappingName, bool enable = true);
/**jsdoc
* Disable a controller mapping. When disabled, the routes in the mapping have no effect.
* @function Controller.disableMapping
* @param {string} mappingName - The name of the mapping.
*/
Q_INVOKABLE void disableMapping(const QString& mappingName) { enableMapping(mappingName, false); }
/**jsdoc
* Create a new controller mapping from a {@link Controller.MappingJSON|MappingJSON} string. Use
* {@link Controller.enableMapping|enableMapping} to enable the mapping for it to take effect.
* @function Controller.parseMapping
* @param {string} jsonString - A JSON string of the {@link Controller.MappingJSON|MappingJSON}.
* @returns {MappingObject} A controller mapping object.
* @example <caption>Create a simple mapping that makes the right trigger move your avatar up.</caption>
* var mappingJSON = {
* "name": "com.highfidelity.controllers.example.jsonMapping",
* "channels": [
* { "from": "Standard.RT", "to": "Actions.TranslateY" }
* ]
* };
*
* var mapping = Controller.parseMapping(JSON.stringify(mappingJSON));
* mapping.enable();
*
* Script.scriptEnding.connect(function () {
* mapping.disable();
* });
*/
Q_INVOKABLE QObject* parseMapping(const QString& json);
/**jsdoc
* Create a new controller mapping from a {@link Controller.MappingJSON|MappingJSON} JSON file at a URL. Use
* {@link Controller.enableMapping|enableMapping} to enable the mapping for it to take effect.
* @function Controller.loadMapping
* @param {string} jsonURL - The URL the {@link Controller.MappingJSON|MappingJSON} JSON file.
* @returns {MappingObject} A controller mapping object.
* @todo <em>Implement this function. It currently does not load the mapping from the file; it just returns
* <code>null</code>.</em>
*/
Q_INVOKABLE QObject* loadMapping(const QString& jsonUrl);
/**jsdoc
* Get the {@link Controller.Hardware} property tree. Calling this function is the same as using the {@link Controller}
* property, <code>Controller.Hardware</code>.
* @function Controller.getHardware
* @returns {Controller.Hardware} The {@link Controller.Hardware} property tree.
*/
Q_INVOKABLE const QVariantMap getHardware() { return _hardware; }
Q_INVOKABLE const QVariantMap getActions() { return _actions; }
/**jsdoc
* Get the {@link Controller.Actions} property tree. Calling this function is the same as using the {@link Controller}
* property, <code>Controller.Actions</code>.
* @function Controller.getActions
* @returns {Controller.Actions} The {@link Controller.Actions} property tree.
*/
Q_INVOKABLE const QVariantMap getActions() { return _actions; } //undefined
/**jsdoc
* Get the {@link Controller.Standard} property tree. Calling this function is the same as using the {@link Controller}
* property, <code>Controller.Standard</code>.
* @function Controller.getStandard
* @returns {Controller.Standard} The {@link Controller.Standard} property tree.
*/
Q_INVOKABLE const QVariantMap getStandard() { return _standard; }
/**jsdoc
* Start making a recording of currently active controllers.
* @function Controller.startInputRecording
* @example <caption>Make a controller recording.</caption>
* // Delay start of recording for 2s.
* Script.setTimeout(function () {
* print("Start input recording");
* Controller.startInputRecording();
* }, 2000);
*
* // Make a 10s recording.
* Script.setTimeout(function () {
* print("Stop input recording");
* Controller.stopInputRecording();
* Controller.saveInputRecording();
* print("Input recording saved in: " + Controller.getInputRecorderSaveDirectory());
* }, 12000);
*/
Q_INVOKABLE void startInputRecording();
/**jsdoc
* Stop making a recording started by {@link Controller.startInputRecording|startInputRecording}.
* @function Controller.stopInputRecording
*/
Q_INVOKABLE void stopInputRecording();
/**jsdoc
* Play back the current recording from the beginning. The current recording may have been recorded by
* {@link Controller.startInputRecording|startInputRecording} and
* {@link Controller.stopInputRecording|stopInputRecording}, or loaded by
* {@link Controller.loadInputRecording|loadInputRecording}. Playback repeats in a loop until
* {@link Controller.stopInputPlayback|stopInputPlayback} is called.
* @function Controller.startInputPlayback
* @example <caption>Play back a controller recording.</caption>
* var file = Window.browse("Select Recording", Controller.getInputRecorderSaveDirectory());
* if (file !== null) {
* print("Play recording: " + file);
* Controller.loadInputRecording("file:///" + file);
* Controller.startInputPlayback();
*
* // Stop playback after 20s.
* Script.setTimeout(function () {
* print("Stop playing recording");
* Controller.stopInputPlayback();
* }, 20000);
* }
*/
Q_INVOKABLE void startInputPlayback();
/**jsdoc
* Stop play back of a recording started by {@link Controller.startInputPlayback|startInputPlayback}.
* @function Controller.stopInputPlayback
*/
Q_INVOKABLE void stopInputPlayback();
/**jsdoc
* Save the current recording to a file. The current recording may have been recorded by
* {@link Controller.startInputRecording|startInputRecording} and
* {@link Controller.stopInputRecording|stopInputRecording}, or loaded by
* {@link Controller.loadInputRecording|loadInputRecording}. It is saved in the directory returned by
* {@link Controller.getInputRecorderSaveDirectory|getInputRecorderSaveDirectory}.
* @function Controller.saveInputRecording
*/
Q_INVOKABLE void saveInputRecording();
/**jsdoc
* Load an input recording, ready for play back.
* @function Controller.loadInputRecording
* @param {string} file - The path to the recording file, prefixed by <code>"file:///"</code>.
*/
Q_INVOKABLE void loadInputRecording(const QString& file);
/**jsdoc
* Get the directory in which input recordings are saved.
* @function Controller.getInputRecorderSaveDirectory
* @returns {string} The directory in which input recordings are saved.
*/
Q_INVOKABLE QString getInputRecorderSaveDirectory();
bool isMouseCaptured() const { return _mouseCaptured; }
@ -114,21 +475,156 @@ namespace controller {
public slots:
/**jsdoc
* Disable processing of mouse "move", "press", "double-press", and "release" events into
* {@link Controller.Hardware|Controller.Hardware.Keyboard} outputs.
* @function Controller.captureMouseEvents
* @example <caption>Disable Controller.Hardware.Keyboard mouse events for a short period.</caption>
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
* mapping.from(Controller.Hardware.Keyboard.MouseX).to(function (x) {
* print("Mouse x = " + x);
* });
* mapping.from(Controller.Hardware.Keyboard.MouseY).to(function (y) {
* print("Mouse y = " + y);
* });
* Controller.enableMapping(MAPPING_NAME);
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*
* Script.setTimeout(function () {
* Controller.captureMouseEvents();
* }, 5000);
*
* Script.setTimeout(function () {
* Controller.releaseMouseEvents();
* }, 10000);
*/
virtual void captureMouseEvents() { _mouseCaptured = true; }
/**jsdoc
* Enable processing of mouse "move", "press", "double-press", and "release" events into
* {@link Controller.Hardware-Keyboard|Controller.Hardware.Keyboard} outputs that were disabled using
* {@link Controller.captureMouseEvents|captureMouseEvents}.
* @function Controller.releaseMouseEvents
*/
virtual void releaseMouseEvents() { _mouseCaptured = false; }
/**jsdoc
* Disable processing of touch "begin", "update", and "end" events into
* {@link Controller.Hardware|Controller.Hardware.Keyboard},
* {@link Controller.Hardware|Controller.Hardware.Touchscreen}, and
* {@link Controller.Hardware|Controller.Hardware.TouchscreenVirtualPad} outputs.
* @function Controller.captureTouchEvents
*/
virtual void captureTouchEvents() { _touchCaptured = true; }
/**jsdoc
* Enable processing of touch "begin", "update", and "end" events into
* {@link Controller.Hardware|Controller.Hardware.Keyboard},
* {@link Controller.Hardware|Controller.Hardware.Touchscreen}, and
* {@link Controller.Hardware|Controller.Hardware.TouchscreenVirtualPad} outputs that were disabled using
* {@link Controller.captureTouchEvents|captureTouchEvents}.
* @function Controller.releaseTouchEvents
*/
virtual void releaseTouchEvents() { _touchCaptured = false; }
/**jsdoc
* Disable processing of mouse wheel rotation events into {@link Controller.Hardware|Controller.Hardware.Keyboard}
* outputs.
* @function Controller.captureWheelEvents
*/
virtual void captureWheelEvents() { _wheelCaptured = true; }
/**jsdoc
* Enable processing of mouse wheel rotation events into {@link Controller.Hardware|Controller.Hardware.Keyboard}
* outputs that wer disabled using {@link Controller.captureWheelEvents|captureWheelEvents}.
* @function Controller.releaseWheelEvents
*/
virtual void releaseWheelEvents() { _wheelCaptured = false; }
/**jsdoc
* Disable translating and rotating the user's avatar in response to keyboard and controller controls.
* @function Controller.captureActionEvents
* @example <caption>Disable avatar translation and rotation for a short period.</caption>
* Script.setTimeout(function () {
* Controller.captureActionEvents();
* }, 5000);
*
* Script.setTimeout(function () {
* Controller.releaseActionEvents();
* }, 10000);
*/
virtual void captureActionEvents() { _actionsCaptured = true; }
/**jsdoc
* Enable translating and rotating the user's avatar in response to keyboard and controller controls that were disabled
* using {@link Controller.captureActionEvents|captureActionEvents}.
* @function Controller.releaseActionEvents
*/
virtual void releaseActionEvents() { _actionsCaptured = false; }
signals:
/**jsdoc
* Triggered when an action occurs.
* @function Controller.actionEvent
* @param {number} actionID - The ID of the action, per {@link Controller.findAction|findAction}.
* @param {number} value - The value associated with the action.
* @returns {Signal}
* @example <caption>Report action events as they occur.</caption>
* var actionNamesForID = {};
* var actionNames = Controller.getActionNames();
* for (var i = 0, length = actionNames.length; i < length; i++) {
* actionNamesForID[Controller.findAction(actionNames[i])] = actionNames[i];
* }
*
* function onActionEvent(action, value) {
* print("onActionEvent() : " + action + " ( " + actionNamesForID[action] + " ) ; " + value);
* }
*
* Controller.actionEvent.connect(onActionEvent);
*
* Script.scriptEnding.connect(function () {
* Controller.actionEvent.disconnect(onActionEvent);
* });
*/
void actionEvent(int action, float state);
/**jsdoc
* Triggered when there is a new controller input event.
* @function Controller.inputEvent
* @param {number} action - The input action, per {@link Controller.Standard}.
* @param {number} value - The value associated with the input action.
* @returns {Signal}
* @example <caption>Report input events as they occur.</caption>
* var inputNamesForID = {};
* for (var property in Controller.Standard) {
* inputNamesForID[Controller.Standard[property]] = "Controller.Standard." + property;
* }
*
* function onInputEvent(input, value) {
* print("onInputEvent() : " + input + " ( " + inputNamesForID[input] + " ) ; " + value);
* }
*
* Controller.inputEvent.connect(onInputEvent);
*
* Script.scriptEnding.connect(function () {
* Controller.inputEvent.disconnect(onInputEvent);
* });
*/
void inputEvent(int action, float state);
/**jsdoc
* Triggered when a device is registered or unregistered by a plugin. Not all plugins generate
* <code>hardwareChanged</code> events: for example connecting or disconnecting a mouse will not generate an event but
* connecting or disconnecting an Xbox controller will.
* @function Controller.hardwareChanged
* @returns {Signal}
*/
void hardwareChanged();
private:
@ -145,8 +641,6 @@ namespace controller {
std::atomic<bool> _actionsCaptured { false };
};
}
#endif // hifi_AbstractControllerScriptingInterface_h

View file

@ -27,6 +27,212 @@ void StandardController::focusOutEvent() {
_buttonPressedMap.clear();
};
/**jsdoc
* <p>The <code>Controller.Standard</code> object has properties representing standard controller outputs. Those for physical
* controllers are based on the XBox controller, with aliases for PlayStation. The property values are integer IDs, uniquely
* identifying each output. <em>Read-only.</em> These can be mapped to actions or functions in a {@link RouteObject}
* mapping.</p>
*
* <p>The data value provided by each control is either a number or a {@link Pose}. Numbers are typically normalized to
* <code>0.0</code> or <code>1.0</code> for button states, the range <code>0.0 &ndash; 1.0</code> for unidirectional scales,
* and the range <code>-1.0 &ndash; 1.0</code> for bidirectional scales.</p>
*
* <p>Each hardware device has a mapping from its outputs to <code>Controller.Standard</code> items, specified in a JSON file.
* For example, <a href="https://github.com/highfidelity/hifi/blob/master/interface/resources/controllers/leapmotion.json">
* leapmotion.json</a> and
* <a href="https://github.com/highfidelity/hifi/blob/master/interface/resources/controllers/vive.json">vive.json</a>.</p>
*
* <table>
* <thead>
* <tr><th>Property</th><th>Type</th><th>Data</th><th>Description</th></tr>
* </thead>
* <tbody>
*
* <tr><td colspan="4"><strong>Buttons</strong></td></tr>
* <tr><td><code>A</code></td><td>number</td><td>number</td><td>"A" button pressed.</td></tr>
* <tr><td><code>B</code></td><td>number</td><td>number</td><td>"B" button pressed.</td></tr>
* <tr><td><code>X</code></td><td>number</td><td>number</td><td>"X" button pressed.</td></tr>
* <tr><td><code>Y</code></td><td>number</td><td>number</td><td>"Y" button pressed.</td></tr>
* <tr><td><code>DL</code></td><td>number</td><td>number</td><td>D-pad left pressed.</td></tr>
* <tr><td><code>DR</code></td><td>number</td><td>number</td><td>D-pad right pressed.</td></tr>
* <tr><td><code>DU</code></td><td>number</td><td>number</td><td>D-pad up pressed.</td></tr>
* <tr><td><code>DD</code></td><td>number</td><td>number</td><td>D-pad down pressed.</td></tr>
* <tr><td><code>Start</code></td><td>number</td><td>number</td><td>"Start" center button pressed.</td></tr>
* <tr><td><code>Back</code></td><td>number</td><td>number</td><td>"Back" center button pressed.</td></tr>
* <tr><td><code>LB</code></td><td>number</td><td>number</td><td>Left bumper button pressed.</td></tr>
* <tr><td><code>RB</code></td><td>number</td><td>number</td><td>Right bumper button pressed.</td></tr>
*
* <tr><td colspan="4"><strong>Sticks</strong></td></tr>
* <tr><td><code>LX</code></td><td>number</td><td>number</td><td>Left stick x-axis scale.</td></tr>
* <tr><td><code>LY</code></td><td>number</td><td>number</td><td>Left stick y-axis scale.</td></tr>
* <tr><td><code>RX</code></td><td>number</td><td>number</td><td>Right stick x-axis scale.</td></tr>
* <tr><td><code>RY</code></td><td>number</td><td>number</td><td>Right stick y-axis scale.</td></tr>
* <tr><td><code>LS</code></td><td>number</td><td>number</td><td>Left stick button pressed.</td></tr>
* <tr><td><code>RS</code></td><td>number</td><td>number</td><td>Right stick button pressed.</td></tr>
* <tr><td><code>LSTouch</code></td><td>number</td><td>number</td><td>Left stick is touched.</td></tr>
* <tr><td><code>RSTouch</code></td><td>number</td><td>number</td><td>Right stick is touched.</td></tr>
*
* <tr><td colspan="4"><strong>Triggers</strong></td></tr>
* <tr><td><code>LT</code></td><td>number</td><td>number</td><td>Left trigger scale.</td></tr>
* <tr><td><code>RT</code></td><td>number</td><td>number</td><td>Right trigger scale.</td></tr>
* <tr><td><code>LTClick</code></td><td>number</td><td>number</td><td>Left trigger click.</td></tr>
* <tr><td><code>RTClick</code></td><td>number</td><td>number</td><td>Right trigger click.</td></tr>
* <tr><td><code>LeftGrip</code></td><td>number</td><td>number</td><td>Left grip scale.</td></tr>
* <tr><td><code>RightGrip</code></td><td>number</td><td>number</td><td>Right grip scale.</td></tr>
* <tr><td><code>LeftGripTouch</code></td><td>number</td><td>number</td><td>Left grip is touched.</td></tr>
* <tr><td><code>RightGripTouch</code></td><td>number</td><td>number</td><td>Right grip is touched.</td></tr>
*
* <tr><td colspan="4"><strong>Aliases, PlayStation Style Names</strong></td></tr>
* <tr><td><code>Cross</code></td><td>number</td><td>number</td><td>Alias for <code>A</code>.</td></tr>
* <tr><td><code>Circle</code></td><td>number</td><td>number</td><td>Alias for <code>B</code>.</td></tr>
* <tr><td><code>Square</code></td><td>number</td><td>number</td><td>Alias for <code>X</code>.</td></tr>
* <tr><td><code>Triangle</code></td><td>number</td><td>number</td><td>Alias for <code>Y</code>.</td></tr>
* <tr><td><code>Left</code></td><td>number</td><td>number</td><td>Alias for <code>DL</code>.</td></tr>
* <tr><td><code>Right</code></td><td>number</td><td>number</td><td>Alias for <code>DR</code>.</td></tr>
* <tr><td><code>Up</code></td><td>number</td><td>number</td><td>Alias for <code>DU</code>.</td></tr>
* <tr><td><code>Down</code></td><td>number</td><td>number</td><td>Alias for <code>DD</code>.</td></tr>
* <tr><td><code>Select</code></td><td>number</td><td>number</td><td>Alias for <code>Back</code>.</td></tr>
* <tr><td><code>L1</code></td><td>number</td><td>number</td><td>Alias for <code>LB</code>.</td></tr>
* <tr><td><code>R1</code></td><td>number</td><td>number</td><td>Alias for <code>RB</code>.</td></tr>
* <tr><td><code>L3</code></td><td>number</td><td>number</td><td>Alias for <code>LS</code>.</td></tr>
* <tr><td><code>R3</code></td><td>number</td><td>number</td><td>Alias for <code>RS</code>.</td></tr>
* <tr><td><code>L2</code></td><td>number</td><td>number</td><td>Alias for <code>LT</code>.</td></tr>
* <tr><td><code>R2</code></td><td>number</td><td>number</td><td>Alias for <code>RT</code>.</td></tr>
*
* <tr><td colspan="4"><strong>Finger Abstractions</strong></td></tr>
* <tr><td><code>LeftPrimaryThumb</code></td><td>number</td><td>number</td><td>Left primary thumb button pressed.</td></tr>
* <tr><td><code>LeftSecondaryThumb</code></td><td>number</td><td>number</td><td>Left secondary thumb button pressed.
* </td></tr>
* <tr><td><code>RightPrimaryThumb</code></td><td>number</td><td>number</td><td>Right primary thumb button pressed.
* </td></tr>
* <tr><td><code>RightSecondaryThumb</code></td><td>number</td><td>number</td><td>Right secondary thumb button pressed.
* </td></tr>
* <tr><td><code>LeftPrimaryThumbTouch</code></td><td>number</td><td>number</td><td>Left thumb touching primary thumb
* button.</td></tr>
* <tr><td><code>LeftSecondaryThumbTouch</code></td><td>number</td><td>number</td><td>Left thumb touching secondary thumb
* button.</td></tr>
* <tr><td><code>LeftThumbUp</code></td><td>number</td><td>number</td><td>Left thumb not touching primary or secondary
* thumb buttons.</td></tr>
* <tr><td><code>RightPrimaryThumbTouch</code></td><td>number</td><td>number</td><td>Right thumb touching primary thumb
* button.</td></tr>
* <tr><td><code>RightSecondaryThumbTouch</code></td><td>number</td><td>number</td><td>Right thumb touching secondary thumb
* button.</td></tr>
* <tr><td><code>RightThumbUp</code></td><td>number</td><td>number</td><td>Right thumb not touching primary or secondary
* thumb buttons.</td></tr>
* <tr><td><code>LeftPrimaryIndex</code></td><td>number</td><td>number</td><td>Left primary index control pressed.
* <strong>To Do:</strong> <em>Implement this for current controllers.</em></td></tr>
* <tr><td><code>LeftSecondaryIndex</code></td><td>number</td><td>number</td><td>Left secondary index control pressed.
* </td></tr>
* <tr><td><code>RightPrimaryIndex</code></td><td>number</td><td>number</td><td>Right primary index control pressed.
* <strong>To Do:</strong> <em>Implement this for current controllers.</em></td></tr>
* <tr><td><code>RightSecondaryIndex</code></td><td>number</td><td>number</td><td>Right secondary index control pressed.
* </td></tr>
* <tr><td><code>LeftPrimaryIndexTouch</code></td><td>number</td><td>number</td><td>Left index finger is touching primary
* index finger control.</td></tr>
* <tr><td><code>LeftSecondaryIndexTouch</code></td><td>number</td><td>number</td><td>Left index finger is touching
* secondary index finger control.</td></tr>
* <tr><td><code>LeftIndexPoint</code></td><td>number</td><td>number</td><td>Left index finger is pointing, not touching
* primary or secondary index finger controls.</td></tr>
* <tr><td><code>RightPrimaryIndexTouch</code></td><td>number</td><td>number</td><td>Right index finger is touching primary
* index finger control.</td></tr>
* <tr><td><code>RightSecondaryIndexTouch</code></td><td>number</td><td>number</td><td>Right index finger is touching
* secondary index finger control.</td></tr>
* <tr><td><code>RightIndexPoint</code></td><td>number</td><td>number</td><td>Right index finger is pointing, not touching
* primary or secondary index finger controls.</td></tr>
*
* <tr><td colspan="4"><strong>Avatar Skeleton</strong></td></tr>
* <tr><td><code>Hips</code></td><td>number</td><td>{@link Pose}</td><td>Hips pose.</td></tr>
* <tr><td><code>Spine2</code></td><td>number</td><td>{@link Pose}</td><td>Spine2 pose.</td></tr>
* <tr><td><code>Head</code></td><td>number</td><td>{@link Pose}</td><td>Head pose.</td></tr>
* <tr><td><code>LeftArm</code></td><td>number</td><td>{@link Pose}</td><td>Left arm pose.</td></tr>
* <tr><td><code>RightArm</code></td><td>number</td><td>{@link Pose}</td><td>Right arm pose</td></tr>
* <tr><td><code>LeftHand</code></td><td>number</td><td>{@link Pose}</td><td>Left hand pose.</td></tr>
* <tr><td><code>LeftHandThumb1</code></td><td>number</td><td>{@link Pose}</td><td>Left thumb 1 finger joint pose.</td></tr>
* <tr><td><code>LeftHandThumb2</code></td><td>number</td><td>{@link Pose}</td><td>Left thumb 2 finger joint pose.</td></tr>
* <tr><td><code>LeftHandThumb3</code></td><td>number</td><td>{@link Pose}</td><td>Left thumb 3 finger joint pose.</td></tr>
* <tr><td><code>LeftHandThumb4</code></td><td>number</td><td>{@link Pose}</td><td>Left thumb 4 finger joint pose.</td></tr>
* <tr><td><code>LeftHandIndex1</code></td><td>number</td><td>{@link Pose}</td><td>Left index 1 finger joint pose.</td></tr>
* <tr><td><code>LeftHandIndex2</code></td><td>number</td><td>{@link Pose}</td><td>Left index 2 finger joint pose.</td></tr>
* <tr><td><code>LeftHandIndex3</code></td><td>number</td><td>{@link Pose}</td><td>Left index 3 finger joint pose.</td></tr>
* <tr><td><code>LeftHandIndex4</code></td><td>number</td><td>{@link Pose}</td><td>Left index 4 finger joint pose.</td></tr>
* <tr><td><code>LeftHandMiddle1</code></td><td>number</td><td>{@link Pose}</td><td>Left middle 1 finger joint pose.
* </td></tr>
* <tr><td><code>LeftHandMiddle2</code></td><td>number</td><td>{@link Pose}</td><td>Left middle 2 finger joint pose.
* </td></tr>
* <tr><td><code>LeftHandMiddle3</code></td><td>number</td><td>{@link Pose}</td><td>Left middle 3 finger joint pose.
* </td></tr>
* <tr><td><code>LeftHandMiddle4</code></td><td>number</td><td>{@link Pose}</td><td>Left middle 4 finger joint pose.
* </td></tr>
* <tr><td><code>LeftHandRing1</code></td><td>number</td><td>{@link Pose}</td><td>Left ring 1 finger joint pose.</td></tr>
* <tr><td><code>LeftHandRing2</code></td><td>number</td><td>{@link Pose}</td><td>Left ring 2 finger joint pose.</td></tr>
* <tr><td><code>LeftHandRing3</code></td><td>number</td><td>{@link Pose}</td><td>Left ring 3 finger joint pose.</td></tr>
* <tr><td><code>LeftHandRing4</code></td><td>number</td><td>{@link Pose}</td><td>Left ring 4 finger joint pose.</td></tr>
* <tr><td><code>LeftHandPinky1</code></td><td>number</td><td>{@link Pose}</td><td>Left pinky 1 finger joint pose.</td></tr>
* <tr><td><code>LeftHandPinky2</code></td><td>number</td><td>{@link Pose}</td><td>Left pinky 2 finger joint pose.</td></tr>
* <tr><td><code>LeftHandPinky3</code></td><td>number</td><td>{@link Pose}</td><td>Left pinky 3 finger joint pose.</td></tr>
* <tr><td><code>LeftHandPinky4</code></td><td>number</td><td>{@link Pose}</td><td>Left pinky 4 finger joint pose.</td></tr>
* <tr><td><code>RightHand</code></td><td>number</td><td>{@link Pose}</td><td>Right hand pose.</td></tr>
* <tr><td><code>RightHandThumb1</code></td><td>number</td><td>{@link Pose}</td><td>Right thumb 1 finger joint pose.
* </td></tr>
* <tr><td><code>RightHandThumb2</code></td><td>number</td><td>{@link Pose}</td><td>Right thumb 2 finger joint pose.
* </td></tr>
* <tr><td><code>RightHandThumb3</code></td><td>number</td><td>{@link Pose}</td><td>Right thumb 3 finger joint pose.
* </td></tr>
* <tr><td><code>RightHandThumb4</code></td><td>number</td><td>{@link Pose}</td><td>Right thumb 4 finger joint pose.
* </td></tr>
* <tr><td><code>RightHandIndex1</code></td><td>number</td><td>{@link Pose}</td><td>Right index 1 finger joint pose.
* </td></tr>
* <tr><td><code>RightHandIndex2</code></td><td>number</td><td>{@link Pose}</td><td>Right index 2 finger joint pose.
* </td></tr>
* <tr><td><code>RightHandIndex3</code></td><td>number</td><td>{@link Pose}</td><td>Right index 3 finger joint pose.
* </td></tr>
* <tr><td><code>RightHandIndex4</code></td><td>number</td><td>{@link Pose}</td><td>Right index 4 finger joint pose.
* </td></tr>
* <tr><td><code>RightHandMiddle1</code></td><td>number</td><td>{@link Pose}</td><td>Right middle 1 finger joint pose.
* </td></tr>
* <tr><td><code>RightHandMiddle2</code></td><td>number</td><td>{@link Pose}</td><td>Right middle 2 finger joint pose.
* </td></tr>
* <tr><td><code>RightHandMiddle3</code></td><td>number</td><td>{@link Pose}</td><td>Right middle 3 finger joint pose.
* </td></tr>
* <tr><td><code>RightHandMiddle4</code></td><td>number</td><td>{@link Pose}</td><td>Right middle 4 finger joint pose.
* </td></tr>
* <tr><td><code>RightHandRing1</code></td><td>number</td><td>{@link Pose}</td><td>Right ring 1 finger joint pose.</td></tr>
* <tr><td><code>RightHandRing2</code></td><td>number</td><td>{@link Pose}</td><td>Right ring 2 finger joint pose.</td></tr>
* <tr><td><code>RightHandRing3</code></td><td>number</td><td>{@link Pose}</td><td>Right ring 3 finger joint pose.</td></tr>
* <tr><td><code>RightHandRing4</code></td><td>number</td><td>{@link Pose}</td><td>Right ring 4 finger joint pose.</td></tr>
* <tr><td><code>RightHandPinky1</code></td><td>number</td><td>{@link Pose}</td><td>Right pinky 1 finger joint pose.
* </td></tr>
* <tr><td><code>RightHandPinky2</code></td><td>number</td><td>{@link Pose}</td><td>Right pinky 2 finger joint pose.
* </td></tr>
* <tr><td><code>RightHandPinky3</code></td><td>number</td><td>{@link Pose}</td><td>Right pinky 3 finger joint pose.
* </td></tr>
* <tr><td><code>RightHandPinky4</code></td><td>number</td><td>{@link Pose}</td><td>Right pinky 4 finger joint pose.
* </td></tr>
* <tr><td><code>LeftFoot</code></td><td>number</td><td>{@link Pose}</td><td>Left foot pose.</td></tr>
* <tr><td><code>RightFoot</code></td><td>number</td><td>{@link Pose}</td><td>Right foot pose.</td></tr>
*
* <tr><td colspan="4"><strong>Trackers</strong></td></tr>
* <tr><td><code>TrackedObject00</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 0 pose.</td></tr>
* <tr><td><code>TrackedObject01</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 1 pose.</td></tr>
* <tr><td><code>TrackedObject02</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 2 pose.</td></tr>
* <tr><td><code>TrackedObject03</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 3 pose.</td></tr>
* <tr><td><code>TrackedObject04</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 4 pose.</td></tr>
* <tr><td><code>TrackedObject05</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 5 pose.</td></tr>
* <tr><td><code>TrackedObject06</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 6 pose.</td></tr>
* <tr><td><code>TrackedObject07</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 7 pose.</td></tr>
* <tr><td><code>TrackedObject08</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 8 pose.</td></tr>
* <tr><td><code>TrackedObject09</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 9 pose.</td></tr>
* <tr><td><code>TrackedObject10</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 10 pose.</td></tr>
* <tr><td><code>TrackedObject11</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 11 pose.</td></tr>
* <tr><td><code>TrackedObject12</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 12 pose.</td></tr>
* <tr><td><code>TrackedObject13</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 13 pose.</td></tr>
* <tr><td><code>TrackedObject14</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 14 pose.</td></tr>
* <tr><td><code>TrackedObject15</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 15 pose.</td></tr>
*
* </tbody>
* </table>
* @typedef Controller.Standard
*/
Input::NamedVector StandardController::getAvailableInputs() const {
static Input::NamedVector availableInputs {
// Buttons

View file

@ -24,6 +24,101 @@ namespace controller {
class ScriptingInterface;
class UserInputMapper;
/**jsdoc
* <p>A {@link Controller} mapping object that can contain a set of routes that map:</p>
* <ul>
* <li>{@link Controller.Standard} outputs to {@link Controller.Actions} actions or script functions.</li>
* <li>{@link Controller.Hardware} outputs to {@link Controller.Standard} outputs, {@link Controller.Actions} actions, or
* script functions.</li>
* </ul>
*
* <p>Create by one of the following methods:</p>
* <ul>
* <li>Use {@link Controller.newMapping} to create the mapping object, add routes using {@link MappingObject#from|from} or
* {@link MappingObject#makeAxis|makeAxis}, and map the routes to actions or functions using {@link RouteObject}
* methods.</li>
* <li>Use {@link Controller.parseMapping} or {@link Controller.loadMapping} to load a {@link Controller.MappingJSON}.</li>
* </ul>
* <p>Enable the mapping using {@link MappingObject#enable|enable} or {@link Controller.enableMapping} for it to take effect.
*
* <p>Mappings and their routes are applied according to the following rules:</p>
* <ul>
* <li>One read per output: after a controller output has been read, it can't be read again. Exception: You can use
* {@link RouteObject#peek} to read a value without marking that output as having been read.</li>
* <li>Existing mapping routes take precedence over new mapping routes: within a mapping, if a route is added for a control
* output that already has a route the new route is ignored.</li>
* <li>New mappings override previous mappings: each output is processed using the route in the most recently enabled
* mapping that contains that output.</li>
* </p>
*
* @class MappingObject
*/
/**jsdoc
* A {@link MappingObject} can be specified in JSON format. A simple example is provided below. Full examples &mdash; the
* default mappings provided in Interface &mdash; can be found at
* <a href="https://github.com/highfidelity/hifi/tree/master/interface/resources/controllers">
* https://github.com/highfidelity/hifi/tree/master/interface/resources/controllers</a>.
* @typedef {object} Controller.MappingJSON
* @property {string} name - The name of the mapping.
* @property {Controller.MappingJSONRoute[]} channels - An array of routes.
* @example <caption>A simple mapping JSON that makes the right trigger move your avatar up after a dead zone.</caption>
* {
* "name": "com.highfidelity.controllers.example.jsonMapping",
* "channels": [
* {
* "from": "Standard.RT",
* "filters": { "type": "deadZone", "min": 0.05 },
* "to": "Actions.TranslateY"
* }
* ]
* }
*/
/**jsdoc
* A route in a {@link Controller.MappingJSON}.
* @typedef {object} Controller.MappingJSONRoute
* @property {string|Controller.MappingJSONAxis} from - The name of a {@link Controller.Hardware} property name or an axis
* made from them. If a property name, the leading <code>"Controller.Hardware."</code> can be omitted.
* @property {boolean} [peek=false] - If <codd>true</code> then peeking is enabled per {@link RouteObject#peek}.
* @property {boolean} [debug=false] - If <code>true</code> then debug is enabled per {@link RouteObject#debug}.
* @property {string|string[]} [when=[]] - One or more numeric {@link Controller.Hardware} property names which are evaluated
* as booleans and ANDed together. Prepend with a <code>!</code> to use the logical NOT of the property value. The leading
* <code>"Controller.Hardware."</code> can be omitted from the property names.
* @property {Controller.MappingJSONFilter|Controller.MappingJSONFilter[]} [filters=[]] - One or more filters in the route.
* @property {string} to - The name of a {@link Controller.Actions} or {@link Controller.Standard} property. The leading
* <code>"Controller."</code> can be omitted.
*/
/**jsdoc
* An axis pair in a {@link Controller.MappingJSONRoute}.
* @typedef {object} Controller.MappingJSONAxis
* @property {string[][]} makeAxis - A two-member array of single-member arrays of {@link Controller.Hardware} property names.
* The leading <code>"Controller.Hardware."</code> can be omitted from the property names.
* @example <caption>An axis using the keyboard's left and right keys.</caption>
* { "makeAxis" : [
* ["Keyboard.Left"],
* ["Keyboard.Right"]
* ]
* }
*/
/**jsdoc
* A filter in a {@link Controller.MappingJSONRoute}.
* @typedef {object} Controller.MappingJSONFilter
* @property {string} type - The name of the filter, being the name of the one of the {@link RouteObject}'s filter methods.
* @property {string} [?] - If the filter method has a first parameter, the property name is the name of that parameter and the
* property value is the value to use.
* @property {string} [?] - If the filter method has a second parameter, the property name is the name of that parameter and
* the property value is the value to use.
* @example <caption>A hysteresis filter.</caption>
* {
* "type": "hysteresis",
* "min": 0.85,
* "max": 0.9
* }
*/
// TODO migrate functionality to a MappingBuilder class and make the proxy defer to that
// (for easier use in both C++ and JS)
class MappingBuilderProxy : public QObject {
@ -32,13 +127,76 @@ public:
MappingBuilderProxy(UserInputMapper& parent, Mapping::Pointer mapping)
: _parent(parent), _mapping(mapping) { }
/**jsdoc
* Create a new {@link RouteObject} from a controller output, ready to be mapped to a standard control, action, or
* function.<br />
* This is a QML-specific version of {@link MappingObject#from|from}: use this version in QML files.
* @function MappingObject#fromQml
* @param {Controller.Standard|Controller.Hardware|function} source - The controller output or function that is the source
* of the route data. If a function, it must return a number or a {@link Pose} value as the route data.
* @returns {RouteObject} A route ready for mapping to an action or function using {@link RouteObject} methods.
*/
Q_INVOKABLE QObject* fromQml(const QJSValue& source);
/**jsdoc
* Create a new {@link RouteObject} from two numeric {@link Controller.Hardware} outputs, one applied in the negative
* direction and the other in the positive direction, ready to be mapped to a standard control, action, or function.<br />
* This is a QML-specific version of {@link MappingObject#makeAxis|makeAxis}: use this version in QML files.
* @function MappingObject#makeAxisQml
* @param {Controller.Hardware} source1 - The first, negative-direction controller output.
* @param {Controller.Hardware} source2 - The second, positive-direction controller output.
* @returns {RouteObject} A route ready for mapping to an action or function using {@link RouteObject} methods. The data
* value passed to the route is the combined value of <code>source2 - source1</code>.
*/
Q_INVOKABLE QObject* makeAxisQml(const QJSValue& source1, const QJSValue& source2);
/**jsdoc
* Create a new {@link RouteObject} from a controller output, ready to be mapped to a standard control, action, or
* function.
* @function MappingObject#from
* @param {Controller.Standard|Controller.Hardware|function} source - The controller output or function that is the source
* of the route data. If a function, it must return a number or a {@link Pose} value as the route data.
* @returns {RouteObject} A route ready for mapping to an action or function using {@link RouteObject} methods.
*/
Q_INVOKABLE QObject* from(const QScriptValue& source);
/**jsdoc
* Create a new {@link RouteObject} from two numeric {@link Controller.Hardware} outputs, one applied in the negative
* direction and the other in the positive direction, ready to be mapped to a standard control, action, or function.
* @function MappingObject#makeAxis
* @param {Controller.Hardware} source1 - The first, negative-direction controller output.
* @param {Controller.Hardware} source2 - The second, positive-direction controller output.
* @returns {RouteObject} A route ready for mapping to an action or function using {@link RouteObject} methods. The data
* value passed to the route is the combined value of <code>source2 - source1</code>.
* @example <caption>Make the Oculus Touch triggers move your avatar up and down.</caption>
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
* mapping
* .makeAxis(Controller.Hardware.OculusTouch.LT, Controller.Hardware.OculusTouch.RT)
* .to(Controller.Actions.Up);
* Controller.enableMapping(MAPPING_NAME);
*
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*/
Q_INVOKABLE QObject* makeAxis(const QScriptValue& source1, const QScriptValue& source2);
/**jsdoc
* Enable or disable the mapping. When enabled, the routes in the mapping take effect.<br />
* Synonymous with {@link Controller.enableMapping}.
* @function MappingObject#enable
* @param {boolean} enable=true - If <code>true</code> then the mapping is enabled, otherwise it is disabled.
* @returns {MappingObject} The mapping object, so that further routes can be added.
*/
Q_INVOKABLE QObject* enable(bool enable = true);
/**jsdoc
* Disable the mapping. When disabled, the routes in the mapping have no effect.<br />
* Synonymous with {@link Controller.disableMapping}.
* @function MappingObject#disable
* @returns {MappingObject} The mapping object, so that further routes can be added.
*/
Q_INVOKABLE QObject* disable() { return enable(false); }
protected:

View file

@ -25,6 +25,18 @@ namespace controller {
class ScriptingInterface;
/**jsdoc
* <p>A route in a {@link MappingObject} used by the {@link Controller} API.</p>
*
* <p>Create a route using {@link MappingObject} methods and apply this object's methods to process it, terminating with
* {@link RouteObject#to} to apply it to a <code>Standard</code> control, action, or script function.</p>
*
* <p>Some methods apply to routes with number data, some apply routes with {@link Pose} data, and some apply to both route
* types.<p>
*
* @class RouteObject
*/
// TODO migrate functionality to a RouteBuilder class and make the proxy defer to that
// (for easier use in both C++ and JS)
class RouteBuilderProxy : public QObject {
@ -33,27 +45,404 @@ class RouteBuilderProxy : public QObject {
RouteBuilderProxy(UserInputMapper& parent, Mapping::Pointer mapping, Route::Pointer route)
: _parent(parent), _mapping(mapping), _route(route) { }
/**jsdoc
* Terminate the route with a standard control, an action, or a script function. The output value from the route is
* sent to the specified destination.<br />
* This is a QML-specific version of {@link MappingObject#to|to}: use this version in QML files.
* @function RouteObject#toQml
* @param {Controller.Standard|Controller.Actions|function} destination - The standard control, action, or JavaScript
* function that the route output is mapped to. For a function, the parameter can be either the name of the function or
* an in-line function definition.
*/
Q_INVOKABLE void toQml(const QJSValue& destination);
/**jsdoc
* Process the route only if a condition is satisfied. The condition is evaluated before the route input is read, and
* the input is read only if the condition is <code>true</code>. Thus, if the condition is not met then subsequent
* routes using the same input are processed.<br />
* This is a QML-specific version of {@link MappingObject#to|to}: use this version in QML files.
* @function RouteObject#whenQml
* @param {condition|condition[]} expression - <p>A <code>condition</code> may be a:</p>
* <ul>
* <li>A boolean or numeric {@link Controller.Hardware} property, which is evaluated as a boolean.</li>
* <li><code>!</code> followed by a {@link Controller.Hardware} property, indicating the logical NOT should be
* used.</li>
* <li>A script function returning a boolean value. This can be either the name of the function or an in-line
* definition.</li>
* </ul>
* <p>If an array of conditions is provided, their values are ANDed together.</p>
* @returns {RouteObject} The <code>RouteObject</code> with the condition added.
*/
Q_INVOKABLE QObject* whenQml(const QJSValue& expression);
/**jsdoc
* Terminate the route with a standard control, an action, or a script function. The output value from the route is
* sent to the specified destination.
* @function RouteObject#to
* @param {Controller.Standard|Controller.Actions|function} destination - The standard control, action, or JavaScript
* function that the route output is mapped to. For a function, the parameter can be either the name of the function or
* an in-line function definition.
*
* @example <caption>Make the right trigger move your avatar up.</caption>
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
*
* mapping.from(Controller.Standard.RT).to(Controller.Actions.TranslateY);
* Controller.enableMapping(MAPPING_NAME);
*
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*
* @example <caption>Make the right trigger call a function.</caption>
* function onRightTrigger(value) {
* print("Trigger value: " + value);
* }
*
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
*
* mapping.from(Controller.Standard.RT).to(onRightTrigger);
* Controller.enableMapping(MAPPING_NAME);
*
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*/
Q_INVOKABLE void to(const QScriptValue& destination);
/**jsdoc
* Enable and disabling writing debug information for a route to the program log.
* @function RouteObject#debug
* @param {boolean} [enable=true] - If <code>true</code> then writing debug information is enabled for the route,
* otherwise it is disabled.
* @returns {RouteObject} The <code>RouteObject</code> with debug output enabled or disabled.
* @example <caption>Write debug information to the program log for a right trigger mapping.</caption>
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
*
* mapping.from(Controller.Standard.RT).debug().to(function (value) {
* print("Value: " + value);
* });
*
* // Information similar to the following is written each frame:
* [DEBUG] [hifi.controllers] Beginning mapping frame
* [DEBUG] [hifi.controllers] Processing device routes
* [DEBUG] [hifi.controllers] Processing standard routes
* [DEBUG] [hifi.controllers] Applying route ""
* [DEBUG] [hifi.controllers] Value was 5.96046e-07
* [DEBUG] [hifi.controllers] Filtered value was 5.96046e-07
*
* Controller.enableMapping(MAPPING_NAME);
*
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*/
Q_INVOKABLE QObject* debug(bool enable = true);
/**jsdoc
* Process the route without marking the controller output as having been read, so that other routes from the same
* controller output can also process.
* @function RouteObject#peek
* @param {boolean} [enable=true] - If <code>true</code> then the route is processed without marking the route's
* controller source as having been read.
* @returns {RouteObject} The <code>RouteObject</code> with the peek feature enabled.
*/
Q_INVOKABLE QObject* peek(bool enable = true);
/**jsdoc
* Process the route only if a condition is satisfied. The condition is evaluated before the route input is read, and
* the input is read only if the condition is <code>true</code>. Thus, if the condition is not met then subsequent
* routes using the same input are processed.
* @function RouteObject#when
* @param {condition|condition[]} expression - <p>A <code>condition</code> may be a:</p>
* <ul>
* <li>A numeric {@link Controller.Hardware} property, which is evaluated as a boolean.</li>
* <li><code>!</code> followed by a {@link Controller.Hardware} property to use the logical NOT of the property
* value.</li>
* <li>A script function returning a boolean value. This can be either the name of the function or an in-line
* definition.</li>
* </ul>
* <p>If an array of conditions is provided, their values are ANDed together.</p>
* @returns {RouteObject} The <code>RouteObject</code> with the condition added.
* @example <caption>Process the right trigger differently in HMD and desktop modes.</caption>
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
*
* // Processed only if in HMD mode.
* mapping.from(Controller.Standard.RT)
* .when(Controller.Hardware.Application.InHMD)
* .to(function () { print("Trigger pressed in HMD mode."); });
*
* // Processed only if previous route not processed.
* mapping.from(Controller.Standard.RT)
* .to(function () { print("Trigger pressed in desktop mode."); });
*
* Controller.enableMapping(MAPPING_NAME);
*
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*/
Q_INVOKABLE QObject* when(const QScriptValue& expression);
/**jsdoc
* Filter numeric route values to lie between two values; values outside this range are not passed on through the
* route.
* @function RouteObject#clamp
* @param {number} min - The minimum value to pass through.
* @param {number} max - The maximum value to pass through.
* @returns {RouteObject} The route object with the clamp filter added.
* @example <caption>Clamp right trigger values to between 0.3 and 0.7.</caption>
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
* mapping.from(Controller.Standard.RT).clamp(0.3, 0.7).to(function (value) {
* print("Value: " + value);
* });
* Controller.enableMapping(MAPPING_NAME);
*
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*/
Q_INVOKABLE QObject* clamp(float min, float max);
/**jsdoc
* Filter numeric route values such that they are rounded to <code>0</code> or <code>1</code> without output values
* flickering when the input value hovers around <code>0.5</code>. For example, this enables you to use an analog input
* as if it were a toggle.
* @function RouteObject#hysteresis
* @param {number} min - When the input value drops below this value the output value changes to <code>0</code>.
* @param {number} max - When the input value rises above this value the output value changes to <code>1</code>.
* @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
* @example <caption>Round the right joystick forward/back values to 0 or 1 with hysteresis.</caption>
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
* mapping.from(Controller.Standard.RY).peek().to(function (value) {
* print("Raw value: " + value); // 0.0 - 1.0.
* });
* mapping.from(Controller.Standard.RY).hysteresis(0.3, 0.7).to(function (value) {
* print("Hysteresis value: " + value); // 0 or 1.
* });
* Controller.enableMapping(MAPPING_NAME);
*
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*/
Q_INVOKABLE QObject* hysteresis(float min, float max);
/**jsdoc
* Filter numeric route values to send at a specified interval.
* @function RouteObject#pulse
* @param {number} interval - The interval between sending values, in seconds.
* @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
* @example <caption>Send right trigger values every half second.</caption>
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
* mapping.from(Controller.Standard.RT).pulse(0.5).to(function (value) {
* print("Value: " + value);
* });
* Controller.enableMapping(MAPPING_NAME);
*
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*/
Q_INVOKABLE QObject* pulse(float interval);
/**jsdoc
* Filter numeric and {@link Pose} route values to be scaled by a constant amount.
* @function RouteObject#scale
* @param {number} multiplier - The scale to multiply the value by.
* @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
* @example <caption>Scale the value of the right joystick forward/back values by 10.</caption>
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
* mapping.from(Controller.Standard.LY).to(function (value) {
* print("L value: " + value); // -1.0 to 1.0 values.
* });
* mapping.from(Controller.Standard.RY).scale(10.0).to(function (value) {
* print("R value: " + value); // -10.0 to -10.0 values.
* });
* Controller.enableMapping(MAPPING_NAME);
*
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*/
Q_INVOKABLE QObject* scale(float multiplier);
/**jsdoc
* Filter numeric and {@link Pose} route values to have the opposite sign, e.g., <code>0.5</code> is changed to
* <code>-0.5</code>.
* @function RouteObject#invert
* @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
* @example <caption>Invert the value of the right joystick forward/back values.</caption>
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
* mapping.from(Controller.Standard.LY).to(function (value) {
* print("L value: " + value); // -1.0 to 1.0 values, forward to back.
* });
* mapping.from(Controller.Standard.RY).invert().to(function (value) {
* print("R value: " + value); // 1.0 to -1.0 values, forward to back.
* });
* Controller.enableMapping(MAPPING_NAME);
*
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*/
Q_INVOKABLE QObject* invert();
/**jsdoc
* Filter numeric route values such that they're sent only when the input value is outside a dead-zone. When the input
* passes the dead-zone value, output is sent starting at <code>0.0</code> and catching up with the input value. As the
* input returns toward the dead-zone value, output values reduce to <code>0.0</code> at the dead-zone value.
* @function RouteObject#deadZone
* @param {number} min - The minimum input value at which to start sending output. For negative input values, the
* negative of this value is used.
* @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
* @example <caption>Apply a dead-zone to the right joystick forward/back values.</caption>
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
* mapping.from(Controller.Standard.RY).deadZone(0.2).to(function (value) {
* print("Value: " + value); // 0.0 - 1.0 values once outside the dead-zone.
* });
* Controller.enableMapping(MAPPING_NAME);
*
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*/
Q_INVOKABLE QObject* deadZone(float min);
/**jsdoc
* Filter numeric route values such that they are rounded to <code>-1</code>, <code>0</code>, or <code>1</code>.
* For example, this enables you to use an analog input as if it were a toggle or, in the case of a bidirectional axis,
* a tri-state switch.
* @function RouteObject#constrainToInteger
* @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
* @example <caption>Round the right joystick forward/back values to <code>-1</code>, <code>0</code>, or
* <code>1</code>.</caption>
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
* mapping.from(Controller.Standard.RY).constrainToInteger().to(function (value) {
* print("Value: " + value); // -1, 0, or 1
* });
* Controller.enableMapping(MAPPING_NAME);
*
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*/
Q_INVOKABLE QObject* constrainToInteger();
/**jsdoc
* Filter numeric route values such that they are rounded to <code>0</code> or <code>1</code>. For example, this
* enables you to use an analog input as if it were a toggle.
* @function RouteObject#constrainToPositiveInteger
* @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
* @example <caption>Round the right joystick forward/back values to <code>0</code> or <code>1</code>.</caption>
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
* mapping.from(Controller.Standard.RY).constrainToPositiveInteger().to(function (value) {
* print("Value: " + value); // 0, or 1
* });
* Controller.enableMapping(MAPPING_NAME);
*
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*/
Q_INVOKABLE QObject* constrainToPositiveInteger();
/**jsdoc
* Filter {@link Pose} route values to have a pre-translation applied.
* @function RouteObject#translate
* @param {Vec3} translate - The pre-translation to add to the pose.
* @returns {RouteObject} The <code>RouteObject</code> with the pre-translation applied.
*/
// No JSDoc example because filter not currently used.
Q_INVOKABLE QObject* translate(glm::vec3 translate);
/**jsdoc
* Filter {@link Pose} route values to have a pre-transform applied.
* @function RouteObject#transform
* @param {Mat4} transform - The pre-transform to apply.
* @returns {RouteObject} The <code>RouteObject</code> with the pre-transform applied.
*/
// No JSDoc example because filter not currently used.
Q_INVOKABLE QObject* transform(glm::mat4 transform);
/**jsdoc
* Filter {@link Pose} route values to have a post-transform applied.
* @function RouteObject#postTransform
* @param {Mat4} transform - The post-transform to apply.
* @returns {RouteObject} The <code>RouteObject</code> with the post-transform applied.
*/
// No JSDoc example because filter not currently used.
Q_INVOKABLE QObject* postTransform(glm::mat4 transform);
/**jsdoc
* Filter {@link Pose} route values to have a pre-rotation applied.
* @function RouteObject#rotate
* @param {Quat} rotation - The pre-rotation to add to the pose.
* @returns {RouteObject} The <code>RouteObject</code> with the pre-rotation applied.
*/
// No JSDoc example because filter not currently used.
Q_INVOKABLE QObject* rotate(glm::quat rotation);
/**jsdoc
* Filter {@link Pose} route values to be smoothed by a low velocity filter. The filter's rotation and translation
* values are calculated as: <code>(1 - f) * currentValue + f * previousValue</code> where
* <code>f = currentVelocity / filterConstant</code>. At low velocities, the filter value is largely the previous
* value; at high velocities the value is wholly the current controller value.
* @function RouteObject#lowVelocity
* @param {number} rotationConstant - The rotational velocity, in rad/s, at which the filter value is wholly the latest
* controller value.
* @param {number} translationConstant - The linear velocity, in m/s, at which the filter value is wholly the latest
* controller value.
* @returns {RouteObject} The <code>RouteObject</code> smoothed by low velocity filtering.
*/
// No JSDoc example because filter not currently used.
Q_INVOKABLE QObject* lowVelocity(float rotationConstant, float translationConstant);
/**jsdoc
* Filter {@link Pose} route values to be smoothed by an exponential decay filter. The filter's rotation and
* translation values are calculated as: <code>filterConstant * currentValue + (1 - filterConstant) *
* previousValue</code>. Values near 1 are less smooth with lower latency; values near 0 are more smooth with higher
* latency.
* @function RouteObject#exponentialSmoothing
* @param {number} rotationConstant - Rotation filter constant, <code>0.0&ndash;1.0</code>.
* @param {number} translationConstant - Translation filter constant, <code>0.0&ndash;1.0</code>.
* @returns {RouteObject} The <code>RouteObject</code> smoothed by an exponential filter.
*/
// No JSDoc example because filter used only in Vive.json.
Q_INVOKABLE QObject* exponentialSmoothing(float rotationConstant, float translationConstant);
/**jsdoc
* Filter numeric route values such that a value of <code>0.0</code> is changed to <code>1.0</code>, and other values
* are changed to <code>0.0</code>.
* @function RouteObject#logicalNot
* @returns {RouteObject} The <code>RouteObject</code> with the filter applied.
* @example <caption>Logical NOT of LSTouch value.</caption>
* var MAPPING_NAME = "com.highfidelity.controllers.example.newMapping";
* var mapping = Controller.newMapping(MAPPING_NAME);
*
* mapping.from(Controller.Standard.RSTouch).peek().to(function (value) {
* print("RSTouch: " + value);
* });
* mapping.from(Controller.Standard.RSTouch).logicalNot().to(function (value) {
* print("localNot of RSTouch: " + value);
* });
* Controller.enableMapping(MAPPING_NAME);
*
* Script.scriptEnding.connect(function () {
* Controller.disableMapping(MAPPING_NAME);
* });
*/
Q_INVOKABLE QObject* logicalNot();
private:

View file

@ -12,6 +12,7 @@
#include <GLMHelpers.h>
// These properties have JSDoc documentation in HMDScriptingInterface.h.
class AbstractHMDScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(bool active READ isHMDMode)
@ -30,7 +31,27 @@ public:
bool isHMDMode() const;
signals:
/**jsdoc
* Triggered when the <code>HMD.ipdScale</code> property value changes.
* @function HMD.IPDScaleChanged
* @returns {Signal}
*/
void IPDScaleChanged();
/**jsdoc
* Triggered when Interface's display mode changes and when the user puts on or takes off their HMD.
* @function HMD.displayModeChanged
* @param {boolean} isHMDMode - <code>true</code> if the display mode is HMD, otherwise <code>false</code>. This is the
* same value as provided by <code>HMD.active</code>.
* @returns {Signal}
* @example <caption>Report when the display mode changes.</caption>
* HMD.displayModeChanged.connect(function (isHMDMode) {
* print("Display mode changed");
* print("isHMD = " + isHMDMode);
* print("HMD.active = " + HMD.active);
* print("HMD.mounted = " + HMD.mounted);
* });
*/
void displayModeChanged(bool isHMDMode);
private:

View file

@ -56,10 +56,8 @@ glm::mat4 StereoDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& basePr
static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate";
std::vector<QAction*> _screenActions;
bool StereoDisplayPlugin::internalActivate() {
auto screens = qApp->screens();
_screenActions.resize(screens.size());
for (int i = 0; i < screens.size(); ++i) {
auto screen = screens.at(i);
QString name = QString("Screen %1: %2").arg(i + 1).arg(screen->name());
@ -67,9 +65,9 @@ bool StereoDisplayPlugin::internalActivate() {
if (screen == qApp->primaryScreen()) {
checked = true;
}
auto action = _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), name,
[this](bool clicked) { updateScreen(); }, true, checked, "Screens");
_screenActions[i] = action;
const uint32_t screenIndex = i;
_container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), name,
[=](bool clicked) { updateScreen(screenIndex); }, true, checked, "Screens");
}
_container->removeMenu(FRAMERATE);
@ -80,18 +78,12 @@ bool StereoDisplayPlugin::internalActivate() {
return Parent::internalActivate();
}
void StereoDisplayPlugin::updateScreen() {
for (uint32_t i = 0; i < _screenActions.size(); ++i) {
if (_screenActions[i]->isChecked()) {
_screen = qApp->screens().at(i);
_container->setFullscreen(_screen);
break;
}
}
void StereoDisplayPlugin::updateScreen(uint32_t i) {
_screen = qApp->screens().at(i);
_container->setFullscreen(_screen);
}
void StereoDisplayPlugin::internalDeactivate() {
_screenActions.clear();
_container->unsetFullscreen();
Parent::internalDeactivate();
}

View file

@ -31,7 +31,7 @@ public:
protected:
virtual bool internalActivate() override;
virtual void internalDeactivate() override;
void updateScreen();
void updateScreen(uint32_t i);
float _ipd{ 0.064f };
QScreen* _screen;

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