Merge branch 'master' of http://github.com/highfidelity/hifi into fade

This commit is contained in:
Olivier Prat 2018-03-28 11:21:07 -04:00
commit 7fa5e384b9
231 changed files with 3704 additions and 7813 deletions

View file

@ -1,4 +1,4 @@
# If we're running under the gradle build, HIFI_ANDROID will be set here, but
# If we're running under the gradle build, HIFI_ANDROID will be set here, but
# ANDROID will not be set until after the `project` statement. This is the *ONLY*
# place you need to use `HIFI_ANDROID` instead of `ANDROID`
if (WIN32 AND NOT HIFI_ANDROID)
@ -14,8 +14,12 @@ include("cmake/init.cmake")
include("cmake/compiler.cmake")
if (BUILD_SCRIBE_ONLY)
add_subdirectory(tools/scribe)
return()
add_subdirectory(tools/scribe)
return()
endif()
if (NOT DEFINED CLIENT_ONLY)
set(CLIENT_ONLY 0)
endif()
if (NOT DEFINED SERVER_ONLY)
@ -23,80 +27,93 @@ if (NOT DEFINED SERVER_ONLY)
endif()
if (ANDROID OR UWP)
set(MOBILE 1)
set(MOBILE 1)
else()
set(MOBILE 0)
set(MOBILE 0)
endif()
set(BUILD_CLIENT_OPTION ON)
set(BUILD_SERVER_OPTION ON)
set(BUILD_TESTS_OPTION ON)
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)
option(BUILD_SERVER "Build server components" OFF)
option(BUILD_TOOLS "Build tools" OFF)
option(BUILD_INSTALLER "Build installer" OFF)
else()
option(BUILD_SERVER "Build server components" ON)
option(BUILD_TOOLS "Build tools" ON)
option(BUILD_INSTALLER "Build installer" ON)
set(BUILD_SERVER_OPTION OFF)
set(BUILD_TOOLS_OPTION OFF)
set(BUILD_INSTALLER OFF)
endif()
if (CLIENT_ONLY)
set(BUILD_SERVER_OPTION OFF)
endif()
if (SERVER_ONLY)
option(BUILD_CLIENT "Build client components" OFF)
option(BUILD_TESTS "Build tests" OFF)
else()
option(BUILD_CLIENT "Build client components" ON)
option(BUILD_TESTS "Build tests" ON)
set(BUILD_CLIENT_OPTION OFF)
set(BUILD_TESTS_OPTION OFF)
endif()
if (ANDROID)
option(USE_GLES "Use OpenGL ES" ON)
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
set(GLES_OPTION ON)
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
else ()
option(USE_GLES "Use OpenGL ES" OFF)
set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets)
set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets)
endif ()
if (USE_GLES AND (NOT ANDROID))
option(DISABLE_QML "Disable QML" ON)
else()
option(DISABLE_QML "Disable QML" OFF)
set(DISABLE_QML_OPTION ON)
endif()
option(BUILD_CLIENT "Build client components" ${BUILD_CLIENT_OPTION})
option(BUILD_SERVER "Build server components" ${BUILD_SERVER_OPTION})
option(BUILD_TESTS "Build tests" ${BUILD_TESTS_OPTION})
option(BUILD_TOOLS "Build tools" ${BUILD_TOOLS_OPTION})
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)
if (USE_GLES)
add_definitions(-DUSE_GLES)
set(PLATFORM_GL_BACKEND gpu-gles)
set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gles)
else()
set(PLATFORM_GL_BACKEND gpu-gl)
set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gl)
endif()
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!")
add_definitions(-DDISABLE_QML)
MESSAGE(STATUS "QML disabled!")
add_definitions(-DDISABLE_QML)
endif()
if (DISABLE_KTX_CACHE)
MESSAGE(STATUS "KTX cache disabled!")
add_definitions(-DDISABLE_KTX_CACHE)
MESSAGE(STATUS "KTX cache disabled!")
add_definitions(-DDISABLE_KTX_CACHE)
endif()
if (UNIX AND DEFINED ENV{HIFI_MEMORY_DEBUGGING})
MESSAGE(STATUS "Memory debugging is enabled")
MESSAGE(STATUS "Memory debugging is enabled")
endif()
#
@ -132,8 +149,8 @@ set_packaging_parameters()
# FIXME hack to work on the proper Android toolchain
if (ANDROID)
add_subdirectory(android/app)
return()
add_subdirectory(android/app)
return()
endif()
# add subdirectories for all targets
@ -148,32 +165,30 @@ if (BUILD_SERVER)
endif()
if (BUILD_CLIENT)
add_subdirectory(interface)
set_target_properties(interface PROPERTIES FOLDER "Apps")
if (ANDROID)
add_subdirectory(gvr-interface)
set_target_properties(gvr-interface PROPERTIES FOLDER "Apps")
endif()
add_subdirectory(interface)
set_target_properties(interface PROPERTIES FOLDER "Apps")
option(USE_SIXENSE "Build Interface with sixense library/plugin" OFF)
endif()
if (BUILD_CLIENT OR BUILD_SERVER)
add_subdirectory(plugins)
add_subdirectory(plugins)
endif()
# BUILD_TOOLS option will be handled inside the tools's CMakeLists.txt because 'scribe' tool is required for build anyway
add_subdirectory(tools)
if (BUILD_TESTS)
add_subdirectory(tests)
add_subdirectory(tests)
endif()
if (BUILD_INSTALLER)
if (UNIX)
install(
DIRECTORY "${CMAKE_SOURCE_DIR}/scripts"
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/interface
COMPONENT ${CLIENT_COMPONENT}
)
endif()
generate_installers()
if (UNIX)
install(
DIRECTORY "${CMAKE_SOURCE_DIR}/scripts"
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/interface
COMPONENT ${CLIENT_COMPONENT}
)
endif()
generate_installers()
endif()

View file

@ -17,6 +17,8 @@ To produce an executable installer on Windows, the following are required:
- [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.0b3
- [UAC Plug-in for Nullsoft](http://nsis.sourceforge.net/UAC_plug-in) - 0.2.4c
- [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6
- [Inetc Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0
- [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0
Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System.

View file

@ -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);

View file

@ -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;
};

View file

@ -476,6 +476,7 @@ void EntityScriptServer::clear() {
// do this here (instead of in deleter) to avoid marshalling unload signals back to this thread
_entitiesScriptEngine->unloadAllEntityScripts();
_entitiesScriptEngine->stop();
_entitiesScriptEngine->waitTillDoneRunning();
}
_entityViewer.clear();
@ -565,8 +566,15 @@ void EntityScriptServer::handleOctreePacket(QSharedPointer<ReceivedMessage> mess
void EntityScriptServer::aboutToFinish() {
shutdownScriptEngine();
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
// our entity tree is going to go away so tell that to the EntityScriptingInterface
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(nullptr);
entityScriptingInterface->setEntityTree(nullptr);
// Should always be true as they are singletons.
if (entityScriptingInterface->getPacketSender() == &_entityEditSender) {
// The packet sender is about to go away.
entityScriptingInterface->setPacketSender(nullptr);
}
DependencyManager::get<ResourceManager>()->cleanup();

View file

@ -4,7 +4,7 @@ cmake_policy(SET CMP0046 OLD)
include(ExternalProject)
set(QUAZIP_CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_PREFIX_PATH=${QT_CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_NAME_DIR:PATH=<INSTALL_DIR>/lib -DZLIB_ROOT=${ZLIB_ROOT} -DCMAKE_POSITION_INDEPENDENT_CODE=ON)
set(QUAZIP_CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_PREFIX_PATH=${QT_CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_NAME_DIR:PATH=<INSTALL_DIR>/lib -DZLIB_ROOT=${ZLIB_ROOT} -DCMAKE_POSITION_INDEPENDENT_CODE=ON)
if (APPLE)
else ()

View 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")

View file

@ -23,7 +23,7 @@ macro(GENERATE_INSTALLERS)
set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME})
set(CPACK_NSIS_PACKAGE_NAME ${_DISPLAY_NAME})
if (PR_BUILD)
set(CPACK_NSIS_COMPRESSOR "/SOLID bzip2")
set(CPACK_NSIS_COMPRESSOR "bzip2")
endif ()
set(CPACK_PACKAGE_INSTALL_DIRECTORY ${_DISPLAY_NAME})
@ -46,9 +46,35 @@ macro(GENERATE_INSTALLERS)
set(UNINSTALLER_HEADER_IMAGE "")
fix_path_for_nsis(${_UNINSTALLER_HEADER_BAD_PATH} UNINSTALLER_HEADER_IMAGE)
# grab the latest VC redist (2017) and add it to the installer, our NSIS template
# will call it during the install
install(CODE "file(DOWNLOAD https://go.microsoft.com/fwlink/?LinkId=746572 \"\${CMAKE_INSTALL_PREFIX}/vcredist_x64.exe\")")
# we use external libraries that still need the 120 (VS2013) redistributables
# so we include them as well until those external libraries are updated
# to use the redistributables that match what we build our applications for
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS
"C:/Windows/System32/msvcp120.dll"
"C:/Windows/System32/msvcr120.dll"
)
set(CMAKE_INSTALL_UCRT_LIBRARIES TRUE)
set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION ${INTERFACE_INSTALL_DIR})
set(CMAKE_INSTALL_SYSTEM_RUNTIME_COMPONENT ${CLIENT_COMPONENT})
include(InstallRequiredSystemLibraries)
if (CLIENT_ONLY OR SERVER_ONLY)
set(CPACK_MONOLITHIC_INSTALL 1)
endif ()
# setup conditional checks for server component selection depending on
# the inclusion of the server component at all
if (CLIENT_ONLY)
set(SERVER_COMPONENT_CONDITIONAL "0 == 1")
set(CLIENT_COMPONENT_CONDITIONAL "1 == 1")
elseif (SERVER_ONLY)
set(SERVER_COMPONENT_CONDITIONAL "1 == 1")
set(CLIENT_COMPONENT_CONDITIONAL "0 == 1")
else ()
set(SERVER_COMPONENT_CONDITIONAL "\\\${SectionIsSelected} \\\${${SERVER_COMPONENT}}")
set(CLIENT_COMPONENT_CONDITIONAL "\\\${SectionIsSelected} \\\${${CLIENT_COMPONENT}}")
endif ()
elseif (APPLE)
# produce a drag and drop DMG on OS X
set(CPACK_GENERATOR "DragNDrop")
@ -79,8 +105,13 @@ macro(GENERATE_INSTALLERS)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")
cpack_add_component(${CLIENT_COMPONENT} DISPLAY_NAME "High Fidelity Interface")
cpack_add_component(${SERVER_COMPONENT} DISPLAY_NAME "High Fidelity Sandbox")
if (BUILD_CLIENT)
cpack_add_component(${CLIENT_COMPONENT} DISPLAY_NAME "High Fidelity Interface")
endif ()
if (BUILD_SERVER)
cpack_add_component(${SERVER_COMPONENT} DISPLAY_NAME "High Fidelity Sandbox")
endif ()
include(CPack)
endmacro()

View file

@ -39,7 +39,9 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT)
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> \"$<TARGET_FILE:${TARGET_NAME}>\""
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND}\
${EXTRA_DEPLOY_OPTIONS} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release>\
--no-compiler-runtime --no-opengl-sw --no-angle -no-system-d3d-compiler \"$<TARGET_FILE:${TARGET_NAME}>\""
)
set(QTAUDIO_PATH "$<TARGET_FILE_DIR:${TARGET_NAME}>/audio")

View file

@ -27,6 +27,11 @@ macro(SET_PACKAGING_PARAMETERS)
message(STATUS "The BRANCH environment variable is: $ENV{BRANCH}")
message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}")
# setup component categories for installer
set(DDE_COMPONENT dde)
set(CLIENT_COMPONENT client)
set(SERVER_COMPONENT server)
if (RELEASE_TYPE STREQUAL "PRODUCTION")
set(DEPLOY_PACKAGE TRUE)
set(PRODUCTION_BUILD 1)
@ -68,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}")
@ -149,13 +159,10 @@ macro(SET_PACKAGING_PARAMETERS)
set(CLIENT_LAUNCH_NOW_REG_KEY "ClientLaunchAfterInstall")
set(SERVER_LAUNCH_NOW_REG_KEY "ServerLaunchAfterInstall")
set(CUSTOM_INSTALL_REG_KEY "CustomInstall")
set(CLIENT_ID_REG_KEY "ClientGUID")
set(GA_TRACKING_ID $ENV{GA_TRACKING_ID})
endif ()
# setup component categories for installer
set(DDE_COMPONENT dde)
set(CLIENT_COMPONENT client)
set(SERVER_COMPONENT server)
# print out some results for testing this new build feature
message(STATUS "The BUILD_GLOBAL_SERVICES variable is: ${BUILD_GLOBAL_SERVICES}")
message(STATUS "The USE_STABLE_GLOBAL_SERVICES variable is: ${USE_STABLE_GLOBAL_SERVICES}")

View file

@ -41,6 +41,10 @@ set(CONSOLE_STARTUP_REG_KEY "@CONSOLE_STARTUP_REG_KEY@")
set(SERVER_LAUNCH_NOW_REG_KEY "@SERVER_LAUNCH_NOW_REG_KEY@")
set(CLIENT_LAUNCH_NOW_REG_KEY "@CLIENT_LAUNCH_NOW_REG_KEY@")
set(CUSTOM_INSTALL_REG_KEY "@CUSTOM_INSTALL_REG_KEY@")
set(GA_TRACKING_ID "@GA_TRACKING_ID@")
set(CLIENT_ID_REG_KEY "@CLIENT_ID_REG_KEY@")
set(INSTALLER_HEADER_IMAGE "@INSTALLER_HEADER_IMAGE@")
set(UNINSTALLER_HEADER_IMAGE "@UNINSTALLER_HEADER_IMAGE@")
set(ADD_REMOVE_ICON_PATH "@ADD_REMOVE_ICON_PATH@")
set(SERVER_COMPONENT_CONDITIONAL "@SERVER_COMPONENT_CONDITIONAL@")
set(CLIENT_COMPONENT_CONDITIONAL "@CLIENT_COMPONENT_CONDITIONAL@")

View file

