diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 105bcb7cb0..f6c0e7b2e5 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -71,9 +71,13 @@ RUN mkdir "$HIFI_BASE" && \ mkdir "$HIFI_VCPKG_BASE" && \ mkdir "$HIFI_ANDROID_PRECOMPILED" -RUN git clone https://github.com/jherico/hifi.git && \ +# Checkout a relatively recent commit from the main repository and use it to cache the +# gradle and vcpkg dependencies +# This commit ID should be updated whenever someone changes the dependency list +# in cmake/ports +RUN git clone https://github.com/highfidelity/hifi.git && \ cd ~/hifi && \ - git checkout quest/build + git checkout 796bfb5d6715ff14c2e60f3ee8fac1465b7578c6 WORKDIR /home/jenkins/hifi diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 2174d7ff05..45511cf6cd 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -83,7 +83,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket"); packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, this, "handleOctreePacket"); - packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnership"); + packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "queueIncomingPacket"); packetReceiver.registerListenerForTypes({ PacketType::ReplicatedAvatarIdentity, @@ -1136,16 +1136,6 @@ void AvatarMixer::entityChange() { _dirtyHeroStatus = true; } -void AvatarMixer::handleChallengeOwnership(QSharedPointer message, SharedNodePointer senderNode) { - if (senderNode->getType() == NodeType::Agent && senderNode->getLinkedData()) { - auto clientData = static_cast(senderNode->getLinkedData()); - auto avatar = clientData->getAvatarSharedPointer(); - if (avatar) { - avatar->handleChallengeResponse(message.data()); - } - } -} - void AvatarMixer::aboutToFinish() { DependencyManager::destroy(); DependencyManager::destroy(); diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 93dc755f51..10dff5e8a4 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -65,7 +65,6 @@ private slots: void domainSettingsRequestComplete(); void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); void handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode); - void handleChallengeOwnership(QSharedPointer message, SharedNodePointer senderNode); void start(); private: diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 8081c77ee8..1195f0d801 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -74,6 +74,9 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData case PacketType::BulkAvatarTraitsAck: processBulkAvatarTraitsAckMessage(*packet); break; + case PacketType::ChallengeOwnership: + _avatar->processChallengeResponse(*packet); + break; default: Q_UNREACHABLE(); } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index d874100ca2..46ca51219d 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -157,11 +157,6 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis ++simpleReceivedIt; } - if (bytesWritten > 0 && sendingAvatar->isCertifyFailed()) { - // Resend identity packet if certification failed: - sendingAvatar->setNeedsIdentityUpdate(); - } - // enumerate the received instanced trait versions auto instancedReceivedIt = lastReceivedVersions.instancedCBegin(); while (instancedReceivedIt != lastReceivedVersions.instancedCEnd()) { diff --git a/assignment-client/src/avatars/MixerAvatar.cpp b/assignment-client/src/avatars/MixerAvatar.cpp index 6d6358ddf5..9be93dad40 100644 --- a/assignment-client/src/avatars/MixerAvatar.cpp +++ b/assignment-client/src/avatars/MixerAvatar.cpp @@ -27,13 +27,33 @@ #include "ClientTraitsHandler.h" #include "AvatarLogging.h" -MixerAvatar::~MixerAvatar() { - if (_challengeTimeout) { - _challengeTimeout->deleteLater(); - } +MixerAvatar::MixerAvatar() { + static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s + + _challengeTimer.setSingleShot(true); + _challengeTimer.setInterval(CHALLENGE_TIMEOUT_MS); + + _challengeTimer.callOnTimeout(this, [this]() { + if (_verifyState == challengeClient) { + _pendingEvent = false; + _verifyState = verificationFailed; + _needsIdentityUpdate = true; + qCDebug(avatars) << "Dynamic verification TIMED-OUT for " << getDisplayName() << getSessionUUID(); + } else { + qCDebug(avatars) << "Ignoring timeout of avatar challenge"; + } + }); + +} + +const char* MixerAvatar::stateToName(VerifyState state) { + return QMetaEnum::fromType().valueToKey(state); } void MixerAvatar::fetchAvatarFST() { + if (_verifyState >= requestingFST && _verifyState <= challengeClient) { + qCDebug(avatars) << "WARNING: Avatar verification restarted; old state:" << stateToName(_verifyState); + } _verifyState = nonCertified; _pendingEvent = false; @@ -80,7 +100,7 @@ void MixerAvatar::fetchAvatarFST() { void MixerAvatar::fstRequestComplete() { ResourceRequest* fstRequest = static_cast(QObject::sender()); QMutexLocker certifyLocker(&_avatarCertifyLock); - if (fstRequest == _avatarRequest) { + if (_verifyState == requestingFST && fstRequest == _avatarRequest) { auto result = fstRequest->getResult(); if (result != ResourceRequest::Success) { _verifyState = error; @@ -90,11 +110,11 @@ void MixerAvatar::fstRequestComplete() { _verifyState = receivedFST; _pendingEvent = true; } - _avatarRequest->deleteLater(); _avatarRequest = nullptr; } else { - qCDebug(avatars) << "Incorrect request for" << getDisplayName(); + qCDebug(avatars) << "Incorrect or outdated FST request for" << getDisplayName(); } + fstRequest->deleteLater(); } bool MixerAvatar::generateFSTHash() { @@ -108,7 +128,7 @@ bool MixerAvatar::generateFSTHash() { return true; } -bool MixerAvatar::validateFSTHash(const QString& publicKey) { +bool MixerAvatar::validateFSTHash(const QString& publicKey) const { // Guess we should refactor this stuff into a Authorization namespace ... return EntityItemProperties::verifySignature(publicKey, _certificateHash, QByteArray::fromBase64(_certificateIdFromFST.toUtf8())); @@ -171,7 +191,9 @@ void MixerAvatar::ownerRequestComplete() { QMutexLocker certifyLocker(&_avatarCertifyLock); QNetworkReply* networkReply = static_cast(QObject::sender()); - if (networkReply->error() == QNetworkReply::NoError) { + if (_verifyState != requestingOwner) { + qCDebug(avatars) << "WARNING: outdated avatar-owner information received in state" << stateToName(_verifyState); + } else if (networkReply->error() == QNetworkReply::NoError) { _dynamicMarketResponse = networkReply->readAll(); _verifyState = ownerResponse; _pendingEvent = true; @@ -259,7 +281,6 @@ void MixerAvatar::processCertifyEvents() { } sendOwnerChallenge(); _verifyState = challengeClient; - _pendingEvent = true; } else { _verifyState = error; qCDebug(avatars) << "Get owner status - couldn't parse response for" << getSessionUUID() @@ -273,46 +294,14 @@ void MixerAvatar::processCertifyEvents() { break; } - case challengeResponse: - { - if (_challengeResponse.length() < 8) { - _verifyState = error; - _pendingEvent = false; - break; - } - - int avatarIDLength; - int signedNonceLength; - { - QDataStream responseStream(_challengeResponse); - responseStream.setByteOrder(QDataStream::LittleEndian); - responseStream >> avatarIDLength >> signedNonceLength; - } - QByteArray avatarID(_challengeResponse.data() + 2 * sizeof(int), avatarIDLength); - QByteArray signedNonce(_challengeResponse.data() + 2 * sizeof(int) + avatarIDLength, signedNonceLength); - - bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash, - QByteArray::fromBase64(signedNonce)); - _verifyState = challengeResult ? verificationSucceeded : verificationFailed; - _needsIdentityUpdate = true; - if (_verifyState == verificationFailed) { - qCDebug(avatars) << "Dynamic verification FAILED for " << getDisplayName() << getSessionUUID(); - } else { - qCDebug(avatars) << "Dynamic verification SUCCEEDED for " << getDisplayName() << getSessionUUID(); - } - _pendingEvent = false; - break; - } - case requestingOwner: - case challengeClient: { // Qt networking done on this thread: QCoreApplication::processEvents(); break; } default: - qCDebug(avatars) << "Unexpected verify state" << _verifyState; + qCDebug(avatars) << "Unexpected verify state" << stateToName(_verifyState); break; } // close switch @@ -334,32 +323,45 @@ void MixerAvatar::sendOwnerChallenge() { QCryptographicHash nonceHash(QCryptographicHash::Sha256); nonceHash.addData(nonce); _challengeNonceHash = nonceHash.result(); - - static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s - if (_challengeTimeout) { - _challengeTimeout->deleteLater(); - } - _challengeTimeout = new QTimer(); - _challengeTimeout->setInterval(CHALLENGE_TIMEOUT_MS); - _challengeTimeout->setSingleShot(true); - _challengeTimeout->connect(_challengeTimeout, &QTimer::timeout, this, [this]() { - if (_verifyState == challengeClient) { - _pendingEvent = false; - _verifyState = verificationFailed; - _needsIdentityUpdate = true; - qCDebug(avatars) << "Dynamic verification TIMED-OUT for " << getDisplayName() << getSessionUUID(); - } - }); - _challengeTimeout->start(); + _pendingEvent = false; + + // QTimer::start is a set of overloaded functions. + QMetaObject::invokeMethod(&_challengeTimer, static_cast(&QTimer::start)); } -void MixerAvatar::handleChallengeResponse(ReceivedMessage* response) { +void MixerAvatar::processChallengeResponse(ReceivedMessage& response) { QByteArray avatarID; - QByteArray encryptedNonce; QMutexLocker certifyLocker(&_avatarCertifyLock); + QMetaObject::invokeMethod(&_challengeTimer, &QTimer::stop); if (_verifyState == challengeClient) { - _challengeResponse = response->readAll(); - _verifyState = challengeResponse; - _pendingEvent = true; + QByteArray responseData = response.readAll(); + if (responseData.length() < 8) { + _verifyState = error; + qCDebug(avatars) << "Avatar challenge response packet too small, length:" << responseData.length(); + return; + } + + int avatarIDLength; + int signedNonceLength; + { + QDataStream responseStream(responseData); + responseStream.setByteOrder(QDataStream::LittleEndian); + responseStream >> avatarIDLength >> signedNonceLength; + } + QByteArray avatarID(responseData.data() + 2 * sizeof(int), avatarIDLength); + QByteArray signedNonce(responseData.data() + 2 * sizeof(int) + avatarIDLength, signedNonceLength); + + bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash, + QByteArray::fromBase64(signedNonce)); + _verifyState = challengeResult ? verificationSucceeded : verificationFailed; + _needsIdentityUpdate = true; + if (_verifyState == verificationFailed) { + qCDebug(avatars) << "Dynamic verification FAILED for " << getDisplayName() << getSessionUUID(); + } else { + qCDebug(avatars) << "Dynamic verification SUCCEEDED for" << getDisplayName() << getSessionUUID(); + } + + } else { + qCDebug(avatars) << "WARNING: Unexpected avatar challenge-response in state" << stateToName(_verifyState); } } diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h index e8d9c959db..2ef4d16dc4 100644 --- a/assignment-client/src/avatars/MixerAvatar.h +++ b/assignment-client/src/avatars/MixerAvatar.h @@ -9,8 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// Avatar class for use within the avatar mixer - encapsulates data required only for -// sorting priorities within the mixer. +// Avatar class for use within the avatar mixer - includes avatar-verification state. #ifndef hifi_MixerAvatar_h #define hifi_MixerAvatar_h @@ -20,8 +19,10 @@ class ResourceRequest; class MixerAvatar : public AvatarData { + Q_OBJECT public: - ~MixerAvatar(); + MixerAvatar(); + bool getNeedsHeroCheck() const { return _needsHeroCheck; } void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; } @@ -31,15 +32,18 @@ public: void setNeedsIdentityUpdate(bool value = true) { _needsIdentityUpdate = value; } void processCertifyEvents(); - void handleChallengeResponse(ReceivedMessage* response); + void processChallengeResponse(ReceivedMessage& response); + + // Avatar certification/verification: + enum VerifyState { + nonCertified, requestingFST, receivedFST, staticValidation, requestingOwner, ownerResponse, + challengeClient, verified, verificationFailed, verificationSucceeded, error + }; + Q_ENUM(VerifyState) private: bool _needsHeroCheck { false }; - - // Avatar certification/verification: - enum VerifyState { nonCertified, requestingFST, receivedFST, staticValidation, requestingOwner, ownerResponse, - challengeClient, challengeResponse, verified, verificationFailed, verificationSucceeded, error }; - Q_ENUM(VerifyState); + static const char* stateToName(VerifyState state); VerifyState _verifyState { nonCertified }; std::atomic _pendingEvent { false }; QMutex _avatarCertifyLock; @@ -53,12 +57,11 @@ private: QString _dynamicMarketResponse; QString _ownerPublicKey; QByteArray _challengeNonceHash; - QByteArray _challengeResponse; - QTimer* _challengeTimeout { nullptr }; + QTimer _challengeTimer; bool _needsIdentityUpdate { false }; bool generateFSTHash(); - bool validateFSTHash(const QString& publicKey); + bool validateFSTHash(const QString& publicKey) const; QByteArray canonicalJson(const QString fstFile); void sendOwnerChallenge(); diff --git a/cmake/compiler.cmake b/cmake/compiler.cmake index aaca33ab1c..45cef27727 100644 --- a/cmake/compiler.cmake +++ b/cmake/compiler.cmake @@ -34,7 +34,7 @@ if (WIN32) list(APPEND CMAKE_PREFIX_PATH "${WINDOW_SDK_PATH}") # /wd4351 disables warning C4351: new behavior: elements of array will be default initialized - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4351") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP${HIFI_MAX_BUILD_CORES} /wd4351") # /LARGEADDRESSAWARE enables 32-bit apps to use more than 2GB of memory. # Caveats: http://stackoverflow.com/questions/2288728/drawbacks-of-using-largeaddressaware-for-32-bit-windows-executables # TODO: Remove when building 64-bit. diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index f8cff9f773..e3369e3afb 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -192,12 +192,12 @@ macro(SET_PACKAGING_PARAMETERS) # shortcut names if (PRODUCTION_BUILD) - set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface") + set(INTERFACE_SHORTCUT_NAME "High Fidelity") set(CONSOLE_SHORTCUT_NAME "Console") set(SANDBOX_SHORTCUT_NAME "Sandbox") set(APP_USER_MODEL_ID "com.highfidelity.console") else () - set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface - ${BUILD_VERSION_NO_SHA}") + set(INTERFACE_SHORTCUT_NAME "High Fidelity - ${BUILD_VERSION_NO_SHA}") set(CONSOLE_SHORTCUT_NAME "Console - ${BUILD_VERSION_NO_SHA}") set(SANDBOX_SHORTCUT_NAME "Sandbox - ${BUILD_VERSION_NO_SHA}") endif () diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 09438b31bc..74ad014b53 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1746,8 +1746,8 @@ void DomainServer::nodePingMonitor() { } void DomainServer::processOctreeDataPersistMessage(QSharedPointer message) { - qDebug() << "Received octree data persist message"; auto data = message->readAll(); + qDebug() << "Received octree data persist message" << (data.size() / 1000) << "kbytes."; auto filePath = getEntitiesFilePath(); QDir dir(getEntitiesDirPath()); @@ -1759,12 +1759,16 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointer width + delegate: Item { + width: tabTitleText.paintedWidth + 32 + height: parent.height + + HifiStylesUit.GraphikRegular { + id: tabTitleText + color: simplifiedUI.colors.text.white + anchors.fill: parent + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: model.tabTitle + size: 24 + } + + MouseArea { + anchors.fill: parent + onClicked: { + root.activeTabView = model.tabViewName; + } + } + } + } + } + + Item { + id: tabViewContainers + anchors.top: tabContainer.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + + HelpControls.HelpControls { + id: controlsTabViewContainer + visible: activeTabView === "controlsTabView" + anchors.fill: parent + } + + HelpSupport.HelpSupport { + id: supportTabViewContainer + visible: activeTabView === "supportTabView" + anchors.fill: parent + } + + HelpFAQ.HelpFAQ { + id: faqTabViewContainer + visible: activeTabView === "faqTabView" + anchors.fill: parent + + onSendToScript: { + root.sendToScript(message); + } + } + + HelpAbout.HelpAbout { + id: aboutTabViewContainer + visible: activeTabView === "aboutTabView" + anchors.fill: parent + } + + SimplifiedControls.VerticalScrollBar { + parent: { + if (activeTabView === "controlsTabView") { + controlsTabViewContainer + } else if (activeTabView === "supportTabView") { + supportTabViewContainer + } else if (activeTabView === "faqTabView") { + faqTabViewContainer + } else if (activeTabView === "aboutTabView") { + aboutTabViewContainer + } + } + } + } + + + function fromScript(message) { + switch (message.method) { + default: + console.log('HelpApp.qml: Unrecognized message from JS'); + break; + } + } + signal sendToScript(var message); +} diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/About.qml b/interface/resources/qml/hifi/simplifiedUI/helpApp/about/HelpAbout.qml similarity index 84% rename from interface/resources/qml/hifi/simplifiedUI/settingsApp/about/About.qml rename to interface/resources/qml/hifi/simplifiedUI/helpApp/about/HelpAbout.qml index 632f137339..4a36232029 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/About.qml +++ b/interface/resources/qml/hifi/simplifiedUI/helpApp/about/HelpAbout.qml @@ -1,7 +1,7 @@ // -// About.qml +// HelpAbout.qml // -// Created by Zach Fox on 2019-06-18 +// Created by Zach Fox on 2019-08-07 // Copyright 2019 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -42,6 +42,21 @@ Flickable { } + Image { + id: accent + source: "../images/accent3.svg" + anchors.left: parent.left + anchors.top: parent.top + width: 83 + height: 156 + transform: Scale { + xScale: -1 + origin.x: accent.width / 2 + origin.y: accent.height / 2 + } + } + + ColumnLayout { id: aboutColumnLayout anchors.left: parent.left @@ -50,16 +65,6 @@ Flickable { anchors.rightMargin: 26 anchors.top: parent.top spacing: 0 - - Image { - source: "images/logo.png" - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 16 - Layout.preferredWidth: 160 - Layout.preferredHeight: 120 - fillMode: Image.PreserveAspectFit - mipmap: true - } ColumnLayout { id: platformInfoContainer @@ -68,9 +73,31 @@ Flickable { spacing: 0 HifiStylesUit.GraphikSemiBold { + text: "About Your Configuration" + Layout.preferredWidth: parent.width + Layout.topMargin: 16 + Layout.bottomMargin: 8 + height: paintedHeight + size: 22 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + } + + HifiStylesUit.GraphikRegular { + text: "Use the button below to get a copy to share with us." + Layout.preferredWidth: parent.width + Layout.bottomMargin: 8 + height: paintedHeight + size: 18 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + } + + HifiStylesUit.GraphikRegular { text: "Version " + Window.checkVersion() - Layout.alignment: Qt.AlignHCenter - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width + Layout.topMargin: 8 + Layout.bottomMargin: 8 height: paintedHeight size: 16 color: simplifiedUI.colors.text.white @@ -79,7 +106,7 @@ Flickable { HifiStylesUit.GraphikSemiBold { text: "Platform Info" - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width Layout.topMargin: 8 Layout.bottomMargin: 8 height: paintedHeight @@ -90,7 +117,7 @@ Flickable { HifiStylesUit.GraphikRegular { text: "Computer Vendor/Model:" - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 16 color: simplifiedUI.colors.text.white @@ -113,7 +140,7 @@ Flickable { HifiStylesUit.GraphikRegular { text: "Profiled Platform Tier: " + PlatformInfo.getTierProfiled() - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 16 color: simplifiedUI.colors.text.white @@ -122,7 +149,7 @@ Flickable { HifiStylesUit.GraphikRegular { text: "OS Type: " + PlatformInfo.getOperatingSystemType() - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 16 color: simplifiedUI.colors.text.white @@ -131,7 +158,7 @@ Flickable { HifiStylesUit.GraphikRegular { text: "CPU:" - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 16 color: simplifiedUI.colors.text.white @@ -150,7 +177,7 @@ Flickable { HifiStylesUit.GraphikRegular { text: "# CPUs: " + PlatformInfo.getNumCPUs() - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 16 color: simplifiedUI.colors.text.white @@ -159,7 +186,7 @@ Flickable { HifiStylesUit.GraphikRegular { text: "# CPU Cores: " + PlatformInfo.getNumLogicalCores() - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 16 color: simplifiedUI.colors.text.white @@ -168,7 +195,7 @@ Flickable { HifiStylesUit.GraphikRegular { text: "RAM: " + PlatformInfo.getTotalSystemMemoryMB() + " MB" - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 16 color: simplifiedUI.colors.text.white @@ -177,7 +204,7 @@ Flickable { HifiStylesUit.GraphikRegular { text: "GPU: " - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 16 color: simplifiedUI.colors.text.white @@ -196,7 +223,7 @@ Flickable { HifiStylesUit.GraphikRegular { text: "VR Hand Controllers: " + (PlatformInfo.hasRiftControllers() ? "Rift" : (PlatformInfo.hasViveControllers() ? "Vive" : "None")) - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 16 color: simplifiedUI.colors.text.white @@ -217,9 +244,7 @@ Flickable { interactive: false delegate: Item { Component.onCompleted: { - if (HMD.active && selectedHMD) { - audioInputDevices.selectedInputDeviceName = model.devicename - } else if (!HMD.active && selectedDesktop) { + if ((HMD.active && selectedHMD) || (!HMD.active && selectedDesktop)) { audioInputDevices.selectedInputDeviceName = model.devicename } } @@ -228,7 +253,7 @@ Flickable { HifiStylesUit.GraphikRegular { text: "Audio Input: " + audioInputDevices.selectedInputDeviceName - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 16 color: simplifiedUI.colors.text.white @@ -250,9 +275,7 @@ Flickable { interactive: false delegate: Item { Component.onCompleted: { - if (HMD.active && selectedHMD) { - audioOutputDevices.selectedOutputDeviceName = model.devicename - } else if (!HMD.active && selectedDesktop) { + if ((HMD.active && selectedHMD) || (!HMD.active && selectedDesktop)) { audioOutputDevices.selectedOutputDeviceName = model.devicename } } @@ -261,7 +284,7 @@ Flickable { HifiStylesUit.GraphikRegular { text: "Audio Output: " + audioOutputDevices.selectedOutputDeviceName - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 16 color: simplifiedUI.colors.text.white diff --git a/interface/resources/qml/hifi/simplifiedUI/helpApp/controls/ControlsTable.qml b/interface/resources/qml/hifi/simplifiedUI/helpApp/controls/ControlsTable.qml new file mode 100644 index 0000000000..b647d5ca24 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/helpApp/controls/ControlsTable.qml @@ -0,0 +1,779 @@ +// +// ControlsTable.qml +// +// Created by Zach Fox on 2019-08-16 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.10 +import QtQuick.Layouts 1.3 +import stylesUit 1.0 as HifiStylesUit + +Item { + id: controlsTableRoot + property int column1Width: 80 + property int column2Width: width - column1Width + property int rowHeight: 31 + property int rowPadding: 8 + property int mainTextSize: 18 + property int subTextSize: 14 + property color separatorColor: "#CCCCCC" + Layout.preferredHeight: controlsTableColumnLayout.height + + // Top separator + Rectangle { + anchors.top: controlsTableColumnLayout.top + anchors.left: controlsTableColumnLayout.left + width: parent.width + height: 1 + color: controlsTableRoot.separatorColor + } + + // Right separator + Rectangle { + anchors.top: controlsTableColumnLayout.top + anchors.right: controlsTableColumnLayout.right + width: 1 + height: controlsTableColumnLayout.height + color: controlsTableRoot.separatorColor + } + + // Bottom separator + Rectangle { + anchors.top: controlsTableColumnLayout.top + anchors.topMargin: controlsTableRoot.height + anchors.left: controlsTableColumnLayout.left + width: parent.width + height: 1 + color: controlsTableRoot.separatorColor + } + + // Left separator + Rectangle { + anchors.top: controlsTableColumnLayout.top + anchors.left: controlsTableColumnLayout.left + width: 1 + height: controlsTableColumnLayout.height + color: controlsTableRoot.separatorColor + } + + ColumnLayout { + id: controlsTableColumnLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: 0 + + Row { + Layout.preferredWidth: parent.width + Layout.preferredHeight: controlsTableRoot.rowHeight + + Item { + width: controlsTableRoot.column1Width + height: parent.height + + Image { + source: "images/rightClick.svg" + anchors.centerIn: parent + width: 24 + height: 24 + mipmap: true + fillMode: Image.PreserveAspectFit + } + + Rectangle { + width: 1 + height: parent.height + color: controlsTableRoot.separatorColor + anchors.right: parent.right + anchors.top: parent.top + } + } + + // Spacer + Item { + width: controlsTableRoot.rowPadding + height: parent.height + } + + Row { + width: controlsTableRoot.column2Width + height: parent.height + spacing: controlsTableRoot.rowPadding + + HifiStylesUit.GraphikRegular { + id: cameraText + text: "Camera" + width: paintedWidth + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + + HifiStylesUit.GraphikRegular { + text: "Right-click and drag to look around" + width: parent.width - cameraText.width - parent.spacing - controlsTableRoot.rowPadding + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + size: controlsTableRoot.subTextSize + color: Qt.rgba(255, 255, 255, 0.5) + } + } + } + + // Bottom separator + Rectangle { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 1 + color: controlsTableRoot.separatorColor + } + + + + + + Row { + Layout.preferredWidth: parent.width + Layout.preferredHeight: controlsTableRoot.rowHeight + + Item { + width: controlsTableRoot.column1Width + height: parent.height + + HifiStylesUit.GraphikRegular { + text: "W / ↑" + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + + Rectangle { + width: 1 + height: parent.height + color: controlsTableRoot.separatorColor + anchors.right: parent.right + anchors.top: parent.top + } + } + + // Spacer + Item { + width: controlsTableRoot.rowPadding + height: parent.height + } + + HifiStylesUit.GraphikRegular { + text: "Walk Forward" + width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2 + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + } + + // Bottom separator + Rectangle { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 1 + color: controlsTableRoot.separatorColor + } + + + + + + Row { + Layout.preferredWidth: parent.width + Layout.preferredHeight: controlsTableRoot.rowHeight + + Item { + width: controlsTableRoot.column1Width + height: parent.height + + HifiStylesUit.GraphikRegular { + text: "S / ↓" + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + + Rectangle { + width: 1 + height: parent.height + color: controlsTableRoot.separatorColor + anchors.right: parent.right + anchors.top: parent.top + } + } + + // Spacer + Item { + width: controlsTableRoot.rowPadding + height: parent.height + } + + HifiStylesUit.GraphikRegular { + text: "Walk Backward" + width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2 + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + } + + // Bottom separator + Rectangle { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 1 + color: controlsTableRoot.separatorColor + } + + + + + + Row { + Layout.preferredWidth: parent.width + Layout.preferredHeight: controlsTableRoot.rowHeight + + Item { + width: controlsTableRoot.column1Width + height: parent.height + + HifiStylesUit.GraphikRegular { + text: "A / ←" + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + + Rectangle { + width: 1 + height: parent.height + color: controlsTableRoot.separatorColor + anchors.right: parent.right + anchors.top: parent.top + } + } + + // Spacer + Item { + width: controlsTableRoot.rowPadding + height: parent.height + } + + HifiStylesUit.GraphikRegular { + text: "Turn Left" + width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2 + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + } + + // Bottom separator + Rectangle { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 1 + color: controlsTableRoot.separatorColor + } + + + + + + Row { + Layout.preferredWidth: parent.width + Layout.preferredHeight: controlsTableRoot.rowHeight + + Item { + width: controlsTableRoot.column1Width + height: parent.height + + HifiStylesUit.GraphikRegular { + text: "D / →" + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + + Rectangle { + width: 1 + height: parent.height + color: controlsTableRoot.separatorColor + anchors.right: parent.right + anchors.top: parent.top + } + } + + // Spacer + Item { + width: controlsTableRoot.rowPadding + height: parent.height + } + + HifiStylesUit.GraphikRegular { + text: "Turn Right" + width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2 + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + } + + // Bottom separator + Rectangle { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 1 + color: controlsTableRoot.separatorColor + } + + + + + + Row { + Layout.preferredWidth: parent.width + Layout.preferredHeight: controlsTableRoot.rowHeight + + Item { + width: controlsTableRoot.column1Width + height: parent.height + + HifiStylesUit.GraphikRegular { + text: "Q" + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + + Rectangle { + width: 1 + height: parent.height + color: controlsTableRoot.separatorColor + anchors.right: parent.right + anchors.top: parent.top + } + } + + // Spacer + Item { + width: controlsTableRoot.rowPadding + height: parent.height + } + + HifiStylesUit.GraphikRegular { + text: "Sidestep Left" + width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2 + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + } + + // Bottom separator + Rectangle { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 1 + color: controlsTableRoot.separatorColor + } + + + + + + Row { + Layout.preferredWidth: parent.width + Layout.preferredHeight: controlsTableRoot.rowHeight + + Item { + width: controlsTableRoot.column1Width + height: parent.height + + HifiStylesUit.GraphikRegular { + text: "E" + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + + Rectangle { + width: 1 + height: parent.height + color: controlsTableRoot.separatorColor + anchors.right: parent.right + anchors.top: parent.top + } + } + + // Spacer + Item { + width: controlsTableRoot.rowPadding + height: parent.height + } + + HifiStylesUit.GraphikRegular { + text: "Sidestep Right" + width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2 + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + } + + // Bottom separator + Rectangle { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 1 + color: controlsTableRoot.separatorColor + } + + + + + + Row { + Layout.preferredWidth: parent.width + Layout.preferredHeight: controlsTableRoot.rowHeight + + Item { + width: controlsTableRoot.column1Width + height: parent.height + + HifiStylesUit.GraphikRegular { + text: "Shift" + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + + Rectangle { + width: 1 + height: parent.height + color: controlsTableRoot.separatorColor + anchors.right: parent.right + anchors.top: parent.top + } + } + + // Spacer + Item { + width: controlsTableRoot.rowPadding + height: parent.height + } + + Row { + width: controlsTableRoot.column2Width + height: parent.height + spacing: controlsTableRoot.rowPadding + + HifiStylesUit.GraphikRegular { + id: runText + text: "Hold + Direction to Run" + width: paintedWidth + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + + HifiStylesUit.GraphikRegular { + text: "Example: Shift + W" + width: parent.width - runText.width - parent.spacing - controlsTableRoot.rowPadding + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + size: controlsTableRoot.subTextSize + color: Qt.rgba(255, 255, 255, 0.5) + } + } + } + + // Bottom separator + Rectangle { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 1 + color: controlsTableRoot.separatorColor + } + + + + + + Row { + Layout.preferredWidth: parent.width + Layout.preferredHeight: controlsTableRoot.rowHeight + + Item { + width: controlsTableRoot.column1Width + height: parent.height + + HifiStylesUit.GraphikRegular { + text: "Space" + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + + Rectangle { + width: 1 + height: parent.height + color: controlsTableRoot.separatorColor + anchors.right: parent.right + anchors.top: parent.top + } + } + + // Spacer + Item { + width: controlsTableRoot.rowPadding + height: parent.height + } + + Row { + width: controlsTableRoot.column2Width + height: parent.height + spacing: controlsTableRoot.rowPadding + + HifiStylesUit.GraphikRegular { + id: jumpText + text: "Jump / Stand Up" + width: paintedWidth + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + + HifiStylesUit.GraphikRegular { + text: "Stand Up only while seated" + width: parent.width - jumpText.width - parent.spacing - controlsTableRoot.rowPadding + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + size: controlsTableRoot.subTextSize + color: Qt.rgba(255, 255, 255, 0.5) + } + } + } + + // Bottom separator + Rectangle { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 1 + color: controlsTableRoot.separatorColor + } + + + + + + Row { + Layout.preferredWidth: parent.width + Layout.preferredHeight: controlsTableRoot.rowHeight + + Item { + width: controlsTableRoot.column1Width + height: parent.height + + HifiStylesUit.GraphikRegular { + text: "1" + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + + Rectangle { + width: 1 + height: parent.height + color: controlsTableRoot.separatorColor + anchors.right: parent.right + anchors.top: parent.top + } + } + + // Spacer + Item { + width: controlsTableRoot.rowPadding + height: parent.height + } + + HifiStylesUit.GraphikRegular { + text: "1st Person View" + width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2 + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + } + + // Bottom separator + Rectangle { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 1 + color: controlsTableRoot.separatorColor + } + + + + + + Row { + Layout.preferredWidth: parent.width + Layout.preferredHeight: controlsTableRoot.rowHeight + + Item { + width: controlsTableRoot.column1Width + height: parent.height + + HifiStylesUit.GraphikRegular { + text: "2" + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + + Rectangle { + width: 1 + height: parent.height + color: controlsTableRoot.separatorColor + anchors.right: parent.right + anchors.top: parent.top + } + } + + // Spacer + Item { + width: controlsTableRoot.rowPadding + height: parent.height + } + + Row { + width: controlsTableRoot.column2Width + height: parent.height + spacing: controlsTableRoot.rowPadding + + HifiStylesUit.GraphikRegular { + id: mirrorText + text: "Mirror Mode" + width: paintedWidth + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + + HifiStylesUit.GraphikRegular { + text: "See your own avatar" + width: parent.width - mirrorText.width - parent.spacing - controlsTableRoot.rowPadding + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + size: controlsTableRoot.subTextSize + color: Qt.rgba(255, 255, 255, 0.5) + } + } + } + + // Bottom separator + Rectangle { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 1 + color: controlsTableRoot.separatorColor + } + + + + + + Row { + Layout.preferredWidth: parent.width + Layout.preferredHeight: controlsTableRoot.rowHeight + + Item { + width: controlsTableRoot.column1Width + height: parent.height + + HifiStylesUit.GraphikRegular { + text: "3" + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + + Rectangle { + width: 1 + height: parent.height + color: controlsTableRoot.separatorColor + anchors.right: parent.right + anchors.top: parent.top + } + } + + // Spacer + Item { + width: controlsTableRoot.rowPadding + height: parent.height + } + + HifiStylesUit.GraphikRegular { + text: "3rd Person View" + width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2 + height: parent.height + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: controlsTableRoot.mainTextSize + color: simplifiedUI.colors.text.white + } + } + } +} \ No newline at end of file diff --git a/interface/resources/qml/hifi/simplifiedUI/helpApp/controls/HelpControls.qml b/interface/resources/qml/hifi/simplifiedUI/helpApp/controls/HelpControls.qml new file mode 100644 index 0000000000..b4f7bd76c0 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/helpApp/controls/HelpControls.qml @@ -0,0 +1,95 @@ +// +// HelpControls.qml +// +// Created by Zach Fox on 2019-08-07 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import "../../simplifiedConstants" as SimplifiedConstants +import "../../simplifiedControls" as SimplifiedControls +import stylesUit 1.0 as HifiStylesUit +import QtQuick.Layouts 1.3 + +Flickable { + id: root + contentWidth: parent.width + contentHeight: controlsColumnLayout.height + clip: true + + onVisibleChanged: { + if (visible) { + root.contentX = 0; + root.contentY = 0; + } + } + + SimplifiedConstants.SimplifiedConstants { + id: simplifiedUI + } + + + Image { + id: accent + source: "../images/accent1.svg" + anchors.left: parent.left + anchors.top: parent.top + width: 83 + height: 156 + transform: Scale { + xScale: -1 + origin.x: accent.width / 2 + origin.y: accent.height / 2 + } + } + + + ColumnLayout { + id: controlsColumnLayout + anchors.left: parent.left + anchors.leftMargin: 26 + anchors.right: parent.right + anchors.rightMargin: 26 + anchors.top: parent.top + spacing: 0 + + HifiStylesUit.GraphikSemiBold { + text: "HQ Controls" + Layout.preferredWidth: parent.width + Layout.topMargin: 16 + height: paintedHeight + size: 22 + color: simplifiedUI.colors.text.white + } + + HifiStylesUit.GraphikRegular { + text: "You can use the following controls to move your avatar around your HQ:" + Layout.preferredWidth: parent.width + wrapMode: Text.Wrap + height: paintedHeight + size: 18 + color: simplifiedUI.colors.text.white + } + + ControlsTable { + Layout.topMargin: 8 + Layout.preferredWidth: parent.width + } + + SimplifiedControls.Button { + Layout.topMargin: 14 + Layout.preferredWidth: 200 + height: 32 + text: "VIEW ALL CONTROLS" + temporaryText: "Viewing!" + + onClicked: { + Qt.openUrlExternally("https://www.highfidelity.com/knowledge/get-around"); + } + } + } +} diff --git a/interface/resources/qml/hifi/simplifiedUI/helpApp/controls/images/rightClick.svg b/interface/resources/qml/hifi/simplifiedUI/helpApp/controls/images/rightClick.svg new file mode 100644 index 0000000000..6df8929a3d --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/helpApp/controls/images/rightClick.svg @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/helpApp/faq/HelpFAQ.qml b/interface/resources/qml/hifi/simplifiedUI/helpApp/faq/HelpFAQ.qml new file mode 100644 index 0000000000..81edc9ff37 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/helpApp/faq/HelpFAQ.qml @@ -0,0 +1,120 @@ +// +// HelpFAQ.qml +// +// Created by Zach Fox on 2019-08-08 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import "../../simplifiedConstants" as SimplifiedConstants +import "../../simplifiedControls" as SimplifiedControls +import stylesUit 1.0 as HifiStylesUit +import QtQuick.Layouts 1.3 +import PerformanceEnums 1.0 + +Item { + id: root + width: parent.width + height: parent.height + + + SimplifiedConstants.SimplifiedConstants { + id: simplifiedUI + } + + + Image { + id: accent + source: "../images/accent3.svg" + anchors.left: parent.left + anchors.top: parent.top + width: 83 + height: 156 + transform: Scale { + xScale: -1 + origin.x: accent.width / 2 + origin.y: accent.height / 2 + } + } + + + ColumnLayout { + id: faqColumnLayout + anchors.left: parent.left + anchors.leftMargin: 26 + anchors.right: parent.right + anchors.rightMargin: 26 + anchors.top: parent.top + spacing: 0 + + HifiStylesUit.GraphikSemiBold { + text: "FAQ" + Layout.preferredWidth: parent.width + Layout.preferredHeight: paintedHeight + Layout.topMargin: 16 + size: 22 + color: simplifiedUI.colors.text.white + } + + HifiStylesUit.GraphikRegular { + text: "You can find answers to all of our frequently asked questions by clicking the button below." + Layout.preferredWidth: parent.width + Layout.preferredHeight: paintedHeight + Layout.topMargin: 8 + size: 18 + wrapMode: Text.Wrap + color: simplifiedUI.colors.text.white + } + + SimplifiedControls.Button { + Layout.topMargin: 8 + width: 200 + height: 32 + text: "VIEW FAQ" + temporaryText: "Viewing!" + + onClicked: { + Qt.openUrlExternally("https://www.highfidelity.com/knowledge"); + } + } + + HifiStylesUit.GraphikSemiBold { + text: "Having problems with your audio?" + Layout.preferredWidth: parent.width + Layout.preferredHeight: paintedHeight + Layout.topMargin: 32 + size: 16 + color: simplifiedUI.colors.text.white + } + + HifiStylesUit.GraphikRegular { + text: "Quickly check your audio configuration and make changes to resolve any audio input/output issues." + Layout.preferredWidth: parent.width + Layout.preferredHeight: paintedHeight + Layout.topMargin: 4 + size: 18 + wrapMode: Text.Wrap + color: simplifiedUI.colors.text.white + } + + SimplifiedControls.Button { + Layout.topMargin: 8 + width: 200 + height: 32 + text: "TEST YOUR AUDIO" + + onClicked: { + root.sendToScript({ + "source": "HelpApp.qml", + "method": "goToAudioSettings" + }); + } + } + } + + signal sendToScript(var message); +} diff --git a/interface/resources/qml/hifi/simplifiedUI/helpApp/images/accent1.svg b/interface/resources/qml/hifi/simplifiedUI/helpApp/images/accent1.svg new file mode 100644 index 0000000000..885edef5ac --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/helpApp/images/accent1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/helpApp/images/accent2.svg b/interface/resources/qml/hifi/simplifiedUI/helpApp/images/accent2.svg new file mode 100644 index 0000000000..027d9bb623 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/helpApp/images/accent2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/helpApp/images/accent3.svg b/interface/resources/qml/hifi/simplifiedUI/helpApp/images/accent3.svg new file mode 100644 index 0000000000..07cc23ef1e --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/helpApp/images/accent3.svg @@ -0,0 +1,4 @@ + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/helpApp/support/HelpSupport.qml b/interface/resources/qml/hifi/simplifiedUI/helpApp/support/HelpSupport.qml new file mode 100644 index 0000000000..31b9593f09 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/helpApp/support/HelpSupport.qml @@ -0,0 +1,85 @@ +// +// HelpSupport.qml +// +// Created by Zach Fox on 2019-08-20 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import "../../simplifiedConstants" as SimplifiedConstants +import "../../simplifiedControls" as SimplifiedControls +import stylesUit 1.0 as HifiStylesUit +import QtQuick.Layouts 1.3 + +Item { + id: root + width: parent.width + height: parent.height + + + SimplifiedConstants.SimplifiedConstants { + id: simplifiedUI + } + + + Image { + id: accent + source: "../images/accent2.svg" + anchors.left: parent.left + anchors.top: parent.top + width: 83 + height: 156 + transform: Scale { + xScale: -1 + origin.x: accent.width / 2 + origin.y: accent.height / 2 + } + } + + + ColumnLayout { + anchors.left: parent.left + anchors.leftMargin: 26 + anchors.right: parent.right + anchors.rightMargin: 26 + anchors.top: parent.top + spacing: 0 + + HifiStylesUit.GraphikSemiBold { + text: "Support" + Layout.preferredWidth: parent.width + Layout.preferredHeight: paintedHeight + Layout.topMargin: 16 + size: 22 + color: simplifiedUI.colors.text.white + } + + HifiStylesUit.GraphikRegular { + text: "You can quickly get the support that you need by clicking the button below." + Layout.preferredWidth: parent.width + Layout.preferredHeight: paintedHeight + Layout.topMargin: 8 + size: 18 + wrapMode: Text.Wrap + color: simplifiedUI.colors.text.white + } + + SimplifiedControls.Button { + Layout.topMargin: 8 + width: 200 + height: 32 + text: "CONTACT SUPPORT" + temporaryText: "Opening browser..." + + onClicked: { + Qt.openUrlExternally("https://www.highfidelity.com/knowledge/kb-tickets/new"); + } + } + } + + signal sendToScript(var message); +} diff --git a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/InputDeviceButton.qml b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/InputDeviceButton.qml index 15f4c42d39..7f007d10df 100644 --- a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/InputDeviceButton.qml +++ b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/InputDeviceButton.qml @@ -33,6 +33,7 @@ Rectangle { readonly property string unmutedIcon: "images/mic-unmute-i.svg" readonly property string mutedIcon: "images/mic-mute-i.svg" readonly property string pushToTalkIcon: "images/mic-ptt-i.svg" + readonly property string pushToTalkClippingIcon: "images/mic-ptt-clip-i.svg" readonly property string pushToTalkMutedIcon: "images/mic-ptt-mute-i.svg" readonly property string clippingIcon: "images/mic-clip-i.svg" readonly property string gatedIcon: "images/mic-gate-i.svg" @@ -107,7 +108,7 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter anchors.rightMargin: 2 - width: pushToTalk ? 16 : (muted ? 20 : 16) + width: pushToTalk ? (clipping && pushingToTalk ? 4 : 16) : (muted ? 20 : 16) height: 22 Item { @@ -115,7 +116,7 @@ Rectangle { Image { id: image visible: false - source: (pushToTalk) ? pushToTalkIcon : muted ? mutedIcon : + source: pushToTalk ? (clipping && pushingToTalk ? pushToTalkClippingIcon : pushToTalkIcon) : muted ? mutedIcon : clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon anchors.fill: parent } @@ -170,12 +171,12 @@ Rectangle { Image { id: maskImage visible: false - source: (pushToTalk) ? pushToTalkIcon : muted ? mutedIcon : - clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon + source: image.source anchors.top: parent.top anchors.left: parent.left width: parent.width height: parent.parent.height + mipmap: true } ColorOverlay { diff --git a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-ptt-clip-i.svg b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-ptt-clip-i.svg new file mode 100644 index 0000000000..b016adb8e5 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/images/mic-ptt-clip-i.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml index a9199ff5f1..ffb4f58355 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml @@ -17,8 +17,6 @@ import "./audio" as AudioSettings import "./general" as GeneralSettings import "./vr" as VrSettings import "./dev" as DevSettings -import "./about" as AboutSettings - Rectangle { property string activeTabView: "generalTabView" id: root @@ -49,6 +47,15 @@ Rectangle { } } + onActiveTabViewChanged: { + for (var i = 0; i < tabListModel.count; i++) { + if (tabListModel.get(i).tabViewName === activeTabView) { + tabListView.currentIndex = i; + return; + } + } + } + Component.onCompleted: { root.forceActiveFocus(); } @@ -77,10 +84,6 @@ Rectangle { tabTitle: "VR" tabViewName: "vrTabView" } - ListElement { - tabTitle: "About" - tabViewName: "aboutTabView" - } ListElement { tabTitle: "Dev" tabViewName: "devTabView" @@ -138,7 +141,6 @@ Rectangle { MouseArea { anchors.fill: parent onClicked: { - tabListView.currentIndex = index; root.activeTabView = model.tabViewName; } } @@ -146,6 +148,7 @@ Rectangle { } } + Item { id: tabViewContainers anchors.top: tabContainer.bottom @@ -153,7 +156,6 @@ Rectangle { anchors.right: parent.right anchors.bottom: parent.bottom - GeneralSettings.General { id: generalTabViewContainer visible: activeTabView === "generalTabView" @@ -161,8 +163,10 @@ Rectangle { onSendNameTagInfo: { sendToScript(message); } + onSendEmoteVisible: { + sendToScript(message); + } } - AudioSettings.Audio { id: audioTabViewContainer @@ -182,12 +186,6 @@ Rectangle { anchors.fill: parent } - AboutSettings.About { - id: aboutTabViewContainer - visible: activeTabView === "aboutTabView" - anchors.fill: parent - } - SimplifiedControls.VerticalScrollBar { parent: { if (activeTabView === "generalTabView") { @@ -198,8 +196,6 @@ Rectangle { vrTabViewContainer } else if (activeTabView === "devTabView") { devTabViewContainer - } else if (activeTabView === "aboutTabView") { - aboutTabViewContainer } } } @@ -207,7 +203,24 @@ Rectangle { function fromScript(message) { + if (message.source !== "simplifiedUI.js") { + return; + } + switch (message.method) { + case "goToSettingsTab": + var tabToGoTo = message.data.settingsTab; + switch (tabToGoTo) { + case "audio": + activeTabView = "audioTabView"; + break; + + default: + console.log("A message was sent to the Settings window to change tabs, but an invalid tab was specified."); + break; + } + break; + default: console.log('SettingsApp.qml: Unrecognized message from JS'); break; diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/images/logo.png b/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/images/logo.png deleted file mode 100644 index d480da86dd..0000000000 Binary files a/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/images/logo.png and /dev/null differ diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml index bfc0bc5200..108016ef8c 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml @@ -174,7 +174,7 @@ Flickable { HifiStylesUit.GraphikSemiBold { id: micControlsTitle text: "Default Mute Controls" - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 22 color: simplifiedUI.colors.text.white @@ -244,7 +244,7 @@ Flickable { HifiStylesUit.GraphikSemiBold { id: inputDeviceTitle text: "Which input device?" - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 22 color: simplifiedUI.colors.text.white @@ -340,7 +340,7 @@ Flickable { HifiStylesUit.GraphikSemiBold { id: outputDeviceTitle text: "Which output device?" - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 22 color: simplifiedUI.colors.text.white diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/dev/Dev.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/dev/Dev.qml index fe623e5d78..4d0589c1e1 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/dev/Dev.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/dev/Dev.qml @@ -68,7 +68,7 @@ Flickable { HifiStylesUit.GraphikSemiBold { id: uiControlsTitle text: "User Interface" - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 22 color: simplifiedUI.colors.text.white @@ -77,7 +77,7 @@ Flickable { HifiStylesUit.GraphikRegular { id: uiControlsSubtitle text: "You'll have to restart Interface after changing either of these settings. If you don't get any Toolbar apps back after restarting, run defaultScripts.js manually." - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 16 color: simplifiedUI.colors.text.white diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml index af7e9ba4ae..9e58a0aa98 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml @@ -72,7 +72,7 @@ Flickable { HifiStylesUit.GraphikSemiBold { id: avatarNameTagsTitle text: "Avatar Name Tags" - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 22 color: simplifiedUI.colors.text.white @@ -112,6 +112,49 @@ Flickable { } } + ColumnLayout { + id: emoteContainer + Layout.preferredWidth: parent.width + spacing: 0 + + HifiStylesUit.GraphikSemiBold { + id: emoteTitle + text: "Emote UI" + Layout.maximumWidth: parent.width + height: paintedHeight + size: 22 + color: simplifiedUI.colors.text.white + } + + ColumnLayout { + id: emoteSwitchGroup + Layout.preferredWidth: parent.width + Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin + + SimplifiedControls.Switch { + id: emoteSwitch + Layout.preferredHeight: 18 + Layout.preferredWidth: parent.width + labelTextOn: "Show Emote UI" + checked: Settings.getValue("simplifiedUI/emoteIndicatorVisible", true) + onClicked: { + var currentSetting = Settings.getValue("simplifiedUI/emoteIndicatorVisible", true); + Settings.setValue("simplifiedUI/emoteIndicatorVisible", !currentSetting); + } + + Connections { + target: Settings + + onValueChanged: { + if (setting === "simplifiedUI/emoteIndicatorVisible") { + emoteSwitch.checked = value; + } + } + } + } + } + } + ColumnLayout { id: performanceContainer Layout.preferredWidth: parent.width @@ -120,7 +163,7 @@ Flickable { HifiStylesUit.GraphikSemiBold { id: performanceTitle text: "Graphics Settings" - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 22 color: simplifiedUI.colors.text.white @@ -168,7 +211,7 @@ Flickable { HifiStylesUit.GraphikSemiBold { id: cameraTitle text: "Camera View" - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 22 color: simplifiedUI.colors.text.white @@ -197,7 +240,7 @@ Flickable { } } - Connections { + Connections { target: Camera onModeUpdated: { @@ -207,7 +250,7 @@ Flickable { thirdPerson.checked = true } } - } + } } } @@ -248,4 +291,5 @@ Flickable { } signal sendNameTagInfo(var message); + signal sendEmoteVisible(var message); } diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml index d7e85c7f68..5f0fbe49d5 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml @@ -77,7 +77,7 @@ Flickable { HifiStylesUit.GraphikSemiBold { id: controlsTitle text: "VR Movement Controls" - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 22 color: simplifiedUI.colors.text.white @@ -163,7 +163,7 @@ Flickable { HifiStylesUit.GraphikSemiBold { id: micControlsTitle text: "Default Mute Controls" - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 22 color: simplifiedUI.colors.text.white @@ -206,7 +206,7 @@ Flickable { HifiStylesUit.GraphikSemiBold { id: inputDeviceTitle text: "Which input device?" - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 22 color: simplifiedUI.colors.text.white @@ -302,7 +302,7 @@ Flickable { HifiStylesUit.GraphikSemiBold { id: outputDeviceTitle text: "Which output device?" - Layout.maximumWidth: parent.width + Layout.preferredWidth: parent.width height: paintedHeight size: 22 color: simplifiedUI.colors.text.white diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml index bc27dbad5f..9fff49442e 100644 --- a/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml @@ -153,6 +153,10 @@ QtObject { readonly property color background: "#474747" readonly property color contentItem: "#0198CB" } + readonly property QtObject table: QtObject { + readonly property color cellBackground: "#000000" + readonly property color textColor: "#ffffff" + } } readonly property color darkSeparator: "#595959" @@ -226,7 +230,7 @@ QtObject { readonly property int textSize: 14 } readonly property QtObject textField: QtObject { - readonly property int editPencilPadding: 6 + readonly property int rightGlyphPadding: 6 } readonly property QtObject scrollBar: QtObject { readonly property int backgroundWidth: 9 diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/qmldir b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/qmldir new file mode 100644 index 0000000000..a2b5c292b2 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/qmldir @@ -0,0 +1,2 @@ +module hifi.simplifiedUI.simplifiedConstants +SimplifiedConstants 1.0 SimplifiedConstants.qml \ No newline at end of file diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/ProgressCircle.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/ProgressCircle.qml new file mode 100644 index 0000000000..77df295395 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/ProgressCircle.qml @@ -0,0 +1,97 @@ +// From http://www.bytebau.com/progress-circle-with-qml-and-javascript/ +// ByteBau (Jörn Buchholz) @bytebau.com + +import QtQuick 2.0 +import QtQml 2.2 + +Item { + id: root + + width: size + height: size + + property int size: 200 // The size of the circle in pixel + property real arcBegin: 0 // start arc angle in degree + property real arcEnd: 360 // end arc angle in degree + property real arcOffset: 0 // rotation + property bool isPie: false // paint a pie instead of an arc + property bool showBackground: false // a full circle as a background of the arc + property real lineWidth: 20 // width of the line + property string colorCircle: "#CC3333" + property string colorBackground: "#779933" + + property alias beginAnimation: animationArcBegin.enabled + property alias endAnimation: animationArcEnd.enabled + + property int animationDuration: 200 + + onArcBeginChanged: canvas.requestPaint() + onArcEndChanged: canvas.requestPaint() + + Behavior on arcBegin { + id: animationArcBegin + enabled: true + NumberAnimation { + duration: root.animationDuration + easing.type: Easing.InOutCubic + } + } + + Behavior on arcEnd { + id: animationArcEnd + enabled: true + NumberAnimation { + duration: root.animationDuration + easing.type: Easing.OutQuad + } + } + + Canvas { + id: canvas + anchors.fill: parent + rotation: -90 + parent.arcOffset + + onPaint: { + var ctx = getContext("2d") + var x = width / 2 + var y = height / 2 + var start = Math.PI * (parent.arcBegin / 180) + var end = Math.PI * (parent.arcEnd / 180) + ctx.reset() + + if (root.isPie) { + if (root.showBackground) { + ctx.beginPath() + ctx.fillStyle = root.colorBackground + ctx.moveTo(x, y) + ctx.arc(x, y, width / 2, 0, Math.PI * 2, false) + ctx.lineTo(x, y) + ctx.fill() + } + ctx.beginPath() + ctx.fillStyle = root.colorCircle + ctx.moveTo(x, y) + // Using `width` instead of `width/2` as the argument here ensures + // that the ProgressCircle mask will cover the entirety of non-circular emoji. + ctx.arc(x, y, width, start, end, false) + ctx.lineTo(x, y) + ctx.fill() + } else { + if (root.showBackground) { + ctx.beginPath(); + ctx.arc(x, y, (width / 2) - parent.lineWidth / 2, 0, Math.PI * 2, false) + ctx.lineWidth = root.lineWidth + ctx.strokeStyle = root.colorBackground + ctx.stroke() + } + ctx.beginPath(); + ctx.arc(x, y, (width / 2) - parent.lineWidth / 2, start, end, false) + ctx.lineWidth = root.lineWidth + ctx.strokeStyle = root.colorCircle + ctx.stroke() + } + + ctx.scale(0.1, 0.2); + } + } +} \ No newline at end of file diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml index 20d45b3361..bd2b6cf538 100644 --- a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml @@ -21,6 +21,8 @@ TextField { id: simplifiedUI } + property string rightGlyph: "" + color: simplifiedUI.colors.text.white font.family: "Graphik Medium" font.pixelSize: 22 @@ -31,7 +33,7 @@ TextField { autoScroll: false hoverEnabled: true leftPadding: 0 - rightPadding: editPencil.implicitWidth + simplifiedUI.sizes.controls.textField.editPencilPadding + rightPadding: root.rightGlyph === "" ? 0 : rightGlyphItem.implicitWidth + simplifiedUI.sizes.controls.textField.rightGlyphPadding onFocusChanged: { if (focus) { @@ -59,8 +61,9 @@ TextField { } HifiStylesUit.HiFiGlyphs { - id: editPencil - text: simplifiedUI.glyphs.pencil + id: rightGlyphItem + text: root.rightGlyph + visible: rightGlyphItem.text !== "" // Text Size size: root.font.pixelSize * 1.5 // Anchors diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/qmldir b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/qmldir new file mode 100644 index 0000000000..49fadf2f21 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/qmldir @@ -0,0 +1,10 @@ +module hifi.simplifiedUI.simplifiedControls +Button 1.0 Button.qml +CheckBox 1.0 CheckBox.qml +InputPeak 1.0 InputPeak.qml +ProgressCircle 1.0 ProgressCircle.qml +RadioButton 1.0 RadioButton.qml +Slider 1.0 Slider.qml +Switch 1.0 Switch.qml +TextField 1.0 TextField.qml +VerticalScrollBar 1.0 VerticalScrollBar.qml \ No newline at end of file diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml index acabbe9485..d87431ea9c 100644 --- a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml @@ -245,6 +245,7 @@ Rectangle { width: outputDeviceButton.outputMuted ? 25 : 26 height: 22 visible: false + mipmap: true } ColorOverlay { @@ -319,6 +320,7 @@ Rectangle { anchors.centerIn: parent width: statusButton.currentStatus === "busy" ? 13 : 14 height: statusButton.currentStatus === "busy" ? 2 : 10 + mipmap: true } ColorOverlay { @@ -352,19 +354,20 @@ Rectangle { Item { id: hmdButtonContainer anchors.verticalCenter: parent.verticalCenter - anchors.right: settingsButtonContainer.left - anchors.rightMargin: 8 + anchors.right: helpButtonContainer.left + anchors.rightMargin: 3 width: 48 height: width visible: false Image { id: displayModeImage - source: HMD.active ? "./images/desktopMode.svg" : "./images/vrMode.svg" + source: HMD.active ? "images/desktopMode.svg" : "images/vrMode.svg" anchors.centerIn: parent width: HMD.active ? 25 : 26 height: HMD.active ? 22 : 14 visible: false + mipmap: true } ColorOverlay { @@ -417,6 +420,49 @@ Rectangle { } + Item { + id: helpButtonContainer + anchors.verticalCenter: parent.verticalCenter + anchors.right: settingsButtonContainer.left + anchors.rightMargin: 4 + width: 36 + height: width + + Image { + id: helpButtonImage + source: "images/questionMark.svg" + anchors.centerIn: parent + width: 13 + height: 22 + visible: false + mipmap: true + } + + ColorOverlay { + opacity: helpButtonMouseArea.containsMouse ? 1.0 : 0.7 + anchors.fill: helpButtonImage + source: helpButtonImage + color: simplifiedUI.colors.text.white + } + + MouseArea { + id: helpButtonMouseArea + anchors.fill: parent + hoverEnabled: true + onEntered: { + Tablet.playSound(TabletEnums.ButtonHover); + } + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + sendToScript({ + "source": "SimplifiedTopBar.qml", + "method": "toggleHelpApp" + }); + } + } + } + + Item { id: settingsButtonContainer @@ -428,11 +474,12 @@ Rectangle { Image { id: settingsButtonImage - source: "./images/settings.svg" + source: "images/settings.svg" anchors.centerIn: parent width: 22 height: 22 visible: false + mipmap: true } ColorOverlay { diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/images/questionMark.svg b/interface/resources/qml/hifi/simplifiedUI/topBar/images/questionMark.svg new file mode 100644 index 0000000000..eec3edb1c6 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/images/questionMark.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/interface/resources/qml/hifi/tablet/DynamicWebview.qml b/interface/resources/qml/hifi/tablet/DynamicWebview.qml new file mode 100644 index 0000000000..4343e71e64 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/DynamicWebview.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 +import "../../controls" as Controls + +Controls.WebView { + id: root + function fromScript(message) { + root.url = message.url; + } +} + + diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 1342e55b5d..fdb05c8a35 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -225,10 +225,6 @@ StackView { verticalCenter: addressLineContainer.verticalCenter; } - onFocusChanged: { - addressBarDialog.raised = focus; - } - onTextChanged: { updateLocationText(text.length > 0); } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 062a8fa9c1..b318f07d8f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -58,6 +58,7 @@ #include #include +#include #include #include #include @@ -73,6 +74,7 @@ #include #include #include +#include #include #include #include @@ -137,6 +139,7 @@ #include #include #include +#include #include #include #include @@ -257,10 +260,6 @@ extern "C" { } #endif -#ifdef Q_OS_MAC -#include "MacHelper.h" -#endif - #if defined(Q_OS_ANDROID) #include #include "AndroidHelper.h" @@ -552,13 +551,6 @@ public: return true; } - if (message->message == WM_POWERBROADCAST) { - if (message->wParam == PBT_APMRESUMEAUTOMATIC) { - qCInfo(interfaceapp) << "Waking up from sleep or hybernation."; - QMetaObject::invokeMethod(DependencyManager::get().data(), "noteAwakening", Qt::QueuedConnection); - } - } - if (message->message == WM_COPYDATA) { COPYDATASTRUCT* pcds = (COPYDATASTRUCT*)(message->lParam); QUrl url = QUrl((const char*)(pcds->lpData)); @@ -964,9 +956,12 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); -#ifdef Q_OS_MAC - DependencyManager::set(); -#endif + PlatformHelper::setup(); + + QObject::connect(PlatformHelper::instance(), &PlatformHelper::systemWillWake, [] { + QMetaObject::invokeMethod(DependencyManager::get().data(), "noteAwakening", Qt::QueuedConnection); + }); + QString setBookmarkValue = getCmdOption(argc, constArgv, "--setBookmark"); if (!setBookmarkValue.isEmpty()) { @@ -1149,7 +1144,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Graphik-SemiBold.ttf"); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Graphik-Regular.ttf"); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Graphik-Medium.ttf"); - _window->setWindowTitle("High Fidelity Interface"); + _window->setWindowTitle("High Fidelity"); Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us @@ -1172,6 +1167,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo deadlockWatchdogThread->setMainThreadID(QThread::currentThreadId()); deadlockWatchdogThread->start(); + // Pause the deadlock watchdog when we sleep, or it might + // trigger a false positive when we wake back up + auto platformHelper = PlatformHelper::instance(); + + connect(platformHelper, &PlatformHelper::systemWillSleep, [] { + DeadlockWatchdogThread::pause(); + }); + + connect(platformHelper, &PlatformHelper::systemWillWake, [] { + DeadlockWatchdogThread::resume(); + }); // Main thread timer to keep the watchdog updated QTimer* watchdogUpdateTimer = new QTimer(this); @@ -2675,6 +2681,8 @@ void Application::updateHeartbeat() const { } void Application::onAboutToQuit() { + setCrashAnnotation("shutdown", "1"); + // quickly save AvatarEntityData before the EntityTree is dismantled getMyAvatar()->saveAvatarEntityDataToSettings(); @@ -2713,6 +2721,11 @@ void Application::onAboutToQuit() { cleanupBeforeQuit(); + if (_crashOnShutdown) { + // triggered by crash menu + crash::nullDeref(); + } + getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::SHUTDOWN); } @@ -2761,7 +2774,6 @@ void Application::cleanupBeforeQuit() { } getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts - getEntities()->clear(); // Clear any queued processing (I/O, FBX/OBJ/Texture parsing) QThreadPool::globalInstance()->clear(); @@ -2869,9 +2881,7 @@ Application::~Application() { _gameWorkload.shutdown(); DependencyManager::destroy(); -#ifdef Q_OS_MAC - DependencyManager::destroy(); -#endif + PlatformHelper::shutdown(); _entityClipboard->eraseAllOctreeElements(); _entityClipboard.reset(); @@ -3141,7 +3151,7 @@ void Application::showLoginScreen() { QJsonObject loginData = {}; loginData["action"] = "login dialog popped up"; UserActivityLogger::getInstance().logAction("encourageLoginDialog", loginData); - _window->setWindowTitle("High Fidelity Interface"); + _window->setWindowTitle("High Fidelity"); } else { resumeAfterLoginDialogActionTaken(); } @@ -3276,6 +3286,9 @@ void Application::initializeUi() { } return result.toPoint(); }); + + // BUGZ-1365 - the root context should explicitly default to being unable to load local HTML content + ContextAwareProfile::restrictContext(offscreenUi->getSurfaceContext(), true); offscreenUi->resume(); #endif connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect& r){ @@ -3563,6 +3576,9 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona surfaceContext->setContextProperty("WalletScriptingInterface", DependencyManager::get().data()); surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get().data()); surfaceContext->setContextProperty("PlatformInfo", PlatformInfoScriptingInterface::getInstance()); + // This `module` context property is blank for the QML scripting interface so that we don't get log errors when importing + // certain JS files from both scripts (in the JS context) and QML (in the QML context). + surfaceContext->setContextProperty("module", ""); } } @@ -4236,12 +4252,43 @@ bool Application::event(QEvent* event) { } bool Application::eventFilter(QObject* object, QEvent* event) { + auto eventType = event->type(); - if (_aboutToQuit && event->type() != QEvent::DeferredDelete && event->type() != QEvent::Destroy) { + if (_aboutToQuit && eventType != QEvent::DeferredDelete && eventType != QEvent::Destroy) { return true; } - auto eventType = event->type(); +#if defined(Q_OS_MAC) + // On Mac OS, Cmd+LeftClick is treated as a RightClick (more specifically, it seems to + // be Cmd+RightClick without the modifier being dropped). Starting in Qt 5.12, only + // on Mac, the MouseButtonRelease event for these mouse presses is sent to the top + // level QWidgetWindow, but are not propagated further. This means that the Application + // will see a MouseButtonPress, but no MouseButtonRelease, causing the client to get + // stuck in "mouse-look." The cause of the problem is in the way QWidgetWindow processes + // events where QMouseEvent::button() is not equal to QMouseEvent::buttons(). In this case + // QMouseEvent::button() is Qt::RightButton, while QMouseEvent::buttons() is (correctly?) + // Qt::LeftButton. + // + // The change here gets around this problem by capturing these + // pseudo-RightClicks, and re-emitting them as "pure" RightClicks, where + // QMouseEvent::button() == QMouseEvent::buttons() == Qt::RightButton. + // + if (eventType == QEvent::MouseButtonPress) { + auto mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::RightButton + && mouseEvent->buttons() == Qt::LeftButton + && mouseEvent->modifiers() == Qt::MetaModifier) { + + QMouseEvent* newEvent = new QMouseEvent( + QEvent::MouseButtonPress, mouseEvent->localPos(), mouseEvent->windowPos(), + mouseEvent->screenPos(), Qt::RightButton, Qt::MouseButtons(Qt::RightButton), + mouseEvent->modifiers()); + QApplication::postEvent(object, newEvent); + return true; + } + } +#endif + if (eventType == QEvent::KeyPress || eventType == QEvent::KeyRelease || eventType == QEvent::MouseMove) { getRefreshRateManager().resetInactiveTimer(); } @@ -6252,11 +6299,13 @@ void Application::tryToEnablePhysics() { // process octree stats packets are sent in between full sends of a scene (this isn't currently true). // We keep physics disabled until we've received a full scene and everything near the avatar in that // scene is ready to compute its collision shape. - if (getMyAvatar()->isReadyForPhysics()) { + auto myAvatar = getMyAvatar(); + if (myAvatar->isReadyForPhysics()) { + myAvatar->getCharacterController()->setPhysicsEngine(_physicsEngine); _octreeProcessor.resetSafeLanding(); _physicsEnabled = true; setIsInterstitialMode(false); - getMyAvatar()->updateMotionBehaviorFromMenu(); + myAvatar->updateMotionBehaviorFromMenu(); } } } @@ -6547,7 +6596,7 @@ void Application::update(float deltaTime) { avatarManager->handleProcessedPhysicsTransaction(transaction); myAvatar->prepareForPhysicsSimulation(); - _physicsEngine->enableGlobalContactAddedCallback(myAvatar->isFlying()); + myAvatar->getCharacterController()->preSimulation(); } } @@ -6600,6 +6649,7 @@ void Application::update(float deltaTime) { { PROFILE_RANGE(simulation_physics, "MyAvatar"); + myAvatar->getCharacterController()->postSimulation(); myAvatar->harvestResultsFromPhysicsSimulation(deltaTime); } @@ -7411,7 +7461,7 @@ void Application::addingEntityWithCertificate(const QString& certificateID, cons ledger->updateLocation(certificateID, placeName); } -void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointer scriptEngine) { +void Application::registerScriptEngineWithApplicationServices(const ScriptEnginePointer& scriptEngine) { scriptEngine->setEmitScriptUpdatesFunction([this]() { SharedNodePointer entityServerNode = DependencyManager::get()->soloNodeOfType(NodeType::EntityServer); @@ -7450,9 +7500,19 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe qScriptRegisterMetaType(scriptEngine.data(), RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue); + bool clientScript = scriptEngine->isClientScript(); + #if !defined(DISABLE_QML) scriptEngine->registerGlobalObject("OffscreenFlags", getOffscreenUI()->getFlags()); - scriptEngine->registerGlobalObject("Desktop", DependencyManager::get().data()); + if (clientScript) { + scriptEngine->registerGlobalObject("Desktop", DependencyManager::get().data()); + } else { + auto desktopScriptingInterface = new DesktopScriptingInterface(nullptr, true); + scriptEngine->registerGlobalObject("Desktop", desktopScriptingInterface); + if (QThread::currentThread() != thread()) { + desktopScriptingInterface->moveToThread(thread()); + } + } #endif qScriptRegisterMetaType(scriptEngine.data(), wrapperToScriptValue, wrapperFromScriptValue); @@ -7477,7 +7537,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter); - bool clientScript = scriptEngine->isClientScript(); scriptEngine->registerFunction("OverlayWindow", clientScript ? QmlWindowClass::constructor : QmlWindowClass::restricted_constructor); #if !defined(Q_OS_ANDROID) && !defined(DISABLE_QML) scriptEngine->registerFunction("OverlayWebWindow", clientScript ? QmlWebWindowClass::constructor : QmlWebWindowClass::restricted_constructor); @@ -9489,6 +9548,14 @@ void Application::showUrlHandler(const QUrl& url) { } }); } + +// used to test "shutdown" crash annotation. +void Application::crashOnShutdown() { + qDebug() << "crashOnShutdown(), ON PURPOSE!"; + _crashOnShutdown = true; + quit(); +} + void Application::overrideEntry(){ _overrideEntry = true; } diff --git a/interface/src/Application.h b/interface/src/Application.h index 913671473d..cd867598c0 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -252,7 +252,7 @@ public: NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; } virtual controller::ScriptingInterface* getControllerScriptingInterface() { return _controllerScriptingInterface; } - virtual void registerScriptEngineWithApplicationServices(ScriptEnginePointer scriptEngine) override; + virtual void registerScriptEngineWithApplicationServices(const ScriptEnginePointer& scriptEngine) override; virtual void copyCurrentViewFrustum(ViewFrustum& viewOut) const override { copyDisplayViewFrustum(viewOut); } virtual QThread* getMainThread() override { return thread(); } @@ -497,6 +497,9 @@ public slots: bool gpuTextureMemSizeStable(); void showUrlHandler(const QUrl& url); + // used to test "shutdown" crash annotation. + void crashOnShutdown(); + private slots: void onDesktopRootItemCreated(QQuickItem* qmlContext); void onDesktopRootContextCreated(QQmlContext* qmlContext); @@ -844,5 +847,7 @@ private: bool _overrideEntry { false }; VisionSqueeze _visionSqueeze; + + bool _crashOnShutdown { false }; }; #endif // hifi_Application_h diff --git a/interface/src/MacHelper.cpp b/interface/src/MacHelper.cpp deleted file mode 100755 index 8527f02918..0000000000 --- a/interface/src/MacHelper.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// -// MacHelper.h -// interface/src -// -// Created by Howard Stearns -// Copyright 2019 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "InterfaceLogging.h" -#include "MacHelper.h" -#include - -#ifdef Q_OS_MAC -#include -#include - -// The type definitions in these variables come from IOKit, which includes a definition of Duration that conflicts with ours. -// So... we include these definitions here rather than in the .h, as the .h is included in Application.cpp which -// uses Duration. -static io_connect_t root_port; -static IONotificationPortRef notifyPortRef; -static io_object_t notifierObject; -static void* refCon; - -static void sleepHandler(void* refCon, io_service_t service, natural_t messageType, void* messageArgument) { - if (messageType == kIOMessageSystemHasPoweredOn) { - qCInfo(interfaceapp) << "Waking up from sleep or hybernation."; - QMetaObject::invokeMethod(DependencyManager::get().data(), "noteAwakening", Qt::QueuedConnection); - } -} -#endif - -MacHelper::MacHelper() { -#ifdef Q_OS_MAC - root_port = IORegisterForSystemPower(refCon, ¬ifyPortRef, sleepHandler, ¬ifierObject); - if (root_port == 0) { - qCWarning(interfaceapp) << "IORegisterForSystemPower failed"; - return; - } - CFRunLoopAddSource(CFRunLoopGetCurrent(), - IONotificationPortGetRunLoopSource(notifyPortRef), - kCFRunLoopCommonModes); -#endif -} - -MacHelper::~MacHelper() { -#ifdef Q_OS_MAC - CFRunLoopRemoveSource(CFRunLoopGetCurrent(), - IONotificationPortGetRunLoopSource(notifyPortRef), - kCFRunLoopCommonModes); - IODeregisterForSystemPower(¬ifierObject); - IOServiceClose(root_port); - IONotificationPortDestroy(notifyPortRef); -#endif -} diff --git a/interface/src/MacHelper.h b/interface/src/MacHelper.h deleted file mode 100755 index 52ad4d3e55..0000000000 --- a/interface/src/MacHelper.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// MacHelper.h -// interface/src -// -// Created by Howard Stearns -// Copyright 2019 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#pragma once - -#include "DependencyManager.h" - -class MacHelper : public Dependency { -public: - MacHelper(); - ~MacHelper(); -}; - diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 4cf78c23ee..8c6292681b 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -763,6 +763,8 @@ Menu::Menu() { action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFaultThreaded); connect(action, &QAction::triggered, qApp, []() { std::thread(crash::newFault).join(); }); + addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOnShutdown, 0, qApp, SLOT(crashOnShutdown())); + // Developer > Show Statistics addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats, 0, true); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 70687786a9..7dff264adc 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -72,6 +72,7 @@ namespace MenuOption { const QString CrashNullDereferenceThreaded = "Null Dereference (threaded)"; const QString CrashAbort = "Abort"; const QString CrashAbortThreaded = "Abort (threaded)"; + const QString CrashOnShutdown = "Crash During Shutdown"; const QString CrashOutOfBoundsVectorAccess = "Out of Bounds Vector Access"; const QString CrashOutOfBoundsVectorAccessThreaded = "Out of Bounds Vector Access (threaded)"; const QString CrashNewFault = "New Fault"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 28f6644d7f..4d1c20010c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -733,7 +733,7 @@ void MyAvatar::update(float deltaTime) { // When needed and ready, arrange to check and fix. _physicsSafetyPending = false; if (_goToSafe) { - safeLanding(_goToPosition); // no-op if already safe + safeLanding(_goToPosition); // no-op if safeLanding logic determines already safe } } @@ -2733,24 +2733,22 @@ void MyAvatar::nextAttitude(glm::vec3 position, glm::quat orientation) { void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) { glm::vec3 position; glm::quat orientation; - if (_characterController.isEnabledAndReady()) { + if (_characterController.isEnabledAndReady() && !(_characterController.needsSafeLandingSupport() || _goToPending)) { _characterController.getPositionAndOrientation(position, orientation); + setWorldVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity()); } else { position = getWorldPosition(); orientation = getWorldOrientation(); + if (_characterController.needsSafeLandingSupport() && !_goToPending) { + _characterController.resetStuckCounter(); + _physicsSafetyPending = true; + _goToSafe = true; + _goToPosition = position; + } + setWorldVelocity(getWorldVelocity() + _characterController.getFollowVelocity()); } nextAttitude(position, orientation); _bodySensorMatrix = _follow.postPhysicsUpdate(*this, _bodySensorMatrix); - - if (_characterController.isEnabledAndReady()) { - setWorldVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity()); - if (_characterController.isStuck()) { - _physicsSafetyPending = true; - _goToPosition = getWorldPosition(); - } - } else { - setWorldVelocity(getWorldVelocity() + _characterController.getFollowVelocity()); - } } QString MyAvatar::getScriptedMotorFrame() const { @@ -3588,6 +3586,8 @@ void MyAvatar::updateActionMotor(float deltaTime) { float speedGrowthTimescale = 2.0f; float speedIncreaseFactor = 1.8f * _walkSpeedScalar; motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale, 0.0f, 1.0f) * speedIncreaseFactor; + // use feedback from CharacterController to prevent tunneling under high motorspeed + motorSpeed *= _characterController.getCollisionBrakeAttenuationFactor(); const float maxBoostSpeed = sensorToWorldScale * MAX_BOOST_SPEED; if (_isPushing) { @@ -5865,8 +5865,13 @@ bool MyAvatar::endReaction(QString reactionName) { int reactionIndex = beginEndReactionNameToIndex(reactionName); if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_BEGIN_END_REACTIONS) { std::lock_guard guard(_reactionLock); - _reactionEnabledRefCounts[reactionIndex]--; - return true; + if (_reactionEnabledRefCounts[reactionIndex] > 0) { + _reactionEnabledRefCounts[reactionIndex]--; + return true; + } else { + _reactionEnabledRefCounts[reactionIndex] = 0; + return false; + } } return false; } @@ -6245,31 +6250,43 @@ void MyAvatar::setSitDriveKeysStatus(bool enabled) { } void MyAvatar::beginSit(const glm::vec3& position, const glm::quat& rotation) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "beginSit", Q_ARG(glm::vec3, position), Q_ARG(glm::quat, rotation)); + return; + } + _characterController.setSeated(true); - setCollisionsEnabled(false); + setCollisionsEnabled(false); setHMDLeanRecenterEnabled(false); // Disable movement setSitDriveKeysStatus(false); centerBody(); int hipIndex = getJointIndex("Hips"); clearPinOnJoint(hipIndex); - goToLocation(position, true, rotation, false, false); pinJoint(hipIndex, position, rotation); } void MyAvatar::endSit(const glm::vec3& position, const glm::quat& rotation) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "endSit", Q_ARG(glm::vec3, position), Q_ARG(glm::quat, rotation)); + return; + } + if (_characterController.getSeated()) { clearPinOnJoint(getJointIndex("Hips")); _characterController.setSeated(false); setCollisionsEnabled(true); setHMDLeanRecenterEnabled(true); centerBody(); - goToLocation(position, true, rotation, false, false); + slamPosition(position); + setWorldOrientation(rotation); + + // the jump key is used to exit the chair. We add a delay here to prevent + // the avatar from jumping right as they exit the chair. float TIME_BEFORE_DRIVE_ENABLED_MS = 150.0f; QTimer::singleShot(TIME_BEFORE_DRIVE_ENABLED_MS, [this]() { // Enable movement again setSitDriveKeysStatus(true); }); } - -} \ No newline at end of file +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index ad45df892b..0108fb5eda 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -154,6 +154,7 @@ class MyAvatar : public Avatar { * * @property {Vec3} qmlPosition - A synonym for position for use by QML. * + * @property {Vec3} feetPosition - The position of the avatar's feet. * @property {boolean} shouldRenderLocally=true - If true then your avatar is rendered for you in Interface, * otherwise it is not rendered for you (but it is still rendered for other users). * @property {Vec3} motorVelocity=Vec3.ZERO - The target velocity of your avatar to be achieved by a scripted motor. @@ -340,6 +341,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition) QVector3D getQmlPosition() { auto p = getWorldPosition(); return QVector3D(p.x, p.y, p.z); } + Q_PROPERTY(glm::vec3 feetPosition READ getWorldFeetPosition WRITE goToFeetLocation) Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally) Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity) Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale) @@ -1866,6 +1868,9 @@ public: // also clears internal reaction triggers void updateRigControllerParameters(Rig::ControllerParameters& params); + // Don't substitute verify-fail: + virtual const QUrl& getSkeletonModelURL() const override { return _skeletonModelURL; } + public slots: /**jsdoc @@ -1935,9 +1940,8 @@ public slots: * @param {boolean} [shouldFaceLocation=false] - Set to true to position the avatar a short distance away from * the new position and orientate the avatar to face the position. */ - void goToFeetLocation(const glm::vec3& newPosition, - bool hasOrientation, const glm::quat& newOrientation, - bool shouldFaceLocation); + void goToFeetLocation(const glm::vec3& newPosition, bool hasOrientation = false, + const glm::quat& newOrientation = glm::quat(), bool shouldFaceLocation = false); /**jsdoc * Moves the avatar to a new position and/or orientation in the domain. diff --git a/interface/src/avatar/MyCharacterController.cpp b/interface/src/avatar/MyCharacterController.cpp index 2f59b70592..3a25721528 100755 --- a/interface/src/avatar/MyCharacterController.cpp +++ b/interface/src/avatar/MyCharacterController.cpp @@ -36,10 +36,10 @@ MyCharacterController::MyCharacterController(std::shared_ptr avatar) { MyCharacterController::~MyCharacterController() { } -void MyCharacterController::setDynamicsWorld(btDynamicsWorld* world) { - CharacterController::setDynamicsWorld(world); - if (world && _rigidBody) { - initRayShotgun(world); +void MyCharacterController::addToWorld() { + CharacterController::addToWorld(); + if (_rigidBody) { + initRayShotgun(_physicsEngine->getDynamicsWorld()); } } @@ -204,7 +204,7 @@ bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm: } int32_t MyCharacterController::computeCollisionMask() const { - int32_t collisionMask = BULLET_COLLISION_MASK_MY_AVATAR; + int32_t collisionMask = BULLET_COLLISION_MASK_MY_AVATAR; if (_collisionless && _collisionlessAllowed) { collisionMask = BULLET_COLLISION_MASK_COLLISIONLESS; } else if (!_collideWithOtherAvatars) { @@ -216,16 +216,17 @@ int32_t MyCharacterController::computeCollisionMask() const { void MyCharacterController::handleChangedCollisionMask() { if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_MASK) { // ATM the easiest way to update collision groups/masks is to remove/re-add the RigidBody - if (_dynamicsWorld) { - _dynamicsWorld->removeRigidBody(_rigidBody); - int32_t collisionMask = computeCollisionMask(); - _dynamicsWorld->addRigidBody(_rigidBody, BULLET_COLLISION_GROUP_MY_AVATAR, collisionMask); - } + // but we don't do it here. Instead we set some flags to remind us to do it later. + _pendingFlags |= (PENDING_FLAG_REMOVE_FROM_SIMULATION | PENDING_FLAG_ADD_TO_SIMULATION); _pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_MASK; updateCurrentGravity(); } } +bool MyCharacterController::needsSafeLandingSupport() const { + return _isStuck && _numStuckSubsteps >= NUM_SUBSTEPS_FOR_SAFE_LANDING_RETRY; +} + btConvexHullShape* MyCharacterController::computeShape() const { // HACK: the avatar collides using convex hull with a collision margin equal to // the old capsule radius. Two points define a capsule and additional points are @@ -447,12 +448,12 @@ public: std::vector MyCharacterController::rayTest(const btVector3& origin, const btVector3& direction, const btScalar& length, const QVector& jointsToExclude) const { std::vector foundAvatars; - if (_dynamicsWorld) { + if (_physicsEngine) { btVector3 end = origin + length * direction; DetailedRayResultCallback rayCallback = DetailedRayResultCallback(); rayCallback.m_flags |= btTriangleRaycastCallback::kF_KeepUnflippedNormal; rayCallback.m_flags |= btTriangleRaycastCallback::kF_UseSubSimplexConvexCastRaytest; - _dynamicsWorld->rayTest(origin, end, rayCallback); + _physicsEngine->getDynamicsWorld()->rayTest(origin, end, rayCallback); if (rayCallback.m_hitFractions.size() > 0) { foundAvatars.reserve(rayCallback.m_hitFractions.size()); for (int32_t i = 0; i < rayCallback.m_hitFractions.size(); i++) { diff --git a/interface/src/avatar/MyCharacterController.h b/interface/src/avatar/MyCharacterController.h index 7ddcf94f67..eefcc92637 100644 --- a/interface/src/avatar/MyCharacterController.h +++ b/interface/src/avatar/MyCharacterController.h @@ -26,7 +26,7 @@ public: explicit MyCharacterController(std::shared_ptr avatar); ~MyCharacterController (); - void setDynamicsWorld(btDynamicsWorld* world) override; + void addToWorld() override; void updateShapeIfNecessary() override; // Sweeping a convex shape through the physics simulation can be expensive when the obstacles are too @@ -69,9 +69,10 @@ public: int32_t computeCollisionMask() const override; void handleChangedCollisionMask() override; - bool _collideWithOtherAvatars{ true }; void setCollideWithOtherAvatars(bool collideWithOtherAvatars) { _collideWithOtherAvatars = collideWithOtherAvatars; } + bool needsSafeLandingSupport() const; + protected: void initRayShotgun(const btCollisionWorld* world); void updateMassProperties() override; @@ -88,6 +89,7 @@ protected: btScalar _density { 1.0f }; std::vector _detailedMotionStates; + bool _collideWithOtherAvatars { true }; }; #endif // hifi_MyCharacterController_h diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 6080911dd9..9d5693da33 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -337,10 +337,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { eyeParams.modelTranslation = getTranslation(); eyeParams.leftEyeJointIndex = _rig.indexOfJoint("LeftEye"); eyeParams.rightEyeJointIndex = _rig.indexOfJoint("RightEye"); - - if (_owningAvatar->getHasProceduralEyeFaceMovement()) { - _rig.updateFromEyeParameters(eyeParams); - } + _rig.updateFromEyeParameters(eyeParams); updateFingers(); } diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 5af7a357b0..c449874117 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -830,9 +830,14 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack } void Wallet::sendChallengeOwnershipResponses() { - if (_pendingChallenges.size() == 0 || getSalt().length() == 0) { + if (_pendingChallenges.size() == 0) { return; } + if (getSalt().length() == 0) { + qCDebug(commerce) << "Not responding to ownership challenge due to missing Wallet salt"; + return; + } + auto nodeList = DependencyManager::get(); EC_KEY* ec = readKeys(keyFilePath()); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index f6ce1ec8ad..a8710159b4 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -70,7 +70,7 @@ int main(int argc, const char* argv[]) { } QCommandLineParser parser; - parser.setApplicationDescription("High Fidelity Interface"); + parser.setApplicationDescription("High Fidelity"); QCommandLineOption versionOption = parser.addVersionOption(); QCommandLineOption helpOption = parser.addHelpOption(); diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index ed779787c9..fbc35f2732 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -121,9 +121,6 @@ void SafeLanding::updateTracking() { if (isEntityPhysicsReady(entity) && isVisuallyReady) { entityMapIter = _trackedEntities.erase(entityMapIter); } else { - if (!isVisuallyReady) { - entity->requestRenderUpdate(); - } entityMapIter++; } } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index f5569a19b2..ba392f0cd1 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -90,16 +90,25 @@ void Audio::setMuted(bool isMuted) { void Audio::setMutedDesktop(bool isMuted) { bool changed = false; + bool isHMD = qApp->isHMDMode(); withWriteLock([&] { if (_mutedDesktop != isMuted) { changed = true; _mutedDesktop = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + if (!isHMD) { + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } } }); + if (!isMuted && _settingsLoaded && !_pushingToTalk) { + // If the user is not pushing to talk and muted is changed to false, disable Push-To-Talk. Settings also need to be loaded. + setPTTDesktop(isMuted); + } if (changed) { - emit mutedChanged(isMuted); + if (!isHMD) { + emit mutedChanged(isMuted); + } emit mutedDesktopChanged(isMuted); } } @@ -112,16 +121,25 @@ bool Audio::getMutedDesktop() const { void Audio::setMutedHMD(bool isMuted) { bool changed = false; + bool isHMD = qApp->isHMDMode(); withWriteLock([&] { if (_mutedHMD != isMuted) { changed = true; _mutedHMD = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + if (isHMD) { + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } } }); + if (!isMuted && _settingsLoaded && !_pushingToTalk) { + // If the user is not pushing to talk and muted is changed to false, disable Push-To-Talk. Settings also need to be loaded. + setPTTHMD(isMuted); + } if (changed) { - emit mutedChanged(isMuted); + if (isHMD) { + emit mutedChanged(isMuted); + } emit mutedHMDChanged(isMuted); } } @@ -177,9 +195,9 @@ void Audio::setPTTDesktop(bool enabled) { _pttDesktop = enabled; } }); - if (enabled || _settingsLoaded) { + if (enabled && _settingsLoaded) { // Set to default behavior (muted for Desktop) on Push-To-Talk disable or when enabled. Settings also need to be loaded. - setMutedDesktop(true); + setMutedDesktop(enabled); } if (changed) { emit pushToTalkChanged(enabled); @@ -201,7 +219,7 @@ void Audio::setPTTHMD(bool enabled) { _pttHMD = enabled; } }); - if (enabled || _settingsLoaded) { + if (enabled && _settingsLoaded) { // Set to default behavior (unmuted for HMD) on Push-To-Talk disable or muted for when PTT is enabled. setMutedHMD(enabled); } @@ -386,6 +404,7 @@ void Audio::onContextChanged() { changed = true; } }); + if (_settingsLoaded) { bool isMuted = isHMD ? getMutedHMD() : getMutedDesktop(); setMuted(isMuted); @@ -395,6 +414,12 @@ void Audio::onContextChanged() { } if (changed) { emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP); + + bool mutedHMD = getMutedHMD(); + bool mutedDesktop = getMutedDesktop(); + if (mutedHMD != mutedDesktop) { + emit mutedChanged(isHMD ? mutedHMD : mutedDesktop); + } } } diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index a1c8e4fc6c..874b3fa42d 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -52,6 +52,9 @@ static const QVariantMap DOCK_AREA { { "RIGHT", DockArea::RIGHT } }; +DesktopScriptingInterface::DesktopScriptingInterface(QObject* parent, bool restricted) + : QObject(parent), _restricted(restricted) { } + int DesktopScriptingInterface::getWidth() { QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize(); return size.width(); @@ -128,7 +131,7 @@ InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& return nullptr; } - return new InteractiveWindow(sourceUrl, properties); + return new InteractiveWindow(sourceUrl, properties, _restricted); } InteractiveWindowPointer DesktopScriptingInterface::createWindowOnThread(const QString& sourceUrl, const QVariantMap& properties, QThread* targetThread) { @@ -139,7 +142,7 @@ InteractiveWindowPointer DesktopScriptingInterface::createWindowOnThread(const Q if (!urlValidator(sourceUrl)) { return nullptr; } - InteractiveWindowPointer window = new InteractiveWindow(sourceUrl, properties); + InteractiveWindowPointer window = new InteractiveWindow(sourceUrl, properties, _restricted); window->moveToThread(targetThread); return window; } diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h index 525fd7c803..e562a32543 100644 --- a/interface/src/scripting/DesktopScriptingInterface.h +++ b/interface/src/scripting/DesktopScriptingInterface.h @@ -54,6 +54,8 @@ class DesktopScriptingInterface : public QObject, public Dependency { Q_PROPERTY(int CLOSE_BUTTON_HIDES READ flagCloseButtonHides CONSTANT FINAL) public: + DesktopScriptingInterface(QObject* parent= nullptr, bool restricted = false); + /**jsdoc * Sets the opacity of the HUD surface. * @function Desktop.setHUDAlpha @@ -106,6 +108,7 @@ private: static QVariantMap getDockArea(); Q_INVOKABLE static QVariantMap getPresentationMode(); + const bool _restricted; }; diff --git a/interface/src/scripting/RenderScriptingInterface.cpp b/interface/src/scripting/RenderScriptingInterface.cpp index 338a5ab883..f23dc598a9 100644 --- a/interface/src/scripting/RenderScriptingInterface.cpp +++ b/interface/src/scripting/RenderScriptingInterface.cpp @@ -54,7 +54,7 @@ void RenderScriptingInterface::forceRenderMethod(RenderMethod renderMethod) { _renderMethod = (int)renderMethod; _renderMethodSetting.set((int)renderMethod); - auto config = dynamic_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("RenderMainView.DeferredForwardSwitch")); + auto config = dynamic_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("RenderMainView.DeferredForwardSwitch")); if (config) { config->setBranch((int)renderMethod); } diff --git a/interface/src/scripting/SettingsScriptingInterface.cpp b/interface/src/scripting/SettingsScriptingInterface.cpp index afafe1a350..c47d8e1da1 100644 --- a/interface/src/scripting/SettingsScriptingInterface.cpp +++ b/interface/src/scripting/SettingsScriptingInterface.cpp @@ -35,8 +35,12 @@ QVariant SettingsScriptingInterface::getValue(const QString& setting, const QVar } void SettingsScriptingInterface::setValue(const QString& setting, const QVariant& value) { + if (getValue(setting) == value) { + return; + } // Make a deep-copy of the string. // Dangling pointers can occur with QStrings that are implicitly shared from a QScriptEngine. QString deepCopy = QString::fromUtf16(setting.utf16()); Setting::Handle(deepCopy).set(value); + emit valueChanged(setting, value); } diff --git a/interface/src/scripting/SettingsScriptingInterface.h b/interface/src/scripting/SettingsScriptingInterface.h index 25a8b627cb..ff00299fd0 100644 --- a/interface/src/scripting/SettingsScriptingInterface.h +++ b/interface/src/scripting/SettingsScriptingInterface.h @@ -64,6 +64,9 @@ public slots: * print("Value: " + (typeof value) + " " + JSON.stringify(value)); // object {"x":0,"y":10,"z":0} */ void setValue(const QString& setting, const QVariant& value); + +signals: + void valueChanged(const QString& setting, const QVariant& value); }; #endif // hifi_SettingsScriptingInterface_h diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 80c0479bd7..f4aa36e2f4 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -56,6 +56,7 @@ WindowScriptingInterface::WindowScriptingInterface() { }); connect(qApp->getWindow(), &MainWindow::windowGeometryChanged, this, &WindowScriptingInterface::onWindowGeometryChanged); + connect(qApp->getWindow(), &MainWindow::windowMinimizedChanged, this, &WindowScriptingInterface::minimizedChanged); connect(qApp, &Application::interstitialModeChanged, [this] (bool interstitialMode) { emit interstitialModeChanged(interstitialMode); }); diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 010f3bcca2..6207b22cb8 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -814,6 +814,17 @@ signals: */ void geometryChanged(QRect geometry); + + /**jsdoc + * Triggered when "minimized" state of the Interface window changes. + * @function Window.minimizedChanged + * @param {bool} isMinimized - true if the Interface window is now minimized; false otherwise. + * @returns {Signal} + * + * Window.minimizedChanged.connect(onWindowMinimizedChanged); + */ + void minimizedChanged(bool isMinimized); + private: QString getPreviousBrowseLocation() const; void setPreviousBrowseLocation(const QString& location); diff --git a/interface/src/ui/InteractiveWindow.cpp b/interface/src/ui/InteractiveWindow.cpp index c0f3e82b29..e63c392a47 100644 --- a/interface/src/ui/InteractiveWindow.cpp +++ b/interface/src/ui/InteractiveWindow.cpp @@ -18,6 +18,9 @@ #include #include +#include +#include +#include #include #include #include @@ -109,6 +112,10 @@ void InteractiveWindow::forwardKeyReleaseEvent(int key, int modifiers) { QCoreApplication::postEvent(QCoreApplication::instance(), event); } +void InteractiveWindow::emitMainWindowResizeEvent() { + emit qApp->getWindow()->windowGeometryChanged(qApp->getWindow()->geometry()); +} + /**jsdoc * A set of properties used when creating an InteractiveWindow. * @typedef {object} InteractiveWindow.Properties @@ -130,7 +137,7 @@ void InteractiveWindow::forwardKeyReleaseEvent(int key, int modifiers) { * Set at window creation. Possible flag values are provided as {@link Desktop|Desktop.ALWAYS_ON_TOP} and {@link Desktop|Desktop.CLOSE_BUTTON_HIDES}. * Additional flag values can be found on Qt's website at https://doc.qt.io/qt-5/qt.html#WindowType-enum. */ -InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties) { +InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties, bool restricted) { InteractiveWindowPresentationMode presentationMode = InteractiveWindowPresentationMode::Native; if (properties.contains(PRESENTATION_MODE_PROPERTY)) { @@ -215,16 +222,25 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap Qt::QueuedConnection); QObject::connect(rootItem, SIGNAL(keyReleaseEvent(int, int)), this, SLOT(forwardKeyReleaseEvent(int, int)), Qt::QueuedConnection); - emit mainWindow->windowGeometryChanged(qApp->getWindow()->geometry()); } }); + QObject::connect(_dockWidget.get(), SIGNAL(onResizeEvent()), this, SLOT(emitMainWindowResizeEvent())); + _dockWidget->setSource(QUrl(sourceUrl)); + _dockWidget->setObjectName("DockedWidget"); mainWindow->addDockWidget(dockArea, _dockWidget.get()); } else { - auto offscreenUi = DependencyManager::get(); - // Build the event bridge and wrapper on the main thread - offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, [&](QQmlContext* context, QObject* object) { + auto contextInitLambda = [&](QQmlContext* context) { + // If the restricted flag is on, the web content will not be able to access local files + ContextAwareProfile::restrictContext(context, restricted); +#if !defined(Q_OS_ANDROID) + FileTypeProfile::registerWithContext(context); + HFWebEngineProfile::registerWithContext(context); +#endif + }; + + auto objectInitLambda = [&](QQmlContext* context, QObject* object) { _qmlWindowProxy = std::shared_ptr(new QmlWindowProxy(object, nullptr), qmlWindowProxyDeleter); context->setContextProperty(EVENT_BRIDGE_PROPERTY, _interactiveWindowProxy.get()); if (properties.contains(ADDITIONAL_FLAGS_PROPERTY)) { @@ -278,8 +294,13 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) { sourceURL = QUrl::fromLocalFile(sourceURL.toString()).toString(); } + object->setObjectName("InteractiveWindow"); object->setProperty(SOURCE_PROPERTY, sourceURL); - }); + }; + auto offscreenUi = DependencyManager::get(); + + // Build the event bridge and wrapper on the main thread + offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, objectInitLambda, contextInitLambda); } } @@ -296,7 +317,9 @@ void InteractiveWindow::sendToQml(const QVariant& message) { QMetaObject::invokeMethod(rootItem, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message)); } } else { - QMetaObject::invokeMethod(_qmlWindowProxy->getQmlWindow(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message)); + if (_qmlWindowProxy) { + QMetaObject::invokeMethod(_qmlWindowProxy->getQmlWindow(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message)); + } } } diff --git a/interface/src/ui/InteractiveWindow.h b/interface/src/ui/InteractiveWindow.h index 67f0a9ea2e..70077a27b0 100644 --- a/interface/src/ui/InteractiveWindow.h +++ b/interface/src/ui/InteractiveWindow.h @@ -126,7 +126,7 @@ class InteractiveWindow : public QObject { Q_PROPERTY(int presentationMode READ getPresentationMode WRITE setPresentationMode) public: - InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties); + InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties, bool restricted); ~InteractiveWindow(); private: @@ -319,6 +319,7 @@ protected slots: void forwardKeyPressEvent(int key, int modifiers); void forwardKeyReleaseEvent(int key, int modifiers); + void emitMainWindowResizeEvent(); private: std::shared_ptr _qmlWindowProxy; diff --git a/launchers/darwin/CMakeLists.txt b/launchers/darwin/CMakeLists.txt index f71976960e..b495893b97 100644 --- a/launchers/darwin/CMakeLists.txt +++ b/launchers/darwin/CMakeLists.txt @@ -45,6 +45,8 @@ set(src_files src/CustomUI.m src/NSTask+NSTaskExecveAdditions.h src/NSTask+NSTaskExecveAdditions.m + src/HQDefaults.h + src/HQDefaults.m src/main.mm nib/Window.xib nib/SplashScreen.xib @@ -69,7 +71,7 @@ set_target_properties(${this_target} PROPERTIES set(MACOSX_BUNDLE_ICON_FILE "interface.icns") function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE) - if (NOT DEFINED ${_RESULT_NAME}) + if (NOT DEFINED ${_RESULT_NAME}) if ("$ENV{${_ENV_VAR_NAME}}" STREQUAL "") set (${_RESULT_NAME} ${_DEFAULT_VALUE} PARENT_SCOPE) else() @@ -89,8 +91,14 @@ if ("${LAUNCHER_HMAC_SECRET}" STREQUAL "") message(FATAL_ERROR "LAUNCHER_HMAC_SECRET is not set") endif() +# Development environments don't set BUILD_VERSION. Let 0 mean a development version. +if(NOT BUILD_VERSION) + set(BUILD_VERSION 0) +endif() + target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_HMAC_SECRET="${LAUNCHER_HMAC_SECRET}") target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_BUILD_VERSION="${BUILD_VERSION}") +target_compile_definitions(${PROJECT_NAME} PRIVATE USER_AGENT_STRING="HQLauncher/${BUILD_VERSION} \(macOS\)") file(GLOB NIB_FILES "nib/*.xib") @@ -112,6 +120,10 @@ add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/images "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources/") +add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_SOURCE_DIR}/data/HQDefaults.plist "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources/") + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND updater COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/updater" "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources/") @@ -140,3 +152,17 @@ set(DMG_SUBFOLDER_ICON "${CMAKE_SOURCE_DIR}/cmake/installer/install-folder.rsrc" set(CPACK_GENERATOR "DragNDrop") include(CPack) + +include(FindXCTest) + +include_directories(${CMAKE_SOURCE_DIR}/src) + +xctest_add_bundle(HQLauncherTests HQLauncher + ${CMAKE_SOURCE_DIR}/src/HQDefaults.m + ${CMAKE_SOURCE_DIR}/tests/HQDefaultsTests.m + ${CMAKE_SOURCE_DIR}/tests/Info.plist +) + +set_target_properties(HQLauncherTests PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/tests/Info.plist + ) diff --git a/launchers/darwin/data/HQDefaults.plist b/launchers/darwin/data/HQDefaults.plist new file mode 100644 index 0000000000..98a29664c9 --- /dev/null +++ b/launchers/darwin/data/HQDefaults.plist @@ -0,0 +1,14 @@ + + + + + + name + thunderURL + defaultValue + https://thunder.highfidelity.com + environmentVariable + HIFI_THUNDER_URL + + + diff --git a/launchers/darwin/nib/DisplayNameScreen.xib b/launchers/darwin/nib/DisplayNameScreen.xib index 9862c3773b..77cc3b2222 100644 --- a/launchers/darwin/nib/DisplayNameScreen.xib +++ b/launchers/darwin/nib/DisplayNameScreen.xib @@ -81,7 +81,7 @@