mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merge branch 'master' into 21168
This commit is contained in:
commit
784009a9fa
52 changed files with 1385 additions and 794 deletions
|
@ -40,6 +40,7 @@ module.exports = {
|
|||
"Settings": false,
|
||||
"SoundCache": false,
|
||||
"Stats": false,
|
||||
"Tablet": false,
|
||||
"TextureCache": false,
|
||||
"Toolbars": false,
|
||||
"Uuid": false,
|
||||
|
@ -61,7 +62,7 @@ module.exports = {
|
|||
"eqeqeq": ["error", "always"],
|
||||
"indent": ["error", 4, { "SwitchCase": 1 }],
|
||||
"keyword-spacing": ["error", { "before": true, "after": true }],
|
||||
"max-len": ["error", 128, 4],
|
||||
"max-len": ["error", 192, 4],
|
||||
"new-cap": ["error"],
|
||||
"no-floating-decimal": ["error"],
|
||||
//"no-magic-numbers": ["error", { "ignore": [0, 1], "ignoreArrayIndexes": true }],
|
||||
|
|
|
@ -61,7 +61,7 @@ void AudioMixerClientData::processPackets() {
|
|||
_packetQueue.node.clear();
|
||||
|
||||
while (!_packetQueue.empty()) {
|
||||
auto& packet = _packetQueue.back();
|
||||
auto& packet = _packetQueue.front();
|
||||
|
||||
switch (packet->getType()) {
|
||||
case PacketType::MicrophoneAudioNoEcho:
|
||||
|
@ -548,9 +548,6 @@ AudioMixerClientData::IgnoreZone& AudioMixerClientData::IgnoreZoneMemo::get(unsi
|
|||
_zone = box;
|
||||
unsigned int oldFrame = _frame.exchange(frame, std::memory_order_release);
|
||||
Q_UNUSED(oldFrame);
|
||||
|
||||
// check the precondition
|
||||
assert(oldFrame == 0 || frame == (oldFrame + 1));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ public:
|
|||
AvatarAudioStream* getAvatarAudioStream();
|
||||
|
||||
// returns whether self (this data's node) should ignore node, memoized by frame
|
||||
// precondition: frame is monotonically increasing after first call
|
||||
// precondition: frame is increasing after first call (including overflow wrap)
|
||||
bool shouldIgnore(SharedNodePointer self, SharedNodePointer node, unsigned int frame);
|
||||
|
||||
// the following methods should be called from the AudioMixer assignment thread ONLY
|
||||
|
@ -131,7 +131,7 @@ private:
|
|||
|
||||
// returns an ignore zone, memoized by frame (lockless if the zone is already memoized)
|
||||
// preconditions:
|
||||
// - frame is monotonically increasing after first call
|
||||
// - frame is increasing after first call (including overflow wrap)
|
||||
// - there are no references left from calls to getIgnoreZone(frame - 1)
|
||||
IgnoreZone& get(unsigned int frame);
|
||||
|
||||
|
|
|
@ -410,7 +410,7 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
bool isInView = nodeData->otherAvatarInView(otherNodeBox);
|
||||
|
||||
// this throttles the extra data to only be sent every Nth message
|
||||
if (!isInView && getsOutOfView && (lastSeqToReceiver % EXTRA_AVATAR_DATA_FRAME_RATIO > 0)) {
|
||||
if (!isInView && !getsOutOfView && (lastSeqToReceiver % EXTRA_AVATAR_DATA_FRAME_RATIO > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -572,6 +572,7 @@ void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMess
|
|||
bool isRequesting;
|
||||
message->readPrimitive(&isRequesting);
|
||||
nodeData->setRequestsDomainListData(isRequesting);
|
||||
qDebug() << "node" << nodeData->getNodeID() << "requestsDomainListData" << isRequesting;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <SimpleEntitySimulation.h>
|
||||
#include <ResourceCache.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <EntityEditFilters.h>
|
||||
|
||||
#include "EntityServer.h"
|
||||
#include "EntityServerConsts.h"
|
||||
|
@ -71,6 +72,7 @@ OctreePointer EntityServer::createTree() {
|
|||
|
||||
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
|
||||
DependencyManager::set<AssignmentParentFinder>(tree);
|
||||
DependencyManager::set<EntityEditFilters>(std::static_pointer_cast<EntityTree>(tree));
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
@ -292,96 +294,26 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
|
|||
} else {
|
||||
tree->setEntityScriptSourceWhitelist("");
|
||||
}
|
||||
|
||||
if (readOptionString("entityEditFilter", settingsSectionObject, _entityEditFilter) && !_entityEditFilter.isEmpty()) {
|
||||
// Tell the tree that we have a filter, so that it doesn't accept edits until we have a filter function set up.
|
||||
std::static_pointer_cast<EntityTree>(_tree)->setHasEntityFilter(true);
|
||||
// Now fetch script from file asynchronously.
|
||||
QUrl scriptURL(_entityEditFilter);
|
||||
|
||||
// The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp)
|
||||
if (scriptURL.scheme().isEmpty() || (scriptURL.scheme() == URL_SCHEME_FILE)) {
|
||||
qWarning() << "Cannot load script from local filesystem, because assignment may be on a different computer.";
|
||||
scriptRequestFinished();
|
||||
return;
|
||||
}
|
||||
auto scriptRequest = ResourceManager::createResourceRequest(this, scriptURL);
|
||||
if (!scriptRequest) {
|
||||
qWarning() << "Could not create ResourceRequest for Agent script at" << scriptURL.toString();
|
||||
scriptRequestFinished();
|
||||
return;
|
||||
}
|
||||
// Agent.cpp sets up a timeout here, but that is unnecessary, as ResourceRequest has its own.
|
||||
connect(scriptRequest, &ResourceRequest::finished, this, &EntityServer::scriptRequestFinished);
|
||||
// FIXME: handle atp rquests setup here. See Agent::requestScript()
|
||||
qInfo() << "Requesting script at URL" << qPrintable(scriptRequest->getUrl().toString());
|
||||
scriptRequest->send();
|
||||
qDebug() << "script request sent";
|
||||
|
||||
auto entityEditFilters = DependencyManager::get<EntityEditFilters>();
|
||||
|
||||
QString filterURL;
|
||||
if (readOptionString("entityEditFilter", settingsSectionObject, filterURL) && !filterURL.isEmpty()) {
|
||||
// connect the filterAdded signal, and block edits until you hear back
|
||||
connect(entityEditFilters.data(), &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded);
|
||||
|
||||
entityEditFilters->addFilter(EntityItemID(), filterURL);
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from ScriptEngine.cpp. We should make this a class method for reuse.
|
||||
// Note: I've deliberately stopped short of using ScriptEngine instead of QScriptEngine, as that is out of project scope at this point.
|
||||
static bool hasCorrectSyntax(const QScriptProgram& program) {
|
||||
const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode());
|
||||
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
|
||||
const auto error = syntaxCheck.errorMessage();
|
||||
const auto line = QString::number(syntaxCheck.errorLineNumber());
|
||||
const auto column = QString::number(syntaxCheck.errorColumnNumber());
|
||||
const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column);
|
||||
qCritical() << qPrintable(message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName) {
|
||||
if (engine.hasUncaughtException()) {
|
||||
const auto backtrace = engine.uncaughtExceptionBacktrace();
|
||||
const auto exception = engine.uncaughtException().toString();
|
||||
const auto line = QString::number(engine.uncaughtExceptionLineNumber());
|
||||
engine.clearExceptions();
|
||||
|
||||
static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3";
|
||||
auto message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line);
|
||||
if (!backtrace.empty()) {
|
||||
static const auto lineSeparator = "\n ";
|
||||
message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator));
|
||||
void EntityServer::entityFilterAdded(EntityItemID id, bool success) {
|
||||
if (id.isInvalidID()) {
|
||||
if (success) {
|
||||
qDebug() << "entity edit filter for " << id << "added successfully";
|
||||
} else {
|
||||
qDebug() << "entity edit filter unsuccessfully added, all edits will be rejected for those without lock rights.";
|
||||
}
|
||||
qCritical() << qPrintable(message);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void EntityServer::scriptRequestFinished() {
|
||||
qDebug() << "script request completed";
|
||||
auto scriptRequest = qobject_cast<ResourceRequest*>(sender());
|
||||
const QString urlString = scriptRequest->getUrl().toString();
|
||||
if (scriptRequest && scriptRequest->getResult() == ResourceRequest::Success) {
|
||||
auto scriptContents = scriptRequest->getData();
|
||||
qInfo() << "Downloaded script:" << scriptContents;
|
||||
QScriptProgram program(scriptContents, urlString);
|
||||
if (hasCorrectSyntax(program)) {
|
||||
_entityEditFilterEngine.evaluate(scriptContents);
|
||||
if (!hadUncaughtExceptions(_entityEditFilterEngine, urlString)) {
|
||||
std::static_pointer_cast<EntityTree>(_tree)->initEntityEditFilterEngine(&_entityEditFilterEngine, [this]() {
|
||||
return hadUncaughtExceptions(_entityEditFilterEngine, _entityEditFilter);
|
||||
});
|
||||
scriptRequest->deleteLater();
|
||||
qDebug() << "script request filter processed";
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (scriptRequest) {
|
||||
qCritical() << "Failed to download script at" << urlString;
|
||||
// See HTTPResourceRequest::onRequestFinished for interpretation of codes. For example, a 404 is code 6 and 403 is 3. A timeout is 2. Go figure.
|
||||
qCritical() << "ResourceRequest error was" << scriptRequest->getResult();
|
||||
} else {
|
||||
qCritical() << "Failed to create script request.";
|
||||
}
|
||||
// Hard stop of the assignment client on failure. We don't want anyone to think they have a filter in place when they don't.
|
||||
// Alas, only indications will be the above logging with assignment client restarting repeatedly, and clients will not see any entities.
|
||||
qDebug() << "script request failure causing stop";
|
||||
stop();
|
||||
}
|
||||
|
||||
void EntityServer::nodeAdded(SharedNodePointer node) {
|
||||
|
|
|
@ -63,13 +63,13 @@ public slots:
|
|||
virtual void nodeAdded(SharedNodePointer node) override;
|
||||
virtual void nodeKilled(SharedNodePointer node) override;
|
||||
void pruneDeletedEntities();
|
||||
void entityFilterAdded(EntityItemID id, bool success);
|
||||
|
||||
protected:
|
||||
virtual OctreePointer createTree() override;
|
||||
|
||||
private slots:
|
||||
void handleEntityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void scriptRequestFinished();
|
||||
|
||||
private:
|
||||
SimpleEntitySimulationPointer _entitySimulation;
|
||||
|
@ -77,9 +77,6 @@ private:
|
|||
|
||||
QReadWriteLock _viewerSendingStatsLock;
|
||||
QMap<QUuid, QMap<QUuid, ViewerSendingStats>> _viewerSendingStats;
|
||||
|
||||
QString _entityEditFilter{};
|
||||
QScriptEngine _entityEditFilterEngine{};
|
||||
};
|
||||
|
||||
#endif // hifi_EntityServer_h
|
||||
|
|
|
@ -20,7 +20,7 @@ endif ()
|
|||
symlink_or_copy_directory_beside_target(${_SHOULD_SYMLINK_RESOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/resources" "resources")
|
||||
|
||||
# link the shared hifi libraries
|
||||
link_hifi_libraries(embedded-webserver networking shared)
|
||||
link_hifi_libraries(embedded-webserver networking shared avatars)
|
||||
|
||||
# find OpenSSL
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
|
|
@ -29,7 +29,8 @@
|
|||
#include <NLPacketList.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <SettingHandle.h>
|
||||
|
||||
#include <AvatarData.h> //for KillAvatarReason
|
||||
#include <FingerprintUtils.h>
|
||||
#include "DomainServerNodeData.h"
|
||||
|
||||
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json";
|
||||
|
@ -439,7 +440,7 @@ bool DomainServerSettingsManager::unpackPermissionsForKeypath(const QString& key
|
|||
foreach (QVariant permsHash, permissionsList) {
|
||||
NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) };
|
||||
QString id = perms->getID();
|
||||
|
||||
|
||||
NodePermissionsKey idKey = perms->getKey();
|
||||
|
||||
if (mapPointer->contains(idKey)) {
|
||||
|
@ -484,7 +485,7 @@ void DomainServerSettingsManager::unpackPermissions() {
|
|||
// make sure that this permission row is for a non-empty hardware
|
||||
if (perms->getKey().first.isEmpty()) {
|
||||
_macPermissions.remove(perms->getKey());
|
||||
|
||||
|
||||
// we removed a row from the MAC permissions, we'll need a re-pack
|
||||
needPack = true;
|
||||
}
|
||||
|
@ -555,7 +556,7 @@ void DomainServerSettingsManager::unpackPermissions() {
|
|||
QList<QHash<NodePermissionsKey, NodePermissionsPointer>> permissionsSets;
|
||||
permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get()
|
||||
<< _groupPermissions.get() << _groupForbiddens.get()
|
||||
<< _ipPermissions.get() << _macPermissions.get()
|
||||
<< _ipPermissions.get() << _macPermissions.get()
|
||||
<< _machineFingerprintPermissions.get();
|
||||
|
||||
foreach (auto permissionSet, permissionsSets) {
|
||||
|
@ -668,77 +669,84 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
|
|||
// ensure that the connect permission is clear
|
||||
userPermissions->clear(NodePermissions::Permission::canConnectToDomain);
|
||||
} else {
|
||||
// otherwise we apply the kick to the IP from active socket for this node and the MAC address
|
||||
|
||||
// remove connect permissions for the IP (falling back to the public socket if not yet active)
|
||||
auto& kickAddress = matchingNode->getActiveSocket()
|
||||
? matchingNode->getActiveSocket()->getAddress()
|
||||
: matchingNode->getPublicSocket().getAddress();
|
||||
|
||||
// probably isLoopback covers it, as whenever I try to ban an agent on same machine as the domain-server
|
||||
// it is always 127.0.0.1, but looking at the public and local addresses just to be sure
|
||||
// TODO: soon we will have feedback (in the form of a message to the client) after we kick. When we
|
||||
// do, we will have a success flag, and perhaps a reason for failure. For now, just don't do it.
|
||||
if (kickAddress == limitedNodeList->getPublicSockAddr().getAddress() ||
|
||||
kickAddress == limitedNodeList->getLocalSockAddr().getAddress() ||
|
||||
kickAddress.isLoopback() ) {
|
||||
qWarning() << "attempt to kick node running on same machine as domain server, ignoring KickRequest";
|
||||
return;
|
||||
}
|
||||
NodePermissionsKey ipAddressKey(kickAddress.toString(), QUuid());
|
||||
|
||||
// check if there were already permissions for the IP
|
||||
bool hadIPPermissions = hasPermissionsForIP(kickAddress);
|
||||
|
||||
// grab or create permissions for the given IP address
|
||||
auto ipPermissions = _ipPermissions[ipAddressKey];
|
||||
|
||||
if (!hadIPPermissions || ipPermissions->can(NodePermissions::Permission::canConnectToDomain)) {
|
||||
newPermissions = true;
|
||||
|
||||
ipPermissions->clear(NodePermissions::Permission::canConnectToDomain);
|
||||
}
|
||||
|
||||
// potentially remove connect permissions for the MAC address and machine fingerprint
|
||||
// remove connect permissions for the machine fingerprint
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
|
||||
if (nodeData) {
|
||||
// mac address first
|
||||
NodePermissionsKey macAddressKey(nodeData->getHardwareAddress(), 0);
|
||||
// get this machine's fingerprint
|
||||
auto domainServerFingerprint = FingerprintUtils::getMachineFingerprint();
|
||||
|
||||
bool hadMACPermissions = hasPermissionsForMAC(nodeData->getHardwareAddress());
|
||||
|
||||
auto macPermissions = _macPermissions[macAddressKey];
|
||||
|
||||
if (!hadMACPermissions || macPermissions->can(NodePermissions::Permission::canConnectToDomain)) {
|
||||
newPermissions = true;
|
||||
|
||||
macPermissions->clear(NodePermissions::Permission::canConnectToDomain);
|
||||
if (nodeData->getMachineFingerprint() == domainServerFingerprint) {
|
||||
qWarning() << "attempt to kick node running on same machine as domain server (by fingerprint), ignoring KickRequest";
|
||||
return;
|
||||
}
|
||||
|
||||
// now for machine fingerprint
|
||||
NodePermissionsKey machineFingerprintKey(nodeData->getMachineFingerprint().toString(), 0);
|
||||
|
||||
|
||||
// check if there were already permissions for the fingerprint
|
||||
bool hadFingerprintPermissions = hasPermissionsForMachineFingerprint(nodeData->getMachineFingerprint());
|
||||
|
||||
|
||||
// grab or create permissions for the given fingerprint
|
||||
auto fingerprintPermissions = _machineFingerprintPermissions[machineFingerprintKey];
|
||||
|
||||
|
||||
// write them
|
||||
if (!hadFingerprintPermissions || fingerprintPermissions->can(NodePermissions::Permission::canConnectToDomain)) {
|
||||
newPermissions = true;
|
||||
fingerprintPermissions->clear(NodePermissions::Permission::canConnectToDomain);
|
||||
}
|
||||
} else {
|
||||
// if no node data, all we can do is IP address
|
||||
auto& kickAddress = matchingNode->getActiveSocket()
|
||||
? matchingNode->getActiveSocket()->getAddress()
|
||||
: matchingNode->getPublicSocket().getAddress();
|
||||
|
||||
// probably isLoopback covers it, as whenever I try to ban an agent on same machine as the domain-server
|
||||
// it is always 127.0.0.1, but looking at the public and local addresses just to be sure
|
||||
// TODO: soon we will have feedback (in the form of a message to the client) after we kick. When we
|
||||
// do, we will have a success flag, and perhaps a reason for failure. For now, just don't do it.
|
||||
if (kickAddress == limitedNodeList->getPublicSockAddr().getAddress() ||
|
||||
kickAddress == limitedNodeList->getLocalSockAddr().getAddress() ||
|
||||
kickAddress.isLoopback() ) {
|
||||
qWarning() << "attempt to kick node running on same machine as domain server, ignoring KickRequest";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
NodePermissionsKey ipAddressKey(kickAddress.toString(), QUuid());
|
||||
|
||||
// check if there were already permissions for the IP
|
||||
bool hadIPPermissions = hasPermissionsForIP(kickAddress);
|
||||
|
||||
// grab or create permissions for the given IP address
|
||||
auto ipPermissions = _ipPermissions[ipAddressKey];
|
||||
|
||||
if (!hadIPPermissions || ipPermissions->can(NodePermissions::Permission::canConnectToDomain)) {
|
||||
newPermissions = true;
|
||||
|
||||
ipPermissions->clear(NodePermissions::Permission::canConnectToDomain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we are here, then we kicked them, so send the KillAvatar message
|
||||
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true);
|
||||
packet->write(nodeUUID.toRfc4122());
|
||||
packet->writePrimitive(KillAvatarReason::NoReason);
|
||||
|
||||
// send to avatar mixer, it sends the kill to everyone else
|
||||
limitedNodeList->broadcastToNodes(std::move(packet), NodeSet() << NodeType::AvatarMixer);
|
||||
|
||||
if (newPermissions) {
|
||||
qDebug() << "Removing connect permission for node" << uuidStringWithoutCurlyBraces(matchingNode->getUUID())
|
||||
<< "after kick request from" << uuidStringWithoutCurlyBraces(sendingNode->getUUID());
|
||||
|
||||
// we've changed permissions, time to store them to disk and emit our signal to say they have changed
|
||||
packPermissions();
|
||||
} else {
|
||||
emit updateNodePermissions();
|
||||
}
|
||||
|
||||
// we emit this no matter what -- though if this isn't a new permission probably 2 people are racing to kick and this
|
||||
// person lost the race. No matter, just be sure this is called as otherwise it takes like 10s for the person being banned
|
||||
// to go away
|
||||
emit updateNodePermissions();
|
||||
|
||||
} else {
|
||||
qWarning() << "Node kick request received for unknown node. Refusing to process.";
|
||||
}
|
||||
|
|
48
interface/resources/icons/tablet-icons/blank.svg
Normal file
48
interface/resources/icons/tablet-icons/blank.svg
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 50 50"
|
||||
style="enable-background:new 0 0 50 50;"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="blank.svg"><metadata
|
||||
id="metadata36"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs34" /><sodipodi:namedview
|
||||
pagecolor="#ff4900"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1149"
|
||||
inkscape:window-height="801"
|
||||
id="namedview32"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="1336"
|
||||
inkscape:window-y="519"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /></svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,81 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 50 200.1"
|
||||
style="enable-background:new 0 0 50 200.1;"
|
||||
xml:space="preserve"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="empty-toolbar-button.svg"><metadata
|
||||
id="metadata116"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs114" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1574"
|
||||
inkscape:window-height="1234"
|
||||
id="namedview112"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.717641"
|
||||
inkscape:cx="-13.634838"
|
||||
inkscape:cy="131.18797"
|
||||
inkscape:window-x="152"
|
||||
inkscape:window-y="117"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Layer_1" /><style
|
||||
type="text/css"
|
||||
id="style3">
|
||||
.st0{fill:#414042;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:#1E1E1E;}
|
||||
.st3{fill:#333333;}
|
||||
</style><g
|
||||
id="g6"
|
||||
style="fill:#ffffff;fill-opacity:1"><g
|
||||
id="g8"
|
||||
style="fill:#ffffff;fill-opacity:1"><path
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path10"
|
||||
d="m 50.1,146.1 c 0,2.2 -1.8,4 -4,4 l -42,0 c -2.2,0 -4,-1.8 -4,-4 l 0,-42 c 0,-2.2 1.8,-4 4,-4 l 42,0 c 2.2,0 4,1.8 4,4 l 0,42 z"
|
||||
class="st0" /></g></g><g
|
||||
id="g12"><g
|
||||
id="g14"><path
|
||||
style="fill:#414042"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path16"
|
||||
d="m 50,196.1 c 0,2.2 -1.8,4 -4,4 l -42,0 c -2.2,0 -4,-1.8 -4,-4 l 0,-42 c 0,-2.2 1.8,-4 4,-4 l 42,0 c 2.2,0 4,1.8 4,4 l 0,42 z"
|
||||
class="st0" /></g></g><g
|
||||
id="g18"
|
||||
style="fill:#f0f0f0;fill-opacity:1"><g
|
||||
id="g20"
|
||||
style="fill:#f0f0f0;fill-opacity:1"><path
|
||||
style="fill:#f0f0f0;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path22"
|
||||
d="m 50,46 c 0,2.2 -1.8,4 -4,4 L 4,50 C 1.8,50 0,48.2 0,46 L 0,4 C 0,1.8 1.8,0 4,0 l 42,0 c 2.2,0 4,1.8 4,4 l 0,42 z"
|
||||
class="st1" /></g></g><g
|
||||
id="g24"><path
|
||||
style="fill:#1e1e1e"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path26"
|
||||
d="m 50,96.1 c 0,2.2 -1.8,4 -4,4 l -42,0 c -2.2,0 -4,-1.8 -4,-4 l 0,-42 c 0,-2.2 1.8,-4 4,-4 l 42,0 c 2.2,0 4,1.8 4,4 l 0,42 z"
|
||||
class="st2" /></g></svg>
|
After Width: | Height: | Size: 3 KiB |
|
@ -48,7 +48,16 @@ OriginalDesktop.Desktop {
|
|||
// This used to create sysToolbar dynamically with a call to getToolbar() within onCompleted.
|
||||
// Beginning with QT 5.6, this stopped working, as anything added to toolbars too early got
|
||||
// wiped during startup.
|
||||
|
||||
Toolbar {
|
||||
id: sysToolbar;
|
||||
objectName: "com.highfidelity.interface.toolbar.system";
|
||||
anchors.horizontalCenter: settings.constrainToolbarToCenterX ? desktop.horizontalCenter : undefined;
|
||||
// Literal 50 is overwritten by settings from previous session, and sysToolbar.x comes from settings when not constrained.
|
||||
x: sysToolbar.x
|
||||
y: 50
|
||||
shown: false
|
||||
}
|
||||
|
||||
Settings {
|
||||
id: settings;
|
||||
category: "toolbar";
|
||||
|
@ -58,8 +67,9 @@ OriginalDesktop.Desktop {
|
|||
settings.constrainToolbarToCenterX = constrain;
|
||||
}
|
||||
property var toolbars: (function (map) { // answer dictionary preloaded with sysToolbar
|
||||
return map; })({});
|
||||
|
||||
map[sysToolbar.objectName] = sysToolbar;
|
||||
return map;
|
||||
})({});
|
||||
|
||||
Component.onCompleted: {
|
||||
WebEngine.settings.javascriptCanOpenWindows = true;
|
||||
|
|
|
@ -360,7 +360,7 @@ Rectangle {
|
|||
TextMetrics {
|
||||
id: displayNameHeaderMetrics
|
||||
text: displayNameHeader.title
|
||||
font: displayNameHeader.font
|
||||
// font: displayNameHeader.font // was this always undefined? giving error now...
|
||||
}
|
||||
// This Rectangle refers to the [?] popup button next to "NAMES"
|
||||
Rectangle {
|
||||
|
@ -426,7 +426,6 @@ Rectangle {
|
|||
onExited: adminHelpText.color = hifi.colors.redHighlight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Keyboard {
|
||||
id: keyboard
|
||||
|
@ -438,6 +437,7 @@ Rectangle {
|
|||
right: parent.right
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Timer used when selecting table rows that aren't yet present in the model
|
||||
// (i.e. when selecting avatars using edit.js or sphere overlays)
|
||||
|
|
|
@ -97,10 +97,12 @@ FocusScope {
|
|||
menuPopperUpper.closeLastMenu();
|
||||
}
|
||||
|
||||
function setRootMenu(menu) {
|
||||
tabletMenu.rootMenu = menu
|
||||
function setRootMenu(rootMenu, subMenu) {
|
||||
tabletMenu.subMenu = subMenu;
|
||||
tabletMenu.rootMenu = rootMenu;
|
||||
buildMenu()
|
||||
}
|
||||
|
||||
function buildMenu() {
|
||||
// Build submenu if specified.
|
||||
if (subMenu !== "") {
|
||||
|
|
|
@ -83,7 +83,7 @@ FocusScope {
|
|||
}
|
||||
|
||||
function recalcSize() {
|
||||
if (model.count !== count || !visible) {
|
||||
if (!model || model.count !== count || !visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,9 @@ Item {
|
|||
objectName: "tabletRoot"
|
||||
property string username: "Unknown user"
|
||||
property var eventBridge;
|
||||
property string option: ""
|
||||
|
||||
property var rootMenu;
|
||||
property string subMenu: ""
|
||||
|
||||
signal showDesktop();
|
||||
|
||||
|
@ -14,7 +16,13 @@ Item {
|
|||
option = value;
|
||||
}
|
||||
|
||||
function setMenuProperties(rootMenu, subMenu) {
|
||||
tabletRoot.rootMenu = rootMenu;
|
||||
tabletRoot.subMenu = subMenu;
|
||||
}
|
||||
|
||||
function loadSource(url) {
|
||||
loader.source = ""; // make sure we load the qml fresh each time.
|
||||
loader.source = url;
|
||||
}
|
||||
|
||||
|
@ -77,13 +85,15 @@ Item {
|
|||
if (loader.item.hasOwnProperty("sendToScript")) {
|
||||
loader.item.sendToScript.connect(tabletRoot.sendToScript);
|
||||
}
|
||||
if (loader.item.hasOwnProperty("subMenu")) {
|
||||
loader.item.subMenu = option;
|
||||
if (loader.item.hasOwnProperty("setRootMenu")) {
|
||||
loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu);
|
||||
}
|
||||
loader.item.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
width: 480
|
||||
height: 720
|
||||
height: 706
|
||||
|
||||
function setShown(value) {}
|
||||
}
|
||||
|
|
111
interface/resources/qml/hifi/tablet/WindowRoot.qml
Normal file
111
interface/resources/qml/hifi/tablet/WindowRoot.qml
Normal file
|
@ -0,0 +1,111 @@
|
|||
//
|
||||
// WindowRoot.qml
|
||||
//
|
||||
// Created by Anthony Thibault on 14 Feb 2017
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
// This qml is used when tablet content is shown on the 2d overlay ui
|
||||
// TODO: FIXME: this is practically identical to TabletRoot.qml
|
||||
|
||||
import "../../windows" as Windows
|
||||
import QtQuick 2.0
|
||||
import Hifi 1.0
|
||||
|
||||
Windows.ScrollingWindow {
|
||||
id: tabletRoot
|
||||
objectName: "tabletRoot"
|
||||
property string username: "Unknown user"
|
||||
property var eventBridge;
|
||||
|
||||
property var rootMenu;
|
||||
property string subMenu: ""
|
||||
|
||||
shown: false
|
||||
resizable: false
|
||||
|
||||
signal showDesktop();
|
||||
|
||||
function setMenuProperties(rootMenu, subMenu) {
|
||||
tabletRoot.rootMenu = rootMenu;
|
||||
tabletRoot.subMenu = subMenu;
|
||||
}
|
||||
|
||||
function loadSource(url) {
|
||||
loader.source = ""; // make sure we load the qml fresh each time.
|
||||
loader.source = url;
|
||||
}
|
||||
|
||||
function loadWebUrl(url, injectedJavaScriptUrl) {
|
||||
loader.item.url = url;
|
||||
loader.item.scriptURL = injectedJavaScriptUrl;
|
||||
}
|
||||
|
||||
// used to send a message from qml to interface script.
|
||||
signal sendToScript(var message);
|
||||
|
||||
// used to receive messages from interface script
|
||||
function fromScript(message) {
|
||||
if (loader.item.hasOwnProperty("fromScript")) {
|
||||
loader.item.fromScript(message);
|
||||
}
|
||||
}
|
||||
|
||||
SoundEffect {
|
||||
id: buttonClickSound
|
||||
volume: 0.1
|
||||
source: "../../../sounds/Gamemaster-Audio-button-click.wav"
|
||||
}
|
||||
|
||||
function playButtonClickSound() {
|
||||
// Because of the asynchronous nature of initalization, it is possible for this function to be
|
||||
// called before the C++ has set the globalPosition context variable.
|
||||
if (typeof globalPosition !== 'undefined') {
|
||||
buttonClickSound.play(globalPosition);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMicEnabled() {
|
||||
ApplicationInterface.toggleMuteAudio();
|
||||
}
|
||||
|
||||
function setUsername(newUsername) {
|
||||
username = newUsername;
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
objectName: "loader"
|
||||
asynchronous: false
|
||||
|
||||
height: pane.scrollHeight
|
||||
width: pane.contentWidth
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
|
||||
onLoaded: {
|
||||
if (loader.item.hasOwnProperty("eventBridge")) {
|
||||
loader.item.eventBridge = eventBridge;
|
||||
|
||||
// Hook up callback for clara.io download from the marketplace.
|
||||
eventBridge.webEventReceived.connect(function (event) {
|
||||
if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") {
|
||||
ApplicationInterface.addAssetToWorldFromURL(event.slice(18));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (loader.item.hasOwnProperty("sendToScript")) {
|
||||
loader.item.sendToScript.connect(tabletRoot.sendToScript);
|
||||
}
|
||||
if (loader.item.hasOwnProperty("setRootMenu")) {
|
||||
loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu);
|
||||
}
|
||||
loader.item.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
implicitWidth: 480
|
||||
implicitHeight: 706
|
||||
}
|
|
@ -29,6 +29,7 @@ Item {
|
|||
id: image
|
||||
y: -parent.yOffset;
|
||||
width: parent.width
|
||||
source: "../../../icons/tablet-icons/empty-toolbar-button.svg"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ Window {
|
|||
property real buttonSize: 50;
|
||||
property var buttons: []
|
||||
property var container: horizontal ? row : column
|
||||
|
||||
|
||||
Settings {
|
||||
category: "toolbar/" + window.objectName
|
||||
property alias x: window.x
|
||||
|
@ -49,6 +49,7 @@ Window {
|
|||
id: content
|
||||
implicitHeight: horizontal ? row.height : column.height
|
||||
implicitWidth: horizontal ? row.width : column.width
|
||||
property bool wasVisibleBeforeBeingPinned: false
|
||||
|
||||
Row {
|
||||
id: row
|
||||
|
@ -65,19 +66,11 @@ Window {
|
|||
Connections {
|
||||
target: desktop
|
||||
onPinnedChanged: {
|
||||
if (!window.pinned) {
|
||||
return;
|
||||
}
|
||||
var newPinned = desktop.pinned;
|
||||
for (var i in buttons) {
|
||||
var child = buttons[i];
|
||||
if (desktop.pinned) {
|
||||
if (!child.pinned) {
|
||||
child.visible = false;
|
||||
}
|
||||
} else {
|
||||
child.visible = true;
|
||||
}
|
||||
if (desktop.pinned) {
|
||||
content.wasVisibleBeforeBeingPinned = window.visible;
|
||||
window.visible = false;
|
||||
} else {
|
||||
window.visible = content.wasVisibleBeforeBeingPinned;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -106,6 +99,24 @@ Window {
|
|||
return buttons[index];
|
||||
}
|
||||
|
||||
function sortButtons() {
|
||||
var children = [];
|
||||
for (var i = 0; i < container.children.length; i++) {
|
||||
children[i] = container.children[i];
|
||||
}
|
||||
|
||||
children.sort(function (a, b) {
|
||||
if (a.sortOrder === b.sortOrder) {
|
||||
// subsort by stableOrder, because JS sort is not stable in qml.
|
||||
return a.stableOrder - b.stableOrder;
|
||||
} else {
|
||||
return a.sortOrder - b.sortOrder;
|
||||
}
|
||||
});
|
||||
|
||||
container.children = children;
|
||||
}
|
||||
|
||||
function addButton(properties) {
|
||||
properties = properties || {}
|
||||
|
||||
|
@ -123,8 +134,12 @@ Window {
|
|||
properties.opacity = 0;
|
||||
result = toolbarButtonBuilder.createObject(container, properties);
|
||||
buttons.push(result);
|
||||
|
||||
result.opacity = 1;
|
||||
updatePinned();
|
||||
|
||||
sortButtons();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -137,6 +152,10 @@ Window {
|
|||
buttons[index].destroy();
|
||||
buttons.splice(index, 1);
|
||||
updatePinned();
|
||||
|
||||
if (buttons.length === 0) {
|
||||
visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePinned() {
|
||||
|
|
|
@ -11,12 +11,33 @@ StateImage {
|
|||
property int imageOnOut: 0
|
||||
property int imageOnIn: 2
|
||||
|
||||
property string text: ""
|
||||
property string hoverText: button.text
|
||||
property string activeText: button.text
|
||||
property string activeHoverText: button.activeText
|
||||
|
||||
property string icon: "icons/tablet-icons/blank.svg"
|
||||
property string hoverIcon: button.icon
|
||||
property string activeIcon: button.icon
|
||||
property string activeHoverIcon: button.activeIcon
|
||||
|
||||
property int sortOrder: 100
|
||||
property int stableSortOrder: 0
|
||||
|
||||
signal clicked()
|
||||
|
||||
function changeProperty(key, value) {
|
||||
button[key] = value;
|
||||
}
|
||||
|
||||
function urlHelper(src) {
|
||||
if (src.match(/\bhttp/)) {
|
||||
return src;
|
||||
} else {
|
||||
return "../../../" + src;
|
||||
}
|
||||
}
|
||||
|
||||
function updateState() {
|
||||
if (!button.isEntered && !button.isActive) {
|
||||
buttonState = imageOffOut;
|
||||
|
@ -38,7 +59,7 @@ StateImage {
|
|||
running: false
|
||||
onTriggered: button.clicked();
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
hoverEnabled: true
|
||||
|
@ -53,5 +74,28 @@ StateImage {
|
|||
updateState();
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: icon
|
||||
width: 28
|
||||
height: 28
|
||||
anchors.bottom: caption.top
|
||||
anchors.bottomMargin: 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
fillMode: Image.Stretch
|
||||
source: urlHelper(button.isActive ? (button.isEntered ? button.activeHoverIcon : button.activeIcon) : (button.isEntered ? button.hoverIcon : button.icon))
|
||||
}
|
||||
|
||||
Text {
|
||||
id: caption
|
||||
color: button.isActive ? "#000000" : "#ffffff"
|
||||
text: button.isActive ? (button.isEntered ? button.activeHoverText : button.activeText) : (button.isEntered ? button.hoverText : button.text)
|
||||
font.bold: false
|
||||
font.pixelSize: 9
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -85,6 +85,10 @@ Fadable {
|
|||
|
||||
function setDefaultFocus() {} // Default function; can be overridden by dialogs.
|
||||
|
||||
function setShown(value) {
|
||||
window.shown = value;
|
||||
}
|
||||
|
||||
property var rectifier: Timer {
|
||||
property bool executing: false;
|
||||
interval: 100
|
||||
|
|
|
@ -545,6 +545,8 @@ Setting::Handle<int> sessionRunTime{ "sessionRunTime", 0 };
|
|||
|
||||
const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 100.0f;
|
||||
const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f;
|
||||
const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true;
|
||||
const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false;
|
||||
|
||||
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) :
|
||||
QApplication(argc, argv),
|
||||
|
@ -565,6 +567,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_fieldOfView("fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES),
|
||||
_hmdTabletScale("hmdTabletScale", DEFAULT_HMD_TABLET_SCALE_PERCENT),
|
||||
_desktopTabletScale("desktopTabletScale", DEFAULT_DESKTOP_TABLET_SCALE_PERCENT),
|
||||
_desktopTabletBecomesToolbarSetting("desktopTabletBecomesToolbar", DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR),
|
||||
_hmdTabletBecomesToolbarSetting("hmdTabletBecomesToolbar", DEFAULT_HMD_TABLET_BECOMES_TOOLBAR),
|
||||
_constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true),
|
||||
_scaleMirror(1.0f),
|
||||
_rotateMirror(0.0f),
|
||||
|
@ -831,6 +835,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress);
|
||||
|
||||
connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateThreadPoolCount);
|
||||
connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateSystemTabletMode);
|
||||
|
||||
// Save avatar location immediately after a teleport.
|
||||
connect(myAvatar.get(), &MyAvatar::positionGoneTo,
|
||||
|
@ -1537,6 +1542,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
connect(this, &QCoreApplication::aboutToQuit, this, &Application::addAssetToWorldMessageClose);
|
||||
connect(&domainHandler, &DomainHandler::hostnameChanged, this, &Application::addAssetToWorldMessageClose);
|
||||
|
||||
updateSystemTabletMode();
|
||||
}
|
||||
|
||||
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
|
||||
|
@ -2330,6 +2337,16 @@ void Application::setDesktopTabletScale(float desktopTabletScale) {
|
|||
_desktopTabletScale.set(desktopTabletScale);
|
||||
}
|
||||
|
||||
void Application::setDesktopTabletBecomesToolbarSetting(bool value) {
|
||||
_desktopTabletBecomesToolbarSetting.set(value);
|
||||
updateSystemTabletMode();
|
||||
}
|
||||
|
||||
void Application::setHmdTabletBecomesToolbarSetting(bool value) {
|
||||
_hmdTabletBecomesToolbarSetting.set(value);
|
||||
updateSystemTabletMode();
|
||||
}
|
||||
|
||||
void Application::setSettingConstrainToolbarPosition(bool setting) {
|
||||
_constrainToolbarPosition.set(setting);
|
||||
DependencyManager::get<OffscreenUi>()->setConstrainToolbarToCenterX(setting);
|
||||
|
@ -5462,6 +5479,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
|
||||
scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get<ToolbarScriptingInterface>().data());
|
||||
|
||||
DependencyManager::get<TabletScriptingInterface>().data()->setToolbarScriptingInterface(DependencyManager::get<ToolbarScriptingInterface>().data());
|
||||
|
||||
scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
|
||||
qScriptRegisterMetaType(scriptEngine, CustomPromptResultToScriptValue, CustomPromptResultFromScriptValue);
|
||||
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
||||
|
@ -6854,6 +6873,14 @@ void Application::updateThreadPoolCount() const {
|
|||
QThreadPool::globalInstance()->setMaxThreadCount(threadPoolSize);
|
||||
}
|
||||
|
||||
void Application::updateSystemTabletMode() {
|
||||
if (isHMDMode()) {
|
||||
DependencyManager::get<TabletScriptingInterface>()->setToolbarMode(getHmdTabletBecomesToolbarSetting());
|
||||
} else {
|
||||
DependencyManager::get<TabletScriptingInterface>()->setToolbarMode(getDesktopTabletBecomesToolbarSetting());
|
||||
}
|
||||
}
|
||||
|
||||
void Application::toggleMuteAudio() {
|
||||
auto menu = Menu::getInstance();
|
||||
menu->setIsOptionChecked(MenuOption::MuteAudio, !menu->isOptionChecked(MenuOption::MuteAudio));
|
||||
|
|
|
@ -214,6 +214,11 @@ public:
|
|||
float getDesktopTabletScale() { return _desktopTabletScale.get(); }
|
||||
void setDesktopTabletScale(float desktopTabletScale);
|
||||
|
||||
bool getDesktopTabletBecomesToolbarSetting() { return _desktopTabletBecomesToolbarSetting.get(); }
|
||||
void setDesktopTabletBecomesToolbarSetting(bool value);
|
||||
bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); }
|
||||
void setHmdTabletBecomesToolbarSetting(bool value);
|
||||
|
||||
float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); }
|
||||
void setSettingConstrainToolbarPosition(bool setting);
|
||||
|
||||
|
@ -310,6 +315,7 @@ public slots:
|
|||
bool exportEntities(const QString& filename, float x, float y, float z, float scale);
|
||||
bool importEntities(const QString& url);
|
||||
void updateThreadPoolCount() const;
|
||||
void updateSystemTabletMode();
|
||||
|
||||
static void setLowVelocityFilter(bool lowVelocityFilter);
|
||||
Q_INVOKABLE void loadDialog();
|
||||
|
@ -550,6 +556,8 @@ private:
|
|||
Setting::Handle<float> _fieldOfView;
|
||||
Setting::Handle<float> _hmdTabletScale;
|
||||
Setting::Handle<float> _desktopTabletScale;
|
||||
Setting::Handle<bool> _desktopTabletBecomesToolbarSetting;
|
||||
Setting::Handle<bool> _hmdTabletBecomesToolbarSetting;
|
||||
Setting::Handle<bool> _constrainToolbarPosition;
|
||||
|
||||
float _scaleMirror;
|
||||
|
|
|
@ -92,6 +92,16 @@ void setupPreferences() {
|
|||
preference->setMax(500);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = []()->bool { return qApp->getDesktopTabletBecomesToolbarSetting(); };
|
||||
auto setter = [](bool value) { qApp->setDesktopTabletBecomesToolbarSetting(value); };
|
||||
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Desktop Tablet Becomes Toolbar", getter, setter));
|
||||
}
|
||||
{
|
||||
auto getter = []()->bool { return qApp->getHmdTabletBecomesToolbarSetting(); };
|
||||
auto setter = [](bool value) { qApp->setHmdTabletBecomesToolbarSetting(value); };
|
||||
preferences->addPreference(new CheckPreference(UI_CATEGORY, "HMD Tablet Becomes Toolbar", getter, setter));
|
||||
}
|
||||
|
||||
// Snapshots
|
||||
static const QString SNAPSHOTS { "Snapshots" };
|
||||
|
|
237
libraries/entities/src/EntityEditFilters.cpp
Normal file
237
libraries/entities/src/EntityEditFilters.cpp
Normal file
|
@ -0,0 +1,237 @@
|
|||
//
|
||||
// EntityEditFilters.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by David Kelly on 2/7/2017.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#include <QUrl>
|
||||
|
||||
#include <ResourceManager.h>
|
||||
#include "EntityEditFilters.h"
|
||||
|
||||
QList<EntityItemID> EntityEditFilters::getZonesByPosition(glm::vec3& position) {
|
||||
QList<EntityItemID> zones;
|
||||
QList<EntityItemID> missingZones;
|
||||
_lock.lockForRead();
|
||||
auto zoneIDs = _filterDataMap.keys();
|
||||
_lock.unlock();
|
||||
for (auto id : zoneIDs) {
|
||||
if (!id.isInvalidID()) {
|
||||
// for now, look it up in the tree (soon we need to cache or similar?)
|
||||
EntityItemPointer itemPtr = _tree->findEntityByEntityItemID(id);
|
||||
auto zone = std::dynamic_pointer_cast<ZoneEntityItem>(itemPtr);
|
||||
if (!zone) {
|
||||
// TODO: maybe remove later?
|
||||
removeFilter(id);
|
||||
} else if (zone->contains(position)) {
|
||||
zones.append(id);
|
||||
}
|
||||
} else {
|
||||
// the null id is the global filter we put in the domain server's
|
||||
// advanced entity server settings
|
||||
zones.append(id);
|
||||
}
|
||||
}
|
||||
return zones;
|
||||
}
|
||||
|
||||
bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged,
|
||||
EntityTree::FilterType filterType, EntityItemID& itemID) {
|
||||
|
||||
// get the ids of all the zones (plus the global entity edit filter) that the position
|
||||
// lies within
|
||||
auto zoneIDs = getZonesByPosition(position);
|
||||
for (auto id : zoneIDs) {
|
||||
if (!itemID.isInvalidID() && id == itemID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the filter pair, etc...
|
||||
_lock.lockForRead();
|
||||
FilterData filterData = _filterDataMap.value(id);
|
||||
_lock.unlock();
|
||||
|
||||
if (filterData.valid()) {
|
||||
if (filterData.rejectAll) {
|
||||
return false;
|
||||
}
|
||||
auto oldProperties = propertiesIn.getDesiredProperties();
|
||||
auto specifiedProperties = propertiesIn.getChangedProperties();
|
||||
propertiesIn.setDesiredProperties(specifiedProperties);
|
||||
QScriptValue inputValues = propertiesIn.copyToScriptValue(filterData.engine, false, true, true);
|
||||
propertiesIn.setDesiredProperties(oldProperties);
|
||||
|
||||
auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter.
|
||||
QScriptValueList args;
|
||||
args << inputValues;
|
||||
args << filterType;
|
||||
|
||||
QScriptValue result = filterData.filterFn.call(_nullObjectForFilter, args);
|
||||
if (filterData.uncaughtExceptions()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result.isObject()){
|
||||
// make propertiesIn reflect the changes, for next filter...
|
||||
propertiesIn.copyFromScriptValue(result, false);
|
||||
|
||||
// and update propertiesOut too. TODO: this could be more efficient...
|
||||
propertiesOut.copyFromScriptValue(result, false);
|
||||
// Javascript objects are == only if they are the same object. To compare arbitrary values, we need to use JSON.
|
||||
auto out = QJsonValue::fromVariant(result.toVariant());
|
||||
wasChanged |= (in != out);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we made it here,
|
||||
return true;
|
||||
}
|
||||
|
||||
void EntityEditFilters::removeFilter(EntityItemID entityID) {
|
||||
QWriteLocker writeLock(&_lock);
|
||||
FilterData filterData = _filterDataMap.value(entityID);
|
||||
if (filterData.valid()) {
|
||||
delete filterData.engine;
|
||||
}
|
||||
_filterDataMap.remove(entityID);
|
||||
}
|
||||
|
||||
void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) {
|
||||
|
||||
QUrl scriptURL(filterURL);
|
||||
|
||||
// setting it to an empty string is same as removing
|
||||
if (filterURL.size() == 0) {
|
||||
removeFilter(entityID);
|
||||
return;
|
||||
}
|
||||
|
||||
// The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp)
|
||||
if (scriptURL.scheme().isEmpty() || (scriptURL.scheme() == URL_SCHEME_FILE)) {
|
||||
qWarning() << "Cannot load script from local filesystem, because assignment may be on a different computer.";
|
||||
scriptRequestFinished(entityID);
|
||||
return;
|
||||
}
|
||||
|
||||
// first remove any existing info for this entity
|
||||
removeFilter(entityID);
|
||||
|
||||
// reject all edits until we load the script
|
||||
FilterData filterData;
|
||||
filterData.rejectAll = true;
|
||||
|
||||
_lock.lockForWrite();
|
||||
_filterDataMap.insert(entityID, filterData);
|
||||
_lock.unlock();
|
||||
|
||||
auto scriptRequest = ResourceManager::createResourceRequest(this, scriptURL);
|
||||
if (!scriptRequest) {
|
||||
qWarning() << "Could not create ResourceRequest for Entity Edit filter script at" << scriptURL.toString();
|
||||
scriptRequestFinished(entityID);
|
||||
return;
|
||||
}
|
||||
// Agent.cpp sets up a timeout here, but that is unnecessary, as ResourceRequest has its own.
|
||||
connect(scriptRequest, &ResourceRequest::finished, this, [this, entityID]{ EntityEditFilters::scriptRequestFinished(entityID);} );
|
||||
// FIXME: handle atp rquests setup here. See Agent::requestScript()
|
||||
qInfo() << "Requesting script at URL" << qPrintable(scriptRequest->getUrl().toString());
|
||||
scriptRequest->send();
|
||||
qDebug() << "script request sent for entity " << entityID;
|
||||
}
|
||||
|
||||
// Copied from ScriptEngine.cpp. We should make this a class method for reuse.
|
||||
// Note: I've deliberately stopped short of using ScriptEngine instead of QScriptEngine, as that is out of project scope at this point.
|
||||
static bool hasCorrectSyntax(const QScriptProgram& program) {
|
||||
const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode());
|
||||
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
|
||||
const auto error = syntaxCheck.errorMessage();
|
||||
const auto line = QString::number(syntaxCheck.errorLineNumber());
|
||||
const auto column = QString::number(syntaxCheck.errorColumnNumber());
|
||||
const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column);
|
||||
qCritical() << qPrintable(message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName) {
|
||||
if (engine.hasUncaughtException()) {
|
||||
const auto backtrace = engine.uncaughtExceptionBacktrace();
|
||||
const auto exception = engine.uncaughtException().toString();
|
||||
const auto line = QString::number(engine.uncaughtExceptionLineNumber());
|
||||
engine.clearExceptions();
|
||||
|
||||
static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3";
|
||||
auto message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line);
|
||||
if (!backtrace.empty()) {
|
||||
static const auto lineSeparator = "\n ";
|
||||
message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator));
|
||||
}
|
||||
qCritical() << qPrintable(message);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) {
|
||||
qDebug() << "script request completed for entity " << entityID;
|
||||
auto scriptRequest = qobject_cast<ResourceRequest*>(sender());
|
||||
const QString urlString = scriptRequest->getUrl().toString();
|
||||
if (scriptRequest && scriptRequest->getResult() == ResourceRequest::Success) {
|
||||
auto scriptContents = scriptRequest->getData();
|
||||
qInfo() << "Downloaded script:" << scriptContents;
|
||||
QScriptProgram program(scriptContents, urlString);
|
||||
if (hasCorrectSyntax(program)) {
|
||||
// create a QScriptEngine for this script
|
||||
QScriptEngine* engine = new QScriptEngine();
|
||||
engine->evaluate(scriptContents);
|
||||
if (!hadUncaughtExceptions(*engine, urlString)) {
|
||||
// put the engine in the engine map (so we don't leak them, etc...)
|
||||
FilterData filterData;
|
||||
filterData.engine = engine;
|
||||
filterData.rejectAll = false;
|
||||
|
||||
// define the uncaughtException function
|
||||
QScriptEngine& engineRef = *engine;
|
||||
filterData.uncaughtExceptions = [this, &engineRef, urlString]() { return hadUncaughtExceptions(engineRef, urlString); };
|
||||
|
||||
// now get the filter function
|
||||
auto global = engine->globalObject();
|
||||
auto entitiesObject = engine->newObject();
|
||||
entitiesObject.setProperty("ADD_FILTER_TYPE", EntityTree::FilterType::Add);
|
||||
entitiesObject.setProperty("EDIT_FILTER_TYPE", EntityTree::FilterType::Edit);
|
||||
entitiesObject.setProperty("PHYSICS_FILTER_TYPE", EntityTree::FilterType::Physics);
|
||||
global.setProperty("Entities", entitiesObject);
|
||||
filterData.filterFn = global.property("filter");
|
||||
if (!filterData.filterFn.isFunction()) {
|
||||
qDebug() << "Filter function specified but not found. Will reject all edits for those without lock rights.";
|
||||
delete engine;
|
||||
filterData.rejectAll=true;
|
||||
}
|
||||
|
||||
|
||||
_lock.lockForWrite();
|
||||
_filterDataMap.insert(entityID, filterData);
|
||||
_lock.unlock();
|
||||
|
||||
qDebug() << "script request filter processed for entity id " << entityID;
|
||||
|
||||
emit filterAdded(entityID, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (scriptRequest) {
|
||||
qCritical() << "Failed to download script at" << urlString;
|
||||
// See HTTPResourceRequest::onRequestFinished for interpretation of codes. For example, a 404 is code 6 and 403 is 3. A timeout is 2. Go figure.
|
||||
qCritical() << "ResourceRequest error was" << scriptRequest->getResult();
|
||||
} else {
|
||||
qCritical() << "Failed to create script request.";
|
||||
}
|
||||
emit filterAdded(entityID, false);
|
||||
}
|
65
libraries/entities/src/EntityEditFilters.h
Normal file
65
libraries/entities/src/EntityEditFilters.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// EntityEditFilters.h
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by David Kelly on 2/7/2017.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#ifndef hifi_EntityEditFilters_h
|
||||
#define hifi_EntityEditFilters_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QMap>
|
||||
#include <QScriptValue>
|
||||
#include <QScriptEngine>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "EntityItemID.h"
|
||||
#include "EntityItemProperties.h"
|
||||
#include "EntityTree.h"
|
||||
|
||||
class EntityEditFilters : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct FilterData {
|
||||
QScriptValue filterFn;
|
||||
std::function<bool()> uncaughtExceptions;
|
||||
QScriptEngine* engine;
|
||||
bool rejectAll;
|
||||
|
||||
FilterData(): engine(nullptr), rejectAll(false) {};
|
||||
bool valid() { return (rejectAll || (engine != nullptr && filterFn.isFunction() && uncaughtExceptions)); }
|
||||
};
|
||||
|
||||
EntityEditFilters() {};
|
||||
EntityEditFilters(EntityTreePointer tree ): _tree(tree) {};
|
||||
|
||||
void addFilter(EntityItemID entityID, QString filterURL);
|
||||
void removeFilter(EntityItemID entityID);
|
||||
|
||||
bool filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged,
|
||||
EntityTree::FilterType filterType, EntityItemID& entityID);
|
||||
|
||||
signals:
|
||||
void filterAdded(EntityItemID id, bool success);
|
||||
|
||||
private slots:
|
||||
void scriptRequestFinished(EntityItemID entityID);
|
||||
|
||||
private:
|
||||
QList<EntityItemID> getZonesByPosition(glm::vec3& position);
|
||||
|
||||
EntityTreePointer _tree {};
|
||||
bool _rejectAll {false};
|
||||
QScriptValue _nullObjectForFilter{};
|
||||
|
||||
QReadWriteLock _lock;
|
||||
QMap<EntityItemID, FilterData> _filterDataMap;
|
||||
};
|
||||
|
||||
#endif //hifi_EntityEditFilters_h
|
|
@ -19,6 +19,7 @@
|
|||
#include "RegisteredMetaTypes.h"
|
||||
#include "EntityItemID.h"
|
||||
|
||||
int entityItemIDTypeID = qRegisterMetaType<EntityItemID>();
|
||||
|
||||
EntityItemID::EntityItemID() : QUuid()
|
||||
{
|
||||
|
|
|
@ -332,6 +332,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
|
||||
CHECK_PROPERTY_CHANGE(PROP_FLYING_ALLOWED, flyingAllowed);
|
||||
CHECK_PROPERTY_CHANGE(PROP_GHOSTING_ALLOWED, ghostingAllowed);
|
||||
CHECK_PROPERTY_CHANGE(PROP_FILTER_URL, filterURL);
|
||||
|
||||
CHECK_PROPERTY_CHANGE(PROP_CLIENT_ONLY, clientOnly);
|
||||
CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID);
|
||||
|
@ -509,6 +510,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FLYING_ALLOWED, flyingAllowed);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GHOSTING_ALLOWED, ghostingAllowed);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FILTER_URL, filterURL);
|
||||
}
|
||||
|
||||
// Web only
|
||||
|
@ -751,6 +753,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
|||
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(flyingAllowed, bool, setFlyingAllowed);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(filterURL, QString, setFilterURL);
|
||||
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(clientOnly, bool, setClientOnly);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID);
|
||||
|
@ -879,6 +882,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
|
|||
|
||||
COPY_PROPERTY_IF_CHANGED(flyingAllowed);
|
||||
COPY_PROPERTY_IF_CHANGED(ghostingAllowed);
|
||||
COPY_PROPERTY_IF_CHANGED(filterURL);
|
||||
|
||||
COPY_PROPERTY_IF_CHANGED(clientOnly);
|
||||
COPY_PROPERTY_IF_CHANGED(owningAvatarID);
|
||||
|
@ -1063,6 +1067,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
|||
|
||||
ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_FILTER_URL, FilterURL, filterURL, QString);
|
||||
|
||||
ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t);
|
||||
|
||||
|
@ -1311,6 +1316,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
|
||||
APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, properties.getFlyingAllowed());
|
||||
APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, properties.getGhostingAllowed());
|
||||
APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, properties.getFilterURL());
|
||||
}
|
||||
|
||||
if (properties.getType() == EntityTypes::PolyVox) {
|
||||
|
@ -1605,6 +1611,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FLYING_ALLOWED, bool, setFlyingAllowed);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FILTER_URL, QString, setFilterURL);
|
||||
}
|
||||
|
||||
if (properties.getType() == EntityTypes::PolyVox) {
|
||||
|
@ -1808,6 +1815,7 @@ void EntityItemProperties::markAllChanged() {
|
|||
|
||||
_flyingAllowedChanged = true;
|
||||
_ghostingAllowedChanged = true;
|
||||
_filterURLChanged = true;
|
||||
|
||||
_clientOnlyChanged = true;
|
||||
_owningAvatarIDChanged = true;
|
||||
|
@ -2150,7 +2158,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
|
|||
if (ghostingAllowedChanged()) {
|
||||
out += "ghostingAllowed";
|
||||
}
|
||||
|
||||
if (filterURLChanged()) {
|
||||
out += "filterURL";
|
||||
}
|
||||
if (dpiChanged()) {
|
||||
out += "dpi";
|
||||
}
|
||||
|
|
|
@ -215,6 +215,7 @@ public:
|
|||
|
||||
DEFINE_PROPERTY(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool, ZoneEntityItem::DEFAULT_FLYING_ALLOWED);
|
||||
DEFINE_PROPERTY(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool, ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED);
|
||||
DEFINE_PROPERTY(PROP_FILTER_URL, FilterURL, filterURL, QString, ZoneEntityItem::DEFAULT_FILTER_URL);
|
||||
|
||||
DEFINE_PROPERTY(PROP_CLIENT_ONLY, ClientOnly, clientOnly, bool, false);
|
||||
DEFINE_PROPERTY_REF(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID);
|
||||
|
@ -458,6 +459,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
|
|||
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, FlyingAllowed, flyingAllowed, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, GhostingAllowed, ghostingAllowed, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, FilterURL, filterURL, "");
|
||||
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, ClientOnly, clientOnly, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, "");
|
||||
|
|
|
@ -185,6 +185,8 @@ enum EntityPropertyList {
|
|||
|
||||
PROP_SERVER_SCRIPTS,
|
||||
|
||||
PROP_FILTER_URL,
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ATTENTION: add new properties to end of list just ABOVE this line
|
||||
PROP_AFTER_LAST_ITEM,
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "EntitiesLogging.h"
|
||||
#include "RecurseOctreeToMapOperator.h"
|
||||
#include "LogHandler.h"
|
||||
#include "EntityEditFilters.h"
|
||||
|
||||
static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50;
|
||||
const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour
|
||||
|
@ -923,55 +924,14 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList<Q
|
|||
}
|
||||
}
|
||||
|
||||
void EntityTree::initEntityEditFilterEngine(QScriptEngine* engine, std::function<bool()> entityEditFilterHadUncaughtExceptions) {
|
||||
_entityEditFilterEngine = engine;
|
||||
_entityEditFilterHadUncaughtExceptions = entityEditFilterHadUncaughtExceptions;
|
||||
auto global = _entityEditFilterEngine->globalObject();
|
||||
_entityEditFilterFunction = global.property("filter");
|
||||
if (!_entityEditFilterFunction.isFunction()) {
|
||||
qCDebug(entities) << "Filter function specified but not found. Will reject all edits.";
|
||||
_entityEditFilterEngine = nullptr; // So that we don't try to call it. See filterProperties.
|
||||
}
|
||||
auto entitiesObject = _entityEditFilterEngine->newObject();
|
||||
entitiesObject.setProperty("ADD_FILTER_TYPE", FilterType::Add);
|
||||
entitiesObject.setProperty("EDIT_FILTER_TYPE", FilterType::Edit);
|
||||
entitiesObject.setProperty("PHYSICS_FILTER_TYPE", FilterType::Physics);
|
||||
global.setProperty("Entities", entitiesObject);
|
||||
_hasEntityEditFilter = true;
|
||||
}
|
||||
|
||||
bool EntityTree::filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType) {
|
||||
if (!_entityEditFilterEngine) {
|
||||
propertiesOut = propertiesIn;
|
||||
wasChanged = false; // not changed
|
||||
if (_hasEntityEditFilter) {
|
||||
qCDebug(entities) << "Rejecting properties because filter has not been set.";
|
||||
return false;
|
||||
}
|
||||
return true; // allowed
|
||||
}
|
||||
auto oldProperties = propertiesIn.getDesiredProperties();
|
||||
auto specifiedProperties = propertiesIn.getChangedProperties();
|
||||
propertiesIn.setDesiredProperties(specifiedProperties);
|
||||
QScriptValue inputValues = propertiesIn.copyToScriptValue(_entityEditFilterEngine, false, true, true);
|
||||
propertiesIn.setDesiredProperties(oldProperties);
|
||||
|
||||
auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter.
|
||||
QScriptValueList args;
|
||||
args << inputValues;
|
||||
args << filterType;
|
||||
|
||||
QScriptValue result = _entityEditFilterFunction.call(_nullObjectForFilter, args);
|
||||
if (_entityEditFilterHadUncaughtExceptions()) {
|
||||
result = QScriptValue();
|
||||
}
|
||||
|
||||
bool accepted = result.isObject(); // filters should return null or false to completely reject edit or add
|
||||
if (accepted) {
|
||||
propertiesOut.copyFromScriptValue(result, false);
|
||||
// Javascript objects are == only if they are the same object. To compare arbitrary values, we need to use JSON.
|
||||
auto out = QJsonValue::fromVariant(result.toVariant());
|
||||
wasChanged = in != out;
|
||||
bool EntityTree::filterProperties(EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType) {
|
||||
bool accepted = true;
|
||||
auto entityEditFilters = DependencyManager::get<EntityEditFilters>();
|
||||
if (entityEditFilters) {
|
||||
auto position = existingEntity ? existingEntity->getPosition() : propertiesIn.getPosition();
|
||||
auto entityID = existingEntity ? existingEntity->getEntityItemID() : EntityItemID();
|
||||
accepted = entityEditFilters->filter(position, propertiesIn, propertiesOut, wasChanged, filterType, entityID);
|
||||
}
|
||||
|
||||
return accepted;
|
||||
|
@ -1076,11 +1036,16 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
// an existing entity... handle appropriately
|
||||
if (validEditPacket) {
|
||||
|
||||
// search for the entity by EntityItemID
|
||||
startLookup = usecTimestampNow();
|
||||
EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID);
|
||||
endLookup = usecTimestampNow();
|
||||
|
||||
startFilter = usecTimestampNow();
|
||||
bool wasChanged = false;
|
||||
// Having (un)lock rights bypasses the filter, unless it's a physics result.
|
||||
FilterType filterType = isPhysics ? FilterType::Physics : (isAdd ? FilterType::Add : FilterType::Edit);
|
||||
bool allowed = (!isPhysics && senderNode->isAllowedEditor()) || filterProperties(properties, properties, wasChanged, filterType);
|
||||
bool allowed = (!isPhysics && senderNode->isAllowedEditor()) || filterProperties(existingEntity, properties, properties, wasChanged, filterType);
|
||||
if (!allowed) {
|
||||
auto timestamp = properties.getLastEdited();
|
||||
properties = EntityItemProperties();
|
||||
|
@ -1093,10 +1058,6 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
}
|
||||
endFilter = usecTimestampNow();
|
||||
|
||||
// search for the entity by EntityItemID
|
||||
startLookup = usecTimestampNow();
|
||||
EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID);
|
||||
endLookup = usecTimestampNow();
|
||||
if (existingEntity && !isAdd) {
|
||||
|
||||
if (suppressDisallowedScript) {
|
||||
|
@ -1767,3 +1728,4 @@ QStringList EntityTree::getJointNames(const QUuid& entityID) const {
|
|||
}
|
||||
return entity->getJointNames();
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ typedef std::shared_ptr<EntityTree> EntityTreePointer;
|
|||
#include "EntityTreeElement.h"
|
||||
#include "DeleteEntityOperator.h"
|
||||
|
||||
class EntityEditFilters;
|
||||
class Model;
|
||||
using ModelPointer = std::shared_ptr<Model>;
|
||||
using ModelWeakPointer = std::weak_ptr<Model>;
|
||||
|
@ -271,9 +272,6 @@ public:
|
|||
|
||||
void notifyNewCollisionSoundURL(const QString& newCollisionSoundURL, const EntityItemID& entityID);
|
||||
|
||||
void initEntityEditFilterEngine(QScriptEngine* engine, std::function<bool()> entityEditFilterHadUncaughtExceptions);
|
||||
void setHasEntityFilter(bool hasFilter) { _hasEntityEditFilter = hasFilter; }
|
||||
|
||||
static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME;
|
||||
|
||||
public slots:
|
||||
|
@ -362,13 +360,8 @@ protected:
|
|||
|
||||
float _maxTmpEntityLifetime { DEFAULT_MAX_TMP_ENTITY_LIFETIME };
|
||||
|
||||
bool filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType);
|
||||
bool filterProperties(EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType);
|
||||
bool _hasEntityEditFilter{ false };
|
||||
QScriptEngine* _entityEditFilterEngine{};
|
||||
QScriptValue _entityEditFilterFunction{};
|
||||
QScriptValue _nullObjectForFilter{};
|
||||
std::function<bool()> _entityEditFilterHadUncaughtExceptions;
|
||||
|
||||
QStringList _entityScriptSourceWhitelist;
|
||||
};
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "EntityTree.h"
|
||||
#include "EntityTreeElement.h"
|
||||
#include "ZoneEntityItem.h"
|
||||
#include "EntityEditFilters.h"
|
||||
|
||||
bool ZoneEntityItem::_zonesArePickable = false;
|
||||
bool ZoneEntityItem::_drawZoneBoundaries = false;
|
||||
|
@ -28,7 +29,7 @@ const ShapeType ZoneEntityItem::DEFAULT_SHAPE_TYPE = SHAPE_TYPE_BOX;
|
|||
const QString ZoneEntityItem::DEFAULT_COMPOUND_SHAPE_URL = "";
|
||||
const bool ZoneEntityItem::DEFAULT_FLYING_ALLOWED = true;
|
||||
const bool ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED = true;
|
||||
|
||||
const QString ZoneEntityItem::DEFAULT_FILTER_URL = "";
|
||||
|
||||
EntityItemPointer ZoneEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
EntityItemPointer entity { new ZoneEntityItem(entityID) };
|
||||
|
@ -61,6 +62,7 @@ EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredPr
|
|||
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(flyingAllowed, getFlyingAllowed);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(ghostingAllowed, getGhostingAllowed);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(filterURL, getFilterURL);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
@ -79,6 +81,7 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(flyingAllowed, setFlyingAllowed);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(filterURL, setFilterURL);
|
||||
|
||||
bool somethingChangedInSkybox = _skyboxProperties.setProperties(properties);
|
||||
|
||||
|
@ -128,6 +131,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
|
||||
READ_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, bool, setFlyingAllowed);
|
||||
READ_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed);
|
||||
READ_ENTITY_PROPERTY(PROP_FILTER_URL, QString, setFilterURL);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
@ -147,6 +151,7 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p
|
|||
|
||||
requestedProperties += PROP_FLYING_ALLOWED;
|
||||
requestedProperties += PROP_GHOSTING_ALLOWED;
|
||||
requestedProperties += PROP_FILTER_URL;
|
||||
|
||||
return requestedProperties;
|
||||
}
|
||||
|
@ -177,6 +182,7 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits
|
|||
|
||||
APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, getFlyingAllowed());
|
||||
APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, getGhostingAllowed());
|
||||
APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, getFilterURL());
|
||||
}
|
||||
|
||||
void ZoneEntityItem::debugDump() const {
|
||||
|
@ -215,3 +221,13 @@ bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const
|
|||
|
||||
return _zonesArePickable;
|
||||
}
|
||||
|
||||
void ZoneEntityItem::setFilterURL(QString url) {
|
||||
_filterURL = url;
|
||||
if (DependencyManager::isSet<EntityEditFilters>()) {
|
||||
auto entityEditFilters = DependencyManager::get<EntityEditFilters>();
|
||||
qCDebug(entities) << "adding filter " << url << "for zone" << getEntityItemID();
|
||||
entityEditFilters->addFilter(getEntityItemID(), url);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,8 @@ public:
|
|||
void setFlyingAllowed(bool value) { _flyingAllowed = value; }
|
||||
bool getGhostingAllowed() const { return _ghostingAllowed; }
|
||||
void setGhostingAllowed(bool value) { _ghostingAllowed = value; }
|
||||
QString getFilterURL() const { return _filterURL; }
|
||||
void setFilterURL(const QString url);
|
||||
|
||||
virtual bool supportsDetailedRayIntersection() const override { return true; }
|
||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
|
@ -87,6 +89,7 @@ public:
|
|||
static const QString DEFAULT_COMPOUND_SHAPE_URL;
|
||||
static const bool DEFAULT_FLYING_ALLOWED;
|
||||
static const bool DEFAULT_GHOSTING_ALLOWED;
|
||||
static const QString DEFAULT_FILTER_URL;
|
||||
|
||||
protected:
|
||||
KeyLightPropertyGroup _keyLightProperties;
|
||||
|
@ -101,6 +104,7 @@ protected:
|
|||
|
||||
bool _flyingAllowed { DEFAULT_FLYING_ALLOWED };
|
||||
bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED };
|
||||
QString _filterURL { DEFAULT_FILTER_URL };
|
||||
|
||||
static bool _drawZoneBoundaries;
|
||||
static bool _zonesArePickable;
|
||||
|
|
|
@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::EntityEdit:
|
||||
case PacketType::EntityData:
|
||||
case PacketType::EntityPhysics:
|
||||
return VERSION_ENTITIES_PHYSICS_PACKET;
|
||||
return VERSION_ENTITIES_ZONE_FILTERS;
|
||||
case PacketType::EntityQuery:
|
||||
return static_cast<PacketVersion>(EntityQueryPacketVersion::JsonFilter);
|
||||
case PacketType::AvatarIdentity:
|
||||
|
|
|
@ -204,6 +204,7 @@ const PacketVersion VERSION_ENTITIES_ARROW_ACTION = 64;
|
|||
const PacketVersion VERSION_ENTITIES_LAST_EDITED_BY = 65;
|
||||
const PacketVersion VERSION_ENTITIES_SERVER_SCRIPTS = 66;
|
||||
const PacketVersion VERSION_ENTITIES_PHYSICS_PACKET = 67;
|
||||
const PacketVersion VERSION_ENTITIES_ZONE_FILTERS = 68;
|
||||
|
||||
enum class EntityQueryPacketVersion: PacketVersion {
|
||||
JsonFilter = 18
|
||||
|
|
|
@ -478,6 +478,9 @@ bool SendQueue::maybeResendPacket() {
|
|||
|
||||
Packet::ObfuscationLevel level = (Packet::ObfuscationLevel)(entry.first < 2 ? 0 : (entry.first - 2) % 4);
|
||||
|
||||
auto wireSize = resendPacket.getWireSize();
|
||||
auto sequenceNumber = it->first;
|
||||
|
||||
if (level != Packet::NoObfuscation) {
|
||||
#ifdef UDT_CONNECTION_DEBUG
|
||||
QString debugString = "Obfuscating packet %1 with level %2";
|
||||
|
@ -512,7 +515,7 @@ bool SendQueue::maybeResendPacket() {
|
|||
sentLocker.unlock();
|
||||
}
|
||||
|
||||
emit packetRetransmitted(resendPacket.getWireSize(), it->first, p_high_resolution_clock::now());
|
||||
emit packetRetransmitted(wireSize, sequenceNumber, p_high_resolution_clock::now());
|
||||
|
||||
// Signal that we did resend a packet
|
||||
return true;
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
#include <AudioInjector.h>
|
||||
|
||||
SoundEffect::~SoundEffect() {
|
||||
if (_sound) {
|
||||
_sound->deleteLater();
|
||||
}
|
||||
if (_injector) {
|
||||
// stop will cause the AudioInjector to delete itself.
|
||||
_injector->stop();
|
||||
|
|
|
@ -11,17 +11,36 @@
|
|||
#include <QtCore/QThread>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include "DependencyManager.h"
|
||||
#include <PathUtils.h>
|
||||
#include <QmlWindowClass.h>
|
||||
#include <QQmlProperty>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include "ScriptEngineLogging.h"
|
||||
#include "DependencyManager.h"
|
||||
#include "OffscreenUi.h"
|
||||
#include <OffscreenUi.h>
|
||||
#include <InfoView.h>
|
||||
#include "SoundEffect.h"
|
||||
|
||||
TabletScriptingInterface::TabletScriptingInterface() {
|
||||
qmlRegisterType<SoundEffect>("Hifi", 1, 0, "SoundEffect");
|
||||
}
|
||||
|
||||
QObject* TabletScriptingInterface::getSystemToolbarProxy() {
|
||||
const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system";
|
||||
Qt::ConnectionType connectionType = Qt::AutoConnection;
|
||||
if (QThread::currentThread() != _toolbarScriptingInterface->thread()) {
|
||||
connectionType = Qt::BlockingQueuedConnection;
|
||||
}
|
||||
QObject* toolbarProxy = nullptr;
|
||||
bool hasResult = QMetaObject::invokeMethod(_toolbarScriptingInterface, "getToolbar", connectionType, Q_RETURN_ARG(QObject*, toolbarProxy), Q_ARG(QString, SYSTEM_TOOLBAR));
|
||||
if (hasResult) {
|
||||
return toolbarProxy;
|
||||
} else {
|
||||
qCWarning(scriptengine) << "ToolbarScriptingInterface getToolbar has no result";
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
QObject* TabletScriptingInterface::getTablet(const QString& tabletId) {
|
||||
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
|
@ -35,10 +54,21 @@ QObject* TabletScriptingInterface::getTablet(const QString& tabletId) {
|
|||
// allocate a new tablet, add it to the map then return it.
|
||||
auto tabletProxy = QSharedPointer<TabletProxy>(new TabletProxy(tabletId));
|
||||
_tabletProxies[tabletId] = tabletProxy;
|
||||
tabletProxy->setToolbarMode(_toolbarMode);
|
||||
return tabletProxy.data();
|
||||
}
|
||||
}
|
||||
|
||||
void TabletScriptingInterface::setToolbarMode(bool toolbarMode) {
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
|
||||
_toolbarMode = toolbarMode;
|
||||
|
||||
for (auto& iter : _tabletProxies) {
|
||||
iter.second->setToolbarMode(toolbarMode);
|
||||
}
|
||||
}
|
||||
|
||||
void TabletScriptingInterface::setQmlTabletRoot(QString tabletId, QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface) {
|
||||
TabletProxy* tablet = qobject_cast<TabletProxy*>(getTablet(tabletId));
|
||||
if (tablet) {
|
||||
|
@ -141,8 +171,51 @@ static const char* TABLET_SOURCE_URL = "Tablet.qml";
|
|||
static const char* WEB_VIEW_SOURCE_URL = "TabletWebView.qml";
|
||||
static const char* VRMENU_SOURCE_URL = "TabletMenu.qml";
|
||||
|
||||
class TabletRootWindow : public QmlWindowClass {
|
||||
virtual QString qmlSource() const { return "hifi/tablet/WindowRoot.qml"; }
|
||||
};
|
||||
|
||||
TabletProxy::TabletProxy(QString name) : _name(name) {
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
void TabletProxy::setToolbarMode(bool toolbarMode) {
|
||||
if (toolbarMode == _toolbarMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
_toolbarMode = toolbarMode;
|
||||
|
||||
if (toolbarMode) {
|
||||
removeButtonsFromHomeScreen();
|
||||
addButtonsToToolbar();
|
||||
|
||||
// create new desktop window
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->executeOnUiThread([=] {
|
||||
auto tabletRootWindow = new TabletRootWindow();
|
||||
tabletRootWindow->initQml(QVariantMap());
|
||||
auto quickItem = tabletRootWindow->asQuickItem();
|
||||
_desktopWindow = tabletRootWindow;
|
||||
QMetaObject::invokeMethod(quickItem, "setShown", Q_ARG(const QVariant&, QVariant(false)));
|
||||
|
||||
QObject::connect(quickItem, SIGNAL(windowClosed()), this, SLOT(desktopWindowClosed()));
|
||||
|
||||
QObject::connect(tabletRootWindow, SIGNAL(webEventReceived(QVariant)), this, SIGNAL(webEventReceived(QVariant)));
|
||||
|
||||
// forward qml surface events to interface js
|
||||
connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml);
|
||||
});
|
||||
} else {
|
||||
removeButtonsFromToolbar();
|
||||
addButtonsToHomeScreen();
|
||||
|
||||
// destroy desktop window
|
||||
if (_desktopWindow) {
|
||||
_desktopWindow->deleteLater();
|
||||
_desktopWindow = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) {
|
||||
|
@ -195,6 +268,13 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr
|
|||
}
|
||||
});
|
||||
|
||||
if (_toolbarMode) {
|
||||
// if someone creates the tablet in toolbar mode, make sure to display the home screen on the tablet.
|
||||
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
|
||||
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()), Qt::DirectConnection);
|
||||
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
|
||||
}
|
||||
|
||||
gotoHomeScreen();
|
||||
|
||||
QMetaObject::invokeMethod(_qmlTabletRoot, "setUsername", Q_ARG(const QVariant&, QVariant(getUsername())));
|
||||
|
@ -214,39 +294,61 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr
|
|||
}
|
||||
|
||||
void TabletProxy::gotoMenuScreen(const QString& submenu) {
|
||||
if (_qmlTabletRoot) {
|
||||
if (_state != State::Menu) {
|
||||
removeButtonsFromHomeScreen();
|
||||
QMetaObject::invokeMethod(_qmlTabletRoot, "setOption", Q_ARG(const QVariant&, QVariant(submenu)));
|
||||
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
|
||||
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToMenuScreen()), Qt::DirectConnection);
|
||||
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL)));
|
||||
_state = State::Menu;
|
||||
emit screenChanged(QVariant("Menu"), QVariant(VRMENU_SOURCE_URL));
|
||||
}
|
||||
|
||||
QObject* root = nullptr;
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
root = _qmlTabletRoot;
|
||||
} else if (_toolbarMode && _desktopWindow) {
|
||||
root = _desktopWindow->asQuickItem();
|
||||
}
|
||||
|
||||
if (root) {
|
||||
removeButtonsFromHomeScreen();
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
QObject* menu = offscreenUi->getRootMenu();
|
||||
QMetaObject::invokeMethod(root, "setMenuProperties", Q_ARG(QVariant, QVariant::fromValue(menu)), Q_ARG(const QVariant&, QVariant(submenu)));
|
||||
QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL)));
|
||||
_state = State::Menu;
|
||||
emit screenChanged(QVariant("Menu"), QVariant(VRMENU_SOURCE_URL));
|
||||
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
|
||||
}
|
||||
}
|
||||
|
||||
void TabletProxy::loadQMLSource(const QVariant& path) {
|
||||
if (_qmlTabletRoot) {
|
||||
|
||||
QObject* root = nullptr;
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
root = _qmlTabletRoot;
|
||||
} else if (_toolbarMode && _desktopWindow) {
|
||||
root = _desktopWindow->asQuickItem();
|
||||
}
|
||||
|
||||
if (root) {
|
||||
if (_state != State::QML) {
|
||||
removeButtonsFromHomeScreen();
|
||||
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, path));
|
||||
QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path));
|
||||
_state = State::QML;
|
||||
emit screenChanged(QVariant("QML"), path);
|
||||
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TabletProxy::gotoHomeScreen() {
|
||||
if (_qmlTabletRoot) {
|
||||
if (_state != State::Home) {
|
||||
if (_state != State::Home) {
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
|
||||
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()), Qt::DirectConnection);
|
||||
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
|
||||
QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound");
|
||||
_state = State::Home;
|
||||
emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL));
|
||||
} else if (_toolbarMode && _desktopWindow) {
|
||||
// close desktop window
|
||||
if (_desktopWindow->asQuickItem()) {
|
||||
QMetaObject::invokeMethod(_desktopWindow->asQuickItem(), "setShown", Q_ARG(const QVariant&, QVariant(false)));
|
||||
}
|
||||
}
|
||||
_state = State::Home;
|
||||
emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,31 +357,52 @@ void TabletProxy::gotoWebScreen(const QString& url) {
|
|||
}
|
||||
|
||||
void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl) {
|
||||
if (_qmlTabletRoot) {
|
||||
if (_state == State::Home) {
|
||||
removeButtonsFromHomeScreen();
|
||||
}
|
||||
if (_state != State::Web) {
|
||||
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL)));
|
||||
_state = State::Web;
|
||||
emit screenChanged(QVariant("Web"), QVariant(url));
|
||||
}
|
||||
QMetaObject::invokeMethod(_qmlTabletRoot, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)),
|
||||
Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl)));
|
||||
|
||||
QObject* root = nullptr;
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
root = _qmlTabletRoot;
|
||||
} else if (_toolbarMode && _desktopWindow) {
|
||||
root = _desktopWindow->asQuickItem();
|
||||
}
|
||||
|
||||
if (root) {
|
||||
QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL)));
|
||||
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
|
||||
QMetaObject::invokeMethod(root, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl)));
|
||||
}
|
||||
_state = State::Web;
|
||||
emit screenChanged(QVariant("Web"), QVariant(url));
|
||||
}
|
||||
|
||||
QObject* TabletProxy::addButton(const QVariant& properties) {
|
||||
auto tabletButtonProxy = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(properties.toMap()));
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
_tabletButtonProxies.push_back(tabletButtonProxy);
|
||||
if (_qmlTabletRoot) {
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
auto tablet = getQmlTablet();
|
||||
if (tablet) {
|
||||
addButtonProxyToQmlTablet(tablet, tabletButtonProxy.data());
|
||||
} else {
|
||||
qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml";
|
||||
}
|
||||
} else if (_toolbarMode) {
|
||||
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
|
||||
|
||||
Qt::ConnectionType connectionType = Qt::AutoConnection;
|
||||
if (QThread::currentThread() != toolbarProxy->thread()) {
|
||||
connectionType = Qt::BlockingQueuedConnection;
|
||||
}
|
||||
|
||||
// copy properties from tablet button proxy to toolbar button proxy.
|
||||
QObject* toolbarButtonProxy = nullptr;
|
||||
bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, tabletButtonProxy->getProperties()));
|
||||
if (hasResult) {
|
||||
tabletButtonProxy->setToolbarButtonProxy(toolbarButtonProxy);
|
||||
} else {
|
||||
qCWarning(scriptengine) << "ToolbarProxy addButton has no result";
|
||||
}
|
||||
}
|
||||
return tabletButtonProxy.data();
|
||||
}
|
||||
|
@ -298,11 +421,18 @@ void TabletProxy::removeButton(QObject* tabletButtonProxy) {
|
|||
|
||||
auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy);
|
||||
if (iter != _tabletButtonProxies.end()) {
|
||||
if (_qmlTabletRoot) {
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
(*iter)->setQmlButton(nullptr);
|
||||
if (tablet) {
|
||||
QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getProperties()));
|
||||
}
|
||||
} else if (_toolbarMode) {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
|
||||
|
||||
// remove button from toolbarProxy
|
||||
QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getUuid().toString()));
|
||||
(*iter)->setToolbarButtonProxy(nullptr);
|
||||
}
|
||||
_tabletButtonProxies.erase(iter);
|
||||
} else {
|
||||
|
@ -329,20 +459,24 @@ void TabletProxy::updateAudioBar(const double micLevel) {
|
|||
}
|
||||
|
||||
void TabletProxy::emitScriptEvent(QVariant msg) {
|
||||
if (_qmlOffscreenSurface) {
|
||||
if (!_toolbarMode && _qmlOffscreenSurface) {
|
||||
QMetaObject::invokeMethod(_qmlOffscreenSurface, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg));
|
||||
} else if (_toolbarMode && _desktopWindow) {
|
||||
QMetaObject::invokeMethod(_desktopWindow, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg));
|
||||
}
|
||||
}
|
||||
|
||||
void TabletProxy::sendToQml(QVariant msg) {
|
||||
if (_qmlOffscreenSurface) {
|
||||
if (!_toolbarMode && _qmlOffscreenSurface) {
|
||||
QMetaObject::invokeMethod(_qmlOffscreenSurface, "sendToQml", Qt::AutoConnection, Q_ARG(QVariant, msg));
|
||||
} else if (_toolbarMode && _desktopWindow) {
|
||||
QMetaObject::invokeMethod(_desktopWindow, "sendToQml", Qt::AutoConnection, Q_ARG(QVariant, msg));
|
||||
}
|
||||
}
|
||||
|
||||
void TabletProxy::addButtonsToHomeScreen() {
|
||||
auto tablet = getQmlTablet();
|
||||
if (!tablet) {
|
||||
if (!tablet || _toolbarMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -358,30 +492,51 @@ QObject* TabletProxy::getTabletSurface() {
|
|||
return _qmlOffscreenSurface;
|
||||
}
|
||||
|
||||
void TabletProxy::addButtonsToMenuScreen() {
|
||||
if (!_qmlTabletRoot) {
|
||||
return;
|
||||
void TabletProxy::removeButtonsFromHomeScreen() {
|
||||
auto tablet = getQmlTablet();
|
||||
for (auto& buttonProxy : _tabletButtonProxies) {
|
||||
if (tablet) {
|
||||
QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties()));
|
||||
}
|
||||
buttonProxy->setQmlButton(nullptr);
|
||||
}
|
||||
|
||||
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
|
||||
if (!loader) {
|
||||
return;
|
||||
}
|
||||
|
||||
QQuickItem* VrMenu = loader->findChild<QQuickItem*>("tabletMenu");
|
||||
if (VrMenu) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
QObject* menu = offscreenUi->getRootMenu();
|
||||
QMetaObject::invokeMethod(VrMenu, "setRootMenu", Qt::AutoConnection, Q_ARG(QVariant, QVariant::fromValue(menu)));
|
||||
}
|
||||
|
||||
QObject::disconnect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToMenuScreen()));
|
||||
}
|
||||
|
||||
void TabletProxy::removeButtonsFromHomeScreen() {
|
||||
void TabletProxy::desktopWindowClosed() {
|
||||
gotoHomeScreen();
|
||||
}
|
||||
|
||||
void TabletProxy::addButtonsToToolbar() {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
|
||||
|
||||
Qt::ConnectionType connectionType = Qt::AutoConnection;
|
||||
if (QThread::currentThread() != toolbarProxy->thread()) {
|
||||
connectionType = Qt::BlockingQueuedConnection;
|
||||
}
|
||||
|
||||
for (auto& buttonProxy : _tabletButtonProxies) {
|
||||
buttonProxy->setQmlButton(nullptr);
|
||||
// copy properties from tablet button proxy to toolbar button proxy.
|
||||
QObject* toolbarButtonProxy = nullptr;
|
||||
bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, buttonProxy->getProperties()));
|
||||
if (hasResult) {
|
||||
buttonProxy->setToolbarButtonProxy(toolbarButtonProxy);
|
||||
} else {
|
||||
qCWarning(scriptengine) << "ToolbarProxy addButton has no result";
|
||||
}
|
||||
}
|
||||
|
||||
// make the toolbar visible
|
||||
QMetaObject::invokeMethod(toolbarProxy, "writeProperty", Qt::AutoConnection, Q_ARG(QString, "visible"), Q_ARG(QVariant, QVariant(true)));
|
||||
}
|
||||
|
||||
void TabletProxy::removeButtonsFromToolbar() {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
|
||||
for (auto& buttonProxy : _tabletButtonProxies) {
|
||||
// remove button from toolbarProxy
|
||||
QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getUuid().toString()));
|
||||
buttonProxy->setToolbarButtonProxy(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -430,12 +585,14 @@ QQuickItem* TabletProxy::getQmlMenu() const {
|
|||
//
|
||||
|
||||
const QString UUID_KEY = "uuid";
|
||||
const QString OBJECT_NAME_KEY = "objectName";
|
||||
const QString STABLE_ORDER_KEY = "stableOrder";
|
||||
static int s_stableOrder = 1;
|
||||
|
||||
TabletButtonProxy::TabletButtonProxy(const QVariantMap& properties) : _uuid(QUuid::createUuid()), _stableOrder(++s_stableOrder), _properties(properties) {
|
||||
// this is used to uniquely identify this button.
|
||||
_properties[UUID_KEY] = _uuid;
|
||||
_properties[OBJECT_NAME_KEY] = _uuid.toString();
|
||||
_properties[STABLE_ORDER_KEY] = _stableOrder;
|
||||
}
|
||||
|
||||
|
@ -444,6 +601,14 @@ void TabletButtonProxy::setQmlButton(QQuickItem* qmlButton) {
|
|||
_qmlButton = qmlButton;
|
||||
}
|
||||
|
||||
void TabletButtonProxy::setToolbarButtonProxy(QObject* toolbarButtonProxy) {
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
_toolbarButtonProxy = toolbarButtonProxy;
|
||||
if (_toolbarButtonProxy) {
|
||||
QObject::connect(_toolbarButtonProxy, SIGNAL(clicked()), this, SLOT(clickedSlot()));
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap TabletButtonProxy::getProperties() const {
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
return _properties;
|
||||
|
@ -451,6 +616,7 @@ QVariantMap TabletButtonProxy::getProperties() const {
|
|||
|
||||
void TabletButtonProxy::editProperties(QVariantMap properties) {
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
|
||||
QVariantMap::const_iterator iter = properties.constBegin();
|
||||
while (iter != properties.constEnd()) {
|
||||
_properties[iter.key()] = iter.value();
|
||||
|
@ -459,6 +625,10 @@ void TabletButtonProxy::editProperties(QVariantMap properties) {
|
|||
}
|
||||
++iter;
|
||||
}
|
||||
|
||||
if (_toolbarButtonProxy) {
|
||||
QMetaObject::invokeMethod(_toolbarButtonProxy, "editProperties", Qt::AutoConnection, Q_ARG(QVariantMap, properties));
|
||||
}
|
||||
}
|
||||
|
||||
#include "TabletScriptingInterface.moc"
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
class TabletProxy;
|
||||
class TabletButtonProxy;
|
||||
class QmlWindowClass;
|
||||
|
||||
/**jsdoc
|
||||
* @namespace Tablet
|
||||
|
@ -35,6 +36,9 @@ class TabletScriptingInterface : public QObject, public Dependency {
|
|||
public:
|
||||
TabletScriptingInterface();
|
||||
|
||||
void setToolbarScriptingInterface(QObject* toolbarScriptingInterface) { _toolbarScriptingInterface = toolbarScriptingInterface; }
|
||||
QObject* getSystemToolbarProxy();
|
||||
|
||||
/**jsdoc
|
||||
* Creates or retruns a new TabletProxy and returns it.
|
||||
* @function Tablet.getTablet
|
||||
|
@ -43,6 +47,8 @@ public:
|
|||
*/
|
||||
Q_INVOKABLE QObject* getTablet(const QString& tabletId);
|
||||
|
||||
void setToolbarMode(bool toolbarMode);
|
||||
|
||||
void setQmlTabletRoot(QString tabletId, QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface);
|
||||
|
||||
void processEvent(const QKeyEvent* event);
|
||||
|
@ -58,15 +64,20 @@ private:
|
|||
protected:
|
||||
std::mutex _mutex;
|
||||
std::map<QString, QSharedPointer<TabletProxy>> _tabletProxies;
|
||||
QObject* _toolbarScriptingInterface { nullptr };
|
||||
bool _toolbarMode { false };
|
||||
};
|
||||
|
||||
/**jsdoc
|
||||
* @class TabletProxy
|
||||
* @property name {string} READ_ONLY: name of this tablet
|
||||
* @property toolbarMode {bool} - used to transition this tablet into and out of toolbar mode.
|
||||
* When tablet is in toolbar mode, all its buttons will appear in a floating toolbar.
|
||||
*/
|
||||
class TabletProxy : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString name READ getName)
|
||||
Q_PROPERTY(bool toolbarMode READ getToolbarMode WRITE setToolbarMode)
|
||||
public:
|
||||
TabletProxy(QString name);
|
||||
|
||||
|
@ -74,6 +85,11 @@ public:
|
|||
|
||||
Q_INVOKABLE void gotoMenuScreen(const QString& submenu = "");
|
||||
|
||||
QString getName() const { return _name; }
|
||||
|
||||
bool getToolbarMode() const { return _toolbarMode; }
|
||||
void setToolbarMode(bool toolbarMode);
|
||||
|
||||
/**jsdoc
|
||||
* transition to the home screen
|
||||
* @function TabletProxy#gotoHomeScreen
|
||||
|
@ -120,8 +136,6 @@ public:
|
|||
*/
|
||||
Q_INVOKABLE void updateAudioBar(const double micLevel);
|
||||
|
||||
QString getName() const { return _name; }
|
||||
|
||||
/**jsdoc
|
||||
* Used to send an event to the html/js embedded in the tablet
|
||||
* @function TabletProxy#emitScriptEvent
|
||||
|
@ -162,24 +176,28 @@ signals:
|
|||
void fromQml(QVariant msg);
|
||||
|
||||
/**jsdoc
|
||||
* Signales when this tablet screen changes.
|
||||
* Signaled when this tablet screen changes.
|
||||
* @function TabletProxy#screenChanged
|
||||
* @param type {string} - "Home", "Web", "Menu", "QML", "Closed"
|
||||
* @param url {string} - only valid for Web and QML.
|
||||
*/
|
||||
void screenChanged(QVariant type, QVariant url);
|
||||
|
||||
private slots:
|
||||
protected slots:
|
||||
void addButtonsToHomeScreen();
|
||||
void addButtonsToMenuScreen();
|
||||
void desktopWindowClosed();
|
||||
protected:
|
||||
void removeButtonsFromHomeScreen();
|
||||
void addButtonsToToolbar();
|
||||
void removeButtonsFromToolbar();
|
||||
|
||||
QString _name;
|
||||
std::mutex _mutex;
|
||||
std::vector<QSharedPointer<TabletButtonProxy>> _tabletButtonProxies;
|
||||
QQuickItem* _qmlTabletRoot { nullptr };
|
||||
QObject* _qmlOffscreenSurface { nullptr };
|
||||
QmlWindowClass* _desktopWindow { nullptr };
|
||||
bool _toolbarMode { false };
|
||||
|
||||
enum class State { Uninitialized, Home, Web, Menu, QML };
|
||||
State _state { State::Uninitialized };
|
||||
|
@ -196,6 +214,7 @@ public:
|
|||
TabletButtonProxy(const QVariantMap& properties);
|
||||
|
||||
void setQmlButton(QQuickItem* qmlButton);
|
||||
void setToolbarButtonProxy(QObject* toolbarButtonProxy);
|
||||
|
||||
QUuid getUuid() const { return _uuid; }
|
||||
|
||||
|
@ -229,6 +248,7 @@ protected:
|
|||
int _stableOrder;
|
||||
mutable std::mutex _mutex;
|
||||
QQuickItem* _qmlButton { nullptr };
|
||||
QObject* _toolbarButtonProxy { nullptr };
|
||||
QVariantMap _properties;
|
||||
};
|
||||
|
||||
|
|
|
@ -20,17 +20,22 @@ const QString InfoView::NAME{ "InfoView" };
|
|||
|
||||
Setting::Handle<QString> infoVersion("info-version", QString());
|
||||
|
||||
InfoView::InfoView(QQuickItem* parent) : QQuickItem(parent) {
|
||||
static bool registered{ false };
|
||||
|
||||
InfoView::InfoView(QQuickItem* parent) : QQuickItem(parent) {
|
||||
registerType();
|
||||
}
|
||||
|
||||
void InfoView::registerType() {
|
||||
qmlRegisterType<InfoView>("Hifi", 1, 0, NAME.toLocal8Bit().constData());
|
||||
}
|
||||
void InfoView::registerType() {
|
||||
if (!registered) {
|
||||
qmlRegisterType<InfoView>("Hifi", 1, 0, NAME.toLocal8Bit().constData());
|
||||
registered = true;
|
||||
}
|
||||
}
|
||||
|
||||
QString fetchVersion(const QUrl& url) {
|
||||
QXmlQuery query;
|
||||
query.bindVariable("file", QVariant(url));
|
||||
query.bindVariable("file", QVariant(url));
|
||||
query.setQuery("string((doc($file)//input[@id='version'])[1]/@value)");
|
||||
QString r;
|
||||
query.evaluateTo(&r);
|
||||
|
@ -38,14 +43,10 @@ QString fetchVersion(const QUrl& url) {
|
|||
}
|
||||
|
||||
void InfoView::show(const QString& path, bool firstOrChangedOnly, QString urlQuery) {
|
||||
static bool registered{ false };
|
||||
if (!registered) {
|
||||
registerType();
|
||||
registered = true;
|
||||
}
|
||||
registerType();
|
||||
QUrl url;
|
||||
if (QDir(path).isRelative()) {
|
||||
url = QUrl::fromLocalFile(PathUtils::resourcesPath() + path);
|
||||
url = QUrl::fromLocalFile(PathUtils::resourcesPath() + path);
|
||||
} else {
|
||||
url = QUrl::fromLocalFile(path);
|
||||
}
|
||||
|
@ -56,7 +57,7 @@ void InfoView::show(const QString& path, bool firstOrChangedOnly, QString urlQue
|
|||
const QString version = fetchVersion(url);
|
||||
// If we have version information stored
|
||||
if (lastVersion != QString::null) {
|
||||
// Check to see the document version. If it's valid and matches
|
||||
// Check to see the document version. If it's valid and matches
|
||||
// the stored version, we're done, so exit
|
||||
if (version == QString::null || version == lastVersion) {
|
||||
return;
|
||||
|
@ -87,4 +88,3 @@ void InfoView::setUrl(const QUrl& url) {
|
|||
emit urlChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,9 @@ public:
|
|||
QmlWindowClass();
|
||||
~QmlWindowClass();
|
||||
|
||||
virtual void initQml(QVariantMap properties);
|
||||
QQuickItem* asQuickItem() const;
|
||||
|
||||
public slots:
|
||||
bool isVisible() const;
|
||||
void setVisible(bool visible);
|
||||
|
@ -81,9 +84,6 @@ protected:
|
|||
|
||||
virtual QString qmlSource() const { return "QmlWindow.qml"; }
|
||||
|
||||
virtual void initQml(QVariantMap properties);
|
||||
QQuickItem* asQuickItem() const;
|
||||
|
||||
// FIXME needs to be initialized in the ctor once we have support
|
||||
// for tool window panes in QML
|
||||
bool _toolWindow { false };
|
||||
|
|
|
@ -9,49 +9,30 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var button;
|
||||
var TOOLBAR_BUTTON_NAME = "MUTE";
|
||||
var TABLET_BUTTON_NAME = "AUDIO";
|
||||
var toolBar = null;
|
||||
var tablet = null;
|
||||
var isHUDUIEnabled = Settings.getValue("HUDUIEnabled");
|
||||
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
|
||||
|
||||
function onMuteToggled() {
|
||||
if (isHUDUIEnabled) {
|
||||
button.editProperties({ isActive: AudioDevice.getMuted() });
|
||||
}
|
||||
button.editProperties({ isActive: AudioDevice.getMuted() });
|
||||
}
|
||||
function onClicked(){
|
||||
if (isHUDUIEnabled) {
|
||||
var menuItem = "Mute Microphone";
|
||||
Menu.setIsOptionChecked(menuItem, !Menu.isOptionChecked(menuItem));
|
||||
} else {
|
||||
var entity = HMD.tabletID;
|
||||
Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) });
|
||||
tablet.gotoMenuScreen("Audio");
|
||||
}
|
||||
var entity = HMD.tabletID;
|
||||
Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) });
|
||||
tablet.gotoMenuScreen("Audio");
|
||||
}
|
||||
|
||||
if (Settings.getValue("HUDUIEnabled")) {
|
||||
toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
|
||||
button = toolBar.addButton({
|
||||
objectName: TOOLBAR_BUTTON_NAME,
|
||||
imageURL: Script.resolvePath("assets/images/tools/mic.svg"),
|
||||
visible: true,
|
||||
alpha: 0.9
|
||||
});
|
||||
} else {
|
||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
button = tablet.addButton({
|
||||
icon: "icons/tablet-icons/mic-i.svg",
|
||||
text: TABLET_BUTTON_NAME,
|
||||
sortOrder: 1
|
||||
});
|
||||
}
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
var button = tablet.addButton({
|
||||
icon: "icons/tablet-icons/mic-unmute-i.svg",
|
||||
activeIcon: "icons/tablet-icons/mic-mute-a.svg",
|
||||
text: TABLET_BUTTON_NAME,
|
||||
sortOrder: 1
|
||||
});
|
||||
|
||||
onMuteToggled();
|
||||
|
||||
button.clicked.connect(onClicked);
|
||||
|
@ -60,12 +41,7 @@ AudioDevice.muteToggled.connect(onMuteToggled);
|
|||
Script.scriptEnding.connect(function () {
|
||||
button.clicked.disconnect(onClicked);
|
||||
AudioDevice.muteToggled.disconnect(onMuteToggled);
|
||||
if (tablet) {
|
||||
tablet.removeButton(button);
|
||||
}
|
||||
if (toolBar) {
|
||||
toolBar.removeButton(TOOLBAR_BUTTON_NAME);
|
||||
}
|
||||
tablet.removeButton(button);
|
||||
});
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
||||
|
|
|
@ -10,11 +10,9 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
/* global Toolbars, Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */
|
||||
|
||||
/* global Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */
|
||||
|
||||
(function () { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var button;
|
||||
// Used for animating and disappearing the bubble
|
||||
var bubbleOverlayTimestamp;
|
||||
|
@ -23,7 +21,7 @@
|
|||
// Used for flashing the HUD button upon activation
|
||||
var bubbleButtonTimestamp;
|
||||
// Affects bubble height
|
||||
const BUBBLE_HEIGHT_SCALE = 0.15;
|
||||
var BUBBLE_HEIGHT_SCALE = 0.15;
|
||||
// The bubble model itself
|
||||
var bubbleOverlay = Overlays.addOverlay("model", {
|
||||
url: Script.resolvePath("assets/models/Bubble-v14.fbx"), // If you'd like to change the model, modify this line (and the dimensions below)
|
||||
|
@ -39,16 +37,8 @@
|
|||
// Is the update() function connected?
|
||||
var updateConnected = false;
|
||||
|
||||
const BUBBLE_VISIBLE_DURATION_MS = 3000;
|
||||
const BUBBLE_RAISE_ANIMATION_DURATION_MS = 750;
|
||||
const BUBBLE_HUD_ICON_FLASH_INTERVAL_MS = 500;
|
||||
|
||||
var ASSETS_PATH = Script.resolvePath("assets");
|
||||
var TOOLS_PATH = Script.resolvePath("assets/images/tools/");
|
||||
|
||||
function buttonImageURL() {
|
||||
return TOOLS_PATH + 'bubble.svg';
|
||||
}
|
||||
var BUBBLE_VISIBLE_DURATION_MS = 3000;
|
||||
var BUBBLE_RAISE_ANIMATION_DURATION_MS = 750;
|
||||
|
||||
// Hides the bubble model overlay and resets the button flash state
|
||||
function hideOverlays() {
|
||||
|
@ -94,7 +84,7 @@
|
|||
}
|
||||
|
||||
// The bubble script's update function
|
||||
update = function () {
|
||||
function update() {
|
||||
var timestamp = Date.now();
|
||||
var delay = (timestamp - bubbleOverlayTimestamp);
|
||||
var overlayAlpha = 1.0 - (delay / BUBBLE_VISIBLE_DURATION_MS);
|
||||
|
@ -146,7 +136,7 @@
|
|||
var bubbleActive = Users.getIgnoreRadiusEnabled();
|
||||
writeButtonProperties(bubbleActive);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// When the space bubble is toggled...
|
||||
function onBubbleToggled() {
|
||||
|
@ -165,38 +155,26 @@
|
|||
|
||||
// Setup the bubble button
|
||||
var buttonName = "BUBBLE";
|
||||
if (Settings.getValue("HUDUIEnabled")) {
|
||||
var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
|
||||
button = toolbar.addButton({
|
||||
objectName: 'bubble',
|
||||
imageURL: buttonImageURL(),
|
||||
visible: true,
|
||||
alpha: 0.9
|
||||
});
|
||||
} else {
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
button = tablet.addButton({
|
||||
icon: "icons/tablet-icons/bubble-i.svg",
|
||||
activeIcon: "icons/tablet-icons/bubble-a.svg",
|
||||
text: buttonName,
|
||||
sortOrder: 4
|
||||
});
|
||||
}
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
button = tablet.addButton({
|
||||
icon: "icons/tablet-icons/bubble-i.svg",
|
||||
activeIcon: "icons/tablet-icons/bubble-a.svg",
|
||||
text: buttonName,
|
||||
sortOrder: 4
|
||||
});
|
||||
|
||||
onBubbleToggled();
|
||||
|
||||
button.clicked.connect(Users.toggleIgnoreRadius);
|
||||
Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled);
|
||||
Users.enteredIgnoreRadius.connect(enteredIgnoreRadius);
|
||||
|
||||
// Cleanup the toolbar button and overlays when script is stopped
|
||||
// Cleanup the tablet button and overlays when script is stopped
|
||||
Script.scriptEnding.connect(function () {
|
||||
button.clicked.disconnect(Users.toggleIgnoreRadius);
|
||||
if (tablet) {
|
||||
tablet.removeButton(button);
|
||||
}
|
||||
if (toolbar) {
|
||||
toolbar.removeButton('bubble');
|
||||
}
|
||||
Users.ignoreRadiusEnabledChanged.disconnect(onBubbleToggled);
|
||||
Users.enteredIgnoreRadius.disconnect(enteredIgnoreRadius);
|
||||
Overlays.deleteOverlay(bubbleOverlay);
|
||||
|
|
|
@ -10,48 +10,21 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
/* globals Tablet, Toolbars, Script, HMD, Controller, Menu */
|
||||
/* globals Tablet, Script, HMD, Controller, Menu */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var button;
|
||||
var buttonName = "HELP";
|
||||
var toolBar = null;
|
||||
var tablet = null;
|
||||
if (Settings.getValue("HUDUIEnabled")) {
|
||||
toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
|
||||
button = toolBar.addButton({
|
||||
objectName: buttonName,
|
||||
imageURL: Script.resolvePath("assets/images/tools/help.svg"),
|
||||
visible: true,
|
||||
alpha: 0.9
|
||||
});
|
||||
} else {
|
||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
button = tablet.addButton({
|
||||
icon: "icons/tablet-icons/help-i.svg",
|
||||
activeIcon: "icons/tablet-icons/help-a.svg",
|
||||
text: buttonName,
|
||||
sortOrder: 6
|
||||
});
|
||||
}
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
var button = tablet.addButton({
|
||||
icon: "icons/tablet-icons/help-i.svg",
|
||||
activeIcon: "icons/tablet-icons/help-a.svg",
|
||||
text: buttonName,
|
||||
sortOrder: 6
|
||||
});
|
||||
|
||||
var enabled = false;
|
||||
function onClicked() {
|
||||
// Similar logic to Application::showHelp()
|
||||
var defaultTab = "kbm";
|
||||
var handControllerName = "vive";
|
||||
if (HMD.active) {
|
||||
if ("Vive" in Controller.Hardware) {
|
||||
defaultTab = "handControllers";
|
||||
handControllerName = "vive";
|
||||
} else if ("OculusTouch" in Controller.Hardware) {
|
||||
defaultTab = "handControllers";
|
||||
handControllerName = "oculus";
|
||||
}
|
||||
} else if ("SDL2" in Controller.Hardware) {
|
||||
defaultTab = "gamepad";
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
Menu.closeInfoView('InfoView_html/help.html');
|
||||
enabled = !enabled;
|
||||
|
@ -80,9 +53,6 @@
|
|||
if (tablet) {
|
||||
tablet.removeButton(button);
|
||||
}
|
||||
if (toolBar) {
|
||||
toolBar.removeButton(buttonName);
|
||||
}
|
||||
});
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
/*globals HMD, Toolbars, Script, Menu, Tablet, Camera */
|
||||
/* globals HMD, Script, Menu, Tablet, Camera */
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
|
@ -37,20 +38,13 @@ function updateControllerDisplay() {
|
|||
}
|
||||
|
||||
var button;
|
||||
var toolBar = null;
|
||||
var tablet = null;
|
||||
|
||||
if (Settings.getValue("HUDUIEnabled")) {
|
||||
toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
|
||||
} else {
|
||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
}
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
|
||||
// Independent and Entity mode make people sick. Third Person and Mirror have traps that we need to work through.
|
||||
// Disable them in hmd.
|
||||
var desktopOnlyViews = ['Mirror', 'Independent Mode', 'Entity Mode'];
|
||||
|
||||
function onHmdChanged(isHmd) {
|
||||
//TODO change button icon when the hmd changes
|
||||
if (isHmd) {
|
||||
button.editProperties({
|
||||
icon: "icons/tablet-icons/switch-desk-i.svg",
|
||||
|
@ -67,25 +61,18 @@ function onHmdChanged(isHmd) {
|
|||
});
|
||||
updateControllerDisplay();
|
||||
}
|
||||
function onClicked(){
|
||||
|
||||
function onClicked() {
|
||||
var isDesktop = Menu.isOptionChecked(desktopMenuItemName);
|
||||
Menu.setIsOptionChecked(isDesktop ? headset : desktopMenuItemName, true);
|
||||
}
|
||||
|
||||
if (headset) {
|
||||
if (Settings.getValue("HUDUIEnabled")) {
|
||||
button = toolBar.addButton({
|
||||
objectName: "hmdToggle",
|
||||
imageURL: Script.resolvePath("assets/images/tools/switch.svg"),
|
||||
visible: true,
|
||||
alpha: 0.9
|
||||
});
|
||||
} else {
|
||||
button = tablet.addButton({
|
||||
icon: HMD.active ? "icons/tablet-icons/switch-desk-i.svg" : "icons/tablet-icons/switch-vr-i.svg",
|
||||
text: HMD.active ? "DESKTOP" : "VR",
|
||||
sortOrder: 2
|
||||
});
|
||||
}
|
||||
button = tablet.addButton({
|
||||
icon: HMD.active ? "icons/tablet-icons/switch-desk-i.svg" : "icons/tablet-icons/switch-vr-i.svg",
|
||||
text: HMD.active ? "DESKTOP" : "VR",
|
||||
sortOrder: 2
|
||||
});
|
||||
onHmdChanged(HMD.active);
|
||||
|
||||
button.clicked.connect(onClicked);
|
||||
|
@ -97,9 +84,6 @@ if (headset) {
|
|||
if (tablet) {
|
||||
tablet.removeButton(button);
|
||||
}
|
||||
if (toolBar) {
|
||||
toolBar.removeButton("hmdToggle");
|
||||
}
|
||||
HMD.displayModeChanged.disconnect(onHmdChanged);
|
||||
Camera.modeUpdated.disconnect(updateControllerDisplay);
|
||||
});
|
||||
|
|
|
@ -450,6 +450,11 @@
|
|||
<input type="checkbox" id="property-zone-ghosting-allowed">
|
||||
<label for="property-zone-ghosting-allowed">Ghosting allowed</label>
|
||||
</div>
|
||||
<hr class="zone-group zone-section">
|
||||
<div class="zone-group zone-section property url ">
|
||||
<label for="property-zone-filter-url">Filter URL</label>
|
||||
<input type="text" id="property-zone-filter-url">
|
||||
</div>
|
||||
<div class="sub-section-header zone-group zone-section keylight-section">
|
||||
<label>Key Light</label>
|
||||
</div>
|
||||
|
|
|
@ -697,6 +697,7 @@ function loaded() {
|
|||
|
||||
var elZoneFlyingAllowed = document.getElementById("property-zone-flying-allowed");
|
||||
var elZoneGhostingAllowed = document.getElementById("property-zone-ghosting-allowed");
|
||||
var elZoneFilterURL = document.getElementById("property-zone-filter-url");
|
||||
|
||||
var elPolyVoxSections = document.querySelectorAll(".poly-vox-section");
|
||||
allSections.push(elPolyVoxSections);
|
||||
|
@ -1032,6 +1033,7 @@ function loaded() {
|
|||
|
||||
elZoneFlyingAllowed.checked = properties.flyingAllowed;
|
||||
elZoneGhostingAllowed.checked = properties.ghostingAllowed;
|
||||
elZoneFilterURL.value = properties.filterURL;
|
||||
|
||||
showElements(document.getElementsByClassName('skybox-section'), elZoneBackgroundMode.value == 'skybox');
|
||||
} else if (properties.type == "PolyVox") {
|
||||
|
@ -1387,7 +1389,8 @@ function loaded() {
|
|||
|
||||
elZoneFlyingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('flyingAllowed'));
|
||||
elZoneGhostingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ghostingAllowed'));
|
||||
|
||||
elZoneFilterURL.addEventListener('change', createEmitTextPropertyUpdateFunction('filterURL'));
|
||||
|
||||
var voxelVolumeSizeChangeFunction = createEmitVec3PropertyUpdateFunction(
|
||||
'voxelVolumeSize', elVoxelVolumeSizeX, elVoxelVolumeSizeY, elVoxelVolumeSizeZ);
|
||||
elVoxelVolumeSizeX.addEventListener('change', voxelVolumeSizeChangeFunction);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
/* global Tablet, Script, HMD, Toolbars, UserActivityLogger, Entities */
|
||||
/* global Tablet, Script, HMD, UserActivityLogger, Entities */
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
@ -33,8 +33,6 @@ var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS";
|
|||
var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS";
|
||||
var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS";
|
||||
|
||||
var marketplaceWindow = null;
|
||||
|
||||
var CLARA_DOWNLOAD_TITLE = "Preparing Download";
|
||||
var messageBox = null;
|
||||
var isDownloadBeingCancelled = false;
|
||||
|
@ -57,52 +55,47 @@ Window.messageBoxClosed.connect(onMessageBoxClosed);
|
|||
function showMarketplace() {
|
||||
UserActivityLogger.openedMarketplace();
|
||||
|
||||
if (tablet) {
|
||||
tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
|
||||
tablet.webEventReceived.connect(function (message) {
|
||||
if (message === GOTO_DIRECTORY) {
|
||||
tablet.gotoWebScreen(MARKETPLACES_URL);
|
||||
}
|
||||
tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
|
||||
tablet.webEventReceived.connect(function (message) {
|
||||
|
||||
if (message === QUERY_CAN_WRITE_ASSETS) {
|
||||
tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets());
|
||||
}
|
||||
if (message === GOTO_DIRECTORY) {
|
||||
tablet.gotoWebScreen(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL);
|
||||
}
|
||||
|
||||
if (message === WARN_USER_NO_PERMISSIONS) {
|
||||
Window.alert(NO_PERMISSIONS_ERROR_MESSAGE);
|
||||
}
|
||||
if (message === QUERY_CAN_WRITE_ASSETS) {
|
||||
tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets());
|
||||
}
|
||||
|
||||
if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) {
|
||||
if (isDownloadBeingCancelled) {
|
||||
return;
|
||||
}
|
||||
if (message === WARN_USER_NO_PERMISSIONS) {
|
||||
Window.alert(NO_PERMISSIONS_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
var text = message.slice(CLARA_IO_STATUS.length);
|
||||
if (messageBox === null) {
|
||||
messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
|
||||
} else {
|
||||
Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
|
||||
}
|
||||
if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) {
|
||||
if (isDownloadBeingCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) {
|
||||
if (messageBox !== null) {
|
||||
Window.closeMessageBox(messageBox);
|
||||
messageBox = null;
|
||||
}
|
||||
return;
|
||||
var text = message.slice(CLARA_IO_STATUS.length);
|
||||
if (messageBox === null) {
|
||||
messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
|
||||
} else {
|
||||
Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (message === CLARA_IO_CANCELLED_DOWNLOAD) {
|
||||
isDownloadBeingCancelled = false;
|
||||
if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) {
|
||||
if (messageBox !== null) {
|
||||
Window.closeMessageBox(messageBox);
|
||||
messageBox = null;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
marketplaceWindow.setURL(MARKETPLACE_URL_INITIAL);
|
||||
marketplaceWindow.setVisible(true);
|
||||
marketplaceVisible = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (message === CLARA_IO_CANCELLED_DOWNLOAD) {
|
||||
isDownloadBeingCancelled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleMarketplace() {
|
||||
|
@ -111,33 +104,12 @@ function toggleMarketplace() {
|
|||
showMarketplace();
|
||||
}
|
||||
|
||||
var tablet = null;
|
||||
var toolBar = null;
|
||||
var marketplaceButton = null;
|
||||
if (Settings.getValue("HUDUIEnabled")) {
|
||||
marketplaceWindow = new OverlayWebWindow({
|
||||
title: "Marketplace",
|
||||
source: "about:blank",
|
||||
width: 900,
|
||||
height: 700,
|
||||
visible: false
|
||||
});
|
||||
marketplaceWindow.setScriptURL(MARKETPLACES_INJECT_SCRIPT_URL);
|
||||
toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
|
||||
var toolIconUrl = Script.resolvePath("../assets/images/tools/");
|
||||
marketplaceButton = toolBar.addButton({
|
||||
imageURL: toolIconUrl + "market.svg",
|
||||
objectName: "marketplace",
|
||||
alpha: 0.9
|
||||
});
|
||||
} else {
|
||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
marketplaceButton = tablet.addButton({
|
||||
icon: "icons/tablet-icons/market-i.svg",
|
||||
text: "MARKET",
|
||||
sortOrder: 9
|
||||
});
|
||||
}
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
var marketplaceButton = tablet.addButton({
|
||||
icon: "icons/tablet-icons/market-i.svg",
|
||||
text: "MARKET",
|
||||
sortOrder: 9
|
||||
});
|
||||
|
||||
function onCanWriteAssetsChanged() {
|
||||
var message = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets();
|
||||
|
@ -152,9 +124,6 @@ marketplaceButton.clicked.connect(onClick);
|
|||
Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged);
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
if (toolBar) {
|
||||
toolBar.removeButton("marketplace");
|
||||
}
|
||||
if (tablet) {
|
||||
tablet.removeButton(marketplaceButton);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"use strict";
|
||||
/*jslint vars: true, plusplus: true, forin: true*/
|
||||
/*globals Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, OverlayWindow, Toolbars, Vec3, Quat, Controller, print, getControllerWorldLocation */
|
||||
/* jslint vars: true, plusplus: true, forin: true*/
|
||||
/* globals Tablet, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, Controller, print, getControllerWorldLocation */
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
//
|
||||
// pal.js
|
||||
//
|
||||
|
@ -13,21 +14,24 @@
|
|||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
|
||||
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
|
||||
// something, will revisit as this is sorta horrible.
|
||||
const UNSELECTED_TEXTURES = {"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"),
|
||||
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png")
|
||||
var UNSELECTED_TEXTURES = {
|
||||
"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"),
|
||||
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png")
|
||||
};
|
||||
const SELECTED_TEXTURES = { "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"),
|
||||
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png")
|
||||
var SELECTED_TEXTURES = {
|
||||
"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"),
|
||||
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png")
|
||||
};
|
||||
const HOVER_TEXTURES = { "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png"),
|
||||
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png")
|
||||
var HOVER_TEXTURES = {
|
||||
"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png"),
|
||||
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png")
|
||||
};
|
||||
|
||||
const UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6};
|
||||
const SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29};
|
||||
const HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now
|
||||
var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6};
|
||||
var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29};
|
||||
var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now
|
||||
|
||||
var conserveResources = true;
|
||||
|
||||
|
@ -87,24 +91,24 @@ ExtendedOverlay.prototype.hover = function (hovering) {
|
|||
} else {
|
||||
lastHoveringId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.editOverlay({color: color(this.selected, hovering, this.audioLevel)});
|
||||
if (this.model) {
|
||||
this.model.editOverlay({textures: textures(this.selected, hovering)});
|
||||
}
|
||||
if (hovering) {
|
||||
// un-hover the last hovering overlay
|
||||
if (lastHoveringId && lastHoveringId != this.key) {
|
||||
if (lastHoveringId && lastHoveringId !== this.key) {
|
||||
ExtendedOverlay.get(lastHoveringId).hover(false);
|
||||
}
|
||||
lastHoveringId = this.key;
|
||||
}
|
||||
}
|
||||
};
|
||||
ExtendedOverlay.prototype.select = function (selected) {
|
||||
if (this.selected === selected) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
UserActivityLogger.palAction(selected ? "avatar_selected" : "avatar_deselected", this.key);
|
||||
|
||||
this.editOverlay({color: color(selected, this.hovering, this.audioLevel)});
|
||||
|
@ -193,17 +197,8 @@ HighlightedEntity.updateOverlays = function updateHighlightedEntities() {
|
|||
});
|
||||
};
|
||||
|
||||
//
|
||||
// The qml window and communications.
|
||||
//
|
||||
var pal = new OverlayWindow({
|
||||
title: 'People Action List',
|
||||
source: 'hifi/Pal.qml',
|
||||
width: 580,
|
||||
height: 640,
|
||||
visible: false
|
||||
});
|
||||
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
|
||||
var data;
|
||||
switch (message.method) {
|
||||
case 'selected':
|
||||
selectedIds = message.params;
|
||||
|
@ -250,7 +245,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
}
|
||||
break;
|
||||
case 'displayNameUpdate':
|
||||
if (MyAvatar.displayName != message.params) {
|
||||
if (MyAvatar.displayName !== message.params) {
|
||||
MyAvatar.displayName = message.params;
|
||||
UserActivityLogger.palAction("display_name_change", "");
|
||||
}
|
||||
|
@ -261,11 +256,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
}
|
||||
|
||||
function sendToQml(message) {
|
||||
if (Settings.getValue("HUDUIEnabled")) {
|
||||
pal.sendToQml(message);
|
||||
} else {
|
||||
tablet.sendToQml(message);
|
||||
}
|
||||
tablet.sendToQml(message);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -273,7 +264,7 @@ function sendToQml(message) {
|
|||
//
|
||||
function addAvatarNode(id) {
|
||||
var selected = ExtendedOverlay.isSelected(id);
|
||||
return new ExtendedOverlay(id, "sphere", {
|
||||
return new ExtendedOverlay(id, "sphere", {
|
||||
drawInFront: true,
|
||||
solid: true,
|
||||
alpha: 0.8,
|
||||
|
@ -290,17 +281,14 @@ function populateUserList(selectData) {
|
|||
userName: '',
|
||||
sessionId: id || '',
|
||||
audioLevel: 0.0,
|
||||
admin: false
|
||||
admin: false,
|
||||
personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null
|
||||
ignore: !!id && Users.getIgnoreStatus(id) // ditto
|
||||
};
|
||||
// Request the username, fingerprint, and admin status from the given UUID
|
||||
// Username and fingerprint returns default constructor output if the requesting user isn't an admin
|
||||
Users.requestUsernameFromID(id);
|
||||
// Request personal mute status and ignore status
|
||||
// from NodeList (as long as we're not requesting it for our own ID)
|
||||
if (id) {
|
||||
avatarPalDatum['personalMute'] = Users.getPersonalMuteStatus(id);
|
||||
avatarPalDatum['ignore'] = Users.getIgnoreStatus(id);
|
||||
addAvatarNode(id); // No overlay for ourselves
|
||||
// Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin.
|
||||
Users.requestUsernameFromID(id);
|
||||
}
|
||||
data.push(avatarPalDatum);
|
||||
print('PAL data:', JSON.stringify(avatarPalDatum));
|
||||
|
@ -314,20 +302,13 @@ function populateUserList(selectData) {
|
|||
|
||||
// The function that handles the reply from the server
|
||||
function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
|
||||
var data;
|
||||
// If the ID we've received is our ID...
|
||||
if (MyAvatar.sessionUUID === id) {
|
||||
// Set the data to contain specific strings.
|
||||
data = ['', username, isAdmin];
|
||||
} else if (Users.canKick) {
|
||||
// Set the data to contain the ID and the username (if we have one)
|
||||
// or fingerprint (if we don't have a username) string.
|
||||
data = [id, username || machineFingerprint, isAdmin];
|
||||
} else {
|
||||
// Set the data to contain specific strings.
|
||||
data = [id, '', isAdmin];
|
||||
}
|
||||
print('Username Data:', JSON.stringify(data));
|
||||
var data = [
|
||||
(MyAvatar.sessionUUID === id) ? '' : id, // Pal.qml recognizes empty id specially.
|
||||
// If we get username (e.g., if in future we receive it when we're friends), use it.
|
||||
// Otherwise, use valid machineFingerprint (which is not valid when not an admin).
|
||||
username || (Users.canKick && machineFingerprint) || '',
|
||||
isAdmin
|
||||
];
|
||||
// Ship the data off to QML
|
||||
sendToQml({ method: 'updateUsername', params: data });
|
||||
}
|
||||
|
@ -339,17 +320,19 @@ function updateOverlays() {
|
|||
if (!id) {
|
||||
return; // don't update ourself
|
||||
}
|
||||
|
||||
var avatar = AvatarList.getAvatar(id);
|
||||
if (!avatar) {
|
||||
return; // will be deleted below if there had been an overlay.
|
||||
}
|
||||
var overlay = ExtendedOverlay.get(id);
|
||||
if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
|
||||
print('Adding non-PAL avatar node', id);
|
||||
overlay = addAvatarNode(id);
|
||||
}
|
||||
var avatar = AvatarList.getAvatar(id);
|
||||
var target = avatar.position;
|
||||
var distance = Vec3.distance(target, eye);
|
||||
var offset = 0.2;
|
||||
|
||||
|
||||
// base offset on 1/2 distance from hips to head if we can
|
||||
var headIndex = avatar.getJointIndex("Head");
|
||||
if (headIndex > 0) {
|
||||
|
@ -358,7 +341,7 @@ function updateOverlays() {
|
|||
|
||||
// get diff between target and eye (a vector pointing to the eye from avatar position)
|
||||
var diff = Vec3.subtract(target, eye);
|
||||
|
||||
|
||||
// move a bit in front, towards the camera
|
||||
target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
|
||||
|
||||
|
@ -369,12 +352,12 @@ function updateOverlays() {
|
|||
overlay.editOverlay({
|
||||
color: color(ExtendedOverlay.isSelected(id), overlay.hovering, overlay.audioLevel),
|
||||
position: target,
|
||||
dimensions: 0.032 * distance
|
||||
dimensions: 0.032 * distance
|
||||
});
|
||||
if (overlay.model) {
|
||||
overlay.model.ping = pingPong;
|
||||
overlay.model.editOverlay({
|
||||
position: target,
|
||||
position: target,
|
||||
scale: 0.2 * distance, // constant apparent size
|
||||
rotation: Camera.orientation
|
||||
});
|
||||
|
@ -393,7 +376,9 @@ function removeOverlays() {
|
|||
selectedIds = [];
|
||||
lastHoveringId = 0;
|
||||
HighlightedEntity.clearOverlays();
|
||||
ExtendedOverlay.some(function (overlay) { overlay.deleteOverlay(); });
|
||||
ExtendedOverlay.some(function (overlay) {
|
||||
overlay.deleteOverlay();
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -423,12 +408,13 @@ function handleMouseMove(pickRay) { // given the pickRay, just do the hover logi
|
|||
|
||||
// handy global to keep track of which hand is the mouse (if any)
|
||||
var currentHandPressed = 0;
|
||||
const TRIGGER_CLICK_THRESHOLD = 0.85;
|
||||
const TRIGGER_PRESS_THRESHOLD = 0.05;
|
||||
var TRIGGER_CLICK_THRESHOLD = 0.85;
|
||||
var TRIGGER_PRESS_THRESHOLD = 0.05;
|
||||
|
||||
function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position
|
||||
var pickRay;
|
||||
if (HMD.active) {
|
||||
if (currentHandPressed != 0) {
|
||||
if (currentHandPressed !== 0) {
|
||||
pickRay = controllerComputePickRay(currentHandPressed);
|
||||
} else {
|
||||
// nothing should hover, so
|
||||
|
@ -441,18 +427,18 @@ function handleMouseMoveEvent(event) { // find out which overlay (if any) is ove
|
|||
handleMouseMove(pickRay);
|
||||
}
|
||||
function handleTriggerPressed(hand, value) {
|
||||
// The idea is if you press one trigger, it is the one
|
||||
// The idea is if you press one trigger, it is the one
|
||||
// we will consider the mouse. Even if the other is pressed,
|
||||
// we ignore it until this one is no longer pressed.
|
||||
isPressed = value > TRIGGER_PRESS_THRESHOLD;
|
||||
if (currentHandPressed == 0) {
|
||||
var isPressed = value > TRIGGER_PRESS_THRESHOLD;
|
||||
if (currentHandPressed === 0) {
|
||||
currentHandPressed = isPressed ? hand : 0;
|
||||
return;
|
||||
}
|
||||
if (currentHandPressed == hand) {
|
||||
if (currentHandPressed === hand) {
|
||||
currentHandPressed = isPressed ? hand : 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// otherwise, the other hand is still triggered
|
||||
// so do nothing.
|
||||
}
|
||||
|
@ -478,7 +464,7 @@ function makeClickHandler(hand) {
|
|||
function makePressHandler(hand) {
|
||||
return function (value) {
|
||||
handleTriggerPressed(hand, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
|
||||
triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
|
||||
|
@ -490,17 +476,14 @@ triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Cont
|
|||
var button;
|
||||
var buttonName = "PEOPLE";
|
||||
var tablet = null;
|
||||
var toolBar = null;
|
||||
if (Settings.getValue("HUDUIEnabled")) {
|
||||
toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
|
||||
button = toolBar.addButton({
|
||||
objectName: buttonName,
|
||||
imageURL: Script.resolvePath("assets/images/tools/people.svg"),
|
||||
visible: true,
|
||||
alpha: 0.9
|
||||
});
|
||||
pal.fromQml.connect(fromQml);
|
||||
} else {
|
||||
|
||||
function onTabletScreenChanged(type, url) {
|
||||
if (type !== "QML" || url !== "../Pal.qml") {
|
||||
off();
|
||||
}
|
||||
}
|
||||
|
||||
function startup() {
|
||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
button = tablet.addButton({
|
||||
text: buttonName,
|
||||
|
@ -508,8 +491,19 @@ if (Settings.getValue("HUDUIEnabled")) {
|
|||
sortOrder: 7
|
||||
});
|
||||
tablet.fromQml.connect(fromQml);
|
||||
button.clicked.connect(onTabletButtonClicked);
|
||||
tablet.screenChanged.connect(onTabletScreenChanged);
|
||||
|
||||
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
||||
Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
|
||||
Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
|
||||
Messages.subscribe(CHANNEL);
|
||||
Messages.messageReceived.connect(receiveMessage);
|
||||
Users.avatarDisconnected.connect(avatarDisconnected);
|
||||
}
|
||||
|
||||
startup();
|
||||
|
||||
var isWired = false;
|
||||
var audioTimer;
|
||||
var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too)
|
||||
|
@ -521,41 +515,26 @@ function off() {
|
|||
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
|
||||
isWired = false;
|
||||
}
|
||||
if (audioTimer) { Script.clearInterval(audioTimer); }
|
||||
if (audioTimer) {
|
||||
Script.clearInterval(audioTimer);
|
||||
}
|
||||
triggerMapping.disable(); // It's ok if we disable twice.
|
||||
triggerPressMapping.disable(); // see above
|
||||
removeOverlays();
|
||||
Users.requestsDomainListData = false;
|
||||
}
|
||||
function onClicked() {
|
||||
if (Settings.getValue("HUDUIEnabled")) {
|
||||
if (!pal.visible) {
|
||||
Users.requestsDomainListData = true;
|
||||
populateUserList();
|
||||
pal.raise();
|
||||
isWired = true;
|
||||
Script.update.connect(updateOverlays);
|
||||
Controller.mousePressEvent.connect(handleMouseEvent);
|
||||
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
|
||||
triggerMapping.enable();
|
||||
triggerPressMapping.enable();
|
||||
audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS);
|
||||
} else {
|
||||
off();
|
||||
}
|
||||
pal.setVisible(!pal.visible);
|
||||
} else {
|
||||
tablet.loadQMLSource("../Pal.qml");
|
||||
Users.requestsDomainListData = true;
|
||||
populateUserList();
|
||||
isWired = true;
|
||||
Script.update.connect(updateOverlays);
|
||||
Controller.mousePressEvent.connect(handleMouseEvent);
|
||||
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
|
||||
triggerMapping.enable();
|
||||
triggerPressMapping.enable();
|
||||
audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS);
|
||||
}
|
||||
|
||||
function onTabletButtonClicked() {
|
||||
tablet.loadQMLSource("../Pal.qml");
|
||||
Users.requestsDomainListData = true;
|
||||
populateUserList();
|
||||
isWired = true;
|
||||
Script.update.connect(updateOverlays);
|
||||
Controller.mousePressEvent.connect(handleMouseEvent);
|
||||
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
|
||||
triggerMapping.enable();
|
||||
triggerPressMapping.enable();
|
||||
audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -570,17 +549,12 @@ function receiveMessage(channel, messageString, senderID) {
|
|||
var message = JSON.parse(messageString);
|
||||
switch (message.method) {
|
||||
case 'select':
|
||||
if (!pal.visible) {
|
||||
onClicked();
|
||||
}
|
||||
sendToQml(message); // Accepts objects, not just strings.
|
||||
break;
|
||||
default:
|
||||
print('Unrecognized PAL message', messageString);
|
||||
}
|
||||
}
|
||||
Messages.subscribe(CHANNEL);
|
||||
Messages.messageReceived.connect(receiveMessage);
|
||||
|
||||
|
||||
var AVERAGING_RATIO = 0.05;
|
||||
|
@ -638,57 +612,29 @@ function avatarDisconnected(nodeID) {
|
|||
// remove from the pal list
|
||||
sendToQml({method: 'avatarDisconnected', params: [nodeID]});
|
||||
}
|
||||
//
|
||||
// Button state.
|
||||
//
|
||||
function onVisibleChanged() {
|
||||
button.editProperties({isActive: pal.visible});
|
||||
}
|
||||
button.clicked.connect(onClicked);
|
||||
pal.visibleChanged.connect(onVisibleChanged);
|
||||
pal.closed.connect(off);
|
||||
|
||||
if (!Settings.getValue("HUDUIEnabled")) {
|
||||
tablet.screenChanged.connect(function (type, url) {
|
||||
if (type !== "QML" || url !== "../Pal.qml") {
|
||||
off();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
||||
Users.avatarDisconnected.connect(avatarDisconnected);
|
||||
|
||||
function clearLocalQMLDataAndClosePAL() {
|
||||
sendToQml({ method: 'clearLocalQMLData' });
|
||||
if (pal.visible) {
|
||||
onClicked(); // Close the PAL
|
||||
}
|
||||
}
|
||||
Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
|
||||
Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
|
||||
|
||||
function shutdown() {
|
||||
button.clicked.disconnect(onTabletButtonClicked);
|
||||
tablet.removeButton(button);
|
||||
tablet.screenChanged.disconnect(onTabletScreenChanged);
|
||||
|
||||
Users.usernameFromIDReply.disconnect(usernameFromIDReply);
|
||||
Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL);
|
||||
Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
|
||||
Messages.subscribe(CHANNEL);
|
||||
Messages.messageReceived.disconnect(receiveMessage);
|
||||
Users.avatarDisconnected.disconnect(avatarDisconnected);
|
||||
|
||||
off();
|
||||
}
|
||||
|
||||
//
|
||||
// Cleanup.
|
||||
//
|
||||
Script.scriptEnding.connect(function () {
|
||||
button.clicked.disconnect(onClicked);
|
||||
if (tablet) {
|
||||
tablet.removeButton(button);
|
||||
}
|
||||
if (toolBar) {
|
||||
toolBar.removeButton(buttonName);
|
||||
}
|
||||
pal.visibleChanged.disconnect(onVisibleChanged);
|
||||
pal.closed.disconnect(off);
|
||||
Users.usernameFromIDReply.disconnect(usernameFromIDReply);
|
||||
Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL);
|
||||
Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
|
||||
Messages.unsubscribe(CHANNEL);
|
||||
Messages.messageReceived.disconnect(receiveMessage);
|
||||
Users.avatarDisconnected.disconnect(avatarDisconnected);
|
||||
off();
|
||||
});
|
||||
|
||||
Script.scriptEnding.connect(shutdown);
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
// Distributed under the Apache License, Version 2.0
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
/* globals Tablet, Toolbars, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar */
|
||||
/* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar */
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
|
@ -17,29 +18,15 @@ var resetOverlays;
|
|||
var reticleVisible;
|
||||
var clearOverlayWhenMoving;
|
||||
|
||||
var button;
|
||||
var buttonName = "SNAP";
|
||||
var tablet = null;
|
||||
var toolBar = null;
|
||||
|
||||
var buttonConnected = false;
|
||||
|
||||
if (Settings.getValue("HUDUIEnabled")) {
|
||||
toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
|
||||
button = toolBar.addButton({
|
||||
objectName: buttonName,
|
||||
imageURL: Script.resolvePath("assets/images/tools/snap.svg"),
|
||||
visible: true,
|
||||
alpha: 0.9,
|
||||
});
|
||||
} else {
|
||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
button = tablet.addButton({
|
||||
icon: "icons/tablet-icons/snap-i.svg",
|
||||
text: buttonName,
|
||||
sortOrder: 5
|
||||
});
|
||||
}
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
var button = tablet.addButton({
|
||||
icon: "icons/tablet-icons/snap-i.svg",
|
||||
text: buttonName,
|
||||
sortOrder: 5
|
||||
});
|
||||
|
||||
function shouldOpenFeedAfterShare() {
|
||||
var persisted = Settings.getValue('openFeedAfterShare', true); // might answer true, false, "true", or "false"
|
||||
|
@ -63,42 +50,42 @@ function confirmShare(data) {
|
|||
var isLoggedIn;
|
||||
var needsLogin = false;
|
||||
switch (message) {
|
||||
case 'ready':
|
||||
dialog.emitScriptEvent(data); // Send it.
|
||||
outstanding = 0;
|
||||
break;
|
||||
case 'openSettings':
|
||||
Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog");
|
||||
break;
|
||||
case 'setOpenFeedFalse':
|
||||
Settings.setValue('openFeedAfterShare', false);
|
||||
break;
|
||||
case 'setOpenFeedTrue':
|
||||
Settings.setValue('openFeedAfterShare', true);
|
||||
break;
|
||||
default:
|
||||
dialog.webEventReceived.disconnect(onMessage);
|
||||
dialog.close();
|
||||
isLoggedIn = Account.isLoggedIn();
|
||||
message.forEach(function (submessage) {
|
||||
if (submessage.share && !isLoggedIn) {
|
||||
needsLogin = true;
|
||||
submessage.share = false;
|
||||
}
|
||||
if (submessage.share) {
|
||||
print('sharing', submessage.localPath);
|
||||
outstanding++;
|
||||
Window.shareSnapshot(submessage.localPath, submessage.href);
|
||||
} else {
|
||||
print('not sharing', submessage.localPath);
|
||||
}
|
||||
});
|
||||
if (!outstanding && shouldOpenFeedAfterShare()) {
|
||||
showFeedWindow();
|
||||
case 'ready':
|
||||
dialog.emitScriptEvent(data); // Send it.
|
||||
outstanding = 0;
|
||||
break;
|
||||
case 'openSettings':
|
||||
Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog");
|
||||
break;
|
||||
case 'setOpenFeedFalse':
|
||||
Settings.setValue('openFeedAfterShare', false);
|
||||
break;
|
||||
case 'setOpenFeedTrue':
|
||||
Settings.setValue('openFeedAfterShare', true);
|
||||
break;
|
||||
default:
|
||||
dialog.webEventReceived.disconnect(onMessage);
|
||||
dialog.close();
|
||||
isLoggedIn = Account.isLoggedIn();
|
||||
message.forEach(function (submessage) {
|
||||
if (submessage.share && !isLoggedIn) {
|
||||
needsLogin = true;
|
||||
submessage.share = false;
|
||||
}
|
||||
if (needsLogin) { // after the possible feed, so that the login is on top
|
||||
Account.checkAndSignalForAccessToken();
|
||||
if (submessage.share) {
|
||||
print('sharing', submessage.localPath);
|
||||
outstanding++;
|
||||
Window.shareSnapshot(submessage.localPath, submessage.href);
|
||||
} else {
|
||||
print('not sharing', submessage.localPath);
|
||||
}
|
||||
});
|
||||
if (!outstanding && shouldOpenFeedAfterShare()) {
|
||||
showFeedWindow();
|
||||
}
|
||||
if (needsLogin) { // after the possible feed, so that the login is on top
|
||||
Account.checkAndSignalForAccessToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
dialog.webEventReceived.connect(onMessage);
|
||||
|
@ -159,7 +146,7 @@ function isDomainOpen(id) {
|
|||
var url = location.metaverseServerUrl + "/api/v1/user_stories?" + options.join('&');
|
||||
request.open("GET", url, false);
|
||||
request.send();
|
||||
if (request.status != 200) {
|
||||
if (request.status !== 200) {
|
||||
return false;
|
||||
}
|
||||
var response = JSON.parse(request.response); // Not parsed for us.
|
||||
|
@ -229,9 +216,6 @@ Script.scriptEnding.connect(function () {
|
|||
if (tablet) {
|
||||
tablet.removeButton(button);
|
||||
}
|
||||
if (toolBar) {
|
||||
toolBar.removeButton(buttonName);
|
||||
}
|
||||
Window.snapshotShared.disconnect(snapshotShared);
|
||||
Window.processingGif.disconnect(processingGif);
|
||||
});
|
||||
|
|
|
@ -12,54 +12,27 @@
|
|||
//
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
var gotoQmlSource = "TabletAddressDialog.qml";
|
||||
var button;
|
||||
var gotoQmlSource = "TabletAddressDialog.qml";
|
||||
var buttonName = "GOTO";
|
||||
var toolBar = null;
|
||||
var tablet = null;
|
||||
function onAddressBarShown(visible) {
|
||||
if (toolBar) {
|
||||
button.editProperties({isActive: visible});
|
||||
}
|
||||
}
|
||||
|
||||
function onClicked(){
|
||||
if (toolBar) {
|
||||
DialogsManager.toggleAddressBar();
|
||||
} else {
|
||||
tablet.loadQMLSource(gotoQmlSource);
|
||||
}
|
||||
tablet.loadQMLSource(gotoQmlSource);
|
||||
}
|
||||
if (Settings.getValue("HUDUIEnabled")) {
|
||||
toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
|
||||
button = toolBar.addButton({
|
||||
objectName: buttonName,
|
||||
imageURL: Script.resolvePath("assets/images/tools/directory.svg"),
|
||||
visible: true,
|
||||
alpha: 0.9
|
||||
});
|
||||
} else {
|
||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
button = tablet.addButton({
|
||||
icon: "icons/tablet-icons/goto-i.svg",
|
||||
activeIcon: "icons/tablet-icons/goto-a.svg",
|
||||
text: buttonName,
|
||||
sortOrder: 8
|
||||
});
|
||||
}
|
||||
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
var button = tablet.addButton({
|
||||
icon: "icons/tablet-icons/goto-i.svg",
|
||||
activeIcon: "icons/tablet-icons/goto-a.svg",
|
||||
text: buttonName,
|
||||
sortOrder: 8
|
||||
});
|
||||
|
||||
button.clicked.connect(onClicked);
|
||||
DialogsManager.addressBarShown.connect(onAddressBarShown);
|
||||
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
button.clicked.disconnect(onClicked);
|
||||
if (tablet) {
|
||||
tablet.removeButton(button);
|
||||
}
|
||||
if (toolBar) {
|
||||
toolBar.removeButton(buttonName);
|
||||
}
|
||||
DialogsManager.addressBarShown.disconnect(onAddressBarShown);
|
||||
});
|
||||
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
||||
|
|
|
@ -52,6 +52,15 @@
|
|||
}
|
||||
|
||||
function updateShowTablet() {
|
||||
|
||||
// close the WebTablet if it we go into toolbar mode.
|
||||
var toolbarMode = Tablet.getTablet("com.highfidelity.interface.tablet.system").toolbarMode;
|
||||
if (tabletShown && toolbarMode) {
|
||||
hideTabletUI();
|
||||
HMD.closeTablet();
|
||||
return;
|
||||
}
|
||||
|
||||
if (tabletShown) {
|
||||
var MUTE_MICROPHONE_MENU_ITEM = "Mute Microphone";
|
||||
var currentMicEnabled = !Menu.isOptionChecked(MUTE_MICROPHONE_MENU_ITEM);
|
||||
|
@ -67,7 +76,7 @@
|
|||
// other reason, close the tablet.
|
||||
hideTabletUI();
|
||||
HMD.closeTablet();
|
||||
} else if (HMD.showTablet && !tabletShown) {
|
||||
} else if (HMD.showTablet && !tabletShown && !toolbarMode) {
|
||||
UserActivityLogger.openedTablet();
|
||||
showTabletUI();
|
||||
} else if (!HMD.showTablet && tabletShown) {
|
||||
|
|
Loading…
Reference in a new issue