@ -319,6 +319,78 @@ Function DownloadFile
FunctionEnd
!endif
!include NSISpcre.nsh
!insertmacro REMatches
Var CampaignName
!macro GetCampaignName RetVar
Call GetCampaignName
Pop ${RetVar}
!macroend
Function GetCampaignName
Push $0 ; Stash $0
; Parse filename out of the path
${RECaptureMatches} $0 "([^\\]*\\)*(.*)\.exe" $EXEPATH 0
${If} $0 == 2
Pop $0 ; Discard Path
Pop $0 ; Recover filename
; Parse campaign out of the filename
${RECaptureMatches} $0 "HighFidelity-([^-]*-)Beta-.*" $0 0
${If} $0 == 1
Pop $0 ; Recover campaign name
StrCpy $0 $0 -1 0 ; Remove trailing - and copy to _RetVar
${Else}
StrCpy $0 ""
${EndIf}
${Else}
StrCpy $0 ""
${EndIf}
Exch $0 ; Restore $0 and push result
FunctionEnd
!macro CreateGUID RetVar
System::Call 'ole32::CoCreateGuid(g .s)'
Pop ${RetVar}
; Strip opening and closing braces
StrCpy ${RetVar} ${RetVar} -1 1
!macroend
Var GAClientID
!macro InitGAClientID
; Generate a new GUID on every run for now
!insertmacro CreateGUID $GAClientID
!macroend
!macro GoogleAnalytics Category Action Label Value
${If} "@GA_TRACKING_ID@" != ""
Push $0
Push $1
StrCpy $0 "https://google-analytics.com/collect?v=1&tid=@GA_TRACKING_ID@"
StrCpy $0 "$0&cid=$GAClientID&t=event&ec=${Category}&ea=${Action}"
${If} "${Label}" != ""
StrCpy $0 "$0&el=${Label}"
${EndIf}
${If} "${Value}" != ""
StrCpy $0 "$0&ev=${Value}"
${EndIf}
GetTempFileName $1
inetc::get /SILENT $0 $1 /END
Delete $1
Pop $1
Pop $0
${EndIf}
!macroend
;--------------------------------
; Installation types
@ -342,28 +414,38 @@ SectionEnd
;--------------------------------
;Pages
!define MUI_CUSTOMFUNCTION_ABORT OnUserAbort
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageWelcomePre
!insertmacro MUI_PAGE_WELCOME
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageLicensePre
!insertmacro MUI_PAGE_LICENSE "@CPACK_RESOURCE_FILE_LICENSE@"
Page custom InstallTypesPage ReadInstallTypes
!define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageDirectoryPre
!insertmacro MUI_PAGE_DIRECTORY
;Start Menu Folder Page Configuration
!define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKLM"
!define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@"
!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
!define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageStartMenuPre
!insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
!define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageComponentsPre
@CPACK_NSIS_PAGE_COMPONENTS@
; the MUI_PAGE_CUSTOMFUNCTION_PRE shouldn't be defined here
; which can happen for a component-less (like client only) install
!ifdef MUI_PAGE_CUSTOMFUNCTION_PRE
!undef MUI_PAGE_CUSTOMFUNCTION_PRE
!endif
Page custom PostInstallOptionsPage ReadPostInstallOptions
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageInstallFilesPre
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
@ -452,8 +534,40 @@ Var CopyFromProductionCheckbox
Var ExpressInstallRadioButton
Var CustomInstallRadioButton
Var InstallTypeDialog
Var Express
Var CustomInstallTemporaryState
Var Express
!macro MaybeSkipPage
; Check if Express is set, if so, abort the post install options page
${If} $Express == "1"
Abort
${EndIf}
!macroend
Function OnUserAbort
!insertmacro GoogleAnalytics "Installer" "Abort" "User Abort" ""
FunctionEnd
Function PageWelcomePre
!insertmacro GoogleAnalytics "Installer" "Welcome" "" ""
FunctionEnd
Function PageLicensePre
!insertmacro GoogleAnalytics "Installer" "License" "" ""
FunctionEnd
Function PageDirectoryPre
!insertmacro MaybeSkipPage
!insertmacro GoogleAnalytics "Installer" "Directory" "" ""
FunctionEnd
Function PageStartMenuPre
!insertmacro MaybeSkipPage
!insertmacro GoogleAnalytics "Installer" "StartMenu" "" ""
FunctionEnd
Function PageComponentsPre
!insertmacro MaybeSkipPage
!insertmacro GoogleAnalytics "Installer" "Components" "" ""
FunctionEnd
Function PageInstallFilesPre
!insertmacro GoogleAnalytics "Installer" "Install" "" ""
FunctionEnd
!macro SetInstallOption Checkbox OptionName Default
; reads the value for the given install option to the registry
@ -472,6 +586,8 @@ Var CustomInstallTemporaryState
!macroend
Function InstallTypesPage
!insertmacro GoogleAnalytics "Installer" "Install Types" "" ""
!insertmacro MUI_HEADER_TEXT "Choose Installation Type" "Express or Custom Install"
nsDialogs::Create 1018
@ -502,10 +618,11 @@ Function InstallTypesPage
${If} $CustomInstallTemporaryState == ${BST_UNCHECKED}
${NSD_Check} $ExpressInstallRadioButton
Call ChangeExpressLabel
${Else}
Call ChangeCustomLabel
${EndIf}
Call ChangeExpressLabel
nsDialogs::Show
FunctionEnd
@ -523,14 +640,10 @@ Function ChangeCustomLabel
Pop $R1
FunctionEnd
Function AbortFunction
; Check if Express is set, if so, abort the post install options page
StrCmp $Express "1" 0 end
Abort
end:
FunctionEnd
Function PostInstallOptionsPage
!insertmacro MaybeSkipPage
!insertmacro GoogleAnalytics "Installer" "Post Install Options" "" ""
!insertmacro MUI_HEADER_TEXT "Setup Options" ""
nsDialogs::Create 1018
@ -540,15 +653,10 @@ Function PostInstallOptionsPage
Abort
${EndIf}
; Check if Express is set, if so, abort the post install options page
StrCmp $Express "1" 0 end
Abort
end:
StrCpy $CurrentOffset 0
StrCpy $OffsetUnits u
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
${If} @CLIENT_COMPONENT_CONDITIONAL@
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @INTERFACE_HF_SHORTCUT_NAME@"
Pop $DesktopClientCheckbox
IntOp $CurrentOffset $CurrentOffset + 15
@ -557,7 +665,7 @@ Function PostInstallOptionsPage
!insertmacro SetInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED}
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
${If} @SERVER_COMPONENT_CONDITIONAL@
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @CONSOLE_HF_SHORTCUT_NAME@"
Pop $DesktopServerCheckbox
IntOp $CurrentOffset $CurrentOffset + 15
@ -566,7 +674,7 @@ Function PostInstallOptionsPage
!insertmacro SetInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED}
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
${If} @SERVER_COMPONENT_CONDITIONAL@
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ after install"
Pop $LaunchServerNowCheckbox
@ -580,7 +688,7 @@ Function PostInstallOptionsPage
IntOp $CurrentOffset $CurrentOffset + 15
${EndIf}
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
${If} @CLIENT_COMPONENT_CONDITIONAL@
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @INTERFACE_HF_SHORTCUT_NAME@ after install"
Pop $LaunchClientNowCheckbox
IntOp $CurrentOffset $CurrentOffset + 30
@ -593,7 +701,7 @@ Function PostInstallOptionsPage
${EndIf}
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
${If} @SERVER_COMPONENT_CONDITIONAL@
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ on startup"
Pop $ServerStartupCheckbox
IntOp $CurrentOffset $CurrentOffset + 15
@ -602,7 +710,7 @@ Function PostInstallOptionsPage
!insertmacro SetInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED}
${EndIf}
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
${If} @SERVER_COMPONENT_CONDITIONAL@
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)"
Pop $CleanInstallCheckbox
IntOp $CurrentOffset $CurrentOffset + 15
@ -610,11 +718,11 @@ Function PostInstallOptionsPage
${If} @PR_BUILD@ == 1
; a PR build defaults all install options expect LaunchServerNowCheckbox, LaunchClientNowCheckbox and the settings copy to unchecked
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
${If} @CLIENT_COMPONENT_CONDITIONAL@
${NSD_SetState} $DesktopClientCheckbox ${BST_UNCHECKED}
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
${If} @SERVER_COMPONENT_CONDITIONAL@
${NSD_SetState} $DesktopServerCheckbox ${BST_UNCHECKED}
${NSD_SetState} $ServerStartupCheckbox ${BST_UNCHECKED}
${EndIf}
@ -673,12 +781,12 @@ Function ReadInstallTypes
FunctionEnd
Function ReadPostInstallOptions
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
${If} @CLIENT_COMPONENT_CONDITIONAL@
; check if the user asked for a desktop shortcut to High Fidelity
${NSD_GetState} $DesktopClientCheckbox $DesktopClientState
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
${If} @SERVER_COMPONENT_CONDITIONAL@
; check if the user asked for a desktop shortcut to Sandbox
${NSD_GetState} $DesktopServerCheckbox $DesktopServerState
@ -691,24 +799,24 @@ Function ReadPostInstallOptions
${NSD_GetState} $CopyFromProductionCheckbox $CopyFromProductionState
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
${If} @SERVER_COMPONENT_CONDITIONAL@
; check if we need to launch the server post-install
${NSD_GetState} $LaunchServerNowCheckbox $LaunchServerNowState
${EndIf}
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
${If} @CLIENT_COMPONENT_CONDITIONAL@
; check if we need to launch the client post-install
${NSD_GetState} $LaunchClientNowCheckbox $LaunchClientNowState
${EndIf}
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
${If} @CLIENT_COMPONENT_CONDITIONAL@
; check if the user asked for a clean install
${NSD_GetState} $CleanInstallCheckbox $CleanInstallState
${EndIf}
FunctionEnd
Function HandlePostInstallOptions
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
${If} @CLIENT_COMPONENT_CONDITIONAL@
; check if the user asked for a desktop shortcut to High Fidelity
${If} $DesktopClientState == ${BST_CHECKED}
CreateShortCut "$DESKTOP\@INTERFACE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"
@ -719,7 +827,7 @@ Function HandlePostInstallOptions
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
${If} @SERVER_COMPONENT_CONDITIONAL@
; check if the user asked for a desktop shortcut to Sandbox
${If} $DesktopServerState == ${BST_CHECKED}
CreateShortCut "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"
@ -748,7 +856,7 @@ Function HandlePostInstallOptions
${EndIf}
${EndIf}
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
${If} @CLIENT_COMPONENT_CONDITIONAL@
; check if the user asked for a clean install
${If} $CleanInstallState == ${BST_CHECKED}
SetShellVarContext current
@ -785,7 +893,8 @@ Function HandlePostInstallOptions
${EndIf}
${EndIf}
${If} $LaunchServerNowState == ${BST_CHECKED}
${If} @SERVER_COMPONENT_CONDITIONAL@
${AndIf} $LaunchServerNowState == ${BST_CHECKED}
!insertmacro WriteInstallOption @SERVER_LAUNCH_NOW_REG_KEY@ YES
; both launches use the explorer trick in case the user has elevated permissions for the installer
@ -799,7 +908,7 @@ Function HandlePostInstallOptions
Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"'
${EndIf}
${Else}
${ElseIf} @CLIENT_COMPONENT_CONDITIONAL@
!insertmacro WriteInstallOption @SERVER_LAUNCH_NOW_REG_KEY@ NO
; launch uses the explorer trick in case the user has elevated permissions for the installer
@ -837,9 +946,6 @@ Section "-Core installation"
Delete "$INSTDIR\ui_resources_200_percent.pak"
Delete "$INSTDIR\vccorlib120.dll"
Delete "$INSTDIR\version"
Delete "$INSTDIR\msvcr140.dll"
Delete "$INSTDIR\msvcp140.dll"
Delete "$INSTDIR\vcruntime140.dll"
Delete "$INSTDIR\xinput1_3.dll"
; Delete old desktop shortcuts before they were renamed during Sandbox rename
@ -858,11 +964,8 @@ Section "-Core installation"
; Rename the incorrectly cased Raleway font
Rename "$INSTDIR\resources\qml\styles-uit\RalewaySemibold.qml" "$INSTDIR\resources\qml\styles-uit\RalewaySemiBold.qml"
ExecWait "$INSTDIR\vcredist_x64.exe /install /q /norestart"
; Remove the Old Interface directory and vcredist_x64.exe (from installs prior to Server Console)
RMDir /r "$INSTDIR\Interface"
Delete "$INSTDIR\vcredist_x64.exe"
;Use the entire tree produced by the INSTALL target. Keep the
;list of directories here in sync with the RMDir commands below.
@ -931,7 +1034,7 @@ Section "-Core installation"
@CPACK_NSIS_CREATE_ICONS_EXTRA@
; Conditional handling for Interface specific options
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
${If} @CLIENT_COMPONENT_CONDITIONAL@
CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@INTERFACE_SHORTCUT_NAME@.lnk" \
"$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"
@ -946,7 +1049,7 @@ Section "-Core installation"
${EndIf}
; Conditional handling for server console shortcut
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
${If} @SERVER_COMPONENT_CONDITIONAL@
CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" \
"$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"
${EndIf}
@ -965,6 +1068,7 @@ Section "-Core installation"
; Handle whichever post install options were set
Call HandlePostInstallOptions
!insertmacro GoogleAnalytics "Installer" "Done" "" ""
SectionEnd
!include nsProcess.nsh
@ -979,7 +1083,7 @@ SectionEnd
${If} $R0 == 0
; the process is running, ask the user to close it
${If} "${displayName}" == "@CONSOLE_DISPLAY_NAME@"
MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION \
"${displayName} cannot be ${action} while ${displayName} is running.$\r$\nPlease close it in the system tray and click Retry to continue." \
@ -992,6 +1096,8 @@ SectionEnd
/SD IDCANCEL IDRETRY Prompt_${UniqueID} IDCANCEL 0
${EndIf}
!insertmacro GoogleAnalytics "Installer" "Abort" "${displayName} Running" ""
; If the user decided to cancel, stop the current installer/uninstaller
Abort
@ -1087,8 +1193,8 @@ Function .onSelChange
!insertmacro SectionList MaybeSelectionChanged
; if neither component is selected, disable the install button
${IfNot} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
${AndIfNot} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
${IfNot} @CLIENT_COMPONENT_CONDITIONAL@
${AndIfNot} @SERVER_COMPONENT_CONDITIONAL@
GetDlgItem $0 $HWNDPARENT 1
EnableWindow $0 0
${Else}
@ -1219,6 +1325,11 @@ Function .onInit
Quit
!endif
!insertmacro InitGAClientID
!insertmacro GetCampaignName $CampaignName
!insertmacro GoogleAnalytics "Installer" "Start" "$CampaignName" ""
; make sure none of the installed applications are still running
!insertmacro CheckForRunningApplications "installed" "Installer"
${nsProcess::Unload}

