Merge branch 'master' of github.com:highfidelity/hifi into feat/disable-add-without-url
|
@ -39,6 +39,7 @@ set(BUILD_TOOLS_OPTION ON)
|
|||
set(BUILD_INSTALLER_OPTION ON)
|
||||
set(GLES_OPTION OFF)
|
||||
set(DISABLE_QML_OPTION OFF)
|
||||
set(DOWNLOAD_SERVERLESS_CONTENT_OPTION OFF)
|
||||
|
||||
if (ANDROID OR UWP)
|
||||
set(BUILD_SERVER_OPTION OFF)
|
||||
|
@ -74,6 +75,11 @@ option(BUILD_INSTALLER "Build installer" ${BUILD_INSTALLER_OPTION})
|
|||
option(USE_GLES "Use OpenGL ES" ${GLES_OPTION})
|
||||
option(DISABLE_QML "Disable QML" ${DISABLE_QML_OPTION})
|
||||
option(DISABLE_KTX_CACHE "Disable KTX Cache" OFF)
|
||||
option(
|
||||
DOWNLOAD_SERVERLESS_CONTENT
|
||||
"Download and setup default serverless content beside Interface"
|
||||
${DOWNLOAD_SERVERLESS_CONTENT_OPTION}
|
||||
)
|
||||
|
||||
set(PLATFORM_QT_GL OpenGL)
|
||||
|
||||
|
@ -88,12 +94,13 @@ foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS})
|
|||
list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}")
|
||||
endforeach()
|
||||
|
||||
MESSAGE(STATUS "Build server: " ${BUILD_SERVER})
|
||||
MESSAGE(STATUS "Build client: " ${BUILD_CLIENT})
|
||||
MESSAGE(STATUS "Build tests: " ${BUILD_TESTS})
|
||||
MESSAGE(STATUS "Build tools: " ${BUILD_TOOLS})
|
||||
MESSAGE(STATUS "Build installer: " ${BUILD_INSTALLER})
|
||||
MESSAGE(STATUS "GL ES: " ${USE_GLES})
|
||||
MESSAGE(STATUS "Build server: " ${BUILD_SERVER})
|
||||
MESSAGE(STATUS "Build client: " ${BUILD_CLIENT})
|
||||
MESSAGE(STATUS "Build tests: " ${BUILD_TESTS})
|
||||
MESSAGE(STATUS "Build tools: " ${BUILD_TOOLS})
|
||||
MESSAGE(STATUS "Build installer: " ${BUILD_INSTALLER})
|
||||
MESSAGE(STATUS "GL ES: " ${USE_GLES})
|
||||
MESSAGE(STATUS "DL serverless content: " ${DOWNLOAD_SERVERLESS_CONTENT})
|
||||
|
||||
if (DISABLE_QML)
|
||||
MESSAGE(STATUS "QML disabled!")
|
||||
|
|
|
@ -46,15 +46,68 @@ static const uint8_t CPU_AFFINITY_COUNT_LOW = 1;
|
|||
static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000;
|
||||
#endif
|
||||
|
||||
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
|
||||
|
||||
static const QStringList BAKEABLE_MODEL_EXTENSIONS = { "fbx" };
|
||||
static QStringList BAKEABLE_TEXTURE_EXTENSIONS;
|
||||
static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {};
|
||||
static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = { };
|
||||
|
||||
static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx";
|
||||
static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx";
|
||||
static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js";
|
||||
|
||||
static const ModelBakeVersion CURRENT_MODEL_BAKE_VERSION = (ModelBakeVersion)((BakeVersion)ModelBakeVersion::COUNT - 1);
|
||||
static const TextureBakeVersion CURRENT_TEXTURE_BAKE_VERSION = (TextureBakeVersion)((BakeVersion)TextureBakeVersion::COUNT - 1);
|
||||
static const ScriptBakeVersion CURRENT_SCRIPT_BAKE_VERSION = (ScriptBakeVersion)((BakeVersion)ScriptBakeVersion::COUNT - 1);
|
||||
|
||||
BakedAssetType assetTypeForExtension(const QString& extension) {
|
||||
auto extensionLower = extension.toLower();
|
||||
if (BAKEABLE_MODEL_EXTENSIONS.contains(extensionLower)) {
|
||||
return Model;
|
||||
} else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extensionLower.toLocal8Bit())) {
|
||||
return Texture;
|
||||
} else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extensionLower)) {
|
||||
return Script;
|
||||
}
|
||||
return Undefined;
|
||||
}
|
||||
|
||||
BakedAssetType assetTypeForFilename(const QString& filename) {
|
||||
auto dotIndex = filename.lastIndexOf(".");
|
||||
if (dotIndex == -1) {
|
||||
return BakedAssetType::Undefined;
|
||||
}
|
||||
|
||||
auto extension = filename.mid(dotIndex + 1);
|
||||
return assetTypeForExtension(extension);
|
||||
}
|
||||
|
||||
QString bakedFilenameForAssetType(BakedAssetType type) {
|
||||
switch (type) {
|
||||
case Model:
|
||||
return BAKED_MODEL_SIMPLE_NAME;
|
||||
case Texture:
|
||||
return BAKED_TEXTURE_SIMPLE_NAME;
|
||||
case Script:
|
||||
return BAKED_SCRIPT_SIMPLE_NAME;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
BakeVersion currentBakeVersionForAssetType(BakedAssetType type) {
|
||||
switch (type) {
|
||||
case Model:
|
||||
return (BakeVersion)CURRENT_MODEL_BAKE_VERSION;
|
||||
case Texture:
|
||||
return (BakeVersion)CURRENT_TEXTURE_BAKE_VERSION;
|
||||
case Script:
|
||||
return (BakeVersion)CURRENT_SCRIPT_BAKE_VERSION;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
|
||||
|
||||
void AssetServer::bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath) {
|
||||
qDebug() << "Starting bake for: " << assetPath << assetHash;
|
||||
auto it = _pendingBakes.find(assetHash);
|
||||
|
@ -167,36 +220,38 @@ bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetU
|
|||
return false;
|
||||
}
|
||||
|
||||
auto dotIndex = path.lastIndexOf(".");
|
||||
if (dotIndex == -1) {
|
||||
BakedAssetType type = assetTypeForFilename(path);
|
||||
|
||||
if (type == Undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto extension = path.mid(dotIndex + 1);
|
||||
QString bakedFilename = bakedFilenameForAssetType(type);
|
||||
auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename;
|
||||
auto mappingIt = _fileMappings.find(bakedPath);
|
||||
bool bakedMappingExists = mappingIt != _fileMappings.end();
|
||||
|
||||
QString bakedFilename;
|
||||
// If the path is mapped to the original file's hash, baking has been disabled for this
|
||||
// asset
|
||||
if (bakedMappingExists && mappingIt->second == assetHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool loaded;
|
||||
AssetMeta meta;
|
||||
std::tie(loaded, meta) = readMetaFile(assetHash);
|
||||
|
||||
// TODO: Allow failed bakes that happened on old versions to be re-baked
|
||||
if (loaded && meta.failedLastBake) {
|
||||
if (type == Texture && !loaded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) {
|
||||
bakedFilename = BAKED_MODEL_SIMPLE_NAME;
|
||||
} else if (loaded && BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit())) {
|
||||
bakedFilename = BAKED_TEXTURE_SIMPLE_NAME;
|
||||
} else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) {
|
||||
bakedFilename = BAKED_SCRIPT_SIMPLE_NAME;
|
||||
} else {
|
||||
auto currentVersion = currentBakeVersionForAssetType(type);
|
||||
|
||||
if (loaded && (meta.failedLastBake && meta.bakeVersion >= currentVersion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename;
|
||||
return _fileMappings.find(bakedPath) == _fileMappings.end();
|
||||
return !bakedMappingExists || (meta.bakeVersion < currentVersion);
|
||||
}
|
||||
|
||||
bool interfaceRunning() {
|
||||
|
@ -598,15 +653,9 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, NLPacketLi
|
|||
|
||||
// first, figure out from the mapping extension what type of file this is
|
||||
auto assetPathExtension = assetPath.mid(assetPath.lastIndexOf('.') + 1).toLower();
|
||||
QString bakedRootFile;
|
||||
|
||||
if (BAKEABLE_MODEL_EXTENSIONS.contains(assetPathExtension)) {
|
||||
bakedRootFile = BAKED_MODEL_SIMPLE_NAME;
|
||||
} else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(assetPathExtension.toLocal8Bit())) {
|
||||
bakedRootFile = BAKED_TEXTURE_SIMPLE_NAME;
|
||||
} else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(assetPathExtension)) {
|
||||
bakedRootFile = BAKED_SCRIPT_SIMPLE_NAME;
|
||||
}
|
||||
auto type = assetTypeForFilename(assetPath);
|
||||
QString bakedRootFile = bakedFilenameForAssetType(type);
|
||||
|
||||
auto originalAssetHash = it->second;
|
||||
QString redirectedAssetHash;
|
||||
|
@ -653,9 +702,19 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, NLPacketLi
|
|||
auto query = QUrlQuery(url.query());
|
||||
bool isSkybox = query.hasQueryItem("skybox");
|
||||
if (isSkybox) {
|
||||
writeMetaFile(originalAssetHash);
|
||||
if (!bakingDisabled) {
|
||||
maybeBake(assetPath, originalAssetHash);
|
||||
bool loaded;
|
||||
AssetMeta meta;
|
||||
std::tie(loaded, meta) = readMetaFile(originalAssetHash);
|
||||
|
||||
if (!loaded) {
|
||||
AssetMeta needsBakingMeta;
|
||||
needsBakingMeta.bakeVersion = NEEDS_BAKING_BAKE_VERSION;
|
||||
|
||||
writeMetaFile(originalAssetHash, needsBakingMeta);
|
||||
if (!bakingDisabled) {
|
||||
maybeBake(assetPath, originalAssetHash);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1275,15 +1334,19 @@ QString getBakeMapping(const AssetUtils::AssetHash& hash, const QString& relativ
|
|||
}
|
||||
|
||||
void AssetServer::handleFailedBake(QString originalAssetHash, QString assetPath, QString errors) {
|
||||
qDebug() << "Failed: " << originalAssetHash << assetPath << errors;
|
||||
qDebug() << "Failed to bake: " << originalAssetHash << assetPath << "(" << errors << ")";
|
||||
|
||||
bool loaded;
|
||||
AssetMeta meta;
|
||||
|
||||
std::tie(loaded, meta) = readMetaFile(originalAssetHash);
|
||||
|
||||
auto type = assetTypeForFilename(assetPath);
|
||||
auto currentTypeVersion = currentBakeVersionForAssetType(type);
|
||||
|
||||
meta.failedLastBake = true;
|
||||
meta.lastBakeErrors = errors;
|
||||
meta.bakeVersion = currentTypeVersion;
|
||||
|
||||
writeMetaFile(originalAssetHash, meta);
|
||||
|
||||
|
@ -1373,17 +1436,20 @@ void AssetServer::handleCompletedBake(QString originalAssetHash, QString origina
|
|||
qWarning() << "Failed to remove temporary directory:" << bakedTempOutputDir;
|
||||
}
|
||||
|
||||
if (!errorCompletingBake) {
|
||||
// create the meta file to store which version of the baking process we just completed
|
||||
writeMetaFile(originalAssetHash);
|
||||
} else {
|
||||
auto type = assetTypeForFilename(originalAssetPath);
|
||||
auto currentTypeVersion = currentBakeVersionForAssetType(type);
|
||||
|
||||
AssetMeta meta;
|
||||
meta.bakeVersion = currentTypeVersion;
|
||||
meta.failedLastBake = errorCompletingBake;
|
||||
|
||||
if (errorCompletingBake) {
|
||||
qWarning() << "Could not complete bake for" << originalAssetHash;
|
||||
AssetMeta meta;
|
||||
meta.failedLastBake = true;
|
||||
meta.lastBakeErrors = errorReason;
|
||||
writeMetaFile(originalAssetHash, meta);
|
||||
}
|
||||
|
||||
writeMetaFile(originalAssetHash, meta);
|
||||
|
||||
_pendingBakes.remove(originalAssetHash);
|
||||
}
|
||||
|
||||
|
@ -1447,7 +1513,7 @@ bool AssetServer::writeMetaFile(AssetUtils::AssetHash originalAssetHash, const A
|
|||
// construct the JSON that will be in the meta file
|
||||
QJsonObject metaFileObject;
|
||||
|
||||
metaFileObject[BAKE_VERSION_KEY] = meta.bakeVersion;
|
||||
metaFileObject[BAKE_VERSION_KEY] = (int)meta.bakeVersion;
|
||||
metaFileObject[FAILED_LAST_BAKE_KEY] = meta.failedLastBake;
|
||||
metaFileObject[LAST_BAKE_ERRORS_KEY] = meta.lastBakeErrors;
|
||||
|
||||
|
@ -1479,27 +1545,13 @@ bool AssetServer::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool
|
|||
for (const auto& path : paths) {
|
||||
auto it = _fileMappings.find(path);
|
||||
if (it != _fileMappings.end()) {
|
||||
auto type = assetTypeForFilename(path);
|
||||
if (type == Undefined) {
|
||||
continue;
|
||||
}
|
||||
QString bakedFilename = bakedFilenameForAssetType(type);
|
||||
|
||||
auto hash = it->second;
|
||||
|
||||
auto dotIndex = path.lastIndexOf(".");
|
||||
if (dotIndex == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto extension = path.mid(dotIndex + 1);
|
||||
|
||||
QString bakedFilename;
|
||||
|
||||
if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) {
|
||||
bakedFilename = BAKED_MODEL_SIMPLE_NAME;
|
||||
} else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit()) && hasMetaFile(hash)) {
|
||||
bakedFilename = BAKED_TEXTURE_SIMPLE_NAME;
|
||||
} else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) {
|
||||
bakedFilename = BAKED_SCRIPT_SIMPLE_NAME;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto bakedMapping = getBakeMapping(hash, bakedFilename);
|
||||
|
||||
auto it = _fileMappings.find(bakedMapping);
|
||||
|
|
|
@ -23,8 +23,47 @@
|
|||
|
||||
#include "RegisteredMetaTypes.h"
|
||||
|
||||
using BakeVersion = int;
|
||||
static const BakeVersion INITIAL_BAKE_VERSION = 0;
|
||||
static const BakeVersion NEEDS_BAKING_BAKE_VERSION = -1;
|
||||
|
||||
enum BakedAssetType : int {
|
||||
Model = 0,
|
||||
Texture,
|
||||
Script,
|
||||
|
||||
NUM_ASSET_TYPES,
|
||||
Undefined
|
||||
};
|
||||
|
||||
// ATTENTION! If you change the current version for an asset type, you will also
|
||||
// need to update the function currentBakeVersionForAssetType() inside of AssetServer.cpp.
|
||||
enum class ModelBakeVersion : BakeVersion {
|
||||
Initial = INITIAL_BAKE_VERSION,
|
||||
|
||||
COUNT
|
||||
};
|
||||
|
||||
// ATTENTION! See above.
|
||||
enum class TextureBakeVersion : BakeVersion {
|
||||
Initial = INITIAL_BAKE_VERSION,
|
||||
|
||||
COUNT
|
||||
};
|
||||
|
||||
// ATTENTION! See above.
|
||||
enum class ScriptBakeVersion : BakeVersion {
|
||||
Initial = INITIAL_BAKE_VERSION,
|
||||
FixEmptyScripts,
|
||||
|
||||
COUNT
|
||||
};
|
||||
|
||||
struct AssetMeta {
|
||||
int bakeVersion { 0 };
|
||||
AssetMeta() {
|
||||
}
|
||||
|
||||
BakeVersion bakeVersion;
|
||||
bool failedLastBake { false };
|
||||
QString lastBakeErrors;
|
||||
};
|
||||
|
|
16
cmake/externals/serverless-content/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
include(ExternalProject)
|
||||
|
||||
set(EXTERNAL_NAME serverless-content)
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC66.zip
|
||||
URL_MD5 91edfde96e06efc847ca327ab97f4c74
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
# Hide this external target (for IDE users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
|
@ -73,6 +73,11 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
add_definitions(-DDEV_BUILD)
|
||||
endif ()
|
||||
|
||||
if (DEPLOY_PACKAGE)
|
||||
# for deployed packages always grab the serverless content
|
||||
set(DOWNLOAD_SERVERLESS_CONTENT ON)
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
set(DMG_SUBFOLDER_NAME "${BUILD_ORGANIZATION}")
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ if (typeof Settings === "undefined") {
|
|||
Settings = {};
|
||||
}
|
||||
|
||||
Object.assign(Settings, {
|
||||
$.extend(Settings, {
|
||||
DEPRECATED_CLASS: 'deprecated-setting',
|
||||
TRIGGER_CHANGE_CLASS: 'trigger-change',
|
||||
DATA_ROW_CLASS: 'value-row',
|
||||
|
|
|
@ -2222,7 +2222,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
const QString ASSIGNMENT_INSTANCES_HEADER = "ASSIGNMENT-INSTANCES";
|
||||
const QString ASSIGNMENT_POOL_HEADER = "ASSIGNMENT-POOL";
|
||||
|
||||
QByteArray assignmentInstancesValue = connection->requestHeaders().value(ASSIGNMENT_INSTANCES_HEADER.toLocal8Bit());
|
||||
QByteArray assignmentInstancesValue = connection->requestHeader(ASSIGNMENT_INSTANCES_HEADER.toLocal8Bit());
|
||||
|
||||
int numInstances = 1;
|
||||
|
||||
|
@ -2234,7 +2234,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
}
|
||||
|
||||
QString assignmentPool = emptyPool;
|
||||
QByteArray assignmentPoolValue = connection->requestHeaders().value(ASSIGNMENT_POOL_HEADER.toLocal8Bit());
|
||||
QByteArray assignmentPoolValue = connection->requestHeader(ASSIGNMENT_POOL_HEADER.toLocal8Bit());
|
||||
|
||||
if (!assignmentPoolValue.isEmpty()) {
|
||||
// specific pool requested, set that on the created assignment
|
||||
|
@ -2632,7 +2632,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
|||
|
||||
if (!_oauthProviderURL.isEmpty()
|
||||
&& (adminUsersVariant.isValid() || adminRolesVariant.isValid())) {
|
||||
QString cookieString = connection->requestHeaders().value(HTTP_COOKIE_HEADER_KEY);
|
||||
QString cookieString = connection->requestHeader(HTTP_COOKIE_HEADER_KEY);
|
||||
|
||||
const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)";
|
||||
QRegExp cookieUUIDRegex(COOKIE_UUID_REGEX_STRING);
|
||||
|
@ -2677,7 +2677,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
|||
static const QByteArray REQUESTED_WITH_HEADER = "X-Requested-With";
|
||||
static const QString XML_REQUESTED_WITH = "XMLHttpRequest";
|
||||
|
||||
if (connection->requestHeaders().value(REQUESTED_WITH_HEADER) == XML_REQUESTED_WITH) {
|
||||
if (connection->requestHeader(REQUESTED_WITH_HEADER) == XML_REQUESTED_WITH) {
|
||||
// unauthorized XHR requests get a 401 and not a 302, since there isn't an XHR
|
||||
// path to OAuth authorize
|
||||
connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY);
|
||||
|
@ -2708,7 +2708,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
|||
const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization";
|
||||
|
||||
// check if a username and password have been provided with the request
|
||||
QString basicAuthString = connection->requestHeaders().value(BASIC_AUTH_HEADER_KEY);
|
||||
QString basicAuthString = connection->requestHeader(BASIC_AUTH_HEADER_KEY);
|
||||
|
||||
if (!basicAuthString.isEmpty()) {
|
||||
QStringList splitAuthString = basicAuthString.split(' ');
|
||||
|
|
|
@ -43,7 +43,6 @@ endif()
|
|||
list(APPEND GENERATE_QRC_DEPENDS ${RESOURCES_RCC})
|
||||
add_custom_target(resources ALL DEPENDS ${GENERATE_QRC_DEPENDS})
|
||||
|
||||
|
||||
# set a default root dir for each of our optional externals if it was not passed
|
||||
set(OPTIONAL_EXTERNALS "LeapMotion")
|
||||
|
||||
|
@ -314,35 +313,41 @@ if (APPLE)
|
|||
)
|
||||
|
||||
set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources")
|
||||
|
||||
set(RESOURCES_DEV_DIR "$<TARGET_FILE_DIR:${TARGET_NAME}>/../Resources")
|
||||
|
||||
# copy script files beside the executable
|
||||
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
||||
"${CMAKE_SOURCE_DIR}/scripts"
|
||||
"$<TARGET_FILE_DIR:${TARGET_NAME}>/../Resources/scripts"
|
||||
"${RESOURCES_DEV_DIR}/scripts"
|
||||
)
|
||||
|
||||
# call the fixup_interface macro to add required bundling commands for installation
|
||||
fixup_interface()
|
||||
|
||||
else()
|
||||
set(INTERFACE_EXEC_DIR "$<TARGET_FILE_DIR:${TARGET_NAME}>")
|
||||
set(RESOURCES_DEV_DIR "${INTERFACE_EXEC_DIR}/resources")
|
||||
|
||||
# copy the resources files beside the executable
|
||||
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
|
||||
"${RESOURCES_RCC}"
|
||||
"$<TARGET_FILE_DIR:interface>"
|
||||
"${RESOURCES_RCC}"
|
||||
"${INTERFACE_EXEC_DIR}"
|
||||
# FIXME, the edit script code loads HTML from the scripts folder
|
||||
# which in turn relies on CSS that refers to the fonts. In theory
|
||||
# we should be able to modify the CSS to reference the QRC path to
|
||||
# the ttf files, but doing so generates a CORS policy violation,
|
||||
# so we have to retain a copy of the fonts outside of the resources binary
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
||||
"${PROJECT_SOURCE_DIR}/resources/fonts"
|
||||
"$<TARGET_FILE_DIR:${TARGET_NAME}>/resources/fonts"
|
||||
"${PROJECT_SOURCE_DIR}/resources/fonts"
|
||||
"${RESOURCES_DEV_DIR}/fonts"
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
||||
"${CMAKE_SOURCE_DIR}/scripts"
|
||||
"$<TARGET_FILE_DIR:${TARGET_NAME}>/scripts"
|
||||
"${CMAKE_SOURCE_DIR}/scripts"
|
||||
"${INTERFACE_EXEC_DIR}/scripts"
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
|
||||
"${PROJECT_SOURCE_DIR}/resources/serverless/tutorial.json"
|
||||
"${RESOURCES_DEV_DIR}/serverless/tutorial.json"
|
||||
)
|
||||
|
||||
# link target to external libraries
|
||||
|
@ -368,7 +373,6 @@ else()
|
|||
endif()
|
||||
|
||||
if (SCRIPTS_INSTALL_DIR)
|
||||
|
||||
# setup install of scripts beside interface executable
|
||||
install(
|
||||
DIRECTORY "${CMAKE_SOURCE_DIR}/scripts/"
|
||||
|
@ -377,6 +381,19 @@ if (SCRIPTS_INSTALL_DIR)
|
|||
)
|
||||
endif()
|
||||
|
||||
if (DOWNLOAD_SERVERLESS_CONTENT)
|
||||
add_dependency_external_projects(serverless-content)
|
||||
|
||||
ExternalProject_Get_Property(serverless-content SOURCE_DIR)
|
||||
|
||||
# for dev builds, copy the serverless content to the resources folder
|
||||
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
||||
"${SOURCE_DIR}"
|
||||
"${RESOURCES_DEV_DIR}/serverless"
|
||||
)
|
||||
endif ()
|
||||
|
||||
if (WIN32)
|
||||
set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"")
|
||||
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
{
|
||||
"name": "Keyboard/Mouse to Actions",
|
||||
"channels": [
|
||||
|
||||
{ "from": "Keyboard.A", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" },
|
||||
{ "from": "Keyboard.D", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" },
|
||||
{ "from": "Keyboard.A", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" },
|
||||
{ "from": "Keyboard.D", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_RIGHT" },
|
||||
{ "from": "Keyboard.E", "when": "Keyboard.Shift", "to": "Actions.BOOM_IN", "filters": [ { "type": "scale", "scale": 0.05 } ] },
|
||||
{ "from": "Keyboard.C", "when": "Keyboard.Shift", "to": "Actions.BOOM_OUT", "filters": [ { "type": "scale", "scale": 0.05 } ] },
|
||||
{ "from": "Keyboard.S", "when": "Keyboard.Shift", "to": "Actions.PITCH_DOWN" },
|
||||
{ "from": "Keyboard.W", "when": "Keyboard.Shift", "to": "Actions.PITCH_UP" },
|
||||
{ "from": "Keyboard.E", "to": "Actions.LATERAL_RIGHT" },
|
||||
{ "from": "Keyboard.Q", "to": "Actions.LATERAL_LEFT" },
|
||||
|
||||
|
||||
{ "comment" : "Mouse turn need to be small continuous increments",
|
||||
|
@ -44,9 +39,24 @@
|
|||
]
|
||||
},
|
||||
|
||||
|
||||
{ "from": { "makeAxis" : [
|
||||
["Keyboard.A", "Keyboard.Left" ],
|
||||
["Keyboard.D", "Keyboard.Right"]
|
||||
["Keyboard.Left" ],
|
||||
["Keyboard.Right"]
|
||||
]
|
||||
},
|
||||
"when": ["Application.InHMD", "Application.SnapTurn", "!Keyboard.Shift"],
|
||||
"to": "Actions.StepYaw",
|
||||
"filters":
|
||||
[
|
||||
{ "type": "pulse", "interval": 0.5, "resetOnZero": true },
|
||||
{ "type": "scale", "scale": 22.5 }
|
||||
]
|
||||
},
|
||||
|
||||
{ "from": { "makeAxis" : [
|
||||
["Keyboard.A"],
|
||||
["Keyboard.D"]
|
||||
]
|
||||
},
|
||||
"when": [ "Application.InHMD", "Application.SnapTurn" ],
|
||||
|
@ -59,26 +69,39 @@
|
|||
},
|
||||
|
||||
{ "from": { "makeAxis" : [
|
||||
["Keyboard.A", "Keyboard.Left", "Keyboard.TouchpadLeft"],
|
||||
["Keyboard.D", "Keyboard.Right", "Keyboard.TouchpadRight"]
|
||||
["Keyboard.Left"],
|
||||
["Keyboard.Right"]
|
||||
]
|
||||
},
|
||||
"when": ["Application.CameraFirstPerson", "!Keyboard.Shift"],
|
||||
"to": "Actions.Yaw"
|
||||
},
|
||||
|
||||
{ "from": { "makeAxis" : [
|
||||
["Keyboard.Left"],
|
||||
["Keyboard.Right"]
|
||||
]
|
||||
},
|
||||
"when": ["Application.CameraThirdPerson", "!Keyboard.Shift"],
|
||||
"to": "Actions.Yaw"
|
||||
},
|
||||
|
||||
{ "from": { "makeAxis" : [
|
||||
["Keyboard.A", "Keyboard.TouchpadLeft"],
|
||||
["Keyboard.D", "Keyboard.TouchpadRight"]
|
||||
]
|
||||
},
|
||||
"when": "Application.CameraFirstPerson",
|
||||
"to": "Actions.Yaw"
|
||||
},
|
||||
{ "from": { "makeAxis" : [
|
||||
["Keyboard.A", "Keyboard.Left", "Keyboard.TouchpadLeft"],
|
||||
["Keyboard.D", "Keyboard.Right", "Keyboard.TouchpadRight"]
|
||||
["Keyboard.A", "Keyboard.TouchpadLeft"],
|
||||
["Keyboard.D", "Keyboard.TouchpadRight"]
|
||||
]
|
||||
},
|
||||
"when": "Application.CameraThirdPerson",
|
||||
"to": "Actions.Yaw"
|
||||
},
|
||||
{ "from": { "makeAxis" : [ ["Keyboard.A"], ["Keyboard.D"] ] },
|
||||
"when": "Application.CameraFSM",
|
||||
"to": "Actions.Yaw"
|
||||
},
|
||||
|
||||
{ "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] },
|
||||
"when": "Keyboard.RightMouseButton",
|
||||
"to": "Actions.Yaw",
|
||||
|
@ -90,14 +113,10 @@
|
|||
|
||||
{ "from": "Keyboard.W", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_FORWARD" },
|
||||
{ "from": "Keyboard.S", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
||||
{ "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" },
|
||||
{ "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" },
|
||||
{ "from": "Keyboard.Left", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" },
|
||||
{ "from": "Keyboard.Right", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_RIGHT" },
|
||||
{ "from": "Keyboard.Shift", "when": ["!Keyboard.Left", "!Keyboard.Right"], "to": "Actions.SPRINT" },
|
||||
{ "from": "Keyboard.Control", "to": "Actions.VERTICAL_DOWN" },
|
||||
{ "from": "Keyboard.Left", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" },
|
||||
{ "from": "Keyboard.Right", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" },
|
||||
{ "from": "Keyboard.Down", "when": "Keyboard.Shift", "to": "Actions.PITCH_DOWN" },
|
||||
{ "from": "Keyboard.Up", "when": "Keyboard.Shift", "to": "Actions.PITCH_UP" },
|
||||
{ "from": "Keyboard.Up", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_FORWARD" },
|
||||
{ "from": "Keyboard.Up", "when": "Application.CameraThirdPerson", "to": "Actions.LONGITUDINAL_FORWARD" },
|
||||
{ "from": "Keyboard.Down", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
||||
|
@ -128,7 +147,7 @@
|
|||
{ "from": "Keyboard.MouseWheelLeft", "to": "Actions.BOOM_OUT", "filters": [ { "type": "scale", "scale": 0.02 } ]},
|
||||
{ "from": "Keyboard.MouseWheelRight", "to": "Actions.BOOM_IN", "filters": [ { "type": "scale", "scale": 0.02 } ]},
|
||||
|
||||
{ "from": "Keyboard.Space", "to": "Actions.SHIFT" },
|
||||
{ "from": "Keyboard.Space", "to": "Actions.VERTICAL_UP" },
|
||||
{ "from": "Keyboard.R", "to": "Actions.ACTION1" },
|
||||
{ "from": "Keyboard.T", "to": "Actions.ACTION2" },
|
||||
{ "from": "Keyboard.Tab", "to": "Actions.ContextMenu" }
|
||||
|
|
Before Width: | Height: | Size: 215 KiB After Width: | Height: | Size: 241 KiB |
16
interface/resources/icons/+android/stats.svg
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<path d="M45,40.3l-7.1-6.3c-0.7-0.7-1.7-0.9-2.6-0.8c2.3-2.9,3.6-6.4,3.6-10.3c0-9.9-7.9-17-16.9-17.1c-9.3,0-16.7,7.4-17,16.3
|
||||
C4.7,32,12.6,39.6,21.9,39.7c4.2,0,8.1-1.5,11-4.1c-0.2,0.9,0.1,1.9,0.9,2.6l7.1,6.3c1.2,1.1,3,1.1,4.1-0.1c0.5-0.6,0.8-1.3,0.8-2
|
||||
C45.9,41.6,45.6,40.8,45,40.3z M22.5,35.4c-7.3,0.3-13.2-5.5-13.2-12.8C9.2,15.8,14.9,10.1,21.9,10c7-0.1,12.8,5.6,12.8,12.7
|
||||
C34.7,29.7,29.2,35.2,22.5,35.4z"/>
|
||||
<path d="M26.2,19.9c-0.5,1.2-0.9,2.4-1.3,3.5c-0.6,1.6-1.2,3.2-1.8,4.8c-0.3,0.8-0.8,1.2-1.6,1.2c-0.8,0-1.2-0.3-1.6-1.2
|
||||
c-0.9-2.1-1.8-4.2-2.6-6.2c0-0.1-0.1-0.2-0.2-0.5c-0.3,0.5-0.6,0.9-0.8,1.4c-0.4,0.9-1.1,1.3-2.1,1.2c-0.6,0-1.2,0-1.8,0
|
||||
c-0.9,0-1.5-0.6-1.5-1.4c-0.1-0.7,0.5-1.4,1.3-1.5c0.2,0,0.4-0.1,0.5-0.1c0.7,0.1,1.1-0.2,1.5-0.9c0.5-1,1.1-2,1.6-3
|
||||
c0.3-0.6,0.8-1,1.6-1c0.7,0,1.2,0.5,1.5,1.1c0.8,1.8,1.6,3.7,2.3,5.5c0.1,0.2,0.1,0.3,0.3,0.6c0.2-0.6,0.4-1.1,0.6-1.6
|
||||
c0.9-2.4,1.8-4.8,2.7-7.2c0.3-0.9,0.8-1.2,1.5-1.2c0.8,0,1.3,0.4,1.6,1.2c0.7,1.8,1.3,3.6,1.9,5.5c0.4,1,0.1,0.8,1.1,0.8
|
||||
c0.4,0,0.8,0,1.1,0.1c0.7,0.2,1.2,0.8,1.1,1.6c-0.1,0.7-0.6,1.3-1.4,1.3c-1,0-2,0-3,0c-0.7,0-1.1-0.4-1.4-1.1
|
||||
C27,22.1,26.6,21.1,26.2,19.9z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
19
interface/resources/icons/tablet-icons/market-a-msg.svg
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#EF3B4E;}
|
||||
</style>
|
||||
<circle class="st0" cx="44.1" cy="6" r="5.6"/>
|
||||
<g>
|
||||
<path d="M17.6,33.4c0.4,0,0.6,0,0.9,0c6.5,0,13,0,19.5,0c1.3,0,2,0.5,2.2,1.5c0.2,1.1-0.5,2.2-1.6,2.3c-0.2,0-0.5,0-0.7,0
|
||||
c-7.4,0-14.8,0-22.3,0c-2,0-2.7-0.9-2.2-2.9c0.3-1.1,0.5-2.3,0.8-3.4c0.3-0.9,0.2-1.8,0-2.8C12.7,22,11.1,15.9,9.6,9.8
|
||||
C9.4,9.3,9.2,9.1,8.7,9.1c-1.3,0-2.6,0-3.9,0c-1.2,0-2-0.8-2-1.9c0-1.1,0.8-1.9,2-1.9c2,0,3.9,0,5.9,0c1.1,0,1.7,0.5,2,1.5
|
||||
c0.4,1.3,1.6,4.8,1.9,6.2c2.2,0.1,4.3,0.2,6.5,0.3c6.7,0.3,11.4,0.4,18.1,0.7c1.2,0.1,2.3,0.1,3.5,0.2c1.4,0.1,2.2,1.4,1.6,2.6
|
||||
c-0.9,2.5-3.5,8.5-4.4,10.4c-0.4,0.8-1,1.1-1.9,1.2c-5.9,0.3-12.5,0.5-18.4,0.7c-0.8,0-1.2,0.3-1.3,1.1
|
||||
C18.4,30.7,17.7,32.9,17.6,33.4z"/>
|
||||
<path d="M39.2,42c0,1.9-1.6,3.6-3.5,3.6c-2,0-3.6-1.6-3.6-3.6c0-1.9,1.6-3.6,3.6-3.6C37.6,38.5,39.2,40.1,39.2,42z"/>
|
||||
<path d="M17.8,41.9c0,1.9-1.7,3.5-3.6,3.5c-1.9,0-3.5-1.7-3.5-3.6c0-2,1.6-3.6,3.6-3.6C16.2,38.3,17.8,39.9,17.8,41.9z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -1,64 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 50 50"
|
||||
style="enable-background:new 0 0 50 50;"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="market-a.svg"><metadata
|
||||
id="metadata20"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs18" /><sodipodi:namedview
|
||||
pagecolor="#ff0000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="852"
|
||||
inkscape:window-height="480"
|
||||
id="namedview16"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><g
|
||||
id="g8"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M45.4,13.7c-1.6-0.1-3.2,0-4.8,0c-7.5,0-18,0-25.7,0c-0.6-1.8-1-3.5-1.7-5.2C13,7.9,12.2,7.1,11.5,7 C9.4,6.7,7.3,6.7,5.2,6.7c-1.1,0-1.9,0.5-1.9,1.7c0,1.2,0.8,1.7,1.9,1.7c1.3,0,2.7,0,4,0.1c0.5,0.1,1.2,0.6,1.4,1.1 c0.9,2.6,1.7,5.2,2.5,7.8c1.3,4.3,1.8,5.5,3.1,10.2c0.6,2.3,1.2,2.8,2.2,3.3c1.1,0.4,2.1,0.4,2.1,0.4h1.8c4.6,0,12.2,0,16.8,0 c1.1,0,2.1-0.1,2.6-1.4c1.9-5.1,3.8-10.2,5.7-15.3C47.8,14.7,47.1,13.8,45.4,13.7z M38.9,28.7c-0.1,0.3-0.8,0.7-1.2,0.7 c-4.6,0-12.2,0-16.8,0c-0.4,0-1.1-0.3-1.2-0.7c-1.3-3.8-2.4-7.6-3.7-11.5h27.1C41.8,21.2,40.4,24.9,38.9,28.7z"
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M17.2,37.3L17.2,37.3c-1,0-1.7,0.2-2.2,0.7c-0.6,0.6-0.9,1.3-0.9,2.3c0,1,0.3,1.8,0.8,2.4 c0.6,0.6,1.4,0.9,2.2,0.9c1.8,0,3.2-1.4,3.2-3.2c0-0.9-0.3-1.7-0.9-2.3C18.8,37.6,18,37.3,17.2,37.3z"
|
||||
id="path12"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M35.1,37.3L35.1,37.3c-1,0-1.7,0.2-2.2,0.7c-0.6,0.6-0.9,1.4-0.9,2.4c0,1,0.3,1.8,0.8,2.4 c0.6,0.6,1.3,0.9,2.2,0.9c1.8,0,3.2-1.5,3.2-3.3c0-0.9-0.3-1.6-0.9-2.2C36.8,37.6,36,37.3,35.1,37.3z"
|
||||
id="path14"
|
||||
style="fill:#000000;fill-opacity:1" /></g></g></svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M17.6,33.4c0.4,0,0.6,0,0.9,0c6.5,0,13,0,19.5,0c1.3,0,2,0.5,2.2,1.5c0.2,1.1-0.5,2.2-1.6,2.3c-0.2,0-0.5,0-0.7,0
|
||||
c-7.4,0-14.8,0-22.3,0c-2,0-2.7-0.9-2.2-2.9c0.3-1.1,0.5-2.3,0.8-3.4c0.3-0.9,0.2-1.8,0-2.8C12.7,22,11.1,15.9,9.6,9.8
|
||||
C9.4,9.3,9.2,9.1,8.7,9.1c-1.3,0-2.6,0-3.9,0c-1.2,0-2-0.8-2-1.9c0-1.1,0.8-1.9,2-1.9c2,0,3.9,0,5.9,0c1.1,0,1.7,0.5,2,1.5
|
||||
c0.4,1.3,1.6,4.8,1.9,6.2c2.2,0.1,4.3,0.2,6.5,0.3c6.7,0.3,11.4,0.4,18.1,0.7c1.2,0.1,2.3,0.1,3.5,0.2c1.4,0.1,2.2,1.4,1.6,2.6
|
||||
c-0.9,2.5-3.5,8.5-4.4,10.4c-0.4,0.8-1,1.1-1.9,1.2c-5.9,0.3-12.5,0.5-18.4,0.7c-0.8,0-1.2,0.3-1.3,1.1
|
||||
C18.4,30.7,17.7,32.9,17.6,33.4z"/>
|
||||
<path d="M39.2,42c0,1.9-1.6,3.6-3.5,3.6c-2,0-3.6-1.6-3.6-3.6c0-1.9,1.6-3.6,3.6-3.6C37.6,38.5,39.2,40.1,39.2,42z"/>
|
||||
<path d="M17.8,41.9c0,1.9-1.7,3.5-3.6,3.5c-1.9,0-3.5-1.7-3.5-3.6c0-2,1.6-3.6,3.6-3.6C16.2,38.3,17.8,39.9,17.8,41.9z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 1.2 KiB |
21
interface/resources/icons/tablet-icons/market-i-msg.svg
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#EF3B4E;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
</style>
|
||||
<circle class="st0" cx="44.1" cy="6" r="5.6"/>
|
||||
<g>
|
||||
<path class="st1" d="M17.6,33.4c0.4,0,0.6,0,0.9,0c6.5,0,13,0,19.5,0c1.3,0,2,0.5,2.2,1.5c0.2,1.1-0.5,2.2-1.6,2.3
|
||||
c-0.2,0-0.5,0-0.7,0c-7.4,0-14.8,0-22.3,0c-2,0-2.7-0.9-2.2-2.9c0.3-1.1,0.5-2.3,0.8-3.4c0.3-0.9,0.2-1.8,0-2.8
|
||||
C12.7,22,11.1,15.9,9.6,9.8C9.4,9.3,9.2,9.1,8.7,9.1c-1.3,0-2.6,0-3.9,0c-1.2,0-2-0.8-2-1.9c0-1.1,0.8-1.9,2-1.9c2,0,3.9,0,5.9,0
|
||||
c1.1,0,1.7,0.5,2,1.5c0.4,1.3,1.6,4.8,1.9,6.2c2.2,0.1,4.3,0.2,6.5,0.3c6.7,0.3,11.4,0.4,18.1,0.7c1.2,0.1,2.3,0.1,3.5,0.2
|
||||
c1.4,0.1,2.2,1.4,1.6,2.6c-0.9,2.5-3.5,8.5-4.4,10.4c-0.4,0.8-1,1.1-1.9,1.2c-5.9,0.3-12.5,0.5-18.4,0.7c-0.8,0-1.2,0.3-1.3,1.1
|
||||
C18.4,30.7,17.7,32.9,17.6,33.4z"/>
|
||||
<path class="st1" d="M39.2,42c0,1.9-1.6,3.6-3.5,3.6c-2,0-3.6-1.6-3.6-3.6c0-1.9,1.6-3.6,3.6-3.6C37.6,38.5,39.2,40.1,39.2,42z"/>
|
||||
<path class="st1" d="M17.8,41.9c0,1.9-1.7,3.5-3.6,3.5c-1.9,0-3.5-1.7-3.5-3.6c0-2,1.6-3.6,3.6-3.6C16.2,38.3,17.8,39.9,17.8,41.9z
|
||||
"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -1,23 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<g>
|
||||
<path class="st0" d="M45.4,13.7c-1.6-0.1-3.2,0-4.8,0c-7.5,0-18,0-25.7,0c-0.6-1.8-1-3.5-1.7-5.2C13,7.9,12.2,7.1,11.5,7
|
||||
C9.4,6.7,7.3,6.7,5.2,6.7c-1.1,0-1.9,0.5-1.9,1.7c0,1.2,0.8,1.7,1.9,1.7c1.3,0,2.7,0,4,0.1c0.5,0.1,1.2,0.6,1.4,1.1
|
||||
c0.9,2.6,1.7,5.2,2.5,7.8c1.3,4.3,1.8,5.5,3.1,10.2c0.6,2.3,1.2,2.8,2.2,3.3c1.1,0.4,2.1,0.4,2.1,0.4h1.8c4.6,0,12.2,0,16.8,0
|
||||
c1.1,0,2.1-0.1,2.6-1.4c1.9-5.1,3.8-10.2,5.7-15.3C47.8,14.7,47.1,13.8,45.4,13.7z M38.9,28.7c-0.1,0.3-0.8,0.7-1.2,0.7
|
||||
c-4.6,0-12.2,0-16.8,0c-0.4,0-1.1-0.3-1.2-0.7c-1.3-3.8-2.4-7.6-3.7-11.5h27.1C41.8,21.2,40.4,24.9,38.9,28.7z"/>
|
||||
<path class="st0" d="M17.2,37.3L17.2,37.3c-1,0-1.7,0.2-2.2,0.7c-0.6,0.6-0.9,1.3-0.9,2.3c0,1,0.3,1.8,0.8,2.4
|
||||
c0.6,0.6,1.4,0.9,2.2,0.9c1.8,0,3.2-1.4,3.2-3.2c0-0.9-0.3-1.7-0.9-2.3C18.8,37.6,18,37.3,17.2,37.3z"/>
|
||||
<path class="st0" d="M35.1,37.3L35.1,37.3c-1,0-1.7,0.2-2.2,0.7c-0.6,0.6-0.9,1.4-0.9,2.4c0,1,0.3,1.8,0.8,2.4
|
||||
c0.6,0.6,1.3,0.9,2.2,0.9c1.8,0,3.2-1.5,3.2-3.3c0-0.9-0.3-1.6-0.9-2.2C36.8,37.6,36,37.3,35.1,37.3z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M17.6,33.4c0.4,0,0.6,0,0.9,0c6.5,0,13,0,19.5,0c1.3,0,2,0.5,2.2,1.5c0.2,1.1-0.5,2.2-1.6,2.3
|
||||
c-0.2,0-0.5,0-0.7,0c-7.4,0-14.8,0-22.3,0c-2,0-2.7-0.9-2.2-2.9c0.3-1.1,0.5-2.3,0.8-3.4c0.3-0.9,0.2-1.8,0-2.8
|
||||
C12.7,22,11.1,15.9,9.6,9.8C9.4,9.3,9.2,9.1,8.7,9.1c-1.3,0-2.6,0-3.9,0c-1.2,0-2-0.8-2-1.9c0-1.1,0.8-1.9,2-1.9c2,0,3.9,0,5.9,0
|
||||
c1.1,0,1.7,0.5,2,1.5c0.4,1.3,1.6,4.8,1.9,6.2c2.2,0.1,4.3,0.2,6.5,0.3c6.7,0.3,11.4,0.4,18.1,0.7c1.2,0.1,2.3,0.1,3.5,0.2
|
||||
c1.4,0.1,2.2,1.4,1.6,2.6c-0.9,2.5-3.5,8.5-4.4,10.4c-0.4,0.8-1,1.1-1.9,1.2c-5.9,0.3-12.5,0.5-18.4,0.7c-0.8,0-1.2,0.3-1.3,1.1
|
||||
C18.4,30.7,17.7,32.9,17.6,33.4z"/>
|
||||
<path class="st0" d="M39.2,42c0,1.9-1.6,3.6-3.5,3.6c-2,0-3.6-1.6-3.6-3.6c0-1.9,1.6-3.6,3.6-3.6C37.6,38.5,39.2,40.1,39.2,42z"/>
|
||||
<path class="st0" d="M17.8,41.9c0,1.9-1.7,3.5-3.6,3.5c-1.9,0-3.5-1.7-3.5-3.6c0-2,1.6-3.6,3.6-3.6C16.2,38.3,17.8,39.9,17.8,41.9z
|
||||
"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -67,6 +67,10 @@ Item {
|
|||
fill: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
QmlHifi.WindowHeader {
|
||||
id: header
|
||||
iconSource: "../../../icons/goto-i.svg"
|
||||
|
|
9
interface/resources/qml/+android/StatText.qml
Normal file
|
@ -0,0 +1,9 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
|
||||
Text {
|
||||
color: "white";
|
||||
style: Text.Outline;
|
||||
styleColor: "black";
|
||||
font.pixelSize: 15;
|
||||
}
|
321
interface/resources/qml/controls-uit/FilterBar.qml
Normal file
|
@ -0,0 +1,321 @@
|
|||
//
|
||||
// FilterBar.qml
|
||||
//
|
||||
// Created by Zach Fox on 17 Feb 2018-03-12
|
||||
// Copyright 2018 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.9
|
||||
import QtQuick.Controls 2.2
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import "../styles-uit"
|
||||
import "../controls-uit" as HifiControls
|
||||
|
||||
Item {
|
||||
id: root;
|
||||
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
|
||||
readonly property bool isFaintGrayColorScheme: colorScheme == hifi.colorSchemes.faintGray
|
||||
property bool error: false;
|
||||
property alias textFieldHeight: textField.height;
|
||||
property string placeholderText;
|
||||
property alias dropdownHeight: dropdownContainer.height;
|
||||
property alias text: textField.text;
|
||||
property alias primaryFilterChoices: filterBarModel;
|
||||
property int primaryFilter_index: -1;
|
||||
property string primaryFilter_filterName: "";
|
||||
property string primaryFilter_displayName: "";
|
||||
signal accepted;
|
||||
|
||||
onPrimaryFilter_indexChanged: {
|
||||
if (primaryFilter_index === -1) {
|
||||
primaryFilter_filterName = "";
|
||||
primaryFilter_displayName = "";
|
||||
} else {
|
||||
primaryFilter_filterName = filterBarModel.get(primaryFilter_index).filterName;
|
||||
primaryFilter_displayName = filterBarModel.get(primaryFilter_index).displayName;
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: textField;
|
||||
|
||||
anchors.top: parent.top;
|
||||
anchors.right: parent.right;
|
||||
anchors.left: parent.left;
|
||||
|
||||
font.family: "Fira Sans"
|
||||
font.pixelSize: hifi.fontSizes.textFieldInput;
|
||||
|
||||
placeholderText: root.primaryFilter_index === -1 ? root.placeholderText : "";
|
||||
|
||||
TextMetrics {
|
||||
id: primaryFilterTextMetrics;
|
||||
font.family: "FiraSans Regular";
|
||||
font.pixelSize: hifi.fontSizes.textFieldInput;
|
||||
font.capitalization: Font.AllUppercase;
|
||||
text: root.primaryFilter_displayName;
|
||||
}
|
||||
|
||||
// workaround for https://bugreports.qt.io/browse/QTBUG-49297
|
||||
Keys.onPressed: {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
event.accepted = true;
|
||||
|
||||
// emit accepted signal manually
|
||||
if (acceptableInput) {
|
||||
root.accepted();
|
||||
root.forceActiveFocus();
|
||||
}
|
||||
break;
|
||||
case Qt.Key_Backspace:
|
||||
if (textField.text === "") {
|
||||
primaryFilter_index = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
root.forceActiveFocus();
|
||||
}
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (!activeFocus) {
|
||||
dropdownContainer.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
color: {
|
||||
if (isLightColorScheme) {
|
||||
if (textField.activeFocus) {
|
||||
hifi.colors.black
|
||||
} else {
|
||||
hifi.colors.lightGray
|
||||
}
|
||||
} else if (isFaintGrayColorScheme) {
|
||||
if (textField.activeFocus) {
|
||||
hifi.colors.black
|
||||
} else {
|
||||
hifi.colors.lightGray
|
||||
}
|
||||
} else {
|
||||
if (textField.activeFocus) {
|
||||
hifi.colors.white
|
||||
} else {
|
||||
hifi.colors.lightGrayText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
id: mainFilterBarRectangle;
|
||||
|
||||
color: {
|
||||
if (isLightColorScheme) {
|
||||
if (textField.activeFocus) {
|
||||
hifi.colors.white
|
||||
} else {
|
||||
hifi.colors.textFieldLightBackground
|
||||
}
|
||||
} else if (isFaintGrayColorScheme) {
|
||||
if (textField.activeFocus) {
|
||||
hifi.colors.white
|
||||
} else {
|
||||
hifi.colors.faintGray50
|
||||
}
|
||||
} else {
|
||||
if (textField.activeFocus) {
|
||||
hifi.colors.black
|
||||
} else {
|
||||
hifi.colors.baseGrayShadow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
border.color: textField.error ? hifi.colors.redHighlight :
|
||||
(textField.activeFocus ? hifi.colors.primaryHighlight : (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray))
|
||||
border.width: 1
|
||||
radius: 4
|
||||
|
||||
Item {
|
||||
id: searchButtonContainer;
|
||||
anchors.left: parent.left;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
height: parent.height;
|
||||
width: 42;
|
||||
|
||||
// Search icon
|
||||
HiFiGlyphs {
|
||||
id: searchIcon;
|
||||
text: hifi.glyphs.search
|
||||
color: textField.color
|
||||
size: 40;
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: paintedWidth;
|
||||
}
|
||||
|
||||
// Carat
|
||||
HiFiGlyphs {
|
||||
text: hifi.glyphs.caratDn;
|
||||
color: textField.color;
|
||||
size: 40;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 15;
|
||||
width: paintedWidth;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
onClicked: {
|
||||
textField.forceActiveFocus();
|
||||
dropdownContainer.visible = !dropdownContainer.visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
z: 999;
|
||||
id: primaryFilterContainer;
|
||||
color: textField.activeFocus ? hifi.colors.faintGray : hifi.colors.white;
|
||||
width: primaryFilterTextMetrics.tightBoundingRect.width + 14;
|
||||
height: parent.height - 8;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.left: searchButtonContainer.right;
|
||||
anchors.leftMargin: 4;
|
||||
visible: primaryFilterText.text !== "";
|
||||
radius: height/2;
|
||||
|
||||
FiraSansRegular {
|
||||
id: primaryFilterText;
|
||||
text: root.primaryFilter_displayName;
|
||||
anchors.fill: parent;
|
||||
color: textField.activeFocus ? hifi.colors.black : hifi.colors.lightGray;
|
||||
horizontalAlignment: Text.AlignHCenter;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
size: hifi.fontSizes.textFieldInput;
|
||||
font.capitalization: Font.AllUppercase;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
onClicked: {
|
||||
textField.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "Clear" button
|
||||
HiFiGlyphs {
|
||||
text: hifi.glyphs.error
|
||||
color: textField.color
|
||||
size: 40
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: hifi.dimensions.textPadding - 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.text !== "" || root.primaryFilter_index !== -1;
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
onClicked: {
|
||||
root.text = "";
|
||||
root.primaryFilter_index = -1;
|
||||
dropdownContainer.visible = false;
|
||||
textField.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectedTextColor: hifi.colors.black
|
||||
selectionColor: hifi.colors.primaryHighlight
|
||||
leftPadding: 44 + (root.primaryFilter_index === -1 ? 0 : primaryFilterTextMetrics.tightBoundingRect.width + 20);
|
||||
rightPadding: 44;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: dropdownContainer;
|
||||
visible: false;
|
||||
height: 50 * filterBarModel.count;
|
||||
width: parent.width;
|
||||
anchors.top: textField.bottom;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
color: hifi.colors.white;
|
||||
|
||||
ListModel {
|
||||
id: filterBarModel;
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: dropdownListView;
|
||||
interactive: false;
|
||||
anchors.fill: parent;
|
||||
model: filterBarModel;
|
||||
delegate: Rectangle {
|
||||
id: dropDownButton;
|
||||
color: hifi.colors.white;
|
||||
width: parent.width;
|
||||
height: 50;
|
||||
|
||||
RalewaySemiBold {
|
||||
id: dropDownButtonText;
|
||||
text: model.displayName;
|
||||
anchors.fill: parent;
|
||||
anchors.leftMargin: 12;
|
||||
color: hifi.colors.baseGray;
|
||||
horizontalAlignment: Text.AlignLeft;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
size: 18;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: true;
|
||||
propagateComposedEvents: false;
|
||||
onEntered: {
|
||||
dropDownButton.color = hifi.colors.blueHighlight;
|
||||
}
|
||||
onExited: {
|
||||
dropDownButton.color = hifi.colors.white;
|
||||
}
|
||||
onClicked: {
|
||||
textField.forceActiveFocus();
|
||||
root.primaryFilter_index = index;
|
||||
dropdownContainer.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
anchors.fill: dropdownContainer;
|
||||
horizontalOffset: 0;
|
||||
verticalOffset: 4;
|
||||
radius: 4.0;
|
||||
samples: 9
|
||||
color: Qt.rgba(0, 0, 0, 0.25);
|
||||
source: dropdownContainer;
|
||||
visible: dropdownContainer.visible;
|
||||
}
|
||||
|
||||
function changeFilterByDisplayName(name) {
|
||||
for (var i = 0; i < filterBarModel.count; i++) {
|
||||
if (filterBarModel.get(i).displayName === name) {
|
||||
root.primaryFilter_index = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Passed displayName not found in filterBarModel! primaryFilter unchanged.");
|
||||
}
|
||||
}
|
|
@ -163,8 +163,10 @@ TextField {
|
|||
text: textField.label
|
||||
colorScheme: textField.colorScheme
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: 3
|
||||
wrapMode: Text.WordWrap
|
||||
visible: label != ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -270,7 +270,9 @@ ModalWindow {
|
|||
onTriggered: {
|
||||
root.result = null;
|
||||
root.canceled();
|
||||
root.destroy();
|
||||
// FIXME we are leaking memory to avoid a crash
|
||||
// root.destroy();
|
||||
visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,7 +294,9 @@ ModalWindow {
|
|||
}
|
||||
root.result = JSON.stringify(result);
|
||||
root.selected(root.result);
|
||||
root.destroy();
|
||||
// FIXME we are leaking memory to avoid a crash
|
||||
// root.destroy();
|
||||
visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -169,7 +169,9 @@ ModalWindow {
|
|||
shortcut: Qt.Key_Escape
|
||||
onTriggered: {
|
||||
root.canceled();
|
||||
root.destroy();
|
||||
// FIXME we are leaking memory to avoid a crash
|
||||
// root.destroy();
|
||||
visible = false;
|
||||
}
|
||||
}
|
||||
Action {
|
||||
|
@ -179,7 +181,9 @@ ModalWindow {
|
|||
onTriggered: {
|
||||
root.result = items ? comboBox.currentText : textResult.text
|
||||
root.selected(root.result);
|
||||
root.destroy();
|
||||
// FIXME we are leaking memory to avoid a crash
|
||||
// root.destroy();
|
||||
visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,34 +65,33 @@ TabletModalWindow {
|
|||
id: modalWindowItem
|
||||
width: parent.width - 12
|
||||
height: 240
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
readonly property int minWidth: 470
|
||||
readonly property int maxWidth: 470
|
||||
readonly property int minWidth: modalWindowItem.width
|
||||
readonly property int maxWidth: modalWindowItem.width
|
||||
readonly property int minHeight: 120
|
||||
readonly property int maxHeight: 720
|
||||
|
||||
function resize() {
|
||||
var targetWidth = Math.max(titleWidth, 470)
|
||||
var targetWidth = Math.max(titleWidth, modalWindowItem.width)
|
||||
var targetHeight = (items ? comboBox.controlHeight : textResult.controlHeight) + 5 * hifi.dimensions.contentSpacing.y + buttons.height
|
||||
modalWindowItem.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth);
|
||||
modalWindowItem.height = ((targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)) + ((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + 2 * hifi.dimensions.contentSpacing.y) : 0) + modalWindowItem.frameMarginTop
|
||||
modalWindowItem.height = ((targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)) + modalWindowItem.frameMarginTop
|
||||
modalWindowItem.y = (root.height - (modalWindowItem.height + ((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + 2 * hifi.dimensions.contentSpacing.y) : 0))) / 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: keyboard.top;
|
||||
bottom: buttons.top;
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
margins: 0
|
||||
bottomMargin: 2 * hifi.dimensions.contentSpacing.y
|
||||
topMargin: modalWindowItem.frameMarginTop
|
||||
}
|
||||
|
||||
// FIXME make a text field type that can be bound to a history for autocompletion
|
||||
|
@ -106,6 +105,7 @@ TabletModalWindow {
|
|||
right: parent.right;
|
||||
bottom: parent.bottom
|
||||
leftMargin: 5
|
||||
rightMargin: 5
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,22 +124,6 @@ TabletModalWindow {
|
|||
}
|
||||
}
|
||||
|
||||
property alias keyboardOverride: root.keyboardOverride
|
||||
property alias keyboardRaised: root.keyboardRaised
|
||||
property alias punctuationMode: root.punctuationMode
|
||||
|
||||
Keyboard {
|
||||
id: keyboard
|
||||
raised: keyboardEnabled && keyboardRaised
|
||||
numeric: punctuationMode
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: buttons.top
|
||||
bottomMargin: raised ? 2 * hifi.dimensions.contentSpacing.y : 0
|
||||
}
|
||||
}
|
||||
|
||||
Flow {
|
||||
id: buttons
|
||||
focus: true
|
||||
|
@ -150,6 +134,7 @@ TabletModalWindow {
|
|||
bottom: parent.bottom
|
||||
right: parent.right
|
||||
margins: 0
|
||||
rightMargin: hifi.dimensions.borderRadius
|
||||
bottomMargin: hifi.dimensions.contentSpacing.y
|
||||
}
|
||||
Button { action: cancelAction }
|
||||
|
@ -177,7 +162,17 @@ TabletModalWindow {
|
|||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
Keyboard {
|
||||
id: keyboard
|
||||
raised: keyboardEnabled && keyboardRaised
|
||||
numeric: punctuationMode
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: modalWindowItem.bottom
|
||||
}
|
||||
}
|
||||
Keys.onPressed: {
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
|
71
interface/resources/qml/hifi/+android/StatsBar.qml
Normal file
|
@ -0,0 +1,71 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
import Qt.labs.settings 1.0
|
||||
import "../../styles-uit"
|
||||
import "../../controls-uit" as HifiControlsUit
|
||||
import "../../controls" as HifiControls
|
||||
import ".."
|
||||
|
||||
Item {
|
||||
id: bar
|
||||
x:300
|
||||
y:0
|
||||
width: 300
|
||||
height: 300
|
||||
z: -1
|
||||
|
||||
signal sendToScript(var message);
|
||||
signal windowClosed();
|
||||
|
||||
property bool shown: true
|
||||
|
||||
onShownChanged: {
|
||||
bar.visible = shown;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill : parent
|
||||
color: "transparent"
|
||||
Flow {
|
||||
id: flowMain
|
||||
spacing: 10
|
||||
flow: Flow.TopToBottom
|
||||
layoutDirection: Flow.TopToBottom
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// put on bottom
|
||||
x = 300;
|
||||
y = 0;
|
||||
width = 300;
|
||||
height = 300;
|
||||
}
|
||||
|
||||
function addButton(properties) {
|
||||
var component = Qt.createComponent("button.qml");
|
||||
if (component.status == Component.Ready) {
|
||||
var button = component.createObject(flowMain);
|
||||
// copy all properites to button
|
||||
var keys = Object.keys(properties).forEach(function (key) {
|
||||
button[key] = properties[key];
|
||||
});
|
||||
return button;
|
||||
} else if( component.status == Component.Error) {
|
||||
console.log("Load button errors " + component.errorString());
|
||||
}
|
||||
}
|
||||
|
||||
function urlHelper(src) {
|
||||
if (src.match(/\bhttp/)) {
|
||||
return src;
|
||||
} else {
|
||||
return "../../../" + src;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -58,6 +58,10 @@ Item {
|
|||
width: parent ? parent.width : 0
|
||||
height: parent ? parent.height : 0
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: android.color.gradientTop }
|
||||
GradientStop { position: 1.0; color: android.color.gradientBottom }
|
||||
|
|
|
@ -118,7 +118,7 @@ Item {
|
|||
tabletRoot.playButtonClickSound();
|
||||
}*/
|
||||
}
|
||||
onEntered: {
|
||||
onPressed: {
|
||||
button.isEntered = true;
|
||||
button.entered();
|
||||
if (button.isActive) {
|
||||
|
@ -127,7 +127,7 @@ Item {
|
|||
button.state = "hover state";
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
onReleased: {
|
||||
button.isEntered = false;
|
||||
button.exited()
|
||||
if (button.isActive) {
|
||||
|
|
|
@ -24,6 +24,7 @@ Windows.ScrollingWindow {
|
|||
objectName: "AssetServer"
|
||||
title: "Asset Browser"
|
||||
resizable: true
|
||||
opacity: parent.opacity
|
||||
destroyOnHidden: true
|
||||
implicitWidth: 384; implicitHeight: 640
|
||||
minSize: Qt.vector2d(200, 300)
|
||||
|
@ -57,7 +58,7 @@ Windows.ScrollingWindow {
|
|||
Component.onDestruction: {
|
||||
assetMappingsModel.autoRefreshEnabled = false;
|
||||
}
|
||||
|
||||
|
||||
function letterbox(headerGlyph, headerText, message) {
|
||||
letterboxMessage.headerGlyph = headerGlyph;
|
||||
letterboxMessage.headerText = headerText;
|
||||
|
@ -144,7 +145,7 @@ Windows.ScrollingWindow {
|
|||
|
||||
function canAddToWorld(path) {
|
||||
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i];
|
||||
|
||||
|
||||
if (selectedItemCount > 1) {
|
||||
return false;
|
||||
}
|
||||
|
@ -153,8 +154,8 @@ Windows.ScrollingWindow {
|
|||
return total | new RegExp(current).test(path);
|
||||
}, false);
|
||||
}
|
||||
|
||||
function canRename() {
|
||||
|
||||
function canRename() {
|
||||
if (treeView.selection.hasSelection && selectedItemCount == 1) {
|
||||
return true;
|
||||
} else {
|
||||
|
@ -198,7 +199,7 @@ Windows.ScrollingWindow {
|
|||
var SHAPE_TYPE_STATIC_MESH = 3;
|
||||
var SHAPE_TYPE_BOX = 4;
|
||||
var SHAPE_TYPE_SPHERE = 5;
|
||||
|
||||
|
||||
var SHAPE_TYPES = [];
|
||||
SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model";
|
||||
|
@ -206,7 +207,7 @@ Windows.ScrollingWindow {
|
|||
SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons";
|
||||
SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere";
|
||||
|
||||
|
||||
var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_SIMPLE_COMPOUND;
|
||||
var DYNAMIC_DEFAULT = false;
|
||||
var prompt = desktop.customInputDialog({
|
||||
|
@ -348,14 +349,14 @@ Windows.ScrollingWindow {
|
|||
}
|
||||
function deleteFile(index) {
|
||||
var paths = [];
|
||||
|
||||
|
||||
if (!index) {
|
||||
for (var i = 0; i < selectedItemCount; ++i) {
|
||||
index = treeView.selection.selectedIndexes[i];
|
||||
paths[i] = assetProxyModel.data(index, 0x100);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!paths) {
|
||||
return;
|
||||
}
|
||||
|
@ -364,13 +365,13 @@ Windows.ScrollingWindow {
|
|||
var items = selectedItemCount.toString();
|
||||
var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101);
|
||||
var typeString = isFolder ? 'folder' : 'file';
|
||||
|
||||
|
||||
if (selectedItemCount > 1) {
|
||||
modalMessage = "You are about to delete " + items + " items \nDo you want to continue?";
|
||||
} else {
|
||||
modalMessage = "You are about to delete the following " + typeString + ":\n" + paths + "\nDo you want to continue?";
|
||||
}
|
||||
|
||||
|
||||
var object = desktop.messageBox({
|
||||
icon: hifi.icons.question,
|
||||
buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No,
|
||||
|
@ -475,11 +476,11 @@ Windows.ScrollingWindow {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
width: pane.contentWidth
|
||||
height: pane.height
|
||||
|
||||
|
||||
// The letterbox used for popup messages
|
||||
LetterboxMessage {
|
||||
id: letterboxMessage;
|
||||
|
@ -541,7 +542,7 @@ Windows.ScrollingWindow {
|
|||
anchors.margins: hifi.dimensions.contentMargin.x + 2 // Extra for border
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
|
||||
treeModel: assetProxyModel
|
||||
selectionMode: SelectionMode.ExtendedSelection
|
||||
headerVisible: true
|
||||
|
@ -561,9 +562,13 @@ Windows.ScrollingWindow {
|
|||
id: bakedColumn
|
||||
title: "Use Baked?"
|
||||
role: "baked"
|
||||
width: 100
|
||||
width: 170
|
||||
}
|
||||
|
||||
|
||||
onSortIndicatorOrderChanged: {
|
||||
Assets.sortProxyModel(sortIndicatorColumn, sortIndicatorOrder);
|
||||
}
|
||||
|
||||
itemDelegate: Loader {
|
||||
id: itemDelegateLoader
|
||||
|
||||
|
@ -599,7 +604,7 @@ Windows.ScrollingWindow {
|
|||
|
||||
}
|
||||
sourceComponent: getComponent()
|
||||
|
||||
|
||||
Component {
|
||||
id: labelComponent
|
||||
FiraSansSemiBold {
|
||||
|
@ -608,15 +613,15 @@ Windows.ScrollingWindow {
|
|||
color: colorScheme == hifi.colorSchemes.light
|
||||
? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight)
|
||||
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
|
||||
|
||||
|
||||
horizontalAlignment: styleData.column === 1 ? TextInput.AlignHCenter : TextInput.AlignLeft
|
||||
|
||||
|
||||
elide: Text.ElideMiddle
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
|
||||
|
||||
acceptedButtons: Qt.NoButton
|
||||
hoverEnabled: true
|
||||
|
||||
|
@ -638,7 +643,7 @@ Windows.ScrollingWindow {
|
|||
color: colorScheme == hifi.colorSchemes.light
|
||||
? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight)
|
||||
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
|
||||
|
||||
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
|
||||
|
@ -725,7 +730,7 @@ Windows.ScrollingWindow {
|
|||
size: hifi.fontSizes.tableText
|
||||
color: colorScheme == hifi.colorSchemes.light ? hifi.colors.black : hifi.colors.lightGrayText
|
||||
}
|
||||
|
||||
|
||||
Timer {
|
||||
id: showTimer
|
||||
interval: 1000
|
||||
|
@ -744,7 +749,7 @@ Windows.ScrollingWindow {
|
|||
treeLabelToolTip.visible = false;
|
||||
}
|
||||
}// End_OF( treeLabelToolTip )
|
||||
|
||||
|
||||
MouseArea {
|
||||
propagateComposedEvents: true
|
||||
anchors.fill: parent
|
||||
|
@ -802,7 +807,7 @@ Windows.ScrollingWindow {
|
|||
anchors.left: treeView.left
|
||||
anchors.right: treeView.right
|
||||
anchors.bottom: uploadSection.top
|
||||
|
||||
|
||||
RalewayRegular {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
|
@ -846,7 +851,7 @@ Windows.ScrollingWindow {
|
|||
|
||||
checked = Qt.binding(isChecked);
|
||||
}
|
||||
|
||||
|
||||
function isEnabled() {
|
||||
if (!treeView.selection.hasSelection) {
|
||||
return false;
|
||||
|
@ -870,7 +875,7 @@ Windows.ScrollingWindow {
|
|||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
function isChecked() {
|
||||
if (!treeView.selection.hasSelection) {
|
||||
|
@ -878,10 +883,10 @@ Windows.ScrollingWindow {
|
|||
}
|
||||
|
||||
var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105);
|
||||
return isEnabled() && status !== "Not Baked";
|
||||
}
|
||||
return isEnabled() && status !== "Not Baked";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: infoGlyph.size;
|
||||
|
@ -905,7 +910,7 @@ Windows.ScrollingWindow {
|
|||
"What is baking?",
|
||||
"Baking compresses and optimizes files for faster network transfer and display. We recommend you bake your content to reduce initial load times for your visitors.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}// End_OF( infoRow )
|
||||
|
||||
HifiControls.ContentSection {
|
||||
|
|
|
@ -30,25 +30,31 @@ Rectangle {
|
|||
property string activeView: "initialize";
|
||||
property bool ownershipStatusReceived: false;
|
||||
property bool balanceReceived: false;
|
||||
property bool availableUpdatesReceived: false;
|
||||
property string baseItemName: "";
|
||||
property string itemName;
|
||||
property string itemId;
|
||||
property string itemHref;
|
||||
property string itemAuthor;
|
||||
property int itemEdition: -1;
|
||||
property string certificateId;
|
||||
property double balanceAfterPurchase;
|
||||
property bool alreadyOwned: false;
|
||||
property int itemPrice: -1;
|
||||
property bool isCertified;
|
||||
property string itemType;
|
||||
property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar"];
|
||||
property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar"];
|
||||
property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR"];
|
||||
property var buttonTextClicked: ["REZZED!", "WORN!", "CONTENT SET REPLACED!", "INSTALLED!", "AVATAR CHANGED!"]
|
||||
property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar];
|
||||
property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar", "unknown"];
|
||||
property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar", "item"];
|
||||
property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR", "REZ"];
|
||||
property var buttonTextClicked: ["REZZED!", "WORN!", "CONTENT SET REPLACED!", "INSTALLED!", "AVATAR CHANGED!", "REZZED!"]
|
||||
property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar, hifi.glyphs.wand];
|
||||
property bool shouldBuyWithControlledFailure: false;
|
||||
property bool debugCheckoutSuccess: false;
|
||||
property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified();
|
||||
property string referrer;
|
||||
property bool isInstalled;
|
||||
property bool isUpdating;
|
||||
property string baseAppURL;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
Connections {
|
||||
|
@ -103,8 +109,8 @@ Rectangle {
|
|||
if (result.status !== 'success') {
|
||||
console.log("Failed to get balance", result.data.message);
|
||||
} else {
|
||||
root.balanceReceived = true;
|
||||
root.balanceAfterPurchase = result.data.balance - root.itemPrice;
|
||||
root.balanceReceived = true;
|
||||
root.refreshBuyUI();
|
||||
}
|
||||
}
|
||||
|
@ -113,13 +119,13 @@ Rectangle {
|
|||
if (result.status !== 'success') {
|
||||
console.log("Failed to get Already Owned status", result.data.message);
|
||||
} else {
|
||||
root.ownershipStatusReceived = true;
|
||||
if (result.data.marketplace_item_id === root.itemId) {
|
||||
root.alreadyOwned = result.data.already_owned;
|
||||
} else {
|
||||
console.log("WARNING - Received 'Already Owned' status about different Marketplace ID!");
|
||||
root.alreadyOwned = false;
|
||||
}
|
||||
root.ownershipStatusReceived = true;
|
||||
root.refreshBuyUI();
|
||||
}
|
||||
}
|
||||
|
@ -129,11 +135,53 @@ Rectangle {
|
|||
root.isInstalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
onAvailableUpdatesResult: {
|
||||
if (result.status !== 'success') {
|
||||
console.log("Failed to get Available Updates", result.data.message);
|
||||
} else {
|
||||
for (var i = 0; i < result.data.updates.length; i++) {
|
||||
// If the ItemID of the item we're looking at matches EITHER the ID of a "base" item
|
||||
// OR the ID of an "updated" item, we're updating.
|
||||
if (root.itemId === result.data.updates[i].item_id ||
|
||||
root.itemId === result.data.updates[i].updated_item_id) {
|
||||
if (root.itemEdition !== -1 && root.itemEdition !== parseInt(result.data.updates[i].edition_number)) {
|
||||
continue;
|
||||
}
|
||||
root.isUpdating = true;
|
||||
root.baseItemName = result.data.updates[i].base_item_title;
|
||||
// This CertID is the one corresponding to the base item CertID that the user already owns
|
||||
root.certificateId = result.data.updates[i].certificate_id;
|
||||
if (root.itemType === "app") {
|
||||
root.baseAppURL = result.data.updates[i].item_download_url;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
root.availableUpdatesReceived = true;
|
||||
refreshBuyUI();
|
||||
}
|
||||
}
|
||||
|
||||
onUpdateItemResult: {
|
||||
if (result.status !== 'success') {
|
||||
failureErrorText.text = result.message;
|
||||
root.activeView = "checkoutFailure";
|
||||
} else {
|
||||
root.itemHref = result.data.download_url;
|
||||
if (result.data.categories.indexOf("Wearables") > -1) {
|
||||
root.itemType = "wearable";
|
||||
}
|
||||
root.activeView = "checkoutSuccess";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onItemIdChanged: {
|
||||
root.ownershipStatusReceived = false;
|
||||
Commerce.alreadyOwned(root.itemId);
|
||||
root.availableUpdatesReceived = false;
|
||||
Commerce.getAvailableUpdates(root.itemId);
|
||||
itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg";
|
||||
}
|
||||
|
||||
|
@ -161,6 +209,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
onItemPriceChanged: {
|
||||
root.balanceReceived = false;
|
||||
Commerce.balance();
|
||||
}
|
||||
|
||||
|
@ -240,6 +289,7 @@ Rectangle {
|
|||
Component.onCompleted: {
|
||||
ownershipStatusReceived = false;
|
||||
balanceReceived = false;
|
||||
availableUpdatesReceived = false;
|
||||
Commerce.getWalletStatus();
|
||||
}
|
||||
}
|
||||
|
@ -316,7 +366,7 @@ Rectangle {
|
|||
Rectangle {
|
||||
id: loading;
|
||||
z: 997;
|
||||
visible: !root.ownershipStatusReceived || !root.balanceReceived;
|
||||
visible: !root.ownershipStatusReceived || !root.balanceReceived || !root.availableUpdatesReceived;
|
||||
anchors.fill: parent;
|
||||
color: hifi.colors.white;
|
||||
|
||||
|
@ -412,6 +462,7 @@ Rectangle {
|
|||
// "HFC" balance label
|
||||
HiFiGlyphs {
|
||||
id: itemPriceTextLabel;
|
||||
visible: !(root.isUpdating && root.itemEdition > 0);
|
||||
text: hifi.glyphs.hfc;
|
||||
// Size
|
||||
size: 30;
|
||||
|
@ -427,9 +478,9 @@ Rectangle {
|
|||
}
|
||||
FiraSansSemiBold {
|
||||
id: itemPriceText;
|
||||
text: (root.itemPrice === -1) ? "--" : root.itemPrice;
|
||||
text: (root.isUpdating && root.itemEdition > 0) ? "FREE\nUPDATE" : ((root.itemPrice === -1) ? "--" : root.itemPrice);
|
||||
// Text size
|
||||
size: 26;
|
||||
size: (root.isUpdating && root.itemEdition > 0) ? 20 : 26;
|
||||
// Anchors
|
||||
anchors.top: parent.top;
|
||||
anchors.right: parent.right;
|
||||
|
@ -529,9 +580,13 @@ Rectangle {
|
|||
height: 50;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
text: "VIEW THIS ITEM IN MY PURCHASES";
|
||||
text: root.isUpdating ? "UPDATE TO THIS ITEM FOR FREE" : "VIEW THIS ITEM IN MY PURCHASES";
|
||||
onClicked: {
|
||||
sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName});
|
||||
if (root.isUpdating) {
|
||||
sendToScript({method: 'checkout_goToPurchases', filterText: root.baseItemName});
|
||||
} else {
|
||||
sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -539,7 +594,7 @@ Rectangle {
|
|||
HifiControlsUit.Button {
|
||||
id: buyButton;
|
||||
visible: !((root.itemType === "avatar" || root.itemType === "app") && viewInMyPurchasesButton.visible)
|
||||
enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived) || (!root.isCertified);
|
||||
enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived && availableUpdatesReceived) || (!root.isCertified) || root.isUpdating;
|
||||
color: viewInMyPurchasesButton.visible ? hifi.buttons.white : hifi.buttons.blue;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.top: viewInMyPurchasesButton.visible ? viewInMyPurchasesButton.bottom :
|
||||
|
@ -548,10 +603,19 @@ Rectangle {
|
|||
height: 50;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
text: ((root.isCertified) ? ((ownershipStatusReceived && balanceReceived) ?
|
||||
(viewInMyPurchasesButton.visible ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item");
|
||||
text: (root.isUpdating && root.itemEdition > 0) ? "CONFIRM UPDATE" : (((root.isCertified) ? ((ownershipStatusReceived && balanceReceived && availableUpdatesReceived) ?
|
||||
((viewInMyPurchasesButton.visible && !root.isUpdating) ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item"));
|
||||
onClicked: {
|
||||
if (root.isCertified) {
|
||||
if (root.isUpdating && root.itemEdition > 0) {
|
||||
// If we're updating an app, the existing app needs to be uninstalled.
|
||||
// This call will fail/return `false` if the app isn't installed, but that's OK.
|
||||
if (root.itemType === "app") {
|
||||
Commerce.uninstallApp(root.baseAppURL);
|
||||
}
|
||||
buyButton.enabled = false;
|
||||
loading.visible = true;
|
||||
Commerce.updateItem(root.certificateId);
|
||||
} else if (root.isCertified) {
|
||||
if (!root.shouldBuyWithControlledFailure) {
|
||||
if (root.itemType === "contentSet" && !Entities.canReplaceContent()) {
|
||||
lightboxPopup.titleText = "Purchase Content Set";
|
||||
|
@ -975,7 +1039,7 @@ Rectangle {
|
|||
buyButton.color = hifi.buttons.red;
|
||||
root.shouldBuyWithControlledFailure = true;
|
||||
} else {
|
||||
buyButton.text = (root.isCertified ? ((ownershipStatusReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item");
|
||||
buyButton.text = (root.isCertified ? ((ownershipStatusReceived && balanceReceived && availableUpdatesReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item");
|
||||
buyButton.color = hifi.buttons.blue;
|
||||
root.shouldBuyWithControlledFailure = false;
|
||||
}
|
||||
|
@ -1001,12 +1065,13 @@ Rectangle {
|
|||
function fromScript(message) {
|
||||
switch (message.method) {
|
||||
case 'updateCheckoutQML':
|
||||
itemId = message.params.itemId;
|
||||
itemName = message.params.itemName;
|
||||
root.itemId = message.params.itemId;
|
||||
root.itemName = message.params.itemName.trim();
|
||||
root.itemPrice = message.params.itemPrice;
|
||||
itemHref = message.params.itemHref;
|
||||
referrer = message.params.referrer;
|
||||
itemAuthor = message.params.itemAuthor;
|
||||
root.itemHref = message.params.itemHref;
|
||||
root.referrer = message.params.referrer;
|
||||
root.itemAuthor = message.params.itemAuthor;
|
||||
root.itemEdition = message.params.itemEdition || -1;
|
||||
refreshBuyUI();
|
||||
break;
|
||||
default:
|
||||
|
@ -1015,35 +1080,70 @@ Rectangle {
|
|||
}
|
||||
signal sendToScript(var message);
|
||||
|
||||
function canBuyAgain() {
|
||||
return (root.itemType === "entity" || root.itemType === "wearable" || root.itemType === "contentSet" || root.itemType === "unknown");
|
||||
}
|
||||
|
||||
function handleContentSets() {
|
||||
if (root.itemType === "contentSet" && !Entities.canReplaceContent()) {
|
||||
buyText.text = "The domain owner must enable 'Replace Content' permissions for you in this " +
|
||||
"<b>domain's server settings</b> before you can replace this domain's content with <b>" + root.itemName + "</b>";
|
||||
buyTextContainer.color = "#FFC3CD";
|
||||
buyTextContainer.border.color = "#F3808F";
|
||||
buyGlyph.text = hifi.glyphs.alert;
|
||||
buyGlyph.size = 54;
|
||||
}
|
||||
}
|
||||
|
||||
function handleBuyAgainLogic() {
|
||||
// If you can buy this item again...
|
||||
if (canBuyAgain()) {
|
||||
// If you can't afford another copy of the item...
|
||||
if (root.balanceAfterPurchase < 0) {
|
||||
// If you already own the item...
|
||||
if (root.alreadyOwned) {
|
||||
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item again.</b>";
|
||||
// Else if you don't already own the item...
|
||||
} else {
|
||||
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item.</b>";
|
||||
}
|
||||
buyTextContainer.color = "#FFC3CD";
|
||||
buyTextContainer.border.color = "#F3808F";
|
||||
buyGlyph.text = hifi.glyphs.alert;
|
||||
buyGlyph.size = 54;
|
||||
// If you CAN afford another copy of the item...
|
||||
} else {
|
||||
handleContentSets();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function refreshBuyUI() {
|
||||
if (root.isCertified) {
|
||||
if (root.ownershipStatusReceived && root.balanceReceived) {
|
||||
if (root.balanceAfterPurchase < 0) {
|
||||
if (root.alreadyOwned) {
|
||||
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item again.</b>";
|
||||
viewInMyPurchasesButton.visible = true;
|
||||
if (root.ownershipStatusReceived && root.balanceReceived && root.availableUpdatesReceived) {
|
||||
buyText.text = "";
|
||||
|
||||
// If the user IS on the checkout page for the updated version of an owned item...
|
||||
if (root.isUpdating) {
|
||||
// If the user HAS already selected a specific edition to update...
|
||||
if (root.itemEdition > 0) {
|
||||
buyText.text = "By pressing \"Confirm Update\", you agree to trade in your old item for the updated item that replaces it.";
|
||||
buyTextContainer.color = "#FFFFFF";
|
||||
buyTextContainer.border.color = "#FFFFFF";
|
||||
// Else if the user HAS NOT selected a specific edition to update...
|
||||
} else {
|
||||
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item.</b>";
|
||||
}
|
||||
buyTextContainer.color = "#FFC3CD";
|
||||
buyTextContainer.border.color = "#F3808F";
|
||||
buyGlyph.text = hifi.glyphs.alert;
|
||||
buyGlyph.size = 54;
|
||||
viewInMyPurchasesButton.visible = true;
|
||||
|
||||
handleBuyAgainLogic();
|
||||
}
|
||||
// If the user IS NOT on the checkout page for the updated verison of an owned item...
|
||||
// (i.e. they are checking out an item "normally")
|
||||
} else {
|
||||
if (root.alreadyOwned) {
|
||||
viewInMyPurchasesButton.visible = true;
|
||||
} else {
|
||||
buyText.text = "";
|
||||
}
|
||||
|
||||
if (root.itemType === "contentSet" && !Entities.canReplaceContent()) {
|
||||
buyText.text = "The domain owner must enable 'Replace Content' permissions for you in this " +
|
||||
"<b>domain's server settings</b> before you can replace this domain's content with <b>" + root.itemName + "</b>";
|
||||
buyTextContainer.color = "#FFC3CD";
|
||||
buyTextContainer.border.color = "#F3808F";
|
||||
buyGlyph.text = hifi.glyphs.alert;
|
||||
buyGlyph.size = 54;
|
||||
}
|
||||
|
||||
handleBuyAgainLogic();
|
||||
}
|
||||
} else {
|
||||
buyText.text = "";
|
||||
|
@ -1060,13 +1160,15 @@ Rectangle {
|
|||
function authSuccessStep() {
|
||||
if (!root.debugCheckoutSuccess) {
|
||||
root.activeView = "checkoutMain";
|
||||
root.ownershipStatusReceived = false;
|
||||
Commerce.alreadyOwned(root.itemId);
|
||||
root.availableUpdatesReceived = false;
|
||||
Commerce.getAvailableUpdates(root.itemId);
|
||||
root.balanceReceived = false;
|
||||
Commerce.balance();
|
||||
} else {
|
||||
root.activeView = "checkoutSuccess";
|
||||
}
|
||||
root.balanceReceived = false;
|
||||
root.ownershipStatusReceived = false;
|
||||
Commerce.alreadyOwned(root.itemId);
|
||||
Commerce.balance();
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -28,6 +28,7 @@ Item {
|
|||
property string referrerURL: (Account.metaverseServerURL + "/marketplace?");
|
||||
readonly property int additionalDropdownHeight: usernameDropdown.height - myUsernameButton.anchors.bottomMargin;
|
||||
property alias usernameDropdownVisible: usernameDropdown.visible;
|
||||
property bool messagesWaiting: false;
|
||||
|
||||
height: mainContainer.height + additionalDropdownHeight;
|
||||
|
||||
|
@ -38,6 +39,7 @@ Item {
|
|||
if (walletStatus === 0) {
|
||||
sendToParent({method: "needsLogIn"});
|
||||
} else if (walletStatus === 5) {
|
||||
Commerce.getAvailableUpdates();
|
||||
Commerce.getSecurityImage();
|
||||
} else if (walletStatus > 5) {
|
||||
console.log("ERROR in EmulatedMarketplaceHeader.qml: Unknown wallet status: " + walletStatus);
|
||||
|
@ -58,6 +60,14 @@ Item {
|
|||
securityImage.source = "image://security/securityImage";
|
||||
}
|
||||
}
|
||||
|
||||
onAvailableUpdatesResult: {
|
||||
if (result.status !== 'success') {
|
||||
console.log("Failed to get Available Updates", result.data.message);
|
||||
} else {
|
||||
root.messagesWaiting = result.data.updates.length > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
|
@ -134,13 +144,25 @@ Item {
|
|||
anchors.fill: parent;
|
||||
hoverEnabled: enabled;
|
||||
onClicked: {
|
||||
sendToParent({method: 'header_goToPurchases'});
|
||||
sendToParent({ method: 'header_goToPurchases', hasUpdates: root.messagesWaiting });
|
||||
}
|
||||
onEntered: myPurchasesText.color = hifi.colors.blueHighlight;
|
||||
onExited: myPurchasesText.color = hifi.colors.blueAccent;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: messagesWaitingLight;
|
||||
visible: root.messagesWaiting;
|
||||
anchors.right: myPurchasesLink.left;
|
||||
anchors.rightMargin: -2;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
height: 10;
|
||||
width: height;
|
||||
radius: height/2;
|
||||
color: "red";
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: textMetrics;
|
||||
font.family: "Raleway"
|
||||
|
|
|
@ -48,11 +48,14 @@ Item {
|
|||
property bool hasPermissionToRezThis;
|
||||
property bool permissionExplanationCardVisible;
|
||||
property bool isInstalled;
|
||||
property string upgradeUrl;
|
||||
property string upgradeTitle;
|
||||
property bool isShowingMyItems;
|
||||
|
||||
property string originalStatusText;
|
||||
property string originalStatusColor;
|
||||
|
||||
height: 110;
|
||||
height: (root.upgradeUrl === "" || root.isShowingMyItems) ? 110 : 150;
|
||||
width: parent.width;
|
||||
|
||||
Connections {
|
||||
|
@ -137,6 +140,14 @@ Item {
|
|||
anchors.verticalCenter: parent.verticalCenter;
|
||||
height: root.height - 10;
|
||||
|
||||
// START "incorrect indentation to prevent insane diffs"
|
||||
Item {
|
||||
id: itemContainer;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
anchors.top: parent.top;
|
||||
height: 100;
|
||||
|
||||
Image {
|
||||
id: itemPreviewImage;
|
||||
source: root.itemPreviewImageUrl;
|
||||
|
@ -357,7 +368,7 @@ Item {
|
|||
|
||||
Item {
|
||||
id: statusContainer;
|
||||
visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged;
|
||||
visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged || root.numberSold > -1;
|
||||
anchors.left: itemName.left;
|
||||
anchors.top: certificateContainer.bottom;
|
||||
anchors.topMargin: 8;
|
||||
|
@ -376,7 +387,7 @@ Item {
|
|||
"PENDING..."
|
||||
} else if (root.purchaseStatus === "invalidated") {
|
||||
"INVALIDATED"
|
||||
} else if (root.numberSold !== -1) {
|
||||
} else if (root.numberSold > -1) {
|
||||
("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "\u221e" : root.limitedRun))
|
||||
} else {
|
||||
""
|
||||
|
@ -634,6 +645,48 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// END "incorrect indentation to prevent insane diffs"
|
||||
|
||||
Rectangle {
|
||||
id: upgradeAvailableContainer;
|
||||
visible: root.upgradeUrl !== "" && !root.isShowingMyItems;
|
||||
anchors.top: itemContainer.bottom;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
color: "#B5EAFF";
|
||||
|
||||
RalewayRegular {
|
||||
id: updateAvailableText;
|
||||
text: "UPDATE AVAILABLE";
|
||||
size: 13;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 12;
|
||||
anchors.top: parent.top;
|
||||
anchors.bottom: parent.bottom;
|
||||
width: paintedWidth;
|
||||
color: hifi.colors.black;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
}
|
||||
|
||||
RalewaySemiBold {
|
||||
id: updateNowText;
|
||||
text: "<font color='#0093C5'><a href='#'>Update this item now</a></font>";
|
||||
size: 13;
|
||||
anchors.left: updateAvailableText.right;
|
||||
anchors.leftMargin: 16;
|
||||
anchors.top: parent.top;
|
||||
anchors.bottom: parent.bottom;
|
||||
width: paintedWidth;
|
||||
color: hifi.colors.black;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
|
||||
onLinkActivated: {
|
||||
sendToPurchases({method: 'updateItemClicked', itemId: root.itemId, itemEdition: root.itemEdition, upgradeUrl: root.upgradeUrl});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
|
|
|
@ -37,6 +37,8 @@ Rectangle {
|
|||
property bool isDebuggingFirstUseTutorial: false;
|
||||
property int pendingItemCount: 0;
|
||||
property string installedApps;
|
||||
property bool keyboardRaised: false;
|
||||
property int numUpdatesAvailable: 0;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
Connections {
|
||||
|
@ -64,6 +66,7 @@ Rectangle {
|
|||
root.activeView = "purchasesMain";
|
||||
root.installedApps = Commerce.getInstalledApps();
|
||||
Commerce.inventory();
|
||||
Commerce.getAvailableUpdates();
|
||||
}
|
||||
} else {
|
||||
console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus);
|
||||
|
@ -119,6 +122,15 @@ Rectangle {
|
|||
|
||||
root.pendingInventoryReply = false;
|
||||
}
|
||||
|
||||
onAvailableUpdatesResult: {
|
||||
if (result.status !== 'success') {
|
||||
console.log("Failed to get Available Updates", result.data.message);
|
||||
} else {
|
||||
sendToScript({method: 'purchases_availableUpdatesReceived', numUpdates: result.data.updates.length });
|
||||
root.numUpdatesAvailable = result.data.updates.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
|
@ -273,6 +285,7 @@ Rectangle {
|
|||
root.activeView = "purchasesMain";
|
||||
root.installedApps = Commerce.getInstalledApps();
|
||||
Commerce.inventory();
|
||||
Commerce.getAvailableUpdates();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -296,6 +309,7 @@ Rectangle {
|
|||
// FILTER BAR START
|
||||
//
|
||||
Item {
|
||||
z: 997;
|
||||
id: filterBarContainer;
|
||||
// Size
|
||||
height: 40;
|
||||
|
@ -321,28 +335,61 @@ Rectangle {
|
|||
size: 22;
|
||||
}
|
||||
|
||||
HifiControlsUit.TextField {
|
||||
HifiControlsUit.FilterBar {
|
||||
id: filterBar;
|
||||
property string previousText: "";
|
||||
property string previousPrimaryFilter: "";
|
||||
colorScheme: hifi.colorSchemes.faintGray;
|
||||
hasClearButton: true;
|
||||
hasRoundedBorder: true;
|
||||
anchors.top: parent.top;
|
||||
anchors.right: parent.right;
|
||||
anchors.left: myText.right;
|
||||
anchors.leftMargin: 16;
|
||||
height: 39;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.right: parent.right;
|
||||
textFieldHeight: 39;
|
||||
height: textFieldHeight + dropdownHeight;
|
||||
placeholderText: "filter items";
|
||||
|
||||
Component.onCompleted: {
|
||||
var choices = [
|
||||
{
|
||||
"displayName": "App",
|
||||
"filterName": "app"
|
||||
},
|
||||
{
|
||||
"displayName": "Avatar",
|
||||
"filterName": "avatar"
|
||||
},
|
||||
{
|
||||
"displayName": "Content Set",
|
||||
"filterName": "contentSet"
|
||||
},
|
||||
{
|
||||
"displayName": "Entity",
|
||||
"filterName": "entity"
|
||||
},
|
||||
{
|
||||
"displayName": "Wearable",
|
||||
"filterName": "wearable"
|
||||
},
|
||||
{
|
||||
"displayName": "Updatable",
|
||||
"filterName": "updatable"
|
||||
}
|
||||
]
|
||||
filterBar.primaryFilterChoices.clear();
|
||||
filterBar.primaryFilterChoices.append(choices);
|
||||
}
|
||||
|
||||
onPrimaryFilter_displayNameChanged: {
|
||||
buildFilteredPurchasesModel();
|
||||
purchasesContentsList.positionViewAtIndex(0, ListView.Beginning)
|
||||
filterBar.previousPrimaryFilter = filterBar.primaryFilter_displayName;
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
buildFilteredPurchasesModel();
|
||||
purchasesContentsList.positionViewAtIndex(0, ListView.Beginning)
|
||||
filterBar.previousText = filterBar.text;
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
focus = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
|
@ -350,6 +397,7 @@ Rectangle {
|
|||
//
|
||||
|
||||
HifiControlsUit.Separator {
|
||||
z: 996;
|
||||
id: separator;
|
||||
colorScheme: 2;
|
||||
anchors.left: parent.left;
|
||||
|
@ -377,12 +425,11 @@ Rectangle {
|
|||
clip: true;
|
||||
model: filteredPurchasesModel;
|
||||
snapMode: ListView.SnapToItem;
|
||||
highlightRangeMode: ListView.StrictlyEnforceRange;
|
||||
// Anchors
|
||||
anchors.top: separator.bottom;
|
||||
anchors.topMargin: 12;
|
||||
anchors.left: parent.left;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.bottom: updatesAvailableBanner.visible ? updatesAvailableBanner.top : parent.bottom;
|
||||
width: parent.width;
|
||||
delegate: PurchasedItem {
|
||||
itemName: title;
|
||||
|
@ -398,21 +445,10 @@ Rectangle {
|
|||
displayedItemCount: model.displayedItemCount;
|
||||
permissionExplanationCardVisible: model.permissionExplanationCardVisible;
|
||||
isInstalled: model.isInstalled;
|
||||
itemType: {
|
||||
if (model.root_file_url.indexOf(".fst") > -1) {
|
||||
"avatar";
|
||||
} else if (model.categories.indexOf("Wearables") > -1) {
|
||||
"wearable";
|
||||
} else if (model.root_file_url.endsWith('.json.gz')) {
|
||||
"contentSet";
|
||||
} else if (model.root_file_url.endsWith('.app.json')) {
|
||||
"app";
|
||||
} else if (model.root_file_url.endsWith('.json')) {
|
||||
"entity";
|
||||
} else {
|
||||
"unknown";
|
||||
}
|
||||
}
|
||||
upgradeUrl: model.upgrade_url;
|
||||
upgradeTitle: model.upgrade_title;
|
||||
itemType: model.itemType;
|
||||
isShowingMyItems: root.isShowingMyItems;
|
||||
anchors.topMargin: 10;
|
||||
anchors.bottomMargin: 10;
|
||||
|
||||
|
@ -485,15 +521,80 @@ Rectangle {
|
|||
filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", true);
|
||||
}
|
||||
}
|
||||
} else if (msg.method === "updateItemClicked") {
|
||||
sendToScript(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: updatesAvailableBanner;
|
||||
visible: root.numUpdatesAvailable > 0 && !root.isShowingMyItems;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
height: 75;
|
||||
color: "#B5EAFF";
|
||||
|
||||
Rectangle {
|
||||
id: updatesAvailableGlyph;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 16;
|
||||
// Size
|
||||
width: 10;
|
||||
height: width;
|
||||
radius: width/2;
|
||||
// Style
|
||||
color: "red";
|
||||
}
|
||||
|
||||
RalewaySemiBold {
|
||||
text: "You have " + root.numUpdatesAvailable + " item updates available.";
|
||||
// Text size
|
||||
size: 18;
|
||||
// Anchors
|
||||
anchors.left: updatesAvailableGlyph.right;
|
||||
anchors.leftMargin: 12;
|
||||
height: parent.height;
|
||||
width: paintedWidth;
|
||||
// Style
|
||||
color: hifi.colors.black;
|
||||
// Alignment
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: true;
|
||||
propagateComposedEvents: false;
|
||||
}
|
||||
|
||||
HifiControlsUit.Button {
|
||||
color: hifi.buttons.white;
|
||||
colorScheme: hifi.colorSchemes.dark;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 12;
|
||||
width: 100;
|
||||
height: 40;
|
||||
text: "SHOW ME";
|
||||
onClicked: {
|
||||
filterBar.text = "";
|
||||
filterBar.changeFilterByDisplayName("Updatable");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: noItemsAlertContainer;
|
||||
visible: !purchasesContentsList.visible && root.purchasesReceived && root.isShowingMyItems && filterBar.text === "";
|
||||
visible: !purchasesContentsList.visible &&
|
||||
root.purchasesReceived &&
|
||||
root.isShowingMyItems &&
|
||||
filterBar.text === "" &&
|
||||
filterBar.primaryFilter_displayName === "";
|
||||
anchors.top: filterBarContainer.bottom;
|
||||
anchors.topMargin: 12;
|
||||
anchors.left: parent.left;
|
||||
|
@ -539,7 +640,11 @@ Rectangle {
|
|||
|
||||
Item {
|
||||
id: noPurchasesAlertContainer;
|
||||
visible: !purchasesContentsList.visible && root.purchasesReceived && !root.isShowingMyItems && filterBar.text === "";
|
||||
visible: !purchasesContentsList.visible &&
|
||||
root.purchasesReceived &&
|
||||
!root.isShowingMyItems &&
|
||||
filterBar.text === "" &&
|
||||
filterBar.primaryFilter_displayName === "";
|
||||
anchors.top: filterBarContainer.bottom;
|
||||
anchors.topMargin: 12;
|
||||
anchors.left: parent.left;
|
||||
|
@ -589,7 +694,7 @@ Rectangle {
|
|||
|
||||
HifiControlsUit.Keyboard {
|
||||
id: keyboard;
|
||||
raised: HMD.mounted && filterBar.focus;
|
||||
raised: HMD.mounted && parent.keyboardRaised;
|
||||
numeric: parent.punctuationMode;
|
||||
anchors {
|
||||
bottom: parent.bottom;
|
||||
|
@ -613,6 +718,7 @@ Rectangle {
|
|||
console.log("Refreshing Purchases...");
|
||||
root.pendingInventoryReply = true;
|
||||
Commerce.inventory();
|
||||
Commerce.getAvailableUpdates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -660,8 +766,13 @@ Rectangle {
|
|||
var sameItemCount = 0;
|
||||
|
||||
tempPurchasesModel.clear();
|
||||
|
||||
for (var i = 0; i < purchasesModel.count; i++) {
|
||||
if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) {
|
||||
if (!purchasesModel.get(i).valid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) {
|
||||
tempPurchasesModel.insert(0, purchasesModel.get(i));
|
||||
} else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") ||
|
||||
|
@ -671,6 +782,35 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// primaryFilter filtering and adding of itemType property to model
|
||||
var currentItemType, currentRootFileUrl, currentCategories;
|
||||
for (var i = 0; i < tempPurchasesModel.count; i++) {
|
||||
currentRootFileUrl = tempPurchasesModel.get(i).root_file_url;
|
||||
currentCategories = tempPurchasesModel.get(i).categories;
|
||||
|
||||
if (currentRootFileUrl.indexOf(".fst") > -1) {
|
||||
currentItemType = "avatar";
|
||||
} else if (currentCategories.indexOf("Wearables") > -1) {
|
||||
currentItemType = "wearable";
|
||||
} else if (currentRootFileUrl.endsWith('.json.gz')) {
|
||||
currentItemType = "contentSet";
|
||||
} else if (currentRootFileUrl.endsWith('.app.json')) {
|
||||
currentItemType = "app";
|
||||
} else if (currentRootFileUrl.endsWith('.json')) {
|
||||
currentItemType = "entity";
|
||||
} else {
|
||||
currentItemType = "unknown";
|
||||
}
|
||||
if (filterBar.primaryFilter_displayName !== "" &&
|
||||
((filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgrade_url === "") ||
|
||||
(filterBar.primaryFilter_displayName !== "Updatable" && filterBar.primaryFilter_filterName.toLowerCase() !== currentItemType.toLowerCase()))) {
|
||||
tempPurchasesModel.remove(i);
|
||||
i--;
|
||||
} else {
|
||||
tempPurchasesModel.setProperty(i, 'itemType', currentItemType);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < tempPurchasesModel.count; i++) {
|
||||
if (!filteredPurchasesModel.get(i)) {
|
||||
sameItemCount = -1;
|
||||
|
@ -682,12 +822,17 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
if (sameItemCount !== tempPurchasesModel.count || filterBar.text !== filterBar.previousText) {
|
||||
if (sameItemCount !== tempPurchasesModel.count ||
|
||||
filterBar.text !== filterBar.previousText ||
|
||||
filterBar.primaryFilter !== filterBar.previousPrimaryFilter) {
|
||||
filteredPurchasesModel.clear();
|
||||
var currentId;
|
||||
for (var i = 0; i < tempPurchasesModel.count; i++) {
|
||||
currentId = tempPurchasesModel.get(i).id;
|
||||
|
||||
|
||||
if (!purchasesModel.get(i).valid) {
|
||||
continue;
|
||||
}
|
||||
filteredPurchasesModel.append(tempPurchasesModel.get(i));
|
||||
filteredPurchasesModel.setProperty(i, 'permissionExplanationCardVisible', false);
|
||||
filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1));
|
||||
|
@ -736,7 +881,7 @@ Rectangle {
|
|||
function fromScript(message) {
|
||||
switch (message.method) {
|
||||
case 'updatePurchases':
|
||||
referrerURL = message.referrerURL;
|
||||
referrerURL = message.referrerURL || "";
|
||||
titleBarContainer.referrerURL = message.referrerURL;
|
||||
filterBar.text = message.filterText ? message.filterText : "";
|
||||
break;
|
||||
|
|
|
@ -39,6 +39,7 @@ Item {
|
|||
root.noMoreHistoryData = false;
|
||||
root.historyRequestPending = true;
|
||||
Commerce.history(root.currentHistoryPage);
|
||||
Commerce.getAvailableUpdates();
|
||||
} else {
|
||||
refreshTimer.stop();
|
||||
}
|
||||
|
@ -133,6 +134,14 @@ Item {
|
|||
refreshTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
onAvailableUpdatesResult: {
|
||||
if (result.status !== 'success') {
|
||||
console.log("Failed to get Available Updates", result.data.message);
|
||||
} else {
|
||||
sendToScript({method: 'wallet_availableUpdatesReceived', numUpdates: result.data.updates.length });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
|
|
@ -25,6 +25,7 @@ ScrollingWindow {
|
|||
resizable: true
|
||||
destroyOnHidden: false
|
||||
implicitWidth: 424
|
||||
opacity: parent.opacity
|
||||
implicitHeight: isHMD ? 695 : 728
|
||||
minSize: Qt.vector2d(424, 300)
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ Rectangle {
|
|||
Component.onDestruction: {
|
||||
assetMappingsModel.autoRefreshEnabled = false;
|
||||
}
|
||||
|
||||
|
||||
function letterbox(headerGlyph, headerText, message) {
|
||||
letterboxMessage.headerGlyph = headerGlyph;
|
||||
letterboxMessage.headerText = headerText;
|
||||
|
@ -66,7 +66,7 @@ Rectangle {
|
|||
letterboxMessage.visible = true;
|
||||
letterboxMessage.popupRadius = 0;
|
||||
}
|
||||
|
||||
|
||||
function errorMessageBox(message) {
|
||||
return tabletRoot.messageBox({
|
||||
icon: hifi.icons.warning,
|
||||
|
@ -145,7 +145,7 @@ Rectangle {
|
|||
|
||||
function canAddToWorld(path) {
|
||||
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i];
|
||||
|
||||
|
||||
if (selectedItemCount > 1) {
|
||||
return false;
|
||||
}
|
||||
|
@ -154,8 +154,8 @@ Rectangle {
|
|||
return total | new RegExp(current).test(path);
|
||||
}, false);
|
||||
}
|
||||
|
||||
function canRename() {
|
||||
|
||||
function canRename() {
|
||||
if (treeView.selection.hasSelection && selectedItemCount == 1) {
|
||||
return true;
|
||||
} else {
|
||||
|
@ -199,7 +199,7 @@ Rectangle {
|
|||
var SHAPE_TYPE_STATIC_MESH = 3;
|
||||
var SHAPE_TYPE_BOX = 4;
|
||||
var SHAPE_TYPE_SPHERE = 5;
|
||||
|
||||
|
||||
var SHAPE_TYPES = [];
|
||||
SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model";
|
||||
|
@ -207,7 +207,7 @@ Rectangle {
|
|||
SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons";
|
||||
SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere";
|
||||
|
||||
|
||||
var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_SIMPLE_COMPOUND;
|
||||
var DYNAMIC_DEFAULT = false;
|
||||
var prompt = tabletRoot.customInputDialog({
|
||||
|
@ -349,14 +349,14 @@ Rectangle {
|
|||
}
|
||||
function deleteFile(index) {
|
||||
var paths = [];
|
||||
|
||||
|
||||
if (!index) {
|
||||
for (var i = 0; i < selectedItemCount; ++i) {
|
||||
index = treeView.selection.selectedIndexes[i];
|
||||
paths[i] = assetProxyModel.data(index, 0x100);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!paths) {
|
||||
return;
|
||||
}
|
||||
|
@ -365,7 +365,7 @@ Rectangle {
|
|||
var items = selectedItemCount.toString();
|
||||
var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101);
|
||||
var typeString = isFolder ? 'folder' : 'file';
|
||||
|
||||
|
||||
if (selectedItemCount > 1) {
|
||||
modalMessage = "You are about to delete " + items + " items \nDo you want to continue?";
|
||||
} else {
|
||||
|
@ -476,7 +476,7 @@ Rectangle {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The letterbox used for popup messages
|
||||
LetterboxMessage {
|
||||
id: letterboxMessage;
|
||||
|
@ -540,7 +540,7 @@ Rectangle {
|
|||
anchors.margins: hifi.dimensions.contentMargin.x + 2 // Extra for border
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
|
||||
treeModel: assetProxyModel
|
||||
selectionMode: SelectionMode.ExtendedSelection
|
||||
headerVisible: true
|
||||
|
@ -560,9 +560,13 @@ Rectangle {
|
|||
id: bakedColumn
|
||||
title: "Use Baked?"
|
||||
role: "baked"
|
||||
width: 100
|
||||
width: 170
|
||||
}
|
||||
|
||||
|
||||
onSortIndicatorOrderChanged: {
|
||||
Assets.sortProxyModel(sortIndicatorColumn, sortIndicatorOrder);
|
||||
}
|
||||
|
||||
itemDelegate: Loader {
|
||||
id: itemDelegateLoader
|
||||
|
||||
|
@ -598,7 +602,7 @@ Rectangle {
|
|||
|
||||
}
|
||||
sourceComponent: getComponent()
|
||||
|
||||
|
||||
Component {
|
||||
id: labelComponent
|
||||
FiraSansSemiBold {
|
||||
|
@ -607,15 +611,15 @@ Rectangle {
|
|||
color: colorScheme == hifi.colorSchemes.light
|
||||
? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight)
|
||||
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
|
||||
|
||||
|
||||
horizontalAlignment: styleData.column === 1 ? TextInput.AlignHCenter : TextInput.AlignLeft
|
||||
|
||||
|
||||
elide: Text.ElideMiddle
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
|
||||
|
||||
acceptedButtons: Qt.NoButton
|
||||
hoverEnabled: true
|
||||
|
||||
|
@ -637,7 +641,7 @@ Rectangle {
|
|||
color: colorScheme == hifi.colorSchemes.light
|
||||
? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight)
|
||||
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
|
||||
|
||||
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
|
||||
|
@ -724,7 +728,7 @@ Rectangle {
|
|||
size: hifi.fontSizes.tableText
|
||||
color: colorScheme == hifi.colorSchemes.light ? hifi.colors.black : hifi.colors.lightGrayText
|
||||
}
|
||||
|
||||
|
||||
Timer {
|
||||
id: showTimer
|
||||
interval: 1000
|
||||
|
@ -743,7 +747,7 @@ Rectangle {
|
|||
treeLabelToolTip.visible = false;
|
||||
}
|
||||
}// End_OF( treeLabelToolTip )
|
||||
|
||||
|
||||
MouseArea {
|
||||
propagateComposedEvents: true
|
||||
anchors.fill: parent
|
||||
|
@ -801,7 +805,7 @@ Rectangle {
|
|||
anchors.left: treeView.left
|
||||
anchors.right: treeView.right
|
||||
anchors.bottomMargin: hifi.dimensions.contentSpacing.y
|
||||
|
||||
|
||||
RalewayRegular {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
|
@ -845,7 +849,7 @@ Rectangle {
|
|||
|
||||
checked = Qt.binding(isChecked);
|
||||
}
|
||||
|
||||
|
||||
function isEnabled() {
|
||||
if (!treeView.selection.hasSelection) {
|
||||
return false;
|
||||
|
@ -869,7 +873,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
function isChecked() {
|
||||
if (!treeView.selection.hasSelection) {
|
||||
|
@ -877,10 +881,10 @@ Rectangle {
|
|||
}
|
||||
|
||||
var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105);
|
||||
return isEnabled() && status !== "Not Baked";
|
||||
}
|
||||
return isEnabled() && status !== "Not Baked";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: infoGlyph.size;
|
||||
|
@ -904,7 +908,7 @@ Rectangle {
|
|||
"What is baking?",
|
||||
"Baking compresses and optimizes files for faster network transfer and display. We recommend you bake your content to reduce initial load times for your visitors.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}// End_OF( infoRow )
|
||||
|
||||
HifiControls.TabletContentSection {
|
||||
|
|
|
@ -113,7 +113,6 @@ StackView {
|
|||
id: addressBarDialog
|
||||
|
||||
property bool keyboardEnabled: false
|
||||
property bool keyboardRaised: false
|
||||
property bool punctuationMode: false
|
||||
|
||||
width: parent.width
|
||||
|
@ -401,11 +400,10 @@ StackView {
|
|||
addressLine.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HifiControls.Keyboard {
|
||||
id: keyboard
|
||||
raised: parent.keyboardEnabled && parent.keyboardRaised
|
||||
raised: parent.keyboardEnabled
|
||||
numeric: parent.punctuationMode
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
|
@ -413,7 +411,7 @@ StackView {
|
|||
right: parent.right
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function updateLocationText(enteringAddress) {
|
||||
|
|
18
interface/resources/serverless/tutorial.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"Entities": [
|
||||
{
|
||||
"type": "Box",
|
||||
"dimensions": {
|
||||
"x": 20,
|
||||
"y": 1,
|
||||
"z": 20
|
||||
},
|
||||
"position" : {
|
||||
"x": 0,
|
||||
"y": -12,
|
||||
"z": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"Version": 84
|
||||
}
|
|
@ -353,7 +353,7 @@ static const QString WEB_VIEW_TAG = "noDownload=true";
|
|||
static const QString ZIP_EXTENSION = ".zip";
|
||||
static const QString CONTENT_ZIP_EXTENSION = ".content.zip";
|
||||
|
||||
static const float MIRROR_FULLSCREEN_DISTANCE = 0.389f;
|
||||
static const float MIRROR_FULLSCREEN_DISTANCE = 0.789f;
|
||||
|
||||
static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND;
|
||||
|
||||
|
@ -377,8 +377,6 @@ static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop";
|
|||
|
||||
static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
|
||||
|
||||
static const QString DOMAIN_SPAWNING_POINT = "/0, -10, 0";
|
||||
|
||||
const std::vector<std::pair<QString, Application::AcceptURLMethod>> Application::_acceptedExtensions {
|
||||
{ SVO_EXTENSION, &Application::importSVOFromURL },
|
||||
{ SVO_JSON_EXTENSION, &Application::importSVOFromURL },
|
||||
|
@ -513,6 +511,27 @@ std::atomic<uint64_t> DeadlockWatchdogThread::_maxElapsed;
|
|||
std::atomic<int> DeadlockWatchdogThread::_maxElapsedAverage;
|
||||
ThreadSafeMovingAverage<int, DeadlockWatchdogThread::HEARTBEAT_SAMPLES> DeadlockWatchdogThread::_movingAverage;
|
||||
|
||||
bool isDomainURL(QUrl url) {
|
||||
if (!url.isValid()) {
|
||||
return false;
|
||||
}
|
||||
if (url.scheme() == URL_SCHEME_HIFI) {
|
||||
return true;
|
||||
}
|
||||
if (url.scheme() != URL_SCHEME_FILE) {
|
||||
// TODO -- once Octree::readFromURL no-longer takes over the main event-loop, serverless-domain urls can
|
||||
// be loaded over http(s)
|
||||
// && url.scheme() != URL_SCHEME_HTTP &&
|
||||
// url.scheme() != URL_SCHEME_HTTPS
|
||||
return false;
|
||||
}
|
||||
if (url.path().endsWith(".json", Qt::CaseInsensitive) ||
|
||||
url.path().endsWith(".json.gz", Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
class MyNativeEventFilter : public QAbstractNativeEventFilter {
|
||||
public:
|
||||
|
@ -542,7 +561,7 @@ public:
|
|||
if (message->message == WM_COPYDATA) {
|
||||
COPYDATASTRUCT* pcds = (COPYDATASTRUCT*)(message->lParam);
|
||||
QUrl url = QUrl((const char*)(pcds->lpData));
|
||||
if (url.isValid() && url.scheme() == HIFI_URL_SCHEME) {
|
||||
if (isDomainURL(url)) {
|
||||
DependencyManager::get<AddressManager>()->handleLookupString(url.toString());
|
||||
return true;
|
||||
}
|
||||
|
@ -922,7 +941,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true),
|
||||
_preferredCursor("preferredCursor", DEFAULT_CURSOR_NAME),
|
||||
_scaleMirror(1.0f),
|
||||
_rotateMirror(0.0f),
|
||||
_mirrorYawOffset(0.0f),
|
||||
_raiseMirror(0.0f),
|
||||
_enableProcessOctreeThread(true),
|
||||
_lastNackTime(usecTimestampNow()),
|
||||
|
@ -1035,7 +1054,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
// setup a timer for domain-server check ins
|
||||
QTimer* domainCheckInTimer = new QTimer(this);
|
||||
connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
|
||||
connect(domainCheckInTimer, &QTimer::timeout, [this, nodeList] {
|
||||
if (!isServerlessMode()) {
|
||||
nodeList->sendDomainServerCheckIn();
|
||||
}
|
||||
});
|
||||
domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
|
||||
connect(this, &QCoreApplication::aboutToQuit, [domainCheckInTimer] {
|
||||
domainCheckInTimer->stop();
|
||||
|
@ -1097,9 +1120,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
const DomainHandler& domainHandler = nodeList->getDomainHandler();
|
||||
|
||||
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
|
||||
connect(&domainHandler, SIGNAL(domainURLChanged(QUrl)), SLOT(domainURLChanged(QUrl)));
|
||||
connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain()));
|
||||
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle()));
|
||||
connect(&domainHandler, SIGNAL(connectedToDomain(QUrl)), SLOT(updateWindowTitle()));
|
||||
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
|
||||
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &Application::clearDomainAvatars);
|
||||
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this]() {
|
||||
|
@ -2046,7 +2069,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
connect(&_addAssetToWorldErrorTimer, &QTimer::timeout, this, &Application::addAssetToWorldErrorTimeout);
|
||||
|
||||
connect(this, &QCoreApplication::aboutToQuit, this, &Application::addAssetToWorldMessageClose);
|
||||
connect(&domainHandler, &DomainHandler::hostnameChanged, this, &Application::addAssetToWorldMessageClose);
|
||||
connect(&domainHandler, &DomainHandler::domainURLChanged, this, &Application::addAssetToWorldMessageClose);
|
||||
|
||||
updateSystemTabletMode();
|
||||
|
||||
|
@ -2788,8 +2811,9 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
|
|||
}
|
||||
else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||
_thirdPersonHMDCameraBoomValid= false;
|
||||
|
||||
if (isHMDMode()) {
|
||||
auto mirrorBodyOrientation = myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f));
|
||||
auto mirrorBodyOrientation = myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, PI + _mirrorYawOffset, 0.0f));
|
||||
|
||||
glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix());
|
||||
// Mirror HMD yaw and roll
|
||||
|
@ -2812,12 +2836,15 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
|
|||
+ mirrorBodyOrientation * hmdOffset);
|
||||
}
|
||||
else {
|
||||
_myCamera.setOrientation(myAvatar->getWorldOrientation()
|
||||
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
const float YAW_SPEED = TWO_PI / 5.0f;
|
||||
float deltaYaw = userInputMapper->getActionState(controller::Action::YAW) * YAW_SPEED * deltaTime;
|
||||
_mirrorYawOffset += deltaYaw;
|
||||
_myCamera.setOrientation(myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, PI + _mirrorYawOffset, 0.0f)));
|
||||
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
|
||||
+ glm::vec3(0, _raiseMirror * myAvatar->getModelScale(), 0)
|
||||
+ (myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
|
||||
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
|
||||
+ (myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, _mirrorYawOffset, 0.0f))) *
|
||||
glm::vec3(0.0f, 0.0f, -1.0f) * myAvatar->getBoomLength() * _scaleMirror);
|
||||
}
|
||||
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
|
||||
}
|
||||
|
@ -3023,27 +3050,27 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
|
|||
|
||||
QString sentTo;
|
||||
|
||||
// If this is a first run we short-circuit the address passed in
|
||||
if (firstRun.get()) {
|
||||
// If this is a first run we short-circuit the address passed in
|
||||
if (firstRun.get()) {
|
||||
#if !defined(Q_OS_ANDROID)
|
||||
showHelp();
|
||||
#endif
|
||||
if (sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox();
|
||||
sentTo = SENT_TO_SANDBOX;
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
sentTo = SENT_TO_ENTRY;
|
||||
}
|
||||
firstRun.set(false);
|
||||
|
||||
showHelp();
|
||||
#endif
|
||||
if (sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox();
|
||||
sentTo = SENT_TO_SANDBOX;
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
|
||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
sentTo = SENT_TO_PREVIOUS_LOCATION;
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
sentTo = SENT_TO_ENTRY;
|
||||
}
|
||||
firstRun.set(false);
|
||||
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
|
||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
sentTo = SENT_TO_PREVIOUS_LOCATION;
|
||||
}
|
||||
|
||||
UserActivityLogger::getInstance().logAction("startup_sent_to", {
|
||||
{ "sent_to", sentTo },
|
||||
|
@ -3083,6 +3110,57 @@ bool Application::importFromZIP(const QString& filePath) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Application::isServerlessMode() const {
|
||||
auto tree = getEntities()->getTree();
|
||||
if (tree) {
|
||||
return tree->isServerlessMode();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Application::setIsServerlessMode(bool serverlessDomain) {
|
||||
auto tree = getEntities()->getTree();
|
||||
if (tree) {
|
||||
tree->setIsServerlessMode(serverlessDomain);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::loadServerlessDomain(QUrl domainURL) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "loadServerlessDomain", Q_ARG(QUrl, domainURL));
|
||||
return;
|
||||
}
|
||||
|
||||
if (domainURL.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QUuid serverlessSessionID = QUuid::createUuid();
|
||||
getMyAvatar()->setSessionUUID(serverlessSessionID);
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->setSessionUUID(serverlessSessionID);
|
||||
|
||||
// there is no domain-server to tell us our permissions, so enable all
|
||||
NodePermissions permissions;
|
||||
permissions.setAll(true);
|
||||
nodeList->setPermissions(permissions);
|
||||
|
||||
// we can't import directly into the main tree because we would need to lock it, and
|
||||
// Octree::readFromURL calls loop.exec which can run code which will also attempt to lock the tree.
|
||||
EntityTreePointer tmpTree(new EntityTree());
|
||||
tmpTree->setIsServerlessMode(true);
|
||||
tmpTree->createRootElement();
|
||||
auto myAvatar = getMyAvatar();
|
||||
tmpTree->setMyAvatar(myAvatar);
|
||||
bool success = tmpTree->readFromURL(domainURL.toString());
|
||||
if (success) {
|
||||
tmpTree->reaverageOctreeElements();
|
||||
tmpTree->sendEntities(&_entityEditSender, getEntities()->getTree(), 0, 0, 0);
|
||||
}
|
||||
|
||||
_fullSceneReceivedCounter++;
|
||||
}
|
||||
|
||||
bool Application::importImage(const QString& urlString) {
|
||||
qCDebug(interfaceapp) << "An image file has been dropped in";
|
||||
QString filepath(urlString);
|
||||
|
@ -3324,8 +3402,6 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
} else {
|
||||
setFullscreen(nullptr);
|
||||
}
|
||||
} else {
|
||||
Menu::getInstance()->triggerOption(MenuOption::AddressBar);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -3387,13 +3463,6 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_F: {
|
||||
if (isOption) {
|
||||
_physicsEngine->dumpNextStats();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::Key_Asterisk:
|
||||
Menu::getInstance()->triggerOption(MenuOption::DefaultSkybox);
|
||||
break;
|
||||
|
@ -3413,22 +3482,25 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
case Qt::Key_S:
|
||||
if (isShifted && isMeta && !isOption) {
|
||||
Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings);
|
||||
} else if (!isOption && !isShifted && isMeta) {
|
||||
AudioInjectorOptions options;
|
||||
options.localOnly = true;
|
||||
options.stereo = true;
|
||||
|
||||
if (_snapshotSoundInjector) {
|
||||
_snapshotSoundInjector->setOptions(options);
|
||||
_snapshotSoundInjector->restart();
|
||||
} else {
|
||||
QByteArray samples = _snapshotSound->getByteArray();
|
||||
_snapshotSoundInjector = AudioInjector::playSound(samples, options);
|
||||
}
|
||||
takeSnapshot(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_P: {
|
||||
AudioInjectorOptions options;
|
||||
options.localOnly = true;
|
||||
options.stereo = true;
|
||||
|
||||
if (_snapshotSoundInjector) {
|
||||
_snapshotSoundInjector->setOptions(options);
|
||||
_snapshotSoundInjector->restart();
|
||||
} else {
|
||||
QByteArray samples = _snapshotSound->getByteArray();
|
||||
_snapshotSoundInjector = AudioInjector::playSound(samples, options);
|
||||
}
|
||||
takeSnapshot(true);
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::Key_Apostrophe: {
|
||||
if (isMeta) {
|
||||
auto cursor = Cursor::Manager::instance().getCursor();
|
||||
|
@ -3452,38 +3524,6 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
Menu::getInstance()->triggerOption(MenuOption::Chat);
|
||||
break;
|
||||
|
||||
case Qt::Key_Up:
|
||||
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||
if (!isShifted) {
|
||||
_scaleMirror *= 0.95f;
|
||||
} else {
|
||||
_raiseMirror += 0.05f;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_Down:
|
||||
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||
if (!isShifted) {
|
||||
_scaleMirror *= 1.05f;
|
||||
} else {
|
||||
_raiseMirror -= 0.05f;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_Left:
|
||||
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||
_rotateMirror += PI / 20.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_Right:
|
||||
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||
_rotateMirror -= PI / 20.0f;
|
||||
}
|
||||
break;
|
||||
|
||||
#if 0
|
||||
case Qt::Key_I:
|
||||
if (isShifted) {
|
||||
|
@ -4583,7 +4623,7 @@ void Application::initDisplay() {
|
|||
}
|
||||
|
||||
void Application::init() {
|
||||
|
||||
|
||||
// Make sure Login state is up to date
|
||||
DependencyManager::get<DialogsManager>()->toggleLoginDialog();
|
||||
if (!DISABLE_DEFERRED) {
|
||||
|
@ -4608,7 +4648,9 @@ void Application::init() {
|
|||
qCDebug(interfaceapp) << "Loaded settings";
|
||||
|
||||
// fire off an immediate domain-server check in now that settings are loaded
|
||||
DependencyManager::get<NodeList>()->sendDomainServerCheckIn();
|
||||
if (!isServerlessMode()) {
|
||||
DependencyManager::get<NodeList>()->sendDomainServerCheckIn();
|
||||
}
|
||||
|
||||
// This allows collision to be set up properly for shape entities supported by GeometryCache.
|
||||
// This is before entity setup to ensure that it's ready for whenever instance collision is initialized.
|
||||
|
@ -4851,8 +4893,10 @@ void Application::cameraMenuChanged() {
|
|||
auto menu = Menu::getInstance();
|
||||
if (menu->isOptionChecked(MenuOption::FullscreenMirror)) {
|
||||
if (!isHMDMode() && _myCamera.getMode() != CAMERA_MODE_MIRROR) {
|
||||
_mirrorYawOffset = 0.0f;
|
||||
_myCamera.setMode(CAMERA_MODE_MIRROR);
|
||||
getMyAvatar()->reset(false, false, false); // to reset any active MyAvatar::FollowHelpers
|
||||
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT);
|
||||
}
|
||||
} else if (menu->isOptionChecked(MenuOption::FirstPerson)) {
|
||||
if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) {
|
||||
|
@ -5132,7 +5176,7 @@ void Application::update(float deltaTime) {
|
|||
// FIXME can we drop drive keys and just have the avatar read the action states directly?
|
||||
myAvatar->clearDriveKeys();
|
||||
if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) {
|
||||
if (!_controllerScriptingInterface->areActionsCaptured()) {
|
||||
if (!_controllerScriptingInterface->areActionsCaptured() && _myCamera.getMode() != CAMERA_MODE_MIRROR) {
|
||||
myAvatar->setDriveKey(MyAvatar::TRANSLATE_Z, -1.0f * userInputMapper->getActionState(controller::Action::TRANSLATE_Z));
|
||||
myAvatar->setDriveKey(MyAvatar::TRANSLATE_Y, userInputMapper->getActionState(controller::Action::TRANSLATE_Y));
|
||||
myAvatar->setDriveKey(MyAvatar::TRANSLATE_X, userInputMapper->getActionState(controller::Action::TRANSLATE_X));
|
||||
|
@ -5145,6 +5189,7 @@ void Application::update(float deltaTime) {
|
|||
myAvatar->setDriveKey(MyAvatar::ZOOM, userInputMapper->getActionState(controller::Action::TRANSLATE_CAMERA_Z));
|
||||
}
|
||||
|
||||
myAvatar->setSprintMode((bool)userInputMapper->getActionState(controller::Action::SPRINT));
|
||||
static const std::vector<controller::Action> avatarControllerActions = {
|
||||
controller::Action::LEFT_HAND,
|
||||
controller::Action::RIGHT_HAND,
|
||||
|
@ -5742,10 +5787,15 @@ void Application::updateWindowTitle() const {
|
|||
|
||||
QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)";
|
||||
QString username = accountManager->getAccountInfo().getUsername();
|
||||
QString currentPlaceName = DependencyManager::get<AddressManager>()->getHost();
|
||||
|
||||
if (currentPlaceName.isEmpty()) {
|
||||
currentPlaceName = nodeList->getDomainHandler().getHostname();
|
||||
QString currentPlaceName;
|
||||
if (isServerlessMode()) {
|
||||
currentPlaceName = "serverless: " + DependencyManager::get<AddressManager>()->getDomainURL().toString();
|
||||
} else {
|
||||
currentPlaceName = DependencyManager::get<AddressManager>()->getDomainURL().host();
|
||||
if (currentPlaceName.isEmpty()) {
|
||||
currentPlaceName = nodeList->getDomainHandler().getHostname();
|
||||
}
|
||||
}
|
||||
|
||||
QString title = QString() + (!username.isEmpty() ? username + " @ " : QString())
|
||||
|
@ -5758,7 +5808,7 @@ void Application::updateWindowTitle() const {
|
|||
_window->setWindowTitle(title);
|
||||
|
||||
// updateTitleWindow gets called whenever there's a change regarding the domain, so rather
|
||||
// than placing this within domainChanged, it's placed here to cover the other potential cases.
|
||||
// than placing this within domainURLChanged, it's placed here to cover the other potential cases.
|
||||
DependencyManager::get< MessagesClient >()->sendLocalMessage("Toolbar-DomainChanged", "");
|
||||
}
|
||||
|
||||
|
@ -5797,15 +5847,22 @@ void Application::clearDomainAvatars() {
|
|||
DependencyManager::get<AvatarManager>()->clearOtherAvatars();
|
||||
}
|
||||
|
||||
void Application::domainChanged(const QString& domainHostname) {
|
||||
updateWindowTitle();
|
||||
void Application::domainURLChanged(QUrl domainURL) {
|
||||
// disable physics until we have enough information about our new location to not cause craziness.
|
||||
resetPhysicsReadyInformation();
|
||||
setIsServerlessMode(domainURL.scheme() != URL_SCHEME_HIFI);
|
||||
if (isServerlessMode()) {
|
||||
loadServerlessDomain(domainURL);
|
||||
}
|
||||
updateWindowTitle();
|
||||
}
|
||||
|
||||
|
||||
void Application::resettingDomain() {
|
||||
_notifiedPacketVersionMismatchThisDomain = false;
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
clearDomainOctreeDetails();
|
||||
}
|
||||
|
||||
void Application::nodeAdded(SharedNodePointer node) const {
|
||||
|
@ -5922,22 +5979,22 @@ bool Application::nearbyEntitiesAreReadyForPhysics() {
|
|||
AABox avatarBox(getMyAvatar()->getWorldPosition() - glm::vec3(PHYSICS_READY_RANGE), glm::vec3(2 * PHYSICS_READY_RANGE));
|
||||
// create two functions that use avatarBox (entityScan and elementScan), the second calls the first
|
||||
std::function<bool (EntityItemPointer&)> entityScan = [=](EntityItemPointer& entity) {
|
||||
if (entity->shouldBePhysical()) {
|
||||
bool success = false;
|
||||
AABox entityBox = entity->getAABox(success);
|
||||
// important: bail for entities that cannot supply a valid AABox
|
||||
return success && avatarBox.touches(entityBox);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (entity->shouldBePhysical()) {
|
||||
bool success = false;
|
||||
AABox entityBox = entity->getAABox(success);
|
||||
// important: bail for entities that cannot supply a valid AABox
|
||||
return success && avatarBox.touches(entityBox);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
std::function<bool(const OctreeElementPointer&, void*)> elementScan = [&](const OctreeElementPointer& element, void* unused) {
|
||||
if (element->getAACube().touches(avatarBox)) {
|
||||
EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element);
|
||||
entityTreeElement->getEntities(entityScan, entities);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (element->getAACube().touches(avatarBox)) {
|
||||
EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element);
|
||||
entityTreeElement->getEntities(entityScan, entities);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
entityTree->withReadLock([&] {
|
||||
// Pass the second function to the general-purpose EntityTree::findEntities()
|
||||
|
@ -6173,7 +6230,7 @@ bool Application::canAcceptURL(const QString& urlString) const {
|
|||
QUrl url(urlString);
|
||||
if (url.query().contains(WEB_VIEW_TAG)) {
|
||||
return false;
|
||||
} else if (urlString.startsWith(HIFI_URL_SCHEME)) {
|
||||
} else if (urlString.startsWith(URL_SCHEME_HIFI)) {
|
||||
return true;
|
||||
}
|
||||
QString lowerPath = url.path().toLower();
|
||||
|
@ -6186,15 +6243,14 @@ bool Application::canAcceptURL(const QString& urlString) const {
|
|||
}
|
||||
|
||||
bool Application::acceptURL(const QString& urlString, bool defaultUpload) {
|
||||
if (urlString.startsWith(HIFI_URL_SCHEME)) {
|
||||
// this is a hifi URL - have the AddressManager handle it
|
||||
emit receivedHifiSchemeURL(urlString);
|
||||
QUrl url(urlString);
|
||||
if (isDomainURL(url)) {
|
||||
// this is a URL for a domain, either hifi:// or serverless - have the AddressManager handle it
|
||||
QMetaObject::invokeMethod(DependencyManager::get<AddressManager>().data(), "handleLookupString",
|
||||
Qt::AutoConnection, Q_ARG(const QString&, urlString));
|
||||
return true;
|
||||
}
|
||||
|
||||
QUrl url(urlString);
|
||||
QString lowerPath = url.path().toLower();
|
||||
for (auto& pair : _acceptedExtensions) {
|
||||
if (lowerPath.endsWith(pair.first, Qt::CaseInsensitive)) {
|
||||
|
@ -7033,7 +7089,7 @@ void Application::packageModel() {
|
|||
|
||||
void Application::openUrl(const QUrl& url) const {
|
||||
if (!url.isEmpty()) {
|
||||
if (url.scheme() == HIFI_URL_SCHEME) {
|
||||
if (url.scheme() == URL_SCHEME_HIFI) {
|
||||
DependencyManager::get<AddressManager>()->handleLookupString(url.toString());
|
||||
} else {
|
||||
// address manager did not handle - ask QDesktopServices to handle
|
||||
|
|
|
@ -284,6 +284,8 @@ public:
|
|||
bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; }
|
||||
void saveNextPhysicsStats(QString filename);
|
||||
|
||||
bool isServerlessMode() const;
|
||||
|
||||
void replaceDomainContent(const QString& url);
|
||||
|
||||
signals:
|
||||
|
@ -295,7 +297,6 @@ signals:
|
|||
void activeDisplayPluginChanged();
|
||||
|
||||
void uploadRequest(QString path);
|
||||
void receivedHifiSchemeURL(const QString& url);
|
||||
|
||||
public slots:
|
||||
QVector<EntityItemID> pasteEntities(float x, float y, float z);
|
||||
|
@ -391,6 +392,9 @@ public slots:
|
|||
const QString getPreferredCursor() const { return _preferredCursor.get(); }
|
||||
void setPreferredCursor(const QString& cursor);
|
||||
|
||||
void setIsServerlessMode(bool serverlessDomain);
|
||||
void loadServerlessDomain(QUrl domainURL);
|
||||
|
||||
Q_INVOKABLE bool askBeforeSetAvatarUrl(const QString& avatarUrl) { return askToSetAvatarUrl(avatarUrl); }
|
||||
|
||||
private slots:
|
||||
|
@ -425,7 +429,7 @@ private slots:
|
|||
|
||||
void setSessionUUID(const QUuid& sessionUUID) const;
|
||||
|
||||
void domainChanged(const QString& domainHostname);
|
||||
void domainURLChanged(QUrl domainURL);
|
||||
void updateWindowTitle() const;
|
||||
void nodeAdded(SharedNodePointer node) const;
|
||||
void nodeActivated(SharedNodePointer node);
|
||||
|
@ -571,7 +575,7 @@ private:
|
|||
Setting::Handle<QString> _preferredCursor;
|
||||
|
||||
float _scaleMirror;
|
||||
float _rotateMirror;
|
||||
float _mirrorYawOffset;
|
||||
float _raiseMirror;
|
||||
|
||||
QSet<int> _keysPressed;
|
||||
|
|
|
@ -49,7 +49,7 @@ void DiscoverabilityManager::updateLocation() {
|
|||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
auto addressManager = DependencyManager::get<AddressManager>();
|
||||
auto& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
|
||||
bool discoverable = (_mode.get() != Discoverability::None);
|
||||
bool discoverable = (_mode.get() != Discoverability::None) && !domainHandler.isServerless();
|
||||
|
||||
|
||||
if (accountManager->isLoggedIn()) {
|
||||
|
|
|
@ -229,21 +229,21 @@ Menu::Menu() {
|
|||
|
||||
// View > First Person
|
||||
auto firstPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
|
||||
viewMenu, MenuOption::FirstPerson, Qt::CTRL | Qt::Key_F,
|
||||
viewMenu, MenuOption::FirstPerson, Qt::Key_1,
|
||||
true, qApp, SLOT(cameraMenuChanged())));
|
||||
|
||||
firstPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
||||
// View > Third Person
|
||||
auto thirdPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
|
||||
viewMenu, MenuOption::ThirdPerson, Qt::CTRL | Qt::Key_G,
|
||||
viewMenu, MenuOption::ThirdPerson, Qt::Key_3,
|
||||
false, qApp, SLOT(cameraMenuChanged())));
|
||||
|
||||
thirdPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
||||
// View > Mirror
|
||||
auto viewMirrorAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
|
||||
viewMenu, MenuOption::FullscreenMirror, Qt::CTRL | Qt::Key_H,
|
||||
viewMenu, MenuOption::FullscreenMirror, Qt::Key_2,
|
||||
false, qApp, SLOT(cameraMenuChanged())));
|
||||
|
||||
viewMirrorAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
|
|
@ -2186,7 +2186,6 @@ void MyAvatar::updateActionMotor(float deltaTime) {
|
|||
glm::vec3 direction = forward + right;
|
||||
if (state == CharacterController::State::Hover ||
|
||||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
|
||||
// we can fly --> support vertical motion
|
||||
glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP;
|
||||
direction += up;
|
||||
}
|
||||
|
@ -2204,10 +2203,11 @@ void MyAvatar::updateActionMotor(float deltaTime) {
|
|||
|
||||
if (state == CharacterController::State::Hover) {
|
||||
// we're flying --> complex acceleration curve that builds on top of current motor speed and caps at some max speed
|
||||
|
||||
float motorSpeed = glm::length(_actionMotorVelocity);
|
||||
float finalMaxMotorSpeed = getSensorToWorldScale() * DEFAULT_AVATAR_MAX_FLYING_SPEED;
|
||||
float finalMaxMotorSpeed = getSensorToWorldScale() * DEFAULT_AVATAR_MAX_FLYING_SPEED * _walkSpeedScalar;
|
||||
float speedGrowthTimescale = 2.0f;
|
||||
float speedIncreaseFactor = 1.8f;
|
||||
float speedIncreaseFactor = 1.8f * _walkSpeedScalar;
|
||||
motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale, 0.0f, 1.0f) * speedIncreaseFactor;
|
||||
const float maxBoostSpeed = getSensorToWorldScale() * MAX_BOOST_SPEED;
|
||||
|
||||
|
@ -2223,7 +2223,7 @@ void MyAvatar::updateActionMotor(float deltaTime) {
|
|||
_actionMotorVelocity = motorSpeed * direction;
|
||||
} else {
|
||||
// we're interacting with a floor --> simple horizontal speed and exponential decay
|
||||
_actionMotorVelocity = getSensorToWorldScale() * _walkSpeed.get() * direction;
|
||||
_actionMotorVelocity = getSensorToWorldScale() * (_walkSpeed.get() * _walkSpeedScalar) * direction;
|
||||
}
|
||||
|
||||
float boomChange = getDriveKey(ZOOM);
|
||||
|
@ -2816,7 +2816,11 @@ float MyAvatar::getUserEyeHeight() const {
|
|||
}
|
||||
|
||||
float MyAvatar::getWalkSpeed() const {
|
||||
return _walkSpeed.get();
|
||||
return _walkSpeed.get() * _walkSpeedScalar;
|
||||
}
|
||||
|
||||
void MyAvatar::setSprintMode(bool sprint) {
|
||||
_walkSpeedScalar = sprint ? AVATAR_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR;
|
||||
}
|
||||
|
||||
void MyAvatar::setWalkSpeed(float value) {
|
||||
|
|
|
@ -395,6 +395,7 @@ public:
|
|||
// Set what driving keys are being pressed to control thrust levels
|
||||
void clearDriveKeys();
|
||||
void setDriveKey(DriveKeys key, float val);
|
||||
void setSprintMode(bool sprint);
|
||||
float getDriveKey(DriveKeys key) const;
|
||||
Q_INVOKABLE float getRawDriveKey(DriveKeys key) const;
|
||||
void relayDriveKeysToCharacterController();
|
||||
|
@ -836,7 +837,8 @@ private:
|
|||
std::map<controller::Action, controller::Pose> _controllerPoseMap;
|
||||
mutable std::mutex _controllerPoseMapMutex;
|
||||
|
||||
bool _hmdLeanRecenterEnabled = true;
|
||||
bool _hmdLeanRecenterEnabled { true };
|
||||
bool _sprint { false };
|
||||
AnimPose _prePhysicsRoomPose;
|
||||
std::mutex _holdActionsMutex;
|
||||
std::vector<AvatarActionHold*> _holdActions;
|
||||
|
@ -866,6 +868,7 @@ private:
|
|||
|
||||
// max unscaled forward movement speed
|
||||
ThreadSafeValueCache<float> _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
|
||||
float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR };
|
||||
};
|
||||
|
||||
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
|
||||
|
|
|
@ -52,6 +52,8 @@ Handler(inventory)
|
|||
Handler(transferHfcToNode)
|
||||
Handler(transferHfcToUsername)
|
||||
Handler(alreadyOwned)
|
||||
Handler(availableUpdates)
|
||||
Handler(updateItem)
|
||||
|
||||
void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
|
@ -376,3 +378,23 @@ void Ledger::alreadyOwned(const QString& marketplaceId) {
|
|||
qDebug(commerce) << "User attempted to use the alreadyOwned endpoint, but cachedPublicKeys was empty!";
|
||||
}
|
||||
}
|
||||
|
||||
void Ledger::getAvailableUpdates(const QString& itemId) {
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
QString endpoint = "available_updates";
|
||||
QJsonObject request;
|
||||
request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys());
|
||||
if (!itemId.isEmpty()) {
|
||||
request["marketplace_item_id"] = itemId;
|
||||
}
|
||||
send(endpoint, "availableUpdatesSuccess", "availableUpdatesFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request);
|
||||
}
|
||||
|
||||
void Ledger::updateItem(const QString& hfc_key, const QString& certificate_id) {
|
||||
QJsonObject transaction;
|
||||
transaction["public_key"] = hfc_key;
|
||||
transaction["certificate_id"] = certificate_id;
|
||||
QJsonDocument transactionDoc{ transaction };
|
||||
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
|
||||
signedSend("transaction", transactionString, hfc_key, "update_item", "updateItemSuccess", "updateItemFailure");
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ public:
|
|||
void transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage);
|
||||
void transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage);
|
||||
void alreadyOwned(const QString& marketplaceId);
|
||||
void getAvailableUpdates(const QString& itemId = "");
|
||||
void updateItem(const QString& hfc_key, const QString& certificate_id);
|
||||
|
||||
enum CertificateStatus {
|
||||
CERTIFICATE_STATUS_UNKNOWN = 0,
|
||||
|
@ -57,6 +59,8 @@ signals:
|
|||
void transferHfcToNodeResult(QJsonObject result);
|
||||
void transferHfcToUsernameResult(QJsonObject result);
|
||||
void alreadyOwnedResult(QJsonObject result);
|
||||
void availableUpdatesResult(QJsonObject result);
|
||||
void updateItemResult(QJsonObject result);
|
||||
|
||||
void updateCertificateStatus(const QString& certID, uint certStatus);
|
||||
|
||||
|
@ -83,6 +87,10 @@ public slots:
|
|||
void transferHfcToUsernameFailure(QNetworkReply& reply);
|
||||
void alreadyOwnedSuccess(QNetworkReply& reply);
|
||||
void alreadyOwnedFailure(QNetworkReply& reply);
|
||||
void availableUpdatesSuccess(QNetworkReply& reply);
|
||||
void availableUpdatesFailure(QNetworkReply& reply);
|
||||
void updateItemSuccess(QNetworkReply& reply);
|
||||
void updateItemFailure(QNetworkReply& reply);
|
||||
|
||||
private:
|
||||
QJsonObject apiResponse(const QString& label, QNetworkReply& reply);
|
||||
|
|
|
@ -38,7 +38,8 @@ QmlCommerce::QmlCommerce() {
|
|||
connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus);
|
||||
connect(ledger.data(), &Ledger::transferHfcToNodeResult, this, &QmlCommerce::transferHfcToNodeResult);
|
||||
connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult);
|
||||
connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult);
|
||||
connect(ledger.data(), &Ledger::availableUpdatesResult, this, &QmlCommerce::availableUpdatesResult);
|
||||
connect(ledger.data(), &Ledger::updateItemResult, this, &QmlCommerce::updateItemResult);
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
|
||||
|
@ -349,3 +350,20 @@ bool QmlCommerce::openApp(const QString& itemHref) {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
void QmlCommerce::getAvailableUpdates(const QString& itemId) {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
ledger->getAvailableUpdates(itemId);
|
||||
}
|
||||
|
||||
void QmlCommerce::updateItem(const QString& certificateId) {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
QStringList keys = wallet->listPublicKeys();
|
||||
if (keys.count() == 0) {
|
||||
QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } };
|
||||
return emit updateItemResult(result);
|
||||
}
|
||||
QString key = keys[0];
|
||||
ledger->updateItem(key, certificateId);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@ signals:
|
|||
void accountResult(QJsonObject result);
|
||||
void certificateInfoResult(QJsonObject result);
|
||||
void alreadyOwnedResult(QJsonObject result);
|
||||
void availableUpdatesResult(QJsonObject result);
|
||||
void updateItemResult(QJsonObject result);
|
||||
|
||||
void updateCertificateStatus(const QString& certID, uint certStatus);
|
||||
|
||||
|
@ -89,6 +91,9 @@ protected:
|
|||
Q_INVOKABLE bool uninstallApp(const QString& appHref);
|
||||
Q_INVOKABLE bool openApp(const QString& appHref);
|
||||
|
||||
Q_INVOKABLE void getAvailableUpdates(const QString& itemId = "");
|
||||
Q_INVOKABLE void updateItem(const QString& certificateId);
|
||||
|
||||
private:
|
||||
QString _appsPath;
|
||||
};
|
||||
|
|
|
@ -129,7 +129,7 @@ int main(int argc, const char* argv[]) {
|
|||
if (socket.waitForConnected(LOCAL_SERVER_TIMEOUT_MS)) {
|
||||
if (parser.isSet(urlOption)) {
|
||||
QUrl url = QUrl(parser.value(urlOption));
|
||||
if (url.isValid() && url.scheme() == HIFI_URL_SCHEME) {
|
||||
if (url.isValid() && url.scheme() == URL_SCHEME_HIFI) {
|
||||
qDebug() << "Writing URL to local socket";
|
||||
socket.write(url.toString().toUtf8());
|
||||
if (!socket.waitForBytesWritten(5000)) {
|
||||
|
|
|
@ -69,11 +69,11 @@ void AssetMappingsScriptingInterface::getMapping(QString path, QJSValue callback
|
|||
|
||||
void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping, QJSValue startedCallback, QJSValue completedCallback, bool dropEvent) {
|
||||
static const QString helpText =
|
||||
"Upload your asset to a specific folder by entering the full path. Specifying\n"
|
||||
"Upload your asset to a specific folder by entering the full path. Specifying "
|
||||
"a new folder name will automatically create that folder for you.";
|
||||
static const QString dropHelpText =
|
||||
"This file will be added to your Asset Server.\n"
|
||||
"Use the field below to place your file in a specific folder or to rename it.\n"
|
||||
"Use the field below to place your file in a specific folder or to rename it. "
|
||||
"Specifying a new folder name will automatically create that folder for you.";
|
||||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
|
@ -152,6 +152,10 @@ void AssetMappingsScriptingInterface::deleteMappings(QStringList paths, QJSValue
|
|||
request->start();
|
||||
}
|
||||
|
||||
void AssetMappingsScriptingInterface::sortProxyModel(int column, Qt::SortOrder order) {
|
||||
_proxyModel.sort(column, order);
|
||||
}
|
||||
|
||||
void AssetMappingsScriptingInterface::getAllMappings(QJSValue callback) {
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto request = assetClient->createGetAllMappingsRequest();
|
||||
|
@ -287,7 +291,7 @@ void AssetMappingModel::refresh() {
|
|||
item->setData(parts[i], Qt::UserRole + 2);
|
||||
item->setData("atp:" + fullPath, Qt::UserRole + 3);
|
||||
item->setData(fullPath, Qt::UserRole + 4);
|
||||
|
||||
|
||||
if (lastItem) {
|
||||
lastItem->appendRow(item);
|
||||
} else {
|
||||
|
|
|
@ -82,6 +82,7 @@ public:
|
|||
Q_INVOKABLE void getAllMappings(QJSValue callback = QJSValue());
|
||||
Q_INVOKABLE void renameMapping(QString oldPath, QString newPath, QJSValue callback = QJSValue());
|
||||
Q_INVOKABLE void setBakingEnabled(QStringList paths, bool enabled, QJSValue callback = QJSValue());
|
||||
Q_INVOKABLE void sortProxyModel(int column, Qt::SortOrder order = Qt::AscendingOrder);
|
||||
|
||||
protected:
|
||||
QSet<AssetRequest*> _pendingRequests;
|
||||
|
|
|
@ -124,7 +124,7 @@ void WindowScriptingInterface::promptAsync(const QString& message, const QString
|
|||
}
|
||||
|
||||
void WindowScriptingInterface::disconnectedFromDomain() {
|
||||
emit domainChanged("");
|
||||
emit domainChanged(QUrl());
|
||||
}
|
||||
|
||||
QString fixupPathForMac(const QString& directory) {
|
||||
|
|
|
@ -524,7 +524,7 @@ signals:
|
|||
* Triggered when you change the domain you're visiting. <strong>Warning:</strong> Is not emitted if you go to domain that
|
||||
* isn't running.
|
||||
* @function Window.domainChanged
|
||||
* @param {string} domain - The domain's IP address.
|
||||
* @param {string} domainURL - The domain's URL.
|
||||
* @returns {Signal}
|
||||
* @example <caption>Report when you change domains.</caption>
|
||||
* function onDomainChanged(domain) {
|
||||
|
@ -533,7 +533,7 @@ signals:
|
|||
*
|
||||
* Window.domainChanged.connect(onDomainChanged);
|
||||
*/
|
||||
void domainChanged(const QString& domain);
|
||||
void domainChanged(QUrl domainURL);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when you try to navigate to a *.json, *.svo, or *.svo.json URL in a Web browser within Interface.
|
||||
|
|
|
@ -45,7 +45,6 @@ AddressBarDialog::AddressBarDialog(QQuickItem* parent) : OffscreenQmlDialog(pare
|
|||
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &AddressBarDialog::hostChanged);
|
||||
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &AddressBarDialog::hostChanged);
|
||||
connect(DependencyManager::get<DialogsManager>().data(), &DialogsManager::setUseFeed, this, &AddressBarDialog::setUseFeed);
|
||||
connect(qApp, &Application::receivedHifiSchemeURL, this, &AddressBarDialog::receivedHifiSchemeURL);
|
||||
}
|
||||
|
||||
void AddressBarDialog::loadAddress(const QString& address, bool fromSuggestions) {
|
||||
|
|
|
@ -36,7 +36,6 @@ signals:
|
|||
void backEnabledChanged();
|
||||
void forwardEnabledChanged();
|
||||
void useFeedChanged();
|
||||
void receivedHifiSchemeURL(const QString& url);
|
||||
void hostChanged();
|
||||
|
||||
protected:
|
||||
|
|
|
@ -388,6 +388,21 @@ QString Overlays::getOverlayType(OverlayID overlayId) {
|
|||
return "";
|
||||
}
|
||||
|
||||
QObject* Overlays::getOverlayObject(OverlayID id) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QObject* result;
|
||||
PROFILE_RANGE(script, __FUNCTION__);
|
||||
BLOCKING_INVOKE_METHOD(this, "getOverlayObject", Q_RETURN_ARG(QObject*, result), Q_ARG(OverlayID, id));
|
||||
return result;
|
||||
}
|
||||
|
||||
Overlay::Pointer thisOverlay = getOverlay(id);
|
||||
if (thisOverlay) {
|
||||
return qobject_cast<QObject*>(&(*thisOverlay));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) {
|
||||
if (!_enabled) {
|
||||
return UNKNOWN_OVERLAY_ID;
|
||||
|
|
|
@ -235,6 +235,50 @@ public slots:
|
|||
*/
|
||||
QString getOverlayType(OverlayID overlayId);
|
||||
|
||||
/**jsdoc
|
||||
* Get the overlay script object. In particular, this is useful for accessing the event bridge for a <code>web3d</code>
|
||||
* overlay.
|
||||
* @function Overlays.getOverlayObject
|
||||
* @param {Uuid} overlayID - The ID of the overlay to get the script object of.
|
||||
* @returns {object} The script object for the overlay if found.
|
||||
* @example <caption>Receive "hello" messages from a <code>web3d</code> overlay.</caption>
|
||||
* // HTML file: name "web3d.html".
|
||||
* <!DOCTYPE html>
|
||||
* <html>
|
||||
* <head>
|
||||
* <title>HELLO</title>
|
||||
* </head>
|
||||
* <body>
|
||||
* <h1>HELLO</h1></h1>
|
||||
* <script>
|
||||
* setInterval(function () {
|
||||
* EventBridge.emitWebEvent("hello");
|
||||
* }, 2000);
|
||||
* </script>
|
||||
* </body>
|
||||
* </html>
|
||||
*
|
||||
* // Script file.
|
||||
* var web3dOverlay = Overlays.addOverlay("web3d", {
|
||||
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.5, z: -3 })),
|
||||
* rotation: MyAvatar.orientation,
|
||||
* url: Script.resolvePath("web3d.html"),
|
||||
* alpha: 1.0
|
||||
* });
|
||||
*
|
||||
* function onWebEventReceived(event) {
|
||||
* print("onWebEventReceived() : " + JSON.stringify(event));
|
||||
* }
|
||||
*
|
||||
* overlayObject = Overlays.getOverlayObject(web3dOverlay);
|
||||
* overlayObject.webEventReceived.connect(onWebEventReceived);
|
||||
*
|
||||
* Script.scriptEnding.connect(function () {
|
||||
* Overlays.deleteOverlay(web3dOverlay);
|
||||
* });
|
||||
*/
|
||||
QObject* getOverlayObject(OverlayID id);
|
||||
|
||||
/**jsdoc
|
||||
* Get the ID of the 2D overlay at a particular point on the screen or HUD.
|
||||
* @function Overlays.getOverlayAtPoint
|
||||
|
|
|
@ -882,6 +882,11 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar
|
|||
|
||||
//virtual
|
||||
const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
|
||||
#ifdef Q_OS_ANDROID
|
||||
// disable IK on android
|
||||
return underPoses;
|
||||
#endif
|
||||
|
||||
// allows solutionSource to be overridden by an animVar
|
||||
auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource);
|
||||
|
||||
|
|
|
@ -221,8 +221,12 @@ void Avatar::updateAvatarEntities() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (getID() == QUuid() || getID() == AVATAR_SELF_ID) {
|
||||
return; // wait until MyAvatar gets an ID before doing this.
|
||||
if (getID().isNull() ||
|
||||
getID() == AVATAR_SELF_ID ||
|
||||
DependencyManager::get<NodeList>()->getSessionUUID() == QUuid()) {
|
||||
// wait until MyAvatar and this Node gets an ID before doing this. Otherwise, various things go wrong --
|
||||
// things get their parent fixed up from AVATAR_SELF_ID to a null uuid which means "no parent".
|
||||
return;
|
||||
}
|
||||
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
|
@ -1806,4 +1810,4 @@ scriptable::ScriptableModelBase Avatar::getScriptableModel() {
|
|||
result.appendMaterials(_materials);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -183,6 +183,7 @@ namespace controller {
|
|||
makeButtonPair(Action::ACTION2, "ACTION2"),
|
||||
makeButtonPair(Action::CONTEXT_MENU, "CONTEXT_MENU"),
|
||||
makeButtonPair(Action::TOGGLE_MUTE, "TOGGLE_MUTE"),
|
||||
makeButtonPair(Action::SPRINT, "SPRINT")
|
||||
};
|
||||
return availableInputs;
|
||||
}
|
||||
|
|
|
@ -174,6 +174,7 @@ enum class Action {
|
|||
TRACKED_OBJECT_13,
|
||||
TRACKED_OBJECT_14,
|
||||
TRACKED_OBJECT_15,
|
||||
SPRINT,
|
||||
|
||||
NUM_ACTIONS
|
||||
};
|
||||
|
|
|
@ -73,7 +73,8 @@ void UserInputMapper::registerDevice(InputDevice::Pointer device) {
|
|||
|
||||
qCDebug(controllers) << "Registered input device <" << device->getName() << "> deviceID = " << deviceID;
|
||||
|
||||
for (const auto& inputMapping : device->getAvailableInputs()) {
|
||||
auto inputs = device->getAvailableInputs();
|
||||
for (const auto& inputMapping : inputs) {
|
||||
const auto& input = inputMapping.first;
|
||||
// Ignore aliases
|
||||
if (_endpointsByInput.count(input)) {
|
||||
|
@ -126,7 +127,8 @@ void UserInputMapper::removeDevice(int deviceID) {
|
|||
_mappingsByDevice.erase(mappingsEntry);
|
||||
}
|
||||
|
||||
for (const auto& inputMapping : device->getAvailableInputs()) {
|
||||
auto inputs = device->getAvailableInputs();
|
||||
for (const auto& inputMapping : inputs) {
|
||||
const auto& input = inputMapping.first;
|
||||
auto endpoint = _endpointsByInput.find(input);
|
||||
if (endpoint != _endpointsByInput.end()) {
|
||||
|
@ -171,7 +173,7 @@ InputDevice::Pointer UserInputMapper::getDevice(const Input& input) {
|
|||
}
|
||||
}
|
||||
|
||||
QString UserInputMapper::getDeviceName(uint16 deviceID) {
|
||||
QString UserInputMapper::getDeviceName(uint16 deviceID) {
|
||||
Locker locker(_lock);
|
||||
if (_registeredDevices.find(deviceID) != _registeredDevices.end()) {
|
||||
return _registeredDevices[deviceID]->_name;
|
||||
|
@ -181,7 +183,7 @@ QString UserInputMapper::getDeviceName(uint16 deviceID) {
|
|||
|
||||
int UserInputMapper::findDevice(QString name) const {
|
||||
Locker locker(_lock);
|
||||
for (auto device : _registeredDevices) {
|
||||
for (const auto& device : _registeredDevices) {
|
||||
if (device.second->_name == name) {
|
||||
return device.first;
|
||||
}
|
||||
|
@ -192,7 +194,7 @@ int UserInputMapper::findDevice(QString name) const {
|
|||
QVector<QString> UserInputMapper::getDeviceNames() {
|
||||
Locker locker(_lock);
|
||||
QVector<QString> result;
|
||||
for (auto device : _registeredDevices) {
|
||||
for (const auto& device : _registeredDevices) {
|
||||
QString deviceName = device.second->_name.split(" (")[0];
|
||||
result << deviceName;
|
||||
}
|
||||
|
@ -218,7 +220,7 @@ Input UserInputMapper::findDeviceInput(const QString& inputName) const {
|
|||
const auto& device = _registeredDevices.at(deviceID);
|
||||
auto deviceInputs = device->getAvailableInputs();
|
||||
|
||||
for (auto input : deviceInputs) {
|
||||
for (const auto& input : deviceInputs) {
|
||||
if (input.second == inputName) {
|
||||
return input.first;
|
||||
}
|
||||
|
@ -321,7 +323,8 @@ QVector<Action> UserInputMapper::getAllActions() const {
|
|||
|
||||
QString UserInputMapper::getActionName(Action action) const {
|
||||
Locker locker(_lock);
|
||||
for (auto actionPair : getActionInputs()) {
|
||||
auto inputs = getActionInputs();
|
||||
for (const auto& actionPair : inputs) {
|
||||
if (actionPair.first.channel == toInt(action)) {
|
||||
return actionPair.second;
|
||||
}
|
||||
|
@ -331,18 +334,20 @@ QString UserInputMapper::getActionName(Action action) const {
|
|||
|
||||
QString UserInputMapper::getStandardPoseName(uint16_t pose) {
|
||||
Locker locker(_lock);
|
||||
for (auto posePair : getStandardInputs()) {
|
||||
auto inputs = getStandardInputs();
|
||||
for (const auto& posePair : inputs) {
|
||||
if (posePair.first.channel == pose && posePair.first.getType() == ChannelType::POSE) {
|
||||
return posePair.second;
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
QVector<QString> UserInputMapper::getActionNames() const {
|
||||
Locker locker(_lock);
|
||||
QVector<QString> result;
|
||||
for (auto actionPair : getActionInputs()) {
|
||||
auto inputs = getActionInputs();
|
||||
for (const auto& actionPair : inputs) {
|
||||
result << actionPair.second;
|
||||
}
|
||||
return result;
|
||||
|
@ -357,7 +362,7 @@ Pose UserInputMapper::getPoseState(Action action) const {
|
|||
bool UserInputMapper::triggerHapticPulse(float strength, float duration, controller::Hand hand) {
|
||||
Locker locker(_lock);
|
||||
bool toReturn = false;
|
||||
for (auto device : _registeredDevices) {
|
||||
for (const auto& device : _registeredDevices) {
|
||||
toReturn = toReturn || device.second->triggerHapticPulse(strength, duration, hand);
|
||||
}
|
||||
return toReturn;
|
||||
|
@ -469,7 +474,7 @@ void UserInputMapper::runMappings() {
|
|||
if (debugRoutes) {
|
||||
qCDebug(controllers) << "Beginning mapping frame";
|
||||
}
|
||||
for (auto endpointEntry : this->_endpointsByInput) {
|
||||
for (const auto& endpointEntry : _endpointsByInput) {
|
||||
endpointEntry.second->reset();
|
||||
}
|
||||
|
||||
|
@ -542,9 +547,9 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) {
|
|||
}
|
||||
|
||||
|
||||
// Most endpoints can only be read once (though a given mapping can route them to
|
||||
// Most endpoints can only be read once (though a given mapping can route them to
|
||||
// multiple places). Consider... If the default is to wire the A button to JUMP
|
||||
// and someone else wires it to CONTEXT_MENU, I don't want both to occur when
|
||||
// and someone else wires it to CONTEXT_MENU, I don't want both to occur when
|
||||
// I press the button. The exception is if I'm wiring a control back to itself
|
||||
// in order to adjust my interface, like inverting the Y axis on an analog stick
|
||||
if (!route->peek && !source->readable()) {
|
||||
|
@ -897,7 +902,8 @@ Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value)
|
|||
if (value.isArray()) {
|
||||
// Support "when" : [ "GamePad.RB", "GamePad.LB" ]
|
||||
Conditional::List children;
|
||||
for (auto arrayItem : value.toArray()) {
|
||||
auto array = value.toArray();
|
||||
for (const auto& arrayItem : array) {
|
||||
Conditional::Pointer childConditional = parseConditional(arrayItem);
|
||||
if (!childConditional) {
|
||||
return Conditional::Pointer();
|
||||
|
@ -908,7 +914,7 @@ Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value)
|
|||
} else if (value.isString()) {
|
||||
// Support "when" : "GamePad.RB"
|
||||
auto conditionalToken = value.toString();
|
||||
|
||||
|
||||
// Detect for modifier case (Not...)
|
||||
QString conditionalModifier;
|
||||
const QString JSON_CONDITIONAL_MODIFIER_NOT("!");
|
||||
|
@ -943,12 +949,12 @@ Filter::Pointer UserInputMapper::parseFilter(const QJsonValue& value) {
|
|||
result = Filter::getFactory().create(value.toString());
|
||||
} else if (value.isObject()) {
|
||||
result = Filter::parse(value.toObject());
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
qWarning() << "Invalid filter definition " << value;
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -960,7 +966,7 @@ Filter::List UserInputMapper::parseFilters(const QJsonValue& value) {
|
|||
if (value.isArray()) {
|
||||
Filter::List result;
|
||||
auto filtersArray = value.toArray();
|
||||
for (auto filterValue : filtersArray) {
|
||||
for (const auto& filterValue : filtersArray) {
|
||||
Filter::Pointer filter = parseFilter(filterValue);
|
||||
if (!filter) {
|
||||
return Filter::List();
|
||||
|
@ -968,7 +974,7 @@ Filter::List UserInputMapper::parseFilters(const QJsonValue& value) {
|
|||
result.push_back(filter);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Filter::Pointer filter = parseFilter(value);
|
||||
if (!filter) {
|
||||
|
@ -980,7 +986,8 @@ Filter::List UserInputMapper::parseFilters(const QJsonValue& value) {
|
|||
Endpoint::Pointer UserInputMapper::parseDestination(const QJsonValue& value) {
|
||||
if (value.isArray()) {
|
||||
ArrayEndpoint::Pointer result = std::make_shared<ArrayEndpoint>();
|
||||
for (auto arrayItem : value.toArray()) {
|
||||
auto array = value.toArray();
|
||||
for (const auto& arrayItem : array) {
|
||||
Endpoint::Pointer destination = parseEndpoint(arrayItem);
|
||||
if (!destination) {
|
||||
return Endpoint::Pointer();
|
||||
|
@ -988,14 +995,14 @@ Endpoint::Pointer UserInputMapper::parseDestination(const QJsonValue& value) {
|
|||
result->_children.push_back(destination);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return parseEndpoint(value);
|
||||
}
|
||||
|
||||
Endpoint::Pointer UserInputMapper::parseAxis(const QJsonValue& value) {
|
||||
if (value.isObject()) {
|
||||
auto object = value.toObject();
|
||||
auto object = value.toObject();
|
||||
if (object.contains("makeAxis")) {
|
||||
auto axisValue = object.value("makeAxis");
|
||||
if (axisValue.isArray()) {
|
||||
|
@ -1017,7 +1024,8 @@ Endpoint::Pointer UserInputMapper::parseAxis(const QJsonValue& value) {
|
|||
Endpoint::Pointer UserInputMapper::parseAny(const QJsonValue& value) {
|
||||
if (value.isArray()) {
|
||||
Endpoint::List children;
|
||||
for (auto arrayItem : value.toArray()) {
|
||||
auto array = value.toArray();
|
||||
for (const auto& arrayItem : array) {
|
||||
Endpoint::Pointer destination = parseEndpoint(arrayItem);
|
||||
if (!destination) {
|
||||
return Endpoint::Pointer();
|
||||
|
@ -1162,7 +1170,7 @@ Mapping::Pointer UserInputMapper::parseMapping(const QString& json) {
|
|||
|
||||
template <typename T>
|
||||
bool hasDebuggableRoute(const T& routes) {
|
||||
for (auto route : routes) {
|
||||
for (const auto& route : routes) {
|
||||
if (route->debug) {
|
||||
return true;
|
||||
}
|
||||
|
@ -1174,7 +1182,7 @@ bool hasDebuggableRoute(const T& routes) {
|
|||
void UserInputMapper::enableMapping(const Mapping::Pointer& mapping) {
|
||||
Locker locker(_lock);
|
||||
// New routes for a device get injected IN FRONT of existing routes. Routes
|
||||
// are processed in order so this ensures that the standard -> action processing
|
||||
// are processed in order so this ensures that the standard -> action processing
|
||||
// takes place after all of the hardware -> standard or hardware -> action processing
|
||||
// because standard -> action is the first set of routes added.
|
||||
Route::List standardRoutes = mapping->routes;
|
||||
|
|
|
@ -16,11 +16,14 @@
|
|||
using namespace controller;
|
||||
|
||||
void ActionEndpoint::apply(float newValue, const Pointer& source) {
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
QString actionName = userInputMapper->getActionName(Action(_input.getChannel()));
|
||||
if(inputRecorder->isPlayingback()) {
|
||||
newValue = inputRecorder->getActionState(actionName);
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
QString actionName;
|
||||
if (inputRecorder->isPlayingback() || inputRecorder->isRecording()) {
|
||||
actionName = userInputMapper->getActionName(Action(_input.getChannel()));
|
||||
if (inputRecorder->isPlayingback()) {
|
||||
newValue = inputRecorder->getActionState(actionName);
|
||||
}
|
||||
}
|
||||
|
||||
_currentValue += newValue;
|
||||
|
@ -32,10 +35,12 @@ void ActionEndpoint::apply(float newValue, const Pointer& source) {
|
|||
|
||||
void ActionEndpoint::apply(const Pose& value, const Pointer& source) {
|
||||
_currentPose = value;
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
QString actionName = userInputMapper->getActionName(Action(_input.getChannel()));
|
||||
inputRecorder->setActionState(actionName, _currentPose);
|
||||
InputRecorder* inputRecorder = InputRecorder::getInstance();
|
||||
if (inputRecorder->isRecording()) {
|
||||
QString actionName = userInputMapper->getActionName(Action(_input.getChannel()));
|
||||
inputRecorder->setActionState(actionName, _currentPose);
|
||||
}
|
||||
|
||||
if (!_currentPose.isValid()) {
|
||||
return;
|
||||
|
|
|
@ -530,7 +530,11 @@ void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::Textur
|
|||
batch.setStateScissorRect(scissor);
|
||||
batch.setViewportTransform(viewport);
|
||||
batch.setResourceTexture(0, texture);
|
||||
#ifndef USE_GLES
|
||||
batch.setPipeline(_presentPipeline);
|
||||
#else
|
||||
batch.setPipeline(_simplePipeline);
|
||||
#endif
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
if (copyFbo) {
|
||||
gpu::Vec4i copyFboRect(0, 0, copyFbo->getWidth(), copyFbo->getHeight());
|
||||
|
|
|
@ -55,7 +55,7 @@ HTTPConnection::~HTTPConnection() {
|
|||
|
||||
QHash<QString, QString> HTTPConnection::parseUrlEncodedForm() {
|
||||
// make sure we have the correct MIME type
|
||||
QList<QByteArray> elements = _requestHeaders.value("Content-Type").split(';');
|
||||
QList<QByteArray> elements = requestHeader("Content-Type").split(';');
|
||||
|
||||
QString contentType = elements.at(0).trimmed();
|
||||
if (contentType != "application/x-www-form-urlencoded") {
|
||||
|
@ -75,7 +75,7 @@ QHash<QString, QString> HTTPConnection::parseUrlEncodedForm() {
|
|||
|
||||
QList<FormData> HTTPConnection::parseFormData() const {
|
||||
// make sure we have the correct MIME type
|
||||
QList<QByteArray> elements = _requestHeaders.value("Content-Type").split(';');
|
||||
QList<QByteArray> elements = requestHeader("Content-Type").split(';');
|
||||
|
||||
QString contentType = elements.at(0).trimmed();
|
||||
|
||||
|
@ -251,7 +251,7 @@ void HTTPConnection::readHeaders() {
|
|||
if (trimmed.isEmpty()) {
|
||||
_socket->disconnect(this, SLOT(readHeaders()));
|
||||
|
||||
QByteArray clength = _requestHeaders.value("Content-Length");
|
||||
QByteArray clength = requestHeader("Content-Length");
|
||||
if (clength.isEmpty()) {
|
||||
_parentManager->handleHTTPRequest(this, _requestUrl);
|
||||
|
||||
|
@ -275,7 +275,7 @@ void HTTPConnection::readHeaders() {
|
|||
respond("400 Bad Request", "The header was malformed.");
|
||||
return;
|
||||
}
|
||||
_lastRequestHeader = trimmed.left(idx);
|
||||
_lastRequestHeader = trimmed.left(idx).toLower();
|
||||
QByteArray& value = _requestHeaders[_lastRequestHeader];
|
||||
if (!value.isEmpty()) {
|
||||
value.append(", ");
|
||||
|
|
|
@ -72,8 +72,8 @@ public:
|
|||
/// Returns a reference to the request URL.
|
||||
const QUrl& requestUrl () const { return _requestUrl; }
|
||||
|
||||
/// Returns a reference to the request headers.
|
||||
const Headers& requestHeaders () const { return _requestHeaders; }
|
||||
/// Returns a copy of the request header value. If it does not exist, it will return a default constructed QByteArray.
|
||||
QByteArray requestHeader(const QString& key) const { return _requestHeaders.value(key.toLower().toLocal8Bit()); }
|
||||
|
||||
/// Returns a reference to the request content.
|
||||
const QByteArray& requestContent () const { return _requestContent; }
|
||||
|
|
|
@ -91,6 +91,11 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
|
|||
return;
|
||||
}
|
||||
|
||||
if (entityTree && entityTree->isServerlessMode()) {
|
||||
// if we are in a serverless domain, don't send edit packets
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0);
|
||||
|
||||
if (type == PacketType::EntityAdd) {
|
||||
|
|
|
@ -963,7 +963,11 @@ void EntityItem::setHref(QString value) {
|
|||
|
||||
// If the string has something and doesn't start with with "hifi://" it shouldn't be set
|
||||
// We allow the string to be empty, because that's the initial state of this property
|
||||
if ( !(value.toLower().startsWith("hifi://")) && !value.isEmpty()) {
|
||||
if (!value.isEmpty() &&
|
||||
!(value.toLower().startsWith("hifi://")) &&
|
||||
!(value.toLower().startsWith("file://"))
|
||||
// TODO: serverless-domains will eventually support http and https also
|
||||
) {
|
||||
return;
|
||||
}
|
||||
withWriteLock([&] {
|
||||
|
|
|
@ -451,8 +451,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
* @property {Entities.EntityType} type - The entity type. You cannot change the type of an entity after it's created. (Though
|
||||
* its value may switch among <code>"Box"</code>, <code>"Shape"</code>, and <code>"Sphere"</code> depending on changes to
|
||||
* the <code>shape</code> property set for entities of these types.) <em>Read-only.</em>
|
||||
* @property {boolean} clientOnly=false - If <code>true</code> then the entity is an avatar entity, otherwise it is a server
|
||||
* entity. <em>Read-only.</em>
|
||||
* @property {boolean} clientOnly=false - If <code>true</code> then the entity is an avatar entity; otherwise it is a server
|
||||
* entity. An avatar entity follows you to each domain you visit, rendering at the same world coordinates unless it's
|
||||
* parented to your avatar. <em>Value cannot be changed after the entity is created.</em><br />
|
||||
* The value can also be set at entity creation by using the <code>clientOnly</code> parameter in
|
||||
* {@link Entities.addEntity}.
|
||||
* @property {Uuid} owningAvatarID=Uuid.NULL - The session ID of the owning avatar if <code>clientOnly</code> is
|
||||
* <code>true</code>, otherwise {@link Uuid|Uuid.NULL}. <em>Read-only.</em>
|
||||
*
|
||||
|
@ -1413,7 +1416,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ANGULAR_VELOCITY, localAngularVelocity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_DIMENSIONS, localDimensions);
|
||||
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); // Gettable but not settable
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); // Gettable but not settable except at entity creation
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); // Gettable but not settable
|
||||
|
||||
// Rendering info
|
||||
|
@ -2864,6 +2867,9 @@ void EntityItemProperties::markAllChanged() {
|
|||
_ambientLight.markAllChanged();
|
||||
_skybox.markAllChanged();
|
||||
|
||||
_keyLightModeChanged = true;
|
||||
_skyboxModeChanged = true;
|
||||
_ambientLightModeChanged = true;
|
||||
_hazeModeChanged = true;
|
||||
|
||||
_animation.markAllChanged();
|
||||
|
|
|
@ -596,7 +596,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
|
|||
shouldDelete = false;
|
||||
} else {
|
||||
// only delete local entities, server entities will round trip through the server filters
|
||||
if (entity->getClientOnly()) {
|
||||
if (entity->getClientOnly() || _entityTree->isServerlessMode()) {
|
||||
_entityTree->deleteEntity(entityID);
|
||||
}
|
||||
}
|
||||
|
@ -1285,10 +1285,10 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID,
|
|||
}
|
||||
|
||||
doTransmit = actor(simulation, entity);
|
||||
_entityTree->entityChanged(entity);
|
||||
if (doTransmit) {
|
||||
properties.setClientOnly(entity->getClientOnly());
|
||||
properties.setOwningAvatarID(entity->getOwningAvatarID());
|
||||
_entityTree->entityChanged(entity);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -203,9 +203,9 @@ public slots:
|
|||
* Add a new entity with specified properties.
|
||||
* @function Entities.addEntity
|
||||
* @param {Entities.EntityProperties} properties - The properties of the entity to create.
|
||||
* @param {boolean} [clientOnly=false] - If <code>true</code>, the entity is created as an avatar entity, otherwise it
|
||||
* is created on the server. An avatar entity follows you to each domain you visit, rendering at the same world
|
||||
* coordinates unless it's parented to your avatar.
|
||||
* @param {boolean} [clientOnly=false] - If <code>true</code>, or if <code>clientOnly</code> is set <code>true</code> in
|
||||
* the properties, the entity is created as an avatar entity; otherwise it is created on the server. An avatar entity
|
||||
* follows you to each domain you visit, rendering at the same world coordinates unless it's parented to your avatar.
|
||||
* @returns {Uuid} The ID of the entity if successfully created, otherwise {@link Uuid|Uuid.NULL}.
|
||||
* @example <caption>Create a box entity in front of your avatar.</caption>
|
||||
* var entityID = Entities.addEntity({
|
||||
|
|
|
@ -493,7 +493,7 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti
|
|||
|
||||
if (!properties.getClientOnly() && getIsClient() &&
|
||||
!nodeList->getThisNodeCanRez() && !nodeList->getThisNodeCanRezTmp() &&
|
||||
!nodeList->getThisNodeCanRezCertified() && !nodeList->getThisNodeCanRezTmpCertified()) {
|
||||
!nodeList->getThisNodeCanRezCertified() && !nodeList->getThisNodeCanRezTmpCertified() && !_serverlessDomain) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -1509,7 +1509,8 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
}
|
||||
|
||||
if (isAdd && properties.getLocked() && !senderNode->isAllowedEditor()) {
|
||||
// if a node can't change locks, don't allow them to create an already-locked entity
|
||||
// if a node can't change locks, don't allow it to create an already-locked entity -- automatically
|
||||
// clear the locked property and allow the unlocked entity to be created.
|
||||
properties.setLocked(false);
|
||||
bumpTimestamp(properties);
|
||||
}
|
||||
|
@ -2181,23 +2182,25 @@ QVector<EntityItemID> EntityTree::sendEntities(EntityEditPacketSender* packetSen
|
|||
localTree->recurseTreeWithOperator(&moveOperator);
|
||||
}
|
||||
|
||||
// send add-entity packets to the server
|
||||
i = map.begin();
|
||||
while (i != map.end()) {
|
||||
EntityItemID newID = i.value();
|
||||
EntityItemPointer entity = localTree->findEntityByEntityItemID(newID);
|
||||
if (entity) {
|
||||
// queue the packet to send to the server
|
||||
entity->updateQueryAACube();
|
||||
EntityItemProperties properties = entity->getProperties();
|
||||
properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityAdd, localTree, newID, properties);
|
||||
i++;
|
||||
} else {
|
||||
i = map.erase(i);
|
||||
if (!_serverlessDomain) {
|
||||
// send add-entity packets to the server
|
||||
i = map.begin();
|
||||
while (i != map.end()) {
|
||||
EntityItemID newID = i.value();
|
||||
EntityItemPointer entity = localTree->findEntityByEntityItemID(newID);
|
||||
if (entity) {
|
||||
// queue the packet to send to the server
|
||||
entity->updateQueryAACube();
|
||||
EntityItemProperties properties = entity->getProperties();
|
||||
properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityAdd, localTree, newID, properties);
|
||||
i++;
|
||||
} else {
|
||||
i = map.erase(i);
|
||||
}
|
||||
}
|
||||
packetSender->releaseQueuedMessages();
|
||||
}
|
||||
packetSender->releaseQueuedMessages();
|
||||
|
||||
return map.values().toVector();
|
||||
}
|
||||
|
|
|
@ -283,6 +283,9 @@ public:
|
|||
|
||||
void setMyAvatar(std::shared_ptr<AvatarData> myAvatar) { _myAvatar = myAvatar; }
|
||||
|
||||
void setIsServerlessMode(bool value) { _serverlessDomain = value; }
|
||||
bool isServerlessMode() const { return _serverlessDomain; }
|
||||
|
||||
static void setAddMaterialToEntityOperator(std::function<bool(const QUuid&, graphics::MaterialLayer, const std::string&)> addMaterialToEntityOperator) { _addMaterialToEntityOperator = addMaterialToEntityOperator; }
|
||||
static void setRemoveMaterialFromEntityOperator(std::function<bool(const QUuid&, graphics::MaterialPointer, const std::string&)> removeMaterialFromEntityOperator) { _removeMaterialFromEntityOperator = removeMaterialFromEntityOperator; }
|
||||
static bool addMaterialToEntity(const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName);
|
||||
|
@ -325,7 +328,7 @@ protected:
|
|||
void notifyNewlyCreatedEntity(const EntityItem& newEntity, const SharedNodePointer& senderNode);
|
||||
|
||||
bool isScriptInWhitelist(const QString& scriptURL);
|
||||
|
||||
|
||||
QReadWriteLock _newlyCreatedHooksLock;
|
||||
QVector<NewlyCreatedEntityHook*> _newlyCreatedHooks;
|
||||
|
||||
|
@ -412,6 +415,8 @@ private:
|
|||
static std::function<bool(const QUuid&, graphics::MaterialPointer, const std::string&)> _removeMaterialFromAvatarOperator;
|
||||
static std::function<bool(const QUuid&, graphics::MaterialLayer, const std::string&)> _addMaterialToOverlayOperator;
|
||||
static std::function<bool(const QUuid&, graphics::MaterialPointer, const std::string&)> _removeMaterialFromOverlayOperator;
|
||||
|
||||
bool _serverlessDomain { false };
|
||||
};
|
||||
|
||||
#endif // hifi_EntityTree_h
|
||||
|
|
|
@ -38,11 +38,13 @@
|
|||
#include <NumericalConstants.h>
|
||||
#include <shared/NsightHelpers.h>
|
||||
#include <shared/FileUtils.h>
|
||||
#include <PathUtils.h>
|
||||
#include <Finally.h>
|
||||
#include <Profile.h>
|
||||
|
||||
#include "NetworkLogging.h"
|
||||
#include "ModelNetworkingLogging.h"
|
||||
#include "NetworkingConstants.h"
|
||||
#include <Trace.h>
|
||||
#include <StatTracker.h>
|
||||
|
||||
|
@ -467,7 +469,7 @@ void NetworkTexture::makeLocalRequest() {
|
|||
const QString scheme = _url.scheme();
|
||||
QString path;
|
||||
if (scheme == URL_SCHEME_FILE) {
|
||||
path = _url.toLocalFile();
|
||||
path = PathUtils::expandToLocalDataAbsolutePath(_url).toLocalFile();
|
||||
} else {
|
||||
path = ":" + _url.path();
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <NumericalConstants.h>
|
||||
#include <SettingHandle.h>
|
||||
#include <UUID.h>
|
||||
#include <PathUtils.h>
|
||||
|
||||
#include "AddressManager.h"
|
||||
#include "NodeList.h"
|
||||
|
@ -29,38 +30,20 @@
|
|||
#include "UserActivityLogger.h"
|
||||
#include "udt/PacketHeaders.h"
|
||||
|
||||
#if USE_STABLE_GLOBAL_SERVICES
|
||||
const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome/hello";
|
||||
#else
|
||||
const QString DEFAULT_HIFI_ADDRESS = "hifi://dev-welcome/hello";
|
||||
#endif
|
||||
|
||||
const QString DEFAULT_HIFI_ADDRESS = "file:///~/serverless/tutorial.json";
|
||||
const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager";
|
||||
const QString SETTINGS_CURRENT_ADDRESS_KEY = "address";
|
||||
|
||||
Setting::Handle<QUrl> currentAddressHandle(QStringList() << ADDRESS_MANAGER_SETTINGS_GROUP << "address", DEFAULT_HIFI_ADDRESS);
|
||||
|
||||
AddressManager::AddressManager() :
|
||||
_port(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool AddressManager::isConnected() {
|
||||
return DependencyManager::get<NodeList>()->getDomainHandler().isConnected();
|
||||
}
|
||||
|
||||
QUrl AddressManager::currentAddress(bool domainOnly) const {
|
||||
QUrl hifiURL;
|
||||
QUrl hifiURL = _domainURL;
|
||||
|
||||
hifiURL.setScheme(HIFI_URL_SCHEME);
|
||||
hifiURL.setHost(_host);
|
||||
|
||||
if (_port != 0 && _port != DEFAULT_DOMAIN_SERVER_PORT) {
|
||||
hifiURL.setPort(_port);
|
||||
}
|
||||
|
||||
if (!domainOnly) {
|
||||
if (!domainOnly && hifiURL.scheme() == URL_SCHEME_HIFI) {
|
||||
hifiURL.setPath(currentPath());
|
||||
}
|
||||
|
||||
|
@ -69,7 +52,9 @@ QUrl AddressManager::currentAddress(bool domainOnly) const {
|
|||
|
||||
QUrl AddressManager::currentFacingAddress() const {
|
||||
auto hifiURL = currentAddress();
|
||||
hifiURL.setPath(currentFacingPath());
|
||||
if (hifiURL.scheme() == URL_SCHEME_HIFI) {
|
||||
hifiURL.setPath(currentFacingPath());
|
||||
}
|
||||
|
||||
return hifiURL;
|
||||
}
|
||||
|
@ -79,7 +64,7 @@ QUrl AddressManager::currentShareableAddress(bool domainOnly) const {
|
|||
// if we have a shareable place name use that instead of whatever the current host is
|
||||
QUrl hifiURL;
|
||||
|
||||
hifiURL.setScheme(HIFI_URL_SCHEME);
|
||||
hifiURL.setScheme(URL_SCHEME_HIFI);
|
||||
hifiURL.setHost(_shareablePlaceName);
|
||||
|
||||
if (!domainOnly) {
|
||||
|
@ -94,7 +79,9 @@ QUrl AddressManager::currentShareableAddress(bool domainOnly) const {
|
|||
|
||||
QUrl AddressManager::currentFacingShareableAddress() const {
|
||||
auto hifiURL = currentShareableAddress();
|
||||
hifiURL.setPath(currentFacingPath());
|
||||
if (hifiURL.scheme() == URL_SCHEME_HIFI) {
|
||||
hifiURL.setPath(currentFacingPath());
|
||||
}
|
||||
|
||||
return hifiURL;
|
||||
}
|
||||
|
@ -139,11 +126,16 @@ void AddressManager::goForward() {
|
|||
|
||||
void AddressManager::storeCurrentAddress() {
|
||||
auto url = currentAddress();
|
||||
|
||||
if (!url.host().isEmpty()) {
|
||||
|
||||
if (url.scheme() == URL_SCHEME_FILE ||
|
||||
(url.scheme() == URL_SCHEME_HIFI && !url.host().isEmpty())) {
|
||||
// TODO -- once Octree::readFromURL no-longer takes over the main event-loop, serverless-domain urls can
|
||||
// be loaded over http(s)
|
||||
// url.scheme() == URL_SCHEME_HTTP ||
|
||||
// url.scheme() == URL_SCHEME_HTTPS ||
|
||||
currentAddressHandle.set(url);
|
||||
} else {
|
||||
qCWarning(networking) << "Ignoring attempt to save current address with an empty host" << url;
|
||||
qCWarning(networking) << "Ignoring attempt to save current address with an invalid url:" << url;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,7 +201,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
|||
static QString URL_TYPE_DOMAIN_ID = "domain_id";
|
||||
static QString URL_TYPE_PLACE = "place";
|
||||
static QString URL_TYPE_NETWORK_ADDRESS = "network_address";
|
||||
if (lookupUrl.scheme() == HIFI_URL_SCHEME) {
|
||||
if (lookupUrl.scheme() == URL_SCHEME_HIFI) {
|
||||
|
||||
qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString();
|
||||
|
||||
|
@ -289,12 +281,36 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
|||
handlePath(lookupUrl.path(), trigger, true);
|
||||
emit lookupResultsFinished();
|
||||
|
||||
return true;
|
||||
} else if (lookupUrl.scheme() == URL_SCHEME_FILE) {
|
||||
// TODO -- once Octree::readFromURL no-longer takes over the main event-loop, serverless-domain urls can
|
||||
// be loaded over http(s)
|
||||
// lookupUrl.scheme() == URL_SCHEME_HTTP ||
|
||||
// lookupUrl.scheme() == URL_SCHEME_HTTPS ||
|
||||
_previousLookup.clear();
|
||||
QUrl domainURL = PathUtils::expandToLocalDataAbsolutePath(lookupUrl);
|
||||
setDomainInfo(domainURL, trigger);
|
||||
emit lookupResultsFinished();
|
||||
handlePath(DOMAIN_SPAWNING_POINT, LookupTrigger::Internal, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isPossiblePlaceName(QString possiblePlaceName) {
|
||||
bool result { false };
|
||||
int length = possiblePlaceName.length();
|
||||
static const int MINIMUM_PLACENAME_LENGTH = 1;
|
||||
static const int MAXIMUM_PLACENAME_LENGTH = 64;
|
||||
if (possiblePlaceName.toLower() != "localhost" &&
|
||||
length >= MINIMUM_PLACENAME_LENGTH && length <= MAXIMUM_PLACENAME_LENGTH) {
|
||||
const QRegExp PLACE_NAME_REGEX = QRegExp("^[0-9A-Za-z](([0-9A-Za-z]|-(?!-))*[^\\W_]$|$)");
|
||||
result = PLACE_NAME_REGEX.indexIn(possiblePlaceName) == 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void AddressManager::handleLookupString(const QString& lookupString, bool fromSuggestions) {
|
||||
if (!lookupString.isEmpty()) {
|
||||
// make this a valid hifi URL and handle it off to handleUrl
|
||||
|
@ -302,12 +318,16 @@ void AddressManager::handleLookupString(const QString& lookupString, bool fromSu
|
|||
QUrl lookupURL;
|
||||
|
||||
if (!lookupString.startsWith('/')) {
|
||||
const QRegExp HIFI_SCHEME_REGEX = QRegExp(HIFI_URL_SCHEME + ":\\/{1,2}", Qt::CaseInsensitive);
|
||||
// sometimes we need to handle lookupStrings like hifi:/somewhere
|
||||
const QRegExp HIFI_SCHEME_REGEX = QRegExp(URL_SCHEME_HIFI + ":\\/{1,2}", Qt::CaseInsensitive);
|
||||
sanitizedString = sanitizedString.remove(HIFI_SCHEME_REGEX);
|
||||
|
||||
lookupURL = QUrl(HIFI_URL_SCHEME + "://" + sanitizedString);
|
||||
lookupURL = QUrl(sanitizedString);
|
||||
if (lookupURL.scheme().isEmpty()) {
|
||||
lookupURL = QUrl("hifi://" + sanitizedString);
|
||||
}
|
||||
} else {
|
||||
lookupURL = QUrl(lookupString);
|
||||
lookupURL = QUrl(sanitizedString);
|
||||
}
|
||||
|
||||
handleUrl(lookupURL, fromSuggestions ? Suggestions : UserInput);
|
||||
|
@ -385,7 +405,11 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const
|
|||
|
||||
qCDebug(networking) << "Possible domain change required to connect to" << domainHostname
|
||||
<< "on" << domainPort;
|
||||
emit possibleDomainChangeRequired(domainHostname, domainPort, domainID);
|
||||
QUrl domainURL;
|
||||
domainURL.setScheme(URL_SCHEME_HIFI);
|
||||
domainURL.setHost(domainHostname);
|
||||
domainURL.setPort(domainPort);
|
||||
emit possibleDomainChangeRequired(domainURL, domainID);
|
||||
} else {
|
||||
QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString();
|
||||
|
||||
|
@ -422,15 +446,10 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const
|
|||
if (setHost(placeName, trigger)) {
|
||||
trigger = LookupTrigger::Internal;
|
||||
}
|
||||
|
||||
_placeName = placeName;
|
||||
} else {
|
||||
if (setHost(domainIDString, trigger)) {
|
||||
trigger = LookupTrigger::Internal;
|
||||
}
|
||||
|
||||
// this isn't a place, so clear the place name
|
||||
_placeName.clear();
|
||||
}
|
||||
|
||||
// check if we had a path to override the path returned
|
||||
|
@ -551,13 +570,17 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString, LookupTri
|
|||
if (ipAddressRegex.indexIn(lookupString) != -1) {
|
||||
QString domainIPString = ipAddressRegex.cap(1);
|
||||
|
||||
qint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
|
||||
quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
|
||||
if (!ipAddressRegex.cap(2).isEmpty()) {
|
||||
domainPort = (qint16) ipAddressRegex.cap(2).toInt();
|
||||
domainPort = (quint16) ipAddressRegex.cap(2).toInt();
|
||||
}
|
||||
|
||||
emit lookupResultsFinished();
|
||||
hostChanged = setDomainInfo(domainIPString, domainPort, trigger);
|
||||
QUrl domainURL;
|
||||
domainURL.setScheme(URL_SCHEME_HIFI);
|
||||
domainURL.setHost(domainIPString);
|
||||
domainURL.setPort(domainPort);
|
||||
hostChanged = setDomainInfo(domainURL, trigger);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -570,11 +593,15 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString, LookupTri
|
|||
quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
|
||||
|
||||
if (!hostnameRegex.cap(2).isEmpty()) {
|
||||
domainPort = (qint16)hostnameRegex.cap(2).toInt();
|
||||
domainPort = (quint16)hostnameRegex.cap(2).toInt();
|
||||
}
|
||||
|
||||
emit lookupResultsFinished();
|
||||
hostChanged = setDomainInfo(domainHostname, domainPort, trigger);
|
||||
QUrl domainURL;
|
||||
domainURL.setScheme(URL_SCHEME_HIFI);
|
||||
domainURL.setHost(domainHostname);
|
||||
domainURL.setPort(domainPort);
|
||||
hostChanged = setDomainInfo(domainURL, trigger);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -643,7 +670,7 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should
|
|||
addCurrentAddressToHistory(trigger);
|
||||
}
|
||||
|
||||
if (!isNaN(newPosition.x) && !isNaN(newPosition.y) && !isNaN(newPosition.z)) {
|
||||
if (!isNaN(newPosition)) {
|
||||
glm::quat newOrientation;
|
||||
|
||||
QRegExp orientationRegex(QUAT_REGEX_STRING);
|
||||
|
@ -663,11 +690,11 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should
|
|||
&& !isNaN(newOrientation.w)) {
|
||||
orientationChanged = true;
|
||||
} else {
|
||||
qCDebug(networking) << "Orientation parsed from lookup string is invalid. Will not use for location change.";
|
||||
qCDebug(networking) << "Orientation parsed from lookup string is invalid. Won't use for location change.";
|
||||
}
|
||||
}
|
||||
|
||||
emit locationChangeRequired(newPosition, orientationChanged,
|
||||
|
||||
emit locationChangeRequired(newPosition, orientationChanged,
|
||||
trigger == LookupTrigger::VisitUserFromPAL ? cancelOutRollAndPitch(newOrientation): newOrientation,
|
||||
shouldFace
|
||||
);
|
||||
|
@ -698,18 +725,20 @@ bool AddressManager::handleUsername(const QString& lookupString) {
|
|||
}
|
||||
|
||||
bool AddressManager::setHost(const QString& host, LookupTrigger trigger, quint16 port) {
|
||||
if (host != _host || port != _port) {
|
||||
|
||||
if (host != _domainURL.host() || port != _domainURL.port()) {
|
||||
addCurrentAddressToHistory(trigger);
|
||||
|
||||
_port = port;
|
||||
bool emitHostChanged = host != _domainURL.host();
|
||||
_domainURL = QUrl();
|
||||
_domainURL.setScheme(URL_SCHEME_HIFI);
|
||||
_domainURL.setHost(host);
|
||||
_domainURL.setPort(port);
|
||||
|
||||
// any host change should clear the shareable place name
|
||||
_shareablePlaceName.clear();
|
||||
|
||||
if (host != _host) {
|
||||
_host = host;
|
||||
emit hostChanged(_host);
|
||||
if (emitHostChanged) {
|
||||
emit hostChanged(host);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -718,20 +747,43 @@ bool AddressManager::setHost(const QString& host, LookupTrigger trigger, quint16
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, LookupTrigger trigger) {
|
||||
bool hostChanged = setHost(hostname, trigger, port);
|
||||
QString AddressManager::getHost() const {
|
||||
if (isPossiblePlaceName(_domainURL.host())) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
return _domainURL.host();
|
||||
}
|
||||
|
||||
bool AddressManager::setDomainInfo(const QUrl& domainURL, LookupTrigger trigger) {
|
||||
const QString hostname = domainURL.host();
|
||||
quint16 port = domainURL.port();
|
||||
bool emitHostChanged { false };
|
||||
|
||||
if (domainURL != _domainURL) {
|
||||
addCurrentAddressToHistory(trigger);
|
||||
emitHostChanged = true;
|
||||
}
|
||||
|
||||
_domainURL = domainURL;
|
||||
|
||||
// clear any current place information
|
||||
_rootPlaceID = QUuid();
|
||||
_placeName.clear();
|
||||
|
||||
qCDebug(networking) << "Possible domain change required to connect to domain at" << hostname << "on" << port;
|
||||
if (_domainURL.scheme() == URL_SCHEME_HIFI) {
|
||||
qCDebug(networking) << "Possible domain change required to connect to domain at" << hostname << "on" << port;
|
||||
} else {
|
||||
qCDebug(networking) << "Possible domain change required to serverless domain: " << domainURL.toString();
|
||||
}
|
||||
|
||||
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress);
|
||||
|
||||
emit possibleDomainChangeRequired(hostname, port, QUuid());
|
||||
if (emitHostChanged) {
|
||||
emit hostChanged(domainURL.host());
|
||||
}
|
||||
emit possibleDomainChangeRequired(_domainURL, QUuid());
|
||||
|
||||
return hostChanged;
|
||||
return emitHostChanged;
|
||||
}
|
||||
|
||||
void AddressManager::goToUser(const QString& username, bool shouldMatchOrientation) {
|
||||
|
@ -820,7 +872,7 @@ void AddressManager::lookupShareableNameForDomainID(const QUuid& domainID) {
|
|||
// then use that for Steam join/invite or copiable address
|
||||
|
||||
// it only makes sense to lookup a shareable default name if we don't have a place name
|
||||
if (_placeName.isEmpty()) {
|
||||
if (getPlaceName().isEmpty()) {
|
||||
JSONCallbackParameters callbackParams;
|
||||
|
||||
// no error callback handling
|
||||
|
@ -872,3 +924,12 @@ void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) {
|
|||
}
|
||||
}
|
||||
|
||||
QString AddressManager::getPlaceName() const {
|
||||
if (!_shareablePlaceName.isEmpty()) {
|
||||
return _shareablePlaceName;
|
||||
}
|
||||
if (isPossiblePlaceName(_domainURL.host())) {
|
||||
return _domainURL.host();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
|
|
@ -22,8 +22,6 @@
|
|||
|
||||
#include "AccountManager.h"
|
||||
|
||||
const QString HIFI_URL_SCHEME = "hifi";
|
||||
|
||||
extern const QString DEFAULT_HIFI_ADDRESS;
|
||||
|
||||
const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost";
|
||||
|
@ -147,7 +145,7 @@ public:
|
|||
};
|
||||
|
||||
bool isConnected();
|
||||
const QString& getProtocol() { return HIFI_URL_SCHEME; };
|
||||
const QString& getProtocol() { return URL_SCHEME_HIFI; };
|
||||
|
||||
QUrl currentAddress(bool domainOnly = false) const;
|
||||
QUrl currentFacingAddress() const;
|
||||
|
@ -157,10 +155,10 @@ public:
|
|||
QString currentFacingPath() const;
|
||||
|
||||
const QUuid& getRootPlaceID() const { return _rootPlaceID; }
|
||||
const QString& getPlaceName() const { return _shareablePlaceName.isEmpty() ? _placeName : _shareablePlaceName; }
|
||||
QString getPlaceName() const;
|
||||
QString getDomainID() const;
|
||||
|
||||
const QString& getHost() const { return _host; }
|
||||
QString getHost() const;
|
||||
|
||||
void setPositionGetter(PositionGetter positionGetter) { _positionGetter = positionGetter; }
|
||||
void setOrientationGetter(OrientationGetter orientationGetter) { _orientationGetter = orientationGetter; }
|
||||
|
@ -170,6 +168,8 @@ public:
|
|||
const QStack<QUrl>& getBackStack() const { return _backStack; }
|
||||
const QStack<QUrl>& getForwardStack() const { return _forwardStack; }
|
||||
|
||||
QUrl getDomainURL() { return _domainURL; }
|
||||
|
||||
public slots:
|
||||
/**jsdoc
|
||||
* Go to a specified metaverse address.
|
||||
|
@ -302,13 +302,12 @@ signals:
|
|||
/**jsdoc
|
||||
* Triggered when a request is made to go to an IP address.
|
||||
* @function location.possibleDomainChangeRequired
|
||||
* @param {string} hostName - The name of the domain to go do.
|
||||
* @param {number} port - The integer number of the network port to connect to.
|
||||
* @param {Url} domainURL - URL for domain
|
||||
* @param {Uuid} domainID - The UUID of the domain to go to.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
// No example because this function isn't typically used in scripts.
|
||||
void possibleDomainChangeRequired(const QString& newHostname, quint16 newPort, const QUuid& domainID);
|
||||
void possibleDomainChangeRequired(QUrl domainURL, QUuid domainID);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when a request is made to go to a named domain or user.
|
||||
|
@ -360,7 +359,7 @@ signals:
|
|||
* location.pathChangeRequired.connect(onPathChangeRequired);
|
||||
*/
|
||||
void pathChangeRequired(const QString& newPath);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when you navigate to a new domain.
|
||||
* @function location.hostChanged
|
||||
|
@ -392,7 +391,7 @@ signals:
|
|||
void goBackPossible(bool isPossible);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when there's a change in whether or not there's a forward location that can be navigated to using
|
||||
* Triggered when there's a change in whether or not there's a forward location that can be navigated to using
|
||||
* {@link location.goForward|goForward}. (Reflects changes in the state of the "Goto" dialog's forward arrow.)
|
||||
* @function location.goForwardPossible
|
||||
* @param {boolean} isPossible - <code>true</code> if there's a forward location to navigate to, otherwise
|
||||
|
@ -407,8 +406,6 @@ signals:
|
|||
*/
|
||||
void goForwardPossible(bool isPossible);
|
||||
|
||||
protected:
|
||||
AddressManager();
|
||||
private slots:
|
||||
void handleAPIResponse(QNetworkReply& requestReply);
|
||||
void handleAPIError(QNetworkReply& errorReply);
|
||||
|
@ -420,7 +417,7 @@ private:
|
|||
|
||||
// Set host and port, and return `true` if it was changed.
|
||||
bool setHost(const QString& host, LookupTrigger trigger, quint16 port = 0);
|
||||
bool setDomainInfo(const QString& hostname, quint16 port, LookupTrigger trigger);
|
||||
bool setDomainInfo(const QUrl& domainURL, LookupTrigger trigger);
|
||||
|
||||
const JSONCallbackParameters& apiCallbackParameters();
|
||||
|
||||
|
@ -438,9 +435,8 @@ private:
|
|||
|
||||
void addCurrentAddressToHistory(LookupTrigger trigger);
|
||||
|
||||
QString _host;
|
||||
quint16 _port;
|
||||
QString _placeName;
|
||||
QUrl _domainURL;
|
||||
|
||||
QUuid _rootPlaceID;
|
||||
PositionGetter _positionGetter;
|
||||
OrientationGetter _orientationGetter;
|
||||
|
@ -452,7 +448,7 @@ private:
|
|||
quint64 _lastBackPush = 0;
|
||||
|
||||
QString _newHostLookupPath;
|
||||
|
||||
|
||||
QUrl _previousLookup;
|
||||
};
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include "NetworkAccessManager.h"
|
||||
#include "NetworkLogging.h"
|
||||
#include "NetworkingConstants.h"
|
||||
|
||||
#include "ResourceManager.h"
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ DomainHandler::DomainHandler(QObject* parent) :
|
|||
|
||||
// if we get a socket that make sure our NetworkPeer ping timer stops
|
||||
connect(this, &DomainHandler::completedSocketDiscovery, &_icePeer, &NetworkPeer::stopPingTimer);
|
||||
|
||||
|
||||
// setup a timeout for failure on settings requests
|
||||
static const int DOMAIN_SETTINGS_TIMEOUT_MS = 5000;
|
||||
_settingsTimer.setInterval(DOMAIN_SETTINGS_TIMEOUT_MS); // 5s, Qt::CoarseTimer acceptable
|
||||
|
@ -60,11 +60,11 @@ void DomainHandler::disconnect() {
|
|||
if (_isConnected) {
|
||||
sendDisconnectPacket();
|
||||
}
|
||||
|
||||
|
||||
// clear member variables that hold the connection state to a domain
|
||||
_uuid = QUuid();
|
||||
_connectionToken = QUuid();
|
||||
|
||||
|
||||
_icePeer.reset();
|
||||
|
||||
if (requiresICE()) {
|
||||
|
@ -78,10 +78,10 @@ void DomainHandler::disconnect() {
|
|||
void DomainHandler::sendDisconnectPacket() {
|
||||
// The DomainDisconnect packet is not verified - we're relying on the eventual addition of DTLS to the
|
||||
// domain-server connection to stop greifing here
|
||||
|
||||
|
||||
// construct the disconnect packet once (an empty packet but sourced with our current session UUID)
|
||||
static auto disconnectPacket = NLPacket::create(PacketType::DomainDisconnectRequest, 0);
|
||||
|
||||
|
||||
// send the disconnect packet to the current domain server
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->sendUnreliablePacket(*disconnectPacket, _sockAddr);
|
||||
|
@ -94,7 +94,7 @@ void DomainHandler::clearSettings() {
|
|||
void DomainHandler::softReset() {
|
||||
qCDebug(networking) << "Resetting current domain connection information.";
|
||||
disconnect();
|
||||
|
||||
|
||||
clearSettings();
|
||||
|
||||
_connectionDenialsSinceKeypairRegen = 0;
|
||||
|
@ -115,8 +115,8 @@ void DomainHandler::hardReset() {
|
|||
qCDebug(networking) << "Hard reset in NodeList DomainHandler.";
|
||||
_pendingDomainID = QUuid();
|
||||
_iceServerSockAddr = HifiSockAddr();
|
||||
_hostname = QString();
|
||||
_sockAddr.clear();
|
||||
_domainURL = QUrl();
|
||||
|
||||
_domainConnectionRefusals.clear();
|
||||
|
||||
|
@ -139,7 +139,10 @@ void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hos
|
|||
}
|
||||
|
||||
// some callers may pass a hostname, this is not to be used for lookup but for DTLS certificate verification
|
||||
_hostname = hostname;
|
||||
_domainURL = QUrl();
|
||||
_domainURL.setScheme(URL_SCHEME_HIFI);
|
||||
_domainURL.setHost(hostname);
|
||||
_domainURL.setPort(_sockAddr.getPort());
|
||||
}
|
||||
|
||||
void DomainHandler::setUUID(const QUuid& uuid) {
|
||||
|
@ -149,36 +152,45 @@ void DomainHandler::setUUID(const QUuid& uuid) {
|
|||
}
|
||||
}
|
||||
|
||||
void DomainHandler::setSocketAndID(const QString& hostname, quint16 port, const QUuid& domainID) {
|
||||
|
||||
void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) {
|
||||
_pendingDomainID = domainID;
|
||||
|
||||
if (hostname != _hostname || _sockAddr.getPort() != port) {
|
||||
if (domainURL.scheme() != URL_SCHEME_HIFI) {
|
||||
_sockAddr.clear();
|
||||
}
|
||||
|
||||
if (_domainURL != domainURL || _sockAddr.getPort() != domainURL.port()) {
|
||||
// re-set the domain info so that auth information is reloaded
|
||||
hardReset();
|
||||
|
||||
if (hostname != _hostname) {
|
||||
// set the new hostname
|
||||
_hostname = hostname;
|
||||
QString previousHost = _domainURL.host();
|
||||
_domainURL = domainURL;
|
||||
|
||||
qCDebug(networking) << "Updated domain hostname to" << _hostname;
|
||||
if (domainURL.scheme() != URL_SCHEME_HIFI) {
|
||||
setIsConnected(true);
|
||||
} else if (previousHost != domainURL.host()) {
|
||||
qCDebug(networking) << "Updated domain hostname to" << domainURL.host();
|
||||
|
||||
// re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname
|
||||
qCDebug(networking, "Looking up DS hostname %s.", _hostname.toLocal8Bit().constData());
|
||||
QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&)));
|
||||
if (!domainURL.host().isEmpty()) {
|
||||
// re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname
|
||||
qCDebug(networking, "Looking up DS hostname %s.", domainURL.host().toLocal8Bit().constData());
|
||||
QHostInfo::lookupHost(domainURL.host(), this, SLOT(completedHostnameLookup(const QHostInfo&)));
|
||||
|
||||
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainHostname);
|
||||
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(
|
||||
LimitedNodeList::ConnectionStep::SetDomainHostname);
|
||||
|
||||
UserActivityLogger::getInstance().changedDomain(_hostname);
|
||||
emit hostnameChanged(_hostname);
|
||||
UserActivityLogger::getInstance().changedDomain(domainURL.host());
|
||||
}
|
||||
}
|
||||
|
||||
if (_sockAddr.getPort() != port) {
|
||||
qCDebug(networking) << "Updated domain port to" << port;
|
||||
emit domainURLChanged(_domainURL);
|
||||
|
||||
if (_sockAddr.getPort() != domainURL.port()) {
|
||||
qCDebug(networking) << "Updated domain port to" << domainURL.port();
|
||||
}
|
||||
|
||||
// grab the port by reading the string after the colon
|
||||
_sockAddr.setPort(port);
|
||||
_sockAddr.setPort(domainURL.port());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,10 +199,10 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname,
|
|||
if (_iceServerSockAddr.getAddress().toString() != iceServerHostname || id != _pendingDomainID) {
|
||||
// re-set the domain info to connect to new domain
|
||||
hardReset();
|
||||
|
||||
|
||||
// refresh our ICE client UUID to something new
|
||||
_iceClientID = QUuid::createUuid();
|
||||
|
||||
|
||||
_pendingDomainID = id;
|
||||
|
||||
HifiSockAddr* replaceableSockAddr = &_iceServerSockAddr;
|
||||
|
@ -216,14 +228,18 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname,
|
|||
void DomainHandler::activateICELocalSocket() {
|
||||
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket);
|
||||
_sockAddr = _icePeer.getLocalSocket();
|
||||
_hostname = _sockAddr.getAddress().toString();
|
||||
_domainURL.setScheme(URL_SCHEME_HIFI);
|
||||
_domainURL.setHost(_sockAddr.getAddress().toString());
|
||||
emit domainURLChanged(_domainURL);
|
||||
emit completedSocketDiscovery();
|
||||
}
|
||||
|
||||
void DomainHandler::activateICEPublicSocket() {
|
||||
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket);
|
||||
_sockAddr = _icePeer.getPublicSocket();
|
||||
_hostname = _sockAddr.getAddress().toString();
|
||||
_domainURL.setScheme(URL_SCHEME_HIFI);
|
||||
_domainURL.setHost(_sockAddr.getAddress().toString());
|
||||
emit domainURLChanged(_domainURL);
|
||||
emit completedSocketDiscovery();
|
||||
}
|
||||
|
||||
|
@ -234,7 +250,7 @@ void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) {
|
|||
|
||||
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket);
|
||||
|
||||
qCDebug(networking, "DS at %s is at %s", _hostname.toLocal8Bit().constData(),
|
||||
qCDebug(networking, "DS at %s is at %s", _domainURL.host().toLocal8Bit().constData(),
|
||||
_sockAddr.getAddress().toString().toLocal8Bit().constData());
|
||||
|
||||
emit completedSocketDiscovery();
|
||||
|
@ -261,10 +277,12 @@ void DomainHandler::setIsConnected(bool isConnected) {
|
|||
_isConnected = isConnected;
|
||||
|
||||
if (_isConnected) {
|
||||
emit connectedToDomain(_hostname);
|
||||
emit connectedToDomain(_domainURL);
|
||||
|
||||
// we've connected to new domain - time to ask it for global settings
|
||||
requestDomainSettings();
|
||||
if (_domainURL.scheme() == URL_SCHEME_HIFI && !_domainURL.host().isEmpty()) {
|
||||
// we've connected to new domain - time to ask it for global settings
|
||||
requestDomainSettings();
|
||||
}
|
||||
|
||||
} else {
|
||||
emit disconnectedFromDomain();
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "NLPacketList.h"
|
||||
#include "Node.h"
|
||||
#include "ReceivedMessage.h"
|
||||
#include "NetworkingConstants.h"
|
||||
|
||||
const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102;
|
||||
const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103;
|
||||
|
@ -37,14 +38,14 @@ class DomainHandler : public QObject {
|
|||
Q_OBJECT
|
||||
public:
|
||||
DomainHandler(QObject* parent = 0);
|
||||
|
||||
|
||||
void disconnect();
|
||||
void clearSettings();
|
||||
|
||||
const QUuid& getUUID() const { return _uuid; }
|
||||
void setUUID(const QUuid& uuid);
|
||||
|
||||
const QString& getHostname() const { return _hostname; }
|
||||
QString getHostname() const { return _domainURL.host(); }
|
||||
|
||||
const QHostAddress& getIP() const { return _sockAddr.getAddress(); }
|
||||
void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); }
|
||||
|
@ -57,7 +58,7 @@ public:
|
|||
|
||||
const QUuid& getConnectionToken() const { return _connectionToken; }
|
||||
void setConnectionToken(const QUuid& connectionToken) { _connectionToken = connectionToken; }
|
||||
|
||||
|
||||
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
|
||||
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
|
||||
|
||||
|
@ -73,11 +74,12 @@ public:
|
|||
|
||||
bool isConnected() const { return _isConnected; }
|
||||
void setIsConnected(bool isConnected);
|
||||
bool isServerless() const { return _domainURL.scheme() != URL_SCHEME_HIFI; }
|
||||
|
||||
bool hasSettings() const { return !_settingsObject.isEmpty(); }
|
||||
void requestDomainSettings();
|
||||
const QJsonObject& getSettingsObject() const { return _settingsObject; }
|
||||
|
||||
|
||||
void setPendingPath(const QString& pendingPath) { _pendingPath = pendingPath; }
|
||||
const QString& getPendingPath() { return _pendingPath; }
|
||||
void clearPendingPath() { _pendingPath.clear(); }
|
||||
|
@ -139,7 +141,7 @@ public:
|
|||
};
|
||||
|
||||
public slots:
|
||||
void setSocketAndID(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT, const QUuid& id = QUuid());
|
||||
void setURLAndID(QUrl domainURL, QUuid id);
|
||||
void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id);
|
||||
|
||||
void processSettingsPacketList(QSharedPointer<ReceivedMessage> packetList);
|
||||
|
@ -153,14 +155,14 @@ private slots:
|
|||
void completedIceServerHostnameLookup();
|
||||
|
||||
signals:
|
||||
void hostnameChanged(const QString& hostname);
|
||||
void domainURLChanged(QUrl domainURL);
|
||||
|
||||
// NOTE: the emission of completedSocketDiscovery does not mean a connection to DS is established
|
||||
// It means that, either from DNS lookup or ICE, we think we have a socket we can talk to DS on
|
||||
void completedSocketDiscovery();
|
||||
|
||||
void resetting();
|
||||
void connectedToDomain(const QString& hostname);
|
||||
void connectedToDomain(QUrl domainURL);
|
||||
void disconnectedFromDomain();
|
||||
|
||||
void iceSocketAndIDReceived();
|
||||
|
@ -179,7 +181,7 @@ private:
|
|||
void hardReset();
|
||||
|
||||
QUuid _uuid;
|
||||
QString _hostname;
|
||||
QUrl _domainURL;
|
||||
HifiSockAddr _sockAddr;
|
||||
QUuid _assignmentUUID;
|
||||
QUuid _connectionToken;
|
||||
|
@ -200,4 +202,7 @@ private:
|
|||
QTimer _apiRefreshTimer;
|
||||
};
|
||||
|
||||
const QString DOMAIN_SPAWNING_POINT { "/0, -10, 0" };
|
||||
|
||||
|
||||
#endif // hifi_DomainHandler_h
|
||||
|
|
|
@ -17,9 +17,11 @@
|
|||
|
||||
#include <StatTracker.h>
|
||||
#include <shared/FileUtils.h>
|
||||
#include <PathUtils.h>
|
||||
|
||||
#include "NetworkLogging.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "NetworkingConstants.h"
|
||||
|
||||
void FileResourceRequest::doSend() {
|
||||
auto statTracker = DependencyManager::get<StatTracker>();
|
||||
|
@ -29,7 +31,7 @@ void FileResourceRequest::doSend() {
|
|||
if (_url.scheme() == URL_SCHEME_QRC) {
|
||||
filename = ":/" + _url.path();
|
||||
} else {
|
||||
filename = _url.toLocalFile();
|
||||
filename = PathUtils::expandToLocalDataAbsolutePath(_url).toLocalFile();
|
||||
// sometimes on windows, we see the toLocalFile() return null,
|
||||
// in this case we will attempt to simply use the url as a string
|
||||
if (filename.isEmpty()) {
|
||||
|
|
24
libraries/networking/src/NetworkingConstants.cpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// NetworkingConstants.cpp
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Seth Alves on 2018-2-28.
|
||||
// Copyright 2018 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 "NetworkingConstants.h"
|
||||
|
||||
namespace NetworkingConstants {
|
||||
// You can change the return of this function if you want to use a custom metaverse URL at compile time
|
||||
// or you can pass a custom URL via the env variable
|
||||
QUrl METAVERSE_SERVER_URL() {
|
||||
const QString HIFI_METAVERSE_URL_ENV = "HIFI_METAVERSE_URL";
|
||||
const QUrl serverURL = QProcessEnvironment::systemEnvironment().contains(HIFI_METAVERSE_URL_ENV)
|
||||
? QUrl(QProcessEnvironment::systemEnvironment().value(HIFI_METAVERSE_URL_ENV))
|
||||
: METAVERSE_SERVER_URL_STABLE;
|
||||
return serverURL;
|
||||
};
|
||||
}
|
|
@ -25,18 +25,17 @@ namespace NetworkingConstants {
|
|||
// if you manually generate a personal access token for the domains scope
|
||||
// at https://staging.highfidelity.com/user/tokens/new?for_domain_server=true
|
||||
|
||||
const QUrl METAVERSE_SERVER_URL_STABLE("https://metaverse.highfidelity.com");
|
||||
const QUrl METAVERSE_SERVER_URL_STAGING("https://staging.highfidelity.com");
|
||||
|
||||
// You can change the return of this function if you want to use a custom metaverse URL at compile time
|
||||
// or you can pass a custom URL via the env variable
|
||||
static const QUrl METAVERSE_SERVER_URL() {
|
||||
static const QString HIFI_METAVERSE_URL_ENV = "HIFI_METAVERSE_URL";
|
||||
static const QUrl serverURL = QProcessEnvironment::systemEnvironment().contains(HIFI_METAVERSE_URL_ENV)
|
||||
? QUrl(QProcessEnvironment::systemEnvironment().value(HIFI_METAVERSE_URL_ENV))
|
||||
: METAVERSE_SERVER_URL_STABLE;
|
||||
return serverURL;
|
||||
};
|
||||
const QUrl METAVERSE_SERVER_URL_STABLE { "https://metaverse.highfidelity.com" };
|
||||
const QUrl METAVERSE_SERVER_URL_STAGING { "https://staging.highfidelity.com" };
|
||||
QUrl METAVERSE_SERVER_URL();
|
||||
}
|
||||
|
||||
const QString URL_SCHEME_HIFI = "hifi";
|
||||
const QString URL_SCHEME_QRC = "qrc";
|
||||
const QString URL_SCHEME_FILE = "file";
|
||||
const QString URL_SCHEME_HTTP = "http";
|
||||
const QString URL_SCHEME_HTTPS = "https";
|
||||
const QString URL_SCHEME_FTP = "ftp";
|
||||
const QString URL_SCHEME_ATP = "atp";
|
||||
|
||||
#endif // hifi_NetworkingConstants_h
|
||||
|
|
|
@ -55,7 +55,7 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort)
|
|||
|
||||
// handle domain change signals from AddressManager
|
||||
connect(addressManager.data(), &AddressManager::possibleDomainChangeRequired,
|
||||
&_domainHandler, &DomainHandler::setSocketAndID);
|
||||
&_domainHandler, &DomainHandler::setURLAndID);
|
||||
|
||||
connect(addressManager.data(), &AddressManager::possibleDomainChangeRequiredViaICEForID,
|
||||
&_domainHandler, &DomainHandler::setIceServerHostnameAndID);
|
||||
|
@ -91,7 +91,7 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort)
|
|||
connect(accountManager.data(), &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn);
|
||||
|
||||
// clear out NodeList when login is finished
|
||||
connect(accountManager.data(), SIGNAL(loginComplete()) , this, SLOT(reset()));
|
||||
connect(accountManager.data(), SIGNAL(loginComplete(const QUrl&)) , this, SLOT(reset()));
|
||||
|
||||
// clear our NodeList when logout is requested
|
||||
connect(accountManager.data(), SIGNAL(logoutComplete()) , this, SLOT(reset()));
|
||||
|
@ -106,7 +106,7 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort)
|
|||
// setup our timer to send keepalive pings (it's started and stopped on domain connect/disconnect)
|
||||
_keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL_MS); // 1s, Qt::CoarseTimer acceptable
|
||||
connect(&_keepAlivePingTimer, &QTimer::timeout, this, &NodeList::sendKeepAlivePings);
|
||||
connect(&_domainHandler, SIGNAL(connectedToDomain(QString)), &_keepAlivePingTimer, SLOT(start()));
|
||||
connect(&_domainHandler, SIGNAL(connectedToDomain(QUrl)), &_keepAlivePingTimer, SLOT(start()));
|
||||
connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, &_keepAlivePingTimer, &QTimer::stop);
|
||||
|
||||
// set our sockAddrBelongsToDomainOrNode method as the connection creation filter for the udt::Socket
|
||||
|
|
|
@ -22,13 +22,6 @@
|
|||
|
||||
#include "ResourceRequest.h"
|
||||
|
||||
const QString URL_SCHEME_QRC = "qrc";
|
||||
const QString URL_SCHEME_FILE = "file";
|
||||
const QString URL_SCHEME_HTTP = "http";
|
||||
const QString URL_SCHEME_HTTPS = "https";
|
||||
const QString URL_SCHEME_FTP = "ftp";
|
||||
const QString URL_SCHEME_ATP = "atp";
|
||||
|
||||
class ResourceManager: public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
|
|
@ -1685,6 +1685,15 @@ bool Octree::readFromURL(const QString& urlString) {
|
|||
}
|
||||
|
||||
auto data = request->getData();
|
||||
|
||||
QByteArray uncompressedJsonData;
|
||||
bool wasCompressed = gunzip(data, uncompressedJsonData);
|
||||
|
||||
if (wasCompressed) {
|
||||
QDataStream inputStream(uncompressedJsonData);
|
||||
return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID);
|
||||
}
|
||||
|
||||
QDataStream inputStream(data);
|
||||
return readFromStream(data.size(), inputStream, marketplaceID);
|
||||
}
|
||||
|
|
|
@ -166,18 +166,30 @@ bool OffscreenSurface::eventFilter(QObject* originalDestination, QEvent* event)
|
|||
case QEvent::TouchUpdate:
|
||||
case QEvent::TouchEnd: {
|
||||
QTouchEvent *originalEvent = static_cast<QTouchEvent *>(event);
|
||||
QTouchEvent fakeEvent(*originalEvent);
|
||||
auto newTouchPoints = fakeEvent.touchPoints();
|
||||
for (size_t i = 0; i < newTouchPoints.size(); ++i) {
|
||||
const auto &originalPoint = originalEvent->touchPoints()[i];
|
||||
auto &newPoint = newTouchPoints[i];
|
||||
newPoint.setPos(originalPoint.pos());
|
||||
QEvent::Type fakeMouseEventType = QEvent::None;
|
||||
Qt::MouseButton fakeMouseButton = Qt::LeftButton;
|
||||
Qt::MouseButtons fakeMouseButtons = Qt::NoButton;
|
||||
switch (event->type()) {
|
||||
case QEvent::TouchBegin:
|
||||
fakeMouseEventType = QEvent::MouseButtonPress;
|
||||
fakeMouseButtons = Qt::LeftButton;
|
||||
break;
|
||||
case QEvent::TouchUpdate:
|
||||
fakeMouseEventType = QEvent::MouseMove;
|
||||
fakeMouseButtons = Qt::LeftButton;
|
||||
break;
|
||||
case QEvent::TouchEnd:
|
||||
fakeMouseEventType = QEvent::MouseButtonRelease;
|
||||
fakeMouseButtons = Qt::NoButton;
|
||||
break;
|
||||
}
|
||||
fakeEvent.setTouchPoints(newTouchPoints);
|
||||
if (QCoreApplication::sendEvent(_sharedObject->getWindow(), &fakeEvent)) {
|
||||
qInfo() << __FUNCTION__ << "sent fake touch event:" << fakeEvent.type()
|
||||
<< "_quickWindow handled it... accepted:" << fakeEvent.isAccepted();
|
||||
return false; //event->isAccepted();
|
||||
// Same case as OffscreenUi.cpp::eventFilter: touch events are always being accepted so we now use mouse events and consider one touch, touchPoints()[0].
|
||||
QMouseEvent fakeMouseEvent(fakeMouseEventType, originalEvent->touchPoints()[0].pos(), fakeMouseButton, fakeMouseButtons, Qt::NoModifier);
|
||||
fakeMouseEvent.ignore();
|
||||
if (QCoreApplication::sendEvent(_sharedObject->getWindow(), &fakeMouseEvent)) {
|
||||
/*qInfo() << __FUNCTION__ << "sent fake touch event:" << fakeMouseEvent.type()
|
||||
<< "_quickWindow handled it... accepted:" << fakeMouseEvent.isAccepted();*/
|
||||
return fakeMouseEvent.isAccepted();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -59,5 +59,7 @@ static const float MIN_AVATAR_SCALE = 0.005f;
|
|||
|
||||
static const float MAX_AVATAR_HEIGHT = 1000.0f * DEFAULT_AVATAR_HEIGHT; // meters
|
||||
static const float MIN_AVATAR_HEIGHT = 0.005f * DEFAULT_AVATAR_HEIGHT; // meters
|
||||
static const float AVATAR_WALK_SPEED_SCALAR = 1.0f;
|
||||
static const float AVATAR_SPRINT_SPEED_SCALAR = 3.0f;
|
||||
|
||||
#endif // hifi_AvatarConstants_h
|
||||
|
|
|
@ -81,7 +81,7 @@ const QString& PathUtils::resourcesPath() {
|
|||
#else
|
||||
staticResourcePath = ":/";
|
||||
#endif
|
||||
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && defined(DEV_BUILD)
|
||||
if (USE_SOURCE_TREE_RESOURCES()) {
|
||||
// For dev builds, optionally load content from the Git source tree
|
||||
|
@ -120,6 +120,31 @@ QUrl PathUtils::resourcesUrl(const QString& relativeUrl) {
|
|||
return QUrl(resourcesUrl() + relativeUrl);
|
||||
}
|
||||
|
||||
QUrl PathUtils::expandToLocalDataAbsolutePath(const QUrl& fileUrl) {
|
||||
QString path = fileUrl.path();
|
||||
|
||||
if (path.startsWith("/~/")) {
|
||||
// this results in a qrc:// url...
|
||||
// return resourcesUrl(path.mid(3));
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/";
|
||||
#elif defined (ANDROID)
|
||||
static const QString staticResourcePath =
|
||||
QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/resources/";
|
||||
#else
|
||||
static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/";
|
||||
#endif
|
||||
path.replace(0, 3, staticResourcePath);
|
||||
QUrl expandedURL = QUrl::fromLocalFile(path);
|
||||
return expandedURL;
|
||||
}
|
||||
|
||||
QUrl::fromLocalFile(resourcesPath()).toString();
|
||||
|
||||
return fileUrl;
|
||||
}
|
||||
|
||||
const QString& PathUtils::qmlBaseUrl() {
|
||||
static const QString staticResourcePath = resourcesUrl() + "qml/";
|
||||
return staticResourcePath;
|
||||
|
|
|
@ -37,6 +37,7 @@ public:
|
|||
static QUrl resourcesUrl(const QString& relative);
|
||||
static const QString& resourcesPath();
|
||||
static const QString& qmlBaseUrl();
|
||||
static QUrl expandToLocalDataAbsolutePath(const QUrl& fileUrl);
|
||||
static QUrl qmlUrl(const QString& relative);
|
||||
#ifdef DEV_BUILD
|
||||
static const QString& projectRootPath();
|
||||
|
|
|
@ -26,14 +26,14 @@ var RADIUS_RATE = 1.0 / 100.0;
|
|||
var PAN_RATE = 250.0;
|
||||
|
||||
var Y_AXIS = {
|
||||
x: 0,
|
||||
y: 1,
|
||||
z: 0
|
||||
x: 0,
|
||||
y: 1,
|
||||
z: 0
|
||||
};
|
||||
var X_AXIS = {
|
||||
x: 1,
|
||||
y: 0,
|
||||
z: 0
|
||||
x: 1,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
|
||||
var LOOK_AT_TIME = 500;
|
||||
|
@ -56,21 +56,20 @@ var mode = noMode;
|
|||
var mouseLastX = 0;
|
||||
var mouseLastY = 0;
|
||||
|
||||
|
||||
var center = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
var position = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
var vector = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
var radius = 0.0;
|
||||
var azimuth = 0.0;
|
||||
|
@ -83,258 +82,248 @@ var rotatingTowardsTarget = false;
|
|||
var targetCamOrientation;
|
||||
var oldPosition, oldOrientation;
|
||||
|
||||
|
||||
function orientationOf(vector) {
|
||||
var direction,
|
||||
yaw,
|
||||
pitch;
|
||||
var direction,
|
||||
yaw,
|
||||
pitch;
|
||||
|
||||
direction = Vec3.normalize(vector);
|
||||
yaw = Quat.angleAxis(Math.atan2(direction.x, direction.z) * RAD_TO_DEG, Y_AXIS);
|
||||
pitch = Quat.angleAxis(Math.asin(-direction.y) * RAD_TO_DEG, X_AXIS);
|
||||
return Quat.multiply(yaw, pitch);
|
||||
direction = Vec3.normalize(vector);
|
||||
yaw = Quat.angleAxis(Math.atan2(direction.x, direction.z) * RAD_TO_DEG, Y_AXIS);
|
||||
pitch = Quat.angleAxis(Math.asin(-direction.y) * RAD_TO_DEG, X_AXIS);
|
||||
return Quat.multiply(yaw, pitch);
|
||||
}
|
||||
|
||||
|
||||
function handleRadialMode(dx, dy) {
|
||||
azimuth += dx / AZIMUTH_RATE;
|
||||
radius += radius * dy * RADIUS_RATE;
|
||||
if (radius < 1) {
|
||||
radius = 1;
|
||||
}
|
||||
azimuth += dx / AZIMUTH_RATE;
|
||||
radius += radius * dy * RADIUS_RATE;
|
||||
if (radius < 1) {
|
||||
radius = 1;
|
||||
}
|
||||
|
||||
vector = {
|
||||
x: (Math.cos(altitude) * Math.cos(azimuth)) * radius,
|
||||
y: Math.sin(altitude) * radius,
|
||||
z: (Math.cos(altitude) * Math.sin(azimuth)) * radius
|
||||
};
|
||||
position = Vec3.sum(center, vector);
|
||||
Camera.setPosition(position);
|
||||
Camera.setOrientation(orientationOf(vector));
|
||||
vector = {
|
||||
x: (Math.cos(altitude) * Math.cos(azimuth)) * radius,
|
||||
y: Math.sin(altitude) * radius,
|
||||
z: (Math.cos(altitude) * Math.sin(azimuth)) * radius
|
||||
};
|
||||
position = Vec3.sum(center, vector);
|
||||
Camera.setPosition(position);
|
||||
Camera.setOrientation(orientationOf(vector));
|
||||
}
|
||||
|
||||
function handleOrbitMode(dx, dy) {
|
||||
azimuth += dx / AZIMUTH_RATE;
|
||||
altitude += dy / ALTITUDE_RATE;
|
||||
if (altitude > PI / 2.0) {
|
||||
altitude = PI / 2.0;
|
||||
}
|
||||
if (altitude < -PI / 2.0) {
|
||||
altitude = -PI / 2.0;
|
||||
}
|
||||
azimuth += dx / AZIMUTH_RATE;
|
||||
altitude += dy / ALTITUDE_RATE;
|
||||
if (altitude > PI / 2.0) {
|
||||
altitude = PI / 2.0;
|
||||
}
|
||||
if (altitude < -PI / 2.0) {
|
||||
altitude = -PI / 2.0;
|
||||
}
|
||||
|
||||
vector = {
|
||||
x: (Math.cos(altitude) * Math.cos(azimuth)) * radius,
|
||||
y: Math.sin(altitude) * radius,
|
||||
z: (Math.cos(altitude) * Math.sin(azimuth)) * radius
|
||||
};
|
||||
position = Vec3.sum(center, vector);
|
||||
Camera.setPosition(position);
|
||||
Camera.setOrientation(orientationOf(vector));
|
||||
vector = {
|
||||
x: (Math.cos(altitude) * Math.cos(azimuth)) * radius,
|
||||
y: Math.sin(altitude) * radius,
|
||||
z: (Math.cos(altitude) * Math.sin(azimuth)) * radius
|
||||
};
|
||||
position = Vec3.sum(center, vector);
|
||||
Camera.setPosition(position);
|
||||
Camera.setOrientation(orientationOf(vector));
|
||||
}
|
||||
|
||||
|
||||
function handlePanMode(dx, dy) {
|
||||
var up = Quat.getUp(Camera.getOrientation());
|
||||
var right = Quat.getRight(Camera.getOrientation());
|
||||
var distance = Vec3.length(vector);
|
||||
var up = Quat.getUp(Camera.getOrientation());
|
||||
var right = Quat.getRight(Camera.getOrientation());
|
||||
var distance = Vec3.length(vector);
|
||||
|
||||
var dv = Vec3.sum(Vec3.multiply(up, distance * dy / PAN_RATE), Vec3.multiply(right, -distance * dx / PAN_RATE));
|
||||
var dv = Vec3.sum(Vec3.multiply(up, distance * dy / PAN_RATE), Vec3.multiply(right, -distance * dx / PAN_RATE));
|
||||
|
||||
center = Vec3.sum(center, dv);
|
||||
position = Vec3.sum(position, dv);
|
||||
center = Vec3.sum(center, dv);
|
||||
position = Vec3.sum(position, dv);
|
||||
|
||||
Camera.setPosition(position);
|
||||
Camera.setOrientation(orientationOf(vector));
|
||||
Camera.setPosition(position);
|
||||
Camera.setOrientation(orientationOf(vector));
|
||||
}
|
||||
|
||||
function saveCameraState() {
|
||||
oldMode = Camera.mode;
|
||||
oldPosition = Camera.getPosition();
|
||||
oldOrientation = Camera.getOrientation();
|
||||
oldMode = Camera.mode;
|
||||
oldPosition = Camera.getPosition();
|
||||
oldOrientation = Camera.getOrientation();
|
||||
|
||||
Camera.mode = "independent";
|
||||
Camera.setPosition(oldPosition);
|
||||
Camera.mode = "independent";
|
||||
Camera.setPosition(oldPosition);
|
||||
}
|
||||
|
||||
function restoreCameraState() {
|
||||
Camera.mode = oldMode;
|
||||
Camera.setPosition(oldPosition);
|
||||
Camera.setOrientation(oldOrientation);
|
||||
Camera.mode = oldMode;
|
||||
Camera.setPosition(oldPosition);
|
||||
Camera.setOrientation(oldOrientation);
|
||||
}
|
||||
|
||||
function handleModes() {
|
||||
var newMode = (mode == noMode) ? noMode : detachedMode;
|
||||
if (alt) {
|
||||
if (control) {
|
||||
if (shift) {
|
||||
newMode = panningMode;
|
||||
} else {
|
||||
newMode = orbitMode;
|
||||
}
|
||||
} else {
|
||||
newMode = radialMode;
|
||||
var newMode = (mode == noMode) ? noMode : detachedMode;
|
||||
if (alt) {
|
||||
if (control) {
|
||||
if (shift) {
|
||||
newMode = panningMode;
|
||||
} else {
|
||||
newMode = orbitMode;
|
||||
}
|
||||
} else {
|
||||
newMode = radialMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if entering detachMode
|
||||
if (newMode == detachedMode && mode != detachedMode) {
|
||||
avatarPosition = MyAvatar.position;
|
||||
avatarOrientation = MyAvatar.orientation;
|
||||
}
|
||||
// if leaving detachMode
|
||||
if (mode == detachedMode && newMode == detachedMode &&
|
||||
(avatarPosition.x != MyAvatar.position.x ||
|
||||
avatarPosition.y != MyAvatar.position.y ||
|
||||
avatarPosition.z != MyAvatar.position.z ||
|
||||
avatarOrientation.x != MyAvatar.orientation.x ||
|
||||
avatarOrientation.y != MyAvatar.orientation.y ||
|
||||
avatarOrientation.z != MyAvatar.orientation.z ||
|
||||
avatarOrientation.w != MyAvatar.orientation.w)) {
|
||||
newMode = noMode;
|
||||
}
|
||||
// if entering detachMode
|
||||
if (newMode == detachedMode && mode != detachedMode) {
|
||||
avatarPosition = MyAvatar.position;
|
||||
avatarOrientation = MyAvatar.orientation;
|
||||
}
|
||||
// if leaving detachMode
|
||||
if (mode == detachedMode && newMode == detachedMode &&
|
||||
(avatarPosition.x != MyAvatar.position.x ||
|
||||
avatarPosition.y != MyAvatar.position.y ||
|
||||
avatarPosition.z != MyAvatar.position.z ||
|
||||
avatarOrientation.x != MyAvatar.orientation.x ||
|
||||
avatarOrientation.y != MyAvatar.orientation.y ||
|
||||
avatarOrientation.z != MyAvatar.orientation.z ||
|
||||
avatarOrientation.w != MyAvatar.orientation.w)) {
|
||||
newMode = noMode;
|
||||
}
|
||||
|
||||
if (mode == noMode && newMode != noMode && Camera.mode == "independent") {
|
||||
newMode = noMode;
|
||||
}
|
||||
if (mode == noMode && newMode != noMode && Camera.mode == "independent") {
|
||||
newMode = noMode;
|
||||
}
|
||||
|
||||
// if leaving noMode
|
||||
if (mode == noMode && newMode != noMode) {
|
||||
saveCameraState();
|
||||
}
|
||||
// if entering noMode
|
||||
if (newMode == noMode && mode != noMode) {
|
||||
restoreCameraState();
|
||||
}
|
||||
// if leaving noMode
|
||||
if (mode == noMode && newMode != noMode) {
|
||||
saveCameraState();
|
||||
}
|
||||
// if entering noMode
|
||||
if (newMode == noMode && mode != noMode) {
|
||||
restoreCameraState();
|
||||
}
|
||||
|
||||
mode = newMode;
|
||||
mode = newMode;
|
||||
}
|
||||
|
||||
function keyPressEvent(event) {
|
||||
var changed = false;
|
||||
var changed = false;
|
||||
|
||||
if (event.text == "ALT") {
|
||||
alt = true;
|
||||
changed = true;
|
||||
}
|
||||
if (event.text == "CONTROL") {
|
||||
control = true;
|
||||
changed = true;
|
||||
}
|
||||
if (event.text == "SHIFT") {
|
||||
shift = true;
|
||||
changed = true;
|
||||
}
|
||||
if (event.text == "ALT") {
|
||||
alt = true;
|
||||
changed = true;
|
||||
}
|
||||
if (event.text == "CONTROL") {
|
||||
control = true;
|
||||
changed = true;
|
||||
}
|
||||
if (event.text == "SHIFT") {
|
||||
shift = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
handleModes();
|
||||
}
|
||||
if (changed) {
|
||||
handleModes();
|
||||
}
|
||||
}
|
||||
|
||||
function keyReleaseEvent(event) {
|
||||
var changed = false;
|
||||
var changed = false;
|
||||
|
||||
if (event.text == "ALT") {
|
||||
alt = false;
|
||||
changed = true;
|
||||
mode = noMode;
|
||||
restoreCameraState();
|
||||
}
|
||||
if (event.text == "CONTROL") {
|
||||
control = false;
|
||||
changed = true;
|
||||
}
|
||||
if (event.text == "SHIFT") {
|
||||
shift = false;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
handleModes();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function mousePressEvent(event) {
|
||||
if (alt && !isActive) {
|
||||
mouseLastX = event.x;
|
||||
mouseLastY = event.y;
|
||||
|
||||
// Compute trajectories related values
|
||||
var pickRay = Camera.computePickRay(mouseLastX, mouseLastY);
|
||||
var modelIntersection = Entities.findRayIntersection(pickRay, true);
|
||||
|
||||
position = Camera.getPosition();
|
||||
|
||||
var avatarTarget = MyAvatar.getTargetAvatarPosition();
|
||||
|
||||
|
||||
var distance = -1;
|
||||
var string;
|
||||
|
||||
if (modelIntersection.intersects && modelIntersection.accurate) {
|
||||
distance = modelIntersection.distance;
|
||||
center = modelIntersection.intersection;
|
||||
string = "Inspecting model";
|
||||
//We've selected our target, now orbit towards it automatically
|
||||
rotatingTowardsTarget = true;
|
||||
//calculate our target cam rotation
|
||||
Script.setTimeout(function() {
|
||||
rotatingTowardsTarget = false;
|
||||
}, LOOK_AT_TIME);
|
||||
|
||||
vector = Vec3.subtract(position, center);
|
||||
targetCamOrientation = orientationOf(vector);
|
||||
radius = Vec3.length(vector);
|
||||
azimuth = Math.atan2(vector.z, vector.x);
|
||||
altitude = Math.asin(vector.y / Vec3.length(vector));
|
||||
|
||||
isActive = true;
|
||||
if (event.text == "ALT") {
|
||||
alt = false;
|
||||
changed = true;
|
||||
mode = noMode;
|
||||
restoreCameraState();
|
||||
}
|
||||
if (event.text == "CONTROL") {
|
||||
control = false;
|
||||
changed = true;
|
||||
}
|
||||
if (event.text == "SHIFT") {
|
||||
shift = false;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
}
|
||||
if (changed) {
|
||||
handleModes();
|
||||
}
|
||||
}
|
||||
|
||||
function mousePressEvent(event) {
|
||||
if (alt && !isActive) {
|
||||
mouseLastX = event.x;
|
||||
mouseLastY = event.y;
|
||||
|
||||
// Compute trajectories related values
|
||||
var pickRay = Camera.computePickRay(mouseLastX, mouseLastY);
|
||||
var modelIntersection = Entities.findRayIntersection(pickRay, true);
|
||||
var avatarIntersection = AvatarList.findRayIntersection(pickRay);
|
||||
|
||||
position = Camera.getPosition();
|
||||
|
||||
if (avatarIntersection.intersects || (modelIntersection.intersects && modelIntersection.accurate)) {
|
||||
if (avatarIntersection.intersects) {
|
||||
center = avatarIntersection.intersection;
|
||||
} else {
|
||||
center = modelIntersection.intersection;
|
||||
}
|
||||
// We've selected our target, now orbit towards it automatically
|
||||
rotatingTowardsTarget = true;
|
||||
// calculate our target cam rotation
|
||||
Script.setTimeout(function () {
|
||||
rotatingTowardsTarget = false;
|
||||
}, LOOK_AT_TIME);
|
||||
|
||||
vector = Vec3.subtract(position, center);
|
||||
targetCamOrientation = orientationOf(vector);
|
||||
radius = Vec3.length(vector);
|
||||
azimuth = Math.atan2(vector.z, vector.x);
|
||||
altitude = Math.asin(vector.y / Vec3.length(vector));
|
||||
|
||||
isActive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mouseReleaseEvent(event) {
|
||||
if (isActive) {
|
||||
isActive = false;
|
||||
}
|
||||
if (isActive) {
|
||||
isActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
function mouseMoveEvent(event) {
|
||||
if (isActive && mode != noMode && !rotatingTowardsTarget) {
|
||||
if (mode == radialMode) {
|
||||
handleRadialMode(event.x - mouseLastX, event.y - mouseLastY);
|
||||
if (isActive && mode != noMode && !rotatingTowardsTarget) {
|
||||
if (mode == radialMode) {
|
||||
handleRadialMode(event.x - mouseLastX, event.y - mouseLastY);
|
||||
}
|
||||
if (mode == orbitMode) {
|
||||
handleOrbitMode(event.x - mouseLastX, event.y - mouseLastY);
|
||||
}
|
||||
if (mode == panningMode) {
|
||||
handlePanMode(event.x - mouseLastX, event.y - mouseLastY);
|
||||
}
|
||||
}
|
||||
if (mode == orbitMode) {
|
||||
handleOrbitMode(event.x - mouseLastX, event.y - mouseLastY);
|
||||
}
|
||||
if (mode == panningMode) {
|
||||
handlePanMode(event.x - mouseLastX, event.y - mouseLastY);
|
||||
}
|
||||
|
||||
}
|
||||
mouseLastX = event.x;
|
||||
mouseLastY = event.y;
|
||||
mouseLastX = event.x;
|
||||
mouseLastY = event.y;
|
||||
}
|
||||
|
||||
function update() {
|
||||
handleModes();
|
||||
if (rotatingTowardsTarget) {
|
||||
rotateTowardsTarget();
|
||||
}
|
||||
handleModes();
|
||||
if (rotatingTowardsTarget) {
|
||||
rotateTowardsTarget();
|
||||
}
|
||||
}
|
||||
|
||||
function rotateTowardsTarget() {
|
||||
var newOrientation = Quat.mix(Camera.getOrientation(), targetCamOrientation, .1);
|
||||
Camera.setOrientation(newOrientation);
|
||||
var newOrientation = Quat.mix(Camera.getOrientation(), targetCamOrientation, 0.1);
|
||||
Camera.setOrientation(newOrientation);
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
if (mode != noMode) {
|
||||
restoreCameraState();
|
||||
}
|
||||
if (mode != noMode) {
|
||||
restoreCameraState();
|
||||
}
|
||||
}
|
||||
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
|
@ -345,4 +334,4 @@ Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
|||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
|
|
@ -16,7 +16,8 @@ var DEFAULT_SCRIPTS_COMBINED = [
|
|||
"system/+android/touchscreenvirtualpad.js",
|
||||
"system/+android/bottombar.js",
|
||||
"system/+android/audio.js" ,
|
||||
"system/+android/modes.js"/*,
|
||||
"system/+android/modes.js",
|
||||
"system/+android/stats.js"/*,
|
||||
"system/away.js",
|
||||
"system/controllers/controllerDisplayManager.js",
|
||||
"system/controllers/handControllerGrabAndroid.js",
|
||||
|
|
|
@ -46,7 +46,6 @@ function onMuteClicked() {
|
|||
printd("On Mute Clicked");
|
||||
//Menu.setIsOptionChecked("Mute Microphone", !Menu.isOptionChecked("Mute Microphone"));
|
||||
Audio.muted = !Audio.muted;
|
||||
onMuteToggled();
|
||||
}
|
||||
|
||||
function onMuteToggled() {
|
||||
|
|
|
@ -35,6 +35,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
break;
|
||||
case 'hide':
|
||||
Controller.setVPadHidden(false);
|
||||
module.exports.hide();
|
||||
module.exports.onHidden();
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -33,7 +33,6 @@ function init() {
|
|||
radar.setUniqueColor(uniqueColor);
|
||||
radar.init();
|
||||
setupModesBar();
|
||||
radar.isTouchValid = isRadarModeValidTouch;
|
||||
}
|
||||
|
||||
function shutdown() {
|
||||
|
@ -183,34 +182,6 @@ function onButtonClicked(clickedButton, whatToDo, hideAllAfter) {
|
|||
}
|
||||
}
|
||||
|
||||
function isRadarModeValidTouch(coords) {
|
||||
var qmlFragments = [modesbar.qmlFragment];
|
||||
var windows = [];
|
||||
for (var i=0; i < qmlFragments.length; i++) {
|
||||
var aQmlFrag = qmlFragments[i];
|
||||
if (aQmlFrag != null && aQmlFrag.isVisible() &&
|
||||
coords.x >= aQmlFrag.position.x * 3 && coords.x <= aQmlFrag.position.x * 3 + aQmlFrag.size.x * 3 &&
|
||||
coords.y >= aQmlFrag.position.y * 3 && coords.y <= aQmlFrag.position.y * 3 + aQmlFrag.size.y * 3
|
||||
) {
|
||||
printd("godViewModeTouchValid- false because of qmlFragments!? idx " + i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i=0; i < windows.length; i++) {
|
||||
var aWin = windows[i];
|
||||
if (aWin != null && aWin.position() != null &&
|
||||
coords.x >= aWin.position().x * 3 && coords.x <= aWin.position().x * 3 + aWin.width() * 3 &&
|
||||
coords.y >= aWin.position().y * 3 && coords.y <= aWin.position().y * 3 + aWin.height() * 3
|
||||
) {
|
||||
printd("godViewModeTouchValid- false because of windows!?");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
printd("godViewModeTouchValid- true by default ");
|
||||
return true;
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
shutdown();
|
||||
});
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
var radarModeInterface = {};
|
||||
|
||||
var logEnabled = true;
|
||||
var logEnabled = false;
|
||||
function printd(str) {
|
||||
if (logEnabled) {
|
||||
print("[radar.js] " + str);
|
||||
|
@ -118,19 +118,10 @@ function actionOnObjectFromEvent(event) {
|
|||
}
|
||||
|
||||
function mousePress(event) {
|
||||
if (!isTouchValid(coords)) {
|
||||
currentTouchIsValid = false;
|
||||
return;
|
||||
} else {
|
||||
currentTouchIsValid = true;
|
||||
}
|
||||
mousePressOrTouchEnd(event);
|
||||
}
|
||||
|
||||
function mousePressOrTouchEnd(event) {
|
||||
if (!currentTouchIsValid) {
|
||||
return;
|
||||
}
|
||||
if (radar) {
|
||||
if (actionOnObjectFromEvent(event)) {
|
||||
return;
|
||||
|
@ -155,9 +146,6 @@ function fakeDoubleTap(event) {
|
|||
teleporter.dragTeleportRelease(event);
|
||||
}
|
||||
|
||||
var currentTouchIsValid = false; // Currently used to know if touch hasn't
|
||||
// started on a UI overlay
|
||||
|
||||
var DOUBLE_TAP_TIME = 300;
|
||||
var fakeDoubleTapStart = Date.now();
|
||||
var touchEndCount = 0;
|
||||
|
@ -238,12 +226,6 @@ function touchEnd(event) {
|
|||
return;
|
||||
}
|
||||
|
||||
// if touch is invalid, cancel
|
||||
if (!currentTouchIsValid) {
|
||||
printd("touchEnd fail because !currentTouchIsValid");
|
||||
return;
|
||||
}
|
||||
|
||||
if (analyzeDoubleTap(event))
|
||||
return; // double tap detected, finish
|
||||
|
||||
|
@ -345,20 +327,6 @@ function computePointAtPlaneY(x, y, py) {
|
|||
p2.z, py);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
function isTouchValid(coords) {
|
||||
// TODO: Extend to the detection of touches on new menu bars
|
||||
var radarModeTouchValid = radarModeInterface.isTouchValid(coords);
|
||||
|
||||
// getItemAtPoint does not exist anymore, look for another way to know if we
|
||||
// are touching buttons
|
||||
// is it still needed?
|
||||
return /* !tablet.getItemAtPoint(coords) && */radarModeTouchValid;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
******************************************************************************/
|
||||
|
@ -373,16 +341,8 @@ function touchBegin(event) {
|
|||
x : event.x,
|
||||
y : event.y
|
||||
};
|
||||
if (!isTouchValid(coords)) {
|
||||
printd("analyze touch - RADAR_TOUCH - INVALID");
|
||||
currentTouchIsValid = false;
|
||||
touchStartingCoordinates = null;
|
||||
} else {
|
||||
printd("analyze touch - RADAR_TOUCH - ok");
|
||||
currentTouchIsValid = true;
|
||||
touchStartingCoordinates = coords;
|
||||
touchBeginTime = Date.now();
|
||||
}
|
||||
touchStartingCoordinates = coords;
|
||||
touchBeginTime = Date.now();
|
||||
}
|
||||
|
||||
var startedDraggingCamera = false; // first time
|
||||
|
@ -848,9 +808,6 @@ function oneFingerTouchUpdate(event) {
|
|||
}
|
||||
|
||||
function touchUpdate(event) {
|
||||
if (!currentTouchIsValid) {
|
||||
return; // avoid moving and zooming when tap is over UI entities
|
||||
}
|
||||
if (event.isPinching || event.isPinchOpening) {
|
||||
pinchUpdate(event);
|
||||
} else {
|
||||
|
|
39
scripts/system/+android/stats.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
"use strict";
|
||||
//
|
||||
// stats.js
|
||||
// scripts/system/
|
||||
//
|
||||
// Created by Sam Gondelman on 3/14/18
|
||||
// Copyright 2018 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
|
||||
//
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var statsbar;
|
||||
var statsButton;
|
||||
|
||||
function init() {
|
||||
statsbar = new QmlFragment({
|
||||
qml: "hifi/StatsBar.qml"
|
||||
});
|
||||
|
||||
statsButton = statsbar.addButton({
|
||||
icon: "icons/stats.svg",
|
||||
activeIcon: "icons/stats.svg",
|
||||
textSize: 45,
|
||||
bgOpacity: 0.0,
|
||||
activeBgOpacity: 0.0,
|
||||
bgColor: "#FFFFFF",
|
||||
text: "STATS"
|
||||
});
|
||||
statsButton.clicked.connect(function() {
|
||||
Menu.triggerOption("Stats");
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 28 KiB |
|
@ -24,8 +24,9 @@ var OVERLAY_HEIGHT = 1080;
|
|||
var OVERLAY_DATA = {
|
||||
width: OVERLAY_WIDTH,
|
||||
height: OVERLAY_HEIGHT,
|
||||
imageURL: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png",
|
||||
color: {red: 255, green: 255, blue: 255},
|
||||
imageURL: Script.resolvePath("assets/images/Overlay-Viz-blank.png"),
|
||||
emissive: true,
|
||||
drawInFront: true,
|
||||
alpha: 1
|
||||
};
|
||||
var AVATAR_MOVE_FOR_ACTIVE_DISTANCE = 0.8; // meters -- no longer away if avatar moves this far while away
|
||||
|
@ -37,7 +38,7 @@ var OVERLAY_DATA_HMD = {
|
|||
localRotation: {x: 0, y: 0, z: 0, w: 1},
|
||||
width: OVERLAY_WIDTH,
|
||||
height: OVERLAY_HEIGHT,
|
||||
url: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png",
|
||||
url: Script.resolvePath("assets/images/Overlay-Viz-blank.png"),
|
||||
color: {red: 255, green: 255, blue: 255},
|
||||
alpha: 1,
|
||||
scale: 2 * MyAvatar.sensorToWorldScale,
|
||||
|
|
|
@ -698,6 +698,9 @@
|
|||
Window.location = "hifi://BankOfHighFidelity";
|
||||
}
|
||||
break;
|
||||
case 'wallet_availableUpdatesReceived':
|
||||
// NOP
|
||||
break;
|
||||
default:
|
||||
print('Unrecognized message from QML:', JSON.stringify(message));
|
||||
}
|
||||
|
|
|
@ -777,9 +777,12 @@ function findClickedEntity(event) {
|
|||
}
|
||||
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
var overlayResult = Overlays.findRayIntersection(pickRay, true, getMainTabletIDs());
|
||||
if (overlayResult.intersects) {
|
||||
return null;
|
||||
var tabletIDs = getMainTabletIDs();
|
||||
if (tabletIDs.length > 0) {
|
||||
var overlayResult = Overlays.findRayIntersection(pickRay, true, tabletIDs);
|
||||
if (overlayResult.intersects) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking
|
||||
|
@ -968,8 +971,13 @@ function mouseReleaseEvent(event) {
|
|||
|
||||
function wasTabletClicked(event) {
|
||||
var rayPick = Camera.computePickRay(event.x, event.y);
|
||||
var result = Overlays.findRayIntersection(rayPick, true, getMainTabletIDs());
|
||||
return result.intersects;
|
||||
var tabletIDs = getMainTabletIDs();
|
||||
if (tabletIDs.length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
var result = Overlays.findRayIntersection(rayPick, true, getMainTabletIDs());
|
||||
return result.intersects;
|
||||
}
|
||||
}
|
||||
|
||||
function mouseClickEvent(event) {
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
var userIsLoggedIn = false;
|
||||
var walletNeedsSetup = false;
|
||||
var marketplaceBaseURL = "https://highfidelity.com";
|
||||
var messagesWaiting = false;
|
||||
|
||||
function injectCommonCode(isDirectoryPage) {
|
||||
|
||||
|
@ -205,16 +206,22 @@
|
|||
|
||||
purchasesElement.id = "purchasesButton";
|
||||
purchasesElement.setAttribute('href', "#");
|
||||
purchasesElement.innerHTML = "My Purchases";
|
||||
purchasesElement.innerHTML = "";
|
||||
if (messagesWaiting) {
|
||||
purchasesElement.innerHTML += "<span style='width:10px;height:10px;background-color:red;border-radius:50%;display:inline-block;'></span> ";
|
||||
}
|
||||
purchasesElement.innerHTML += "My Purchases";
|
||||
// FRONTEND WEBDEV RANT: The username dropdown should REALLY not be programmed to be on the same
|
||||
// line as the search bar, overlaid on top of the search bar, floated right, and then relatively bumped up using "top:-50px".
|
||||
$('.navbar-brand').css('margin-right', '10px');
|
||||
purchasesElement.style = "height:100%;margin-top:18px;font-weight:bold;float:right;margin-right:" + (dropDownElement.offsetWidth + 30) +
|
||||
"px;position:relative;z-index:999;";
|
||||
navbarBrandElement.parentNode.insertAdjacentElement('beforeend', purchasesElement);
|
||||
$('#purchasesButton').on('click', function () {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "PURCHASES",
|
||||
referrerURL: window.location.href
|
||||
referrerURL: window.location.href,
|
||||
hasUpdates: messagesWaiting
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
@ -243,7 +250,7 @@
|
|||
});
|
||||
}
|
||||
|
||||
function buyButtonClicked(id, name, author, price, href, referrer) {
|
||||
function buyButtonClicked(id, name, author, price, href, referrer, edition) {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "CHECKOUT",
|
||||
itemId: id,
|
||||
|
@ -251,7 +258,8 @@
|
|||
itemPrice: price ? parseInt(price, 10) : 0,
|
||||
itemHref: href,
|
||||
referrer: referrer,
|
||||
itemAuthor: author
|
||||
itemAuthor: author,
|
||||
itemEdition: edition
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -319,7 +327,8 @@
|
|||
$(this).closest('.grid-item').find('.creator').find('.value').text(),
|
||||
$(this).closest('.grid-item').find('.item-cost').text(),
|
||||
$(this).attr('data-href'),
|
||||
"mainPage");
|
||||
"mainPage",
|
||||
-1);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -410,7 +419,11 @@
|
|||
}
|
||||
|
||||
var cost = $('.item-cost').text();
|
||||
if (availability !== 'available') {
|
||||
var isUpdating = window.location.href.indexOf('edition=') > -1;
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
if (isUpdating) {
|
||||
purchaseButton.html('UPDATE FOR FREE');
|
||||
} else if (availability !== 'available') {
|
||||
purchaseButton.html('UNAVAILABLE (' + availability + ')');
|
||||
} else if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) {
|
||||
purchaseButton.html('PURCHASE <span class="hifi-glyph hifi-glyph-hfc" style="filter:invert(1);background-size:20px;' +
|
||||
|
@ -418,13 +431,14 @@
|
|||
}
|
||||
|
||||
purchaseButton.on('click', function () {
|
||||
if ('available' === availability) {
|
||||
if ('available' === availability || isUpdating) {
|
||||
buyButtonClicked(window.location.pathname.split("/")[3],
|
||||
$('#top-center').find('h1').text(),
|
||||
$('#creator').find('.value').text(),
|
||||
cost,
|
||||
href,
|
||||
"itemPage");
|
||||
"itemPage",
|
||||
urlParams.get('edition'));
|
||||
}
|
||||
});
|
||||
maybeAddPurchasesButton();
|
||||
|
@ -698,6 +712,7 @@
|
|||
if (marketplaceBaseURL.indexOf('metaverse.') !== -1) {
|
||||
marketplaceBaseURL = marketplaceBaseURL.replace('metaverse.', '');
|
||||
}
|
||||
messagesWaiting = parsedJsonMessage.data.messagesWaiting;
|
||||
injectCode();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -307,6 +307,10 @@ WebTablet.prototype.setScriptURL = function (scriptURL) {
|
|||
Overlays.editOverlay(this.webOverlayID, { scriptURL: scriptURL });
|
||||
};
|
||||
|
||||
WebTablet.prototype.getOverlayObject = function () {
|
||||
return Overlays.getOverlayObject(this.webOverlayID);
|
||||
};
|
||||
|
||||
WebTablet.prototype.setWidth = function (width) {
|
||||
// imported from libraries/utils.js
|
||||
resizeTablet(width);
|
||||
|
|
|
@ -87,13 +87,24 @@ var selectionDisplay = null; // for gridTool.js to ignore
|
|||
}
|
||||
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
var NORMAL_ICON = "icons/tablet-icons/market-i.svg";
|
||||
var NORMAL_ACTIVE = "icons/tablet-icons/market-a.svg";
|
||||
var WAITING_ICON = "icons/tablet-icons/market-i-msg.svg";
|
||||
var WAITING_ACTIVE = "icons/tablet-icons/market-a-msg.svg";
|
||||
var marketplaceButton = tablet.addButton({
|
||||
icon: "icons/tablet-icons/market-i.svg",
|
||||
activeIcon: "icons/tablet-icons/market-a.svg",
|
||||
icon: NORMAL_ICON,
|
||||
activeIcon: NORMAL_ACTIVE,
|
||||
text: "MARKET",
|
||||
sortOrder: 9
|
||||
});
|
||||
|
||||
function messagesWaiting(isWaiting) {
|
||||
marketplaceButton.editProperties({
|
||||
icon: (isWaiting ? WAITING_ICON : NORMAL_ICON),
|
||||
activeIcon: (isWaiting ? WAITING_ACTIVE : NORMAL_ACTIVE)
|
||||
});
|
||||
}
|
||||
|
||||
function onCanWriteAssetsChanged() {
|
||||
var message = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets();
|
||||
tablet.emitScriptEvent(message);
|
||||
|
@ -198,6 +209,7 @@ var selectionDisplay = null; // for gridTool.js to ignore
|
|||
}
|
||||
}
|
||||
|
||||
var userHasUpdates = false;
|
||||
function sendCommerceSettings() {
|
||||
tablet.emitScriptEvent(JSON.stringify({
|
||||
type: "marketplaces",
|
||||
|
@ -206,7 +218,8 @@ var selectionDisplay = null; // for gridTool.js to ignore
|
|||
commerceMode: Settings.getValue("commerce", true),
|
||||
userIsLoggedIn: Account.loggedIn,
|
||||
walletNeedsSetup: Wallet.walletStatus === 1,
|
||||
metaverseServerURL: Account.metaverseServerURL
|
||||
metaverseServerURL: Account.metaverseServerURL,
|
||||
messagesWaiting: userHasUpdates
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
@ -583,6 +596,10 @@ var selectionDisplay = null; // for gridTool.js to ignore
|
|||
case 'purchases_goToMarketplaceClicked':
|
||||
tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
|
||||
break;
|
||||
case 'updateItemClicked':
|
||||
tablet.gotoWebScreen(message.upgradeUrl + "?edition=" + message.itemEdition,
|
||||
MARKETPLACES_INJECT_SCRIPT_URL);
|
||||
break;
|
||||
case 'passphrasePopup_cancelClicked':
|
||||
case 'needsLogIn_cancelClicked':
|
||||
tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
|
||||
|
@ -637,6 +654,11 @@ var selectionDisplay = null; // for gridTool.js to ignore
|
|||
case 'sendMoney_sendPublicly':
|
||||
// NOP
|
||||
break;
|
||||
case 'wallet_availableUpdatesReceived':
|
||||
case 'purchases_availableUpdatesReceived':
|
||||
userHasUpdates = message.numUpdates > 0;
|
||||
messagesWaiting(userHasUpdates);
|
||||
break;
|
||||
default:
|
||||
print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message));
|
||||
}
|
||||
|
|