Merge branch 'master' into 21164
# Conflicts: # scripts/system/audio.js
4
BUILD.md
|
@ -2,8 +2,8 @@
|
|||
|
||||
* [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 3.3.2
|
||||
* [Qt](http://www.qt.io/download-open-source) ~> 5.6.1
|
||||
* [OpenSSL](https://www.openssl.org/community/binaries.html) ~> 1.0.1m
|
||||
* IMPORTANT: Using the recommended version of OpenSSL is critical to avoid security vulnerabilities.
|
||||
* [OpenSSL](https://www.openssl.org/community/binaries.html)
|
||||
* IMPORTANT: Use the latest available version of OpenSSL to avoid security vulnerabilities.
|
||||
* [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional)
|
||||
|
||||
####CMake External Project Dependencies
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include <SharedUtil.h>
|
||||
#include <StDev.h>
|
||||
#include <UUID.h>
|
||||
#include <CPUDetect.h>
|
||||
|
||||
#include "AudioHelpers.h"
|
||||
#include "AudioRingBuffer.h"
|
||||
|
@ -56,9 +57,9 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
|
|||
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||
|
||||
packetReceiver.registerListenerForTypes({ PacketType::MicrophoneAudioNoEcho, PacketType::MicrophoneAudioWithEcho,
|
||||
PacketType::InjectAudio, PacketType::SilentAudioFrame,
|
||||
PacketType::AudioStreamStats },
|
||||
this, "handleNodeAudioPacket");
|
||||
PacketType::InjectAudio, PacketType::AudioStreamStats },
|
||||
this, "handleAudioPacket");
|
||||
packetReceiver.registerListenerForTypes({ PacketType::SilentAudioFrame }, this, "handleSilentAudioPacket");
|
||||
packetReceiver.registerListener(PacketType::NegotiateAudioFormat, this, "handleNegotiateAudioFormat");
|
||||
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
|
||||
|
@ -71,7 +72,13 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
|
|||
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled);
|
||||
}
|
||||
|
||||
void AudioMixer::handleNodeAudioPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
void AudioMixer::handleAudioPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
getOrCreateClientData(sendingNode.data());
|
||||
DependencyManager::get<NodeList>()->updateNodeWithDataFromPacket(message, sendingNode);
|
||||
}
|
||||
|
||||
void AudioMixer::handleSilentAudioPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
_numSilentPackets++;
|
||||
getOrCreateClientData(sendingNode.data());
|
||||
DependencyManager::get<NodeList>()->updateNodeWithDataFromPacket(message, sendingNode);
|
||||
}
|
||||
|
@ -184,8 +191,7 @@ void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) {
|
|||
nodeList->eachNode([&killedNode](const SharedNodePointer& node) {
|
||||
auto clientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
if (clientData) {
|
||||
QUuid killedUUID = killedNode->getUUID();
|
||||
clientData->removeHRTFsForNode(killedUUID);
|
||||
clientData->removeNode(killedNode->getUUID());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -299,6 +305,8 @@ void AudioMixer::sendStatsPacket() {
|
|||
statsObject["avg_streams_per_frame"] = (float)_stats.sumStreams / (float)_numStatFrames;
|
||||
statsObject["avg_listeners_per_frame"] = (float)_stats.sumListeners / (float)_numStatFrames;
|
||||
|
||||
statsObject["silent_packets_per_frame"] = (float)_numSilentPackets / (float)_numStatFrames;
|
||||
|
||||
// timing stats
|
||||
QJsonObject timingStats;
|
||||
|
||||
|
@ -316,8 +324,8 @@ void AudioMixer::sendStatsPacket() {
|
|||
addTiming(_mixTiming, "mix");
|
||||
addTiming(_eventsTiming, "events");
|
||||
|
||||
#ifdef HIFI_AUDIO_THROTTLE_DEBUG
|
||||
timingStats["ns_per_throttle"] = (_stats.totalMixes > 0) ? (float)(_stats.throttleTime / _stats.totalMixes) : 0;
|
||||
#ifdef HIFI_AUDIO_MIXER_DEBUG
|
||||
timingStats["ns_per_mix"] = (_stats.totalMixes > 0) ? (float)(_stats.mixTime / _stats.totalMixes) : 0;
|
||||
#endif
|
||||
|
||||
// call it "avg_..." to keep it higher in the display, sorted alphabetically
|
||||
|
@ -337,7 +345,7 @@ void AudioMixer::sendStatsPacket() {
|
|||
|
||||
statsObject["mix_stats"] = mixStats;
|
||||
|
||||
_numStatFrames = 0;
|
||||
_numStatFrames = _numSilentPackets = 0;
|
||||
_stats.reset();
|
||||
|
||||
// add stats for each listerner
|
||||
|
@ -536,6 +544,8 @@ int AudioMixer::prepareFrame(const SharedNodePointer& node, unsigned int frame)
|
|||
}
|
||||
|
||||
void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) {
|
||||
qDebug() << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled");
|
||||
|
||||
if (settingsObject.contains(AUDIO_THREADING_GROUP_KEY)) {
|
||||
QJsonObject audioThreadingGroupObject = settingsObject[AUDIO_THREADING_GROUP_KEY].toObject();
|
||||
const QString AUTO_THREADS = "auto_threads";
|
||||
|
|
|
@ -56,7 +56,8 @@ public slots:
|
|||
|
||||
private slots:
|
||||
// packet handlers
|
||||
void handleNodeAudioPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
void handleAudioPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
void handleSilentAudioPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
void handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
void handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
void handleNodeKilled(SharedNodePointer killedNode);
|
||||
|
@ -87,6 +88,8 @@ private:
|
|||
float _trailingMixRatio { 0.0f };
|
||||
float _throttlingRatio { 0.0f };
|
||||
|
||||
int _numSilentPackets { 0 };
|
||||
|
||||
int _numStatFrames { 0 };
|
||||
AudioMixerStats _stats;
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
AudioMixerClientData::AudioMixerClientData(const QUuid& nodeID) :
|
||||
NodeData(nodeID),
|
||||
audioLimiter(AudioConstants::SAMPLE_RATE, AudioConstants::STEREO),
|
||||
_ignoreZone(*this),
|
||||
_outgoingMixedAudioSequenceNumber(0),
|
||||
_downstreamAudioStreamStats()
|
||||
{
|
||||
|
@ -427,3 +428,99 @@ void AudioMixerClientData::cleanupCodec() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
AudioMixerClientData::IgnoreZone& AudioMixerClientData::IgnoreZoneMemo::get(unsigned int frame) {
|
||||
// check for a memoized zone
|
||||
if (frame != _frame.load(std::memory_order_acquire)) {
|
||||
AvatarAudioStream* stream = _data.getAvatarAudioStream();
|
||||
|
||||
// get the initial dimensions from the stream
|
||||
glm::vec3 corner = stream ? stream->getAvatarBoundingBoxCorner() : glm::vec3(0);
|
||||
glm::vec3 scale = stream ? stream->getAvatarBoundingBoxScale() : glm::vec3(0);
|
||||
|
||||
// enforce a minimum scale
|
||||
static const glm::vec3 MIN_IGNORE_BOX_SCALE = glm::vec3(0.3f, 1.3f, 0.3f);
|
||||
if (glm::any(glm::lessThan(scale, MIN_IGNORE_BOX_SCALE))) {
|
||||
scale = MIN_IGNORE_BOX_SCALE;
|
||||
}
|
||||
|
||||
// quadruple the scale (this is arbitrary number chosen for comfort)
|
||||
const float IGNORE_BOX_SCALE_FACTOR = 4.0f;
|
||||
scale *= IGNORE_BOX_SCALE_FACTOR;
|
||||
|
||||
// create the box (we use a box for the zone for convenience)
|
||||
AABox box(corner, scale);
|
||||
|
||||
// update the memoized zone
|
||||
// This may be called by multiple threads concurrently,
|
||||
// so take a lock and only update the memo if this call is first.
|
||||
// This prevents concurrent updates from invalidating the returned reference
|
||||
// (contingent on the preconditions listed in the header).
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (frame != _frame.load(std::memory_order_acquire)) {
|
||||
_zone = box;
|
||||
unsigned int oldFrame = _frame.exchange(frame, std::memory_order_release);
|
||||
Q_UNUSED(oldFrame);
|
||||
|
||||
// check the precondition
|
||||
assert(oldFrame == 0 || frame == (oldFrame + 1));
|
||||
}
|
||||
}
|
||||
|
||||
return _zone;
|
||||
}
|
||||
|
||||
void AudioMixerClientData::IgnoreNodeCache::cache(bool shouldIgnore) {
|
||||
if (!_isCached) {
|
||||
_shouldIgnore = shouldIgnore;
|
||||
_isCached = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioMixerClientData::IgnoreNodeCache::isCached() {
|
||||
return _isCached;
|
||||
}
|
||||
|
||||
bool AudioMixerClientData::IgnoreNodeCache::shouldIgnore() {
|
||||
bool ignore = _shouldIgnore;
|
||||
_isCached = false;
|
||||
return ignore;
|
||||
}
|
||||
|
||||
bool AudioMixerClientData::shouldIgnore(const SharedNodePointer self, const SharedNodePointer node, unsigned int frame) {
|
||||
// this is symmetric over self / node; if computed, it is cached in the other
|
||||
|
||||
// check the cache to avoid computation
|
||||
auto& cache = _nodeSourcesIgnoreMap[node->getUUID()];
|
||||
if (cache.isCached()) {
|
||||
return cache.shouldIgnore();
|
||||
}
|
||||
|
||||
AudioMixerClientData* nodeData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
if (!nodeData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// compute shouldIgnore
|
||||
bool shouldIgnore = true;
|
||||
if ( // the nodes are not ignoring each other explicitly (or are but get data regardless)
|
||||
(!self->isIgnoringNodeWithID(node->getUUID()) ||
|
||||
(nodeData->getRequestsDomainListData() && node->getCanKick())) &&
|
||||
(!node->isIgnoringNodeWithID(self->getUUID()) ||
|
||||
(getRequestsDomainListData() && self->getCanKick()))) {
|
||||
|
||||
// if either node is enabling an ignore radius, check their proximity
|
||||
if ((self->isIgnoreRadiusEnabled() || node->isIgnoreRadiusEnabled())) {
|
||||
auto& zone = _ignoreZone.get(frame);
|
||||
auto& nodeZone = nodeData->_ignoreZone.get(frame);
|
||||
shouldIgnore = zone.touches(nodeZone);
|
||||
} else {
|
||||
shouldIgnore = false;
|
||||
}
|
||||
}
|
||||
|
||||
// cache in node
|
||||
nodeData->_nodeSourcesIgnoreMap[self->getUUID()].cache(shouldIgnore);
|
||||
|
||||
return shouldIgnore;
|
||||
}
|
||||
|
|
|
@ -38,18 +38,22 @@ public:
|
|||
AudioStreamMap getAudioStreams() { QReadLocker readLock { &_streamsLock }; return _audioStreams; }
|
||||
AvatarAudioStream* getAvatarAudioStream();
|
||||
|
||||
// returns whether self (this data's node) should ignore node, memoized by frame
|
||||
// precondition: frame is monotonically increasing after first call
|
||||
bool shouldIgnore(SharedNodePointer self, SharedNodePointer node, unsigned int frame);
|
||||
|
||||
// the following methods should be called from the AudioMixer assignment thread ONLY
|
||||
// they are not thread-safe
|
||||
|
||||
// returns a new or existing HRTF object for the given stream from the given node
|
||||
AudioHRTF& hrtfForStream(const QUuid& nodeID, const QUuid& streamID = QUuid()) { return _nodeSourcesHRTFMap[nodeID][streamID]; }
|
||||
|
||||
// remove HRTFs for all sources from this node
|
||||
void removeHRTFsForNode(const QUuid& nodeID) { _nodeSourcesHRTFMap.erase(nodeID); }
|
||||
|
||||
// removes an AudioHRTF object for a given stream
|
||||
void removeHRTFForStream(const QUuid& nodeID, const QUuid& streamID = QUuid());
|
||||
|
||||
// remove all sources and data from this node
|
||||
void removeNode(const QUuid& nodeID) { _nodeSourcesIgnoreMap.unsafe_erase(nodeID); _nodeSourcesHRTFMap.erase(nodeID); }
|
||||
|
||||
void removeAgentAvatarAudioStream();
|
||||
|
||||
int parseData(ReceivedMessage& message) override;
|
||||
|
@ -86,12 +90,10 @@ public:
|
|||
bool shouldFlushEncoder() { return _shouldFlushEncoder; }
|
||||
|
||||
QString getCodecName() { return _selectedCodecName; }
|
||||
|
||||
|
||||
bool shouldMuteClient() { return _shouldMuteClient; }
|
||||
void setShouldMuteClient(bool shouldMuteClient) { _shouldMuteClient = shouldMuteClient; }
|
||||
glm::vec3 getPosition() { return getAvatarAudioStream() ? getAvatarAudioStream()->getPosition() : glm::vec3(0); }
|
||||
glm::vec3 getAvatarBoundingBoxCorner() { return getAvatarAudioStream() ? getAvatarAudioStream()->getAvatarBoundingBoxCorner() : glm::vec3(0); }
|
||||
glm::vec3 getAvatarBoundingBoxScale() { return getAvatarAudioStream() ? getAvatarAudioStream()->getAvatarBoundingBoxScale() : glm::vec3(0); }
|
||||
bool getRequestsDomainListData() { return _requestsDomainListData; }
|
||||
void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; }
|
||||
|
||||
|
@ -103,9 +105,48 @@ public slots:
|
|||
void sendSelectAudioFormat(SharedNodePointer node, const QString& selectedCodecName);
|
||||
|
||||
private:
|
||||
using IgnoreZone = AABox;
|
||||
|
||||
QReadWriteLock _streamsLock;
|
||||
AudioStreamMap _audioStreams; // microphone stream from avatar is stored under key of null UUID
|
||||
|
||||
class IgnoreZoneMemo {
|
||||
public:
|
||||
IgnoreZoneMemo(AudioMixerClientData& data) : _data(data) {}
|
||||
|
||||
// returns an ignore zone, memoized by frame (lockless if the zone is already memoized)
|
||||
// preconditions:
|
||||
// - frame is monotonically increasing after first call
|
||||
// - there are no references left from calls to getIgnoreZone(frame - 1)
|
||||
IgnoreZone& get(unsigned int frame);
|
||||
|
||||
private:
|
||||
AudioMixerClientData& _data;
|
||||
IgnoreZone _zone;
|
||||
std::atomic<unsigned int> _frame { 0 };
|
||||
std::mutex _mutex;
|
||||
};
|
||||
IgnoreZoneMemo _ignoreZone;
|
||||
|
||||
class IgnoreNodeCache {
|
||||
public:
|
||||
// std::atomic is not copyable - always initialize uncached
|
||||
IgnoreNodeCache() {}
|
||||
IgnoreNodeCache(const IgnoreNodeCache& other) {}
|
||||
|
||||
void cache(bool shouldIgnore);
|
||||
bool isCached();
|
||||
bool shouldIgnore();
|
||||
|
||||
private:
|
||||
std::atomic<bool> _isCached { false };
|
||||
bool _shouldIgnore { false };
|
||||
};
|
||||
struct IgnoreNodeCacheHasher { std::size_t operator()(const QUuid& key) const { return qHash(key); } };
|
||||
|
||||
using NodeSourcesIgnoreMap = tbb::concurrent_unordered_map<QUuid, IgnoreNodeCache, IgnoreNodeCacheHasher>;
|
||||
NodeSourcesIgnoreMap _nodeSourcesIgnoreMap;
|
||||
|
||||
using HRTFMap = std::unordered_map<QUuid, AudioHRTF>;
|
||||
using NodeSourcesHRTFMap = std::unordered_map<QUuid, HRTFMap>;
|
||||
NodeSourcesHRTFMap _nodeSourcesHRTFMap;
|
||||
|
|
|
@ -46,7 +46,6 @@ void sendMutePacket(const SharedNodePointer& node, AudioMixerClientData&);
|
|||
void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData& data);
|
||||
|
||||
// mix helpers
|
||||
inline bool shouldIgnoreNode(const SharedNodePointer& listener, const SharedNodePointer& node);
|
||||
inline float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||
const glm::vec3& relativePosition);
|
||||
inline float computeGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||
|
@ -126,8 +125,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
|
||||
typedef void (AudioMixerSlave::*MixFunctor)(
|
||||
AudioMixerClientData&, const QUuid&, const AvatarAudioStream&, const PositionalAudioStream&);
|
||||
auto allStreams = [&](const SharedNodePointer& node, MixFunctor mixFunctor) {
|
||||
AudioMixerClientData* nodeData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
auto forAllStreams = [&](const SharedNodePointer& node, AudioMixerClientData* nodeData, MixFunctor mixFunctor) {
|
||||
auto nodeID = node->getUUID();
|
||||
for (auto& streamPair : nodeData->getAudioStreams()) {
|
||||
auto nodeStream = streamPair.second;
|
||||
|
@ -135,10 +133,17 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
}
|
||||
};
|
||||
|
||||
std::for_each(_begin, _end, [&](const SharedNodePointer& node) {
|
||||
if (*node == *listener) {
|
||||
AudioMixerClientData* nodeData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
#ifdef HIFI_AUDIO_MIXER_DEBUG
|
||||
auto mixStart = p_high_resolution_clock::now();
|
||||
#endif
|
||||
|
||||
std::for_each(_begin, _end, [&](const SharedNodePointer& node) {
|
||||
AudioMixerClientData* nodeData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
if (!nodeData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (*node == *listener) {
|
||||
// only mix the echo, if requested
|
||||
for (auto& streamPair : nodeData->getAudioStreams()) {
|
||||
auto nodeStream = streamPair.second;
|
||||
|
@ -146,15 +151,10 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
mixStream(*listenerData, node->getUUID(), *listenerAudioStream, *nodeStream);
|
||||
}
|
||||
}
|
||||
} else if (!shouldIgnoreNode(listener, node)) {
|
||||
} else if (!listenerData->shouldIgnore(listener, node, _frame)) {
|
||||
if (!isThrottling) {
|
||||
allStreams(node, &AudioMixerSlave::mixStream);
|
||||
forAllStreams(node, nodeData, &AudioMixerSlave::mixStream);
|
||||
} else {
|
||||
#ifdef HIFI_AUDIO_THROTTLE_DEBUG
|
||||
auto throttleStart = p_high_resolution_clock::now();
|
||||
#endif
|
||||
|
||||
AudioMixerClientData* nodeData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
auto nodeID = node->getUUID();
|
||||
|
||||
// compute the node's max relative volume
|
||||
|
@ -179,13 +179,6 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
if (!throttledNodes.empty()) {
|
||||
std::push_heap(throttledNodes.begin(), throttledNodes.end());
|
||||
}
|
||||
|
||||
#ifdef HIFI_AUDIO_THROTTLE_DEBUG
|
||||
auto throttleEnd = p_high_resolution_clock::now();
|
||||
uint64_t throttleTime =
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(throttleEnd - throttleStart).count();
|
||||
stats.throttleTime += throttleTime;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -201,7 +194,8 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
std::pop_heap(throttledNodes.begin(), throttledNodes.end());
|
||||
|
||||
auto& node = throttledNodes.back().second;
|
||||
allStreams(node, &AudioMixerSlave::mixStream);
|
||||
AudioMixerClientData* nodeData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
forAllStreams(node, nodeData, &AudioMixerSlave::mixStream);
|
||||
|
||||
throttledNodes.pop_back();
|
||||
}
|
||||
|
@ -209,10 +203,17 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
// throttle the remaining nodes' streams
|
||||
for (const std::pair<float, SharedNodePointer>& nodePair : throttledNodes) {
|
||||
auto& node = nodePair.second;
|
||||
allStreams(node, &AudioMixerSlave::throttleStream);
|
||||
AudioMixerClientData* nodeData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
forAllStreams(node, nodeData, &AudioMixerSlave::throttleStream);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HIFI_AUDIO_MIXER_DEBUG
|
||||
auto mixEnd = p_high_resolution_clock::now();
|
||||
auto mixTime = std::chrono::duration_cast<std::chrono::nanoseconds>(mixEnd - mixStart);
|
||||
stats.mixTime += mixTime.count();
|
||||
#endif
|
||||
|
||||
// use the per listener AudioLimiter to render the mixed data...
|
||||
listenerData->audioLimiter.render(_mixSamples, _bufferSamples, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
|
||||
|
@ -452,55 +453,6 @@ void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData&
|
|||
}
|
||||
}
|
||||
|
||||
bool shouldIgnoreNode(const SharedNodePointer& listener, const SharedNodePointer& node) {
|
||||
AudioMixerClientData* listenerData = static_cast<AudioMixerClientData*>(listener->getLinkedData());
|
||||
AudioMixerClientData* nodeData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
|
||||
// when this is true, the AudioMixer will send Audio data to a client about avatars that have ignored them
|
||||
bool getsAnyIgnored = listenerData->getRequestsDomainListData() && listener->getCanKick();
|
||||
|
||||
bool ignore = true;
|
||||
|
||||
if (nodeData &&
|
||||
// make sure that it isn't being ignored by our listening node
|
||||
(!listener->isIgnoringNodeWithID(node->getUUID()) || (nodeData->getRequestsDomainListData() && node->getCanKick())) &&
|
||||
// and that it isn't ignoring our listening node
|
||||
(!node->isIgnoringNodeWithID(listener->getUUID()) || getsAnyIgnored)) {
|
||||
|
||||
// is either node enabling the space bubble / ignore radius?
|
||||
if ((listener->isIgnoreRadiusEnabled() || node->isIgnoreRadiusEnabled())) {
|
||||
// define the minimum bubble size
|
||||
static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f);
|
||||
|
||||
// set up the bounding box for the listener
|
||||
AABox listenerBox(listenerData->getAvatarBoundingBoxCorner(), listenerData->getAvatarBoundingBoxScale());
|
||||
if (glm::any(glm::lessThan(listenerData->getAvatarBoundingBoxScale(), minBubbleSize))) {
|
||||
listenerBox.setScaleStayCentered(minBubbleSize);
|
||||
}
|
||||
|
||||
// set up the bounding box for the node
|
||||
AABox nodeBox(nodeData->getAvatarBoundingBoxCorner(), nodeData->getAvatarBoundingBoxScale());
|
||||
// Clamp the size of the bounding box to a minimum scale
|
||||
if (glm::any(glm::lessThan(nodeData->getAvatarBoundingBoxScale(), minBubbleSize))) {
|
||||
nodeBox.setScaleStayCentered(minBubbleSize);
|
||||
}
|
||||
|
||||
// quadruple the scale of both bounding boxes
|
||||
listenerBox.embiggen(4.0f);
|
||||
nodeBox.embiggen(4.0f);
|
||||
|
||||
// perform the collision check between the two bounding boxes
|
||||
ignore = listenerBox.touches(nodeBox);
|
||||
} else {
|
||||
ignore = false;
|
||||
}
|
||||
}
|
||||
|
||||
return ignore;
|
||||
}
|
||||
|
||||
static const float ATTENUATION_START_DISTANCE = 1.0f;
|
||||
|
||||
float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||
const glm::vec3& relativePosition) {
|
||||
float gain = 1.0f;
|
||||
|
@ -556,6 +508,7 @@ float computeGain(const AvatarAudioStream& listeningNodeStream, const Positional
|
|||
}
|
||||
|
||||
// distance attenuation
|
||||
const float ATTENUATION_START_DISTANCE = 1.0f;
|
||||
float distance = glm::length(relativePosition);
|
||||
assert(ATTENUATION_START_DISTANCE > EPSILON);
|
||||
if (distance >= ATTENUATION_START_DISTANCE) {
|
||||
|
|
|
@ -20,8 +20,8 @@ void AudioMixerStats::reset() {
|
|||
hrtfThrottleRenders = 0;
|
||||
manualStereoMixes = 0;
|
||||
manualEchoMixes = 0;
|
||||
#ifdef HIFI_AUDIO_THROTTLE_DEBUG
|
||||
throttleTime = 0;
|
||||
#ifdef HIFI_AUDIO_MIXER_DEBUG
|
||||
mixTime = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ void AudioMixerStats::accumulate(const AudioMixerStats& otherStats) {
|
|||
hrtfThrottleRenders += otherStats.hrtfThrottleRenders;
|
||||
manualStereoMixes += otherStats.manualStereoMixes;
|
||||
manualEchoMixes += otherStats.manualEchoMixes;
|
||||
#ifdef HIFI_AUDIO_THROTTLE_DEBUG
|
||||
throttleTime += otherStats.throttleTime;
|
||||
#ifdef HIFI_AUDIO_MIXER_DEBUG
|
||||
mixTime += otherStats.mixTime;
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#ifndef hifi_AudioMixerStats_h
|
||||
#define hifi_AudioMixerStats_h
|
||||
|
||||
#ifdef HIFI_AUDIO_THROTTLE_DEBUG
|
||||
#ifdef HIFI_AUDIO_MIXER_DEBUG
|
||||
#include <cstdint>
|
||||
#endif
|
||||
|
||||
|
@ -29,8 +29,8 @@ struct AudioMixerStats {
|
|||
int manualStereoMixes { 0 };
|
||||
int manualEchoMixes { 0 };
|
||||
|
||||
#ifdef HIFI_AUDIO_THROTTLE_DEBUG
|
||||
uint64_t throttleTime { 0 };
|
||||
#ifdef HIFI_AUDIO_MIXER_DEBUG
|
||||
uint64_t mixTime { 0 };
|
||||
#endif
|
||||
|
||||
void reset();
|
||||
|
|
|
@ -405,7 +405,7 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
otherNodeData->getLastReceivedSequenceNumber());
|
||||
|
||||
// determine if avatar is in view, to determine how much data to include...
|
||||
glm::vec3 otherNodeBoxScale = (otherNodeData->getPosition() - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f;
|
||||
glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f;
|
||||
AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale);
|
||||
bool isInView = nodeData->otherAvatarInView(otherNodeBox);
|
||||
|
||||
|
@ -431,7 +431,7 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID());
|
||||
QVector<JointData>& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID());
|
||||
bool distanceAdjust = true;
|
||||
glm::vec3 viewerPosition = nodeData->getPosition();
|
||||
glm::vec3 viewerPosition = myPosition;
|
||||
auto bytes = otherAvatar.toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, distanceAdjust, viewerPosition, &lastSentJointsForOther);
|
||||
numAvatarDataBytes += avatarPacketList->write(bytes);
|
||||
|
||||
|
|
|
@ -37,8 +37,10 @@ void MessagesMixer::nodeKilled(SharedNodePointer killedNode) {
|
|||
|
||||
void MessagesMixer::handleMessages(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode) {
|
||||
QString channel, message;
|
||||
QByteArray data;
|
||||
QUuid senderID;
|
||||
MessagesClient::decodeMessagesPacket(receivedMessage, channel, message, senderID);
|
||||
bool isText;
|
||||
MessagesClient::decodeMessagesPacket(receivedMessage, channel, isText, message, data, senderID);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
|
@ -47,7 +49,8 @@ void MessagesMixer::handleMessages(QSharedPointer<ReceivedMessage> receivedMessa
|
|||
return node->getActiveSocket() && _channelSubscribers[channel].contains(node->getUUID());
|
||||
},
|
||||
[&](const SharedNodePointer& node) {
|
||||
auto packetList = MessagesClient::encodeMessagesPacket(channel, message, senderID);
|
||||
auto packetList = isText ? MessagesClient::encodeMessagesPacket(channel, message, senderID) :
|
||||
MessagesClient::encodeMessagesDataPacket(channel, data, senderID);
|
||||
nodeList->sendPacketList(std::move(packetList), *node);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -667,7 +667,17 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
|
|||
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
|
||||
|
|
96
interface/resources/icons/tablet-icons/bubble-a.svg
Normal file
|
@ -0,0 +1,96 @@
|
|||
<?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="bubble-a.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="485"
|
||||
inkscape:window-y="514"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><g
|
||||
id="g8"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M23.2,24.1c-0.8,0.9-1.5,1.8-2.2,2.6c-0.1,0.2-0.1,0.5-0.1,0.7c0.1,1.7,0.2,3.4,0.2,5.1 c0,0.8-0.4,1.2-1.1,1.3c-0.7,0.1-1.3-0.4-1.4-1.1c-0.2-2.2-0.3-4.3-0.5-6.5c0-0.3,0.1-0.7,0.4-1c1.1-1.5,2.3-3,3.4-4.5 c0.6-0.7,1.6-1.6,2.6-1.6c0.3,0,1.1,0,1.4,0c0.8-0.1,1.3,0.1,1.9,0.9c1,1.2,1.5,2.3,2.4,3.6c0.7,1.1,1.4,1.6,2.9,1.9 c1.1,0.2,2.2,0.5,3.3,0.8c0.3,0.1,0.6,0.2,0.8,0.3c0.5,0.3,0.7,0.8,0.6,1.3c-0.1,0.5-0.5,0.7-1,0.8c-0.4,0-0.9,0-1.3-0.1 c-1.4-0.3-2.7-0.6-4.1-0.9c-0.8-0.2-1.5-0.6-2.1-1.1c-0.3-0.3-0.6-0.5-0.9-0.8c0,0.3,0,0.5,0,0.7c0,1.2,0,2.4,0,3.6 c0,0.4-0.3,12.6-0.1,16.8c0,0.5-0.1,1-0.2,1.5c-0.2,0.7-0.6,1-1.4,1.1c-0.8,0-1.4-0.3-1.7-1c-0.2-0.5-0.3-1.1-0.4-1.6 c-0.4-4.6-0.9-12.9-1.1-13.8c-0.1-0.8-0.2-1.1-0.3-2.1c-0.1-0.5-0.1-0.9-0.1-1.3C23.3,27.9,23.2,26.1,23.2,24.1z"
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M28.2,14.6c0,1.4-1.1,2.6-2.6,2.6l0,0c-1.4,0-2.6-1.1-2.6-2.6v-1.6c0-1.4,1.1-2.6,2.6-2.6l0,0 c1.4,0,2.6,1.1,2.6,2.6V14.6z"
|
||||
id="path12"
|
||||
style="fill:#000000;fill-opacity:1" /></g><path
|
||||
class="st0"
|
||||
d="M8.4,38.9c2.8,3.2,6.4,5.5,10.5,6.7c0.6,0.2,1.3,0.1,1.7-0.3c0.4-0.3,0.6-0.6,0.7-1c0.2-0.5,0.1-1.1-0.2-1.5 c-0.3-0.5-0.7-0.8-1.2-1c-1.6-0.5-3.2-1.2-4.6-2.1c-1.5-0.9-2.8-2.1-4-3.4c-0.4-0.4-0.9-0.7-1.5-0.7c-0.5,0-1,0.2-1.3,0.5 c-0.4,0.4-0.6,0.8-0.7,1.4C7.8,38,8,38.5,8.4,38.9z"
|
||||
id="path14"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M43.7,36.8c0.3-0.4,0.4-1,0.3-1.5c-0.1-0.5-0.4-1-0.8-1.3c-0.3-0.2-0.7-0.3-1.1-0.3c-0.7,0-1.3,0.3-1.7,0.9 c-1.1,1.6-2.4,3-4,4.2c-1.2,0.9-2.5,1.7-3.9,2.3c-0.5,0.2-0.9,0.6-1.1,1.1c-0.2,0.5-0.2,1,0,1.5c0.4,1,1.6,1.5,2.6,1 c1.7-0.7,3.3-1.7,4.8-2.8C40.7,40.4,42.4,38.7,43.7,36.8z"
|
||||
id="path16"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M5.1,33.2c0.5,0.4,1.2,0.4,1.8,0.2c0.5-0.2,0.9-0.6,1.1-1.1c0.2-0.5,0.2-1,0-1.5c-0.1-0.4-0.4-0.7-0.7-0.9 c-0.3-0.2-0.7-0.3-1.1-0.3c-0.2,0-0.5,0-0.7,0.1c-1,0.4-1.5,1.6-1.1,2.6C4.5,32.7,4.7,33,5.1,33.2z"
|
||||
id="path18"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M45.4,27.3c-0.2,0-0.3-0.1-0.5-0.1c-0.9,0-1.7,0.6-1.9,1.5c-0.1,0.5-0.1,1.1,0.2,1.5c0.3,0.5,0.7,0.8,1.2,0.9 c0.2,0,0.3,0.1,0.5,0.1c0.9,0,1.7-0.6,1.9-1.5c0.1-0.5,0.1-1.1-0.2-1.5C46.4,27.8,45.9,27.5,45.4,27.3z"
|
||||
id="path20"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M8.6,12c-0.3-0.2-0.7-0.3-1-0.3c-0.3,0-0.7,0.1-1,0.3c-0.3,0.2-0.6,0.4-0.7,0.7c-2,3.5-3.1,7.4-3.1,11.4 c0,0.2,0,0.4,0,0.6c0,0.5,0.2,1,0.6,1.4c0.4,0.4,0.9,0.6,1.4,0.6v0.4l0.1-0.4c0.5,0,1-0.2,1.4-0.6c0.4-0.4,0.6-0.9,0.5-1.4 c0-0.2,0-0.4,0-0.5c0-3.3,0.9-6.6,2.6-9.4C9.9,13.8,9.6,12.6,8.6,12z"
|
||||
id="path22"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M39.3,11.4c-0.1,0.5,0.1,1.1,0.4,1.5c1.1,1.4,2,3,2.6,4.6c0.6,1.6,1,3.2,1.2,4.9c0,0.5,0.3,1,0.6,1.3 c0.4,0.4,1,0.6,1.5,0.5c0.5,0,1-0.3,1.4-0.7c0.3-0.4,0.5-0.9,0.5-1.5c-0.4-4.2-2-8.2-4.6-11.6c-0.4-0.5-1-0.8-1.6-0.8 c-0.4,0-0.9,0.1-1.2,0.4C39.7,10.4,39.4,10.8,39.3,11.4z"
|
||||
id="path24"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M12.2,6.3c-0.5,0-0.9,0.2-1.3,0.5c-0.4,0.3-0.7,0.8-0.7,1.4c-0.1,0.5,0.1,1.1,0.4,1.5c0.7,0.8,2,1,2.8,0.3 c0.4-0.3,0.7-0.8,0.7-1.3c0.1-0.5-0.1-1.1-0.4-1.5C13.4,6.6,12.8,6.3,12.2,6.3z"
|
||||
id="path26"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M37.2,5.2c-0.3-0.2-0.7-0.3-1.1-0.3c-0.7,0-1.3,0.3-1.7,0.9c-0.3,0.4-0.4,1-0.3,1.5c0.1,0.5,0.4,1,0.9,1.3 C36,9.2,37.3,8.9,37.9,8C38.4,7.1,38.2,5.8,37.2,5.2z"
|
||||
id="path28"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M16.5,4c-0.2,0.5-0.3,1-0.1,1.5c0.4,1,1.5,1.6,2.6,1.2c3.3-1.2,6.8-1.4,10.2-0.6c0.6,0.1,1.2,0,1.7-0.4 c0.4-0.3,0.6-0.7,0.7-1.1c0.1-0.5,0-1.1-0.3-1.5c-0.3-0.5-0.7-0.8-1.3-0.9c-1.6-0.4-3.3-0.5-4.9-0.5c-2.6,0-5.1,0.4-7.5,1.3 C17.1,3.2,16.7,3.6,16.5,4z"
|
||||
id="path30"
|
||||
style="fill:#000000;fill-opacity:1" /></g></svg>
|
After Width: | Height: | Size: 5.8 KiB |
62
interface/resources/icons/tablet-icons/edit-a.svg
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?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 50"
|
||||
style="enable-background:new 0 0 50 50;"
|
||||
xml:space="preserve"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="edit-a.svg"><metadata
|
||||
id="metadata18"><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="defs16" /><sodipodi:namedview
|
||||
pagecolor="#ff0000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview14"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="299"
|
||||
inkscape:window-y="306"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Layer_1" /><style
|
||||
type="text/css"
|
||||
id="style3">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="g6"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M20.7,29.7c-2.2,2.2-4.4,4.4-6.7,6.7c-0.5-0.5-1.1-1.1-1.6-1.6c2.2-2.2,4.4-4.4,6.7-6.7l-1.8-1.8 c-2.6,2.5-5.1,5.1-7.7,7.6c-0.5,0.5-0.9,1.1-1,1.8C8.3,37.8,8,39.8,7.7,42c0.2,0,0.4,0,0.5,0c2-0.4,4-0.8,5.9-1.2 c0.4-0.1,0.8-0.3,1.1-0.6c2.7-2.6,5.3-5.3,8-8L20.7,29.7z"
|
||||
id="path8"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M31.1,11c0.8-0.8,1.8-1.8,2.7-2.7C34.2,8,34.6,8,34.9,8.4c1.6,1.6,3.1,3.1,4.7,4.7c0.4,0.4,0.4,0.8,0,1.2 c-0.9,0.9-1.8,1.8-2.7,2.7C35,15,33.1,13,31.1,11z"
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M33,25.9c-0.4,0.1-0.6,0-0.9-0.2c-0.6-0.6-1.3-1.3-1.9-1.9c1.5-1.5,3.1-3.1,4.6-4.6c0.1-0.1,0.3-0.3,0.3-0.3 c-2-2-3.9-4-5.9-6.1c-0.1,0.2-0.2,0.3-0.4,0.5c-1.5,1.5-3,3-4.6,4.6c-2.8-2.8-5.6-5.6-8.4-8.4c-0.2-0.2-0.4-0.4-0.6-0.6 c-1.5-1.2-3.5-1-4.8,0.4c-1.2,1.4-1.1,3.5,0.2,4.8c4.2,4.2,8.3,8.3,12.5,12.5c1.4,1.4,2.7,2.7,4.1,4.1c0.2,0.2,0.2,0.4,0.2,0.7 c-0.2,0.6-0.3,1.2-0.3,1.9c-0.3,4,2.3,7.5,6.1,8.5c1.6,0.4,3.2,0.3,4.8-0.3c-0.1-0.2-0.3-0.2-0.4-0.4c-1.2-1.2-2.3-2.3-3.5-3.5 c-0.8-0.9-0.9-2.1-0.1-3c0.6-0.7,1.3-1.3,2-2c0.9-0.8,2-0.8,2.9,0c0.2,0.2,0.3,0.3,0.5,0.5c1.2,1.2,2.3,2.3,3.5,3.5 c0.1,0,0.1,0,0.2,0c0.1-0.7,0.3-1.3,0.3-2C43.9,28.7,38.5,24.3,33,25.9z M12.9,12.6c-0.6,0-1.2-0.5-1.2-1.2s0.5-1.2,1.2-1.2 c0.6,0,1.2,0.6,1.2,1.2C14.1,12,13.6,12.6,12.9,12.6z M29.3,16.3c0.5,0.5,1,1.1,1.6,1.6c-1.1,1.1-2.2,2.2-3.3,3.3 c-0.5-0.5-1.1-1.1-1.6-1.6C27.1,18.5,28.2,17.4,29.3,16.3z"
|
||||
id="path12"
|
||||
style="fill:#000000;fill-opacity:1" /></g></svg>
|
After Width: | Height: | Size: 3.2 KiB |
54
interface/resources/icons/tablet-icons/goto-a.svg
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?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="goto-a.svg"><metadata
|
||||
id="metadata14"><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="defs12" /><sodipodi:namedview
|
||||
pagecolor="#ff0000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M47.2,41.3l-9.1-9.1c-0.8-0.8-1.9-1.1-3-1l-2.4-2.4c1.8-2.6,2.8-5.7,2.8-9c0-8.9-7.2-16.1-16.1-16.1 S3.3,11,3.3,19.8c0,8.9,7.2,16.1,16.1,16.1c4.1,0,7.8-1.5,10.6-4l2.2,2.2c-0.2,1.1,0.1,2.2,1,3l9.1,9.1c1.4,1.4,3.6,1.4,4.9,0 C48.5,44.9,48.5,42.7,47.2,41.3z M19.4,32.2c-6.8,0-12.3-5.5-12.3-12.3c0-6.8,5.5-12.3,12.3-12.3s12.3,5.5,12.3,12.3 C31.8,26.6,26.2,32.2,19.4,32.2z"
|
||||
id="path8"
|
||||
style="fill:#000000;fill-opacity:1" /></g></svg>
|
After Width: | Height: | Size: 2.1 KiB |
65
interface/resources/icons/tablet-icons/help-a.svg
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?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="help-a.svg"><metadata
|
||||
id="metadata18"><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="defs16" /><sodipodi:namedview
|
||||
pagecolor="#ff0000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview14"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#FFFFFF;stroke:#FFFFFF;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="g4149"><path
|
||||
class="st0"
|
||||
d="M 11.3,36.7 C 11.1,36.4 10.9,36.1 10.7,35.9 6.3,29 5.8,21.8 10,14.8 14.3,7.6 21,4.4 29.3,5.5 c 7.9,1.1 13.4,5.8 15.8,13.4 2.4,7.8 0.4,14.6 -5.5,20.1 -3.8,3.6 -8.4,5.1 -13.5,5.1 C 18.8,44.1 11.5,44 4.2,44 4,44 3.7,44 3.2,44 6,41.5 8.6,39.1 11.3,36.7 Z M 10.9,41 c 0.3,0 0.6,0.1 0.8,0.1 4.9,0 9.9,0 14.8,0 C 35.6,41 42.8,34 43,25.1 43.2,15.8 35.7,8.2 26.4,8.4 c -6.9,0.1 -12,3.5 -14.9,9.7 -2.9,6.4 -1.8,12.5 2.8,17.8 0.3,0.4 0.7,0.7 1,1.1 -1.4,1.3 -2.8,2.6 -4.4,4 z"
|
||||
id="path8"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
class="st1"
|
||||
d="m 22.5,20.9 c 0,-0.7 0.1,-1.4 0.3,-2 0.2,-0.6 0.6,-1.1 1,-1.6 0.4,-0.4 0.9,-0.8 1.6,-1 0.6,-0.2 1.3,-0.4 2,-0.4 0.6,0 1.2,0.1 1.7,0.3 0.5,0.2 1,0.4 1.4,0.8 0.4,0.3 0.7,0.8 1,1.3 0.2,0.5 0.3,1.1 0.3,1.8 0,0.5 -0.1,0.9 -0.2,1.2 -0.1,0.3 -0.2,0.6 -0.4,0.9 -0.2,0.3 -0.4,0.5 -0.6,0.7 -0.2,0.2 -0.4,0.4 -0.7,0.6 -0.3,0.2 -0.5,0.4 -0.7,0.6 -0.2,0.2 -0.4,0.4 -0.6,0.7 -0.2,0.3 -0.3,0.5 -0.4,0.9 -0.1,0.3 -0.1,0.8 -0.1,1.2 l -2.1,0 c 0,-0.6 0,-1.1 0.1,-1.5 0.1,-0.4 0.2,-0.8 0.3,-1.1 0.1,-0.3 0.3,-0.6 0.5,-0.8 0.2,-0.2 0.4,-0.5 0.7,-0.7 0.2,-0.2 0.4,-0.4 0.6,-0.5 0.2,-0.2 0.4,-0.3 0.5,-0.5 0.2,-0.2 0.3,-0.4 0.4,-0.7 0.1,-0.2 0.1,-0.5 0.1,-0.9 0,-0.4 -0.1,-0.8 -0.2,-1.1 -0.1,-0.3 -0.3,-0.5 -0.5,-0.7 -0.2,-0.2 -0.4,-0.3 -0.7,-0.4 -0.2,-0.1 -0.4,-0.1 -0.6,-0.1 -0.8,0 -1.5,0.3 -1.9,0.8 -0.4,0.6 -0.6,1.3 -0.6,2.2 l -2.2,0 z"
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
class="st0"
|
||||
d="m 27.6,29.8 c -0.1,0 -0.3,-0.1 -0.4,-0.1 -0.8,0 -1.4,0.5 -1.6,1.2 -0.1,0.4 0,0.9 0.2,1.3 0.2,0.4 0.6,0.7 1,0.8 0.1,0 0.3,0.1 0.4,0.1 0.8,0 1.4,-0.5 1.6,-1.2 0.1,-0.4 0.1,-0.9 -0.2,-1.3 -0.2,-0.5 -0.6,-0.7 -1,-0.8 z"
|
||||
id="path12"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none"
|
||||
inkscape:connector-curvature="0" /></g></svg>
|
After Width: | Height: | Size: 3.7 KiB |
74
interface/resources/icons/tablet-icons/ignore-a.svg
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?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="ignore-a.svg"><metadata
|
||||
id="metadata24"><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="defs22" /><sodipodi:namedview
|
||||
pagecolor="#ff0000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview20"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M27.1,42.3c-1.4,0.3-2.7,0.5-4.1,0.5c-9.3,0-16.8-7.5-16.8-16.8S13.8,9.2,23,9.2S39.8,16.7,39.8,26 c0,0.2,0,0.3,0,0.5c0.7-0.7,1.4-1,2.4-1.5C41.7,14.9,33.3,6.8,23,6.8C12.4,6.8,3.8,15.4,3.8,26S12.4,45.2,23,45.2 c1.7,0,3.4-0.3,5.1-0.7C27.8,43.6,27.5,43,27.1,42.3z"
|
||||
id="path8"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M32.6,20.7L32.6,20.7c-0.4-0.4-0.7-0.4-1.2-0.3c0,0-5.9,0.7-8.4,0.7h-0.1c-2.5,0-8.6-0.9-8.6-0.9 c-0.4-0.1-0.9,0-1.2,0.4L13,21c-0.1,0.3-0.1,0.6-0.1,1c0.1,0.3,0.3,0.6,0.6,0.7c1,0.4,4.9,1.8,5.9,2.1c0.1,0,0.6,0.1,0.6,0.9 c0,0.9-0.3,4.6-0.7,6.4c-0.4,1.8-1.2,4-1.2,4c-0.1,0.6,0.1,1.3,0.7,1.5l0.7,0.3c0.3,0.1,0.6,0.1,0.9,0c0.3-0.1,0.4-0.4,0.6-0.7 l2.1-6.4l1.9,6.5c0.1,0.3,0.3,0.6,0.6,0.7c0.1,0.1,0.3,0.1,0.4,0.1c0.1,0,0.3,0,0.4-0.1l0.7-0.3c0.6-0.1,0.9-0.7,0.7-1.3 c0,0-0.6-2.4-1-4.3c-0.3-1.2-0.4-3-0.4-4.3c0-0.9-0.1-1.6-0.1-2.2c0-0.3,0.1-0.6,0.6-0.7c0.1,0,5.5-1.9,5.5-1.9 c0.4-0.1,0.6-0.4,0.7-0.9C32.9,21.5,32.9,21,32.6,20.7z"
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1" /><circle
|
||||
class="st0"
|
||||
cx="23"
|
||||
cy="17"
|
||||
r="2.5"
|
||||
id="circle12"
|
||||
style="fill:#000000;fill-opacity:1" /><g
|
||||
id="g14"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M43.1,43L43.1,43L43.1,43z"
|
||||
id="path16"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M41,35.6l4.6-4.6c0.7-0.7,0.7-1.9,0-2.6c-0.7-0.7-1.9-0.7-2.6,0L38.5,33l-4.6-4.6c-0.7-0.7-1.9-0.7-2.6,0 s-0.7,1.9,0,2.6l4.6,4.6l-4.6,4.6c-0.7,0.7-0.7,1.9,0,2.6s1.9,0.7,2.6,0l4.6-4.6l4.6,4.6c0.7,0.7,1.9,0.7,2.6,0 c0.7-0.7,0.7-1.9,0-2.6L41,35.6z"
|
||||
id="path18"
|
||||
style="fill:#000000;fill-opacity:1" /></g></g></svg>
|
After Width: | Height: | Size: 3.4 KiB |
64
interface/resources/icons/tablet-icons/market-a.svg
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?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="market-a.svg"><metadata
|
||||
id="metadata20"><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="defs18" /><sodipodi:namedview
|
||||
pagecolor="#ff0000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview16"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><g
|
||||
id="g8"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M45.4,13.7c-1.6-0.1-3.2,0-4.8,0c-7.5,0-18,0-25.7,0c-0.6-1.8-1-3.5-1.7-5.2C13,7.9,12.2,7.1,11.5,7 C9.4,6.7,7.3,6.7,5.2,6.7c-1.1,0-1.9,0.5-1.9,1.7c0,1.2,0.8,1.7,1.9,1.7c1.3,0,2.7,0,4,0.1c0.5,0.1,1.2,0.6,1.4,1.1 c0.9,2.6,1.7,5.2,2.5,7.8c1.3,4.3,1.8,5.5,3.1,10.2c0.6,2.3,1.2,2.8,2.2,3.3c1.1,0.4,2.1,0.4,2.1,0.4h1.8c4.6,0,12.2,0,16.8,0 c1.1,0,2.1-0.1,2.6-1.4c1.9-5.1,3.8-10.2,5.7-15.3C47.8,14.7,47.1,13.8,45.4,13.7z M38.9,28.7c-0.1,0.3-0.8,0.7-1.2,0.7 c-4.6,0-12.2,0-16.8,0c-0.4,0-1.1-0.3-1.2-0.7c-1.3-3.8-2.4-7.6-3.7-11.5h27.1C41.8,21.2,40.4,24.9,38.9,28.7z"
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M17.2,37.3L17.2,37.3c-1,0-1.7,0.2-2.2,0.7c-0.6,0.6-0.9,1.3-0.9,2.3c0,1,0.3,1.8,0.8,2.4 c0.6,0.6,1.4,0.9,2.2,0.9c1.8,0,3.2-1.4,3.2-3.2c0-0.9-0.3-1.7-0.9-2.3C18.8,37.6,18,37.3,17.2,37.3z"
|
||||
id="path12"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M35.1,37.3L35.1,37.3c-1,0-1.7,0.2-2.2,0.7c-0.6,0.6-0.9,1.4-0.9,2.4c0,1,0.3,1.8,0.8,2.4 c0.6,0.6,1.3,0.9,2.2,0.9c1.8,0,3.2-1.5,3.2-3.3c0-0.9-0.3-1.6-0.9-2.2C36.8,37.6,36,37.3,35.1,37.3z"
|
||||
id="path14"
|
||||
style="fill:#000000;fill-opacity:1" /></g></g></svg>
|
After Width: | Height: | Size: 3 KiB |
62
interface/resources/icons/tablet-icons/menu-a.svg
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?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="menu-a.svg"><metadata
|
||||
id="metadata18"><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="defs16" /><sodipodi:namedview
|
||||
pagecolor="#ff0000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview14"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M37,18.5H14.1c-0.9,0-1.7-0.7-1.7-1.7c0-0.9,0.8-1.7,1.7-1.7H37c0.9,0,1.7,0.7,1.7,1.7 C38.6,17.8,37.9,18.5,37,18.5z"
|
||||
id="path8"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M37,28H14.1c-0.9,0-1.7-0.8-1.7-1.7c0-0.9,0.8-1.7,1.7-1.7H37c0.9,0,1.7,0.8,1.7,1.7C38.6,27.3,37.9,28,37,28z "
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M37,37.4H14.1c-0.9,0-1.7-0.8-1.7-1.7s0.8-1.7,1.7-1.7H37c0.9,0,1.7,0.8,1.7,1.7S37.9,37.4,37,37.4z"
|
||||
id="path12"
|
||||
style="fill:#000000;fill-opacity:1" /></g></svg>
|
After Width: | Height: | Size: 2.3 KiB |
70
interface/resources/icons/tablet-icons/mic-mute-a.svg
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?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="mic-mute-a.svg"><metadata
|
||||
id="metadata22"><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="defs20" /><sodipodi:namedview
|
||||
pagecolor="#ff0000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview18"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M28.9,17.1v-0.5c0-2-1.7-3.6-3.7-3.6h0c-2,0-3.7,1.6-3.7,3.6v6.9L28.9,17.1z"
|
||||
id="path8"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M21.5,29.2v0.2c0,2,1.6,3.6,3.7,3.6h0c2,0,3.7-1.6,3.7-3.6v-6.6L21.5,29.2z"
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M39.1,16.8L13.6,39.1c-0.7,0.6-1.8,0.5-2.4-0.2l-0.2-0.2c-0.6-0.7-0.5-1.8,0.2-2.4l25.4-22.4 c0.7-0.6,1.8-0.5,2.4,0.2l0.2,0.2C39.8,15.1,39.7,16.1,39.1,16.8z"
|
||||
id="path12"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M23.4,40.2l0,3.4h-4.3c-1,0-1.8,0.8-1.8,1.8c0,1,0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8 c0-1-0.8-1.8-1.8-1.8h-4.4l0-3.4c5.2-0.8,9.2-5,9.2-10.1c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8 c0,0.3,0,4.8,0,5.2c0,3.7-3.4,6.7-7.5,6.7c-3.6,0-6.7-2.3-7.3-5.4L15,34C16.4,37.2,19.6,39.7,23.4,40.2z"
|
||||
id="path14"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M17.7,24.9c0-1-0.7-1.8-1.6-1.8c-1-0.1-1.8,0.7-1.9,1.6c0,0.2,0,4.2,0,5.3l3.5-3.1 C17.7,25.9,17.7,25,17.7,24.9z"
|
||||
id="path16"
|
||||
style="fill:#000000;fill-opacity:1" /></g></svg>
|
After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
70
interface/resources/icons/tablet-icons/mic-unmute-a.svg
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?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="mic-unmute-a.svg"><metadata
|
||||
id="metadata22"><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="defs20" /><sodipodi:namedview
|
||||
pagecolor="#ff0000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview18"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M31.4,14.1l2.2-2.2c-2.1-2.5-5.3-4.1-8.8-4.1c-3.4,0-6.4,1.5-8.5,3.8c0.7,0.7,1.5,1.5,2.2,2.2 c1.6-1.7,3.8-2.9,6.3-2.9C27.5,10.9,29.9,12.1,31.4,14.1z"
|
||||
id="path8"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M36.5,9l2.2-2.2C35.3,3,30.3,0.6,24.8,0.6c-5.3,0-10.2,2.3-13.6,5.9c0.7,0.7,1.5,1.5,2.2,2.2 c2.9-3,6.9-5,11.4-5C29.5,3.7,33.6,5.8,36.5,9z"
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M28.5,22.7v-4.4c0-2-1.6-3.6-3.7-3.6h0c-2,0-3.7,1.6-3.7,3.6v4.4H28.5z"
|
||||
id="path12"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M21.1,26.5v4.3c0,2,1.7,3.6,3.7,3.6h0c2,0,3.7-1.6,3.7-3.6v-4.3H21.1z"
|
||||
id="path14"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M36,31.5c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8c0,0.3,0,4.8,0,5.2 c0,3.7-3.4,6.7-7.5,6.7c-4.1,0-7.5-3-7.5-6.7c0-0.4,0-4.9,0-5.3c0-1-0.7-1.8-1.6-1.8C14.9,24.3,14,25,14,26c0,0.3,0,5.4,0,5.5 c0,5.1,4,9.3,9.2,10.1l0,3.4h-4.3c-1,0-1.8,0.8-1.8,1.8c0,1,0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8c0-1-0.8-1.8-1.8-1.8h-4.4 l0-3.4C32,40.7,36,36.6,36,31.5z"
|
||||
id="path16"
|
||||
style="fill:#000000;fill-opacity:1" /></g></svg>
|
After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
80
interface/resources/icons/tablet-icons/people-a.svg
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?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="people-a.svg"><metadata
|
||||
id="metadata24"><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="defs22" /><sodipodi:namedview
|
||||
pagecolor="#ff0000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview20"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><circle
|
||||
class="st0"
|
||||
cx="25.6"
|
||||
cy="14.8"
|
||||
r="8.1"
|
||||
id="circle8"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M31.4,27h-11c-4.6,0-8.2,3.9-8.2,8.5v4.3c3.8,2.8,8.1,4.5,13.1,4.5c5.6,0,10.6-2.1,14.4-5.6v-3.2 C39.6,30.9,35.9,27,31.4,27z"
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1" /><circle
|
||||
class="st0"
|
||||
cx="41.6"
|
||||
cy="17.1"
|
||||
r="3.5"
|
||||
id="circle12"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M43.9,23.9h-4.1c-0.9,0-1.7,0.4-2.3,1c1.2,0.6,2.3,1.6,3.1,2.5c1,1.2,1.5,2.7,1.7,4.3c0.3,0.9,0.4,1.8,0.2,2.8 v0.6c1.6-2.2,3.3-6,4-8.1v-0.3C46.5,25.7,45.3,23.9,43.9,23.9z"
|
||||
id="path14"
|
||||
style="fill:#000000;fill-opacity:1" /><circle
|
||||
class="st0"
|
||||
cx="9.4"
|
||||
cy="18"
|
||||
r="3.5"
|
||||
id="circle16"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M8.5,35.7c-0.1-0.7-0.1-1.4,0-2.1l0.1-0.2c0-0.2,0-0.4,0-0.6c0-0.2,0-0.3,0.1-0.5c0-0.2,0-0.3,0-0.5 c0.2-2.1,1.6-4.4,3.2-5.7c0.4-0.4,0.9-0.6,1.4-0.9c-0.5-0.5-1.3-0.7-2-0.7H7c-1.4,0-2.6,1.8-2.4,2.7v0.3 C5.1,29.8,6.8,33.5,8.5,35.7z"
|
||||
id="path18"
|
||||
style="fill:#000000;fill-opacity:1" /></g></svg>
|
After Width: | Height: | Size: 2.9 KiB |
68
interface/resources/icons/tablet-icons/scripts-a.svg
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?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="scripts-a.svg"><metadata
|
||||
id="metadata22"><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="defs20" /><sodipodi:namedview
|
||||
pagecolor="#ff0000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview18"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><g
|
||||
id="g8"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M29.1,45H12c-3.5,0-6.4-1.8-8.4-5.1c-1.4-2.4-1.9-4.7-1.9-4.8l-0.3-1.6h10.4V4.8h35.8v27 c0.1,0.8,0.5,6.6-2.4,10c-1.4,1.6-3.3,2.4-5.6,2.4c-4.8,0-7.2-2.6-8.3-4.7c-0.6-1.2-1-2.3-1.1-3.2H5c0.3,0.7,0.6,1.5,1.1,2.3 c1.5,2.4,3.5,3.6,5.9,3.6h17.1c0.8,0,1.4,0.6,1.4,1.4C30.5,44.3,29.9,45,29.1,45z M14.6,33.5h18l0.1,1.3c0,0,0.1,1.7,1,3.4 c1.2,2.1,3.1,3.2,5.8,3.2c1.5,0,2.7-0.5,3.5-1.4c1.2-1.3,1.6-3.3,1.7-4.8c0.2-1.7,0-3.1,0-3.1l0-0.1l0-0.1V7.6H14.6V33.5z"
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M39.6,14.3H19.7c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5h19.9c0.8,0,1.5,0.7,1.5,1.5S40.4,14.3,39.6,14.3z"
|
||||
id="path12"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M39.6,21.3H19.7c-0.8,0-1.5-0.7-1.5-1.6s0.7-1.6,1.5-1.6h19.9c0.8,0,1.5,0.7,1.5,1.6S40.4,21.3,39.6,21.3z"
|
||||
id="path14"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M39.6,28.1H19.7c-0.8,0-1.5-0.7-1.5-1.6s0.7-1.6,1.5-1.6h19.9c0.8,0,1.5,0.7,1.5,1.6S40.4,28.1,39.6,28.1z"
|
||||
id="path16"
|
||||
style="fill:#000000;fill-opacity:1" /></g></g></svg>
|
After Width: | Height: | Size: 2.9 KiB |
62
interface/resources/icons/tablet-icons/snap-a.svg
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?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="snap-a.svg"><metadata
|
||||
id="metadata18"><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="defs16" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview14"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><g
|
||||
id="g8"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M38.5,16.3h-3.7v-1.6c0-2.4-1.6-3-4-3H18.7c-2.4,0-3.6,0.5-3.6,3v1.6h-3.6c-2.4,0-4.5,1-4.5,3.5V36 c0,2.4,1.9,4.6,4.5,4.6h27.1c2.4,0,4.2-2.7,4.2-5.2V19.7C42.8,17.3,41,16.3,38.5,16.3z M25.2,37c-5.5,0-9.9-4.5-9.9-9.9 c0-5.5,4.5-9.9,9.9-9.9c5.5,0,9.9,4.5,9.9,9.9S30.8,37,25.2,37z"
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1" /><circle
|
||||
class="st0"
|
||||
cx="25.2"
|
||||
cy="27"
|
||||
r="5.9"
|
||||
id="circle12"
|
||||
style="fill:#000000;fill-opacity:1" /></g></g></svg>
|
After Width: | Height: | Size: 2.2 KiB |
54
interface/resources/icons/tablet-icons/switch-desk-a.svg
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?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="switch-desk-a.svg"><metadata
|
||||
id="metadata14"><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="defs12" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M42.2,6.2H8.1c-3.5,0-6.4,3.1-6.4,6.8v18c0,3.8,2.9,6.8,6.4,6.8h15.3v3.5h-8.9c-0.8,0-1.5,0.9-1.5,1.8 c0,0.9,0.7,1.8,1.5,1.8h21.9c0.8,0,1.5-0.9,1.5-1.8c0-0.9-0.7-1.8-1.5-1.8h-9.6v-3.4c-0.3,0-0.6,0-0.9,0h16.3 c3.5,0,6.4-3.1,6.4-6.8V13C48.6,9.2,45.7,6.2,42.2,6.2z M45.2,31c0,1.7-1.3,3.1-2.9,3.1H8.1c-1.6,0-2.9-1.4-2.9-3.1V13 c0-1.7,1.3-3.1,2.9-3.1h34.2c1.6,0,2.9,1.4,2.9,3.1V31z"
|
||||
id="path8"
|
||||
style="fill:#000000;fill-opacity:1" /></g></svg>
|
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 842 B After Width: | Height: | Size: 842 B |
54
interface/resources/icons/tablet-icons/switch-vr-a.svg
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?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="switch-vr-a.svg"><metadata
|
||||
id="metadata14"><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="defs12" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M25.4,28.7c1.7,0,2.8,1.8,3.8,3.6c0.5,0.8,1.6,2.6,2,2.6h7.5c4.2,0,7.6-3.8,7.6-8.4v-5.4 c0-4.6-3.4-8.4-7.6-8.4H11.9c-4.2,0-7.6,3.8-7.6,8.4v5.4c0,4.6,3.4,8.4,7.6,8.4h7.3c0.6,0,1.7-1.6,2.4-2.7 C22.6,30.5,23.7,28.7,25.4,28.7C25.3,28.7,25.4,28.7,25.4,28.7z"
|
||||
id="path8"
|
||||
style="fill:#000000;fill-opacity:1" /></g></svg>
|
After Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 717 B |
94
interface/resources/icons/tablet-icons/users-a.svg
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?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="users-a.svg"><metadata
|
||||
id="metadata34"><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="defs32" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview30"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><g
|
||||
id="g8"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M22.6,14.8c-0.1-0.9-0.1-1.8,0.1-2.6h-8.6c-0.9,0-1.7,0.8-1.7,1.8s0.8,1.8,1.7,1.8h8.7 C22.7,15.4,22.7,15.1,22.6,14.8z"
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M26.5,22.3c-0.6-0.5-1.1-1-1.6-1.6H14.1c-0.9,0-1.7,0.8-1.7,1.8c0,1,0.8,1.8,1.7,1.8h8.2 c0.3-0.2,0.6-0.3,0.9-0.4c1.2-0.8,2.5-1.2,3.9-1.3C26.9,22.6,26.7,22.5,26.5,22.3z"
|
||||
id="path12"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M17.9,30.3c0.1-0.3,0.1-0.5,0.2-0.7h-4c-0.9,0-1.7,0.8-1.7,1.8c0,1,0.8,1.8,1.7,1.8h3.2 C17.3,32.2,17.5,31.2,17.9,30.3z"
|
||||
id="path14"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M5.9,15.7H4.5c-0.9,0-1.7-0.8-1.7-1.8s0.8-1.8,1.7-1.8h1.4c0.9,0,1.7,0.8,1.7,1.8S6.8,15.7,5.9,15.7z"
|
||||
id="path16"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M5.9,24.4H4.5c-0.9,0-1.7-0.8-1.7-1.8c0-1,0.8-1.8,1.7-1.8h1.4c0.9,0,1.7,0.8,1.7,1.8 C7.6,23.6,6.8,24.4,5.9,24.4z"
|
||||
id="path18"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M5.9,33.2H4.5c-0.9,0-1.7-0.8-1.7-1.8s0.8-1.8,1.7-1.8h1.4c0.9,0,1.7,0.8,1.7,1.8S6.8,33.2,5.9,33.2z"
|
||||
id="path20"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M17.3,38.3h-3.2c-0.9,0-1.7,0.8-1.7,1.8s0.8,1.8,1.7,1.8h3.2V38.3z"
|
||||
id="path22"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M5.9,42H4.5c-0.9,0-1.7-0.8-1.7-1.8c0-1,0.8-1.8,1.7-1.8h1.4c0.9,0,1.7,0.8,1.7,1.8C7.6,41.1,6.8,42,5.9,42z"
|
||||
id="path24"
|
||||
style="fill:#000000;fill-opacity:1" /><circle
|
||||
class="st0"
|
||||
cx="34.5"
|
||||
cy="14.3"
|
||||
r="7.8"
|
||||
id="circle26"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M23.9,44.1c10.4,0,19.5-4.4,23.3-13.4c-1.3-2.7-3.9-4.7-7.1-4.7H29.5c-4.4,0-8.2,3.8-8.2,8.3v9.7 C22.2,44,23,44.1,23.9,44.1z"
|
||||
id="path28"
|
||||
style="fill:#000000;fill-opacity:1" /></g></g></svg>
|
After Width: | Height: | Size: 3.8 KiB |
211
interface/resources/qml/controls-uit/TabletComboBox.qml
Normal file
|
@ -0,0 +1,211 @@
|
|||
//
|
||||
// ComboBox.qml
|
||||
//
|
||||
// Created by Dante Ruiz on 13 Feb 2017
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import "../styles-uit"
|
||||
import "../controls-uit" as HifiControls
|
||||
import "." as VrControls
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
property alias model: comboBox.model;
|
||||
property alias comboBox: comboBox
|
||||
readonly property alias currentText: comboBox.currentText;
|
||||
property alias currentIndex: comboBox.currentIndex;
|
||||
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
|
||||
property string label: ""
|
||||
property real controlHeight: height + (comboBoxLabel.visible ? comboBoxLabel.height + comboBoxLabel.anchors.bottomMargin : 0)
|
||||
|
||||
readonly property ComboBox control: comboBox
|
||||
|
||||
signal accepted();
|
||||
|
||||
implicitHeight: comboBox.height;
|
||||
focus: true
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.2
|
||||
color: popup.visible
|
||||
? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark)
|
||||
: (isLightColorScheme ? hifi.colors.dropDownLightStart : hifi.colors.dropDownDarkStart)
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: popup.visible
|
||||
? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark)
|
||||
: (isLightColorScheme ? hifi.colors.dropDownLightFinish : hifi.colors.dropDownDarkFinish)
|
||||
}
|
||||
}
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
SystemPalette { id: palette }
|
||||
|
||||
ComboBox {
|
||||
id: comboBox
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
height: hifi.fontSizes.textFieldInput + 13 // Match height of TextField control.
|
||||
}
|
||||
|
||||
FiraSansSemiBold {
|
||||
id: textField
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: hifi.dimensions.textPadding
|
||||
right: dropIcon.left
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
size: hifi.fontSizes.textFieldInput
|
||||
text: comboBox.currentText
|
||||
elide: Text.ElideRight
|
||||
color: controlHover.containsMouse || popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText )
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dropIcon
|
||||
anchors { right: parent.right; verticalCenter: parent.verticalCenter }
|
||||
height: background.height
|
||||
width: height
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
color: isLightColorScheme ? hifi.colors.faintGray : hifi.colors.baseGray
|
||||
}
|
||||
HiFiGlyphs {
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: -11
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
size: hifi.dimensions.spinnerSize
|
||||
text: hifi.glyphs.caratDn
|
||||
color: controlHover.containsMouse || popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText)
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: controlHover
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
onClicked: toggleList();
|
||||
}
|
||||
|
||||
function toggleList() {
|
||||
if (popup.visible) {
|
||||
hideList();
|
||||
} else {
|
||||
showList();
|
||||
}
|
||||
}
|
||||
|
||||
function showList() {
|
||||
var r = 20//desktop.mapFromItem(root, 0, 0, root.width, root.height);
|
||||
var y = 200;
|
||||
var bottom = 0 + scrollView.height;
|
||||
if (bottom > 720) {
|
||||
y -= bottom - 720 + 8;
|
||||
}
|
||||
scrollView.x = 0;
|
||||
scrollView.y = 0;
|
||||
popup.visible = true;
|
||||
popup.forceActiveFocus();
|
||||
listView.currentIndex = root.currentIndex;
|
||||
scrollView.hoverEnabled = true;
|
||||
}
|
||||
|
||||
function hideList() {
|
||||
popup.visible = false;
|
||||
scrollView.hoverEnabled = false;
|
||||
root.accepted();
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: popup
|
||||
parent: parent
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
focus: true
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: hideList();
|
||||
}
|
||||
|
||||
function previousItem() { listView.currentIndex = (listView.currentIndex + listView.count - 1) % listView.count; }
|
||||
function nextItem() { listView.currentIndex = (listView.currentIndex + listView.count + 1) % listView.count; }
|
||||
function selectCurrentItem() { root.currentIndex = listView.currentIndex; hideList(); }
|
||||
function selectSpecificItem(index) { root.currentIndex = index; hideList(); }
|
||||
|
||||
Keys.onUpPressed: previousItem();
|
||||
Keys.onDownPressed: nextItem();
|
||||
Keys.onSpacePressed: selectCurrentItem();
|
||||
Keys.onRightPressed: selectCurrentItem();
|
||||
Keys.onReturnPressed: selectCurrentItem();
|
||||
Keys.onEscapePressed: hideList();
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
height: 480
|
||||
width: root.width + 4
|
||||
property bool hoverEnabled: false;
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
height: textField.height * count * 1.4
|
||||
model: root.model
|
||||
delegate: Rectangle {
|
||||
width: root.width + 4
|
||||
height: popupText.implicitHeight * 1.4
|
||||
color: (listView.currentIndex === index) ? hifi.colors.primaryHighlight :
|
||||
(isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark)
|
||||
FiraSansSemiBold {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: hifi.dimensions.textPadding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
id: popupText
|
||||
text: listView.model[index] ? listView.model[index] : ""
|
||||
size: hifi.fontSizes.textFieldInput
|
||||
color: hifi.colors.baseGray
|
||||
}
|
||||
MouseArea {
|
||||
id: popupHover
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: scrollView.hoverEnabled;
|
||||
onEntered: listView.currentIndex = index;
|
||||
onClicked: popup.selectSpecificItem(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Label {
|
||||
id: comboBoxLabel
|
||||
text: root.label
|
||||
colorScheme: root.colorScheme
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: 4
|
||||
visible: label != ""
|
||||
}
|
||||
}
|
138
interface/resources/qml/controls-uit/TabletContentSection.qml
Normal file
|
@ -0,0 +1,138 @@
|
|||
//
|
||||
// ContentSection.qml
|
||||
//
|
||||
// Created by Dante Ruiz on 13 Feb 2017
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import "../styles-uit"
|
||||
|
||||
Column {
|
||||
property string name: "Content Section"
|
||||
property bool isFirst: false
|
||||
property bool isCollapsible: false // Set at creation.
|
||||
property bool isCollapsed: false
|
||||
|
||||
spacing: 0 // Defer spacing decisions to individual controls.
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: hifi.dimensions.contentMargin.x
|
||||
right: parent.right
|
||||
rightMargin: hifi.dimensions.contentMargin.x
|
||||
}
|
||||
|
||||
function toggleCollapsed() {
|
||||
if (isCollapsible) {
|
||||
isCollapsed = !isCollapsed;
|
||||
for (var i = 1; i < children.length; i++) {
|
||||
children[i].visible = !isCollapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: sectionName
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: leadingSpace.height + topBar.height + heading.height + bottomBar.height
|
||||
|
||||
Item {
|
||||
id: leadingSpace
|
||||
width: 1
|
||||
height: isFirst ? 7 : 0
|
||||
anchors.top: parent.top
|
||||
}
|
||||
|
||||
Item {
|
||||
id: topBar
|
||||
visible: !isFirst
|
||||
height: visible ? 2 : 0
|
||||
anchors.top: leadingSpace.bottom
|
||||
|
||||
Rectangle {
|
||||
id: shadow
|
||||
width: 480
|
||||
height: 1
|
||||
color: hifi.colors.baseGrayShadow
|
||||
x: -hifi.dimensions.contentMargin.x
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 480
|
||||
height: 1
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
x: -hifi.dimensions.contentMargin.x
|
||||
anchors.top: shadow.bottom
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: heading
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: topBar.bottom
|
||||
}
|
||||
height: isCollapsible ? 36 : 28
|
||||
|
||||
RalewayRegular {
|
||||
id: title
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
topMargin: 12
|
||||
}
|
||||
size: hifi.fontSizes.sectionName
|
||||
font.capitalization: Font.AllUppercase
|
||||
text: name
|
||||
color: hifi.colors.lightGrayText
|
||||
}
|
||||
|
||||
HiFiGlyphs {
|
||||
anchors {
|
||||
top: title.top
|
||||
topMargin: -9
|
||||
right: parent.right
|
||||
rightMargin: -4
|
||||
}
|
||||
size: hifi.fontSizes.disclosureButton
|
||||
text: isCollapsed ? hifi.glyphs.disclosureButtonExpand : hifi.glyphs.disclosureButtonCollapse
|
||||
color: hifi.colors.lightGrayText
|
||||
visible: isCollapsible
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
// Events are propogated so that any active control is defocused.
|
||||
anchors.fill: parent
|
||||
propagateComposedEvents: true
|
||||
onPressed: {
|
||||
toggleCollapsed();
|
||||
mouse.accepted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LinearGradient {
|
||||
id: bottomBar
|
||||
visible: false
|
||||
width: 480
|
||||
height: visible ? 4 : 0
|
||||
x: -hifi.dimensions.contentMargin.x
|
||||
anchors.top: heading.bottom
|
||||
start: Qt.point(0, 0)
|
||||
end: Qt.point(0, 4)
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: hifi.colors.darkGray }
|
||||
GradientStop { position: 1.0; color: hifi.colors.baseGray } // Equivalent of darkGray0 over baseGray background.
|
||||
}
|
||||
cached: true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,10 +4,14 @@ import QtGraphicalEffects 1.0
|
|||
Item {
|
||||
id: tabletButton
|
||||
property var uuid;
|
||||
property string text: "EDIT"
|
||||
property string icon: "icons/edit-icon.svg"
|
||||
property string activeText: tabletButton.text
|
||||
property string icon: "icons/tablet-icons/edit-i.svg"
|
||||
property string hoverIcon: tabletButton.icon
|
||||
property string activeIcon: tabletButton.icon
|
||||
property string activeHoverIcon: tabletButton.activeIcon
|
||||
property string text: "EDIT"
|
||||
property string hoverText: tabletButton.text
|
||||
property string activeText: tabletButton.text
|
||||
property string activeHoverText: tabletButton.activeText
|
||||
property bool isActive: false
|
||||
property bool inDebugMode: false
|
||||
property bool isEntered: false
|
||||
|
@ -25,9 +29,9 @@ Item {
|
|||
|
||||
onIsActiveChanged: {
|
||||
if (tabletButton.isEntered) {
|
||||
tabletButton.state = (tabletButton.isActive) ? "hover active state" : "hover sate";
|
||||
tabletButton.state = (tabletButton.isActive) ? "hover active state" : "hover state";
|
||||
} else {
|
||||
tabletButton.state = (tabletButton.isActive) ? "active state" : "base sate";
|
||||
tabletButton.state = (tabletButton.isActive) ? "active state" : "base state";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +93,6 @@ Item {
|
|||
id: icon
|
||||
width: 50
|
||||
height: 50
|
||||
visible: false
|
||||
anchors.bottom: text.top
|
||||
anchors.bottomMargin: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
@ -97,13 +100,6 @@ Item {
|
|||
source: tabletButton.urlHelper(tabletButton.icon)
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
id: iconColorOverlay
|
||||
anchors.fill: icon
|
||||
source: icon
|
||||
color: "#ffffff"
|
||||
}
|
||||
|
||||
Text {
|
||||
id: text
|
||||
color: "#ffffff"
|
||||
|
@ -166,6 +162,17 @@ Item {
|
|||
target: glow
|
||||
visible: true
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: text
|
||||
color: "#ffffff"
|
||||
text: tabletButton.hoverText
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: icon
|
||||
source: tabletButton.urlHelper(tabletButton.hoverIcon)
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "active state"
|
||||
|
@ -188,11 +195,6 @@ Item {
|
|||
text: tabletButton.activeText
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: iconColorOverlay
|
||||
color: "#333333"
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: icon
|
||||
source: tabletButton.urlHelper(tabletButton.activeIcon)
|
||||
|
@ -221,13 +223,13 @@ Item {
|
|||
PropertyChanges {
|
||||
target: text
|
||||
color: "#333333"
|
||||
text: tabletButton.activeHoverText
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: iconColorOverlay
|
||||
color: "#333333"
|
||||
target: icon
|
||||
source: tabletButton.urlHelper(tabletButton.activeHoverIcon)
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// TabletGeneralSettings.qml
|
||||
// scripts/system/
|
||||
//
|
||||
// Created by Dante Ruiz on 9 Feb 2017
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import "tabletWindows"
|
||||
import "../../dialogs"
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
StackView {
|
||||
id: profileRoot
|
||||
initialItem: root
|
||||
objectName: "stack"
|
||||
|
||||
property var eventBridge;
|
||||
signal sendToScript(var message);
|
||||
|
||||
function pushSource(path) {
|
||||
editRoot.push(Qt.reslovedUrl(path));
|
||||
}
|
||||
|
||||
function popSource() {
|
||||
|
||||
}
|
||||
|
||||
TabletPreferencesDialog {
|
||||
id: root
|
||||
objectName: "GeneralPreferencesDialog"
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
showCategories: ["UI", "Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Sixense Controllers", "Perception Neuron", "Kinect"]
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,775 @@
|
|||
//
|
||||
// FileDialog.qml
|
||||
//
|
||||
// Created by Bradley Dante Ruiz on 13 Feb 2017
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import Qt.labs.folderlistmodel 2.1
|
||||
import Qt.labs.settings 1.0
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtQuick.Dialogs 1.2 as OriginalDialogs
|
||||
|
||||
import ".."
|
||||
import "../../../controls-uit"
|
||||
import "../../../styles-uit"
|
||||
import "../../../windows"
|
||||
|
||||
import "../../../dialogs/fileDialog"
|
||||
|
||||
//FIXME implement shortcuts for favorite location
|
||||
Item {
|
||||
id: root
|
||||
anchors.top: parent.top
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
Settings {
|
||||
category: "FileDialog"
|
||||
property alias width: root.width
|
||||
property alias height: root.height
|
||||
property alias x: root.x
|
||||
property alias y: root.y
|
||||
}
|
||||
|
||||
|
||||
// Set from OffscreenUi::getOpenFile()
|
||||
// property alias caption: root.title;
|
||||
// Set from OffscreenUi::getOpenFile()
|
||||
property alias dir: fileTableModel.folder;
|
||||
// Set from OffscreenUi::getOpenFile()
|
||||
property alias filter: selectionType.filtersString;
|
||||
// Set from OffscreenUi::getOpenFile()
|
||||
property int options; // <-- FIXME unused
|
||||
|
||||
property string iconText: root.title !== "" ? hifi.glyphs.scriptUpload : ""
|
||||
property int iconSize: 40
|
||||
|
||||
property bool selectDirectory: false;
|
||||
property bool showHidden: false;
|
||||
// FIXME implement
|
||||
property bool multiSelect: false;
|
||||
property bool saveDialog: false;
|
||||
property var helper: fileDialogHelper
|
||||
property alias model: fileTableView.model
|
||||
property var drives: helper.drives()
|
||||
|
||||
property int titleWidth: 0
|
||||
|
||||
signal selectedFile(var file);
|
||||
signal canceled();
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("Helper " + helper + " drives " + drives);
|
||||
|
||||
fileDialogItem.keyboardEnabled = HMD.active;
|
||||
|
||||
// HACK: The following lines force the model to initialize properly such that the go-up button
|
||||
// works properly from the initial screen.
|
||||
var initialFolder = folderListModel.folder;
|
||||
fileTableModel.folder = helper.pathToUrl(drives[0]);
|
||||
fileTableModel.folder = initialFolder;
|
||||
|
||||
iconText = root.title !== "" ? hifi.glyphs.scriptUpload : "";
|
||||
|
||||
// Clear selection when click on external frame.
|
||||
//frameClicked.connect(function() { d.clearSelection(); });
|
||||
|
||||
if (selectDirectory) {
|
||||
currentSelection.text = d.capitalizeDrive(helper.urlToPath(initialFolder));
|
||||
d.currentSelectionIsFolder = true;
|
||||
d.currentSelectionUrl = initialFolder;
|
||||
}
|
||||
|
||||
helper.contentsChanged.connect(function() {
|
||||
if (folderListModel) {
|
||||
// Make folderListModel refresh.
|
||||
var save = folderListModel.folder;
|
||||
folderListModel.folder = "";
|
||||
folderListModel.folder = save;
|
||||
}
|
||||
});
|
||||
|
||||
fileTableView.forceActiveFocus();
|
||||
}
|
||||
|
||||
Item {
|
||||
id: fileDialogItem
|
||||
clip: true
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
anchors.margins: 0
|
||||
|
||||
property bool keyboardEnabled: false
|
||||
property bool keyboardRaised: false
|
||||
property bool punctuationMode: false
|
||||
|
||||
MouseArea {
|
||||
// Clear selection when click on internal unused area.
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
d.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: navControls
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: hifi.dimensions.contentMargin.y
|
||||
left: parent.left
|
||||
}
|
||||
spacing: hifi.dimensions.contentSpacing.x
|
||||
|
||||
GlyphButton {
|
||||
id: upButton
|
||||
glyph: hifi.glyphs.levelUp
|
||||
width: height
|
||||
size: 30
|
||||
enabled: fileTableModel.parentFolder && fileTableModel.parentFolder !== ""
|
||||
onClicked: d.navigateUp();
|
||||
}
|
||||
|
||||
GlyphButton {
|
||||
id: homeButton
|
||||
property var destination: helper.home();
|
||||
glyph: hifi.glyphs.home
|
||||
size: 28
|
||||
width: height
|
||||
enabled: d.homeDestination ? true : false
|
||||
onClicked: d.navigateHome();
|
||||
}
|
||||
}
|
||||
|
||||
TabletComboBox {
|
||||
id: pathSelector
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: hifi.dimensions.contentMargin.y
|
||||
left: navControls.right
|
||||
leftMargin: hifi.dimensions.contentSpacing.x
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
property var lastValidFolder: helper.urlToPath(fileTableModel.folder)
|
||||
|
||||
function calculatePathChoices(folder) {
|
||||
var folders = folder.split("/"),
|
||||
choices = [],
|
||||
i, length;
|
||||
|
||||
if (folders[folders.length - 1] === "") {
|
||||
folders.pop();
|
||||
}
|
||||
|
||||
choices.push(folders[0]);
|
||||
|
||||
for (i = 1, length = folders.length; i < length; i++) {
|
||||
choices.push(choices[i - 1] + "/" + folders[i]);
|
||||
}
|
||||
|
||||
if (folders[0] === "") {
|
||||
// Special handling for OSX root dir.
|
||||
choices[0] = "/";
|
||||
}
|
||||
|
||||
choices.reverse();
|
||||
|
||||
if (drives && drives.length > 1) {
|
||||
choices.push("This PC");
|
||||
}
|
||||
|
||||
if (choices.length > 0) {
|
||||
pathSelector.model = choices;
|
||||
}
|
||||
}
|
||||
|
||||
onLastValidFolderChanged: {
|
||||
var folder = d.capitalizeDrive(lastValidFolder);
|
||||
calculatePathChoices(folder);
|
||||
}
|
||||
|
||||
onCurrentTextChanged: {
|
||||
var folder = currentText;
|
||||
|
||||
if (/^[a-zA-z]:$/.test(folder)) {
|
||||
folder = "file:///" + folder + "/";
|
||||
} else if (folder === "This PC") {
|
||||
folder = "file:///";
|
||||
} else {
|
||||
folder = helper.pathToUrl(folder);
|
||||
}
|
||||
|
||||
if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) {
|
||||
if (root.selectDirectory) {
|
||||
currentSelection.text = currentText !== "This PC" ? currentText : "";
|
||||
d.currentSelectionUrl = helper.pathToUrl(currentText);
|
||||
}
|
||||
fileTableModel.folder = folder;
|
||||
fileTableView.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property var currentSelectionUrl;
|
||||
readonly property string currentSelectionPath: helper.urlToPath(currentSelectionUrl);
|
||||
property bool currentSelectionIsFolder;
|
||||
property var backStack: []
|
||||
property var tableViewConnection: Connections { target: fileTableView; onCurrentRowChanged: d.update(); }
|
||||
property var modelConnection: Connections { target: fileTableModel; onFolderChanged: d.update(); }
|
||||
property var homeDestination: helper.home();
|
||||
|
||||
function capitalizeDrive(path) {
|
||||
// Consistently capitalize drive letter for Windows.
|
||||
if (/[a-zA-Z]:/.test(path)) {
|
||||
return path.charAt(0).toUpperCase() + path.slice(1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
function update() {
|
||||
var row = fileTableView.currentRow;
|
||||
|
||||
if (row === -1) {
|
||||
if (!root.selectDirectory) {
|
||||
currentSelection.text = "";
|
||||
currentSelectionIsFolder = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath);
|
||||
currentSelectionIsFolder = fileTableView.model.isFolder(row);
|
||||
if (root.selectDirectory || !currentSelectionIsFolder) {
|
||||
currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl));
|
||||
} else {
|
||||
currentSelection.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
function navigateUp() {
|
||||
if (fileTableModel.parentFolder && fileTableModel.parentFolder !== "") {
|
||||
fileTableModel.folder = fileTableModel.parentFolder;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function navigateHome() {
|
||||
fileTableModel.folder = homeDestination;
|
||||
return true;
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
fileTableView.selection.clear();
|
||||
fileTableView.currentRow = -1;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
FolderListModel {
|
||||
id: folderListModel
|
||||
nameFilters: selectionType.currentFilter
|
||||
showDirsFirst: true
|
||||
showDotAndDotDot: false
|
||||
showFiles: !root.selectDirectory
|
||||
Component.onCompleted: {
|
||||
showFiles = !root.selectDirectory
|
||||
}
|
||||
|
||||
onFolderChanged: {
|
||||
fileTableModel.update(); // Update once the data from the folder change is available.
|
||||
}
|
||||
|
||||
function getItem(index, field) {
|
||||
return get(index, field);
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
// Emulates FolderListModel but contains drive data.
|
||||
id: driveListModel
|
||||
|
||||
property int count: 1
|
||||
|
||||
Component.onCompleted: initialize();
|
||||
|
||||
function initialize() {
|
||||
var drive,
|
||||
i;
|
||||
|
||||
count = drives.length;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
drive = drives[i].slice(0, -1); // Remove trailing "/".
|
||||
append({
|
||||
fileName: drive,
|
||||
fileModified: new Date(0),
|
||||
fileSize: 0,
|
||||
filePath: drive + "/",
|
||||
fileIsDir: true,
|
||||
fileNameSort: drive.toLowerCase()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getItem(index, field) {
|
||||
return get(index)[field];
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: fileTableModel
|
||||
|
||||
// FolderListModel has a couple of problems:
|
||||
// 1) Files and directories sort case-sensitively: https://bugreports.qt.io/browse/QTBUG-48757
|
||||
// 2) Cannot browse up to the "computer" level to view Windows drives: https://bugreports.qt.io/browse/QTBUG-42901
|
||||
//
|
||||
// To solve these problems an intermediary ListModel is used that implements proper sorting and can be populated with
|
||||
// drive information when viewing at the computer level.
|
||||
|
||||
property var folder
|
||||
property int sortOrder: Qt.AscendingOrder
|
||||
property int sortColumn: 0
|
||||
property var model: folderListModel
|
||||
property string parentFolder: calculateParentFolder();
|
||||
|
||||
readonly property string rootFolder: "file:///"
|
||||
|
||||
function calculateParentFolder() {
|
||||
if (model === folderListModel) {
|
||||
if (folderListModel.parentFolder.toString() === "" && driveListModel.count > 1) {
|
||||
return rootFolder;
|
||||
} else {
|
||||
return folderListModel.parentFolder;
|
||||
}
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
onFolderChanged: {
|
||||
if (folder === rootFolder) {
|
||||
model = driveListModel;
|
||||
helper.monitorDirectory("");
|
||||
update();
|
||||
} else {
|
||||
var needsUpdate = model === driveListModel && folder === folderListModel.folder;
|
||||
|
||||
model = folderListModel;
|
||||
folderListModel.folder = folder;
|
||||
helper.monitorDirectory(helper.urlToPath(folder));
|
||||
|
||||
if (needsUpdate) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isFolder(row) {
|
||||
if (row === -1) {
|
||||
return false;
|
||||
}
|
||||
return get(row).fileIsDir;
|
||||
}
|
||||
|
||||
function update() {
|
||||
var dataFields = ["fileName", "fileModified", "fileSize"],
|
||||
sortFields = ["fileNameSort", "fileModified", "fileSize"],
|
||||
dataField = dataFields[sortColumn],
|
||||
sortField = sortFields[sortColumn],
|
||||
sortValue,
|
||||
fileName,
|
||||
fileIsDir,
|
||||
comparisonFunction,
|
||||
lower,
|
||||
middle,
|
||||
upper,
|
||||
rows = 0,
|
||||
i;
|
||||
|
||||
clear();
|
||||
|
||||
comparisonFunction = sortOrder === Qt.AscendingOrder
|
||||
? function(a, b) { return a < b; }
|
||||
: function(a, b) { return a > b; }
|
||||
|
||||
for (i = 0; i < model.count; i++) {
|
||||
fileName = model.getItem(i, "fileName");
|
||||
fileIsDir = model.getItem(i, "fileIsDir");
|
||||
|
||||
sortValue = model.getItem(i, dataField);
|
||||
if (dataField === "fileName") {
|
||||
// Directories first by prefixing a "*".
|
||||
// Case-insensitive.
|
||||
sortValue = (fileIsDir ? "*" : "") + sortValue.toLowerCase();
|
||||
}
|
||||
|
||||
lower = 0;
|
||||
upper = rows;
|
||||
while (lower < upper) {
|
||||
middle = Math.floor((lower + upper) / 2);
|
||||
var lessThan;
|
||||
if (comparisonFunction(sortValue, get(middle)[sortField])) {
|
||||
lessThan = true;
|
||||
upper = middle;
|
||||
} else {
|
||||
lessThan = false;
|
||||
lower = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
insert(lower, {
|
||||
fileName: fileName,
|
||||
fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
|
||||
fileSize: model.getItem(i, "fileSize"),
|
||||
filePath: model.getItem(i, "filePath"),
|
||||
fileIsDir: fileIsDir,
|
||||
fileNameSort: (fileIsDir ? "*" : "") + fileName.toLowerCase()
|
||||
});
|
||||
|
||||
rows++;
|
||||
}
|
||||
|
||||
d.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
Table {
|
||||
id: fileTableView
|
||||
colorScheme: hifi.colorSchemes.light
|
||||
anchors {
|
||||
top: navControls.bottom
|
||||
topMargin: hifi.dimensions.contentSpacing.y
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: currentSelection.top
|
||||
bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height
|
||||
}
|
||||
headerVisible: !selectDirectory
|
||||
onDoubleClicked: navigateToRow(row);
|
||||
focus: true
|
||||
Keys.onReturnPressed: navigateToCurrentRow();
|
||||
Keys.onEnterPressed: navigateToCurrentRow();
|
||||
|
||||
sortIndicatorColumn: 0
|
||||
sortIndicatorOrder: Qt.AscendingOrder
|
||||
sortIndicatorVisible: true
|
||||
|
||||
model: fileTableModel
|
||||
|
||||
function updateSort() {
|
||||
model.sortOrder = sortIndicatorOrder;
|
||||
model.sortColumn = sortIndicatorColumn;
|
||||
model.update();
|
||||
}
|
||||
|
||||
onSortIndicatorColumnChanged: { updateSort(); }
|
||||
|
||||
onSortIndicatorOrderChanged: { updateSort(); }
|
||||
|
||||
itemDelegate: Item {
|
||||
clip: true
|
||||
|
||||
//FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
|
||||
//FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; }
|
||||
|
||||
FiraSansSemiBold {
|
||||
text: getText();
|
||||
elide: styleData.elideMode
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: hifi.dimensions.tablePadding
|
||||
right: parent.right
|
||||
rightMargin: hifi.dimensions.tablePadding
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
size: hifi.fontSizes.tableText
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
//font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir)
|
||||
//? firaSansSemiBold.name : firaSansRegular.name
|
||||
|
||||
function getText() {
|
||||
if (styleData.row === -1) {
|
||||
return styleData.value;
|
||||
}
|
||||
|
||||
switch (styleData.column) {
|
||||
case 1: return fileTableView.model.get(styleData.row).fileIsDir ? "" : styleData.value;
|
||||
case 2: return fileTableView.model.get(styleData.row).fileIsDir ? "" : formatSize(styleData.value);
|
||||
default: return styleData.value;
|
||||
}
|
||||
}
|
||||
function formatSize(size) {
|
||||
var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ];
|
||||
var suffixIndex = 0
|
||||
while ((size / 1024.0) > 1.1) {
|
||||
size /= 1024.0;
|
||||
++suffixIndex;
|
||||
}
|
||||
|
||||
size = Math.round(size*1000)/1000;
|
||||
size = size.toLocaleString()
|
||||
|
||||
return size + " " + suffixes[suffixIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TableViewColumn {
|
||||
id: fileNameColumn
|
||||
role: "fileName"
|
||||
title: "Name"
|
||||
width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width
|
||||
movable: false
|
||||
resizable: true
|
||||
}
|
||||
TableViewColumn {
|
||||
id: fileMofifiedColumn
|
||||
role: "fileModified"
|
||||
title: "Date"
|
||||
width: 0.3 * fileTableView.width
|
||||
movable: false
|
||||
resizable: true
|
||||
visible: !selectDirectory
|
||||
}
|
||||
TableViewColumn {
|
||||
role: "fileSize"
|
||||
title: "Size"
|
||||
width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width
|
||||
movable: false
|
||||
resizable: true
|
||||
visible: !selectDirectory
|
||||
}
|
||||
|
||||
function navigateToRow(row) {
|
||||
currentRow = row;
|
||||
navigateToCurrentRow();
|
||||
}
|
||||
|
||||
function navigateToCurrentRow() {
|
||||
var row = fileTableView.currentRow
|
||||
var isFolder = model.isFolder(row);
|
||||
var file = model.get(row).filePath;
|
||||
if (isFolder) {
|
||||
fileTableView.model.folder = helper.pathToUrl(file);
|
||||
} else {
|
||||
okAction.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
property string prefix: ""
|
||||
|
||||
function addToPrefix(event) {
|
||||
if (!event.text || event.text === "") {
|
||||
return false;
|
||||
}
|
||||
var newPrefix = prefix + event.text.toLowerCase();
|
||||
var matchedIndex = -1;
|
||||
for (var i = 0; i < model.count; ++i) {
|
||||
var name = model.get(i).fileName.toLowerCase();
|
||||
if (0 === name.indexOf(newPrefix)) {
|
||||
matchedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedIndex !== -1) {
|
||||
fileTableView.selection.clear();
|
||||
fileTableView.selection.select(matchedIndex);
|
||||
fileTableView.currentRow = matchedIndex;
|
||||
fileTableView.prefix = newPrefix;
|
||||
}
|
||||
prefixClearTimer.restart();
|
||||
return true;
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: prefixClearTimer
|
||||
interval: 1000
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: fileTableView.prefix = "";
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Backspace:
|
||||
case Qt.Key_Tab:
|
||||
case Qt.Key_Backtab:
|
||||
event.accepted = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (addToPrefix(event)) {
|
||||
event.accepted = true
|
||||
} else {
|
||||
event.accepted = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: currentSelection
|
||||
label: selectDirectory ? "Directory:" : "File name:"
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: selectionType.visible ? selectionType.left: parent.right
|
||||
rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0
|
||||
bottom: keyboard.top
|
||||
bottomMargin: hifi.dimensions.contentSpacing.y
|
||||
}
|
||||
readOnly: !root.saveDialog
|
||||
activeFocusOnTab: !readOnly
|
||||
onActiveFocusChanged: if (activeFocus) { selectAll(); }
|
||||
onAccepted: okAction.trigger();
|
||||
}
|
||||
|
||||
FileTypeSelection {
|
||||
id: selectionType
|
||||
anchors {
|
||||
top: currentSelection.top
|
||||
left: buttonRow.left
|
||||
right: parent.right
|
||||
}
|
||||
visible: !selectDirectory && filtersCount > 1
|
||||
KeyNavigation.left: fileTableView
|
||||
KeyNavigation.right: openButton
|
||||
}
|
||||
|
||||
Keyboard {
|
||||
id: keyboard
|
||||
raised: parent.keyboardEnabled && parent.keyboardRaised
|
||||
numeric: parent.punctuationMode
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: buttonRow.top
|
||||
bottomMargin: visible ? hifi.dimensions.contentSpacing.y : 0
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttonRow
|
||||
anchors {
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
spacing: hifi.dimensions.contentSpacing.y
|
||||
|
||||
Button {
|
||||
id: openButton
|
||||
color: hifi.buttons.blue
|
||||
action: okAction
|
||||
Keys.onReturnPressed: okAction.trigger()
|
||||
KeyNavigation.up: selectionType
|
||||
KeyNavigation.left: selectionType
|
||||
KeyNavigation.right: cancelButton
|
||||
}
|
||||
|
||||
Button {
|
||||
id: cancelButton
|
||||
action: cancelAction
|
||||
KeyNavigation.up: selectionType
|
||||
KeyNavigation.left: openButton
|
||||
KeyNavigation.right: fileTableView.contentItem
|
||||
Keys.onReturnPressed: { canceled(); root.enabled = false }
|
||||
}
|
||||
}
|
||||
|
||||
Action {
|
||||
id: okAction
|
||||
text: currentSelection.text ? (root.selectDirectory && fileTableView.currentRow === -1 ? "Choose" : (root.saveDialog ? "Save" : "Open")) : "Open"
|
||||
enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false
|
||||
onTriggered: {
|
||||
if (!root.selectDirectory && !d.currentSelectionIsFolder
|
||||
|| root.selectDirectory && fileTableView.currentRow === -1) {
|
||||
okActionTimer.start();
|
||||
} else {
|
||||
fileTableView.navigateToCurrentRow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: okActionTimer
|
||||
interval: 50
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (!root.saveDialog) {
|
||||
selectedFile(d.currentSelectionUrl);
|
||||
profileRoot.pop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the ambiguity between different cases
|
||||
// * typed name (with or without extension)
|
||||
// * full path vs relative vs filename only
|
||||
var selection = helper.saveHelper(currentSelection.text, root.dir, selectionType.currentFilter);
|
||||
|
||||
if (!selection) {
|
||||
desktop.messageBox({ icon: OriginalDialogs.StandardIcon.Warning, text: "Unable to parse selection" })
|
||||
return;
|
||||
}
|
||||
|
||||
if (helper.urlIsDir(selection)) {
|
||||
root.dir = selection;
|
||||
currentSelection.text = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the file is a valid target
|
||||
if (!helper.urlIsWritable(selection)) {
|
||||
desktop.messageBox({
|
||||
icon: OriginalDialogs.StandardIcon.Warning,
|
||||
text: "Unable to write to location " + selection
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
if (helper.urlExists(selection)) {
|
||||
var messageBox = desktop.messageBox({
|
||||
icon: OriginalDialogs.StandardIcon.Question,
|
||||
buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No,
|
||||
text: "Do you wish to overwrite " + selection + "?",
|
||||
});
|
||||
var result = messageBox.exec();
|
||||
if (OriginalDialogs.StandardButton.Yes !== result) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Selecting " + selection)
|
||||
selectedFile(selection);
|
||||
//root.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
Action {
|
||||
id: cancelAction
|
||||
text: "Cancel"
|
||||
onTriggered: { profileRoot.pop(); }
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Backspace:
|
||||
event.accepted = d.navigateUp();
|
||||
break;
|
||||
|
||||
case Qt.Key_Home:
|
||||
event.accepted = d.navigateHome();
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
//
|
||||
// TabletPreferencesDialog.qml
|
||||
//
|
||||
// Created by Dante Ruiz on 9 Feb 2017
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
|
||||
import "."
|
||||
import "./preferences"
|
||||
import "../../../styles-uit"
|
||||
import "../../../controls-uit" as HifiControls
|
||||
|
||||
Item {
|
||||
id: dialog
|
||||
width: 480
|
||||
height: 720
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
property var sections: []
|
||||
property var showCategories: []
|
||||
|
||||
function saveAll() {
|
||||
for (var i = 0; i < sections.length; ++i) {
|
||||
var section = sections[i];
|
||||
section.saveAll();
|
||||
}
|
||||
}
|
||||
|
||||
function restoreAll() {
|
||||
for (var i = 0; i < sections.length; ++i) {
|
||||
var section = sections[i];
|
||||
section.restoreAll();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: main
|
||||
height: parent.height - 40
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: footer.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0
|
||||
color: "#2b2b2b"
|
||||
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
position: 1
|
||||
color: "#0f212e"
|
||||
}
|
||||
}
|
||||
Flickable {
|
||||
id: scrollView
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentWidth: parent.width
|
||||
contentHeight: getSectionsHeight();
|
||||
Column {
|
||||
width: 480
|
||||
Component {
|
||||
id: sectionBuilder
|
||||
Section {}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
var categories = Preferences.categories;
|
||||
var i;
|
||||
|
||||
// build a map of valid categories.
|
||||
var categoryMap = {};
|
||||
for (i = 0; i < categories.length; i++) {
|
||||
categoryMap[categories[i]] = true;
|
||||
}
|
||||
|
||||
// create a section for each valid category in showCategories
|
||||
// NOTE: the sort order of items in the showCategories array is the same order in the dialog.
|
||||
for (i = 0; i < showCategories.length; i++) {
|
||||
if (categoryMap[showCategories[i]]) {
|
||||
sections.push(sectionBuilder.createObject(prefControls, {name: showCategories[i]}));
|
||||
}
|
||||
}
|
||||
|
||||
if (sections.length) {
|
||||
// Default sections to expanded/collapsed as appropriate for dialog.
|
||||
if (sections.length === 1) {
|
||||
sections[0].collapsable = false
|
||||
sections[0].expanded = true
|
||||
} else {
|
||||
for (i = 0; i < sections.length; i++) {
|
||||
sections[i].collapsable = false;
|
||||
sections[i].expanded = true;
|
||||
}
|
||||
}
|
||||
sections[0].isFirst = true;
|
||||
sections[sections.length - 1].isLast = true;
|
||||
}
|
||||
|
||||
scrollView.contentHeight = scrollView.getSectionsHeight();
|
||||
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
id: prefControls
|
||||
width: 480
|
||||
}
|
||||
}
|
||||
|
||||
function getSectionsHeight() {
|
||||
var totalHeight = 0;
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
totalHeight += sections[i].height + sections[i].getPreferencesHeight();
|
||||
}
|
||||
console.log(totalHeight);
|
||||
return totalHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: footer
|
||||
height: 40
|
||||
|
||||
anchors {
|
||||
top: main.bottom
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0
|
||||
color: "#2b2b2b"
|
||||
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
position: 1
|
||||
color: "#0f212e"
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors {
|
||||
top: parent,top
|
||||
right: parent.right
|
||||
rightMargin: hifi.dimensions.contentMargin.x
|
||||
}
|
||||
|
||||
spacing: hifi.dimensions.contentSpacing.x
|
||||
HifiControls.Button {
|
||||
text: "Save changes"
|
||||
color: hifi.buttons.blue
|
||||
onClicked: root.saveAll()
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
text: "Cancel"
|
||||
color: hifi.buttons.white
|
||||
onClicked: root.restoreAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// Preference.qml
|
||||
//
|
||||
// Created by Bradley Dante Ruiz on 13 Feb 2017
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
Item {
|
||||
id: root
|
||||
anchors { left: parent.left; right: parent.right }
|
||||
property var preference;
|
||||
property string label: preference ? preference.name : "";
|
||||
property bool isFirstCheckBox;
|
||||
Component.onCompleted: {
|
||||
if (preference) {
|
||||
preference.load();
|
||||
enabled = Qt.binding(function() { return preference.enabled; } );
|
||||
}
|
||||
}
|
||||
|
||||
function restore() { }
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
//
|
||||
// Section.qml
|
||||
//
|
||||
// Created by Bradley Dante Ruiz on 13 Feb 2017
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import Hifi 1.0
|
||||
|
||||
import "../../../../dialogs/preferences"
|
||||
import "../../../../controls-uit" as HiFiControls
|
||||
import "../../../../styles-uit"
|
||||
import "."
|
||||
|
||||
Preference {
|
||||
id: root
|
||||
property bool collapsable: false
|
||||
property bool expanded: false
|
||||
property bool isFirst: false
|
||||
property bool isLast: false
|
||||
property string name: "Header"
|
||||
property real spacing: 8
|
||||
default property alias preferences: contentContainer.children
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
function saveAll() {
|
||||
for (var i = 0; i < d.preferences.length; ++i) {
|
||||
var preference = d.preferences[i];
|
||||
preference.save();
|
||||
}
|
||||
}
|
||||
|
||||
function restoreAll() {
|
||||
for (var i = 0; i < d.preferences.length; ++i) {
|
||||
var preference = d.preferences[i];
|
||||
preference.restore();
|
||||
}
|
||||
}
|
||||
|
||||
function getPreferencesHeight() {
|
||||
var height = 0;
|
||||
for (var index = 0; index < d.preferences.length; index++) {
|
||||
height += d.preferences[index].height;
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
children: [ contentContainer ]
|
||||
|
||||
height: contentContainer.height + (contentContainer.isCollapsed ? 0 : hifi.dimensions.controlInterlineHeight)
|
||||
|
||||
Component.onCompleted: d.buildPreferences();
|
||||
|
||||
HiFiControls.TabletContentSection {
|
||||
id: contentContainer
|
||||
name: root.name
|
||||
isFirst: root.isFirst
|
||||
isCollapsible: root.collapsable
|
||||
isCollapsed: !root.expanded
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: 0
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property var editableBuilder: Component { EditablePreference { } }
|
||||
property var browsableBuilder: Component { TabletBrowsablePreference { } }
|
||||
property var spinnerBuilder: Component { SpinBoxPreference { } }
|
||||
property var checkboxBuilder: Component { CheckBoxPreference { } }
|
||||
property var sliderBuilder: Component { SliderPreference { } }
|
||||
property var avatarBuilder: Component { AvatarPreference { } }
|
||||
property var buttonBuilder: Component { ButtonPreference { } }
|
||||
property var comboBoxBuilder: Component { ComboBoxPreference { } }
|
||||
property var preferences: []
|
||||
property int checkBoxCount: 0
|
||||
|
||||
function buildPreferences() {
|
||||
var categoryPreferences = Preferences.preferencesByCategory[root.name];
|
||||
if (categoryPreferences) {
|
||||
console.log("Category " + root.name + " with " + categoryPreferences.length + " preferences");
|
||||
for (var j = 0; j < categoryPreferences.length; ++j) {
|
||||
buildPreference(categoryPreferences[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildPreference(preference) {
|
||||
console.log("\tPreference type " + preference.type + " name " + preference.name)
|
||||
var builder;
|
||||
switch (preference.type) {
|
||||
case Preference.Editable:
|
||||
checkBoxCount = 0;
|
||||
builder = editableBuilder;
|
||||
break;
|
||||
|
||||
case Preference.Browsable:
|
||||
checkBoxCount = 0;
|
||||
builder = browsableBuilder;
|
||||
break;
|
||||
|
||||
case Preference.Spinner:
|
||||
checkBoxCount = 0;
|
||||
builder = spinnerBuilder;
|
||||
break;
|
||||
|
||||
case Preference.Slider:
|
||||
checkBoxCount = 0;
|
||||
builder = sliderBuilder;
|
||||
break;
|
||||
|
||||
case Preference.Checkbox:
|
||||
checkBoxCount++;
|
||||
builder = checkboxBuilder;
|
||||
break;
|
||||
|
||||
case Preference.Avatar:
|
||||
checkBoxCount = 0;
|
||||
builder = avatarBuilder;
|
||||
break;
|
||||
|
||||
case Preference.Button:
|
||||
checkBoxCount = 0;
|
||||
builder = buttonBuilder;
|
||||
break;
|
||||
|
||||
case Preference.ComboBox:
|
||||
checkBoxCount = 0;
|
||||
builder = comboBoxBuilder;
|
||||
break;
|
||||
};
|
||||
|
||||
if (builder) {
|
||||
preferences.push(builder.createObject(contentContainer, { preference: preference, isFirstCheckBox: (checkBoxCount === 1) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// BrowsablePreference.qml
|
||||
//
|
||||
// Created by Dante Ruiz Davis on 13 Feb 2017
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
|
||||
import "../../../../dialogs"
|
||||
import "../../../../controls-uit"
|
||||
import "../"
|
||||
|
||||
Preference {
|
||||
id: root
|
||||
property alias text: dataTextField.text
|
||||
property alias placeholderText: dataTextField.placeholderText
|
||||
height: control.height + hifi.dimensions.controlInterlineHeight
|
||||
|
||||
Component.onCompleted: {
|
||||
dataTextField.text = preference.value;
|
||||
}
|
||||
|
||||
function save() {
|
||||
preference.value = dataTextField.text;
|
||||
preference.save();
|
||||
}
|
||||
|
||||
Item {
|
||||
id: control
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
height: Math.max(dataTextField.controlHeight, button.height)
|
||||
|
||||
TextField {
|
||||
id: dataTextField
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: button.left
|
||||
rightMargin: hifi.dimensions.contentSpacing.x
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
label: root.label
|
||||
placeholderText: root.placeholderText
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileBrowserBuilder;
|
||||
TabletFileDialog { selectDirectory: true }
|
||||
}
|
||||
|
||||
Button {
|
||||
id: button
|
||||
text: preference.browseLabel
|
||||
anchors {
|
||||
right: parent.right
|
||||
verticalCenter: dataTextField.verticalCenter
|
||||
}
|
||||
onClicked: {
|
||||
var browser = fileBrowserBuilder.createObject({
|
||||
selectDirectory: true,
|
||||
dir: fileDialogHelper.pathToUrl(preference.value)
|
||||
});
|
||||
|
||||
browser.selectedFile.connect(function(fileUrl){
|
||||
console.log(fileUrl);
|
||||
dataTextField.text = fileDialogHelper.urlToPath(fileUrl);
|
||||
});
|
||||
|
||||
profileRoot.push(browser);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -296,6 +296,10 @@ Fadable {
|
|||
// fall through
|
||||
|
||||
default:
|
||||
if (MyAvatar.isAway) {
|
||||
// If stuck in a window and a key is pressed this should exit paused mode
|
||||
MyAvatar.isAway = false;
|
||||
}
|
||||
// Consume unmodified keyboard entries while the window is focused, to prevent them
|
||||
// from propagating to the application
|
||||
if (event.modifiers === Qt.NoModifier) {
|
||||
|
|
|
@ -869,10 +869,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
// connect to the packet sent signal of the _entityEditSender
|
||||
connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent);
|
||||
|
||||
// send the identity packet for our avatar each second to our avatar mixer
|
||||
connect(&identityPacketTimer, &QTimer::timeout, myAvatar.get(), &MyAvatar::sendIdentityPacket);
|
||||
identityPacketTimer.start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
||||
|
||||
const char** constArgv = const_cast<const char**>(argv);
|
||||
QString concurrentDownloadsStr = getCmdOption(argc, constArgv, "--concurrent-downloads");
|
||||
bool success;
|
||||
|
@ -3111,7 +3107,10 @@ void Application::mousePressEvent(QMouseEvent* event) {
|
|||
|
||||
if (!_aboutToQuit) {
|
||||
getOverlays().mousePressEvent(&mappedEvent);
|
||||
getEntities()->mousePressEvent(&mappedEvent);
|
||||
|
||||
if (!_controllerScriptingInterface->areEntityClicksCaptured()) {
|
||||
getEntities()->mousePressEvent(&mappedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
_controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts
|
||||
|
|
|
@ -228,17 +228,43 @@ void Avatar::updateAvatarEntities() {
|
|||
return;
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
QScriptEngine scriptEngine;
|
||||
entityTree->withWriteLock([&] {
|
||||
AvatarEntityMap avatarEntities = getAvatarEntityData();
|
||||
for (auto entityID : avatarEntities.keys()) {
|
||||
AvatarEntityMap::const_iterator dataItr = avatarEntities.begin();
|
||||
while (dataItr != avatarEntities.end()) {
|
||||
// compute hash of data. TODO? cache this?
|
||||
QByteArray data = dataItr.value();
|
||||
uint32_t newHash = qHash(data);
|
||||
|
||||
// check to see if we recognize this hash and whether it was already successfully processed
|
||||
QUuid entityID = dataItr.key();
|
||||
MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID);
|
||||
if (stateItr != _avatarEntityDataHashes.end()) {
|
||||
if (stateItr.value().success) {
|
||||
if (newHash == stateItr.value().hash) {
|
||||
// data hasn't changed --> nothing to do
|
||||
++dataItr;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// NOTE: if the data was unsuccessful in producing an entity in the past
|
||||
// we will try again just in case something changed (unlikely).
|
||||
// Unfortunately constantly trying to build the entity for this data costs
|
||||
// CPU cycles that we'd rather not spend.
|
||||
// TODO? put a maximum number of tries on this?
|
||||
}
|
||||
} else {
|
||||
// remember this hash for the future
|
||||
stateItr = _avatarEntityDataHashes.insert(entityID, AvatarEntityDataHash(newHash));
|
||||
}
|
||||
++dataItr;
|
||||
|
||||
// see EntityEditPacketSender::queueEditEntityMessage for the other end of this. unpack properties
|
||||
// and either add or update the entity.
|
||||
QByteArray jsonByteArray = avatarEntities.value(entityID);
|
||||
QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(jsonByteArray);
|
||||
QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(data);
|
||||
if (!jsonProperties.isObject()) {
|
||||
qCDebug(interfaceapp) << "got bad avatarEntity json" << QString(jsonByteArray.toHex());
|
||||
qCDebug(interfaceapp) << "got bad avatarEntity json" << QString(data.toHex());
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -266,8 +292,9 @@ void Avatar::updateAvatarEntities() {
|
|||
properties.setScript(noScript);
|
||||
}
|
||||
|
||||
// try to build the entity
|
||||
EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID));
|
||||
|
||||
bool success = true;
|
||||
if (entity) {
|
||||
if (entityTree->updateEntity(entityID, properties)) {
|
||||
entity->updateLastEditedFromRemote();
|
||||
|
@ -280,6 +307,7 @@ void Avatar::updateAvatarEntities() {
|
|||
success = false;
|
||||
}
|
||||
}
|
||||
stateItr.value().success = success;
|
||||
}
|
||||
|
||||
AvatarEntityIDs recentlyDettachedAvatarEntities = getAndClearRecentlyDetachedIDs();
|
||||
|
@ -292,12 +320,18 @@ void Avatar::updateAvatarEntities() {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
// remove stale data hashes
|
||||
foreach (auto entityID, recentlyDettachedAvatarEntities) {
|
||||
MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID);
|
||||
if (stateItr != _avatarEntityDataHashes.end()) {
|
||||
_avatarEntityDataHashes.erase(stateItr);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (success) {
|
||||
setAvatarEntityDataChanged(false);
|
||||
}
|
||||
setAvatarEntityDataChanged(false);
|
||||
}
|
||||
|
||||
bool Avatar::shouldDie() const {
|
||||
|
@ -364,6 +398,9 @@ void Avatar::simulate(float deltaTime, bool inView) {
|
|||
measureMotionDerivatives(deltaTime);
|
||||
simulateAttachments(deltaTime);
|
||||
updatePalms();
|
||||
}
|
||||
{
|
||||
PROFILE_RANGE(simulation, "entities");
|
||||
updateAvatarEntities();
|
||||
}
|
||||
}
|
||||
|
@ -1324,6 +1361,7 @@ void Avatar::setParentID(const QUuid& parentID) {
|
|||
if (!isMyAvatar()) {
|
||||
return;
|
||||
}
|
||||
QUuid initialParentID = getParentID();
|
||||
bool success;
|
||||
Transform beforeChangeTransform = getTransform(success);
|
||||
SpatiallyNestable::setParentID(parentID);
|
||||
|
@ -1332,6 +1370,9 @@ void Avatar::setParentID(const QUuid& parentID) {
|
|||
if (!success) {
|
||||
qCDebug(interfaceapp) << "Avatar::setParentID failed to reset avatar's location.";
|
||||
}
|
||||
if (initialParentID != parentID) {
|
||||
_parentChanged = usecTimestampNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -269,6 +269,16 @@ protected:
|
|||
|
||||
|
||||
private:
|
||||
class AvatarEntityDataHash {
|
||||
public:
|
||||
AvatarEntityDataHash(uint32_t h) : hash(h) {};
|
||||
uint32_t hash { 0 };
|
||||
bool success { false };
|
||||
};
|
||||
|
||||
using MapOfAvatarEntityDataHashes = QMap<QUuid, AvatarEntityDataHash>;
|
||||
MapOfAvatarEntityDataHashes _avatarEntityDataHashes;
|
||||
|
||||
uint64_t _lastRenderUpdateTime { 0 };
|
||||
int _leftPointerGeometryID { 0 };
|
||||
int _rightPointerGeometryID { 0 };
|
||||
|
|
|
@ -88,6 +88,7 @@ MyAvatar::MyAvatar(RigPointer rig) :
|
|||
_isPushing(false),
|
||||
_isBeingPushed(false),
|
||||
_isBraking(false),
|
||||
_isAway(false),
|
||||
_boomLength(ZOOM_DEFAULT),
|
||||
_yawSpeed(YAW_SPEED_DEFAULT),
|
||||
_pitchSpeed(PITCH_SPEED_DEFAULT),
|
||||
|
@ -376,7 +377,9 @@ void MyAvatar::update(float deltaTime) {
|
|||
Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)),
|
||||
Q_ARG(glm::vec3, (halfBoundingBoxDimensions*2.0f)));
|
||||
|
||||
if (_avatarEntityDataLocallyEdited) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
if (now > _identityPacketExpiry || _avatarEntityDataLocallyEdited) {
|
||||
_identityPacketExpiry = now + AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS;
|
||||
sendIdentityPacket();
|
||||
}
|
||||
|
||||
|
@ -1212,7 +1215,7 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
|
|||
setSkeletonModelURL(fullAvatarURL);
|
||||
UserActivityLogger::getInstance().changedModel("skeleton", urlString);
|
||||
}
|
||||
sendIdentityPacket();
|
||||
_identityPacketExpiry = 0; // triggers an identity packet next update()
|
||||
}
|
||||
|
||||
void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
||||
|
@ -2359,6 +2362,15 @@ bool MyAvatar::hasDriveInput() const {
|
|||
return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Y]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f;
|
||||
}
|
||||
|
||||
void MyAvatar::setAway(bool value) {
|
||||
_isAway = value;
|
||||
if (_isAway) {
|
||||
emit wentAway();
|
||||
} else {
|
||||
emit wentActive();
|
||||
}
|
||||
}
|
||||
|
||||
// The resulting matrix is used to render the hand controllers, even if the camera is decoupled from the avatar.
|
||||
// Specificly, if we are rendering using a third person camera. We would like to render the hand controllers in front of the camera,
|
||||
// not in front of the avatar.
|
||||
|
|
|
@ -82,6 +82,7 @@ class MyAvatar : public Avatar {
|
|||
Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose)
|
||||
|
||||
Q_PROPERTY(float energy READ getEnergy WRITE setEnergy)
|
||||
Q_PROPERTY(float isAway READ getIsAway WRITE setAway)
|
||||
|
||||
Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled)
|
||||
Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled)
|
||||
|
@ -328,6 +329,8 @@ signals:
|
|||
void energyChanged(float newEnergy);
|
||||
void positionGoneTo();
|
||||
void onLoadComplete();
|
||||
void wentAway();
|
||||
void wentActive();
|
||||
|
||||
private:
|
||||
|
||||
|
@ -385,6 +388,7 @@ private:
|
|||
bool _isPushing;
|
||||
bool _isBeingPushed;
|
||||
bool _isBraking;
|
||||
bool _isAway;
|
||||
|
||||
float _boomLength;
|
||||
float _yawSpeed; // degrees/sec
|
||||
|
@ -507,6 +511,8 @@ private:
|
|||
std::mutex _holdActionsMutex;
|
||||
std::vector<AvatarActionHold*> _holdActions;
|
||||
|
||||
uint64_t _identityPacketExpiry { 0 };
|
||||
|
||||
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
|
||||
float AUDIO_ENERGY_CONSTANT { 0.000001f };
|
||||
float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f };
|
||||
|
@ -519,6 +525,8 @@ private:
|
|||
float getEnergy();
|
||||
void setEnergy(float value);
|
||||
bool didTeleport();
|
||||
bool getIsAway() const { return _isAway; }
|
||||
void setAway(bool value);
|
||||
};
|
||||
|
||||
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
|
||||
|
|
|
@ -60,6 +60,18 @@ void ControllerScriptingInterface::releaseKeyEvents(const KeyEvent& event) {
|
|||
}
|
||||
}
|
||||
|
||||
bool ControllerScriptingInterface::areEntityClicksCaptured() const {
|
||||
return _captureEntityClicks;
|
||||
}
|
||||
|
||||
void ControllerScriptingInterface::captureEntityClickEvents() {
|
||||
_captureEntityClicks = true;
|
||||
}
|
||||
|
||||
void ControllerScriptingInterface::releaseEntityClickEvents() {
|
||||
_captureEntityClicks = false;
|
||||
}
|
||||
|
||||
bool ControllerScriptingInterface::isJoystickCaptured(int joystickIndex) const {
|
||||
return _capturedJoysticks.contains(joystickIndex);
|
||||
}
|
||||
|
|
|
@ -84,6 +84,7 @@ public:
|
|||
bool isKeyCaptured(QKeyEvent* event) const;
|
||||
bool isKeyCaptured(const KeyEvent& event) const;
|
||||
bool isJoystickCaptured(int joystickIndex) const;
|
||||
bool areEntityClicksCaptured() const;
|
||||
|
||||
void updateInputControllers();
|
||||
|
||||
|
@ -95,6 +96,9 @@ public slots:
|
|||
virtual void captureJoystick(int joystickIndex);
|
||||
virtual void releaseJoystick(int joystickIndex);
|
||||
|
||||
virtual void captureEntityClickEvents();
|
||||
virtual void releaseEntityClickEvents();
|
||||
|
||||
virtual glm::vec2 getViewportDimensions() const;
|
||||
virtual QVariant getRecommendedOverlayRect() const;
|
||||
|
||||
|
@ -128,6 +132,7 @@ private:
|
|||
|
||||
QMultiMap<int,KeyEvent> _capturedKeys;
|
||||
QSet<int> _capturedJoysticks;
|
||||
bool _captureEntityClicks;
|
||||
|
||||
using InputKey = controller::InputController::Key;
|
||||
using InputControllerMap = std::map<InputKey, controller::InputController::Pointer>;
|
||||
|
|
|
@ -38,7 +38,7 @@ Line3DOverlay::~Line3DOverlay() {
|
|||
|
||||
glm::vec3 Line3DOverlay::getStart() const {
|
||||
bool success;
|
||||
glm::vec3 worldStart = localToWorld(_start, _parentID, _parentJointIndex, success);
|
||||
glm::vec3 worldStart = localToWorld(_start, getParentID(), getParentJointIndex(), success);
|
||||
if (!success) {
|
||||
qDebug() << "Line3DOverlay::getStart failed";
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ glm::vec3 Line3DOverlay::getStart() const {
|
|||
|
||||
glm::vec3 Line3DOverlay::getEnd() const {
|
||||
bool success;
|
||||
glm::vec3 worldEnd = localToWorld(_end, _parentID, _parentJointIndex, success);
|
||||
glm::vec3 worldEnd = localToWorld(_end, getParentID(), getParentJointIndex(), success);
|
||||
if (!success) {
|
||||
qDebug() << "Line3DOverlay::getEnd failed";
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ glm::vec3 Line3DOverlay::getEnd() const {
|
|||
|
||||
void Line3DOverlay::setStart(const glm::vec3& start) {
|
||||
bool success;
|
||||
_start = worldToLocal(start, _parentID, _parentJointIndex, success);
|
||||
_start = worldToLocal(start, getParentID(), getParentJointIndex(), success);
|
||||
if (!success) {
|
||||
qDebug() << "Line3DOverlay::setStart failed";
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ void Line3DOverlay::setStart(const glm::vec3& start) {
|
|||
|
||||
void Line3DOverlay::setEnd(const glm::vec3& end) {
|
||||
bool success;
|
||||
_end = worldToLocal(end, _parentID, _parentJointIndex, success);
|
||||
_end = worldToLocal(end, getParentID(), getParentJointIndex(), success);
|
||||
if (!success) {
|
||||
qDebug() << "Line3DOverlay::setEnd failed";
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@
|
|||
#include <AddressManager.h>
|
||||
#include "scripting/AccountScriptingInterface.h"
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
#include <Preferences.h>
|
||||
#include "FileDialogHelper.h"
|
||||
|
||||
static const float DPI = 30.47f;
|
||||
static const float INCHES_TO_METERS = 1.0f / 39.3701f;
|
||||
|
@ -158,6 +160,7 @@ void Web3DOverlay::loadSourceURL() {
|
|||
_webSurface->getRootContext()->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
|
||||
_webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
|
||||
_webSurface->getRootContext()->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
|
||||
_webSurface->getRootContext()->setContextProperty("Preferences", DependencyManager::get<Preferences>().data());
|
||||
|
||||
if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
|
@ -166,6 +169,7 @@ void Web3DOverlay::loadSourceURL() {
|
|||
_webSurface->getRootContext()->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
|
||||
_webSurface->getRootContext()->setContextProperty("Account", AccountScriptingInterface::getInstance());
|
||||
_webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
|
||||
_webSurface->getRootContext()->setContextProperty("fileDialogHelper", new FileDialogHelper());
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data());
|
||||
|
||||
// Override min fps for tablet UI, for silky smooth scrolling
|
||||
|
|
|
@ -63,7 +63,6 @@ AvatarData::AvatarData() :
|
|||
_handState(0),
|
||||
_keyState(NO_KEY_DOWN),
|
||||
_forceFaceTrackerConnected(false),
|
||||
_hasNewJointData(true),
|
||||
_headData(NULL),
|
||||
_displayNameTargetAlpha(1.0f),
|
||||
_displayNameAlpha(1.0f),
|
||||
|
@ -258,8 +257,10 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
// local position, and parent info only apply to avatars that are parented. The local position
|
||||
// and the parent info can change independently though, so we track their "changed since"
|
||||
// separately
|
||||
bool hasParentInfo = hasParent() && (sendAll || parentInfoChangedSince(lastSentTime));
|
||||
bool hasAvatarLocalPosition = hasParent() && (sendAll || tranlationChangedSince(lastSentTime));
|
||||
bool hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime);
|
||||
bool hasAvatarLocalPosition = hasParent() && (sendAll ||
|
||||
tranlationChangedSince(lastSentTime) ||
|
||||
parentInfoChangedSince(lastSentTime));
|
||||
|
||||
bool hasFaceTrackerInfo = hasFaceTracker() && (sendAll || faceTrackerInfoChangedSince(lastSentTime));
|
||||
bool hasJointData = sendAll || !sendMinimum;
|
||||
|
@ -405,6 +406,18 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
_additionalFlagsRateOutbound.increment(numBytes);
|
||||
}
|
||||
|
||||
if (hasParentInfo) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto parentInfo = reinterpret_cast<AvatarDataPacket::ParentInfo*>(destinationBuffer);
|
||||
QByteArray referentialAsBytes = parentID.toRfc4122();
|
||||
memcpy(parentInfo->parentUUID, referentialAsBytes.data(), referentialAsBytes.size());
|
||||
parentInfo->parentJointIndex = getParentJointIndex();
|
||||
destinationBuffer += sizeof(AvatarDataPacket::ParentInfo);
|
||||
|
||||
int numBytes = destinationBuffer - startSection;
|
||||
_parentInfoRateOutbound.increment(numBytes);
|
||||
}
|
||||
|
||||
if (hasAvatarLocalPosition) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto data = reinterpret_cast<AvatarDataPacket::AvatarLocalPosition*>(destinationBuffer);
|
||||
|
@ -418,18 +431,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
_localPositionRateOutbound.increment(numBytes);
|
||||
}
|
||||
|
||||
if (hasParentInfo) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto parentInfo = reinterpret_cast<AvatarDataPacket::ParentInfo*>(destinationBuffer);
|
||||
QByteArray referentialAsBytes = parentID.toRfc4122();
|
||||
memcpy(parentInfo->parentUUID, referentialAsBytes.data(), referentialAsBytes.size());
|
||||
parentInfo->parentJointIndex = _parentJointIndex;
|
||||
destinationBuffer += sizeof(AvatarDataPacket::ParentInfo);
|
||||
|
||||
int numBytes = destinationBuffer - startSection;
|
||||
_parentInfoRateOutbound.increment(numBytes);
|
||||
}
|
||||
|
||||
// If it is connected, pack up the data
|
||||
if (hasFaceTrackerInfo) {
|
||||
auto startSection = destinationBuffer;
|
||||
|
@ -703,7 +704,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
bool hasFaceTrackerInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO);
|
||||
bool hasJointData = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DATA);
|
||||
|
||||
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
if (hasAvatarGlobalPosition) {
|
||||
|
@ -884,7 +884,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
|
||||
sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags);
|
||||
|
||||
if (somethingChanged) {
|
||||
if (somethingChanged) {
|
||||
_additionalFlagsChanged = usecTimestampNow();
|
||||
}
|
||||
int numBytesRead = sourceBuffer - startSection;
|
||||
|
@ -892,8 +892,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
_additionalFlagsUpdateRate.increment();
|
||||
}
|
||||
|
||||
// FIXME -- make sure to handle the existance of a parent vs a change in the parent...
|
||||
//bool hasReferential = oneAtBit(bitItems, HAS_REFERENTIAL);
|
||||
if (hasParentInfo) {
|
||||
auto startSection = sourceBuffer;
|
||||
PACKET_READ_CHECK(ParentInfo, sizeof(AvatarDataPacket::ParentInfo));
|
||||
|
@ -904,9 +902,9 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
|
||||
auto newParentID = QUuid::fromRfc4122(byteArray);
|
||||
|
||||
if ((_parentID != newParentID) || (_parentJointIndex = parentInfo->parentJointIndex)) {
|
||||
_parentID = newParentID;
|
||||
_parentJointIndex = parentInfo->parentJointIndex;
|
||||
if ((getParentID() != newParentID) || (getParentJointIndex() != parentInfo->parentJointIndex)) {
|
||||
SpatiallyNestable::setParentID(newParentID);
|
||||
SpatiallyNestable::setParentJointIndex(parentInfo->parentJointIndex);
|
||||
_parentChanged = usecTimestampNow();
|
||||
}
|
||||
|
||||
|
@ -914,13 +912,8 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
_parentInfoRate.increment(numBytesRead);
|
||||
_parentInfoUpdateRate.increment();
|
||||
}
|
||||
else {
|
||||
// FIXME - this aint totally right, for switching to parent/no-parent
|
||||
_parentID = QUuid();
|
||||
}
|
||||
|
||||
if (hasAvatarLocalPosition) {
|
||||
assert(hasParent()); // we shouldn't have local position unless we have a parent
|
||||
auto startSection = sourceBuffer;
|
||||
|
||||
PACKET_READ_CHECK(AvatarLocalPosition, sizeof(AvatarDataPacket::AvatarLocalPosition));
|
||||
|
@ -932,7 +925,11 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
}
|
||||
return buffer.size();
|
||||
}
|
||||
setLocalPosition(position);
|
||||
if (hasParent()) {
|
||||
setLocalPosition(position);
|
||||
} else {
|
||||
qCWarning(avatars) << "received localPosition for avatar with no parent";
|
||||
}
|
||||
sourceBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition);
|
||||
int numBytesRead = sourceBuffer - startSection;
|
||||
_localPositionRate.increment(numBytesRead);
|
||||
|
@ -2202,14 +2199,24 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) {
|
|||
setAttachmentData(newAttachments);
|
||||
}
|
||||
|
||||
const int MAX_NUM_AVATAR_ENTITIES = 42;
|
||||
|
||||
void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "updateAvatarEntity", Q_ARG(const QUuid&, entityID), Q_ARG(QByteArray, entityData));
|
||||
return;
|
||||
}
|
||||
_avatarEntitiesLock.withWriteLock([&] {
|
||||
_avatarEntityData.insert(entityID, entityData);
|
||||
_avatarEntityDataLocallyEdited = true;
|
||||
AvatarEntityMap::iterator itr = _avatarEntityData.find(entityID);
|
||||
if (itr == _avatarEntityData.end()) {
|
||||
if (_avatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) {
|
||||
_avatarEntityData.insert(entityID, entityData);
|
||||
_avatarEntityDataLocallyEdited = true;
|
||||
}
|
||||
} else {
|
||||
itr.value() = entityData;
|
||||
_avatarEntityDataLocallyEdited = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2240,6 +2247,11 @@ AvatarEntityMap AvatarData::getAvatarEntityData() const {
|
|||
}
|
||||
|
||||
void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
|
||||
if (avatarEntityData.size() > MAX_NUM_AVATAR_ENTITIES) {
|
||||
// the data is suspect
|
||||
qCDebug(avatars) << "discard suspect AvatarEntityData with size =" << avatarEntityData.size();
|
||||
return;
|
||||
}
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setAvatarEntityData", Q_ARG(const AvatarEntityMap&, avatarEntityData));
|
||||
return;
|
||||
|
|
|
@ -552,7 +552,7 @@ public:
|
|||
|
||||
int getJointCount() { return _jointData.size(); }
|
||||
|
||||
QVector<JointData> getLastSentJointData() {
|
||||
QVector<JointData> getLastSentJointData() {
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
_lastSentJointData.resize(_jointData.size());
|
||||
return _lastSentJointData;
|
||||
|
@ -614,7 +614,7 @@ protected:
|
|||
KeyState _keyState;
|
||||
|
||||
bool _forceFaceTrackerConnected;
|
||||
bool _hasNewJointData; // set in AvatarData, cleared in Avatar
|
||||
bool _hasNewJointData { true }; // set in AvatarData, cleared in Avatar
|
||||
|
||||
HeadData* _headData { nullptr };
|
||||
|
||||
|
|
|
@ -938,17 +938,19 @@ void EntityTreeRenderer::addEntityToScene(EntityItemPointer entity) {
|
|||
|
||||
|
||||
void EntityTreeRenderer::entityScriptChanging(const EntityItemID& entityID, const bool reload) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||
checkAndCallPreload(entityID, reload);
|
||||
}
|
||||
checkAndCallPreload(entityID, reload, true);
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) {
|
||||
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload, const bool unloadFirst) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
EntityItemPointer entity = getTree()->findEntityByEntityItemID(entityID);
|
||||
if (entity && entity->shouldPreloadScript() && _entitiesScriptEngine) {
|
||||
QString scriptUrl = entity->getScript();
|
||||
bool shouldLoad = entity && entity->shouldPreloadScript() && _entitiesScriptEngine;
|
||||
QString scriptUrl = entity->getScript();
|
||||
if ((unloadFirst && shouldLoad) || scriptUrl.isEmpty()) {
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||
entity->scriptHasUnloaded();
|
||||
}
|
||||
if (shouldLoad && !scriptUrl.isEmpty()) {
|
||||
scriptUrl = ResourceManager::normalizeURL(scriptUrl);
|
||||
ScriptEngine::loadEntityScript(_entitiesScriptEngine, entityID, scriptUrl, reload);
|
||||
entity->scriptHasPreloaded();
|
||||
|
|
|
@ -148,7 +148,7 @@ private:
|
|||
bool layerZoneAndHasSkybox(const std::shared_ptr<ZoneEntityItem>& zone);
|
||||
bool applySkyboxAndHasAmbient();
|
||||
|
||||
void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false);
|
||||
void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false, const bool unloadFirst = false);
|
||||
|
||||
QList<ModelPointer> _releasedModels;
|
||||
RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType,
|
||||
|
|
|
@ -38,19 +38,7 @@ void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type,
|
|||
EntityTreePointer entityTree,
|
||||
EntityItemID entityItemID,
|
||||
const EntityItemProperties& properties) {
|
||||
if (!_shouldSend) {
|
||||
return; // bail early
|
||||
}
|
||||
|
||||
if (properties.getOwningAvatarID() != _myAvatar->getID()) {
|
||||
return; // don't send updates for someone else's avatarEntity
|
||||
}
|
||||
|
||||
assert(properties.getClientOnly());
|
||||
|
||||
// this is an avatar-based entity. update our avatar-data rather than sending to the entity-server
|
||||
assert(_myAvatar);
|
||||
|
||||
if (!entityTree) {
|
||||
qCDebug(entities) << "EntityEditPacketSender::queueEditEntityMessage null entityTree.";
|
||||
return;
|
||||
|
@ -93,7 +81,8 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
|
|||
return; // bail early
|
||||
}
|
||||
|
||||
if (properties.getClientOnly()) {
|
||||
if (properties.getClientOnly() && properties.getOwningAvatarID() == _myAvatar->getID()) {
|
||||
// this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server
|
||||
queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -27,10 +27,6 @@ public:
|
|||
AvatarData* getMyAvatar() { return _myAvatar; }
|
||||
void clearAvatarEntity(QUuid entityID) { assert(_myAvatar); _myAvatar->clearAvatarEntity(entityID); }
|
||||
|
||||
void queueEditAvatarEntityMessage(PacketType type, EntityTreePointer entityTree,
|
||||
EntityItemID entityItemID, const EntityItemProperties& properties);
|
||||
|
||||
|
||||
/// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines
|
||||
/// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in
|
||||
/// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known.
|
||||
|
@ -48,6 +44,10 @@ public:
|
|||
public slots:
|
||||
void processEntityEditNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
|
||||
private:
|
||||
void queueEditAvatarEntityMessage(PacketType type, EntityTreePointer entityTree,
|
||||
EntityItemID entityItemID, const EntityItemProperties& properties);
|
||||
|
||||
private:
|
||||
AvatarData* _myAvatar { nullptr };
|
||||
QScriptEngine _scriptEngine;
|
||||
|
|
|
@ -1595,7 +1595,7 @@ void EntityItem::updatePosition(const glm::vec3& value) {
|
|||
}
|
||||
|
||||
void EntityItem::updateParentID(const QUuid& value) {
|
||||
if (_parentID != value) {
|
||||
if (getParentID() != value) {
|
||||
setParentID(value);
|
||||
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; // children are forced to be kinematic
|
||||
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
||||
|
|
|
@ -446,6 +446,7 @@ public:
|
|||
bool shouldPreloadScript() const { return !_script.isEmpty() &&
|
||||
((_loadedScript != _script) || (_loadedScriptTimestamp != _scriptTimestamp)); }
|
||||
void scriptHasPreloaded() { _loadedScript = _script; _loadedScriptTimestamp = _scriptTimestamp; }
|
||||
void scriptHasUnloaded() { _loadedScript = ""; _loadedScriptTimestamp = 0; }
|
||||
|
||||
bool getClientOnly() const { return _clientOnly; }
|
||||
void setClientOnly(bool clientOnly) { _clientOnly = clientOnly; }
|
||||
|
|
|
@ -684,22 +684,8 @@ bool EntityScriptingInterface::getServerScriptStatus(QUuid entityID, QScriptValu
|
|||
auto client = DependencyManager::get<EntityScriptClient>();
|
||||
auto request = client->createScriptStatusRequest(entityID);
|
||||
connect(request, &GetScriptStatusRequest::finished, callback.engine(), [callback](GetScriptStatusRequest* request) mutable {
|
||||
QString statusString;
|
||||
switch (request->getStatus()) {
|
||||
case RUNNING:
|
||||
statusString = "running";
|
||||
break;
|
||||
case ERROR_LOADING_SCRIPT:
|
||||
statusString = "error_loading_script";
|
||||
break;
|
||||
case ERROR_RUNNING_SCRIPT:
|
||||
statusString = "error_running_script";
|
||||
break;
|
||||
default:
|
||||
statusString = "";
|
||||
break;
|
||||
}
|
||||
QScriptValueList args { request->getResponseReceived(), request->getIsRunning(), statusString, request->getErrorInfo() };
|
||||
QString statusString = EntityScriptStatus_::valueToKey(request->getStatus());;
|
||||
QScriptValueList args { request->getResponseReceived(), request->getIsRunning(), statusString.toLower(), request->getErrorInfo() };
|
||||
callback.call(QScriptValue(), args);
|
||||
request->deleteLater();
|
||||
});
|
||||
|
|
|
@ -1119,7 +1119,9 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
endLogging = usecTimestampNow();
|
||||
|
||||
startUpdate = usecTimestampNow();
|
||||
properties.setLastEditedBy(senderNode->getUUID());
|
||||
if (!isPhysics) {
|
||||
properties.setLastEditedBy(senderNode->getUUID());
|
||||
}
|
||||
updateEntity(entityItemID, properties, senderNode);
|
||||
existingEntity->markAsChangedOnServer();
|
||||
endUpdate = usecTimestampNow();
|
||||
|
|
|
@ -399,7 +399,7 @@ void ImageReader::run() {
|
|||
int originalHeight = imageHeight;
|
||||
imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f);
|
||||
imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f);
|
||||
QImage newImage = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio);
|
||||
QImage newImage = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
image.swap(newImage);
|
||||
qCDebug(modelnetworking) << "Downscale image" << _url
|
||||
<< "from" << originalWidth << "x" << originalHeight
|
||||
|
|
|
@ -74,7 +74,7 @@ QImage processSourceImage(const QImage& srcImage, bool cubemap) {
|
|||
if (targetSize != srcImageSize) {
|
||||
PROFILE_RANGE(resource_parse, "processSourceImage Rectify");
|
||||
qCDebug(modelLog) << "Resizing texture from " << srcImageSize.x << "x" << srcImageSize.y << " to " << targetSize.x << "x" << targetSize.y;
|
||||
return srcImage.scaled(fromGlm(targetSize));
|
||||
return srcImage.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
return srcImage;
|
||||
|
@ -202,14 +202,19 @@ const QImage& image, bool isLinear, bool doCompress) {
|
|||
|
||||
#define CPU_MIPMAPS 1
|
||||
|
||||
void generateMips(gpu::Texture* texture, QImage& image, gpu::Element formatMip) {
|
||||
void generateMips(gpu::Texture* texture, QImage& image, gpu::Element formatMip, bool fastResize) {
|
||||
#if CPU_MIPMAPS
|
||||
PROFILE_RANGE(resource_parse, "generateMips");
|
||||
auto numMips = texture->evalNumMips();
|
||||
for (uint16 level = 1; level < numMips; ++level) {
|
||||
QSize mipSize(texture->evalMipWidth(level), texture->evalMipHeight(level));
|
||||
image = image.scaled(mipSize);
|
||||
texture->assignStoredMip(level, formatMip, image.byteCount(), image.constBits());
|
||||
if (fastResize) {
|
||||
image = image.scaled(mipSize);
|
||||
texture->assignStoredMip(level, formatMip, image.byteCount(), image.constBits());
|
||||
} else {
|
||||
QImage mipImage = image.scaled(mipSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
texture->assignStoredMip(level, formatMip, mipImage.byteCount(), mipImage.constBits());
|
||||
}
|
||||
}
|
||||
#else
|
||||
texture->autoGenerateMips(-1);
|
||||
|
@ -222,8 +227,8 @@ void generateFaceMips(gpu::Texture* texture, QImage& image, gpu::Element formatM
|
|||
auto numMips = texture->evalNumMips();
|
||||
for (uint16 level = 1; level < numMips; ++level) {
|
||||
QSize mipSize(texture->evalMipWidth(level), texture->evalMipHeight(level));
|
||||
image = image.scaled(mipSize);
|
||||
texture->assignStoredMipFace(level, formatMip, image.byteCount(), image.constBits(), face);
|
||||
QImage mipImage = image.scaled(mipSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
texture->assignStoredMipFace(level, formatMip, mipImage.byteCount(), mipImage.constBits(), face);
|
||||
}
|
||||
#else
|
||||
texture->autoGenerateMips(-1);
|
||||
|
@ -257,7 +262,7 @@ gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImag
|
|||
theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits());
|
||||
|
||||
if (generateMips) {
|
||||
::generateMips(theTexture, image, formatMip);
|
||||
::generateMips(theTexture, image, formatMip, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -300,7 +305,7 @@ gpu::Texture* TextureUsage::createNormalTextureFromNormalImage(const QImage& src
|
|||
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
|
||||
theTexture->setSource(srcImageName);
|
||||
theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits());
|
||||
generateMips(theTexture, image, formatMip);
|
||||
generateMips(theTexture, image, formatMip, true);
|
||||
}
|
||||
|
||||
return theTexture;
|
||||
|
@ -386,7 +391,7 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm
|
|||
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
|
||||
theTexture->setSource(srcImageName);
|
||||
theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits());
|
||||
generateMips(theTexture, image, formatMip);
|
||||
generateMips(theTexture, image, formatMip, true);
|
||||
}
|
||||
|
||||
return theTexture;
|
||||
|
@ -419,7 +424,7 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromImage(const QImage& srcIma
|
|||
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
|
||||
theTexture->setSource(srcImageName);
|
||||
theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits());
|
||||
generateMips(theTexture, image, formatMip);
|
||||
generateMips(theTexture, image, formatMip, true);
|
||||
|
||||
// FIXME queue for transfer to GPU and block on completion
|
||||
}
|
||||
|
@ -458,7 +463,7 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromGlossImage(const QImage& s
|
|||
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
|
||||
theTexture->setSource(srcImageName);
|
||||
theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits());
|
||||
generateMips(theTexture, image, formatMip);
|
||||
generateMips(theTexture, image, formatMip, true);
|
||||
|
||||
// FIXME queue for transfer to GPU and block on completion
|
||||
}
|
||||
|
@ -494,7 +499,7 @@ gpu::Texture* TextureUsage::createMetallicTextureFromImage(const QImage& srcImag
|
|||
theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
|
||||
theTexture->setSource(srcImageName);
|
||||
theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits());
|
||||
generateMips(theTexture, image, formatMip);
|
||||
generateMips(theTexture, image, formatMip, true);
|
||||
|
||||
// FIXME queue for transfer to GPU and block on completion
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ MessageID EntityScriptClient::getEntityServerScriptStatus(QUuid entityID, GetScr
|
|||
}
|
||||
}
|
||||
|
||||
callback(false, false, ERROR_LOADING_SCRIPT, "");
|
||||
callback(false, false, EntityScriptStatus::ERROR_LOADING_SCRIPT, "");
|
||||
return INVALID_MESSAGE_ID;
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ void EntityScriptClient::handleGetScriptStatusReply(QSharedPointer<ReceivedMessa
|
|||
|
||||
MessageID messageID;
|
||||
bool isKnown { false };
|
||||
EntityScriptStatus status = ERROR_LOADING_SCRIPT;
|
||||
EntityScriptStatus status = EntityScriptStatus::ERROR_LOADING_SCRIPT;
|
||||
QString errorInfo { "" };
|
||||
|
||||
message->readPrimitive(&messageID);
|
||||
|
@ -157,7 +157,7 @@ void EntityScriptClient::forceFailureOfPendingRequests(SharedNodePointer node) {
|
|||
auto messageMapIt = _pendingEntityScriptStatusRequests.find(node);
|
||||
if (messageMapIt != _pendingEntityScriptStatusRequests.end()) {
|
||||
for (const auto& value : messageMapIt->second) {
|
||||
value.second(false, false, ERROR_LOADING_SCRIPT, "");
|
||||
value.second(false, false, EntityScriptStatus::ERROR_LOADING_SCRIPT, "");
|
||||
}
|
||||
messageMapIt->second.clear();
|
||||
}
|
||||
|
|
|
@ -11,11 +11,23 @@
|
|||
|
||||
#ifndef hifi_EntityScriptUtils_h
|
||||
#define hifi_EntityScriptUtils_h
|
||||
#include <QMetaEnum>
|
||||
|
||||
enum EntityScriptStatus {
|
||||
ERROR_LOADING_SCRIPT,
|
||||
ERROR_RUNNING_SCRIPT,
|
||||
RUNNING
|
||||
class EntityScriptStatus_ : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum EntityScriptStatus {
|
||||
PENDING,
|
||||
LOADING,
|
||||
ERROR_LOADING_SCRIPT,
|
||||
ERROR_RUNNING_SCRIPT,
|
||||
RUNNING,
|
||||
UNLOADED
|
||||
};
|
||||
Q_ENUM(EntityScriptStatus)
|
||||
static QString valueToKey(EntityScriptStatus status) {
|
||||
return QMetaEnum::fromType<EntityScriptStatus>().valueToKey(status);
|
||||
}
|
||||
};
|
||||
|
||||
using EntityScriptStatus = EntityScriptStatus_::EntityScriptStatus;
|
||||
#endif // hifi_EntityScriptUtils_h
|
|
@ -36,16 +36,23 @@ void MessagesClient::init() {
|
|||
}
|
||||
}
|
||||
|
||||
void MessagesClient::decodeMessagesPacket(QSharedPointer<ReceivedMessage> receivedMessage, QString& channel, QString& message, QUuid& senderID) {
|
||||
void MessagesClient::decodeMessagesPacket(QSharedPointer<ReceivedMessage> receivedMessage, QString& channel,
|
||||
bool& isText, QString& message, QByteArray& data, QUuid& senderID) {
|
||||
quint16 channelLength;
|
||||
receivedMessage->readPrimitive(&channelLength);
|
||||
auto channelData = receivedMessage->read(channelLength);
|
||||
channel = QString::fromUtf8(channelData);
|
||||
|
||||
quint16 messageLength;
|
||||
receivedMessage->readPrimitive(&isText);
|
||||
|
||||
quint32 messageLength;
|
||||
receivedMessage->readPrimitive(&messageLength);
|
||||
auto messageData = receivedMessage->read(messageLength);
|
||||
message = QString::fromUtf8(messageData);
|
||||
if (isText) {
|
||||
message = QString::fromUtf8(messageData);
|
||||
} else {
|
||||
data = messageData;
|
||||
}
|
||||
|
||||
QByteArray bytesSenderID = receivedMessage->read(NUM_BYTES_RFC4122_UUID);
|
||||
if (bytesSenderID.length() == NUM_BYTES_RFC4122_UUID) {
|
||||
|
@ -64,8 +71,11 @@ std::unique_ptr<NLPacketList> MessagesClient::encodeMessagesPacket(QString chann
|
|||
packetList->writePrimitive(channelLength);
|
||||
packetList->write(channelUtf8);
|
||||
|
||||
bool isTextMessage = true;
|
||||
packetList->writePrimitive(isTextMessage);
|
||||
|
||||
auto messageUtf8 = message.toUtf8();
|
||||
quint16 messageLength = messageUtf8.length();
|
||||
quint32 messageLength = messageUtf8.length();
|
||||
packetList->writePrimitive(messageLength);
|
||||
packetList->write(messageUtf8);
|
||||
|
||||
|
@ -74,12 +84,38 @@ std::unique_ptr<NLPacketList> MessagesClient::encodeMessagesPacket(QString chann
|
|||
return packetList;
|
||||
}
|
||||
|
||||
std::unique_ptr<NLPacketList> MessagesClient::encodeMessagesDataPacket(QString channel, QByteArray data, QUuid senderID) {
|
||||
auto packetList = NLPacketList::create(PacketType::MessagesData, QByteArray(), true, true);
|
||||
|
||||
auto channelUtf8 = channel.toUtf8();
|
||||
quint16 channelLength = channelUtf8.length();
|
||||
packetList->writePrimitive(channelLength);
|
||||
packetList->write(channelUtf8);
|
||||
|
||||
bool isTextMessage = false;
|
||||
packetList->writePrimitive(isTextMessage);
|
||||
|
||||
quint32 dataLength = data.length();
|
||||
packetList->writePrimitive(dataLength);
|
||||
packetList->write(data);
|
||||
|
||||
packetList->write(senderID.toRfc4122());
|
||||
|
||||
return packetList;
|
||||
}
|
||||
|
||||
|
||||
void MessagesClient::handleMessagesPacket(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode) {
|
||||
QString channel, message;
|
||||
QByteArray data;
|
||||
bool isText { false };
|
||||
QUuid senderID;
|
||||
decodeMessagesPacket(receivedMessage, channel, message, senderID);
|
||||
emit messageReceived(channel, message, senderID, false);
|
||||
decodeMessagesPacket(receivedMessage, channel, isText, message, data, senderID);
|
||||
if (isText) {
|
||||
emit messageReceived(channel, message, senderID, false);
|
||||
} else {
|
||||
emit dataReceived(channel, data, senderID, false);
|
||||
}
|
||||
}
|
||||
|
||||
void MessagesClient::sendMessage(QString channel, QString message, bool localOnly) {
|
||||
|
@ -98,6 +134,22 @@ void MessagesClient::sendMessage(QString channel, QString message, bool localOnl
|
|||
}
|
||||
}
|
||||
|
||||
void MessagesClient::sendData(QString channel, QByteArray data, bool localOnly) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
if (localOnly) {
|
||||
QUuid senderID = nodeList->getSessionUUID();
|
||||
emit dataReceived(channel, data, senderID, true);
|
||||
} else {
|
||||
SharedNodePointer messagesMixer = nodeList->soloNodeOfType(NodeType::MessagesMixer);
|
||||
|
||||
if (messagesMixer) {
|
||||
QUuid senderID = nodeList->getSessionUUID();
|
||||
auto packetList = encodeMessagesDataPacket(channel, data, senderID);
|
||||
nodeList->sendPacketList(std::move(packetList), *messagesMixer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessagesClient::sendLocalMessage(QString channel, QString message) {
|
||||
sendMessage(channel, message, true);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#define hifi_MessagesClient_h
|
||||
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
|
@ -31,15 +32,19 @@ public:
|
|||
|
||||
Q_INVOKABLE void sendMessage(QString channel, QString message, bool localOnly = false);
|
||||
Q_INVOKABLE void sendLocalMessage(QString channel, QString message);
|
||||
Q_INVOKABLE void sendData(QString channel, QByteArray data, bool localOnly = false);
|
||||
Q_INVOKABLE void subscribe(QString channel);
|
||||
Q_INVOKABLE void unsubscribe(QString channel);
|
||||
|
||||
static void decodeMessagesPacket(QSharedPointer<ReceivedMessage> receivedMessage, QString& channel, QString& message, QUuid& senderID);
|
||||
static std::unique_ptr<NLPacketList> encodeMessagesPacket(QString channel, QString message, QUuid senderID);
|
||||
static void decodeMessagesPacket(QSharedPointer<ReceivedMessage> receivedMessage, QString& channel,
|
||||
bool& isText, QString& message, QByteArray& data, QUuid& senderID);
|
||||
|
||||
static std::unique_ptr<NLPacketList> encodeMessagesPacket(QString channel, QString message, QUuid senderID);
|
||||
static std::unique_ptr<NLPacketList> encodeMessagesDataPacket(QString channel, QByteArray data, QUuid senderID);
|
||||
|
||||
signals:
|
||||
void messageReceived(QString channel, QString message, QUuid senderUUID, bool localOnly);
|
||||
void dataReceived(QString channel, QByteArray data, QUuid senderUUID, bool localOnly);
|
||||
|
||||
private slots:
|
||||
void handleMessagesPacket(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode);
|
||||
|
|
|
@ -38,6 +38,7 @@ public:
|
|||
InvalidURL,
|
||||
NotFound
|
||||
};
|
||||
Q_ENUM(Result)
|
||||
|
||||
QByteArray getData() { return _data; }
|
||||
State getState() const { return _state; }
|
||||
|
|
|
@ -56,7 +56,9 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::AvatarData:
|
||||
case PacketType::BulkAvatarData:
|
||||
case PacketType::KillAvatar:
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::VariableAvatarData);
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::AvatarAsChildFixes);
|
||||
case PacketType::MessagesData:
|
||||
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
|
||||
case PacketType::ICEServerHeartbeat:
|
||||
return 18; // ICE Server Heartbeat signing
|
||||
case PacketType::AssetGetInfo:
|
||||
|
|
|
@ -224,7 +224,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
|
|||
SessionDisplayName,
|
||||
Unignore,
|
||||
ImmediateSessionDisplayNameUpdates,
|
||||
VariableAvatarData
|
||||
VariableAvatarData,
|
||||
AvatarAsChildFixes
|
||||
};
|
||||
|
||||
enum class DomainConnectRequestVersion : PacketVersion {
|
||||
|
@ -263,4 +264,8 @@ enum class AudioVersion : PacketVersion {
|
|||
HighDynamicRangeVolume,
|
||||
};
|
||||
|
||||
enum class MessageDataVersion : PacketVersion {
|
||||
TextOrBinaryData = 18
|
||||
};
|
||||
|
||||
#endif // hifi_PacketHeaders_h
|
||||
|
|
|
@ -63,7 +63,8 @@ float fetchRoughnessMap(vec2 uv) {
|
|||
<@if withNormal@>
|
||||
uniform sampler2D normalMap;
|
||||
vec3 fetchNormalMap(vec2 uv) {
|
||||
return texture(normalMap, uv).xyz;
|
||||
// unpack normal, swizzle to get into hifi tangent space with Y axis pointing out
|
||||
return normalize(texture(normalMap, uv).xzy -vec3(0.5, 0.5, 0.5));
|
||||
}
|
||||
<@endif@>
|
||||
|
||||
|
@ -148,11 +149,23 @@ vec3 fetchLightmapMap(vec2 uv) {
|
|||
vec3 normalizedNormal = normalize(<$interpolatedNormal$>.xyz);
|
||||
vec3 normalizedTangent = normalize(<$interpolatedTangent$>.xyz);
|
||||
vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent));
|
||||
vec3 localNormal = normalize(<$fetchedNormal$> - vec3(0.5, 0.5, 0.5));
|
||||
vec3 localNormal = <$fetchedNormal$>;
|
||||
<$normal$> = vec3(normalizedTangent * localNormal.x + normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z);
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func tangentToViewSpaceLOD(fragPos, fetchedNormal, interpolatedNormal, interpolatedTangent, normal)@>
|
||||
{
|
||||
vec3 normalizedNormal = normalize(<$interpolatedNormal$>.xyz);
|
||||
vec3 normalizedTangent = normalize(<$interpolatedTangent$>.xyz);
|
||||
vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent));
|
||||
// attenuate the normal map divergence from the mesh normal based on distance
|
||||
// THe attenuation range [20,100] meters from the eye is arbitrary for now
|
||||
vec3 localNormal = mix(<$fetchedNormal$>, vec3(0.0, 1.0, 0.0), smoothstep(20, 100, (-<$fragPos$>).z));
|
||||
<$normal$> = vec3(normalizedTangent * localNormal.x + normalizedNormal * localNormal.y + normalizedBitangent * localNormal.z);
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func evalMaterialAlbedo(fetchedAlbedo, materialAlbedo, matKey, albedo)@>
|
||||
{
|
||||
<$albedo$>.xyz = (((<$matKey$> & ALBEDO_VAL_BIT) != 0) ? <$materialAlbedo$> : vec3(1.0));
|
||||
|
|
|
@ -47,7 +47,7 @@ void main(void) {
|
|||
<$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>;
|
||||
|
||||
vec3 viewNormal;
|
||||
<$tangentToViewSpace(normalTex, _normal, _tangent, viewNormal)$>
|
||||
<$tangentToViewSpaceLOD(_position, normalTex, _normal, _tangent, viewNormal)$>
|
||||
|
||||
float scattering = getMaterialScattering(mat);
|
||||
<$evalMaterialScattering(scatteringTex, scattering, matKey, scattering)$>;
|
||||
|
|
|
@ -47,7 +47,7 @@ void main(void) {
|
|||
<$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>;
|
||||
|
||||
vec3 viewNormal;
|
||||
<$tangentToViewSpace(normalTex, _normal, _tangent, viewNormal)$>
|
||||
<$tangentToViewSpaceLOD(_position, normalTex, _normal, _tangent, viewNormal)$>
|
||||
|
||||
float metallic = getMaterialMetallic(mat);
|
||||
<$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>;
|
||||
|
|
|
@ -34,7 +34,7 @@ void main(void) {
|
|||
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$>
|
||||
|
||||
vec3 viewNormal;
|
||||
<$tangentToViewSpace(normalTexel, _normal, _tangent, viewNormal)$>
|
||||
<$tangentToViewSpaceLOD(_position, normalTexel, _normal, _tangent, viewNormal)$>
|
||||
|
||||
packDeferredFragmentLightmap(
|
||||
normalize(viewNormal.xyz),
|
||||
|
|
|
@ -34,7 +34,7 @@ void main(void) {
|
|||
<$fetchMaterialTexturesCoord1(matKey, _texCoord1, _SCRIBE_NULL, lightmapVal)$>
|
||||
|
||||
vec3 viewNormal;
|
||||
<$tangentToViewSpace(normalTexel, _normal, _tangent, viewNormal)$>
|
||||
<$tangentToViewSpaceLOD(_position, normalTexel, _normal, _tangent, viewNormal)$>
|
||||
|
||||
packDeferredFragmentLightmap(
|
||||
normalize(viewNormal.xyz),
|
||||
|
|
|
@ -47,7 +47,7 @@ void main(void) {
|
|||
<$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>;
|
||||
|
||||
vec3 viewNormal;
|
||||
<$tangentToViewSpace(normalTex, _normal, _tangent, viewNormal)$>
|
||||
<$tangentToViewSpaceLOD(_position, normalTex, _normal, _tangent, viewNormal)$>
|
||||
|
||||
float scattering = getMaterialScattering(mat);
|
||||
<$evalMaterialScattering(scatteringTex, scattering, matKey, scattering)$>;
|
||||
|
|
|
@ -47,7 +47,7 @@ void main(void) {
|
|||
<$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>;
|
||||
|
||||
vec3 viewNormal;
|
||||
<$tangentToViewSpace(normalTex, _normal, _tangent, viewNormal)$>
|
||||
<$tangentToViewSpaceLOD(_position, normalTex, _normal, _tangent, viewNormal)$>
|
||||
|
||||
float metallic = getMaterialMetallic(mat);
|
||||
<$evalMaterialMetallic(metallicTex, metallic, matKey, metallic)$>;
|
||||
|
|
|
@ -27,11 +27,12 @@ BatchLoader::BatchLoader(const QList<QUrl>& urls)
|
|||
_started(false),
|
||||
_finished(false),
|
||||
_urls(urls.toSet()),
|
||||
_data() {
|
||||
_data(),
|
||||
_status() {
|
||||
qRegisterMetaType<QMap<QUrl, QString>>("QMap<QUrl, QString>");
|
||||
}
|
||||
|
||||
void BatchLoader::start() {
|
||||
void BatchLoader::start(int maxRetries) {
|
||||
if (_started) {
|
||||
return;
|
||||
}
|
||||
|
@ -40,7 +41,7 @@ void BatchLoader::start() {
|
|||
|
||||
if (_urls.size() == 0) {
|
||||
_finished = true;
|
||||
emit finished(_data);
|
||||
emit finished(_data, _status);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -58,7 +59,8 @@ void BatchLoader::start() {
|
|||
ScriptCacheSignalProxy* proxy = new ScriptCacheSignalProxy();
|
||||
connect(scriptCache.data(), &ScriptCache::destroyed, proxy, &ScriptCacheSignalProxy::deleteLater);
|
||||
|
||||
connect(proxy, &ScriptCacheSignalProxy::contentAvailable, this, [this](const QString& url, const QString& contents, bool isURL, bool success) {
|
||||
connect(proxy, &ScriptCacheSignalProxy::contentAvailable, this, [this](const QString& url, const QString& contents, bool isURL, bool success, const QString& status) {
|
||||
_status.insert(url, status);
|
||||
if (isURL && success) {
|
||||
_data.insert(url, contents);
|
||||
qCDebug(scriptengine) << "Loaded: " << url;
|
||||
|
@ -69,17 +71,17 @@ void BatchLoader::start() {
|
|||
|
||||
if (!_finished && _urls.size() == _data.size()) {
|
||||
_finished = true;
|
||||
emit finished(_data);
|
||||
emit finished(_data, _status);
|
||||
}
|
||||
});
|
||||
|
||||
scriptCache->getScriptContents(url.toString(), [proxy](const QString& url, const QString& contents, bool isURL, bool success) {
|
||||
proxy->receivedContent(url, contents, isURL, success);
|
||||
scriptCache->getScriptContents(url.toString(), [proxy](const QString& url, const QString& contents, bool isURL, bool success, const QString& status) {
|
||||
proxy->receivedContent(url, contents, isURL, success, status);
|
||||
proxy->deleteLater();
|
||||
}, false);
|
||||
}, false, maxRetries);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptCacheSignalProxy::receivedContent(const QString& url, const QString& contents, bool isURL, bool success) {
|
||||
emit contentAvailable(url, contents, isURL, success);
|
||||
void ScriptCacheSignalProxy::receivedContent(const QString& url, const QString& contents, bool isURL, bool success, const QString& status) {
|
||||
emit contentAvailable(url, contents, isURL, success, status);
|
||||
}
|
||||
|
|
|
@ -19,15 +19,17 @@
|
|||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include "ScriptCache.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
class ScriptCacheSignalProxy : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
void receivedContent(const QString& url, const QString& contents, bool isURL, bool success);
|
||||
void receivedContent(const QString& url, const QString& contents, bool isURL, bool success, const QString& status);
|
||||
|
||||
signals:
|
||||
void contentAvailable(const QString& url, const QString& contents, bool isURL, bool success);
|
||||
void contentAvailable(const QString& url, const QString& contents, bool isURL, bool success, const QString& status);
|
||||
};
|
||||
|
||||
class BatchLoader : public QObject {
|
||||
|
@ -35,11 +37,11 @@ class BatchLoader : public QObject {
|
|||
public:
|
||||
BatchLoader(const QList<QUrl>& urls);
|
||||
|
||||
void start();
|
||||
void start(int maxRetries = ScriptRequest::MAX_RETRIES);
|
||||
bool isFinished() const { return _finished; };
|
||||
|
||||
signals:
|
||||
void finished(const QMap<QUrl, QString>& data);
|
||||
void finished(const QMap<QUrl, QString>& data, const QMap<QUrl, QString>& status);
|
||||
|
||||
private:
|
||||
void checkFinished();
|
||||
|
@ -48,6 +50,7 @@ private:
|
|||
bool _finished;
|
||||
QSet<QUrl> _urls;
|
||||
QMap<QUrl, QString> _data;
|
||||
QMap<QUrl, QString> _status;
|
||||
};
|
||||
|
||||
#endif // hifi_BatchLoader_h
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QRegularExpression>
|
||||
#include <QMetaEnum>
|
||||
|
||||
#include <assert.h>
|
||||
#include <SharedUtil.h>
|
||||
|
@ -27,12 +28,18 @@
|
|||
#include "ScriptEngineLogging.h"
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
const QString ScriptCache::STATUS_INLINE { "Inline" };
|
||||
const QString ScriptCache::STATUS_CACHED { "Cached" };
|
||||
|
||||
ScriptCache::ScriptCache(QObject* parent) {
|
||||
// nothing to do here...
|
||||
}
|
||||
|
||||
void ScriptCache::clearCache() {
|
||||
Lock lock(_containerLock);
|
||||
foreach(auto& url, _scriptCache.keys()) {
|
||||
qCDebug(scriptengine) << "clearing cache: " << url;
|
||||
}
|
||||
_scriptCache.clear();
|
||||
}
|
||||
|
||||
|
@ -49,35 +56,6 @@ void ScriptCache::clearATPScriptsFromCache() {
|
|||
}
|
||||
}
|
||||
|
||||
QString ScriptCache::getScript(const QUrl& unnormalizedURL, ScriptUser* scriptUser, bool& isPending, bool reload) {
|
||||
QUrl url = ResourceManager::normalizeURL(unnormalizedURL);
|
||||
QString scriptContents;
|
||||
|
||||
Lock lock(_containerLock);
|
||||
if (_scriptCache.contains(url) && !reload) {
|
||||
qCDebug(scriptengine) << "Found script in cache:" << url.toString();
|
||||
scriptContents = _scriptCache[url];
|
||||
lock.unlock();
|
||||
scriptUser->scriptContentsAvailable(url, scriptContents);
|
||||
isPending = false;
|
||||
} else {
|
||||
isPending = true;
|
||||
bool alreadyWaiting = _scriptUsers.contains(url);
|
||||
_scriptUsers.insert(url, scriptUser);
|
||||
lock.unlock();
|
||||
|
||||
if (alreadyWaiting) {
|
||||
qCDebug(scriptengine) << "Already downloading script at:" << url.toString();
|
||||
} else {
|
||||
auto request = ResourceManager::createResourceRequest(nullptr, url);
|
||||
request->setCacheEnabled(!reload);
|
||||
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptDownloaded);
|
||||
request->send();
|
||||
}
|
||||
}
|
||||
return scriptContents;
|
||||
}
|
||||
|
||||
void ScriptCache::deleteScript(const QUrl& unnormalizedURL) {
|
||||
QUrl url = ResourceManager::normalizeURL(unnormalizedURL);
|
||||
Lock lock(_containerLock);
|
||||
|
@ -87,37 +65,7 @@ void ScriptCache::deleteScript(const QUrl& unnormalizedURL) {
|
|||
}
|
||||
}
|
||||
|
||||
void ScriptCache::scriptDownloaded() {
|
||||
ResourceRequest* req = qobject_cast<ResourceRequest*>(sender());
|
||||
QUrl url = req->getUrl();
|
||||
|
||||
Lock lock(_containerLock);
|
||||
QList<ScriptUser*> scriptUsers = _scriptUsers.values(url);
|
||||
_scriptUsers.remove(url);
|
||||
|
||||
if (!DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||
if (req->getResult() == ResourceRequest::Success) {
|
||||
auto scriptContents = req->getData();
|
||||
_scriptCache[url] = scriptContents;
|
||||
lock.unlock();
|
||||
qCDebug(scriptengine) << "Done downloading script at:" << url.toString();
|
||||
|
||||
foreach(ScriptUser* user, scriptUsers) {
|
||||
user->scriptContentsAvailable(url, scriptContents);
|
||||
}
|
||||
} else {
|
||||
lock.unlock();
|
||||
qCWarning(scriptengine) << "Error loading script from URL " << url;
|
||||
foreach(ScriptUser* user, scriptUsers) {
|
||||
user->errorInLoadingScript(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
req->deleteLater();
|
||||
}
|
||||
|
||||
void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailableCallback contentAvailable, bool forceDownload) {
|
||||
void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailableCallback contentAvailable, bool forceDownload, int maxRetries) {
|
||||
#ifdef THREAD_DEBUGGING
|
||||
qCDebug(scriptengine) << "ScriptCache::getScriptContents() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||
#endif
|
||||
|
@ -128,7 +76,7 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
|
|||
// entityScript use case)
|
||||
if (unnormalizedURL.scheme().isEmpty() &&
|
||||
scriptOrURL.simplified().replace(" ", "").contains(QRegularExpression(R"(\(function\([a-z]?[\w,]*\){)"))) {
|
||||
contentAvailable(scriptOrURL, scriptOrURL, false, true);
|
||||
contentAvailable(scriptOrURL, scriptOrURL, false, true, STATUS_INLINE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -136,7 +84,7 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
|
|||
if (unnormalizedURL.scheme() == "javascript") {
|
||||
QString contents { scriptOrURL };
|
||||
contents.replace(QRegularExpression("^javascript:"), "");
|
||||
contentAvailable(scriptOrURL, contents, false, true);
|
||||
contentAvailable(scriptOrURL, contents, false, true, STATUS_INLINE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -145,34 +93,32 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
|
|||
auto scriptContent = _scriptCache[url];
|
||||
lock.unlock();
|
||||
qCDebug(scriptengine) << "Found script in cache:" << url.toString();
|
||||
contentAvailable(url.toString(), scriptContent, true, true);
|
||||
contentAvailable(url.toString(), scriptContent, true, true, STATUS_CACHED);
|
||||
} else {
|
||||
auto& scriptRequest = _activeScriptRequests[url];
|
||||
|
||||
bool alreadyWaiting = scriptRequest.scriptUsers.size() > 0;
|
||||
scriptRequest.scriptUsers.push_back(contentAvailable);
|
||||
|
||||
lock.unlock();
|
||||
|
||||
if (alreadyWaiting) {
|
||||
qCDebug(scriptengine) << "Already downloading script at:" << url.toString();
|
||||
qCDebug(scriptengine) << QString("Already downloading script at: %1 (retry: %2; scriptusers: %3)")
|
||||
.arg(url.toString()).arg(scriptRequest.numRetries).arg(scriptRequest.scriptUsers.size());
|
||||
} else {
|
||||
scriptRequest.maxRetries = maxRetries;
|
||||
#ifdef THREAD_DEBUGGING
|
||||
qCDebug(scriptengine) << "about to call: ResourceManager::createResourceRequest(this, url); on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||
#endif
|
||||
auto request = ResourceManager::createResourceRequest(nullptr, url);
|
||||
Q_ASSERT(request);
|
||||
request->setCacheEnabled(!forceDownload);
|
||||
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptContentAvailable);
|
||||
connect(request, &ResourceRequest::finished, this, [=]{ scriptContentAvailable(maxRetries); });
|
||||
request->send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const int MAX_RETRIES = 5;
|
||||
static int START_DELAY_BETWEEN_RETRIES = 200;
|
||||
|
||||
void ScriptCache::scriptContentAvailable() {
|
||||
void ScriptCache::scriptContentAvailable(int maxRetries) {
|
||||
#ifdef THREAD_DEBUGGING
|
||||
qCDebug(scriptengine) << "ScriptCache::scriptContentAvailable() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||
#endif
|
||||
|
@ -181,7 +127,7 @@ void ScriptCache::scriptContentAvailable() {
|
|||
|
||||
QString scriptContent;
|
||||
std::vector<contentAvailableCallback> allCallbacks;
|
||||
bool finished { false };
|
||||
QString status = QMetaEnum::fromType<ResourceRequest::Result>().valueToKey(req->getResult());
|
||||
bool success { false };
|
||||
|
||||
{
|
||||
|
@ -199,7 +145,6 @@ void ScriptCache::scriptContentAvailable() {
|
|||
_activeScriptRequests.remove(url);
|
||||
|
||||
_scriptCache[url] = scriptContent = req->getData();
|
||||
finished = true;
|
||||
qCDebug(scriptengine) << "Done downloading script at:" << url.toString();
|
||||
} else {
|
||||
auto result = req->getResult();
|
||||
|
@ -207,16 +152,19 @@ void ScriptCache::scriptContentAvailable() {
|
|||
result == ResourceRequest::AccessDenied ||
|
||||
result == ResourceRequest::InvalidURL ||
|
||||
result == ResourceRequest::NotFound ||
|
||||
scriptRequest.numRetries >= MAX_RETRIES;
|
||||
scriptRequest.numRetries >= maxRetries;
|
||||
|
||||
if (!irrecoverable) {
|
||||
++scriptRequest.numRetries;
|
||||
|
||||
qCDebug(scriptengine) << "Script request failed: " << url;
|
||||
int timeout = exp(scriptRequest.numRetries) * ScriptRequest::START_DELAY_BETWEEN_RETRIES;
|
||||
int attempt = scriptRequest.numRetries;
|
||||
qCDebug(scriptengine) << QString("Script request failed [%1]: %2 (will retry %3 more times; attempt #%4 in %5ms...)")
|
||||
.arg(status).arg(url.toString()).arg(maxRetries - attempt + 1).arg(attempt).arg(timeout);
|
||||
|
||||
int timeout = exp(scriptRequest.numRetries) * START_DELAY_BETWEEN_RETRIES;
|
||||
QTimer::singleShot(timeout, this, [this, url]() {
|
||||
qCDebug(scriptengine) << "Retrying script request: " << url;
|
||||
QTimer::singleShot(timeout, this, [this, url, attempt, maxRetries]() {
|
||||
qCDebug(scriptengine) << QString("Retrying script request [%1 / %2]: %3")
|
||||
.arg(attempt).arg(maxRetries).arg(url.toString());
|
||||
|
||||
auto request = ResourceManager::createResourceRequest(nullptr, url);
|
||||
Q_ASSERT(request);
|
||||
|
@ -224,7 +172,7 @@ void ScriptCache::scriptContentAvailable() {
|
|||
// We've already made a request, so the cache must be disabled or it wasn't there, so enabling
|
||||
// it will do nothing.
|
||||
request->setCacheEnabled(false);
|
||||
connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptContentAvailable);
|
||||
connect(request, &ResourceRequest::finished, this, [=]{ scriptContentAvailable(maxRetries); });
|
||||
request->send();
|
||||
});
|
||||
} else {
|
||||
|
@ -232,9 +180,12 @@ void ScriptCache::scriptContentAvailable() {
|
|||
|
||||
allCallbacks = scriptRequest.scriptUsers;
|
||||
|
||||
scriptContent = _scriptCache[url];
|
||||
finished = true;
|
||||
qCWarning(scriptengine) << "Error loading script from URL " << url;
|
||||
if (_scriptCache.contains(url)) {
|
||||
scriptContent = _scriptCache[url];
|
||||
}
|
||||
_activeScriptRequests.remove(url);
|
||||
qCWarning(scriptengine) << "Error loading script from URL " << url << "(" << status <<")";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -242,9 +193,9 @@ void ScriptCache::scriptContentAvailable() {
|
|||
|
||||
req->deleteLater();
|
||||
|
||||
if (finished && !DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||
if (allCallbacks.size() > 0 && !DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||
foreach(contentAvailableCallback thisCallback, allCallbacks) {
|
||||
thisCallback(url.toString(), scriptContent, true, success);
|
||||
thisCallback(url.toString(), scriptContent, true, success, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include <mutex>
|
||||
#include <ResourceCache.h>
|
||||
|
||||
using contentAvailableCallback = std::function<void(const QString& scriptOrURL, const QString& contents, bool isURL, bool contentAvailable)>;
|
||||
using contentAvailableCallback = std::function<void(const QString& scriptOrURL, const QString& contents, bool isURL, bool contentAvailable, const QString& status)>;
|
||||
|
||||
class ScriptUser {
|
||||
public:
|
||||
|
@ -25,8 +25,11 @@ public:
|
|||
|
||||
class ScriptRequest {
|
||||
public:
|
||||
static const int MAX_RETRIES { 5 };
|
||||
static const int START_DELAY_BETWEEN_RETRIES { 200 };
|
||||
std::vector<contentAvailableCallback> scriptUsers { };
|
||||
int numRetries { 0 };
|
||||
int maxRetries { MAX_RETRIES };
|
||||
};
|
||||
|
||||
/// Interface for loading scripts
|
||||
|
@ -38,23 +41,17 @@ class ScriptCache : public QObject, public Dependency {
|
|||
using Lock = std::unique_lock<Mutex>;
|
||||
|
||||
public:
|
||||
static const QString STATUS_INLINE;
|
||||
static const QString STATUS_CACHED;
|
||||
|
||||
void clearCache();
|
||||
Q_INVOKABLE void clearATPScriptsFromCache();
|
||||
void getScriptContents(const QString& scriptOrURL, contentAvailableCallback contentAvailable, bool forceDownload = false);
|
||||
void getScriptContents(const QString& scriptOrURL, contentAvailableCallback contentAvailable, bool forceDownload = false, int maxRetries = ScriptRequest::MAX_RETRIES);
|
||||
|
||||
|
||||
QString getScript(const QUrl& unnormalizedURL, ScriptUser* scriptUser, bool& isPending, bool redownload = false);
|
||||
void deleteScript(const QUrl& unnormalizedURL);
|
||||
|
||||
// FIXME - how do we remove a script from the bad script list in the case of a redownload?
|
||||
void addScriptToBadScriptList(const QUrl& url) { _badScripts.insert(url); }
|
||||
bool isInBadScriptList(const QUrl& url) { return _badScripts.contains(url); }
|
||||
|
||||
private slots:
|
||||
void scriptDownloaded(); // old version
|
||||
void scriptContentAvailable(); // new version
|
||||
|
||||
private:
|
||||
void scriptContentAvailable(int maxRetries); // new version
|
||||
ScriptCache(QObject* parent = NULL);
|
||||
|
||||
Mutex _containerLock;
|
||||
|
@ -62,7 +59,6 @@ private:
|
|||
|
||||
QHash<QUrl, QString> _scriptCache;
|
||||
QMultiMap<QUrl, ScriptUser*> _scriptUsers;
|
||||
QSet<QUrl> _badScripts;
|
||||
};
|
||||
|
||||
#endif // hifi_ScriptCache_h
|
||||
|
|
|
@ -68,10 +68,12 @@
|
|||
|
||||
#include "MIDIEvent.h"
|
||||
|
||||
static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3";
|
||||
const QString BaseScriptEngine::SCRIPT_EXCEPTION_FORMAT { "[UncaughtException] %1 in %2:%3" };
|
||||
static const QScriptEngine::QObjectWrapOptions DEFAULT_QOBJECT_WRAP_OPTIONS =
|
||||
QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects;
|
||||
|
||||
static const bool HIFI_AUTOREFRESH_FILE_SCRIPTS { true };
|
||||
|
||||
Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature)
|
||||
int functionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature>();
|
||||
|
||||
|
@ -137,40 +139,51 @@ QString encodeEntityIdIntoEntityUrl(const QString& url, const QString& entityID)
|
|||
return url + " [EntityID:" + entityID + "]";
|
||||
}
|
||||
|
||||
static bool hasCorrectSyntax(const QScriptProgram& program, ScriptEngine* reportingEngine) {
|
||||
const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode());
|
||||
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
|
||||
QString BaseScriptEngine::lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber) {
|
||||
const auto syntaxCheck = checkSyntax(sourceCode);
|
||||
if (syntaxCheck.state() != syntaxCheck.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);
|
||||
reportingEngine->scriptErrorMessage(qPrintable(message));
|
||||
return false;
|
||||
const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, fileName, line, column);
|
||||
return message;
|
||||
}
|
||||
return true;
|
||||
return QString();
|
||||
}
|
||||
|
||||
static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName, ScriptEngine* reportingEngine, QString* exceptionMessage = nullptr) {
|
||||
if (engine.hasUncaughtException()) {
|
||||
const auto backtrace = engine.uncaughtExceptionBacktrace();
|
||||
const auto exception = engine.uncaughtException().toString();
|
||||
const auto line = QString::number(engine.uncaughtExceptionLineNumber());
|
||||
engine.clearExceptions();
|
||||
QString BaseScriptEngine::formatUncaughtException(const QString& overrideFileName) {
|
||||
QString message;
|
||||
if (hasUncaughtException()) {
|
||||
const auto error = uncaughtException();
|
||||
const auto backtrace = uncaughtExceptionBacktrace();
|
||||
const auto exception = error.toString();
|
||||
auto filename = overrideFileName;
|
||||
if (filename.isEmpty()) {
|
||||
QScriptContextInfo ctx { currentContext() };
|
||||
filename = ctx.fileName();
|
||||
}
|
||||
const auto line = QString::number(uncaughtExceptionLineNumber());
|
||||
|
||||
QString message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line);
|
||||
message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, overrideFileName, line);
|
||||
if (!backtrace.empty()) {
|
||||
static const auto lineSeparator = "\n ";
|
||||
message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator));
|
||||
}
|
||||
reportingEngine->scriptErrorMessage(qPrintable(message));
|
||||
if (exceptionMessage) {
|
||||
*exceptionMessage = message;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return message;
|
||||
}
|
||||
|
||||
QString ScriptEngine::reportUncaughtException(const QString& overrideFileName) {
|
||||
QString message;
|
||||
if (!hasUncaughtException()) {
|
||||
return message;
|
||||
}
|
||||
message = formatUncaughtException(overrideFileName.isEmpty() ? _fileNameString : overrideFileName);
|
||||
scriptErrorMessage(qPrintable(message));
|
||||
return message;
|
||||
}
|
||||
|
||||
int ScriptEngine::processLevelMaxRetries { ScriptRequest::MAX_RETRIES };
|
||||
ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const QString& fileNameString) :
|
||||
_context(context),
|
||||
_scriptContents(scriptContents),
|
||||
|
@ -181,10 +194,16 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const
|
|||
DependencyManager::get<ScriptEngines>()->addScriptEngine(this);
|
||||
|
||||
connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) {
|
||||
hadUncaughtExceptions(*this, _fileNameString, this);
|
||||
reportUncaughtException();
|
||||
clearExceptions();
|
||||
});
|
||||
|
||||
setProcessEventsInterval(MSECS_PER_SECOND);
|
||||
if (isEntityServerScript()) {
|
||||
qCDebug(scriptengine) << "isEntityServerScript() -- limiting maxRetries to 1";
|
||||
processLevelMaxRetries = 1;
|
||||
}
|
||||
qCDebug(scriptengine) << getContext() << "processLevelMaxRetries =" << processLevelMaxRetries;
|
||||
}
|
||||
|
||||
QString ScriptEngine::getContext() const {
|
||||
|
@ -301,7 +320,10 @@ void ScriptEngine::runDebuggable() {
|
|||
}
|
||||
_lastUpdate = now;
|
||||
// Debug and clear exceptions
|
||||
hadUncaughtExceptions(*this, _fileNameString, this);
|
||||
if (hasUncaughtException()) {
|
||||
reportUncaughtException();
|
||||
clearExceptions();
|
||||
}
|
||||
});
|
||||
|
||||
timer->start(10);
|
||||
|
@ -334,6 +356,16 @@ void ScriptEngine::runInThread() {
|
|||
workerThread->start();
|
||||
}
|
||||
|
||||
void ScriptEngine::executeOnScriptThread(std::function<void()> function, bool blocking ) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "executeOnScriptThread", blocking ? Qt::BlockingQueuedConnection : Qt::QueuedConnection,
|
||||
Q_ARG(std::function<void()>, function));
|
||||
return;
|
||||
}
|
||||
|
||||
function();
|
||||
}
|
||||
|
||||
void ScriptEngine::waitTillDoneRunning() {
|
||||
auto workerThread = thread();
|
||||
|
||||
|
@ -399,8 +431,6 @@ QString ScriptEngine::getFilename() const {
|
|||
return lastPart;
|
||||
}
|
||||
|
||||
|
||||
// FIXME - switch this to the new model of ScriptCache callbacks
|
||||
void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) {
|
||||
if (_isRunning) {
|
||||
return;
|
||||
|
@ -410,19 +440,27 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) {
|
|||
_fileNameString = url.toString();
|
||||
_isReloading = reload;
|
||||
|
||||
bool isPending;
|
||||
const auto maxRetries = 0; // for consistency with previous scriptCache->getScript() behavior
|
||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||
scriptCache->getScript(url, this, isPending, reload);
|
||||
}
|
||||
scriptCache->getScriptContents(url.toString(), [this](const QString& url, const QString& scriptContents, bool isURL, bool success, const QString&status) {
|
||||
qCDebug(scriptengine) << "loadURL" << url << status << QThread::currentThread();
|
||||
if (!success) {
|
||||
scriptErrorMessage("ERROR Loading file (" + status + "):" + url);
|
||||
emit errorLoadingScript(_fileNameString);
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME - switch this to the new model of ScriptCache callbacks
|
||||
void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) {
|
||||
_scriptContents = scriptContents;
|
||||
static const QString DEBUG_FLAG("#debug");
|
||||
if (QRegularExpression(DEBUG_FLAG).match(scriptContents).hasMatch()) {
|
||||
_debuggable = true;
|
||||
}
|
||||
emit scriptLoaded(url.toString());
|
||||
_scriptContents = scriptContents;
|
||||
|
||||
{
|
||||
static const QString DEBUG_FLAG("#debug");
|
||||
if (QRegularExpression(DEBUG_FLAG).match(scriptContents).hasMatch()) {
|
||||
qCWarning(scriptengine) << "NOTE: ScriptEngine for " << QUrl(url).fileName() << " will be launched in debug mode";
|
||||
_debuggable = true;
|
||||
}
|
||||
}
|
||||
emit scriptLoaded(url);
|
||||
}, reload, maxRetries);
|
||||
}
|
||||
|
||||
void ScriptEngine::scriptErrorMessage(const QString& message) {
|
||||
|
@ -440,12 +478,6 @@ void ScriptEngine::scriptInfoMessage(const QString& message) {
|
|||
emit infoMessage(message);
|
||||
}
|
||||
|
||||
// FIXME - switch this to the new model of ScriptCache callbacks
|
||||
void ScriptEngine::errorInLoadingScript(const QUrl& url) {
|
||||
scriptErrorMessage("ERROR Loading file:" + url.toString());
|
||||
emit errorLoadingScript(_fileNameString);
|
||||
}
|
||||
|
||||
// Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of
|
||||
// callAnimationStateHandler requires that the type be registered.
|
||||
// These two are meaningful, if we ever do want to use them...
|
||||
|
@ -520,6 +552,15 @@ void ScriptEngine::init() {
|
|||
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
entityScriptingInterface->init();
|
||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) {
|
||||
if (_entityScripts.contains(entityID)) {
|
||||
if (isEntityScriptRunning(entityID)) {
|
||||
qCWarning(scriptengine) << "deletingEntity while entity script is still running!" << entityID;
|
||||
}
|
||||
_entityScripts.remove(entityID);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// register various meta-types
|
||||
registerMetaTypes(this);
|
||||
|
@ -850,17 +891,25 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi
|
|||
}
|
||||
|
||||
// Check syntax
|
||||
const QScriptProgram program(sourceCode, fileName, lineNumber);
|
||||
if (!hasCorrectSyntax(program, this)) {
|
||||
auto syntaxError = lintScript(sourceCode, fileName);
|
||||
QScriptProgram program { sourceCode, fileName, lineNumber };
|
||||
if (!syntaxError.isEmpty() || program.isNull()) {
|
||||
scriptErrorMessage(qPrintable(syntaxError));
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
++_evaluatesPending;
|
||||
const auto result = QScriptEngine::evaluate(program);
|
||||
auto result = BaseScriptEngine::evaluate(program);
|
||||
--_evaluatesPending;
|
||||
|
||||
const auto hadUncaughtException = hadUncaughtExceptions(*this, program.fileName(), this);
|
||||
emit evaluationFinished(result, hadUncaughtException);
|
||||
if (hasUncaughtException()) {
|
||||
result = uncaughtException();
|
||||
reportUncaughtException(program.fileName());
|
||||
emit evaluationFinished(result, true);
|
||||
clearExceptions();
|
||||
} else {
|
||||
emit evaluationFinished(result, false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1009,7 +1058,10 @@ void ScriptEngine::run() {
|
|||
_lastUpdate = now;
|
||||
|
||||
// Debug and clear exceptions
|
||||
hadUncaughtExceptions(*this, _fileNameString, this);
|
||||
if (hasUncaughtException()) {
|
||||
reportUncaughtException();
|
||||
clearExceptions();
|
||||
}
|
||||
}
|
||||
|
||||
scriptInfoMessage("Script Engine stopping:" + getFilename());
|
||||
|
@ -1299,12 +1351,12 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
|
|||
EntityItemID capturedEntityIdentifier = currentEntityIdentifier;
|
||||
QUrl capturedSandboxURL = currentSandboxURL;
|
||||
|
||||
auto evaluateScripts = [=](const QMap<QUrl, QString>& data) {
|
||||
auto evaluateScripts = [=](const QMap<QUrl, QString>& data, const QMap<QUrl, QString>& status) {
|
||||
auto parentURL = _parentURL;
|
||||
for (QUrl url : urls) {
|
||||
QString contents = data[url];
|
||||
if (contents.isNull()) {
|
||||
scriptErrorMessage("Error loading file: " + url.toString());
|
||||
scriptErrorMessage("Error loading file (" + status[url] +"): " + url.toString());
|
||||
} else {
|
||||
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||
if (!_includedURLs.contains(url)) {
|
||||
|
@ -1336,7 +1388,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
|
|||
// If we are destroyed before the loader completes, make sure to clean it up
|
||||
connect(this, &QObject::destroyed, loader, &QObject::deleteLater);
|
||||
|
||||
loader->start();
|
||||
loader->start(processLevelMaxRetries);
|
||||
|
||||
if (!callback.isFunction() && !loader->isFinished()) {
|
||||
QEventLoop loop;
|
||||
|
@ -1368,7 +1420,7 @@ void ScriptEngine::load(const QString& loadFile) {
|
|||
}
|
||||
if (!currentEntityIdentifier.isInvalidID()) {
|
||||
scriptWarningMessage("Script.load() from entity script is ignored... loadFile:"
|
||||
+ loadFile + "parent script:" + getFilename());
|
||||
+ loadFile + "parent script:" + getFilename() + "entity: " + currentEntityIdentifier.toString());
|
||||
return; // bail early
|
||||
}
|
||||
|
||||
|
@ -1411,13 +1463,19 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin
|
|||
int ScriptEngine::getNumRunningEntityScripts() const {
|
||||
int sum = 0;
|
||||
for (auto& st : _entityScripts) {
|
||||
if (st.status == RUNNING) {
|
||||
if (st.status == EntityScriptStatus::RUNNING) {
|
||||
++sum;
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
QString ScriptEngine::getEntityScriptStatus(const EntityItemID& entityID) {
|
||||
if (_entityScripts.contains(entityID))
|
||||
return EntityScriptStatus_::valueToKey(_entityScripts[entityID].status).toLower();
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const {
|
||||
auto it = _entityScripts.constFind(entityID);
|
||||
if (it == _entityScripts.constEnd()) {
|
||||
|
@ -1427,28 +1485,48 @@ bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntitySc
|
|||
return true;
|
||||
}
|
||||
|
||||
void ScriptEngine::setEntityScriptDetails(const EntityItemID& entityID, const EntityScriptDetails& details) {
|
||||
_entityScripts[entityID] = details;
|
||||
emit entityScriptDetailsUpdated();
|
||||
}
|
||||
|
||||
void ScriptEngine::updateEntityScriptStatus(const EntityItemID& entityID, const EntityScriptStatus &status, const QString& errorInfo) {
|
||||
EntityScriptDetails &details = _entityScripts[entityID];
|
||||
details.status = status;
|
||||
details.errorInfo = errorInfo;
|
||||
emit entityScriptDetailsUpdated();
|
||||
}
|
||||
|
||||
// since all of these operations can be asynch we will always do the actual work in the response handler
|
||||
// for the download
|
||||
void ScriptEngine::loadEntityScript(QWeakPointer<ScriptEngine> theEngine, const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) {
|
||||
auto engine = theEngine.data();
|
||||
engine->executeOnScriptThread([=]{
|
||||
EntityScriptDetails details = engine->_entityScripts[entityID];
|
||||
if (details.status == EntityScriptStatus::PENDING || details.status == EntityScriptStatus::UNLOADED) {
|
||||
engine->updateEntityScriptStatus(entityID, EntityScriptStatus::LOADING, QThread::currentThread()->objectName());
|
||||
}
|
||||
});
|
||||
|
||||
// NOTE: If the script content is not currently in the cache, the LAMBDA here will be called on the Main Thread
|
||||
// which means we're guaranteed that it's not the correct thread for the ScriptEngine. This means
|
||||
// when we get into entityScriptContentAvailable() we will likely invokeMethod() to get it over
|
||||
// to the "Entities" ScriptEngine thread.
|
||||
DependencyManager::get<ScriptCache>()->getScriptContents(entityScript, [theEngine, entityID](const QString& scriptOrURL, const QString& contents, bool isURL, bool success) {
|
||||
DependencyManager::get<ScriptCache>()->getScriptContents(entityScript, [theEngine, entityID](const QString& scriptOrURL, const QString& contents, bool isURL, bool success, const QString &status) {
|
||||
QSharedPointer<ScriptEngine> strongEngine = theEngine.toStrongRef();
|
||||
if (strongEngine) {
|
||||
#ifdef THREAD_DEBUGGING
|
||||
qCDebug(scriptengine) << "ScriptEngine::entityScriptContentAvailable() IN LAMBDA contentAvailable on thread ["
|
||||
<< QThread::currentThread() << "] expected thread [" << strongEngine->thread() << "]";
|
||||
#endif
|
||||
strongEngine->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success);
|
||||
strongEngine->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success, status);
|
||||
}
|
||||
}, forceRedownload);
|
||||
}, forceRedownload, processLevelMaxRetries);
|
||||
}
|
||||
|
||||
// since all of these operations can be asynch we will always do the actual work in the response handler
|
||||
// for the download
|
||||
void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success) {
|
||||
void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success , const QString& status) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
#ifdef THREAD_DEBUGGING
|
||||
qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::entityScriptContentAvailable() called on wrong thread ["
|
||||
|
@ -1462,7 +1540,8 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
Q_ARG(const QString&, scriptOrURL),
|
||||
Q_ARG(const QString&, contents),
|
||||
Q_ARG(bool, isURL),
|
||||
Q_ARG(bool, success));
|
||||
Q_ARG(bool, success),
|
||||
Q_ARG(const QString&, status));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1478,22 +1557,19 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
newDetails.scriptText = scriptOrURL;
|
||||
|
||||
if (!success) {
|
||||
newDetails.status = ERROR_LOADING_SCRIPT;
|
||||
newDetails.errorInfo = "Failed to load script";
|
||||
_entityScripts[entityID] = newDetails;
|
||||
emit entityScriptDetailsUpdated();
|
||||
newDetails.status = EntityScriptStatus::ERROR_LOADING_SCRIPT;
|
||||
newDetails.errorInfo = "Failed to load script (" + status + ")";
|
||||
setEntityScriptDetails(entityID, newDetails);
|
||||
return;
|
||||
}
|
||||
|
||||
QScriptProgram program(contents, fileName);
|
||||
if (!hasCorrectSyntax(program, this)) {
|
||||
if (!isFileUrl) {
|
||||
scriptCache->addScriptToBadScriptList(scriptOrURL);
|
||||
}
|
||||
newDetails.status = ERROR_RUNNING_SCRIPT;
|
||||
newDetails.errorInfo = "Bad syntax";
|
||||
_entityScripts[entityID] = newDetails;
|
||||
emit entityScriptDetailsUpdated();
|
||||
auto syntaxError = lintScript(contents, fileName);
|
||||
QScriptProgram program { contents, fileName };
|
||||
if (!syntaxError.isNull() || program.isNull()) {
|
||||
newDetails.status = EntityScriptStatus::ERROR_RUNNING_SCRIPT;
|
||||
newDetails.errorInfo = QString("Bad syntax (%1)").arg(syntaxError);
|
||||
setEntityScriptDetails(entityID, newDetails);
|
||||
qCDebug(scriptengine) << newDetails.errorInfo << scriptOrURL;
|
||||
return; // done processing script
|
||||
}
|
||||
|
||||
|
@ -1502,16 +1578,18 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
}
|
||||
|
||||
const int SANDBOX_TIMEOUT = 0.25 * MSECS_PER_SECOND;
|
||||
QScriptEngine sandbox;
|
||||
BaseScriptEngine sandbox;
|
||||
sandbox.setProcessEventsInterval(SANDBOX_TIMEOUT);
|
||||
QScriptValue testConstructor;
|
||||
{
|
||||
QTimer timeout;
|
||||
timeout.setSingleShot(true);
|
||||
timeout.start(SANDBOX_TIMEOUT);
|
||||
connect(&timeout, &QTimer::timeout, [&sandbox, SANDBOX_TIMEOUT]{
|
||||
connect(&timeout, &QTimer::timeout, [&sandbox, SANDBOX_TIMEOUT, scriptOrURL]{
|
||||
auto context = sandbox.currentContext();
|
||||
if (context) {
|
||||
qCDebug(scriptengine) << "ScriptEngine::entityScriptContentAvailable timeout(" << scriptOrURL << ")";
|
||||
|
||||
// Guard against infinite loops and non-performant code
|
||||
context->throwError(QString("Timed out (entity constructors are limited to %1ms)").arg(SANDBOX_TIMEOUT));
|
||||
}
|
||||
|
@ -1519,13 +1597,12 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
testConstructor = sandbox.evaluate(program);
|
||||
}
|
||||
|
||||
QString exceptionMessage;
|
||||
if (hadUncaughtExceptions(sandbox, program.fileName(), this, &exceptionMessage)) {
|
||||
newDetails.status = ERROR_RUNNING_SCRIPT;
|
||||
QString exceptionMessage = sandbox.formatUncaughtException(program.fileName());
|
||||
if (!exceptionMessage.isNull()) {
|
||||
newDetails.status = EntityScriptStatus::ERROR_RUNNING_SCRIPT;
|
||||
newDetails.errorInfo = exceptionMessage;
|
||||
_entityScripts[entityID] = newDetails;
|
||||
emit entityScriptDetailsUpdated();
|
||||
|
||||
setEntityScriptDetails(entityID, newDetails);
|
||||
qCDebug(scriptengine) << "----- ScriptEngine::entityScriptContentAvailable -- hadUncaughtExceptions (" << scriptOrURL << ")";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1544,15 +1621,11 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
+ "," + testConstructorValue
|
||||
+ "," + scriptOrURL);
|
||||
|
||||
if (!isFileUrl) {
|
||||
scriptCache->addScriptToBadScriptList(scriptOrURL);
|
||||
}
|
||||
|
||||
newDetails.status = ERROR_RUNNING_SCRIPT;
|
||||
newDetails.status = EntityScriptStatus::ERROR_RUNNING_SCRIPT;
|
||||
newDetails.errorInfo = "Could not find constructor";
|
||||
_entityScripts[entityID] = newDetails;
|
||||
emit entityScriptDetailsUpdated();
|
||||
setEntityScriptDetails(entityID, newDetails);
|
||||
|
||||
qCDebug(scriptengine) << "----- ScriptEngine::entityScriptContentAvailable -- failed to run (" << scriptOrURL << ")";
|
||||
return; // done processing script
|
||||
}
|
||||
|
||||
|
@ -1569,11 +1642,11 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
};
|
||||
doWithEnvironment(entityID, sandboxURL, initialization);
|
||||
|
||||
newDetails.status = EntityScriptStatus::RUNNING;
|
||||
newDetails.scriptObject = entityScriptObject;
|
||||
newDetails.lastModified = lastModified;
|
||||
newDetails.definingSandboxURL = sandboxURL;
|
||||
_entityScripts[entityID] = newDetails;
|
||||
emit entityScriptDetailsUpdated();
|
||||
setEntityScriptDetails(entityID, newDetails);
|
||||
|
||||
if (isURL) {
|
||||
setParentURL("");
|
||||
|
@ -1600,12 +1673,13 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) {
|
|||
#endif
|
||||
|
||||
if (_entityScripts.contains(entityID)) {
|
||||
if (_entityScripts[entityID].status == RUNNING) {
|
||||
if (isEntityScriptRunning(entityID)) {
|
||||
callEntityScriptMethod(entityID, "unload");
|
||||
}
|
||||
_entityScripts.remove(entityID);
|
||||
EntityScriptDetails newDetails;
|
||||
newDetails.status = EntityScriptStatus::UNLOADED;
|
||||
setEntityScriptDetails(entityID, newDetails);
|
||||
stopAllTimersForEntityScript(entityID);
|
||||
emit entityScriptDetailsUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1622,9 +1696,7 @@ void ScriptEngine::unloadAllEntityScripts() {
|
|||
qCDebug(scriptengine) << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]";
|
||||
#endif
|
||||
foreach(const EntityItemID& entityID, _entityScripts.keys()) {
|
||||
if (_entityScripts[entityID].status == RUNNING) {
|
||||
callEntityScriptMethod(entityID, "unload");
|
||||
}
|
||||
unloadEntityScript(entityID);
|
||||
}
|
||||
_entityScripts.clear();
|
||||
emit entityScriptDetailsUpdated();
|
||||
|
@ -1641,7 +1713,7 @@ void ScriptEngine::unloadAllEntityScripts() {
|
|||
}
|
||||
|
||||
void ScriptEngine::refreshFileScript(const EntityItemID& entityID) {
|
||||
if (!_entityScripts.contains(entityID)) {
|
||||
if (!HIFI_AUTOREFRESH_FILE_SCRIPTS || !_entityScripts.contains(entityID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1663,8 +1735,8 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) {
|
|||
file.open(QIODevice::ReadOnly);
|
||||
QString scriptContents = QTextStream(&file).readAll();
|
||||
this->unloadEntityScript(entityID);
|
||||
this->entityScriptContentAvailable(entityID, details.scriptText, scriptContents, true, true);
|
||||
if (!_entityScripts.contains(entityID) || _entityScripts[entityID].status != RUNNING) {
|
||||
this->entityScriptContentAvailable(entityID, details.scriptText, scriptContents, true, true, "Success");
|
||||
if (!isEntityScriptRunning(entityID)) {
|
||||
scriptWarningMessage("Reload script " + details.scriptText + " failed");
|
||||
} else {
|
||||
details = _entityScripts[entityID];
|
||||
|
@ -1692,7 +1764,10 @@ void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, const QUrl& s
|
|||
#else
|
||||
operation();
|
||||
#endif
|
||||
hadUncaughtExceptions(*this, _fileNameString, this);
|
||||
if (hasUncaughtException()) {
|
||||
reportUncaughtException();
|
||||
clearExceptions();
|
||||
}
|
||||
|
||||
currentEntityIdentifier = oldIdentifier;
|
||||
currentSandboxURL = oldSandboxURL;
|
||||
|
@ -1722,8 +1797,10 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
|
|||
"entityID:" << entityID << "methodName:" << methodName;
|
||||
#endif
|
||||
|
||||
refreshFileScript(entityID);
|
||||
if (_entityScripts.contains(entityID) && _entityScripts[entityID].status == RUNNING) {
|
||||
if (HIFI_AUTOREFRESH_FILE_SCRIPTS && methodName != "unload") {
|
||||
refreshFileScript(entityID);
|
||||
}
|
||||
if (isEntityScriptRunning(entityID)) {
|
||||
EntityScriptDetails details = _entityScripts[entityID];
|
||||
QScriptValue entityScript = details.scriptObject; // previously loaded
|
||||
if (entityScript.property(methodName).isFunction()) {
|
||||
|
@ -1754,8 +1831,10 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
|
|||
"entityID:" << entityID << "methodName:" << methodName << "event: pointerEvent";
|
||||
#endif
|
||||
|
||||
refreshFileScript(entityID);
|
||||
if (_entityScripts.contains(entityID) && _entityScripts[entityID].status == RUNNING) {
|
||||
if (HIFI_AUTOREFRESH_FILE_SCRIPTS) {
|
||||
refreshFileScript(entityID);
|
||||
}
|
||||
if (isEntityScriptRunning(entityID)) {
|
||||
EntityScriptDetails details = _entityScripts[entityID];
|
||||
QScriptValue entityScript = details.scriptObject; // previously loaded
|
||||
if (entityScript.property(methodName).isFunction()) {
|
||||
|
@ -1787,8 +1866,10 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
|
|||
"entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision";
|
||||
#endif
|
||||
|
||||
refreshFileScript(entityID);
|
||||
if (_entityScripts.contains(entityID) && _entityScripts[entityID].status == RUNNING) {
|
||||
if (HIFI_AUTOREFRESH_FILE_SCRIPTS) {
|
||||
refreshFileScript(entityID);
|
||||
}
|
||||
if (isEntityScriptRunning(entityID)) {
|
||||
EntityScriptDetails details = _entityScripts[entityID];
|
||||
QScriptValue entityScript = details.scriptObject; // previously loaded
|
||||
if (entityScript.property(methodName).isFunction()) {
|
||||
|
|
|
@ -59,7 +59,7 @@ typedef QHash<QString, CallbackList> RegisteredEventHandlers;
|
|||
|
||||
class EntityScriptDetails {
|
||||
public:
|
||||
EntityScriptStatus status { RUNNING };
|
||||
EntityScriptStatus status { EntityScriptStatus::PENDING };
|
||||
|
||||
// If status indicates an error, this contains a human-readable string giving more information about the error.
|
||||
QString errorInfo { "" };
|
||||
|
@ -70,7 +70,15 @@ public:
|
|||
QUrl definingSandboxURL { QUrl() };
|
||||
};
|
||||
|
||||
class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider {
|
||||
// common base class with just QScriptEngine-dependent helper methods
|
||||
class BaseScriptEngine : public QScriptEngine {
|
||||
public:
|
||||
static const QString SCRIPT_EXCEPTION_FORMAT;
|
||||
QString lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1);
|
||||
QString formatUncaughtException(const QString& overrideFileName = QString());
|
||||
};
|
||||
|
||||
class ScriptEngine : public BaseScriptEngine, public EntitiesScriptEngineProvider {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString context READ getContext)
|
||||
public:
|
||||
|
@ -82,6 +90,7 @@ public:
|
|||
AGENT_SCRIPT
|
||||
};
|
||||
|
||||
static int processLevelMaxRetries;
|
||||
ScriptEngine(Context context, const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString(""));
|
||||
~ScriptEngine();
|
||||
|
||||
|
@ -89,6 +98,7 @@ public:
|
|||
/// the current script contents and calling run(). Callers will likely want to register the script with external
|
||||
/// services before calling this.
|
||||
void runInThread();
|
||||
Q_INVOKABLE void executeOnScriptThread(std::function<void()> function, bool blocking = false);
|
||||
|
||||
void runDebuggable();
|
||||
|
||||
|
@ -157,6 +167,10 @@ public:
|
|||
Q_INVOKABLE QUrl resourcesPath() const;
|
||||
|
||||
// Entity Script Related methods
|
||||
Q_INVOKABLE QString getEntityScriptStatus(const EntityItemID& entityID);
|
||||
Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) {
|
||||
return _entityScripts.contains(entityID) && _entityScripts[entityID].status == EntityScriptStatus::RUNNING;
|
||||
}
|
||||
static void loadEntityScript(QWeakPointer<ScriptEngine> theEngine, const EntityItemID& entityID, const QString& entityScript, bool forceRedownload);
|
||||
Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID); // will call unload method
|
||||
Q_INVOKABLE void unloadAllEntityScripts();
|
||||
|
@ -180,11 +194,6 @@ public:
|
|||
void disconnectNonEssentialSignals();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// NOTE - These are the callback implementations for ScriptUser the get called by ScriptCache when the contents
|
||||
// of a script are available.
|
||||
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents) override;
|
||||
virtual void errorInLoadingScript(const QUrl& url) override;
|
||||
|
||||
// These are currently used by Application to track if a script is user loaded or not. Consider finding a solution
|
||||
// inside of Application so that the ScriptEngine class is not polluted by this notion
|
||||
void setUserLoaded(bool isUserLoaded) { _isUserLoaded = isUserLoaded; }
|
||||
|
@ -203,6 +212,7 @@ public:
|
|||
bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const;
|
||||
|
||||
public slots:
|
||||
int evaluatePending() const { return _evaluatesPending; }
|
||||
void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler);
|
||||
void updateMemoryCost(const qint64&);
|
||||
|
||||
|
@ -230,12 +240,13 @@ signals:
|
|||
protected:
|
||||
void init();
|
||||
|
||||
bool evaluatePending() const { return _evaluatesPending > 0; }
|
||||
QString reportUncaughtException(const QString& overrideFileName = QString());
|
||||
void timerFired();
|
||||
void stopAllTimers();
|
||||
void stopAllTimersForEntityScript(const EntityItemID& entityID);
|
||||
void refreshFileScript(const EntityItemID& entityID);
|
||||
|
||||
void updateEntityScriptStatus(const EntityItemID& entityID, const EntityScriptStatus& status, const QString& errorInfo = QString());
|
||||
void setEntityScriptDetails(const EntityItemID& entityID, const EntityScriptDetails& details);
|
||||
void setParentURL(const QString& parentURL) { _parentURL = parentURL; }
|
||||
|
||||
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
|
||||
|
@ -243,7 +254,7 @@ protected:
|
|||
|
||||
QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers;
|
||||
void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs);
|
||||
Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success);
|
||||
Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success, const QString& status);
|
||||
|
||||
EntityItemID currentEntityIdentifier {}; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution.
|
||||
QUrl currentSandboxURL {}; // The toplevel url string for the entity script that loaded the code being executed, else empty.
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
|
||||
static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
|
||||
static const bool HIFI_SCRIPT_DEBUGGABLES { true };
|
||||
|
||||
ScriptsModel& getScriptsModel() {
|
||||
static ScriptsModel scriptsModel;
|
||||
return scriptsModel;
|
||||
|
@ -517,8 +519,9 @@ void ScriptEngines::launchScriptEngine(ScriptEngine* scriptEngine) {
|
|||
for (auto initializer : _scriptInitializers) {
|
||||
initializer(scriptEngine);
|
||||
}
|
||||
|
||||
if (scriptEngine->isDebuggable() || (qApp->queryKeyboardModifiers() & Qt::ShiftModifier)) {
|
||||
|
||||
auto const wantDebug = scriptEngine->isDebuggable() || (qApp->queryKeyboardModifiers() & Qt::ShiftModifier);
|
||||
if (HIFI_SCRIPT_DEBUGGABLES && wantDebug) {
|
||||
scriptEngine->runDebuggable();
|
||||
} else {
|
||||
scriptEngine->runInThread();
|
||||
|
|
|
@ -234,10 +234,14 @@ protected:
|
|||
|
||||
/**jsdoc
|
||||
* @typedef TabletButtonProxy.ButtonProperties
|
||||
* @property {string} text - button caption
|
||||
* @property {string} icon - url to button icon. (50 x 50)
|
||||
* @property {string} activeText - button caption when button is active
|
||||
* @property {string} hoverIcon - url to button icon, displayed during mouse hover. (50 x 50)
|
||||
* @property {string} activeHoverIcon - url to button icon used when button is active, and during mouse hover. (50 x 50)
|
||||
* @property {string} activeIcon - url to button icon used when button is active. (50 x 50)
|
||||
* @property {string} text - button caption
|
||||
* @property {string} hoverText - button caption when button is not-active but during mouse hover.
|
||||
* @property {string} activeText - button caption when button is active
|
||||
* @property {string} activeHoverText - button caption when button is active and during mouse hover.
|
||||
* @property {string} isActive - true when button is active.
|
||||
* @property {number} sortOrder - determines sort order on tablet. lower numbers will appear before larger numbers. default is 100
|
||||
*/
|
||||
|
|
|
@ -186,9 +186,6 @@ public:
|
|||
protected:
|
||||
const NestableType _nestableType; // EntityItem or an AvatarData
|
||||
QUuid _id;
|
||||
QUuid _parentID; // what is this thing's transform relative to?
|
||||
quint16 _parentJointIndex { INVALID_JOINT_INDEX }; // which joint of the parent is this relative to?
|
||||
|
||||
mutable SpatiallyNestableWeakPointer _parent;
|
||||
|
||||
virtual void beParentOfChild(SpatiallyNestablePointer newChild) const;
|
||||
|
@ -211,6 +208,9 @@ protected:
|
|||
quint64 _rotationChanged { 0 };
|
||||
|
||||
private:
|
||||
QUuid _parentID; // what is this thing's transform relative to?
|
||||
quint16 _parentJointIndex { INVALID_JOINT_INDEX }; // which joint of the parent is this relative to?
|
||||
|
||||
mutable ReadWriteLockable _transformLock;
|
||||
mutable ReadWriteLockable _idLock;
|
||||
mutable ReadWriteLockable _velocityLock;
|
||||
|
|
122
script-archive/displayLastEditedBy.js
Normal file
|
@ -0,0 +1,122 @@
|
|||
//
|
||||
// displayLastEditedBy.js
|
||||
//
|
||||
// Created by Si Fi Faye Li on 2 December, 2016
|
||||
//
|
||||
// Draws a line from each entity to the user in the current session who last changed a property, if any, as recorded
|
||||
// by the lastEditedBy property.
|
||||
|
||||
(function () {
|
||||
var SHOW_LAST_EDITED_BY_ME = true;
|
||||
var SEARCH_RADIUS = 40;
|
||||
// in meter, if the entities is too far away(out of search radius), we won't display its last edited by
|
||||
|
||||
var LINE_COLOR = { red: 0, green: 255, blue: 255};
|
||||
var LINE_EXPRIRATION_TIME = 3000; // in ms
|
||||
var UPDATE_INTERVAL = 1 / 60; // 60fps
|
||||
var myHashMap = {}; // stores {entityID of target entity : overlayID of the line}
|
||||
|
||||
var timer = 0;
|
||||
var lastUpdateTime = 0;
|
||||
function update(deltaTime) {
|
||||
timer += deltaTime;
|
||||
if (timer - lastUpdateTime > UPDATE_INTERVAL) {
|
||||
var targetEntityIDs = Entities.findEntities(MyAvatar.position,SEARCH_RADIUS);
|
||||
|
||||
targetEntityIDs.forEach(function(targetEntityID){
|
||||
var targetEntityProps = Entities.getEntityProperties(targetEntityID);
|
||||
|
||||
|
||||
// don't draw lines for entities that were last edited long time ago
|
||||
if (targetEntityProps.hasOwnProperty("lastEdited")) {
|
||||
var currentTime = new Date().getTime();
|
||||
// lastEdited is in usec while JS date object returns msec
|
||||
var timeDiff = currentTime - targetEntityProps.lastEdited/1000;
|
||||
if (timeDiff > LINE_EXPRIRATION_TIME) {
|
||||
if (myHashMap.hasOwnProperty(targetEntityID)) {
|
||||
var overlayID = myHashMap[targetEntityID];
|
||||
Overlays.deleteOverlay(overlayID);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var targetAvatarUUID = targetEntityProps.lastEditedBy;
|
||||
|
||||
// don't draw lines for entities last edited by myself
|
||||
// you may set SHOW_LAST_EDITED_BY_ME to true if you want to see these lines
|
||||
if (targetAvatarUUID === MyAvatar.sessionUUID && !SHOW_LAST_EDITED_BY_ME) {
|
||||
if (myHashMap.hasOwnProperty(targetEntityID)) {
|
||||
var overlayID = myHashMap[targetEntityID];
|
||||
Overlays.deleteOverlay(overlayID);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// don't draw lines for entities with no last edited by
|
||||
if (targetAvatarUUID === "{00000000-0000-0000-0000-000000000000}") {
|
||||
if (myHashMap.hasOwnProperty(targetEntityID)) {
|
||||
var overlayID = myHashMap[targetEntityID];
|
||||
Overlays.deleteOverlay(overlayID);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var targetAvatar = AvatarList.getAvatar(targetAvatarUUID);
|
||||
|
||||
// skip adding overlay if the avatar can't be found
|
||||
if (targetAvatar === null) {
|
||||
// delete overlay if the avatar was found before but no long here
|
||||
if (myHashMap.hasOwnProperty(targetEntityID)) {
|
||||
var overlayID = myHashMap[targetEntityID];
|
||||
Overlays.deleteOverlay(overlayID);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var props = {
|
||||
start: targetEntityProps.position,
|
||||
end: targetAvatar.position,
|
||||
color: LINE_COLOR,
|
||||
alpha: 1,
|
||||
ignoreRayIntersection: true,
|
||||
visible: true,
|
||||
solid: true,
|
||||
drawInFront: true
|
||||
};
|
||||
|
||||
if (myHashMap.hasOwnProperty(targetEntityID)) {
|
||||
var overlayID = myHashMap[targetEntityID];
|
||||
Overlays.editOverlay(overlayID, props);
|
||||
} else {
|
||||
var newOverlayID = Overlays.addOverlay("line3d", props);
|
||||
myHashMap[targetEntityID] = newOverlayID;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// remove lines for entities no longer within search radius
|
||||
for (var key in myHashMap) {
|
||||
if (myHashMap.hasOwnProperty(key)) {
|
||||
if (targetEntityIDs.indexOf(key) === -1) {
|
||||
var overlayID = myHashMap[key];
|
||||
Overlays.deleteOverlay(overlayID);
|
||||
delete myHashMap[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastUpdateTime = timer;
|
||||
}
|
||||
}
|
||||
Script.update.connect(update);
|
||||
|
||||
function cleanup() {
|
||||
for (var key in myHashMap) {
|
||||
if (myHashMap.hasOwnProperty(key)) {
|
||||
var overlayID = myHashMap[key];
|
||||
Overlays.deleteOverlay(overlayID);
|
||||
}
|
||||
}
|
||||
}
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
})();
|
38
scripts/developer/tests/messagesTests.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
|
||||
var channelName = "com.highfidelity.example.dataMessages";
|
||||
|
||||
Messages.subscribe(channelName);
|
||||
|
||||
//messageReceived(QString channel, QString message, QUuid senderUUID, bool localOnly);
|
||||
Messages.messageReceived.connect(function(channel, message, sender, local) {
|
||||
print("message recieved on ", channel, " message:", message, " from:", sender, " local:", local);
|
||||
});
|
||||
|
||||
Messages.dataReceived.connect(function(channel, data, sender, local) {
|
||||
var int8data = new Int8Array(data);
|
||||
var dataAsString = "";
|
||||
for (var i = 0; i < int8data.length; i++) {
|
||||
if (i > 0) {
|
||||
dataAsString += ", ";
|
||||
}
|
||||
dataAsString += int8data[i];
|
||||
}
|
||||
print("data recieved on ", channel, " from:", sender, " local:", local, "length of data:", int8data.length, " data:", dataAsString);
|
||||
});
|
||||
|
||||
var counter = 0;
|
||||
Script.update.connect(function(){
|
||||
counter++;
|
||||
if (counter == 100) {
|
||||
Messages.sendMessage(channelName, "foo");
|
||||
} else if (counter == 200) {
|
||||
var data = new Int8Array([0,1,10,2,20,3,30]);
|
||||
print("about to call sendData() data.length:", data.length);
|
||||
Messages.sendData(channelName, data.buffer);
|
||||
counter = 0;
|
||||
}
|
||||
});
|
||||
|
||||
Script.scriptEnding.connect(function(){
|
||||
Messages.unsubscribe(channelName);
|
||||
});
|
|
@ -165,9 +165,36 @@ function goAway(fromStartup) {
|
|||
if (!isEnabled || isAway) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// If we're entering away mode from some other state than startup, then we create our move timer immediately.
|
||||
// However if we're just stating up, we need to delay this process so that we don't think the initial teleport
|
||||
// is actually a move.
|
||||
if (fromStartup === undefined || fromStartup === false) {
|
||||
avatarMovedInterval = Script.setInterval(ifAvatarMovedGoActive, BASIC_TIMER_INTERVAL);
|
||||
} else {
|
||||
var WAIT_FOR_MOVE_ON_STARTUP = 3000; // 3 seconds
|
||||
Script.setTimeout(function() {
|
||||
avatarMovedInterval = Script.setInterval(ifAvatarMovedGoActive, BASIC_TIMER_INTERVAL);
|
||||
}, WAIT_FOR_MOVE_ON_STARTUP);
|
||||
}
|
||||
|
||||
UserActivityLogger.toggledAway(true);
|
||||
MyAvatar.isAway = true;
|
||||
}
|
||||
|
||||
function goActive() {
|
||||
if (!isAway) {
|
||||
return;
|
||||
}
|
||||
|
||||
UserActivityLogger.toggledAway(false);
|
||||
MyAvatar.isAway = false;
|
||||
}
|
||||
|
||||
MyAvatar.wentAway.connect(setAwayProperties)
|
||||
MyAvatar.wentActive.connect(setActiveProperties)
|
||||
|
||||
function setAwayProperties() {
|
||||
isAway = true;
|
||||
wasMuted = AudioDevice.getMuted();
|
||||
if (!wasMuted) {
|
||||
|
@ -189,27 +216,9 @@ function goAway(fromStartup) {
|
|||
wasHmdMounted = HMD.mounted; // always remember the correct state
|
||||
|
||||
avatarPosition = MyAvatar.position;
|
||||
|
||||
// If we're entering away mode from some other state than startup, then we create our move timer immediately.
|
||||
// However if we're just stating up, we need to delay this process so that we don't think the initial teleport
|
||||
// is actually a move.
|
||||
if (fromStartup === undefined || fromStartup === false) {
|
||||
avatarMovedInterval = Script.setInterval(ifAvatarMovedGoActive, BASIC_TIMER_INTERVAL);
|
||||
} else {
|
||||
var WAIT_FOR_MOVE_ON_STARTUP = 3000; // 3 seconds
|
||||
Script.setTimeout(function() {
|
||||
avatarMovedInterval = Script.setInterval(ifAvatarMovedGoActive, BASIC_TIMER_INTERVAL);
|
||||
}, WAIT_FOR_MOVE_ON_STARTUP);
|
||||
}
|
||||
}
|
||||
|
||||
function goActive() {
|
||||
if (!isAway) {
|
||||
return;
|
||||
}
|
||||
|
||||
UserActivityLogger.toggledAway(false);
|
||||
|
||||
function setActiveProperties() {
|
||||
isAway = false;
|
||||
if (!wasMuted) {
|
||||
AudioDevice.toggleMute();
|
||||
|
|
|
@ -177,6 +177,7 @@
|
|||
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
|
||||
});
|
||||
|
|
|
@ -404,7 +404,7 @@ Grabber.prototype.pressEvent = function(event) {
|
|||
};
|
||||
|
||||
Grabber.prototype.releaseEvent = function(event) {
|
||||
if (event.isLeftButton!==true ||event.isRightButton===true || event.isMiddleButton===true) {
|
||||
if (event.isLeftButton!==true ||event.isRightButton===true || event.isMiddleButton===true) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -253,6 +253,7 @@ var toolBar = (function () {
|
|||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
activeButton = tablet.addButton({
|
||||
icon: "icons/tablet-icons/edit-i.svg",
|
||||
activeIcon: "icons/tablet-icons/edit-a.svg",
|
||||
text: "EDIT",
|
||||
sortOrder: 10
|
||||
});
|
||||
|
@ -462,6 +463,11 @@ var toolBar = (function () {
|
|||
|
||||
that.setActive = function (active) {
|
||||
Settings.setValue(EDIT_SETTING, active);
|
||||
if (active) {
|
||||
Controller.captureEntityClickEvents();
|
||||
} else {
|
||||
Controller.releaseEntityClickEvents();
|
||||
}
|
||||
if (active === isActive) {
|
||||
return;
|
||||
}
|
||||
|
@ -965,6 +971,7 @@ function cleanupModelMenus() {
|
|||
}
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
toolBar.setActive(false);
|
||||
Settings.setValue(SETTING_AUTO_FOCUS_ON_SELECT, Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT));
|
||||
Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
|
||||
Settings.setValue(SETTING_SHOW_LIGHTS_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE));
|
||||
|
@ -1463,8 +1470,8 @@ var PropertiesTool = function (opts) {
|
|||
|
||||
function resetScriptStatus() {
|
||||
updateScriptStatus({
|
||||
statusRetrieved: false,
|
||||
isRunning: false,
|
||||
statusRetrieved: undefined,
|
||||
isRunning: undefined,
|
||||
status: "",
|
||||
errorInfo: ""
|
||||
});
|
||||
|
|
57
scripts/system/generalSettings.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
"use strict";
|
||||
|
||||
//
|
||||
// generalSettings.js
|
||||
// scripts/system/
|
||||
//
|
||||
// Created by Dante Ruiz on 9 Feb 2017
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
/* globals Tablet, Toolbars, Script, HMD, DialogsManager */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var button;
|
||||
var buttonName = "Settings";
|
||||
var toolBar = null;
|
||||
var tablet = null;
|
||||
var settings = "TabletGeneralSettings.qml"
|
||||
function onClicked(){
|
||||
if (tablet) {
|
||||
tablet.loadQMLSource(settings);
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
text: buttonName,
|
||||
sortOrder: 8
|
||||
});
|
||||
}
|
||||
|
||||
button.clicked.connect(onClicked);
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
button.clicked.disconnect(onClicked);
|
||||
if (tablet) {
|
||||
tablet.removeButton(button);
|
||||
}
|
||||
if (toolBar) {
|
||||
toolBar.removeButton(buttonName);
|
||||
}
|
||||
});
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
|
@ -39,6 +39,7 @@ if (Settings.getValue("HUDUIEnabled")) {
|
|||
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
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
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
|
||||
});
|
||||
|
|
|
@ -53,14 +53,13 @@ function onHmdChanged(isHmd) {
|
|||
//TODO change button icon when the hmd changes
|
||||
if (isHmd) {
|
||||
button.editProperties({
|
||||
icon: "icons/tablet-icons/switch-a.svg",
|
||||
icon: "icons/tablet-icons/switch-desk-i.svg",
|
||||
text: "DESKTOP"
|
||||
});
|
||||
} else {
|
||||
button.editProperties({
|
||||
icon: "icons/tablet-icons/switch-i.svg",
|
||||
text: "VR",
|
||||
sortOrder: 2
|
||||
icon: "icons/tablet-icons/switch-vr-i.svg",
|
||||
text: "VR"
|
||||
});
|
||||
}
|
||||
desktopOnlyViews.forEach(function (view) {
|
||||
|
@ -82,8 +81,8 @@ if (headset) {
|
|||
});
|
||||
} else {
|
||||
button = tablet.addButton({
|
||||
icon: "icons/tablet-icons/switch-a.svg",
|
||||
text: "SWITCH",
|
||||
icon: HMD.active ? "icons/tablet-icons/switch-desk-i.svg" : "icons/tablet-icons/switch-vr-i.svg",
|
||||
text: HMD.active ? "DESKTOP" : "VR",
|
||||
sortOrder: 2
|
||||
});
|
||||
}
|
||||
|
|
|
@ -318,6 +318,7 @@
|
|||
<input type="text" id="property-script-url">
|
||||
<input type="button" id="reload-script-button" class="glyph" value="F">
|
||||
</div>
|
||||
<hr class="behavior-group" />
|
||||
<div class="behavior-group property url refresh">
|
||||
<label for="property-server-scripts">Server Script URL</label>
|
||||
<input type="text" id="property-server-scripts">
|
||||
|
|
|
@ -713,24 +713,22 @@ function loaded() {
|
|||
EventBridge.scriptEventReceived.connect(function(data) {
|
||||
data = JSON.parse(data);
|
||||
if (data.type == "server_script_status") {
|
||||
if (!data.statusRetrieved) {
|
||||
elServerScriptStatus.innerHTML = "Failed to retrieve status";
|
||||
elServerScriptError.style.display = "none";
|
||||
elServerScriptError.value = data.errorInfo;
|
||||
elServerScriptError.style.display = data.errorInfo ? "block" : "none";
|
||||
if (data.statusRetrieved === false) {
|
||||
elServerScriptStatus.innerText = "Failed to retrieve status";
|
||||
} else if (data.isRunning) {
|
||||
if (data.status == "running") {
|
||||
elServerScriptStatus.innerHTML = "Running";
|
||||
elServerScriptError.style.display = "none";
|
||||
} else if (data.status == "error_loading_script") {
|
||||
elServerScriptStatus.innerHTML = "Error loading script";
|
||||
elServerScriptError.style.display = "block";
|
||||
} else if (data.status == "error_running_script") {
|
||||
elServerScriptStatus.innerHTML = "Error running script";
|
||||
elServerScriptError.style.display = "block";
|
||||
}
|
||||
elServerScriptError.innerHTML = data.errorInfo;;
|
||||
var ENTITY_SCRIPT_STATUS = {
|
||||
pending: "Pending",
|
||||
loading: "Loading",
|
||||
error_loading_script: "Error loading script",
|
||||
error_running_script: "Error running script",
|
||||
running: "Running",
|
||||
unloaded: "Unloaded",
|
||||
};
|
||||
elServerScriptStatus.innerText = ENTITY_SCRIPT_STATUS[data.status] || data.status;
|
||||
} else {
|
||||
elServerScriptStatus.innerHTML = "Not running";
|
||||
elServerScriptError.style.display = "none";
|
||||
elServerScriptStatus.innerText = "Not running";
|
||||
}
|
||||
} else if (data.type == "update") {
|
||||
|
||||
|
@ -1169,6 +1167,10 @@ function loaded() {
|
|||
elScriptURL.addEventListener('change', createEmitTextPropertyUpdateFunction('script'));
|
||||
elScriptTimestamp.addEventListener('change', createEmitNumberPropertyUpdateFunction('scriptTimestamp'));
|
||||
elServerScripts.addEventListener('change', createEmitTextPropertyUpdateFunction('serverScripts'));
|
||||
elServerScripts.addEventListener('change', function() {
|
||||
// invalidate the current status (so that same-same updates can still be observed visually)
|
||||
elServerScriptStatus.innerText = '[' + elServerScriptStatus.innerText + ']';
|
||||
});
|
||||
|
||||
elClearUserData.addEventListener("click", function() {
|
||||
deleteJSONEditor();
|
||||
|
@ -1428,6 +1430,8 @@ function loaded() {
|
|||
}));
|
||||
});
|
||||
elReloadServerScriptsButton.addEventListener("click", function() {
|
||||
// invalidate the current status (so that same-same updates can still be observed visually)
|
||||
elServerScriptStatus.innerText = '[' + elServerScriptStatus.innerText + ']';
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "action",
|
||||
action: "reloadServerScripts"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
// note: this constant is currently duplicated in edit.js
|
||||
EDIT_SETTING = "io.highfidelity.isEditting";
|
||||
isInEditMode = function isInEditMode() {
|
||||
return Settings.getValue(EDIT_SETTING) === "false" ? false : !!Settings.getValue(EDIT_SETTING);
|
||||
return Settings.getValue(EDIT_SETTING);
|
||||
};
|
||||
|
||||
if (!Function.prototype.bind) {
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
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
|
||||
});
|
||||
|
|