View file

@ -132,6 +132,41 @@ $(document).ready(function(){
var ACTIVE_BACKUP_ROW_CLASS = 'active-backup';
var CORRUPTED_ROW_CLASS = 'danger';
$('body').on('click', '.' + BACKUP_DOWNLOAD_LINK_CLASS, function(ev) {
ev.preventDefault();
var backupID = $(this).data('backup-id');
showSpinnerAlert("Preparing backup...");
function checkBackupStatus() {
$.ajax({
url: "/api/backups/" + backupID,
dataType: 'json',
success: function(data) {
if (data.complete) {
if (data.error == '') {
location.href = "/api/backups/download/" + backupID;
swal.close();
} else {
showErrorMessage(
"Error",
"There was an error preparing your backup. Please refresh the page and try again."
);
}
} else {
setTimeout(checkBackupStatus, 500);
}
},
error: function() {
showErrorMessage(
"Error",
"There was an error preparing your backup."
);
},
});
}
checkBackupStatus();
});
function reloadBackupInformation() {
// make a GET request to get backup information to populate the table
$.ajax({
@ -164,7 +199,7 @@ $(document).ready(function(){
+ "<div class='dropdown'><div class='dropdown-toggle' data-toggle='dropdown' aria-expanded='false'><span class='glyphicon glyphicon-option-vertical'></span></div>"
+ "<ul class='dropdown-menu dropdown-menu-right'>"
+ "<li><a class='" + BACKUP_RESTORE_LINK_CLASS + "' href='#'>Restore from here</a></li><li class='divider'></li>"
+ "<li><a class='" + BACKUP_DOWNLOAD_LINK_CLASS + "' href='/api/backups/" + backup.id + "'>Download</a></li><li class='divider'></li>"
+ "<li><a class='" + BACKUP_DOWNLOAD_LINK_CLASS + "' data-backup-id='" + backup.id + "' href='#'>Download</a></li><li class='divider'></li>"
+ "<li><a class='" + BACKUP_DELETE_LINK_CLASS + "' href='#' target='_blank'>Delete</a></li></ul></div></td>";
}

View file

@ -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',

View file

@ -55,15 +55,20 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire
const QVariantList& backupRules,
std::chrono::milliseconds persistInterval,
bool debugTimestampNow) :
_consolidatedBackupDirectory(PathUtils::generateTemporaryDir()),
_backupDirectory(backupDirectory), _persistInterval(persistInterval), _lastCheck(p_high_resolution_clock::now())
{
setObjectName("DomainContentBackupManager");
// Make sure the backup directory exists.
QDir(_backupDirectory).mkpath(".");
parseBackupRules(backupRules);
constexpr int CONSOLIDATED_BACKUP_CLEANER_INTERVAL_MSECS = 30 * 1000;
_consolidatedBackupCleanupTimer.setInterval(CONSOLIDATED_BACKUP_CLEANER_INTERVAL_MSECS);
connect(&_consolidatedBackupCleanupTimer, &QTimer::timeout, this, &DomainContentBackupManager::removeOldConsolidatedBackups);
_consolidatedBackupCleanupTimer.start();
}
void DomainContentBackupManager::parseBackupRules(const QVariantList& backupRules) {
@ -498,23 +503,87 @@ void DomainContentBackupManager::backup() {
}
}
void DomainContentBackupManager::consolidateBackup(MiniPromise::Promise promise, QString fileName) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "consolidateBackup", Q_ARG(MiniPromise::Promise, promise),
Q_ARG(QString, fileName));
return;
void DomainContentBackupManager::removeOldConsolidatedBackups() {
constexpr std::chrono::minutes MAX_TIME_TO_KEEP_CONSOLIDATED_BACKUP { 30 };
auto now = std::chrono::system_clock::now();
auto it = _consolidatedBackups.begin();
while (it != _consolidatedBackups.end()) {
auto& backup = it->second;
auto diff = now - backup.createdAt;
if (diff > MAX_TIME_TO_KEEP_CONSOLIDATED_BACKUP) {
QFile oldBackup(backup.absoluteFilePath);
if (!oldBackup.exists() || oldBackup.remove()) {
qDebug() << "Removed old consolidated backup: " << backup.absoluteFilePath;
it = _consolidatedBackups.erase(it);
} else {
qDebug() << "Failed to remove old consolidated backup: " << backup.absoluteFilePath;
it++;
}
} else {
it++;
}
}
}
ConsolidatedBackupInfo DomainContentBackupManager::consolidateBackup(QString fileName) {
{
std::lock_guard<std::mutex> lock { _consolidatedBackupsMutex };
auto it = _consolidatedBackups.find(fileName);
if (it != _consolidatedBackups.end()) {
return it->second;
}
}
QMetaObject::invokeMethod(this, "consolidateBackupInternal", Q_ARG(QString, fileName));
return {
ConsolidatedBackupInfo::CONSOLIDATING,
"",
"",
std::chrono::system_clock::now()
};
}
void DomainContentBackupManager::consolidateBackupInternal(QString fileName) {
auto markFailure = [this, &fileName](QString error) {
qWarning() << "Failed to consolidate backup:" << fileName << error;
{
std::lock_guard<std::mutex> lock { _consolidatedBackupsMutex };
auto& consolidatedBackup = _consolidatedBackups[fileName];
consolidatedBackup.state = ConsolidatedBackupInfo::COMPLETE_WITH_ERROR;
consolidatedBackup.error = error;
}
};
{
std::lock_guard<std::mutex> lock { _consolidatedBackupsMutex };
auto it = _consolidatedBackups.find(fileName);
if (it != _consolidatedBackups.end()) {
return;
}
_consolidatedBackups[fileName] = {
ConsolidatedBackupInfo::CONSOLIDATING,
"",
"",
std::chrono::system_clock::now()
};
}
QDir backupDir { _backupDirectory };
if (!backupDir.exists()) {
qCritical() << "Backup directory does not exist, bailing consolidation of backup";
promise->resolve({ { "success", false } });
markFailure("Backup directory does not exist, bailing consolidation of backup");
return;
}
auto filePath = backupDir.absoluteFilePath(fileName);
if (!QFile::exists(filePath)) {
markFailure("Backup does not exist");
return;
}
auto copyFilePath = QDir::tempPath() + "/" + fileName;
auto copyFilePath = _consolidatedBackupDirectory + "/" + fileName;
{
QFile copyFile(copyFilePath);
@ -523,8 +592,7 @@ void DomainContentBackupManager::consolidateBackup(MiniPromise::Promise promise,
}
auto copySuccess = QFile::copy(filePath, copyFilePath);
if (!copySuccess) {
qCritical() << "Failed to create copy of backup.";
promise->resolve({ { "success", false } });
markFailure("Failed to create copy of backup.");
return;
}
@ -532,7 +600,7 @@ void DomainContentBackupManager::consolidateBackup(MiniPromise::Promise promise,
if (!zip.open(QuaZip::mdAdd)) {
qCritical() << "Could not open backup archive:" << filePath;
qCritical() << " ERROR:" << zip.getZipError();
promise->resolve({ { "success", false } });
markFailure("Could not open backup archive");
return;
}
@ -544,14 +612,17 @@ void DomainContentBackupManager::consolidateBackup(MiniPromise::Promise promise,
if (zip.getZipError() != UNZ_OK) {
qCritical() << "Failed to consolidate backup: " << zip.getZipError();
promise->resolve({ { "success", false } });
markFailure("Failed to consolidate backup");
return;
}
promise->resolve({
{ "success", true },
{ "backupFilePath", copyFilePath }
});
{
std::lock_guard<std::mutex> lock { _consolidatedBackupsMutex };
auto& consolidatedBackup = _consolidatedBackups[fileName];
consolidatedBackup.state = ConsolidatedBackupInfo::COMPLETE_WITH_SUCCESS;
consolidatedBackup.absoluteFilePath = copyFilePath;
}
}
void DomainContentBackupManager::createManualBackup(MiniPromise::Promise promise, const QString& name) {

View file

@ -15,9 +15,15 @@
#ifndef hifi_DomainContentBackupManager_h
#define hifi_DomainContentBackupManager_h
#include <RegisteredMetaTypes.h>
#include <QString>
#include <QVector>
#include <QDateTime>
#include <QTimer>
#include <mutex>
#include <unordered_map>
#include <GenericThread.h>
@ -38,6 +44,18 @@ struct BackupItemInfo {
bool isManualBackup;
};
struct ConsolidatedBackupInfo {
enum State {
CONSOLIDATING,
COMPLETE_WITH_ERROR,
COMPLETE_WITH_SUCCESS
};
State state;
QString error;
QString absoluteFilePath;
std::chrono::system_clock::time_point createdAt;
};
class DomainContentBackupManager : public GenericThread {
Q_OBJECT
public:
@ -61,6 +79,7 @@ public:
void addBackupHandler(BackupHandlerPointer handler);
void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist
void replaceData(QByteArray data);
ConsolidatedBackupInfo consolidateBackup(QString fileName);
public slots:
void getAllBackupsAndStatus(MiniPromise::Promise promise);
@ -68,7 +87,6 @@ public slots:
void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName);
void recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup);
void deleteBackup(MiniPromise::Promise promise, const QString& backupName);
void consolidateBackup(MiniPromise::Promise promise, QString fileName);
signals:
void loadCompleted();
@ -91,11 +109,21 @@ protected:
bool recoverFromBackupZip(const QString& backupName, QuaZip& backupZip);
private slots:
void removeOldConsolidatedBackups();
void consolidateBackupInternal(QString fileName);
private:
QTimer _consolidatedBackupCleanupTimer;
const QString _consolidatedBackupDirectory;
const QString _backupDirectory;
std::vector<BackupHandlerPointer> _backupHandlers;
std::chrono::milliseconds _persistInterval { 0 };
std::mutex _consolidatedBackupsMutex;
std::unordered_map<QString, ConsolidatedBackupInfo> _consolidatedBackups;
std::atomic<bool> _isRecovering { false };
QString _recoveryFilename { };

View file

@ -20,6 +20,7 @@
#include <QJsonArray>
#include <QProcess>
#include <QSharedMemory>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QTimer>
#include <QUrlQuery>
@ -163,6 +164,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_iceServerAddr(ICE_SERVER_DEFAULT_HOSTNAME),
_iceServerPort(ICE_SERVER_DEFAULT_PORT)
{
PathUtils::removeTemporaryApplicationDirs();
parseCommandLine();
DependencyManager::set<tracing::Tracer>();
@ -727,7 +730,7 @@ void DomainServer::setupNodeListAndAssignments() {
packetReceiver.registerListener(PacketType::OctreeDataPersist, this, "processOctreeDataPersistMessage");
packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacementRequest");
packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURLRequest");
packetReceiver.registerListener(PacketType::DomainContentReplacementFromUrl, this, "handleDomainContentReplacementFromURLRequest");
// set a custom packetVersionMatch as the verify packet operator for the udt::Socket
nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified);
@ -736,7 +739,6 @@ void DomainServer::setupNodeListAndAssignments() {
auto assetClient = DependencyManager::set<AssetClient>();
assetClient->moveToThread(&_assetClientThread);
_assetClientThread.start();
// add whatever static assignments that have been parsed to the queue
addStaticAssignmentsToQueue();
}
@ -1933,6 +1935,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
const QString URI_API_DOMAINS_ID = "/api/domains/";
const QString URI_API_BACKUPS = "/api/backups";
const QString URI_API_BACKUPS_ID = "/api/backups/";
const QString URI_API_BACKUPS_DOWNLOAD_ID = "/api/backups/download/";
const QString URI_API_BACKUPS_RECOVER = "/api/backups/recover/";
const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
@ -2133,30 +2136,40 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
});
_contentManager->getAllBackupsAndStatus(deferred);
return true;
} else if (url.path().startsWith(URI_API_BACKUPS_DOWNLOAD_ID)) {
auto id = url.path().mid(QString(URI_API_BACKUPS_DOWNLOAD_ID).length());
auto info = _contentManager->consolidateBackup(id);
if (info.state == ConsolidatedBackupInfo::COMPLETE_WITH_SUCCESS) {
auto file { std::unique_ptr<QFile>(new QFile(info.absoluteFilePath)) };
if (file->open(QIODevice::ReadOnly)) {
constexpr const char* CONTENT_TYPE_ZIP = "application/zip";
auto downloadedFilename = id;
downloadedFilename.replace(QRegularExpression(".zip$"), ".content.zip");
auto contentDisposition = "attachment; filename=\"" + downloadedFilename + "\"";
connectionPtr->respond(HTTPConnection::StatusCode200, std::move(file), CONTENT_TYPE_ZIP, {
{ "Content-Disposition", contentDisposition.toUtf8() }
});
} else {
qCritical(domain_server) << "Unable to load consolidated backup at:" << info.absoluteFilePath;
connectionPtr->respond(HTTPConnection::StatusCode500, "Error opening backup");
}
} else if (info.state == ConsolidatedBackupInfo::COMPLETE_WITH_ERROR) {
connectionPtr->respond(HTTPConnection::StatusCode500, ("Error creating backup: " + info.error).toUtf8());
} else {
connectionPtr->respond(HTTPConnection::StatusCode400, "Backup unavailable");
}
return true;
} else if (url.path().startsWith(URI_API_BACKUPS_ID)) {
auto id = url.path().mid(QString(URI_API_BACKUPS_ID).length());
auto deferred = makePromise("consolidateBackup");
deferred->then([connectionPtr, JSON_MIME_TYPE](QString error, QVariantMap result) {
if (!connectionPtr) {
return;
}
auto info = _contentManager->consolidateBackup(id);
QJsonObject rootJSON;
auto success = result["success"].toBool();
if (success) {
auto path = result["backupFilePath"].toString();
auto file { std::unique_ptr<QFile>(new QFile(path)) };
if (file->open(QIODevice::ReadOnly)) {
connectionPtr->respond(HTTPConnection::StatusCode200, std::move(file));
} else {
qCritical(domain_server) << "Unable to load consolidated backup at:" << path << result;
connectionPtr->respond(HTTPConnection::StatusCode500, "Error opening backup");
}
} else {
connectionPtr->respond(HTTPConnection::StatusCode400);
}
});
_contentManager->consolidateBackup(deferred, id);
QJsonObject rootJSON {
{ "complete", info.state == ConsolidatedBackupInfo::COMPLETE_WITH_SUCCESS },
{ "error", info.error }
};
QJsonDocument docJSON { rootJSON };
connectionPtr->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8());
return true;
} else if (url.path() == URI_RESTART) {
@ -2209,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;
@ -2221,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
@ -2619,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);
@ -2664,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);
@ -2695,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(' ');
@ -3429,13 +3442,10 @@ void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) {
}
}
void DomainServer::handleOctreeFileReplacementFromURLRequest(QSharedPointer<ReceivedMessage> message) {
void DomainServer::handleDomainContentReplacementFromURLRequest(QSharedPointer<ReceivedMessage> message) {
qInfo() << "Received request to replace content from a url";
auto node = DependencyManager::get<LimitedNodeList>()->findNodeWithAddr(message->getSenderSockAddr());
if (node) {
qDebug() << "Found node: " << node->getCanReplaceContent();
}
if (node->getCanReplaceContent()) {
if (node && node->getCanReplaceContent()) {
// Convert message data into our URL
QString url(message->getMessage());
QUrl modelsURL = QUrl(url, QUrl::StrictMode);
@ -3448,7 +3458,12 @@ void DomainServer::handleOctreeFileReplacementFromURLRequest(QSharedPointer<Rece
connect(reply, &QNetworkReply::finished, [this, reply, modelsURL]() {
QNetworkReply::NetworkError networkError = reply->error();
if (networkError == QNetworkReply::NoError) {
handleOctreeFileReplacement(reply->readAll());
if (modelsURL.fileName().endsWith(".json.gz")) {
handleOctreeFileReplacement(reply->readAll());
} else if (modelsURL.fileName().endsWith(".zip")) {
auto deferred = makePromise("recoverFromUploadedBackup");
_contentManager->recoverFromUploadedBackup(deferred, reply->readAll());
}
} else {
qDebug() << "Error downloading JSON from specified file: " << modelsURL;
}
@ -3456,12 +3471,9 @@ void DomainServer::handleOctreeFileReplacementFromURLRequest(QSharedPointer<Rece
}
}
void DomainServer::handleOctreeFileReplacementRequest(QSharedPointer<ReceivedMessage> message) {
auto node = DependencyManager::get<NodeList>()->nodeWithUUID(message->getSourceID());
if (node->getCanReplaceContent()) {
handleOctreeFileReplacement(message->readAll());
}
}
}

View file

@ -91,7 +91,7 @@ private slots:
void processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message);
void processICEServerHeartbeatACK(QSharedPointer<ReceivedMessage> message);
void handleOctreeFileReplacementFromURLRequest(QSharedPointer<ReceivedMessage> message);
void handleDomainContentReplacementFromURLRequest(QSharedPointer<ReceivedMessage> message);
void handleOctreeFileReplacementRequest(QSharedPointer<ReceivedMessage> message);
void handleOctreeFileReplacement(QByteArray octreeFile);

View file

@ -26,24 +26,23 @@ generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources
if (ANDROID)
# on Android, don't compress the rcc binary
add_custom_command(
OUTPUT ${RESOURCES_RCC}
DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS}
COMMAND "${QT_DIR}/bin/rcc"
ARGS ${RESOURCES_QRC} -no-compress -binary -o ${RESOURCES_RCC}
OUTPUT ${RESOURCES_RCC}
DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS}
COMMAND "${QT_DIR}/bin/rcc"
ARGS ${RESOURCES_QRC} -no-compress -binary -o ${RESOURCES_RCC}
)
else ()
add_custom_command(
OUTPUT ${RESOURCES_RCC}
DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS}
COMMAND "${QT_DIR}/bin/rcc"
ARGS ${RESOURCES_QRC} -binary -o ${RESOURCES_RCC}
OUTPUT ${RESOURCES_RCC}
DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS}
COMMAND "${QT_DIR}/bin/rcc"
ARGS ${RESOURCES_QRC} -binary -o ${RESOURCES_RCC}
)
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")
@ -191,7 +190,11 @@ add_dependencies(${TARGET_NAME} resources)
if (WIN32)
# These are external plugins, but we need to do the 'add dependency' here so that their
# binary directories get added to the fixup path
add_dependency_external_projects(sixense)
if (USE_SIXENSE)
add_dependency_external_projects(sixense)
endif ()
add_dependency_external_projects(sdl2)
add_dependency_external_projects(OpenVR)
add_dependency_external_projects(neuron)
@ -199,12 +202,6 @@ if (WIN32)
add_dependency_external_projects(steamworks)
endif()
# include OPENSSL
include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
# append OpenSSL to our list of libraries to link
target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES})
# disable /OPT:REF and /OPT:ICF for the Debug builds
# This will prevent the following linker warnings
# LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:ICF' specification
@ -228,6 +225,9 @@ link_hifi_libraries(
# include the binary directory of render-utils for shader includes
target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/render-utils")
# include OpenSSL
target_openssl()
target_bullet()
target_opengl()
add_crashpad()
@ -312,36 +312,41 @@ if (APPLE)
COMPONENT ${CLIENT_COMPONENT}
)
set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources")
set(RESOURCES_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(RESOURCES_DEV_DIR "$<TARGET_FILE_DIR:${TARGET_NAME}>/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>"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"${RESOURCES_RCC}"
"$<TARGET_FILE_DIR:interface>"
# 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,
# 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"
"$<TARGET_FILE_DIR:${TARGET_NAME}>/resources/fonts"
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${CMAKE_SOURCE_DIR}/scripts"
"$<TARGET_FILE_DIR:${TARGET_NAME}>/scripts"
"${CMAKE_SOURCE_DIR}/scripts"
"${RESOURCES_DEV_DIR}/scripts"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"${PROJECT_SOURCE_DIR}/resources/serverless/tutorial.json"
"$<TARGET_FILE_DIR:${TARGET_NAME}>/resources/serverless/tutorial.json"
)
# link target to external libraries
@ -358,7 +363,7 @@ else()
PATTERN "*.exp" EXCLUDE
)
set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_DIR}")
set(RESOURCES_INSTALL_DIR "${INTERFACE_INSTALL_DIR}")
set(EXECUTABLE_COMPONENT ${CLIENT_COMPONENT})
@ -366,16 +371,28 @@ else()
endif()
endif()
if (SCRIPTS_INSTALL_DIR)
if (RESOURCES_INSTALL_DIR)
# setup install of scripts beside interface executable
install(
DIRECTORY "${CMAKE_SOURCE_DIR}/scripts/"
DESTINATION ${SCRIPTS_INSTALL_DIR}/scripts
DESTINATION ${RESOURCES_INSTALL_DIR}/scripts
COMPONENT ${CLIENT_COMPONENT}
)
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\"")
@ -389,3 +406,6 @@ endif()
add_dependency_external_projects(GifCreator)
find_package(GifCreator REQUIRED)
target_include_directories(${TARGET_NAME} PUBLIC ${GIFCREATOR_INCLUDE_DIRS})
# tell CMake to exclude ui_console.h for policy CMP0071
set_property(SOURCE ui_console.h PROPERTY SKIP_AUTOMOC ON)

View file

@ -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" }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

After

Width:  |  Height:  |  Size: 241 KiB

View 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

View 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

View file

@ -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

View 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

View file

@ -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

View 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;
}

View 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.");
}
}

View file

@ -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 != ""
}
}

View file

@ -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
}

View 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;
}
}
}

View file

@ -25,6 +25,7 @@ import "."
Item {
id: bar
x:0
height: 255
property bool shown: true
@ -45,10 +46,10 @@ Item {
anchors.fill: parent
}
Rectangle {
Rectangle {
id: background
anchors.fill : parent
color: "#FF000000"
color: "#FF000000"
border.color: "#FFFFFF"
anchors.bottomMargin: -1
anchors.leftMargin: -1
@ -104,13 +105,25 @@ Item {
}
}
}
}
}
function relocateAndResize(newWindowWidth, newWindowHeight) {
width = newWindowWidth;
y = newWindowHeight - height;
}
function onWindowGeometryChanged(rect) {
relocateAndResize(rect.width, rect.height);
}
Component.onCompleted: {
// put on bottom
width = Window.innerWidth;
height = 255;
y = Window.innerHeight - height;
relocateAndResize(Window.innerWidth, Window.innerHeight);
Window.geometryChanged.connect(onWindowGeometryChanged); // In devices with bars appearing at startup we should listen for this
}
Component.onDestruction: {
Window.geometryChanged.disconnect(onWindowGeometryChanged);
}
function addButton(properties) {

View file

@ -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)

View file

@ -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();
}
//

View file

@ -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"

View file

@ -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 {

View file

@ -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;

View file

@ -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 {

View file

@ -25,6 +25,7 @@ ScrollingWindow {
resizable: true
destroyOnHidden: false
implicitWidth: 424
opacity: parent.opacity
implicitHeight: isHMD ? 695 : 728
minSize: Qt.vector2d(424, 300)

View file

@ -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) {

View file

@ -11,44 +11,34 @@
import QtQuick 2.5
import QtQuick.Window 2.2
Item {
readonly property alias colors: colors
readonly property alias colorSchemes: colorSchemes
readonly property alias dimensions: dimensions
readonly property alias fontSizes: fontSizes
readonly property alias glyphs: glyphs
readonly property alias icons: icons
readonly property alias buttons: buttons
readonly property alias effects: effects
QtObject {
function glyphForIcon(icon) {
// Translates icon enum to glyph char.
var glyph;
switch (icon) {
case hifi.icons.information:
glyph = hifi.glyphs.info;
case icons.information:
glyph = glyphs.info;
break;
case hifi.icons.question:
glyph = hifi.glyphs.question;
case icons.question:
glyph = glyphs.question;
break;
case hifi.icons.warning:
glyph = hifi.glyphs.alert;
case icons.warning:
glyph = glyphs.alert;
break;
case hifi.icons.critical:
glyph = hifi.glyphs.error;
case icons.critical:
glyph = glyphs.error;
break;
case hifi.icons.placemark:
glyph = hifi.glyphs.placemark;
case icons.placemark:
glyph = glyphs.placemark;
break;
default:
glyph = hifi.glyphs.noIcon;
glyph = glyphs.noIcon;
}
return glyph;
}
Item {
id: colors
readonly property QtObject colors: QtObject {
// Base colors
readonly property color baseGray: "#393939"
readonly property color darkGray: "#121212"
@ -134,15 +124,13 @@ Item {
readonly property color tabBackgroundLight: "#d4d4d4"
}
Item {
id: colorSchemes
readonly property QtObject colorSchemes: QtObject {
readonly property int light: 0
readonly property int dark: 1
readonly property int faintGray: 2
}
Item {
id: dimensions
readonly property QtObject dimensions: QtObject {
readonly property bool largeScreen: Screen.width >= 1920 && Screen.height >= 1080
readonly property real borderRadius: largeScreen ? 7.5 : 5.0
readonly property real borderWidth: largeScreen ? 2 : 1
@ -168,8 +156,8 @@ Item {
readonly property real buttonWidth: 120
}
Item {
id: fontSizes // In pixels
readonly property QtObject fontSizes: QtObject {
// In pixels
readonly property real overlayTitle: dimensions.largeScreen ? 18 : 14
readonly property real tabName: dimensions.largeScreen ? 12 : 10
readonly property real sectionName: dimensions.largeScreen ? 12 : 10
@ -194,8 +182,7 @@ Item {
readonly property real disclosureButton: dimensions.largeScreen ? 30 : 22
}
Item {
id: icons
readonly property QtObject icons: QtObject {
// Values per OffscreenUi::Icon
readonly property int none: 0
readonly property int question: 1
@ -205,8 +192,7 @@ Item {
readonly property int placemark: 5
}
Item {
id: buttons
readonly property QtObject buttons: QtObject {
readonly property int white: 0
readonly property int blue: 1
readonly property int red: 2
@ -227,12 +213,11 @@ Item {
readonly property int radius: 5
}
QtObject {
id: effects
readonly property QtObject effects: QtObject {
readonly property int fadeInDuration: 300
}
Item {
id: glyphs
readonly property QtObject glyphs: QtObject {
readonly property string noIcon: ""
readonly property string hmd: "b"
readonly property string screen: "c"

View file

@ -0,0 +1,18 @@
{
"Entities": [
{
"type": "Box",
"dimensions": {
"x": 20,
"y": 1,
"z": 20
},
"position" : {
"x": 0,
"y": -12,
"z": 0
}
}
],
"Version": 84
}

View file

@ -351,8 +351,9 @@ static const QString OBJ_EXTENSION = ".obj";
static const QString AVA_JSON_EXTENSION = ".ava.json";
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;
@ -376,9 +377,7 @@ 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 QHash<QString, Application::AcceptURLMethod> Application::_acceptedExtensions {
const std::vector<std::pair<QString, Application::AcceptURLMethod>> Application::_acceptedExtensions {
{ SVO_EXTENSION, &Application::importSVOFromURL },
{ SVO_JSON_EXTENSION, &Application::importSVOFromURL },
{ AVA_JSON_EXTENSION, &Application::askToWearAvatarAttachmentUrl },
@ -386,6 +385,7 @@ const QHash<QString, Application::AcceptURLMethod> Application::_acceptedExtensi
{ JS_EXTENSION, &Application::askToLoadScript },
{ FST_EXTENSION, &Application::askToSetAvatarUrl },
{ JSON_GZ_EXTENSION, &Application::askToReplaceDomainContent },
{ CONTENT_ZIP_EXTENSION, &Application::askToReplaceDomainContent },
{ ZIP_EXTENSION, &Application::importFromZIP },
{ JPG_EXTENSION, &Application::importImage },
{ PNG_EXTENSION, &Application::importImage }
@ -511,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:
@ -540,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;
}
@ -920,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()),
@ -938,6 +959,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning()));
setProperty(hifi::properties::CRASHED, _previousSessionCrashed);
_entityClipboard->setIsServerlessMode(true);
{
const QString TEST_SCRIPT = "--testScript";
const QString TRACE_FILE = "--traceFile";
@ -1033,7 +1056,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();
@ -1095,9 +1122,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]() {
@ -2044,7 +2071,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();
@ -2704,6 +2731,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
surfaceContext->setContextProperty("ApplicationCompositor", &getApplicationCompositor());
surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
surfaceContext->setContextProperty("Selection", DependencyManager::get<SelectionScriptingInterface>().data());
surfaceContext->setContextProperty("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data());
surfaceContext->setContextProperty("Wallet", DependencyManager::get<WalletScriptingInterface>().data());
@ -2785,8 +2813,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
@ -2809,12 +2838,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;
}
@ -3020,27 +3052,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 },
@ -3080,6 +3112,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);
@ -3321,8 +3404,6 @@ void Application::keyPressEvent(QKeyEvent* event) {
} else {
setFullscreen(nullptr);
}
} else {
Menu::getInstance()->triggerOption(MenuOption::AddressBar);
}
break;
@ -3384,13 +3465,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;
@ -3410,22 +3484,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();
@ -3449,38 +3526,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) {
@ -4580,7 +4625,7 @@ void Application::initDisplay() {
}
void Application::init() {
// Make sure Login state is up to date
DependencyManager::get<DialogsManager>()->toggleLoginDialog();
if (!DISABLE_DEFERRED) {
@ -4605,7 +4650,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.
@ -4848,8 +4895,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) {
@ -5129,7 +5178,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));
@ -5142,6 +5191,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,
@ -5739,10 +5789,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())
@ -5755,7 +5810,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", "");
}
@ -5794,15 +5849,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 {
@ -5919,22 +5981,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()
@ -6170,14 +6232,12 @@ 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;
}
QHashIterator<QString, AcceptURLMethod> i(_acceptedExtensions);
QString lowerPath = url.path().toLower();
while (i.hasNext()) {
i.next();
if (lowerPath.endsWith(i.key(), Qt::CaseInsensitive)) {
for (auto& pair : _acceptedExtensions) {
if (lowerPath.endsWith(pair.first, Qt::CaseInsensitive)) {
return true;
}
}
@ -6185,21 +6245,18 @@ 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);
QHashIterator<QString, AcceptURLMethod> i(_acceptedExtensions);
QString lowerPath = url.path().toLower();
while (i.hasNext()) {
i.next();
if (lowerPath.endsWith(i.key(), Qt::CaseInsensitive)) {
AcceptURLMethod method = i.value();
for (auto& pair : _acceptedExtensions) {
if (lowerPath.endsWith(pair.first, Qt::CaseInsensitive)) {
AcceptURLMethod method = pair.second;
return (this->*method)(urlString);
}
}
@ -6386,13 +6443,11 @@ void Application::replaceDomainContent(const QString& url) {
QByteArray urlData(url.toUtf8());
auto limitedNodeList = DependencyManager::get<NodeList>();
const auto& domainHandler = limitedNodeList->getDomainHandler();
limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) {
return node->getType() == NodeType::EntityServer && node->getActiveSocket();
}, [&urlData, limitedNodeList, &domainHandler](const SharedNodePointer& octreeNode) {
auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true);
octreeFilePacket->write(urlData);
limitedNodeList->sendPacket(std::move(octreeFilePacket), domainHandler.getSockAddr());
});
auto octreeFilePacket = NLPacket::create(PacketType::DomainContentReplacementFromUrl, urlData.size(), true);
octreeFilePacket->write(urlData);
limitedNodeList->sendPacket(std::move(octreeFilePacket), domainHandler.getSockAddr());
auto addressManager = DependencyManager::get<AddressManager>();
addressManager->handleLookupString(DOMAIN_SPAWNING_POINT);
QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT;
@ -7036,7 +7091,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
@ -7350,10 +7405,35 @@ bool Application::isThrottleRendering() const {
}
bool Application::hasFocus() const {
if (_displayPlugin) {
return getActiveDisplayPlugin()->hasFocus();
bool result = (QApplication::activeWindow() != nullptr);
#if defined(Q_OS_WIN)
// On Windows, QWidget::activateWindow() - as called in setFocus() - makes the application's taskbar icon flash but doesn't
// take user focus away from their current window. So also check whether the application is the user's current foreground
// window.
result = result && (HWND)QApplication::activeWindow()->winId() == GetForegroundWindow();
#endif
return result;
}
void Application::setFocus() {
// Note: Windows doesn't allow a user focus to be taken away from another application. Instead, it changes the color of and
// flashes the taskbar icon.
auto window = qApp->getWindow();
window->activateWindow();
}
void Application::raise() {
auto windowState = qApp->getWindow()->windowState();
if (windowState & Qt::WindowMinimized) {
if (windowState & Qt::WindowMaximized) {
qApp->getWindow()->showMaximized();
} else if (windowState & Qt::WindowFullScreen) {
qApp->getWindow()->showFullScreen();
} else {
qApp->getWindow()->showNormal();
}
}
return (QApplication::activeWindow() != nullptr);
qApp->getWindow()->raise();
}
void Application::setMaxOctreePacketsPerSecond(int maxOctreePPS) {

View file

@ -161,6 +161,8 @@ public:
QRect getRecommendedHUDRect() const;
glm::vec2 getDeviceSize() const;
bool hasFocus() const;
void setFocus();
void raise();
void showCursor(const Cursor::Icon& cursor);
@ -282,6 +284,8 @@ public:
bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; }
void saveNextPhysicsStats(QString filename);
bool isServerlessMode() const;
void replaceDomainContent(const QString& url);
signals:
@ -293,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);
@ -389,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:
@ -423,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);
@ -569,7 +575,7 @@ private:
Setting::Handle<QString> _preferredCursor;
float _scaleMirror;
float _rotateMirror;
float _mirrorYawOffset;
float _raiseMirror;
QSet<int> _keysPressed;
@ -603,7 +609,7 @@ private:
GLCanvas* _glWidget{ nullptr };
typedef bool (Application::* AcceptURLMethod)(const QString &);
static const QHash<QString, AcceptURLMethod> _acceptedExtensions;
static const std::vector<std::pair<QString, Application::AcceptURLMethod>> _acceptedExtensions;
glm::uvec2 _renderResolution;

View file

@ -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()) {

View file

@ -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));

View file

@ -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) {

View file

@ -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);

View file

@ -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");
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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;
};

View file

@ -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)) {

View file

@ -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>();

View file

@ -74,16 +74,14 @@ QScriptValue WindowScriptingInterface::hasFocus() {
void WindowScriptingInterface::setFocus() {
// It's forbidden to call focus() from another thread.
qApp->postLambdaEvent([] {
auto window = qApp->getWindow();
window->activateWindow();
window->setFocus();
qApp->setFocus();
});
}
void WindowScriptingInterface::raiseMainWindow() {
// It's forbidden to call raise() from another thread.
qApp->postLambdaEvent([] {
qApp->getWindow()->raise();
qApp->raise();
});
}
@ -126,7 +124,7 @@ void WindowScriptingInterface::promptAsync(const QString& message, const QString
}
void WindowScriptingInterface::disconnectedFromDomain() {
emit domainChanged("");
emit domainChanged(QUrl());
}
QString fixupPathForMac(const QString& directory) {

View file

@ -62,13 +62,14 @@ public slots:
QScriptValue hasFocus();
/**jsdoc
* Make the Interface window have focus.
* Make the Interface window have focus. On Windows, if Interface doesn't already have focus, the task bar icon flashes to
* indicate that Interface wants attention but focus isn't taken away from the application that the user is using.
* @function Window.setFocus
*/
void setFocus();
/**jsdoc
* Raise the Interface window if it is minimized, and give it focus.
* Raise the Interface window if it is minimized. If raised, the window gains focus.
* @function Window.raiseMainWindow
*/
void raiseMainWindow();
@ -523,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) {
@ -532,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.

View file

@ -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) {

View file

@ -36,7 +36,6 @@ signals:
void backEnabledChanged();
void forwardEnabledChanged();
void useFeedChanged();
void receivedHifiSchemeURL(const QString& url);
void hostChanged();
protected:

View file

@ -63,6 +63,19 @@ static const float OPAQUE_ALPHA_THRESHOLD = 0.99f;
const QString Web3DOverlay::TYPE = "web3d";
const QString Web3DOverlay::QML = "Web3DOverlay.qml";
static auto qmlSurfaceDeleter = [](OffscreenQmlSurface* surface) {
AbstractViewStateInterface::instance()->postLambdaEvent([surface] {
if (AbstractViewStateInterface::instance()->isAboutToQuit()) {
// WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown
// if the application has already stopped its event loop, delete must be explicit
delete surface;
} else {
surface->deleteLater();
}
});
};
Web3DOverlay::Web3DOverlay() {
_touchDevice.setCapabilities(QTouchDevice::Position);
_touchDevice.setType(QTouchDevice::TouchScreen);
@ -75,7 +88,8 @@ Web3DOverlay::Web3DOverlay() {
connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface);
//need to be intialized before Tablet 1st open
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(_url);
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(QML);
_cachedWebSurface = true;
_webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
_webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
@ -114,6 +128,7 @@ void Web3DOverlay::destroyWebSurface() {
if (!_webSurface) {
return;
}
QQuickItem* rootItem = _webSurface->getRootItem();
if (rootItem && rootItem->objectName() == "tabletRoot") {
@ -135,10 +150,15 @@ void Web3DOverlay::destroyWebSurface() {
QObject::disconnect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent);
QObject::disconnect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived);
auto offscreenCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
// FIXME prevents crash on shutdown, but we shoudln't have to do this check
if (offscreenCache) {
offscreenCache->release(QML, _webSurface);
// If the web surface was fetched out of the cache, release it back into the cache
if (_cachedWebSurface) {
auto offscreenCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
// FIXME prevents crash on shutdown, but we shoudln't have to do this check
if (offscreenCache) {
offscreenCache->release(QML, _webSurface);
}
_cachedWebSurface = false;
}
_webSurface.reset();
}
@ -147,6 +167,8 @@ void Web3DOverlay::buildWebSurface() {
if (_webSurface) {
return;
}
// FIXME the context save here is most likely unecessary since the QML surfaces now render
// off the main thread, and all GL context work is done off the main thread (I *think*)
gl::withSavedContext([&] {
// FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces
// and the current rendering load)
@ -156,10 +178,13 @@ void Web3DOverlay::buildWebSurface() {
if (isWebContent()) {
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(QML);
_cachedWebSurface = true;
_webSurface->getRootItem()->setProperty("url", _url);
_webSurface->getRootItem()->setProperty("scriptURL", _scriptURL);
} else {
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(_url);
_webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), qmlSurfaceDeleter);
_webSurface->load(_url);
_cachedWebSurface = false;
setupQmlSurface();
}
_webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getWorldPosition()));

View file

@ -88,6 +88,7 @@ private:
InputMode _inputMode { Touch };
QSharedPointer<OffscreenQmlSurface> _webSurface;
bool _cachedWebSurface{ false };
gpu::TexturePointer _texture;
QString _url;
QString _scriptURL;

View file

@ -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);

View file

@ -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;
}
}

View file

@ -190,7 +190,7 @@ bool JSBaker::handleMultiLineComments(QTextStream& in) {
while (!in.atEnd()) {
in >> character;
if (character == '*') {
if (in.read(1) == '/') {
if (in.read(1) == "/") {
return true;
}
}
@ -228,7 +228,7 @@ bool JSBaker::isSpecialCharacter(QChar c) {
// If previous character is a special character, maybe don't omit new line (depends on next character as well)
bool JSBaker::isSpecialCharacterPrevious(QChar c) {
return (c == '\'' || c == '$' || c == '_' || c == '}' || c == ']' || c == ')' || c == '+' || c == '-'
|| c == '"' || c == "'");
|| c == '"' || c == '\'');
}
// If next character is a special character, maybe don't omit new line (depends on previous character as well)
@ -243,5 +243,5 @@ bool JSBaker::isSpaceOrTab(QChar c) {
// Check If the currentCharacter is " or ' or `
bool JSBaker::isQuote(QChar c) {
return (c == '"' || c == "'" || c == '`');
return (c == '"' || c == '\'' || c == '`');
}

View file

@ -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;
}

View file

@ -174,6 +174,7 @@ enum class Action {
TRACKED_OBJECT_13,
TRACKED_OBJECT_14,
TRACKED_OBJECT_15,
SPRINT,
NUM_ACTIONS
};

View file

@ -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;

View file

@ -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;

View file

@ -21,10 +21,6 @@ glm::uvec2 NullDisplayPlugin::getRecommendedRenderSize() const {
return glm::uvec2(100, 100);
}
bool NullDisplayPlugin::hasFocus() const {
return false;
}
void NullDisplayPlugin::submitFrame(const gpu::FramePointer& frame) {
if (frame) {
_gpuContext->consumeFrameUpdates(frame);

View file

@ -16,7 +16,6 @@ public:
grouping getGrouping() const override { return DEVELOPER; }
glm::uvec2 getRecommendedRenderSize() const override;
bool hasFocus() const override;
void submitFrame(const gpu::FramePointer& newFrame) override;
QImage getScreenshot(float aspectRatio = 0.0f) const override;
QImage getSecondaryCameraScreenshot() const override;

View file

@ -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());
@ -831,11 +835,6 @@ glm::uvec2 OpenGLDisplayPlugin::getSurfaceSize() const {
return result;
}
bool OpenGLDisplayPlugin::hasFocus() const {
auto window = _container->getPrimaryWidget();
return window ? window->hasFocus() : false;
}
void OpenGLDisplayPlugin::assertNotPresentThread() const {
Q_ASSERT(QThread::currentThread() != _presentThread);
}

View file

@ -98,8 +98,6 @@ protected:
virtual void compositePointer();
virtual void compositeExtra() {};
virtual bool hasFocus() const override;
// These functions must only be called on the presentation thread
virtual void customizeContext();
virtual void uncustomizeContext();

View file

@ -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(", ");

View file

@ -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; }

View file

@ -9,6 +9,7 @@
#include "RenderableMaterialEntityItem.h"
#include "RenderPipelines.h"
#include "GeometryCache.h"
using namespace render;
using namespace render::entities;
@ -90,138 +91,6 @@ ShapeKey MaterialEntityRenderer::getShapeKey() {
return builder.build();
}
glm::vec3 MaterialEntityRenderer::getVertexPos(float phi, float theta) {
return glm::vec3(glm::sin(theta) * glm::cos(phi), glm::cos(theta), glm::sin(theta) * glm::sin(phi));
}
glm::vec3 MaterialEntityRenderer::getTangent(float phi, float theta) {
return glm::vec3(-glm::cos(theta) * glm::cos(phi), glm::sin(theta), -glm::cos(theta) * glm::sin(phi));
}
void MaterialEntityRenderer::addVertex(std::vector<float>& buffer, const glm::vec3& pos, const glm::vec3& tan, const glm::vec2 uv) {
buffer.push_back(pos.x); buffer.push_back(pos.y); buffer.push_back(pos.z);
buffer.push_back(tan.x); buffer.push_back(tan.y); buffer.push_back(tan.z);
buffer.push_back(uv.x); buffer.push_back(uv.y);
}
void MaterialEntityRenderer::addTriangleFan(std::vector<float>& buffer, int stack, int step) {
float v1 = ((float)stack) / STACKS;
float theta1 = v1 * (float)M_PI;
glm::vec3 tip = getVertexPos(0, theta1);
float v2 = ((float)(stack + step)) / STACKS;
float theta2 = v2 * (float)M_PI;
for (int i = 0; i < SLICES; i++) {
float u1 = ((float)i) / SLICES;
float u2 = ((float)(i + step)) / SLICES;
float phi1 = u1 * M_PI_TIMES_2;
float phi2 = u2 * M_PI_TIMES_2;
/* (flipped for negative step)
p1
/ \
/ \
/ \
p3 ------ p2
*/
glm::vec3 pos2 = getVertexPos(phi2, theta2);
glm::vec3 pos3 = getVertexPos(phi1, theta2);
glm::vec3 tan1 = getTangent(0, theta1);
glm::vec3 tan2 = getTangent(phi2, theta2);
glm::vec3 tan3 = getTangent(phi1, theta2);
glm::vec2 uv1 = glm::vec2((u1 + u2) / 2.0f, v1);
glm::vec2 uv2 = glm::vec2(u2, v2);
glm::vec2 uv3 = glm::vec2(u1, v2);
addVertex(buffer, tip, tan1, uv1);
addVertex(buffer, pos2, tan2, uv2);
addVertex(buffer, pos3, tan3, uv3);
_numVertices += 3;
}
}
int MaterialEntityRenderer::_numVertices = 0;
std::shared_ptr<gpu::Stream::Format> MaterialEntityRenderer::_streamFormat = nullptr;
std::shared_ptr<gpu::BufferStream> MaterialEntityRenderer::_stream = nullptr;
std::shared_ptr<gpu::Buffer> MaterialEntityRenderer::_verticesBuffer = nullptr;
void MaterialEntityRenderer::generateMesh() {
_streamFormat = std::make_shared<gpu::Stream::Format>();
_stream = std::make_shared<gpu::BufferStream>();
_verticesBuffer = std::make_shared<gpu::Buffer>();
const int NUM_POS_COORDS = 3;
const int NUM_TANGENT_COORDS = 3;
const int VERTEX_TANGENT_OFFSET = NUM_POS_COORDS * sizeof(float);
const int VERTEX_TEXCOORD_OFFSET = VERTEX_TANGENT_OFFSET + NUM_TANGENT_COORDS * sizeof(float);
_streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
_streamFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
_streamFormat->setAttribute(gpu::Stream::TANGENT, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), VERTEX_TANGENT_OFFSET);
_streamFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), VERTEX_TEXCOORD_OFFSET);
_stream->addBuffer(_verticesBuffer, 0, _streamFormat->getChannels().at(0)._stride);
std::vector<float> vertexBuffer;
// Top
addTriangleFan(vertexBuffer, 0, 1);
// Middle section
for (int j = 1; j < STACKS - 1; j++) {
float v1 = ((float)j) / STACKS;
float v2 = ((float)(j + 1)) / STACKS;
float theta1 = v1 * (float)M_PI;
float theta2 = v2 * (float)M_PI;
for (int i = 0; i < SLICES; i++) {
float u1 = ((float)i) / SLICES;
float u2 = ((float)(i + 1)) / SLICES;
float phi1 = u1 * M_PI_TIMES_2;
float phi2 = u2 * M_PI_TIMES_2;
/*
p2 ---- p3
| / |
| / |
| / |
p1 ---- p4
*/
glm::vec3 pos1 = getVertexPos(phi1, theta2);
glm::vec3 pos2 = getVertexPos(phi1, theta1);
glm::vec3 pos3 = getVertexPos(phi2, theta1);
glm::vec3 pos4 = getVertexPos(phi2, theta2);
glm::vec3 tan1 = getTangent(phi1, theta2);
glm::vec3 tan2 = getTangent(phi1, theta1);
glm::vec3 tan3 = getTangent(phi2, theta1);
glm::vec3 tan4 = getTangent(phi2, theta2);
glm::vec2 uv1 = glm::vec2(u1, v2);
glm::vec2 uv2 = glm::vec2(u1, v1);
glm::vec2 uv3 = glm::vec2(u2, v1);
glm::vec2 uv4 = glm::vec2(u2, v2);
addVertex(vertexBuffer, pos1, tan1, uv1);
addVertex(vertexBuffer, pos2, tan2, uv2);
addVertex(vertexBuffer, pos3, tan3, uv3);
addVertex(vertexBuffer, pos3, tan3, uv3);
addVertex(vertexBuffer, pos4, tan4, uv4);
addVertex(vertexBuffer, pos1, tan1, uv1);
_numVertices += 6;
}
}
// Bottom
addTriangleFan(vertexBuffer, STACKS, -1);
_verticesBuffer->append(vertexBuffer.size() * sizeof(float), (gpu::Byte*) vertexBuffer.data());
}
void MaterialEntityRenderer::doRender(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableMaterialEntityItem::render");
Q_ASSERT(args->_batch);
@ -252,14 +121,7 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) {
args->_details._materialSwitches++;
// Draw!
if (_numVertices == 0) {
generateMesh();
}
DependencyManager::get<GeometryCache>()->renderSphere(batch);
batch.setInputFormat(_streamFormat);
batch.setInputStream(0, *_stream);
batch.draw(gpu::TRIANGLES, _numVertices, 0);
const int NUM_VERTICES_PER_TRIANGLE = 3;
args->_details._trianglesRendered += _numVertices / NUM_VERTICES_PER_TRIANGLE;
args->_details._trianglesRendered += (int)DependencyManager::get<GeometryCache>()->getSphereTriangleCount();
}

View file

@ -40,20 +40,6 @@ private:
Transform _renderTransform;
std::shared_ptr<NetworkMaterial> _drawMaterial;
static int _numVertices;
static std::shared_ptr<gpu::Stream::Format> _streamFormat;
static std::shared_ptr<gpu::BufferStream> _stream;
static std::shared_ptr<gpu::Buffer> _verticesBuffer;
void generateMesh();
void addTriangleFan(std::vector<float>& buffer, int stack, int step);
static glm::vec3 getVertexPos(float phi, float theta);
static glm::vec3 getTangent(float phi, float theta);
static void addVertex(std::vector<float>& buffer, const glm::vec3& pos, const glm::vec3& tan, const glm::vec2 uv);
const int SLICES = 15;
const int STACKS = 9;
const float M_PI_TIMES_2 = 2.0f * (float)M_PI;
};
} }

View file

@ -19,6 +19,8 @@
#include "render-utils/simple_vert.h"
#include "render-utils/simple_frag.h"
#include "RenderPipelines.h"
//#define SHAPE_ENTITY_USE_FADE_EFFECT
#ifdef SHAPE_ENTITY_USE_FADE_EFFECT
#include <FadeEffect.h>
@ -108,11 +110,94 @@ bool ShapeEntityRenderer::isTransparent() const {
if (_procedural.isEnabled() && _procedural.isFading()) {
return Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) < 1.0f;
}
// return _entity->getLocalRenderAlpha() < 1.0f || Parent::isTransparent();
auto mat = _materials.find("0");
if (mat != _materials.end()) {
if (mat->second.top().material) {
auto matKey = mat->second.top().material->getKey();
if (matKey.isTranslucent()) {
return true;
}
}
}
return Parent::isTransparent();
}
ItemKey ShapeEntityRenderer::getKey() {
ItemKey::Builder builder;
builder.withTypeShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1);
withReadLock([&] {
if (isTransparent()) {
builder.withTransparent();
}
});
return builder.build();
}
bool ShapeEntityRenderer::useMaterialPipeline() const {
bool proceduralReady = resultWithReadLock<bool>([&] {
return _procedural.isReady();
});
if (proceduralReady) {
return false;
}
graphics::MaterialKey drawMaterialKey;
auto mat = _materials.find("0");
if (mat != _materials.end() && mat->second.top().material) {
drawMaterialKey = mat->second.top().material->getKey();
}
if (drawMaterialKey.isEmissive() || drawMaterialKey.isUnlit() || drawMaterialKey.isMetallic() || drawMaterialKey.isScattering()) {
return true;
}
// If the material is using any map, we need to use a material ShapeKey
for (int i = 0; i < graphics::Material::MapChannel::NUM_MAP_CHANNELS; i++) {
if (drawMaterialKey.isMapChannel(graphics::Material::MapChannel(i))) {
return true;
}
}
return false;
}
ShapeKey ShapeEntityRenderer::getShapeKey() {
if (useMaterialPipeline()) {
graphics::MaterialKey drawMaterialKey;
if (_materials["0"].top().material) {
drawMaterialKey = _materials["0"].top().material->getKey();
}
bool isTranslucent = drawMaterialKey.isTranslucent();
bool hasTangents = drawMaterialKey.isNormalMap();
bool hasLightmap = drawMaterialKey.isLightmapMap();
bool isUnlit = drawMaterialKey.isUnlit();
ShapeKey::Builder builder;
builder.withMaterial();
if (isTranslucent) {
builder.withTranslucent();
}
if (hasTangents) {
builder.withTangents();
}
if (hasLightmap) {
builder.withLightmap();
}
if (isUnlit) {
builder.withUnlit();
}
return builder.build();
} else {
return Parent::getShapeKey();
}
}
void ShapeEntityRenderer::doRender(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableShapeEntityItem::render");
Q_ASSERT(args->_batch);
@ -149,7 +234,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
} else {
geometryCache->renderShape(batch, geometryShape, outColor);
}
} else {
} else if (!useMaterialPipeline()) {
// FIXME, support instanced multi-shape rendering using multidraw indirect
outColor.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
auto pipeline = outColor.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline();
@ -158,6 +243,11 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
} else {
geometryCache->renderSolidShapeInstance(args, batch, geometryShape, outColor, pipeline);
}
} else {
RenderPipelines::bindMaterial(mat, batch, args->_enableTexturing);
args->_details._materialSwitches++;
geometryCache->renderShape(batch, geometryShape);
}
const auto triCount = geometryCache->getShapeTriangleCount(geometryShape);

View file

@ -24,6 +24,10 @@ public:
virtual scriptable::ScriptableModelBase getScriptableModel() override;
protected:
ItemKey getKey() override;
ShapeKey getShapeKey() override;
private:
virtual bool needsRenderUpdate() const override;
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
@ -32,6 +36,8 @@ private:
virtual void doRender(RenderArgs* args) override;
virtual bool isTransparent() const override;
bool useMaterialPipeline() const;
Procedural _procedural;
QString _lastUserData;
Transform _renderTransform;

View file

@ -239,7 +239,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) {
}
bool WebEntityRenderer::hasWebSurface() {
return (bool)_webSurface;
return (bool)_webSurface && _webSurface->getRootItem();
}
bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) {
@ -303,7 +303,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) {
_fadeStartTime = usecTimestampNow();
_webSurface->resume();
return true;
return _webSurface->getRootItem();
}
void WebEntityRenderer::destroyWebSurface() {

View file

@ -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) {

View file

@ -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([&] {

View file

@ -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();

View file

@ -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);
}
});

View file

@ -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({

View file

@ -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();
}

View file

@ -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

View file

@ -157,7 +157,7 @@ void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool u
}
if (usingUserData) {
_parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(getUserData().toUtf8()));
_parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(getUserData().toUtf8()), materialURLString);
// Since our material changed, the current name might not be valid anymore, so we need to update
setCurrentMaterialName(_currentMaterialName);

View file

@ -69,7 +69,7 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) {
qFatal("Offscreen surface is invalid");
}
#endif
if (gl::Context::enableDebugLogger()) {
_context->makeCurrent(_offscreenSurface);
QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this);

View file

@ -0,0 +1,6 @@
set(TARGET_NAME gpu-gl-common)
setup_hifi_library(Concurrent)
link_hifi_libraries(shared gl gpu)
GroupSources("src")
target_opengl()

View file

@ -1,9 +1,9 @@
//
// GLBackend.cpp
// libraries/gpu-gl-android/src/gpu/gl
// libraries/gpu/src/gpu
//
// Created by Cristian Duarte & Gabriel Calero on 9/21/2016.
// Copyright 2016 High Fidelity, Inc.
// Created by Sam Gateau on 10/27/2014.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -16,16 +16,11 @@
#include <functional>
#include <glm/gtc/type_ptr.hpp>
#include "../gles/GLESBackend.h"
#if defined(NSIGHT_FOUND)
#include "nvToolsExt.h"
#endif
#include <shared/GlobalAppProperties.h>
#include <GPUIdent.h>
#include <gl/QOpenGLContextWrapper.h>
#include <QtCore/QProcessEnvironment>
#include "GLTexture.h"
#include "GLShader.h"
@ -33,39 +28,6 @@
using namespace gpu;
using namespace gpu::gl;
static GLBackend* INSTANCE{ nullptr };
BackendPointer GLBackend::createBackend() {
// FIXME provide a mechanism to override the backend for testing
// Where the gpuContext is initialized and where the TRUE Backend is created and assigned
//auto version = QOpenGLContextWrapper::currentContextVersion();
std::shared_ptr<GLBackend> result;
qDebug() << "Using OpenGL ES backend";
result = std::make_shared<gpu::gles::GLESBackend>();
result->initInput();
result->initTransform();
result->initTextureManagementStage();
INSTANCE = result.get();
void* voidInstance = &(*result);
qApp->setProperty(hifi::properties::gl::BACKEND, QVariant::fromValue(voidInstance));
return result;
}
GLBackend& getBackend() {
if (!INSTANCE) {
INSTANCE = static_cast<GLBackend*>(qApp->property(hifi::properties::gl::BACKEND).value<void*>());
}
return *INSTANCE;
}
bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler) {
return GLShader::makeProgram(getBackend(), shader, slotBindings, handler);
}
GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] =
{
(&::gpu::gl::GLBackend::do_draw),
@ -151,6 +113,9 @@ void GLBackend::init() {
qCDebug(gpugllogging) << "\tcard:" << gpu->getName();
qCDebug(gpugllogging) << "\tdriver:" << gpu->getDriver();
qCDebug(gpugllogging) << "\tdedicated memory:" << gpu->getMemory() << "MB";
#if !defined(USE_GLES)
qCDebug(gpugllogging, "V-Sync is %s\n", (::gl::getSwapInterval() > 0 ? "ON" : "OFF"));
#endif
#if THREADED_TEXTURE_BUFFERING
// This has to happen on the main thread in order to give the thread
// pool a reasonable parent object
@ -228,7 +193,7 @@ void GLBackend::renderPassTransfer(const Batch& batch) {
}
{ // Sync the transform buffers
PROFILE_RANGE(render_gpu_gl_detail, "transferGPUTransform");
PROFILE_RANGE(render_gpu_gl_detail, "syncGPUTransform");
transferTransformState(batch);
}
@ -296,7 +261,7 @@ void GLBackend::render(const Batch& batch) {
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
if (_stereo.isStereo()) {
glEnable(GL_CLIP_DISTANCE0_EXT);
glEnable(GL_CLIP_DISTANCE0);
}
#endif
{
@ -305,7 +270,7 @@ void GLBackend::render(const Batch& batch) {
}
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
if (_stereo.isStereo()) {
glDisable(GL_CLIP_DISTANCE0_EXT);
glDisable(GL_CLIP_DISTANCE0);
}
#endif

View file

@ -32,9 +32,13 @@
// Different versions for the stereo drawcall
// Current preferred is "instanced" which draw the shape twice but instanced and rely on clipping plane to draw left/right side only
//#define GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE
#if defined(USE_GLES)
#define GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE
#else
//#define GPU_STEREO_TECHNIQUE_DOUBLED_SMARTER
#define GPU_STEREO_TECHNIQUE_INSTANCED
#endif
// Let these be configured by the one define picked above

View file

@ -17,6 +17,11 @@
using namespace gpu;
using namespace gpu::gl;
#if defined(USE_GLES)
#define GL_FRAMEBUFFER_SRGB GL_FRAMEBUFFER_SRGB_EXT
#define glClearDepth glClearDepthf
#endif
void GLBackend::syncOutputStateCache() {
GLint currentFBO;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &currentFBO);
@ -88,7 +93,7 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) {
if (masks & Framebuffer::BUFFER_STENCIL) {
glClearStencil(stencil);
glmask |= GL_STENCIL_BUFFER_BIT;
cacheStencilMask = _pipeline._stateCache.stencilActivation.getWriteMaskFront();
if (cacheStencilMask != 0xFF) {
restoreStencilMask = true;
@ -182,7 +187,11 @@ void GLBackend::downloadFramebuffer(const FramebufferPointer& srcFramebuffer, co
return;
}
#if defined(USE_GLES)
GLenum format = GL_RGBA;
#else
GLenum format = GL_BGRA;
#endif
if (destImage.format() != QImage::Format_ARGB32) {
qCWarning(gpugllogging) << "GLBackend::downloadFramebuffer : destImage format must be FORMAT_ARGB32 to receive the region of the framebuffer";
return;

View file

@ -25,6 +25,7 @@ static bool timeElapsed = false;
#endif
void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) {
#if !defined(USE_GLES)
auto query = batch._queries.get(batch._params[paramOffset]._uint);
GLQuery* glquery = syncGPUObject(*query);
if (glquery) {
@ -43,9 +44,11 @@ void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) {
glquery->_rangeQueryDepth = _queryStage._rangeQueryDepth;
(void)CHECK_GL_ERROR();
}
#endif
}
void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) {
#if !defined(USE_GLES)
auto query = batch._queries.get(batch._params[paramOffset]._uint);
GLQuery* glquery = syncGPUObject(*query);
if (glquery) {
@ -66,9 +69,11 @@ void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) {
(void)CHECK_GL_ERROR();
}
#endif
}
void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) {
#if !defined(USE_GLES)
auto query = batch._queries.get(batch._params[paramOffset]._uint);
GLQuery* glquery = syncGPUObject(*query);
if (glquery) {
@ -90,6 +95,7 @@ void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) {
(void)CHECK_GL_ERROR();
}
}
#endif
}
void GLBackend::resetQueryStage() {

View file

@ -14,15 +14,32 @@ using namespace gpu::gl;
// GLSL version
std::string GLBackend::getBackendShaderHeader() const {
return std::string("#version 410 core");
#if defined(USE_GLES)
static const std::string header(
R"SHADER(#version 310 es
#extension GL_EXT_texture_buffer : enable
precision lowp float; // check precision 2
precision lowp samplerBuffer;
precision lowp sampler2DShadow;
)SHADER");
#else
static const std::string header(
R"SHADER(#version 410 core
)SHADER");
#endif
return header;
}
// Shader domain
static const size_t NUM_SHADER_DOMAINS = 3;
static_assert(Shader::Type::NUM_DOMAINS == NUM_SHADER_DOMAINS, "GL shader domains must equal defined GPU shader domains");
// GL Shader type enums
// Must match the order of type specified in gpu::Shader::Type
static const std::array<GLenum, NUM_SHADER_DOMAINS> SHADER_DOMAINS { {
static const std::array<GLenum, NUM_SHADER_DOMAINS> SHADER_DOMAINS{ {
GL_VERTEX_SHADER,
GL_FRAGMENT_SHADER,
GL_GEOMETRY_SHADER,
@ -30,22 +47,33 @@ static const std::array<GLenum, NUM_SHADER_DOMAINS> SHADER_DOMAINS { {
// Domain specific defines
// Must match the order of type specified in gpu::Shader::Type
static const std::array<std::string, NUM_SHADER_DOMAINS> DOMAIN_DEFINES { {
static const std::array<std::string, NUM_SHADER_DOMAINS> DOMAIN_DEFINES{ {
"#define GPU_VERTEX_SHADER",
"#define GPU_PIXEL_SHADER",
"#define GPU_GEOMETRY_SHADER",
} };
// Stereo specific defines
static const std::string stereoVersion {
static const std::string stereoVersion{
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
"#define GPU_TRANSFORM_IS_STEREO\n#define GPU_TRANSFORM_STEREO_CAMERA\n#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED\n#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN"
R"SHADER(
#define GPU_TRANSFORM_IS_STEREO
#define GPU_TRANSFORM_STEREO_CAMERA
#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED
#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN
)SHADER"
#endif
#ifdef GPU_STEREO_DRAWCALL_DOUBLED
#ifdef GPU_STEREO_CAMERA_BUFFER
"#define GPU_TRANSFORM_IS_STEREO\n#define GPU_TRANSFORM_STEREO_CAMERA\n#define GPU_TRANSFORM_STEREO_CAMERA_ATTRIBUTED"
R"SHADER(
#define GPU_TRANSFORM_IS_STEREO
#define GPU_TRANSFORM_STEREO_CAMERA
#define GPU_TRANSFORM_STEREO_CAMERA_ATTRIBUTED
)SHADER"
#else
"#define GPU_TRANSFORM_IS_STEREO"
R"SHADER(
#define GPU_TRANSFORM_IS_STEREO
)SHADER"
#endif
#endif
};
@ -67,7 +95,10 @@ GLShader* GLBackend::compileBackendShader(const Shader& shader, const Shader::Co
for (int version = 0; version < GLShader::NumVersions; version++) {
auto& shaderObject = shaderObjects[version];
std::string shaderDefines = getBackendShaderHeader() + "\n" + DOMAIN_DEFINES[shader.getType()] + "\n" + VERSION_DEFINES[version];
std::string shaderDefines = getBackendShaderHeader() + "\n"
+ DOMAIN_DEFINES[shader.getType()] + "\n"
+ VERSION_DEFINES[version];
if (handler) {
bool retest = true;
std::string currentSrc = shaderSource;
@ -154,149 +185,173 @@ GLShader* GLBackend::compileBackendProgram(const Shader& program, const Shader::
return object;
}
GLBackend::ElementResource GLBackend::getFormatFromGLUniform(GLenum gltype) {
switch (gltype) {
case GL_FLOAT: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER);
case GL_FLOAT_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER);
case GL_FLOAT_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER);
case GL_FLOAT_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER);
/*
case GL_DOUBLE: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER);
case GL_DOUBLE_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER);
case GL_DOUBLE_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER);
case GL_DOUBLE_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER);
*/
case GL_INT: return ElementResource(Element(SCALAR, gpu::INT32, UNIFORM), Resource::BUFFER);
case GL_INT_VEC2: return ElementResource(Element(VEC2, gpu::INT32, UNIFORM), Resource::BUFFER);
case GL_INT_VEC3: return ElementResource(Element(VEC3, gpu::INT32, UNIFORM), Resource::BUFFER);
case GL_INT_VEC4: return ElementResource(Element(VEC4, gpu::INT32, UNIFORM), Resource::BUFFER);
case GL_FLOAT:
return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER);
case GL_FLOAT_VEC2:
return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER);
case GL_FLOAT_VEC3:
return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER);
case GL_FLOAT_VEC4:
return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER);
case GL_UNSIGNED_INT: return ElementResource(Element(SCALAR, gpu::UINT32, UNIFORM), Resource::BUFFER);
#if defined(Q_OS_WIN)
case GL_UNSIGNED_INT_VEC2: return ElementResource(Element(VEC2, gpu::UINT32, UNIFORM), Resource::BUFFER);
case GL_UNSIGNED_INT_VEC3: return ElementResource(Element(VEC3, gpu::UINT32, UNIFORM), Resource::BUFFER);
case GL_UNSIGNED_INT_VEC4: return ElementResource(Element(VEC4, gpu::UINT32, UNIFORM), Resource::BUFFER);
case GL_INT:
return ElementResource(Element(SCALAR, gpu::INT32, UNIFORM), Resource::BUFFER);
case GL_INT_VEC2:
return ElementResource(Element(VEC2, gpu::INT32, UNIFORM), Resource::BUFFER);
case GL_INT_VEC3:
return ElementResource(Element(VEC3, gpu::INT32, UNIFORM), Resource::BUFFER);
case GL_INT_VEC4:
return ElementResource(Element(VEC4, gpu::INT32, UNIFORM), Resource::BUFFER);
case GL_UNSIGNED_INT:
return ElementResource(Element(SCALAR, gpu::UINT32, UNIFORM), Resource::BUFFER);
case GL_UNSIGNED_INT_VEC2:
return ElementResource(Element(VEC2, gpu::UINT32, UNIFORM), Resource::BUFFER);
case GL_UNSIGNED_INT_VEC3:
return ElementResource(Element(VEC3, gpu::UINT32, UNIFORM), Resource::BUFFER);
case GL_UNSIGNED_INT_VEC4:
return ElementResource(Element(VEC4, gpu::UINT32, UNIFORM), Resource::BUFFER);
case GL_BOOL:
return ElementResource(Element(SCALAR, gpu::BOOL, UNIFORM), Resource::BUFFER);
case GL_BOOL_VEC2:
return ElementResource(Element(VEC2, gpu::BOOL, UNIFORM), Resource::BUFFER);
case GL_BOOL_VEC3:
return ElementResource(Element(VEC3, gpu::BOOL, UNIFORM), Resource::BUFFER);
case GL_BOOL_VEC4:
return ElementResource(Element(VEC4, gpu::BOOL, UNIFORM), Resource::BUFFER);
case GL_FLOAT_MAT2:
return ElementResource(Element(gpu::MAT2, gpu::FLOAT, UNIFORM), Resource::BUFFER);
case GL_FLOAT_MAT3:
return ElementResource(Element(MAT3, gpu::FLOAT, UNIFORM), Resource::BUFFER);
case GL_FLOAT_MAT4:
return ElementResource(Element(MAT4, gpu::FLOAT, UNIFORM), Resource::BUFFER);
case GL_SAMPLER_2D:
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D);
case GL_SAMPLER_3D:
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_3D);
case GL_SAMPLER_CUBE:
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_CUBE);
case GL_SAMPLER_2D_MULTISAMPLE:
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D);
case GL_SAMPLER_2D_ARRAY:
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D_ARRAY);
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY);
case GL_SAMPLER_2D_SHADOW:
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D);
case GL_SAMPLER_CUBE_SHADOW:
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_CUBE);
case GL_SAMPLER_2D_ARRAY_SHADOW:
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D_ARRAY);
case GL_SAMPLER_BUFFER:
return ElementResource(Element(SCALAR, gpu::FLOAT, RESOURCE_BUFFER), Resource::BUFFER);
case GL_INT_SAMPLER_2D:
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D);
case GL_INT_SAMPLER_2D_MULTISAMPLE:
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D);
case GL_INT_SAMPLER_3D:
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_3D);
case GL_INT_SAMPLER_CUBE:
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_CUBE);
case GL_INT_SAMPLER_2D_ARRAY:
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D_ARRAY);
case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY);
case GL_UNSIGNED_INT_SAMPLER_2D:
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D);
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE:
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D);
case GL_UNSIGNED_INT_SAMPLER_3D:
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_3D);
case GL_UNSIGNED_INT_SAMPLER_CUBE:
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_CUBE);
case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D_ARRAY);
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY);
#if !defined(USE_GLES)
case GL_SAMPLER_1D:
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D);
case GL_SAMPLER_1D_ARRAY:
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D_ARRAY);
case GL_INT_SAMPLER_1D:
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D);
case GL_INT_SAMPLER_1D_ARRAY:
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D_ARRAY);
case GL_UNSIGNED_INT_SAMPLER_1D:
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D);
case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY:
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D_ARRAY);
#endif
case GL_BOOL: return ElementResource(Element(SCALAR, gpu::BOOL, UNIFORM), Resource::BUFFER);
case GL_BOOL_VEC2: return ElementResource(Element(VEC2, gpu::BOOL, UNIFORM), Resource::BUFFER);
case GL_BOOL_VEC3: return ElementResource(Element(VEC3, gpu::BOOL, UNIFORM), Resource::BUFFER);
case GL_BOOL_VEC4: return ElementResource(Element(VEC4, gpu::BOOL, UNIFORM), Resource::BUFFER);
case GL_FLOAT_MAT2: return ElementResource(Element(gpu::MAT2, gpu::FLOAT, UNIFORM), Resource::BUFFER);
case GL_FLOAT_MAT3: return ElementResource(Element(MAT3, gpu::FLOAT, UNIFORM), Resource::BUFFER);
case GL_FLOAT_MAT4: return ElementResource(Element(MAT4, gpu::FLOAT, UNIFORM), Resource::BUFFER);
/* {GL_FLOAT_MAT2x3 mat2x3},
{GL_FLOAT_MAT2x4 mat2x4},
{GL_FLOAT_MAT3x2 mat3x2},
{GL_FLOAT_MAT3x4 mat3x4},
{GL_FLOAT_MAT4x2 mat4x2},
{GL_FLOAT_MAT4x3 mat4x3},
{GL_DOUBLE_MAT2 dmat2},
{GL_DOUBLE_MAT3 dmat3},
{GL_DOUBLE_MAT4 dmat4},
{GL_DOUBLE_MAT2x3 dmat2x3},
{GL_DOUBLE_MAT2x4 dmat2x4},
{GL_DOUBLE_MAT3x2 dmat3x2},
{GL_DOUBLE_MAT3x4 dmat3x4},
{GL_DOUBLE_MAT4x2 dmat4x2},
{GL_DOUBLE_MAT4x3 dmat4x3},
*/
case GL_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D);
case GL_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D);
case GL_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_3D);
case GL_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_CUBE);
#if defined(Q_OS_WIN)
case GL_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D);
case GL_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D_ARRAY);
case GL_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D_ARRAY);
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY);
#endif
case GL_SAMPLER_2D_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D);
#if defined(Q_OS_WIN)
case GL_SAMPLER_CUBE_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_CUBE);
case GL_SAMPLER_2D_ARRAY_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D_ARRAY);
#endif
// {GL_SAMPLER_1D_SHADOW sampler1DShadow},
// {GL_SAMPLER_1D_ARRAY_SHADOW sampler1DArrayShadow},
case GL_SAMPLER_BUFFER: return ElementResource(Element(SCALAR, gpu::FLOAT, RESOURCE_BUFFER), Resource::BUFFER);
// {GL_SAMPLER_2D_RECT sampler2DRect},
// {GL_SAMPLER_2D_RECT_SHADOW sampler2DRectShadow},
#if defined(Q_OS_WIN)
case GL_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D);
case GL_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D);
case GL_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D);
case GL_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_3D);
case GL_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_CUBE);
case GL_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D_ARRAY);
case GL_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D_ARRAY);
case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY);
// {GL_INT_SAMPLER_BUFFER isamplerBuffer},
// {GL_INT_SAMPLER_2D_RECT isampler2DRect},
case GL_UNSIGNED_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D);
case GL_UNSIGNED_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D);
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D);
case GL_UNSIGNED_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_3D);
case GL_UNSIGNED_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_CUBE);
case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D_ARRAY);
case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D_ARRAY);
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY);
#endif
// {GL_UNSIGNED_INT_SAMPLER_BUFFER usamplerBuffer},
// {GL_UNSIGNED_INT_SAMPLER_2D_RECT usampler2DRect},
/*
{GL_IMAGE_1D image1D},
{GL_IMAGE_2D image2D},
{GL_IMAGE_3D image3D},
{GL_IMAGE_2D_RECT image2DRect},
{GL_IMAGE_CUBE imageCube},
{GL_IMAGE_BUFFER imageBuffer},
{GL_IMAGE_1D_ARRAY image1DArray},
{GL_IMAGE_2D_ARRAY image2DArray},
{GL_IMAGE_2D_MULTISAMPLE image2DMS},
{GL_IMAGE_2D_MULTISAMPLE_ARRAY image2DMSArray},
{GL_INT_IMAGE_1D iimage1D},
{GL_INT_IMAGE_2D iimage2D},
{GL_INT_IMAGE_3D iimage3D},
{GL_INT_IMAGE_2D_RECT iimage2DRect},
{GL_INT_IMAGE_CUBE iimageCube},
{GL_INT_IMAGE_BUFFER iimageBuffer},
{GL_INT_IMAGE_1D_ARRAY iimage1DArray},
{GL_INT_IMAGE_2D_ARRAY iimage2DArray},
{GL_INT_IMAGE_2D_MULTISAMPLE iimage2DMS},
{GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY iimage2DMSArray},
{GL_UNSIGNED_INT_IMAGE_1D uimage1D},
{GL_UNSIGNED_INT_IMAGE_2D uimage2D},
{GL_UNSIGNED_INT_IMAGE_3D uimage3D},
{GL_UNSIGNED_INT_IMAGE_2D_RECT uimage2DRect},
{GL_UNSIGNED_INT_IMAGE_CUBE uimageCube},+ [0] {_name="fInnerRadius" _location=0 _element={_semantic=15 '\xf' _dimension=0 '\0' _type=0 '\0' } } gpu::Shader::Slot
{GL_UNSIGNED_INT_IMAGE_BUFFER uimageBuffer},
{GL_UNSIGNED_INT_IMAGE_1D_ARRAY uimage1DArray},
{GL_UNSIGNED_INT_IMAGE_2D_ARRAY uimage2DArray},
{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS},
{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray},
{GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint}
*/
default:
return ElementResource(Element(), Resource::BUFFER);
}
// Non-covered types
//{GL_FLOAT_MAT2x3 mat2x3},
//{GL_FLOAT_MAT2x4 mat2x4},
//{GL_FLOAT_MAT3x2 mat3x2},
//{GL_FLOAT_MAT3x4 mat3x4},
//{GL_FLOAT_MAT4x2 mat4x2},
//{GL_FLOAT_MAT4x3 mat4x3},
//{GL_DOUBLE_MAT2 dmat2},
//{GL_DOUBLE_MAT3 dmat3},
//{GL_DOUBLE_MAT4 dmat4},
//{GL_DOUBLE_MAT2x3 dmat2x3},
//{GL_DOUBLE_MAT2x4 dmat2x4},
//{GL_DOUBLE_MAT3x2 dmat3x2},
//{GL_DOUBLE_MAT3x4 dmat3x4},
//{GL_DOUBLE_MAT4x2 dmat4x2},
//{GL_DOUBLE_MAT4x3 dmat4x3},
//{GL_SAMPLER_1D_SHADOW sampler1DShadow},
//{GL_SAMPLER_1D_ARRAY_SHADOW sampler1DArrayShadow},
//{GL_SAMPLER_2D_RECT sampler2DRect},
//{GL_SAMPLER_2D_RECT_SHADOW sampler2DRectShadow},
//{GL_INT_SAMPLER_BUFFER isamplerBuffer},
//{GL_INT_SAMPLER_2D_RECT isampler2DRect},
//{GL_UNSIGNED_INT_SAMPLER_BUFFER usamplerBuffer},
//{GL_UNSIGNED_INT_SAMPLER_2D_RECT usampler2DRect},
//{GL_IMAGE_1D image1D},
//{GL_IMAGE_2D image2D},
//{GL_IMAGE_3D image3D},
//{GL_IMAGE_2D_RECT image2DRect},
//{GL_IMAGE_CUBE imageCube},
//{GL_IMAGE_BUFFER imageBuffer},
//{GL_IMAGE_1D_ARRAY image1DArray},
//{GL_IMAGE_2D_ARRAY image2DArray},
//{GL_IMAGE_2D_MULTISAMPLE image2DMS},
//{GL_IMAGE_2D_MULTISAMPLE_ARRAY image2DMSArray},
//{GL_INT_IMAGE_1D iimage1D},
//{GL_INT_IMAGE_2D iimage2D},
//{GL_INT_IMAGE_3D iimage3D},
//{GL_INT_IMAGE_2D_RECT iimage2DRect},
//{GL_INT_IMAGE_CUBE iimageCube},
//{GL_INT_IMAGE_BUFFER iimageBuffer},
//{GL_INT_IMAGE_1D_ARRAY iimage1DArray},
//{GL_INT_IMAGE_2D_ARRAY iimage2DArray},
//{GL_INT_IMAGE_2D_MULTISAMPLE iimage2DMS},
//{GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY iimage2DMSArray},
//{GL_UNSIGNED_INT_IMAGE_1D uimage1D},
//{GL_UNSIGNED_INT_IMAGE_2D uimage2D},
//{GL_UNSIGNED_INT_IMAGE_3D uimage3D},
//{GL_UNSIGNED_INT_IMAGE_2D_RECT uimage2DRect},
//{GL_UNSIGNED_INT_IMAGE_CUBE uimageCube},
//{GL_UNSIGNED_INT_IMAGE_BUFFER uimageBuffer},
//{GL_UNSIGNED_INT_IMAGE_1D_ARRAY uimage1DArray},
//{GL_UNSIGNED_INT_IMAGE_2D_ARRAY uimage2DArray},
//{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS},
//{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray},
//{GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint}
};
int GLBackend::makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings,
@ -550,4 +605,3 @@ void GLBackend::makeProgramBindings(ShaderObject& shaderObject) {
qCWarning(gpugllogging) << "GLShader::makeBindings - failed to link after assigning slotBindings?";
}
}

View file

@ -29,18 +29,19 @@ void GLBackend::resetPipelineState(State::Signature nextSignature) {
}
}
// Default line width accross the board
glLineWidth(1.0f);
#if !defined(USE_GLES)
// force a few states regardless
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
// Point size is always on
// FIXME CORE
//glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
glEnable(GL_PROGRAM_POINT_SIZE_EXT);
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
// Default line width accross the board
glLineWidth(1.0f);
glEnable(GL_LINE_SMOOTH);
#endif
}
@ -48,17 +49,19 @@ void GLBackend::syncPipelineStateCache() {
State::Data state;
// force a few states regardless
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
// Point size is always on
// FIXME CORE
//glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
glEnable(GL_PROGRAM_POINT_SIZE_EXT);
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
// Default line width accross the board
glLineWidth(1.0f);
#if !defined(USE_GLES)
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
// Point size is always on
//glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
glEnable(GL_PROGRAM_POINT_SIZE_EXT);
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
glEnable(GL_LINE_SMOOTH);
#endif
getCurrentGLState(state);
State::Signature signature = State::evalSignature(state);
@ -70,11 +73,13 @@ void GLBackend::syncPipelineStateCache() {
void GLBackend::do_setStateFillMode(int32 mode) {
if (_pipeline._stateCache.fillMode != mode) {
#if !defined(USE_GLES)
static GLenum GL_FILL_MODES[] = { GL_POINT, GL_LINE, GL_FILL };
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL_MODES[mode]);
(void)CHECK_GL_ERROR();
_pipeline._stateCache.fillMode = State::FillMode(mode);
#endif
}
}
@ -106,14 +111,15 @@ void GLBackend::do_setStateFrontFaceClockwise(bool isClockwise) {
void GLBackend::do_setStateDepthClampEnable(bool enable) {
if (_pipeline._stateCache.depthClampEnable != enable) {
#if !defined(USE_GLES)
if (enable) {
glEnable(GL_DEPTH_CLAMP);
} else {
glDisable(GL_DEPTH_CLAMP);
}
(void)CHECK_GL_ERROR();
_pipeline._stateCache.depthClampEnable = enable;
#endif
}
}
@ -132,6 +138,7 @@ void GLBackend::do_setStateScissorEnable(bool enable) {
void GLBackend::do_setStateMultisampleEnable(bool enable) {
if (_pipeline._stateCache.multisampleEnable != enable) {
#if !defined(USE_GLES)
if (enable) {
glEnable(GL_MULTISAMPLE);
} else {
@ -140,11 +147,13 @@ void GLBackend::do_setStateMultisampleEnable(bool enable) {
(void)CHECK_GL_ERROR();
_pipeline._stateCache.multisampleEnable = enable;
#endif
}
}
void GLBackend::do_setStateAntialiasedLineEnable(bool enable) {
if (_pipeline._stateCache.antialisedLineEnable != enable) {
#if !defined(USE_GLES)
if (enable) {
glEnable(GL_LINE_SMOOTH);
} else {
@ -153,6 +162,7 @@ void GLBackend::do_setStateAntialiasedLineEnable(bool enable) {
(void)CHECK_GL_ERROR();
_pipeline._stateCache.antialisedLineEnable = enable;
#endif
}
}
@ -160,13 +170,17 @@ void GLBackend::do_setStateDepthBias(Vec2 bias) {
if ((bias.x != _pipeline._stateCache.depthBias) || (bias.y != _pipeline._stateCache.depthBiasSlopeScale)) {
if ((bias.x != 0.0f) || (bias.y != 0.0f)) {
glEnable(GL_POLYGON_OFFSET_FILL);
#if !defined(USE_GLES)
glEnable(GL_POLYGON_OFFSET_LINE);
glEnable(GL_POLYGON_OFFSET_POINT);
#endif
glPolygonOffset(bias.x, bias.y);
} else {
glDisable(GL_POLYGON_OFFSET_FILL);
#if !defined(USE_GLES)
glDisable(GL_POLYGON_OFFSET_LINE);
glDisable(GL_POLYGON_OFFSET_POINT);
#endif
}
(void)CHECK_GL_ERROR();

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