Merged with master

This commit is contained in:
Olivier Prat 2017-10-10 12:11:23 +02:00
commit db9a9e723c
604 changed files with 32315 additions and 10180 deletions

1
.gitattributes vendored
View file

@ -10,6 +10,7 @@
*.json text
*.js text
*.qml text
*.qrc text
*.slf text
*.slh text
*.slv text

5
.gitignore vendored
View file

@ -12,6 +12,11 @@ ext/
Makefile
*.user
# Android Studio
*.iml
local.properties
android/libraries
# Xcode
*.xcodeproj
*.xcworkspace

View file

@ -1,19 +1,56 @@
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Android specific instructions are found in this file.
### Android Dependencies
# Android Dependencies
You will need the following tools to build our Android targets.
* [cmake](http://www.cmake.org/download/) ~> 3.5.1
* [Qt](http://www.qt.io/download-open-source/#) ~> 5.6.2
* [ant](http://ant.apache.org/bindownload.cgi) ~> 1.9.4
* [Android NDK](https://developer.android.com/tools/sdk/ndk/index.html) ~> r10d
* [Android SDK](http://developer.android.com/sdk/installing/index.html) ~> 24.4.1.1
* Install the latest Platform-tools
* Install the latest Build-tools
* Install the SDK Platform for API Level 19
* Install Sources for Android SDK for API Level 19
* Install the ARM EABI v7a System Image if you want to run an emulator.
* [Qt](http://www.qt.io/download-open-source/#) ~> 5.9.1
* [Android Studio](https://developer.android.com/studio/index.html)
* [Google VR SDK](https://github.com/googlevr/gvr-android-sdk/releases)
* [Gradle](https://gradle.org/releases/)
### Qt
Download the Qt online installer. Run the installer and select the android_armv7 binaries. Installing to the default path is recommended
### Android Studio
Download the Android Studio installer and run it. Once installed, at the welcome screen, click configure in the lower right corner and select SDK manager
From the SDK Platforms tab, select API level 26.
* Install the ARM EABI v7a System Image if you want to run an emulator.
From the SDK Tools tab select the following
* Android SDK Build-Tools
* GPU Debugging Tools
* CMake (even if you have a separate CMake installation)
* LLDB
* Android SDK Platform-Tools
* Android SDK Tools
* Android SDK Tools
* NDK (even if you have the NDK installed separately)
### Google VR SDK
Download the 1.8 Google VR SDK [release](https://github.com/googlevr/gvr-android-sdk/archive/v1.80.0.zip). Unzip the archive to a location on your drive.
### Gradle
Download [Gradle 4.1](https://services.gradle.org/distributions/gradle-4.1-all.zip) and unzip it on your local drive. You may wish to add the location of the bin directory within the archive to your path
#### Set up machine specific Gradle properties
Create a `gradle.properties` file in ~/.gradle. Edit the file to contain the following
QT5_ROOT=C\:\\Qt\\5.9.1\\android_armv7
GVR_ROOT=C\:\\Android\\gvr-android-sdk
Replace the paths with your local installations of Qt5 and the Google VR SDK
# TODO fix the rest
You will also need to cross-compile the dependencies required for all platforms for Android, and help CMake find these compiled libraries on your machine.

View file

@ -1,29 +1,28 @@
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only OS X specific instructions are found in this file.
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only macOS specific instructions are found in this file.
### Homebrew
[Homebrew](https://brew.sh/) is an excellent package manager for OS X. It makes install of some High Fidelity dependencies very simple.
[Homebrew](https://brew.sh/) is an excellent package manager for macOS. It makes install of some High Fidelity dependencies very simple.
brew tap homebrew/versions
brew install cmake openssl
brew install cmake openssl qt
### OpenSSL
Assuming you've installed OpenSSL using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR so CMake can find your installations.
For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR:
export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2h_1/
export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2l
Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change.
### Qt
Download and install the [Qt 5.6.2 for macOS](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-mac-x64-clang-5.6.2.dmg).
Assuming you've installed Qt using the homebrew instructions above, you'll need to set QT_CMAKE_PREFIX_PATH so CMake can find your installations.
For Qt installed via homebrew, set QT_CMAKE_PREFIX_PATH:
Keep the default components checked when going through the installer.
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt/5.9.1/lib/cmake
Once Qt is installed, you need to manually configure the following:
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.2/5.6/clang_64/lib/cmake/` directory.
Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change.
### Xcode

View file

@ -1,13 +1,16 @@
if (WIN32)
# 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)
cmake_minimum_required(VERSION 3.7)
else()
cmake_minimum_required(VERSION 3.2)
endif()
include("cmake/init.cmake")
project(hifi)
include("cmake/init.cmake")
include("cmake/compiler.cmake")
if (NOT DEFINED SERVER_ONLY)
@ -54,11 +57,13 @@ endif()
file(GLOB_RECURSE CMAKE_SRC cmake/*.cmake cmake/CMakeLists.txt)
add_custom_target(cmake SOURCES ${CMAKE_SRC})
GroupSources("cmake")
unset(CMAKE_SRC)
file(GLOB_RECURSE JS_SRC scripts/*.js unpublishedScripts/*.js)
add_custom_target(js SOURCES ${JS_SRC})
GroupSources("scripts")
GroupSources("unpublishedScripts")
unset(JS_SRC)
# Locate the required Qt build on the filesystem
setup_qt()
@ -77,6 +82,12 @@ option(USE_NSIGHT "Attempt to find the nSight libraries" 1)
set_packaging_parameters()
# FIXME hack to work on the proper Android toolchain
if (ANDROID)
add_subdirectory(android/app)
return()
endif()
# add subdirectories for all targets
if (BUILD_SERVER)
add_subdirectory(assignment-client)
@ -101,9 +112,8 @@ if (BUILD_CLIENT OR BUILD_SERVER)
add_subdirectory(plugins)
endif()
if (BUILD_TOOLS)
add_subdirectory(tools)
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)

View file

@ -0,0 +1,8 @@
set(TARGET_NAME native-lib)
setup_hifi_library()
link_hifi_libraries(shared networking gl gpu gpu-gles render-utils)
autoscribe_shader_lib(gpu model render render-utils)
target_opengl()
target_link_libraries(native-lib android log m)
target_include_directories(native-lib PRIVATE "${GVR_ROOT}/libraries/headers")
target_link_libraries(native-lib "C:/Users/bdavis/Git/hifi/android/libraries/jni/armeabi-v7a/libgvr.so")

57
android/app/build.gradle Normal file
View file

@ -0,0 +1,57 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion "26.0.1"
defaultConfig {
applicationId "org.saintandreas.testapp"
minSdkVersion 24
targetSdkVersion 26
versionCode 1
versionName "1.0"
ndk { abiFilters 'armeabi-v7a' }
externalNativeBuild {
cmake {
arguments '-DHIFI_ANDROID=1',
'-DANDROID_PLATFORM=android-24',
'-DANDROID_TOOLCHAIN=clang',
'-DANDROID_STL=gnustl_shared',
'-DGVR_ROOT=' + GVR_ROOT,
'-DNATIVE_SCRIBE=c:/bin/scribe.exe',
"-DHIFI_ANDROID_PRECOMPILED=${project.rootDir}/libraries/jni/armeabi-v7a"
}
}
jackOptions { enabled true }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
jniLibs.srcDirs += '../libraries/jni';
}
}
externalNativeBuild {
cmake {
path '../../CMakeLists.txt'
}
}
}
dependencies {
compile fileTree(dir: "${project.rootDir}/libraries/jar", include: 'QtAndroid-bundled.jar')
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.vr:sdk-audio:1.80.0'
compile 'com.google.vr:sdk-base:1.80.0'
}
build.dependsOn(':extractQt5')

25
android/app/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Android\SDK/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.saintandreas.testapp">
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="26" />
<uses-feature android:glEsVersion="0x00030001" android:required="true" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true"/>
<uses-feature android:name="android.hardware.sensor.gyroscope" android:required="true"/>
<uses-feature android:name="android.software.vr.mode" android:required="false"/>
<uses-feature android:name="android.hardware.vr.high_performance" android:required="false"/>
<application
android:allowBackup="true"
android:theme="@style/VrActivityTheme"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden|screenSize"
android:enableVrMode="@string/gvr_vr_mode_component"
android:resizeableActivity="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="com.google.intent.category.DAYDREAM"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="com.google.intent.category.CARDBOARD" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -0,0 +1,50 @@
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <vr/gvr/capi/include/gvr.h>
namespace googlevr {
// Convert a GVR matrix to GLM matrix
glm::mat4 toGlm(const gvr::Mat4f &matrix) {
glm::mat4 result;
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
result[j][i] = matrix.m[i][j];
}
}
return result;
}
// Given a field of view in degrees, compute the corresponding projection
// matrix.
glm::mat4 perspectiveMatrixFromView(const gvr::Rectf& fov, float z_near, float z_far) {
const float x_left = -std::tan(fov.left * M_PI / 180.0f) * z_near;
const float x_right = std::tan(fov.right * M_PI / 180.0f) * z_near;
const float y_bottom = -std::tan(fov.bottom * M_PI / 180.0f) * z_near;
const float y_top = std::tan(fov.top * M_PI / 180.0f) * z_near;
const float Y = (2 * z_near) / (y_top - y_bottom);
const float A = (x_right + x_left) / (x_right - x_left);
const float B = (y_top + y_bottom) / (y_top - y_bottom);
const float C = (z_near + z_far) / (z_near - z_far);
const float D = (2 * z_near * z_far) / (z_near - z_far);
glm::mat4 result { 0 };
result[2][0] = A;
result[1][1] = Y;
result[2][1] = B;
result[2][2] = C;
result[3][2] = D;
result[2][3] = -1;
return result;
}
glm::quat toGlm(const gvr::ControllerQuat& q) {
glm::quat result;
result.w = q.qw;
result.x = q.qx;
result.y = q.qy;
result.z = q.qz;
return result;
}
}

View file

@ -0,0 +1,78 @@
#include <jni.h>
#include <android/log.h>
#include <QtCore/QDebug>
#include "renderer.h"
int QtMsgTypeToAndroidPriority(QtMsgType type) {
int priority = ANDROID_LOG_UNKNOWN;
switch (type) {
case QtDebugMsg: priority = ANDROID_LOG_DEBUG; break;
case QtWarningMsg: priority = ANDROID_LOG_WARN; break;
case QtCriticalMsg: priority = ANDROID_LOG_ERROR; break;
case QtFatalMsg: priority = ANDROID_LOG_FATAL; break;
case QtInfoMsg: priority = ANDROID_LOG_INFO; break;
default: break;
}
return priority;
}
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
__android_log_write(QtMsgTypeToAndroidPriority(type), "Interface", message.toStdString().c_str());
}
static jlong toJni(NativeRenderer *renderer) {
return reinterpret_cast<intptr_t>(renderer);
}
static NativeRenderer *fromJni(jlong renderer) {
return reinterpret_cast<NativeRenderer*>(renderer);
}
#define JNI_METHOD(r, name) JNIEXPORT r JNICALL Java_org_saintandreas_testapp_MainActivity_##name
extern "C" {
JNI_METHOD(jlong, nativeCreateRenderer)
(JNIEnv *env, jclass clazz, jobject class_loader, jobject android_context, jlong native_gvr_api) {
qInstallMessageHandler(messageHandler);
#if defined(GVR)
auto gvrContext = reinterpret_cast<gvr_context *>(native_gvr_api);
return toJni(new NativeRenderer(gvrContext));
#else
return toJni(new NativeRenderer(nullptr));
#endif
}
JNI_METHOD(void, nativeDestroyRenderer)
(JNIEnv *env, jclass clazz, jlong renderer) {
delete fromJni(renderer);
}
JNI_METHOD(void, nativeInitializeGl)
(JNIEnv *env, jobject obj, jlong renderer) {
fromJni(renderer)->InitializeGl();
}
JNI_METHOD(void, nativeDrawFrame)
(JNIEnv *env, jobject obj, jlong renderer) {
fromJni(renderer)->DrawFrame();
}
JNI_METHOD(void, nativeOnTriggerEvent)
(JNIEnv *env, jobject obj, jlong renderer) {
fromJni(renderer)->OnTriggerEvent();
}
JNI_METHOD(void, nativeOnPause)
(JNIEnv *env, jobject obj, jlong renderer) {
fromJni(renderer)->OnPause();
}
JNI_METHOD(void, nativeOnResume)
(JNIEnv *env, jobject obj, jlong renderer) {
fromJni(renderer)->OnResume();
}
} // extern "C"

View file

@ -0,0 +1,636 @@
#include "renderer.h"
#include <mutex>
#include <QtCore/QDebug>
#include <gl/Config.h>
#include "GoogleVRHelpers.h"
#include <gl/GLShaders.h>
#include <shared/Bilateral.h>
#include <gpu/DrawTransformUnitQuad_vert.h>
#include <gpu/DrawTexcoordRectTransformUnitQuad_vert.h>
#include <gpu/DrawViewportQuadTransformTexcoord_vert.h>
#include <gpu/DrawTexture_frag.h>
#include <gpu/DrawTextureOpaque_frag.h>
#include <gpu/DrawColoredTexture_frag.h>
#include <render-utils/simple_vert.h>
#include <render-utils/simple_frag.h>
#include <render-utils/simple_textured_frag.h>
#include <render-utils/simple_textured_unlit_frag.h>
#include <render-utils/deferred_light_vert.h>
#include <render-utils/deferred_light_point_vert.h>
#include <render-utils/deferred_light_spot_vert.h>
#include <render-utils/directional_ambient_light_frag.h>
#include <render-utils/directional_skybox_light_frag.h>
#include <render-utils/standardTransformPNTC_vert.h>
#include <render-utils/standardDrawTexture_frag.h>
#include <render-utils/model_vert.h>
#include <render-utils/model_shadow_vert.h>
#include <render-utils/model_normal_map_vert.h>
#include <render-utils/model_lightmap_vert.h>
#include <render-utils/model_lightmap_normal_map_vert.h>
#include <render-utils/skin_model_vert.h>
#include <render-utils/skin_model_shadow_vert.h>
#include <render-utils/skin_model_normal_map_vert.h>
#include <render-utils/model_frag.h>
#include <render-utils/model_shadow_frag.h>
#include <render-utils/model_normal_map_frag.h>
#include <render-utils/model_normal_specular_map_frag.h>
#include <render-utils/model_specular_map_frag.h>
#include <render-utils/model_lightmap_frag.h>
#include <render-utils/model_lightmap_normal_map_frag.h>
#include <render-utils/model_lightmap_normal_specular_map_frag.h>
#include <render-utils/model_lightmap_specular_map_frag.h>
#include <render-utils/model_translucent_frag.h>
#include <render-utils/overlay3D_vert.h>
#include <render-utils/overlay3D_frag.h>
#include <render-utils/sdf_text3D_vert.h>
#include <render-utils/sdf_text3D_frag.h>
#if 0
#include <model/skybox_vert.h>
#include <model/skybox_frag.h>
#include <entities-renderer/textured_particle_frag.h>
#include <entities-renderer/textured_particle_vert.h>
#include <entities-renderer/paintStroke_vert.h>
#include <entities-renderer/paintStroke_frag.h>
#include <entities-renderer/polyvox_vert.h>
#include <entities-renderer/polyvox_frag.h>
#endif
template <typename F>
void withFrameBuffer(gvr::Frame& frame, int32_t index, F f) {
frame.BindBuffer(index);
f();
frame.Unbind();
}
static const uint64_t kPredictionTimeWithoutVsyncNanos = 50000000;
// Each shader has two variants: a single-eye ES 2.0 variant, and a multiview
// ES 3.0 variant. The multiview vertex shaders use transforms defined by
// arrays of mat4 uniforms, using gl_ViewID_OVR to determine the array index.
#define UNIFORM_LIGHT_POS 20
#define UNIFORM_M 16
#define UNIFORM_MV 8
#define UNIFORM_MVP 0
#if 0
uniform Transform { // API uses “Transform[2]” to refer to instance 2
mat4 u_MVP[2];
mat4 u_MVMatrix[2];
mat4 u_Model;
vec3 u_LightPos[2];
};
static const char *kDiffuseLightingVertexShader = R"glsl(
#version 300 es
#extension GL_OVR_multiview2 : enable
layout(num_views=2) in;
layout(location = 0) uniform mat4 u_MVP[2];
layout(location = 8) uniform mat4 u_MVMatrix[2];
layout(location = 16) uniform mat4 u_Model;
layout(location = 20) uniform vec3 u_LightPos[2];
layout(location = 0) in vec4 a_Position;
layout(location = 1) in vec4 a_Color;
layout(location = 2) in vec3 a_Normal;
out vec4 v_Color;
out vec3 v_Grid;
void main() {
mat4 mvp = u_MVP[gl_ViewID_OVR];
mat4 modelview = u_MVMatrix[gl_ViewID_OVR];
vec3 lightpos = u_LightPos[gl_ViewID_OVR];
v_Grid = vec3(u_Model * a_Position);
vec3 modelViewVertex = vec3(modelview * a_Position);
vec3 modelViewNormal = vec3(modelview * vec4(a_Normal, 0.0));
float distance = length(lightpos - modelViewVertex);
vec3 lightVector = normalize(lightpos - modelViewVertex);
float diffuse = max(dot(modelViewNormal, lightVector), 0.5);
diffuse = diffuse * (1.0 / (1.0 + (0.00001 * distance * distance)));
v_Color = vec4(a_Color.rgb * diffuse, a_Color.a);
gl_Position = mvp * a_Position;
}
)glsl";
#endif
static const char *kSimepleVertexShader = R"glsl(
#version 300 es
#extension GL_OVR_multiview2 : enable
layout(num_views=2) in;
layout(location = 0) in vec4 a_Position;
out vec4 v_Color;
void main() {
v_Color = vec4(a_Position.xyz, 1.0);
gl_Position = vec4(a_Position.xyz, 1.0);
}
)glsl";
static const char *kPassthroughFragmentShader = R"glsl(
#version 300 es
precision mediump float;
in vec4 v_Color;
out vec4 FragColor;
void main() { FragColor = v_Color; }
)glsl";
static void CheckGLError(const char* label) {
int gl_error = glGetError();
if (gl_error != GL_NO_ERROR) {
qWarning("GL error @ %s: %d", label, gl_error);
// Crash immediately to make OpenGL errors obvious.
abort();
}
}
// Contains vertex, normal and other data.
namespace cube {
const std::array<float, 108> CUBE_COORDS{{
// Front face
-1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
// Right face
1.0f, 1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
1.0f, 1.0f, -1.0f,
1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
// Back face
1.0f, 1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
// Left face
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
// Top face
-1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, 1.0f,
1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, -1.0f,
// Bottom face
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, -1.0f,
1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, -1.0f
}};
const std::array<float, 108> CUBE_COLORS{{
// front, green
0.0f, 0.5273f, 0.2656f,
0.0f, 0.5273f, 0.2656f,
0.0f, 0.5273f, 0.2656f,
0.0f, 0.5273f, 0.2656f,
0.0f, 0.5273f, 0.2656f,
0.0f, 0.5273f, 0.2656f,
// right, blue
0.0f, 0.3398f, 0.9023f,
0.0f, 0.3398f, 0.9023f,
0.0f, 0.3398f, 0.9023f,
0.0f, 0.3398f, 0.9023f,
0.0f, 0.3398f, 0.9023f,
0.0f, 0.3398f, 0.9023f,
// back, also green
0.0f, 0.5273f, 0.2656f,
0.0f, 0.5273f, 0.2656f,
0.0f, 0.5273f, 0.2656f,
0.0f, 0.5273f, 0.2656f,
0.0f, 0.5273f, 0.2656f,
0.0f, 0.5273f, 0.2656f,
// left, also blue
0.0f, 0.3398f, 0.9023f,
0.0f, 0.3398f, 0.9023f,
0.0f, 0.3398f, 0.9023f,
0.0f, 0.3398f, 0.9023f,
0.0f, 0.3398f, 0.9023f,
0.0f, 0.3398f, 0.9023f,
// top, red
0.8359375f, 0.17578125f, 0.125f,
0.8359375f, 0.17578125f, 0.125f,
0.8359375f, 0.17578125f, 0.125f,
0.8359375f, 0.17578125f, 0.125f,
0.8359375f, 0.17578125f, 0.125f,
0.8359375f, 0.17578125f, 0.125f,
// bottom, also red
0.8359375f, 0.17578125f, 0.125f,
0.8359375f, 0.17578125f, 0.125f,
0.8359375f, 0.17578125f, 0.125f,
0.8359375f, 0.17578125f, 0.125f,
0.8359375f, 0.17578125f, 0.125f,
0.8359375f, 0.17578125f, 0.125f
}};
const std::array<float, 108> CUBE_NORMALS{{
// Front face
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
// Right face
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
// Back face
0.0f, 0.0f, -1.0f,
0.0f, 0.0f, -1.0f,
0.0f, 0.0f, -1.0f,
0.0f, 0.0f, -1.0f,
0.0f, 0.0f, -1.0f,
0.0f, 0.0f, -1.0f,
// Left face
-1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f,
-1.0f, 0.0f, 0.0f,
// Top face
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
// Bottom face
0.0f, -1.0f, 0.0f,
0.0f, -1.0f, 0.0f,
0.0f, -1.0f, 0.0f,
0.0f, -1.0f, 0.0f,
0.0f, -1.0f, 0.0f,
0.0f, -1.0f, 0.0f
}};
}
namespace triangle {
static std::array<float, 9> TRIANGLE_VERTS {{
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
}};
}
std::array<gvr::BufferViewport, 2> buildViewports(const std::unique_ptr<gvr::GvrApi> &gvrapi) {
return { {gvrapi->CreateBufferViewport(), gvrapi->CreateBufferViewport()} };
};
const std::string VERTEX_SHADER_DEFINES{ R"GLSL(
#version 300 es
#extension GL_EXT_clip_cull_distance : enable
#define GPU_VERTEX_SHADER
#define GPU_SSBO_TRANSFORM_OBJECT 1
#define GPU_TRANSFORM_IS_STEREO
#define GPU_TRANSFORM_STEREO_CAMERA
#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED
#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN
)GLSL" };
const std::string PIXEL_SHADER_DEFINES{ R"GLSL(
#version 300 es
precision mediump float;
#define GPU_PIXEL_SHADER
#define GPU_TRANSFORM_IS_STEREO
#define GPU_TRANSFORM_STEREO_CAMERA
#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED
#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN
)GLSL" };
#if defined(GVR)
NativeRenderer::NativeRenderer(gvr_context *vrContext) :
_gvrapi(new gvr::GvrApi(vrContext, false)),
_viewports(buildViewports(_gvrapi)),
_gvr_viewer_type(_gvrapi->GetViewerType())
#else
NativeRenderer::NativeRenderer(void *vrContext)
#endif
{
start = std::chrono::system_clock::now();
qDebug() << "QQQ" << __FUNCTION__;
}
/**
* Converts a raw text file, saved as a resource, into an OpenGL ES shader.
*
* @param type The type of shader we will be creating.
* @param resId The resource ID of the raw text file.
* @return The shader object handler.
*/
int LoadGLShader(int type, const char *shadercode) {
GLuint result = 0;
std::string shaderError;
static const std::string SHADER_DEFINES;
if (!gl::compileShader(type, shadercode, SHADER_DEFINES, result, shaderError)) {
qWarning() << "QQQ" << __FUNCTION__ << "Shader compile failure" << shaderError.c_str();
}
return result;
}
// Computes a texture size that has approximately half as many pixels. This is
// equivalent to scaling each dimension by approximately sqrt(2)/2.
static gvr::Sizei HalfPixelCount(const gvr::Sizei &in) {
// Scale each dimension by sqrt(2)/2 ~= 7/10ths.
gvr::Sizei out;
out.width = (7 * in.width) / 10;
out.height = (7 * in.height) / 10;
return out;
}
#if defined(GVR)
void NativeRenderer::InitializeVR() {
_gvrapi->InitializeGl();
bool multiviewEnabled = _gvrapi->IsFeatureSupported(GVR_FEATURE_MULTIVIEW);
qWarning() << "QQQ" << __FUNCTION__ << "Multiview enabled " << multiviewEnabled;
// Because we are using 2X MSAA, we can render to half as many pixels and
// achieve similar quality.
_renderSize = HalfPixelCount(_gvrapi->GetMaximumEffectiveRenderTargetSize());
std::vector<gvr::BufferSpec> specs;
specs.push_back(_gvrapi->CreateBufferSpec());
specs[0].SetColorFormat(GVR_COLOR_FORMAT_RGBA_8888);
specs[0].SetDepthStencilFormat(GVR_DEPTH_STENCIL_FORMAT_DEPTH_16);
specs[0].SetSamples(2);
gvr::Sizei half_size = {_renderSize.width / 2, _renderSize.height};
specs[0].SetMultiviewLayers(2);
specs[0].SetSize(half_size);
_swapchain.reset(new gvr::SwapChain(_gvrapi->CreateSwapChain(specs)));
_viewportlist.reset(new gvr::BufferViewportList(_gvrapi->CreateEmptyBufferViewportList()));
}
void NativeRenderer::PrepareFramebuffer() {
const gvr::Sizei recommended_size = HalfPixelCount(
_gvrapi->GetMaximumEffectiveRenderTargetSize());
if (_renderSize.width != recommended_size.width ||
_renderSize.height != recommended_size.height) {
// We need to resize the framebuffer. Note that multiview uses two texture
// layers, each with half the render width.
gvr::Sizei framebuffer_size = recommended_size;
framebuffer_size.width /= 2;
_swapchain->ResizeBuffer(0, framebuffer_size);
_renderSize = recommended_size;
}
}
#endif
void testShaderBuild(const char* vs_src, const char * fs_src) {
std::string error;
GLuint vs, fs;
if (!gl::compileShader(GL_VERTEX_SHADER, vs_src, VERTEX_SHADER_DEFINES, vs, error) ||
!gl::compileShader(GL_FRAGMENT_SHADER, fs_src, PIXEL_SHADER_DEFINES, fs, error)) {
throw std::runtime_error("Failed to compile shader");
}
auto pr = gl::compileProgram({ vs, fs }, error);
if (!pr) {
throw std::runtime_error("Failed to link shader");
}
}
void NativeRenderer::InitializeGl() {
qDebug() << "QQQ" << __FUNCTION__;
//gl::initModuleGl();
#if defined(GVR)
InitializeVR();
#endif
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
const uint32_t vertShader = LoadGLShader(GL_VERTEX_SHADER, kSimepleVertexShader);
//const uint32_t vertShader = LoadGLShader(GL_VERTEX_SHADER, kDiffuseLightingVertexShader);
const uint32_t fragShader = LoadGLShader(GL_FRAGMENT_SHADER, kPassthroughFragmentShader);
std::string error;
_cubeProgram = gl::compileProgram({ vertShader, fragShader }, error);
CheckGLError("build program");
glGenBuffers(1, &_cubeBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _cubeBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 9, triangle::TRIANGLE_VERTS.data(), GL_STATIC_DRAW);
/*
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 108 * 3, NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * 108 * 0, sizeof(float) * 108, cube::CUBE_COORDS.data());
glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * 108 * 1, sizeof(float) * 108, cube::CUBE_COLORS.data());
glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * 108 * 2, sizeof(float) * 108, cube::CUBE_NORMALS.data());
*/
glBindBuffer(GL_ARRAY_BUFFER, 0);
CheckGLError("upload vertices");
glGenVertexArrays(1, &_cubeVao);
glBindBuffer(GL_ARRAY_BUFFER, _cubeBuffer);
glBindVertexArray(_cubeVao);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
/*
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, false, 0, (const void*)(sizeof(float) * 108 * 1) );
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 3, GL_FLOAT, false, 0, (const void*)(sizeof(float) * 108 * 2));
glEnableVertexAttribArray(2);
*/
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
CheckGLError("build vao ");
static std::once_flag once;
std::call_once(once, [&]{
testShaderBuild(sdf_text3D_vert, sdf_text3D_frag);
testShaderBuild(DrawTransformUnitQuad_vert, DrawTexture_frag);
testShaderBuild(DrawTexcoordRectTransformUnitQuad_vert, DrawTexture_frag);
testShaderBuild(DrawViewportQuadTransformTexcoord_vert, DrawTexture_frag);
testShaderBuild(DrawTransformUnitQuad_vert, DrawTextureOpaque_frag);
testShaderBuild(DrawTransformUnitQuad_vert, DrawColoredTexture_frag);
testShaderBuild(simple_vert, simple_frag);
testShaderBuild(simple_vert, simple_textured_frag);
testShaderBuild(simple_vert, simple_textured_unlit_frag);
testShaderBuild(deferred_light_vert, directional_ambient_light_frag);
testShaderBuild(deferred_light_vert, directional_skybox_light_frag);
testShaderBuild(standardTransformPNTC_vert, standardDrawTexture_frag);
testShaderBuild(standardTransformPNTC_vert, DrawTextureOpaque_frag);
testShaderBuild(model_vert, model_frag);
testShaderBuild(model_normal_map_vert, model_normal_map_frag);
testShaderBuild(model_vert, model_specular_map_frag);
testShaderBuild(model_normal_map_vert, model_normal_specular_map_frag);
testShaderBuild(model_vert, model_translucent_frag);
testShaderBuild(model_normal_map_vert, model_translucent_frag);
testShaderBuild(model_lightmap_vert, model_lightmap_frag);
testShaderBuild(model_lightmap_normal_map_vert, model_lightmap_normal_map_frag);
testShaderBuild(model_lightmap_vert, model_lightmap_specular_map_frag);
testShaderBuild(model_lightmap_normal_map_vert, model_lightmap_normal_specular_map_frag);
testShaderBuild(skin_model_vert, model_frag);
testShaderBuild(skin_model_normal_map_vert, model_normal_map_frag);
testShaderBuild(skin_model_vert, model_specular_map_frag);
testShaderBuild(skin_model_normal_map_vert, model_normal_specular_map_frag);
testShaderBuild(skin_model_vert, model_translucent_frag);
testShaderBuild(skin_model_normal_map_vert, model_translucent_frag);
testShaderBuild(model_shadow_vert, model_shadow_frag);
testShaderBuild(overlay3D_vert, overlay3D_frag);
#if 0
testShaderBuild(textured_particle_vert, textured_particle_frag);
testShaderBuild(skybox_vert, skybox_frag);
testShaderBuild(paintStroke_vert,paintStroke_frag);
testShaderBuild(polyvox_vert, polyvox_frag);
#endif
});
qDebug() << "done";
}
static const float kZNear = 1.0f;
static const float kZFar = 100.0f;
static const gvr_rectf fullscreen = {0, 1, 0, 1};
void NativeRenderer::DrawFrame() {
auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now() - start);
glm::vec3 v;
v.r = (float) (now.count() % 1000) / 1000.0f;
v.g = 1.0f - v.r;
v.b = 1.0f;
PrepareFramebuffer();
// A client app does its rendering here.
gvr::ClockTimePoint target_time = gvr::GvrApi::GetTimePointNow();
target_time.monotonic_system_time_nanos += kPredictionTimeWithoutVsyncNanos;
using namespace googlevr;
using namespace bilateral;
const auto gvrHeadPose = _gvrapi->GetHeadSpaceFromStartSpaceRotation(target_time);
_head_view = toGlm(gvrHeadPose);
_viewportlist->SetToRecommendedBufferViewports();
glm::mat4 eye_views[2];
for_each_side([&](bilateral::Side side) {
int eye = index(side);
const gvr::Eye gvr_eye = eye == 0 ? GVR_LEFT_EYE : GVR_RIGHT_EYE;
const auto& eyeView = eye_views[eye] = toGlm(_gvrapi->GetEyeFromHeadMatrix(gvr_eye)) * _head_view;
auto& viewport = _viewports[eye];
_viewportlist->GetBufferViewport(eye, &viewport);
viewport.SetSourceUv(fullscreen);
viewport.SetSourceLayer(eye);
_viewportlist->SetBufferViewport(eye, viewport);
const auto &mvc = _modelview_cube[eye] = eyeView * _model_cube;
const auto &mvf = _modelview_floor[eye] = eyeView * _model_floor;
const gvr_rectf fov = viewport.GetSourceFov();
const glm::mat4 perspective = perspectiveMatrixFromView(fov, kZNear, kZFar);
_modelview_projection_cube[eye] = perspective * mvc;
_modelview_projection_floor[eye] = perspective * mvf;
_light_pos_eye_space[eye] = glm::vec3(eyeView * _light_pos_world_space);
});
gvr::Frame frame = _swapchain->AcquireFrame();
withFrameBuffer(frame, 0, [&]{
glClearColor(v.r, v.g, v.b, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, _renderSize.width / 2, _renderSize.height);
glUseProgram(_cubeProgram);
glBindVertexArray(_cubeVao);
glDrawArrays(GL_TRIANGLES, 0, 3);
/*
float* fp;
fp = (float*)&_light_pos_eye_space[0];
glUniform3fv(UNIFORM_LIGHT_POS, 2, fp);
fp = (float*)&_modelview_cube[0];
glUniformMatrix4fv(UNIFORM_MV, 2, GL_FALSE, fp);
fp = (float*)&_modelview_projection_cube[0];
glUniformMatrix4fv(UNIFORM_MVP, 2, GL_FALSE, fp);
fp = (float*)&_model_cube;
glUniformMatrix4fv(UNIFORM_M, 1, GL_FALSE, fp);
glDrawArrays(GL_TRIANGLES, 0, 36);
*/
glBindVertexArray(0);
});
frame.Submit(*_viewportlist, gvrHeadPose);
CheckGLError("onDrawFrame");
}
void NativeRenderer::OnTriggerEvent() {
qDebug() << "QQQ" << __FUNCTION__;
}
void NativeRenderer::OnPause() {
qDebug() << "QQQ" << __FUNCTION__;
_gvrapi->PauseTracking();
}
void NativeRenderer::OnResume() {
qDebug() << "QQQ" << __FUNCTION__;
_gvrapi->ResumeTracking();
_gvrapi->RefreshViewerProfile();
}

View file

@ -0,0 +1,60 @@
#pragma once
#include <chrono>
#include <array>
#include <glm/glm.hpp>
#define GVR
#if defined(GVR)
#include <vr/gvr/capi/include/gvr.h>
#endif
class NativeRenderer {
public:
#if defined(GVR)
NativeRenderer(gvr_context* vrContext);
#else
NativeRenderer(void* vrContext);
#endif
void InitializeGl();
void DrawFrame();
void OnTriggerEvent();
void OnPause();
void OnResume();
private:
std::chrono::time_point<std::chrono::system_clock> start;
#if defined(GVR)
void InitializeVR();
void PrepareFramebuffer();
std::unique_ptr<gvr::GvrApi> _gvrapi;
gvr::ViewerType _gvr_viewer_type;
std::unique_ptr<gvr::BufferViewportList> _viewportlist;
std::unique_ptr<gvr::SwapChain> _swapchain;
std::array<gvr::BufferViewport, 2> _viewports;
gvr::Sizei _renderSize;
#endif
uint32_t _cubeBuffer { 0 };
uint32_t _cubeVao { 0 };
uint32_t _cubeProgram { 0 };
glm::mat4 _head_view;
glm::mat4 _model_cube;
glm::mat4 _camera;
glm::mat4 _view;
glm::mat4 _model_floor;
std::array<glm::mat4, 2> _modelview_cube;
std::array<glm::mat4, 2> _modelview_floor;
std::array<glm::mat4, 2> _modelview_projection_cube;
std::array<glm::mat4, 2> _modelview_projection_floor;
std::array<glm::vec3, 2> _light_pos_eye_space;
const glm::vec4 _light_pos_world_space{ 0, 2, 0, 1};
};

View file

@ -0,0 +1,105 @@
package org.saintandreas.testapp;
import android.app.Activity;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.view.View;
import com.google.vr.ndk.base.AndroidCompat;
import com.google.vr.ndk.base.GvrLayout;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class MainActivity extends Activity {
private final static int IMMERSIVE_STICKY_VIEW_FLAGS = View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
static {
System.loadLibrary("gvr");
System.loadLibrary("native-lib");
}
private long nativeRenderer;
private GvrLayout gvrLayout;
private GLSurfaceView surfaceView;
private native long nativeCreateRenderer(ClassLoader appClassLoader, Context context, long nativeGvrContext);
private native void nativeDestroyRenderer(long renderer);
private native void nativeInitializeGl(long renderer);
private native void nativeDrawFrame(long renderer);
private native void nativeOnTriggerEvent(long renderer);
private native void nativeOnPause(long renderer);
private native void nativeOnResume(long renderer);
class NativeRenderer implements GLSurfaceView.Renderer {
@Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { nativeInitializeGl(nativeRenderer); }
@Override public void onSurfaceChanged(GL10 gl, int width, int height) { }
@Override public void onDrawFrame(GL10 gl) {
nativeDrawFrame(nativeRenderer);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setImmersiveSticky();
getWindow()
.getDecorView()
.setOnSystemUiVisibilityChangeListener((int visibility)->{
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { setImmersiveSticky(); }
});
gvrLayout = new GvrLayout(this);
nativeRenderer = nativeCreateRenderer(
getClass().getClassLoader(),
getApplicationContext(),
gvrLayout.getGvrApi().getNativeGvrContext());
surfaceView = new GLSurfaceView(this);
surfaceView.setEGLContextClientVersion(3);
surfaceView.setEGLConfigChooser(8, 8, 8, 0, 0, 0);
surfaceView.setPreserveEGLContextOnPause(true);
surfaceView.setRenderer(new NativeRenderer());
gvrLayout.setPresentationView(surfaceView);
setContentView(gvrLayout);
if (gvrLayout.setAsyncReprojectionEnabled(true)) {
AndroidCompat.setSustainedPerformanceMode(this, true);
}
AndroidCompat.setVrModeEnabled(this, true);
}
@Override
protected void onDestroy() {
super.onDestroy();
gvrLayout.shutdown();
nativeDestroyRenderer(nativeRenderer);
nativeRenderer = 0;
}
@Override
protected void onPause() {
surfaceView.queueEvent(()->nativeOnPause(nativeRenderer));
surfaceView.onPause();
gvrLayout.onPause();
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
gvrLayout.onResume();
surfaceView.onResume();
surfaceView.queueEvent(()->nativeOnResume(nativeRenderer));
}
private void setImmersiveSticky() {
getWindow().getDecorView().setSystemUiVisibility(IMMERSIVE_STICKY_VIEW_FLAGS);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="white_opaque">#ffffff</color>
</resources>

View file

@ -0,0 +1,3 @@
<resources>
<string name="app_name">TestApp</string>
</resources>

View file

@ -0,0 +1,15 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="android:style/Theme.NoTitleBar.Fullscreen">
<!-- Customize your theme here. -->
<!--item name="android:windowFullscreen">true</item-->
<!--item name="android:windowNoTitle">true</item-->
<!--item name="android:windowActionBar">false</item-->
<!--item name="android:windowContentOverlay">@null</item-->
<!--item name="android:background">@color/white_opaque</item-->
</style>
</resources>

91
android/build.gradle Normal file
View file

@ -0,0 +1,91 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
task extractQt5jars(type: Copy) {
from fileTree(QT5_ROOT + "/jar")
into("${project.rootDir}/libraries/jar")
include("*.jar")
}
task extractQt5so(type: Copy) {
from fileTree(QT5_ROOT + "/lib")
into("${project.rootDir}/libraries/jni/armeabi-v7a/")
include("libQt5AndroidExtras.so")
include("libQt5Concurrent.so")
include("libQt5Core.so")
include("libQt5Gamepad.so")
include("libQt5Gui.so")
include("libQt5Location.so")
include("libQt5Multimedia.so")
include("libQt5MultimediaQuick_p.so")
include("libQt5Network.so")
include("libQt5NetworkAuth.so")
include("libQt5OpenGL.so")
include("libQt5Positioning.so")
include("libQt5Qml.so")
include("libQt5Quick.so")
include("libQt5QuickControls2.so")
include("libQt5QuickParticles.so")
include("libQt5QuickTemplates2.so")
include("libQt5QuickWidgets.so")
include("libQt5Script.so")
include("libQt5ScriptTools.so")
include("libQt5Sensors.so")
include("libQt5Svg.so")
include("libQt5WebChannel.so")
include("libQt5WebSockets.so")
include("libQt5WebView.so")
include("libQt5Widgets.so")
include("libQt5Xml.so")
include("libQt5XmlPatterns.so")
}
task extractAudioSo(type: Copy) {
from zipTree(GVR_ROOT + "/libraries/sdk-audio-1.80.0.aar")
into "${project.rootDir}/libraries/"
include "jni/armeabi-v7a/libgvr_audio.so"
}
task extractGvrSo(type: Copy) {
from zipTree(GVR_ROOT + "/libraries/sdk-base-1.80.0.aar")
into "${project.rootDir}/libraries/"
include "jni/armeabi-v7a/libgvr.so"
}
task extractNdk { }
extractNdk.dependsOn extractAudioSo
extractNdk.dependsOn extractGvrSo
task extractQt5 { }
extractQt5.dependsOn extractQt5so
extractQt5.dependsOn extractQt5jars
task extractBinaries { }
extractBinaries.dependsOn extractQt5
extractBinaries.dependsOn extractNdk
task deleteBinaries(type: Delete) {
delete "${project.rootDir}/libraries/jni"
}
//clean.dependsOn(deleteBinaries)

17
android/gradle.properties Normal file
View file

@ -0,0 +1,17 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

1
android/settings.gradle Normal file
View file

@ -0,0 +1 @@
include ':app'

View file

@ -1162,7 +1162,8 @@ void AssetServer::handleFailedBake(QString originalAssetHash, QString assetPath,
_pendingBakes.remove(originalAssetHash);
}
void AssetServer::handleCompletedBake(QString originalAssetHash, QString originalAssetPath, QVector<QString> bakedFilePaths) {
void AssetServer::handleCompletedBake(QString originalAssetHash, QString originalAssetPath,
QString bakedTempOutputDir, QVector<QString> bakedFilePaths) {
bool errorCompletingBake { false };
QString errorReason;
@ -1234,6 +1235,16 @@ void AssetServer::handleCompletedBake(QString originalAssetHash, QString origina
}
}
for (auto& filePath : bakedFilePaths) {
QFile file(filePath);
if (!file.remove()) {
qWarning() << "Failed to remove temporary file:" << filePath;
}
}
if (!QDir(bakedTempOutputDir).rmdir(".")) {
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);

View file

@ -100,7 +100,8 @@ private:
void bakeAsset(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath);
/// Move baked content for asset to baked directory and update baked status
void handleCompletedBake(QString originalAssetHash, QString assetPath, QVector<QString> bakedFilePaths);
void handleCompletedBake(QString originalAssetHash, QString assetPath, QString bakedTempOutputDir,
QVector<QString> bakedFilePaths);
void handleFailedBake(QString originalAssetHash, QString assetPath, QString errors);
void handleAbortedBake(QString originalAssetHash, QString assetPath);

View file

@ -24,20 +24,39 @@ BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetP
}
void cleanupTempFiles(QString tempOutputDir, std::vector<QString> files) {
for (const auto& filename : files) {
QFile f { filename };
if (!f.remove()) {
qDebug() << "Failed to remove:" << filename;
}
}
if (!tempOutputDir.isEmpty()) {
QDir dir { tempOutputDir };
if (!dir.rmdir(".")) {
qDebug() << "Failed to remove temporary directory:" << tempOutputDir;
}
}
};
void BakeAssetTask::run() {
_isBaking.store(true);
qRegisterMetaType<QVector<QString> >("QVector<QString>");
TextureBakerThreadGetter fn = []() -> QThread* { return QThread::currentThread(); };
QString tempOutputDir;
if (_assetPath.endsWith(".fbx")) {
tempOutputDir = PathUtils::generateTemporaryDir();
_baker = std::unique_ptr<FBXBaker> {
new FBXBaker(QUrl("file:///" + _filePath), fn, PathUtils::generateTemporaryDir())
new FBXBaker(QUrl("file:///" + _filePath), fn, tempOutputDir)
};
} else {
tempOutputDir = PathUtils::generateTemporaryDir();
_baker = std::unique_ptr<TextureBaker> {
new TextureBaker(QUrl("file:///" + _filePath), image::TextureUsage::CUBE_TEXTURE,
PathUtils::generateTemporaryDir())
tempOutputDir)
};
}
@ -52,6 +71,8 @@ void BakeAssetTask::run() {
_wasAborted.store(true);
cleanupTempFiles(tempOutputDir, _baker->getOutputFiles());
emit bakeAborted(_assetHash, _assetPath);
} else if (_baker->hasErrors()) {
qDebug() << "Failed to bake: " << _assetHash << _assetPath << _baker->getErrors();
@ -60,6 +81,8 @@ void BakeAssetTask::run() {
_didFinish.store(true);
cleanupTempFiles(tempOutputDir, _baker->getOutputFiles());
emit bakeFailed(_assetHash, _assetPath, errors);
} else {
auto vectorOutputFiles = QVector<QString>::fromStdVector(_baker->getOutputFiles());
@ -68,7 +91,7 @@ void BakeAssetTask::run() {
_didFinish.store(true);
emit bakeComplete(_assetHash, _assetPath, vectorOutputFiles);
emit bakeComplete(_assetHash, _assetPath, tempOutputDir, vectorOutputFiles);
}
}

View file

@ -35,7 +35,7 @@ public:
bool didFinish() const { return _didFinish.load(); }
signals:
void bakeComplete(QString assetHash, QString assetPath, QVector<QString> outputFiles);
void bakeComplete(QString assetHash, QString assetPath, QString tempOutputDir, QVector<QString> outputFiles);
void bakeFailed(QString assetHash, QString assetPath, QString errors);
void bakeAborted(QString assetHash, QString assetPath);

View file

@ -0,0 +1,53 @@
//
// EntityPriorityQueue.cpp
// assignment-client/src/entities
//
// Created by Andrew Meadows 2017.08.08
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "EntityPriorityQueue.h"
const float PrioritizedEntity::DO_NOT_SEND = -1.0e-6f;
const float PrioritizedEntity::FORCE_REMOVE = -1.0e-5f;
const float PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY = 1.0f;
void ConicalView::set(const ViewFrustum& viewFrustum) {
// The ConicalView has two parts: a central sphere (same as ViewFrustum) and a circular cone that bounds the frustum part.
// Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum.
_position = viewFrustum.getPosition();
_direction = viewFrustum.getDirection();
// We cache the sin and cos of the half angle of the cone that bounds the frustum.
// (the math here is left as an exercise for the reader)
float A = viewFrustum.getAspectRatio();
float t = tanf(0.5f * viewFrustum.getFieldOfView());
_cosAngle = 1.0f / sqrtf(1.0f + (A * A + 1.0f) * (t * t));
_sinAngle = sqrtf(1.0f - _cosAngle * _cosAngle);
_radius = viewFrustum.getCenterRadius();
}
float ConicalView::computePriority(const AACube& cube) const {
glm::vec3 p = cube.calcCenter() - _position; // position of bounding sphere in view-frame
float d = glm::length(p); // distance to center of bounding sphere
float r = 0.5f * cube.getScale(); // radius of bounding sphere
if (d < _radius + r) {
return r;
}
// We check the angle between the center of the cube and the _direction of the view.
// If it is less than the sum of the half-angle from center of cone to outer edge plus
// the half apparent angle of the bounding sphere then it is in view.
//
// The math here is left as an exercise for the reader with the following hints:
// (1) We actually check the dot product of the cube's local position rather than the angle and
// (2) we take advantage of this trig identity: cos(A+B) = cos(A)*cos(B) - sin(A)*sin(B)
if (glm::dot(p, _direction) > sqrtf(d * d - r * r) * _cosAngle - r * _sinAngle) {
const float AVOID_DIVIDE_BY_ZERO = 0.001f;
return r / (d + AVOID_DIVIDE_BY_ZERO);
}
return PrioritizedEntity::DO_NOT_SEND;
}

View file

@ -0,0 +1,66 @@
//
// EntityPriorityQueue.h
// assignment-client/src/entities
//
// Created by Andrew Meadows 2017.08.08
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_EntityPriorityQueue_h
#define hifi_EntityPriorityQueue_h
#include <queue>
#include <AACube.h>
#include <EntityTreeElement.h>
const float SQRT_TWO_OVER_TWO = 0.7071067811865f;
const float DEFAULT_VIEW_RADIUS = 10.0f;
// ConicalView is an approximation of a ViewFrustum for fast calculation of sort priority.
class ConicalView {
public:
ConicalView() {}
ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); }
void set(const ViewFrustum& viewFrustum);
float computePriority(const AACube& cube) const;
private:
glm::vec3 _position { 0.0f, 0.0f, 0.0f };
glm::vec3 _direction { 0.0f, 0.0f, 1.0f };
float _sinAngle { SQRT_TWO_OVER_TWO };
float _cosAngle { SQRT_TWO_OVER_TWO };
float _radius { DEFAULT_VIEW_RADIUS };
};
// PrioritizedEntity is a placeholder in a sorted queue.
class PrioritizedEntity {
public:
static const float DO_NOT_SEND;
static const float FORCE_REMOVE;
static const float WHEN_IN_DOUBT_PRIORITY;
PrioritizedEntity(EntityItemPointer entity, float priority, bool forceRemove = false) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority), _forceRemove(forceRemove) {}
EntityItemPointer getEntity() const { return _weakEntity.lock(); }
EntityItem* getRawEntityPointer() const { return _rawEntityPointer; }
float getPriority() const { return _priority; }
bool shouldForceRemove() const { return _forceRemove; }
class Compare {
public:
bool operator() (const PrioritizedEntity& A, const PrioritizedEntity& B) { return A._priority < B._priority; }
};
friend class Compare;
private:
EntityItemWeakPointer _weakEntity;
EntityItem* _rawEntityPointer;
float _priority;
bool _forceRemove;
};
using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector<PrioritizedEntity>, PrioritizedEntity::Compare >;
#endif // hifi_EntityPriorityQueue_h

View file

@ -13,9 +13,18 @@
#include <EntityNodeData.h>
#include <EntityTypes.h>
#include <OctreeUtils.h>
#include "EntityServer.h"
EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) :
OctreeSendThread(myServer, node)
{
connect(std::static_pointer_cast<EntityTree>(myServer->getOctree()).get(), &EntityTree::editingEntityPointer, this, &EntityTreeSendThread::editingEntityPointer, Qt::QueuedConnection);
connect(std::static_pointer_cast<EntityTree>(myServer->getOctree()).get(), &EntityTree::deletingEntityPointer, this, &EntityTreeSendThread::deletingEntityPointer, Qt::QueuedConnection);
}
void EntityTreeSendThread::preDistributionProcessing() {
auto node = _node.toStrongRef();
auto nodeData = static_cast<EntityNodeData*>(node->getLinkedData());
@ -80,6 +89,72 @@ void EntityTreeSendThread::preDistributionProcessing() {
}
}
void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
bool viewFrustumChanged, bool isFullScene) {
if (viewFrustumChanged || _traversal.finished()) {
ViewFrustum viewFrustum;
nodeData->copyCurrentViewFrustum(viewFrustum);
EntityTreeElementPointer root = std::dynamic_pointer_cast<EntityTreeElement>(_myServer->getOctree()->getRoot());
int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
startNewTraversal(viewFrustum, root, lodLevelOffset, nodeData->getUsesFrustum());
// When the viewFrustum changed the sort order may be incorrect, so we re-sort
// and also use the opportunity to cull anything no longer in view
if (viewFrustumChanged && !_sendQueue.empty()) {
EntityPriorityQueue prevSendQueue;
_sendQueue.swap(prevSendQueue);
_entitiesInQueue.clear();
// Re-add elements from previous traversal if they still need to be sent
float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
while (!prevSendQueue.empty()) {
EntityItemPointer entity = prevSendQueue.top().getEntity();
bool forceRemove = prevSendQueue.top().shouldForceRemove();
prevSendQueue.pop();
if (entity) {
if (!forceRemove) {
bool success = false;
AACube cube = entity->getQueryAACube(success);
if (success) {
if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
float priority = _conicalView.computePriority(cube);
if (priority != PrioritizedEntity::DO_NOT_SEND) {
float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
float angularDiameter = cube.getScale() / distance;
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
_sendQueue.push(PrioritizedEntity(entity, priority));
_entitiesInQueue.insert(entity.get());
}
}
}
} else {
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
_entitiesInQueue.insert(entity.get());
}
} else {
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true));
_entitiesInQueue.insert(entity.get());
}
}
}
}
}
if (!_traversal.finished()) {
quint64 startTime = usecTimestampNow();
#ifdef DEBUG
const uint64_t TIME_BUDGET = 400; // usec
#else
const uint64_t TIME_BUDGET = 200; // usec
#endif
_traversal.traverse(TIME_BUDGET);
OctreeServer::trackTreeTraverseTime((float)(usecTimestampNow() - startTime));
}
OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);
}
bool EntityTreeSendThread::addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID,
EntityItem& entityItem, EntityNodeData& nodeData) {
// check if this entity has a parent that is also an entity
@ -129,4 +204,288 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil
return hasNewChild || hasNewDescendants;
}
void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum) {
DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesViewFrustum);
// there are three types of traversal:
//
// (1) FirstTime = at login --> find everything in view
// (2) Repeat = view hasn't changed --> find what has changed since last complete traversal
// (3) Differential = view has changed --> find what has changed or in new view but not old
//
// The "scanCallback" we provide to the traversal depends on the type:
//
// The _conicalView is updated here as a cached view approximation used by the lambdas for efficient
// computation of entity sorting priorities.
//
_conicalView.set(_traversal.getCurrentView());
switch (type) {
case DiffTraversal::First:
// When we get to a First traversal, clear the _knownState
_knownState.clear();
if (usesViewFrustum) {
float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
_traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) {
next.element->forEachEntity([=](EntityItemPointer entity) {
// Bail early if we've already checked this entity this frame
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
return;
}
bool success = false;
AACube cube = entity->getQueryAACube(success);
if (success) {
if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
// Check the size of the entity, it's possible that a "too small to see" entity is included in a
// larger octree cell because of its position (for example if it crosses the boundary of a cell it
// pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
// before we consider including it.
float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
float angularDiameter = cube.getScale() / distance;
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
float priority = _conicalView.computePriority(cube);
_sendQueue.push(PrioritizedEntity(entity, priority));
_entitiesInQueue.insert(entity.get());
}
}
} else {
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
_entitiesInQueue.insert(entity.get());
}
});
});
} else {
_traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
next.element->forEachEntity([this](EntityItemPointer entity) {
// Bail early if we've already checked this entity this frame
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
return;
}
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
_entitiesInQueue.insert(entity.get());
});
});
}
break;
case DiffTraversal::Repeat:
if (usesViewFrustum) {
float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
_traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) {
uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();
if (next.element->getLastChangedContent() > startOfCompletedTraversal) {
next.element->forEachEntity([=](EntityItemPointer entity) {
// Bail early if we've already checked this entity this frame
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
return;
}
auto knownTimestamp = _knownState.find(entity.get());
if (knownTimestamp == _knownState.end()) {
bool success = false;
AACube cube = entity->getQueryAACube(success);
if (success) {
if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
// See the DiffTraversal::First case for an explanation of the "entity is too small" check
float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
float angularDiameter = cube.getScale() / distance;
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
float priority = _conicalView.computePriority(cube);
_sendQueue.push(PrioritizedEntity(entity, priority));
_entitiesInQueue.insert(entity.get());
}
}
} else {
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
_entitiesInQueue.insert(entity.get());
}
} else if (entity->getLastEdited() > knownTimestamp->second) {
// it is known and it changed --> put it on the queue with any priority
// TODO: sort these correctly
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
_entitiesInQueue.insert(entity.get());
}
});
}
});
} else {
_traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();
if (next.element->getLastChangedContent() > startOfCompletedTraversal) {
next.element->forEachEntity([this](EntityItemPointer entity) {
// Bail early if we've already checked this entity this frame
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
return;
}
auto knownTimestamp = _knownState.find(entity.get());
if (knownTimestamp == _knownState.end() || entity->getLastEdited() > knownTimestamp->second) {
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
_entitiesInQueue.insert(entity.get());
}
});
}
});
}
break;
case DiffTraversal::Differential:
assert(usesViewFrustum);
float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
float completedLODScaleFactor = _traversal.getCompletedLODScaleFactor();
glm::vec3 completedViewPosition = _traversal.getCompletedView().getPosition();
_traversal.setScanCallback([=] (DiffTraversal::VisibleElement& next) {
next.element->forEachEntity([=](EntityItemPointer entity) {
// Bail early if we've already checked this entity this frame
if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
return;
}
auto knownTimestamp = _knownState.find(entity.get());
if (knownTimestamp == _knownState.end()) {
bool success = false;
AACube cube = entity->getQueryAACube(success);
if (success) {
if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
// See the DiffTraversal::First case for an explanation of the "entity is too small" check
float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
float angularDiameter = cube.getScale() / distance;
if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
if (!_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) {
float priority = _conicalView.computePriority(cube);
_sendQueue.push(PrioritizedEntity(entity, priority));
_entitiesInQueue.insert(entity.get());
} else {
// If this entity was skipped last time because it was too small, we still need to send it
distance = glm::distance(cube.calcCenter(), completedViewPosition) + MIN_VISIBLE_DISTANCE;
angularDiameter = cube.getScale() / distance;
if (angularDiameter <= MIN_ENTITY_ANGULAR_DIAMETER * completedLODScaleFactor) {
// this object was skipped in last completed traversal
float priority = _conicalView.computePriority(cube);
_sendQueue.push(PrioritizedEntity(entity, priority));
_entitiesInQueue.insert(entity.get());
}
}
}
}
} else {
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
_entitiesInQueue.insert(entity.get());
}
} else if (entity->getLastEdited() > knownTimestamp->second) {
// it is known and it changed --> put it on the queue with any priority
// TODO: sort these correctly
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
_entitiesInQueue.insert(entity.get());
}
});
});
break;
}
}
bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) {
if (_sendQueue.empty()) {
OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME);
return false;
}
quint64 encodeStart = usecTimestampNow();
if (!_packetData.hasContent()) {
// This is the beginning of a new packet.
// We pack minimal data for this to be accepted as an OctreeElement payload for the root element.
// The Octree header bytes look like this:
//
// 0x00 octalcode for root
// 0x00 colors (1 bit where recipient should call: child->readElementDataFromBuffer())
// 0xXX childrenInTreeMask (when params.includeExistsBits is true: 1 bit where child is existant)
// 0x00 childrenInBufferMask (1 bit where recipient should call: child->readElementData() recursively)
const uint8_t zeroByte = 0;
_packetData.appendValue(zeroByte); // octalcode
_packetData.appendValue(zeroByte); // colors
if (params.includeExistsBits) {
uint8_t childrenExistBits = 0;
EntityTreeElementPointer root = std::dynamic_pointer_cast<EntityTreeElement>(_myServer->getOctree()->getRoot());
for (int32_t i = 0; i < NUMBER_OF_CHILDREN; ++i) {
if (root->getChildAtIndex(i)) {
childrenExistBits += (1 << i);
}
}
_packetData.appendValue(childrenExistBits); // childrenInTreeMask
}
_packetData.appendValue(zeroByte); // childrenInBufferMask
// Pack zero for numEntities.
// But before we do: grab current byteOffset so we can come back later
// and update this with the real number.
_numEntities = 0;
_numEntitiesOffset = _packetData.getUncompressedByteOffset();
_packetData.appendValue(_numEntities);
}
LevelDetails entitiesLevel = _packetData.startLevel();
uint64_t sendTime = usecTimestampNow();
auto nodeData = static_cast<OctreeQueryNode*>(params.nodeData);
nodeData->stats.encodeStarted();
while(!_sendQueue.empty()) {
PrioritizedEntity queuedItem = _sendQueue.top();
EntityItemPointer entity = queuedItem.getEntity();
if (entity) {
// Only send entities that match the jsonFilters, but keep track of everything we've tried to send so we don't try to send it again
if (entity->matchesJSONFilters(jsonFilters)) {
OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData);
if (appendEntityState != OctreeElement::COMPLETED) {
if (appendEntityState == OctreeElement::PARTIAL) {
++_numEntities;
}
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
break;
}
++_numEntities;
}
if (queuedItem.shouldForceRemove()) {
_knownState.erase(entity.get());
} else {
_knownState[entity.get()] = sendTime;
}
}
_sendQueue.pop();
_entitiesInQueue.erase(entity.get());
}
nodeData->stats.encodeStopped();
if (_sendQueue.empty()) {
assert(_entitiesInQueue.empty());
params.stopReason = EncodeBitstreamParams::FINISHED;
_extraEncodeData->entities.clear();
}
if (_numEntities == 0) {
_packetData.discardLevel(entitiesLevel);
OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart));
return false;
}
_packetData.endLevel(entitiesLevel);
_packetData.updatePriorBytes(_numEntitiesOffset, (const unsigned char*)&_numEntities, sizeof(_numEntities));
OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart));
return true;
}
void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer& entity) {
if (entity) {
if (_entitiesInQueue.find(entity.get()) == _entitiesInQueue.end() && _knownState.find(entity.get()) != _knownState.end()) {
bool success = false;
AACube cube = entity->getQueryAACube(success);
if (success) {
// We can force a removal from _knownState if the current view is used and entity is out of view
if (_traversal.doesCurrentUseViewFrustum() && !_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true));
_entitiesInQueue.insert(entity.get());
}
} else {
_sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true));
_entitiesInQueue.insert(entity.get());
}
}
}
}
void EntityTreeSendThread::deletingEntityPointer(EntityItem* entity) {
_knownState.erase(entity);
}

View file

@ -12,24 +12,55 @@
#ifndef hifi_EntityTreeSendThread_h
#define hifi_EntityTreeSendThread_h
#include <unordered_set>
#include "../octree/OctreeSendThread.h"
#include <DiffTraversal.h>
#include "EntityPriorityQueue.h"
class EntityNodeData;
class EntityItem;
class EntityTreeSendThread : public OctreeSendThread {
Q_OBJECT
public:
EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) {};
EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node);
protected:
virtual void preDistributionProcessing() override;
void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
bool viewFrustumChanged, bool isFullScene) override;
private:
// the following two methods return booleans to indicate if any extra flagged entities were new additions to set
bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum);
bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override;
void preDistributionProcessing() override;
bool hasSomethingToSend(OctreeQueryNode* nodeData) override { return !_sendQueue.empty(); }
bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) override { return viewFrustumChanged || _traversal.finished(); }
void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) override {};
bool shouldTraverseAndSend(OctreeQueryNode* nodeData) override { return true; }
DiffTraversal _traversal;
EntityPriorityQueue _sendQueue;
std::unordered_set<EntityItem*> _entitiesInQueue;
std::unordered_map<EntityItem*, uint64_t> _knownState;
ConicalView _conicalView; // cached optimized view for fast priority calculations
// packet construction stuff
EntityTreeElementExtraEncodeDataPointer _extraEncodeData { new EntityTreeElementExtraEncodeData() };
int32_t _numEntitiesOffset { 0 };
uint16_t _numEntities { 0 };
private slots:
void editingEntityPointer(const EntityItemPointer& entity);
void deletingEntityPointer(EntityItem* entity);
};
#endif // hifi_EntityTreeSendThread_h

View file

@ -17,7 +17,6 @@
#include <udt/PacketHeaders.h>
#include <PerfStat.h>
#include "OctreeQueryNode.h"
#include "OctreeSendThread.h"
#include "OctreeServer.h"
#include "OctreeServerConsts.h"
@ -27,8 +26,8 @@ quint64 startSceneSleepTime = 0;
quint64 endSceneSleepTime = 0;
OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) :
_myServer(myServer),
_node(node),
_myServer(myServer),
_nodeUuid(node->getUUID())
{
QString safeServerName("Octree");
@ -48,7 +47,7 @@ OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePoint
OctreeSendThread::~OctreeSendThread() {
setIsShuttingDown();
QString safeServerName("Octree");
if (_myServer) {
safeServerName = _myServer->getMyServerName();
@ -301,9 +300,25 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
return numPackets;
}
void OctreeSendThread::preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) {
// If we're starting a full scene, then definitely we want to empty the elementBag
if (isFullScene) {
nodeData->elementBag.deleteAll();
}
// This is the start of "resending" the scene.
bool dontRestartSceneOnMove = false; // this is experimental
if (dontRestartSceneOnMove) {
if (nodeData->elementBag.isEmpty()) {
nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
}
} else {
nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
}
}
/// Version of octree element distributor that sends the deepest LOD level at once
int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged) {
OctreeServer::didPacketDistributor(this);
// if shutting down, exit early
@ -311,7 +326,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
return 0;
}
if (nodeData->elementBag.isEmpty()) {
if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) {
// if we're about to do a fresh pass,
// give our pre-distribution processing a chance to do what it needs
preDistributionProcessing();
@ -345,7 +360,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
// If the current view frustum has changed OR we have nothing to send, then search against
// the current view frustum for things to send.
if (viewFrustumChanged || nodeData->elementBag.isEmpty()) {
if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) {
// if our view has changed, we need to reset these things...
if (viewFrustumChanged) {
@ -367,11 +382,6 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
_packetsSentThisInterval += handlePacketSend(node, nodeData, isFullScene);
// If we're starting a full scene, then definitely we want to empty the elementBag
if (isFullScene) {
nodeData->elementBag.deleteAll();
}
// TODO: add these to stats page
//::startSceneSleepTime = _usleepTime;
@ -380,19 +390,11 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged,
_myServer->getOctree()->getRoot(), _myServer->getJurisdiction());
// This is the start of "resending" the scene.
bool dontRestartSceneOnMove = false; // this is experimental
if (dontRestartSceneOnMove) {
if (nodeData->elementBag.isEmpty()) {
nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
}
} else {
nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
}
preStartNewScene(nodeData, isFullScene);
}
// If we have something in our elementBag, then turn them into packets and send them out...
if (!nodeData->elementBag.isEmpty()) {
if (shouldTraverseAndSend(nodeData)) {
quint64 start = usecTimestampNow();
traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);
@ -441,7 +443,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
// the octree elements from the current view frustum
if (nodeData->elementBag.isEmpty()) {
if (!hasSomethingToSend(nodeData)) {
nodeData->updateLastKnownViewFrustum();
nodeData->setViewSent(true);
@ -458,7 +460,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
return _truePacketsSent;
}
bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params) {
bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) {
bool somethingToSend = false;
OctreeQueryNode* nodeData = static_cast<OctreeQueryNode*>(params.nodeData);
if (!nodeData->elementBag.isEmpty()) {
@ -502,8 +504,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP,
viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale,
isFullScene, _myServer->getJurisdiction(), nodeData);
// Our trackSend() function is implemented by the server subclass, and will be called back
// during the encodeTreeBitstream() as new entities/data elements are sent
// Our trackSend() function is implemented by the server subclass, and will be called back as new entities/data elements are sent
params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
_myServer->trackSend(dataID, dataEdited, _nodeUuid);
};
@ -513,7 +514,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
}
bool somethingToSend = true; // assume we have something
bool bagHadSomething = !nodeData->elementBag.isEmpty();
bool hadSomething = hasSomethingToSend(nodeData);
while (somethingToSend && _packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) {
float compressAndWriteElapsedUsec = OctreeServer::SKIP_TIME;
float packetSendingElapsedUsec = OctreeServer::SKIP_TIME;
@ -523,7 +524,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
bool lastNodeDidntFit = false; // assume each node fits
params.stopReason = EncodeBitstreamParams::UNKNOWN; // reset params.stopReason before traversal
somethingToSend = traverseTreeAndBuildNextPacketPayload(params);
somethingToSend = traverseTreeAndBuildNextPacketPayload(params, nodeData->getJSONParameters());
if (params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
lastNodeDidntFit = true;
@ -531,7 +532,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
}
// If the bag had contents but is now empty then we know we've sent the entire scene.
bool completedScene = bagHadSomething && nodeData->elementBag.isEmpty();
bool completedScene = hadSomething && nodeData->elementBag.isEmpty();
if (completedScene || lastNodeDidntFit) {
// we probably want to flush what has accumulated in nodeData but:
// do we have more data to send? and is there room?

View file

@ -19,6 +19,7 @@
#include <GenericThread.h>
#include <Node.h>
#include <OctreePacketData.h>
#include "OctreeQueryNode.h"
class OctreeQueryNode;
class OctreeServer;
@ -51,24 +52,27 @@ protected:
/// Implements generic processing behavior for this thread.
virtual bool process() override;
/// Called before a packetDistributor pass to allow for pre-distribution processing
virtual void preDistributionProcessing() {};
virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
bool viewFrustumChanged, bool isFullScene);
virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params);
virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters);
OctreeServer* _myServer { nullptr };
OctreePacketData _packetData;
QWeakPointer<Node> _node;
OctreeServer* _myServer { nullptr };
private:
/// Called before a packetDistributor pass to allow for pre-distribution processing
virtual void preDistributionProcessing() {};
int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate = false);
int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged);
virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) { return !nodeData->elementBag.isEmpty(); }
virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) { return viewFrustumChanged || !hasSomethingToSend(nodeData); }
virtual void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene);
virtual bool shouldTraverseAndSend(OctreeQueryNode* nodeData) { return hasSomethingToSend(nodeData); }
QUuid _nodeUuid;
OctreePacketData _packetData;
int _truePacketsSent { 0 }; // available for debug stats
int _trueBytesSent { 0 }; // available for debug stats
int _packetsSentThisInterval { 0 }; // used for bandwidth throttle condition

View file

@ -60,6 +60,8 @@ int OctreeServer::_longTreeWait = 0;
int OctreeServer::_shortTreeWait = 0;
int OctreeServer::_noTreeWait = 0;
SimpleMovingAverage OctreeServer::_averageTreeTraverseTime(MOVING_AVERAGE_SAMPLE_COUNTS);
SimpleMovingAverage OctreeServer::_averageNodeWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS);
SimpleMovingAverage OctreeServer::_averageCompressAndWriteTime(MOVING_AVERAGE_SAMPLE_COUNTS);
@ -106,6 +108,8 @@ void OctreeServer::resetSendingStats() {
_shortTreeWait = 0;
_noTreeWait = 0;
_averageTreeTraverseTime.reset();
_averageNodeWaitTime.reset();
_averageCompressAndWriteTime.reset();
@ -522,6 +526,10 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
(double)_averageTreeExtraLongWaitTime.getAverage(),
(double)(extraLongVsTotal * AS_PERCENT), _extraLongTreeWait);
// traverse
float averageTreeTraverseTime = getAverageTreeTraverseTime();
statsString += QString().sprintf(" Average tree traverse time: %9.2f usecs\r\n\r\n", (double)averageTreeTraverseTime);
// encode
float averageEncodeTime = getAverageEncodeTime();
statsString += QString().sprintf(" Average encode time: %9.2f usecs\r\n", (double)averageEncodeTime);
@ -883,7 +891,7 @@ OctreeServer::UniqueSendThread OctreeServer::newSendThread(const SharedNodePoint
OctreeServer::UniqueSendThread OctreeServer::createSendThread(const SharedNodePointer& node) {
auto sendThread = newSendThread(node);
// we want to be notified when the thread finishes
connect(sendThread.get(), &GenericThread::finished, this, &OctreeServer::removeSendThread);
sendThread->initialize(true);
@ -905,13 +913,13 @@ void OctreeServer::handleOctreeQueryPacket(QSharedPointer<ReceivedMessage> messa
// need to make sure we have it in our nodeList.
auto nodeList = DependencyManager::get<NodeList>();
nodeList->updateNodeWithDataFromPacket(message, senderNode);
auto it = _sendThreads.find(senderNode->getUUID());
if (it == _sendThreads.end()) {
_sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode));
} else if (it->second->isShuttingDown()) {
_sendThreads.erase(it); // Remove right away and wait on thread to be
_sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode));
}
}
@ -1085,7 +1093,7 @@ void OctreeServer::readConfiguration() {
if (getPayload().size() > 0) {
parsePayload();
}
const QJsonObject& settingsObject = DependencyManager::get<NodeList>()->getDomainHandler().getSettingsObject();
QString settingsKey = getMyDomainSettingsKey();
@ -1212,9 +1220,9 @@ void OctreeServer::run() {
OctreeElement::resetPopulationStatistics();
_tree = createTree();
_tree->setIsServer(true);
qDebug() << "Waiting for connection to domain to request settings from domain-server.";
// wait until we have the domain-server settings, otherwise we bail
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
connect(&domainHandler, &DomainHandler::settingsReceived, this, &OctreeServer::domainSettingsRequestComplete);
@ -1225,9 +1233,9 @@ void OctreeServer::run() {
}
void OctreeServer::domainSettingsRequestComplete() {
auto nodeList = DependencyManager::get<NodeList>();
// we need to ask the DS about agents so we can ping/reply with them
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
@ -1237,26 +1245,26 @@ void OctreeServer::domainSettingsRequestComplete() {
packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket");
packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacement");
packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURL");
readConfiguration();
beforeRun(); // after payload has been processed
connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer)));
connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
#ifndef WIN32
setvbuf(stdout, NULL, _IOLBF, 0);
#endif
nodeList->linkedDataCreateCallback = [this](Node* node) {
auto queryNodeData = createOctreeQueryNode();
queryNodeData->init();
node->setLinkedData(std::move(queryNodeData));
};
srand((unsigned)time(0));
// if we want Persistence, set up the local file and persist thread
if (_wantPersist) {
// If persist filename does not exist, let's see if there is one beside the application binary
@ -1351,24 +1359,24 @@ void OctreeServer::domainSettingsRequestComplete() {
}
}
qDebug() << "Backups will be stored in: " << _backupDirectoryPath;
// now set up PersistThread
_persistThread = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval,
_wantBackup, _settings, _debugTimestampNow, _persistAsFileType);
_persistThread->initialize(true);
}
// set up our jurisdiction broadcaster...
if (_jurisdiction) {
_jurisdiction->setNodeType(getMyNodeType());
}
_jurisdictionSender = new JurisdictionSender(_jurisdiction, getMyNodeType());
_jurisdictionSender->initialize(true);
// set up our OctreeServerPacketProcessor
_octreeInboundPacketProcessor = new OctreeInboundPacketProcessor(this);
_octreeInboundPacketProcessor->initialize(true);
// Convert now to tm struct for local timezone
tm* localtm = localtime(&_started);
const int MAX_TIME_LENGTH = 128;
@ -1380,7 +1388,7 @@ void OctreeServer::domainSettingsRequestComplete() {
if (gmtm) {
strftime(utcBuffer, MAX_TIME_LENGTH, " [%m/%d/%Y %X UTC]", gmtm);
}
qDebug() << "Now running... started at: " << localBuffer << utcBuffer;
}
@ -1391,7 +1399,7 @@ void OctreeServer::nodeAdded(SharedNodePointer node) {
void OctreeServer::nodeKilled(SharedNodePointer node) {
quint64 start = usecTimestampNow();
// Shutdown send thread
auto it = _sendThreads.find(node->getUUID());
if (it != _sendThreads.end()) {
@ -1437,13 +1445,13 @@ void OctreeServer::aboutToFinish() {
if (_jurisdictionSender) {
_jurisdictionSender->terminating();
}
// Shut down all the send threads
for (auto& it : _sendThreads) {
auto& sendThread = *it.second;
sendThread.setIsShuttingDown();
}
// Clear will destruct all the unique_ptr to OctreeSendThreads which will call the GenericThread's dtor
// which waits on the thread to be done before returning
_sendThreads.clear(); // Cleans up all the send threads.
@ -1563,7 +1571,7 @@ void OctreeServer::sendStatsPacket() {
threadsStats["2. packetDistributor"] = (double)howManyThreadsDidPacketDistributor(oneSecondAgo);
threadsStats["3. handlePacektSend"] = (double)howManyThreadsDidHandlePacketSend(oneSecondAgo);
threadsStats["4. writeDatagram"] = (double)howManyThreadsDidCallWriteDatagram(oneSecondAgo);
QJsonObject statsArray1;
statsArray1["1. configuration"] = getConfiguration();
statsArray1["2. detailed_stats_url"] = getStatusLink();
@ -1571,13 +1579,13 @@ void OctreeServer::sendStatsPacket() {
statsArray1["4. persistFileLoadTime"] = getFileLoadTime();
statsArray1["5. clients"] = getCurrentClientCount();
statsArray1["6. threads"] = threadsStats;
// Octree Stats
QJsonObject octreeStats;
octreeStats["1. elementCount"] = (double)OctreeElement::getNodeCount();
octreeStats["2. internalElementCount"] = (double)OctreeElement::getInternalNodeCount();
octreeStats["3. leafElementCount"] = (double)OctreeElement::getLeafNodeCount();
// Stats Object 2
QJsonObject dataObject1;
dataObject1["1. totalPackets"] = (double)OctreeSendThread::_totalPackets;
@ -1590,12 +1598,12 @@ void OctreeServer::sendStatsPacket() {
QJsonObject timingArray1;
timingArray1["1. avgLoopTime"] = getAverageLoopTime();
timingArray1["2. avgInsideTime"] = getAverageInsideTime();
timingArray1["3. avgTreeLockTime"] = getAverageTreeWaitTime();
timingArray1["3. avgTreeTraverseTime"] = getAverageTreeTraverseTime();
timingArray1["4. avgEncodeTime"] = getAverageEncodeTime();
timingArray1["5. avgCompressAndWriteTime"] = getAverageCompressAndWriteTime();
timingArray1["6. avgSendTime"] = getAveragePacketSendingTime();
timingArray1["7. nodeWaitTime"] = getAverageNodeWaitTime();
QJsonObject statsObject2;
statsObject2["data"] = dataObject1;
statsObject2["timing"] = timingArray1;
@ -1615,18 +1623,18 @@ void OctreeServer::sendStatsPacket() {
timingArray2["4. avgProcessTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerElement();
timingArray2["5. avgLockWaitTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement();
}
QJsonObject statsObject3;
statsObject3["data"] = dataArray2;
statsObject3["timing"] = timingArray2;
// Merge everything
QJsonObject jsonArray;
jsonArray["1. misc"] = statsArray1;
jsonArray["2. octree"] = octreeStats;
jsonArray["3. outbound"] = statsObject2;
jsonArray["4. inbound"] = statsObject3;
QJsonObject statsObject;
statsObject[QString(getMyServerName()) + "Server"] = jsonArray;
addPacketStatsAndSendStatsPacket(statsObject);

View file

@ -96,6 +96,9 @@ public:
static void trackTreeWaitTime(float time);
static float getAverageTreeWaitTime() { return _averageTreeWaitTime.getAverage(); }
static void trackTreeTraverseTime(float time) { _averageTreeTraverseTime.updateAverage(time); }
static float getAverageTreeTraverseTime() { return _averageTreeTraverseTime.getAverage(); }
static void trackNodeWaitTime(float time) { _averageNodeWaitTime.updateAverage(time); }
static float getAverageNodeWaitTime() { return _averageNodeWaitTime.getAverage(); }
@ -228,6 +231,8 @@ protected:
static int _shortTreeWait;
static int _noTreeWait;
static SimpleMovingAverage _averageTreeTraverseTime;
static SimpleMovingAverage _averageNodeWaitTime;
static SimpleMovingAverage _averageCompressAndWriteTime;

View file

@ -102,7 +102,7 @@ static const QString ENTITY_SCRIPT_SERVER_LOGGING_NAME = "entity-script-server";
void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
// These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them
// about each other.
if (senderNode->getCanRez() || senderNode->getCanRezTmp()) {
if (senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified()) {
auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID));
if (_entityViewer.getTree() && !_shuttingDown) {
@ -116,7 +116,7 @@ void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer<Rec
void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
// These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them
// about each other.
if (senderNode->getCanRez() || senderNode->getCanRezTmp()) {
if (senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified()) {
MessageID messageID;
message->readPrimitive(&messageID);
auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID));

View file

@ -1,82 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- IMPORTANT: Do not manually manipulate this automatically generated file, changes will be gone after the next build! -->
<manifest package="${ANDROID_APK_PACKAGE}" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="${ANDROID_APK_VERSION_NAME}" android:versionCode="${ANDROID_APK_VERSION_CODE}" android:installLocation="auto">
<application
android:hardwareAccelerated="true"
android:name="org.qtproject.qt5.android.bindings.QtApplication"
android:label="@string/AppDisplayName"
android:icon="@drawable/icon"
android:debuggable="${ANDROID_APK_DEBUGGABLE}">
<!-- VR MODE -->
<meta-data android:name="com.samsung.android.vr.application.mode" android:value="vr_only"/>
<activity
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|locale|fontScale|keyboard|keyboardHidden|navigation"
android:name="${ANDROID_ACTIVITY_NAME}"
android:label="@string/AppDisplayName"
android:screenOrientation="landscape"
android:launchMode="singleTop"
${ANDROID_APK_THEME}>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs" android:value="-- %%INSERT_LOCAL_LIBS%% --"/>
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!-- <meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/> -->
${ANDROID_EXTRA_ACTIVITY_XML}
</activity>
<activity
android:name="com.oculusvr.vrlib.PlatformActivity"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
android:launchMode="singleTask"
android:screenOrientation="landscape"
android:configChanges="screenSize|orientation|keyboardHidden|keyboard">
</activity>
${ANDROID_EXTRA_APPLICATION_XML}
</application>
<uses-sdk android:minSdkVersion="${ANDROID_API_LEVEL}" android:targetSdkVersion="${ANDROID_API_LEVEL}"/>
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- camera permission required for GEAR VR passthrough camera -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
<!-- Tell the system this app requires OpenGL ES 3.0. -->
<uses-feature android:glEsVersion="0x00030000" android:required="true" />
</manifest>

View file

@ -1,159 +0,0 @@
#
# QtCreateAPK.cmake
#
# Created by Stephen Birarda on 11/18/14.
# Copyright 2013 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
#
#
# OPTIONS
# These options will modify how QtCreateAPK behaves. May be useful if somebody wants to fork.
# For High Fidelity purposes these should not need to be changed.
#
set(ANDROID_THIS_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) # Directory this CMake file is in
if (POLICY CMP0026)
cmake_policy(SET CMP0026 OLD)
endif ()
macro(qt_create_apk)
if(ANDROID_APK_FULLSCREEN)
set(ANDROID_APK_THEME "android:theme=\"@android:style/Theme.NoTitleBar.Fullscreen\"")
else()
set(ANDROID_APK_THEME "")
endif()
if (UPPER_CMAKE_BUILD_TYPE MATCHES RELEASE)
set(ANDROID_APK_DEBUGGABLE "false")
set(ANDROID_APK_RELEASE_LOCAL ${ANDROID_APK_RELEASE})
else ()
set(ANDROID_APK_DEBUGGABLE "true")
set(ANDROID_APK_RELEASE_LOCAL "0")
endif ()
# Create "AndroidManifest.xml"
configure_file("${ANDROID_THIS_DIRECTORY}/AndroidManifest.xml.in" "${ANDROID_APK_BUILD_DIR}/AndroidManifest.xml")
# create "strings.xml"
configure_file("${ANDROID_THIS_DIRECTORY}/strings.xml.in" "${ANDROID_APK_BUILD_DIR}/res/values/strings.xml")
# find androiddeployqt
find_program(ANDROID_DEPLOY_QT androiddeployqt HINTS "${QT_DIR}/bin")
# set the path to our app shared library
set(EXECUTABLE_DESTINATION_PATH "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}/lib${TARGET_NAME}.so")
# add our dependencies to the deployment file
get_property(_DEPENDENCIES TARGET ${TARGET_NAME} PROPERTY INTERFACE_LINK_LIBRARIES)
foreach(_IGNORE_COPY IN LISTS IGNORE_COPY_LIBS)
list(REMOVE_ITEM _DEPENDENCIES ${_IGNORE_COPY})
endforeach()
foreach(_DEP IN LISTS _DEPENDENCIES)
if (NOT TARGET ${_DEP})
list(APPEND _DEPS_LIST ${_DEP})
else ()
if(NOT _DEP MATCHES "Qt5::.*")
get_property(_DEP_LOCATION TARGET ${_DEP} PROPERTY "LOCATION_${CMAKE_BUILD_TYPE}")
# recurisvely add libraries which are dependencies of this target
get_property(_DEP_DEPENDENCIES TARGET ${_DEP} PROPERTY INTERFACE_LINK_LIBRARIES)
foreach(_SUB_DEP IN LISTS _DEP_DEPENDENCIES)
if (NOT TARGET ${_SUB_DEP} AND NOT _SUB_DEP MATCHES "Qt5::.*")
list(APPEND _DEPS_LIST ${_SUB_DEP})
endif()
endforeach()
list(APPEND _DEPS_LIST ${_DEP_LOCATION})
endif()
endif ()
endforeach()
list(REMOVE_DUPLICATES _DEPS_LIST)
# just copy static libs to apk libs folder - don't add to deps list
foreach(_LOCATED_DEP IN LISTS _DEPS_LIST)
if (_LOCATED_DEP MATCHES "\\.a$")
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${_LOCATED_DEP} "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}"
)
list(REMOVE_ITEM _DEPS_LIST ${_LOCATED_DEP})
endif ()
endforeach()
string(REPLACE ";" "," _DEPS "${_DEPS_LIST}")
configure_file("${ANDROID_THIS_DIRECTORY}/deployment-file.json.in" "${TARGET_NAME}-deployment.json")
# copy the res folder from the target to the apk build dir
add_custom_target(
${TARGET_NAME}-copy-res
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/res" "${ANDROID_APK_BUILD_DIR}/res"
)
# copy the assets folder from the target to the apk build dir
add_custom_target(
${TARGET_NAME}-copy-assets
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/assets" "${ANDROID_APK_BUILD_DIR}/assets"
)
# copy the java folder from src to the apk build dir
add_custom_target(
${TARGET_NAME}-copy-java
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/src/java" "${ANDROID_APK_BUILD_DIR}/src"
)
# copy the libs folder from src to the apk build dir
add_custom_target(
${TARGET_NAME}-copy-libs
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/libs" "${ANDROID_APK_BUILD_DIR}/libs"
)
# handle setup for ndk-gdb
add_custom_target(${TARGET_NAME}-gdb DEPENDS ${TARGET_NAME})
if (ANDROID_APK_DEBUGGABLE)
get_property(TARGET_LOCATION TARGET ${TARGET_NAME} PROPERTY LOCATION)
set(GDB_SOLIB_PATH ${ANDROID_APK_BUILD_DIR}/obj/local/${ANDROID_NDK_ABI_NAME}/)
# generate essential Android Makefiles
file(WRITE ${ANDROID_APK_BUILD_DIR}/jni/Android.mk "APP_ABI := ${ANDROID_NDK_ABI_NAME}\n")
file(WRITE ${ANDROID_APK_BUILD_DIR}/jni/Application.mk "APP_ABI := ${ANDROID_NDK_ABI_NAME}\n")
# create gdb.setup
get_directory_property(PROJECT_INCLUDES DIRECTORY ${PROJECT_SOURCE_DIR} INCLUDE_DIRECTORIES)
string(REGEX REPLACE ";" " " PROJECT_INCLUDES "${PROJECT_INCLUDES}")
file(WRITE ${ANDROID_APK_BUILD_DIR}/libs/${ANDROID_NDK_ABI_NAME}/gdb.setup "set solib-search-path ${GDB_SOLIB_PATH}\n")
file(APPEND ${ANDROID_APK_BUILD_DIR}/libs/${ANDROID_NDK_ABI_NAME}/gdb.setup "directory ${PROJECT_INCLUDES}\n")
# copy lib to obj
add_custom_command(TARGET ${TARGET_NAME}-gdb PRE_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory ${GDB_SOLIB_PATH})
add_custom_command(TARGET ${TARGET_NAME}-gdb PRE_BUILD COMMAND cp ${TARGET_LOCATION} ${GDB_SOLIB_PATH})
# strip symbols
add_custom_command(TARGET ${TARGET_NAME}-gdb PRE_BUILD COMMAND ${CMAKE_STRIP} ${TARGET_LOCATION})
endif ()
# use androiddeployqt to create the apk
add_custom_target(${TARGET_NAME}-apk
COMMAND ${ANDROID_DEPLOY_QT} --input "${TARGET_NAME}-deployment.json" --output "${ANDROID_APK_OUTPUT_DIR}" --android-platform android-${ANDROID_API_LEVEL} ${ANDROID_DEPLOY_QT_INSTALL} --verbose --deployment bundled "\\$(ARGS)"
DEPENDS ${TARGET_NAME} ${TARGET_NAME}-copy-res ${TARGET_NAME}-copy-assets ${TARGET_NAME}-copy-java ${TARGET_NAME}-copy-libs ${TARGET_NAME}-gdb
)
# rename the APK if the caller asked us to
if (ANDROID_APK_CUSTOM_NAME)
add_custom_command(
TARGET ${TARGET_NAME}-apk
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E rename "${ANDROID_APK_OUTPUT_DIR}/bin/QtApp-debug.apk" "${ANDROID_APK_OUTPUT_DIR}/bin/${ANDROID_APK_CUSTOM_NAME}"
)
endif ()
endmacro()

File diff suppressed because it is too large Load diff

View file

@ -1,13 +0,0 @@
{
"qt": "@QT_DIR@",
"sdk": "@ANDROID_SDK_ROOT@",
"ndk": "@ANDROID_NDK@",
"toolchain-prefix": "@ANDROID_TOOLCHAIN_MACHINE_NAME@",
"tool-prefix": "@ANDROID_TOOLCHAIN_MACHINE_NAME@",
"toolchain-version": "@ANDROID_COMPILER_VERSION@",
"ndk-host": "@ANDROID_NDK_HOST_SYSTEM_NAME@",
"target-architecture": "@ANDROID_ABI@",
"application-binary": "@EXECUTABLE_DESTINATION_PATH@",
"android-extra-libs": "@_DEPS@",
"android-package-source-directory": "@ANDROID_APK_BUILD_DIR@"
}

View file

@ -1,11 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<!-- IMPORTANT: Do not manually manipulate this automatically generated file, changes will be gone after the next build! -->
<resources>
<string name="AppDisplayName">${ANDROID_APP_DISPLAY_NAME}</string>
<string name="ministro_not_found_msg">Can\'t find Ministro service.\nThe application can\'t start.</string>
<string name="ministro_needed_msg">This application requires Ministro service. Would you like to install it?</string>
<string name="fatal_error_msg">Your application encountered a fatal error and cannot continue.</string>
</resources>

View file

@ -6,7 +6,7 @@ ExternalProject_Add(
URL https://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.8.zip
URL_MD5 579ac77a3110befa3244d68c0ceb7281
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> ${EXTERNAL_ARGS}
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
LOG_BUILD 1

View file

@ -8,9 +8,6 @@ if (WIN32)
elseif (APPLE)
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb2017_20170604oss_mac_slim.tar.gz)
set(DOWNLOAD_MD5 62bde626b396f8e1a85c6a8ded1d8105)
elseif (ANDROID)
set(DOWNLOAD_URL http://hifi-public.s3.amazonaws.com/dependencies/tbb2017_20170604oss_and_slim.tar.gz)
set(DOWNLOAD_MD5 04d50b64e1d81245a1be5f75f34d64c7)
else ()
set(DOWNLOAD_URL http://hifi-public.s3.amazonaws.com/dependencies/tbb2017_20170604oss_lin_slim.tar.gz)
set(DOWNLOAD_MD5 2a5c721f40fa3503ffc12c18dd00011c)
@ -107,3 +104,4 @@ endif ()
if (DEFINED ${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE "List of tbb include directories")
endif ()

View file

@ -34,10 +34,23 @@ file(GLOB HIFI_CUSTOM_MACROS "cmake/macros/*.cmake")
foreach(CUSTOM_MACRO ${HIFI_CUSTOM_MACROS})
include(${CUSTOM_MACRO})
endforeach()
unset(HIFI_CUSTOM_MACROS)
if (ANDROID)
file(GLOB ANDROID_CUSTOM_MACROS "cmake/android/*.cmake")
foreach(CUSTOM_MACRO ${ANDROID_CUSTOM_MACROS})
include(${CUSTOM_MACRO})
endforeach()
set(BUILD_SHARED_LIBS ON)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
string(REGEX REPLACE "\\\\" "/" ANDROID_NDK ${ANDROID_NDK})
string(REGEX REPLACE "\\\\" "/" CMAKE_TOOLCHAIN_FILE ${CMAKE_TOOLCHAIN_FILE})
string(REGEX REPLACE "\\\\" "/" ANDROID_TOOLCHAIN ${ANDROID_TOOLCHAIN})
string(REGEX REPLACE "\\\\" "/" CMAKE_MAKE_PROGRAM ${CMAKE_MAKE_PROGRAM})
list(APPEND EXTERNAL_ARGS -DANDROID_ABI=${ANDROID_ABI})
list(APPEND EXTERNAL_ARGS -DANDROID_NDK=${ANDROID_NDK})
list(APPEND EXTERNAL_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE})
list(APPEND EXTERNAL_ARGS -DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM})
list(APPEND EXTERNAL_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE})
list(APPEND EXTERNAL_ARGS -DHIFI_ANDROID=${HIFI_ANDROID})
list(APPEND EXTERNAL_ARGS -DANDROID_PLATFORM=${ANDROID_PLATFORM})
list(APPEND EXTERNAL_ARGS -DANDROID_TOOLCHAIN=${ANDROID_TOOLCHAIN})
list(APPEND EXTERNAL_ARGS -DANDROID_STL=${ANDROID_STL})
endif ()

View file

@ -62,7 +62,9 @@ function(AUTOSCRIBE_SHADER SHADER_FILE)
# since it's unrunnable by the cross-compiling build machine
# so, we require the compiling user to point us at a compiled executable version for their native toolchain
find_program(NATIVE_SCRIBE scribe PATHS ${SCRIBE_PATH} ENV SCRIBE_PATH)
if (NOT NATIVE_SCRIBE)
find_program(NATIVE_SCRIBE scribe PATHS ${SCRIBE_PATH} ENV SCRIBE_PATH)
endif()
if (NOT NATIVE_SCRIBE)
message(FATAL_ERROR "The High Fidelity scribe tool is required for shader pre-processing. \

View file

@ -162,5 +162,6 @@ macro(SET_PACKAGING_PARAMETERS)
# create a header file our targets can use to find out the application version
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/includes")
configure_file("${HF_CMAKE_DIR}/templates/BuildInfo.h.in" "${CMAKE_BINARY_DIR}/includes/BuildInfo.h")
include_directories("${CMAKE_BINARY_DIR}/includes")
endmacro(SET_PACKAGING_PARAMETERS)

View file

@ -12,7 +12,7 @@ macro(SETUP_HIFI_LIBRARY)
project(${TARGET_NAME})
# grab the implementation and header files
file(GLOB_RECURSE LIB_SRCS "src/*.h" "src/*.cpp" "src/*.c")
file(GLOB_RECURSE LIB_SRCS "src/*.h" "src/*.cpp" "src/*.c" "src/*.qrc")
list(APPEND ${TARGET_NAME}_SRCS ${LIB_SRCS})
# add compiler flags to AVX source files
@ -65,7 +65,7 @@ macro(SETUP_HIFI_LIBRARY)
list(APPEND ${TARGET_NAME}_DEPENDENCY_QT_MODULES Core)
# find these Qt modules and link them to our own target
find_package(Qt5 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED)
find_package(Qt5 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED CMAKE_FIND_ROOT_PATH_BOTH)
foreach(QT_MODULE ${${TARGET_NAME}_DEPENDENCY_QT_MODULES})
target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE})

View file

@ -28,7 +28,7 @@ function(calculate_default_qt_dir _RESULT_NAME)
set(QT_DEFAULT_ARCH "gcc_64")
endif()
if (WIN32)
if (WIN32 OR (ANDROID AND ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")))
set(QT_DEFAULT_ROOT "c:/Qt")
else()
set(QT_DEFAULT_ROOT "$ENV{HOME}/Qt")
@ -44,7 +44,11 @@ endfunction()
# Sets the QT_CMAKE_PREFIX_PATH and QT_DIR variables
# Also enables CMAKE_AUTOMOC and CMAKE_AUTORCC
macro(setup_qt)
set(QT_CMAKE_PREFIX_PATH "$ENV{QT_CMAKE_PREFIX_PATH}")
# if QT_CMAKE_PREFIX_PATH was not specified before hand,
# try to use the environment variable
if (NOT QT_CMAKE_PREFIX_PATH)
set(QT_CMAKE_PREFIX_PATH "$ENV{QT_CMAKE_PREFIX_PATH}")
endif()
if (("QT_CMAKE_PREFIX_PATH" STREQUAL "") OR (NOT EXISTS "${QT_CMAKE_PREFIX_PATH}"))
calculate_default_qt_dir(QT_DIR)
set(QT_CMAKE_PREFIX_PATH "${QT_DIR}/lib/cmake")
@ -81,4 +85,4 @@ macro(setup_qt)
add_paths_to_fixup_libs("${QT_DIR}/bin")
endif ()
endmacro()
endmacro()

View file

@ -6,9 +6,11 @@
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(TARGET_GLEW)
add_dependency_external_projects(glew)
find_package(GLEW REQUIRED)
add_definitions(-DGLEW_STATIC)
target_include_directories(${TARGET_NAME} PUBLIC ${GLEW_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${GLEW_LIBRARY})
if (NOT ANDROID)
add_definitions(-DGLEW_STATIC)
add_dependency_external_projects(glew)
find_package(GLEW REQUIRED)
target_include_directories(${TARGET_NAME} PUBLIC ${GLEW_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${GLEW_LIBRARY})
endif()
endmacro()

View file

@ -6,15 +6,13 @@
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(TARGET_OPENGL)
add_definitions(-DGLEW_STATIC)
if (APPLE)
# link in required OS X frameworks and include the right GL headers
find_library(OpenGL OpenGL)
target_link_libraries(${TARGET_NAME} ${OpenGL})
elseif(ANDROID)
target_link_libraries(${TARGET_NAME} "-lGLESv3" "-lEGL")
target_link_libraries(${TARGET_NAME} GLESv3 EGL)
else()
target_nsight()
find_package(OpenGL REQUIRED)
if (${OPENGL_INCLUDE_DIR})
include_directories(SYSTEM "${OPENGL_INCLUDE_DIR}")
@ -22,4 +20,6 @@ macro(TARGET_OPENGL)
target_link_libraries(${TARGET_NAME} "${OPENGL_LIBRARY}")
target_include_directories(${TARGET_NAME} PUBLIC ${OPENGL_INCLUDE_DIR})
endif()
target_nsight()
target_glew()
endmacro()

View file

@ -0,0 +1,32 @@
#
# Copyright 2015 High Fidelity, Inc.
# Created by Bradley Austin Davis on 2015/10/10
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(TARGET_OPENSSL)
if (ANDROID)
# FIXME use a distributable binary
set(OPENSSL_INSTALL_DIR C:/Android/openssl)
set(OPENSSL_INCLUDE_DIR "${OPENSSL_INSTALL_DIR}/include" CACHE TYPE INTERNAL)
set(OPENSSL_LIBRARIES "${OPENSSL_INSTALL_DIR}/lib/libcrypto.a;${OPENSSL_INSTALL_DIR}/lib/libssl.a" CACHE TYPE INTERNAL)
else()
find_package(OpenSSL REQUIRED)
if (APPLE AND ${OPENSSL_INCLUDE_DIR} STREQUAL "/usr/include")
# this is a user on OS X using system OpenSSL, which is going to throw warnings since they're deprecating for their common crypto
message(WARNING "The found version of OpenSSL is the OS X system version. This will produce deprecation warnings."
"\nWe recommend you install a newer version (at least 1.0.1h) in a different directory and set OPENSSL_ROOT_DIR in your env so Cmake can find it.")
endif()
endif()
include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES})
endmacro()

View file

@ -0,0 +1,24 @@
#
# Copyright 2015 High Fidelity, Inc.
# Created by Bradley Austin Davis on 2015/10/10
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(TARGET_TBB)
if (ANDROID)
set(TBB_INSTALL_DIR C:/tbb-2018/built)
set(TBB_LIBRARY ${HIFI_ANDROID_PRECOMPILED}/libtbb.so CACHE FILEPATH "TBB library location")
set(TBB_MALLOC_LIBRARY ${HIFI_ANDROID_PRECOMPILED}/libtbbmalloc.so CACHE FILEPATH "TBB malloc library location")
set(TBB_INCLUDE_DIRS ${TBB_INSTALL_DIR}/include CACHE TYPE "List of tbb include directories" CACHE FILEPATH "TBB includes location")
set(TBB_LIBRARIES ${TBB_LIBRARY} ${TBB_MALLOC_LIBRARY})
else()
add_dependency_external_projects(tbb)
find_package(TBB REQUIRED)
endif()
target_link_libraries(${TARGET_NAME} ${TBB_LIBRARIES})
target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${TBB_INCLUDE_DIRS})
endmacro()

View file

@ -1,3 +1,3 @@
// Here you can put a script that will be run by an assignment-client (AC)
// For examples, please go to http://public.highfidelity.io/scripts
// For examples, please go to https://github.com/highfidelity/hifi/tree/master/script-archive/acScripts
// The directory named acScripts contains assignment-client specific scripts you can try.

View file

@ -269,6 +269,8 @@ void DomainGatekeeper::updateNodePermissions() {
userPerms.permissions |= NodePermissions::Permission::canAdjustLocks;
userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities;
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities;
userPerms.permissions |= NodePermissions::Permission::canRezPermanentCertifiedEntities;
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryCertifiedEntities;
userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer;
userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent;
} else {
@ -358,6 +360,8 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
userPerms.permissions |= NodePermissions::Permission::canAdjustLocks;
userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities;
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities;
userPerms.permissions |= NodePermissions::Permission::canRezPermanentCertifiedEntities;
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryCertifiedEntities;
userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer;
userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent;
newNode->setPermissions(userPerms);

View file

@ -959,7 +959,8 @@ bool DomainServer::isInInterestSet(const SharedNodePointer& nodeA, const SharedN
bool isAgentWithoutRights = nodeA->getType() == NodeType::Agent
&& nodeB->getType() == NodeType::EntityScriptServer
&& !nodeA->getCanRez() && !nodeA->getCanRezTmp();
&& !nodeA->getCanRez() && !nodeA->getCanRezTmp()
&& !nodeA->getCanRezCertified() && !nodeA->getCanRezTmpCertified();
if (isAgentWithoutRights) {
return false;
@ -968,7 +969,7 @@ bool DomainServer::isInInterestSet(const SharedNodePointer& nodeA, const SharedN
bool isScriptServerForIneffectiveAgent =
(nodeA->getType() == NodeType::EntityScriptServer && nodeB->getType() == NodeType::Agent)
&& ((nodeBData && !nodeBData->getNodeInterestSet().contains(NodeType::EntityScriptServer))
|| (!nodeB->getCanRez() && !nodeB->getCanRezTmp()));
|| (!nodeB->getCanRez() && !nodeB->getCanRezTmp() && !nodeB->getCanRezCertified() && !nodeB->getCanRezTmpCertified()));
return !isScriptServerForIneffectiveAgent;
} else {

View file

@ -302,6 +302,14 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
_standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canReplaceDomainContent);
packPermissions();
}
if (oldVersion < 1.9) {
unpackPermissions();
// This was prior to addition of canRez(Tmp)Certified; add those to localhost permissions by default
_standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canRezPermanentCertifiedEntities);
_standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canRezTemporaryCertifiedEntities);
packPermissions();
}
}
unpackPermissions();

View file

@ -171,8 +171,6 @@ else ()
add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM})
endif ()
target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes")
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
@ -214,10 +212,6 @@ target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries
target_bullet()
target_opengl()
if (NOT ANDROID)
target_glew()
endif ()
# perform standard include and linking for found externals
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})

View file

@ -14,7 +14,6 @@
var isWindowFocused = true;
var isKeyboardRaised = false;
var isNumericKeyboard = false;
var KEYBOARD_HEIGHT = 200;
function shouldRaiseKeyboard() {
var nodeName = document.activeElement.nodeName;
@ -38,6 +37,19 @@
return document.activeElement.type === "number";
};
function scheduleBringToView(timeout) {
var timer = setTimeout(function () {
clearTimeout(timer);
var elementRect = document.activeElement.getBoundingClientRect();
var absoluteElementTop = elementRect.top + window.scrollY;
var middle = absoluteElementTop - (window.innerHeight / 2);
window.scrollTo(0, middle);
}, timeout);
}
setInterval(function () {
var keyboardRaised = shouldRaiseKeyboard();
var numericKeyboard = shouldSetNumeric();
@ -56,13 +68,8 @@
}
if (!isKeyboardRaised) {
var delta = document.activeElement.getBoundingClientRect().bottom + 10
- (document.body.clientHeight - KEYBOARD_HEIGHT);
if (delta > 0) {
setTimeout(function () {
document.body.scrollTop += delta;
}, 500); // Allow time for keyboard to be raised in QML.
}
scheduleBringToView(250); // Allow time for keyboard to be raised in QML.
// 2DO: should it be rather done from 'client area height changed' event?
}
isKeyboardRaised = keyboardRaised;
@ -70,6 +77,13 @@
}
}, POLL_FREQUENCY);
window.addEventListener("click", function () {
var keyboardRaised = shouldRaiseKeyboard();
if(keyboardRaised && isKeyboardRaised) {
scheduleBringToView(150);
}
});
window.addEventListener("focus", function () {
isWindowFocused = true;
});

View file

@ -27,6 +27,8 @@ ModalWindow {
destroyOnHidden: true
visible: true
readonly property bool isTablet: false
property string iconText: ""
property int iconSize: 50
@ -35,6 +37,10 @@ ModalWindow {
keyboardOverride: true // Disable ModalWindow's keyboard.
function tryDestroy() {
root.destroy()
}
LoginDialog {
id: loginDialog

View file

@ -29,11 +29,12 @@ Item {
readonly property int maxHeight: 720
function resize() {
var targetWidth = Math.max(titleWidth, Math.max(additionalTextContainer.contentWidth,
termsContainer.contentWidth))
if (root.isTablet === false) {
var targetWidth = Math.max(titleWidth, Math.max(additionalTextContainer.contentWidth,
termsContainer.contentWidth))
parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
}
var targetHeight = 5 * hifi.dimensions.contentSpacing.y + buttons.height + additionalTextContainer.height + termsContainer.height
parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth))
parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
}
}
@ -61,11 +62,8 @@ Item {
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Cancel")
onClicked: root.destroy()
onClicked: root.tryDestroy()
}
}
@ -96,17 +94,19 @@ Item {
id: termsContainer
anchors {
top: additionalTextContainer.bottom
left: parent.left
margins: 0
topMargin: 2 * hifi.dimensions.contentSpacing.y
horizontalCenter: parent.horizontalCenter
}
width: parent.width
text: qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
wrapMode: Text.WordWrap
color: hifi.colors.baseGrayHighlight
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
fontSizeMode: Text.HorizontalFit
horizontalAlignment: Text.AlignVCenter
onLinkActivated: loginDialog.openUrl(link)
}
@ -128,8 +128,10 @@ Item {
console.log("Create Failed: " + error)
bodyLoader.source = "UsernameCollisionBody.qml"
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
if (!root.isTablet) {
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
}
}
onHandleLoginCompleted: {
console.log("Login Succeeded")

View file

@ -9,7 +9,7 @@
//
import Hifi 1.0
import QtQuick 2.4
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 as OriginalStyles
@ -45,8 +45,7 @@ Item {
function resize() {
var targetWidth = Math.max(titleWidth, form.contentWidth);
var targetHeight = hifi.dimensions.contentSpacing.y + mainTextContainer.height +
4 * hifi.dimensions.contentSpacing.y + form.height +
hifi.dimensions.contentSpacing.y + buttons.height;
4 * hifi.dimensions.contentSpacing.y + form.height;
if (additionalInformation.visible) {
targetWidth = Math.max(targetWidth, additionalInformation.width);
@ -66,9 +65,6 @@ Item {
if (loginDialog.isSteamRunning()) {
additionalInformation.visible = !isLoading
}
leftButton.visible = !isLoading
buttons.visible = !isLoading
}
BusyIndicator {
@ -108,30 +104,27 @@ Item {
Column {
id: form
width: parent.width
onHeightChanged: d.resize(); onWidthChanged: d.resize();
anchors {
top: mainTextContainer.bottom
left: parent.left
margins: 0
topMargin: 2 * hifi.dimensions.contentSpacing.y
}
spacing: 2 * hifi.dimensions.contentSpacing.y
Row {
spacing: hifi.dimensions.contentSpacing.x
TextField {
id: usernameField
anchors {
verticalCenter: parent.verticalCenter
}
width: 350
label: "Username or Email"
}
TextField {
id: usernameField
width: parent.width
focus: true
label: "Username or Email"
ShortcutText {
anchors {
verticalCenter: parent.verticalCenter
verticalCenter: usernameField.textFieldLabel.verticalCenter
left: usernameField.textFieldLabel.right
leftMargin: 10
}
text: "<a href='https://highfidelity.com/users/password/new'>Forgot Username?</a>"
@ -143,23 +136,19 @@ Item {
onLinkActivated: loginDialog.openUrl(link)
}
}
Row {
spacing: hifi.dimensions.contentSpacing.x
TextField {
id: passwordField
anchors {
verticalCenter: parent.verticalCenter
}
width: 350
TextField {
id: passwordField
width: parent.width
label: "Password"
echoMode: TextInput.Password
}
label: "Password"
echoMode: showPassword.checked ? TextInput.Normal : TextInput.Password
ShortcutText {
anchors {
verticalCenter: parent.verticalCenter
verticalCenter: passwordField.textFieldLabel.verticalCenter
left: passwordField.textFieldLabel.right
leftMargin: 10
}
text: "<a href='https://highfidelity.com/users/password/new'>Forgot Password?</a>"
@ -172,25 +161,76 @@ Item {
}
}
}
InfoItem {
id: additionalInformation
anchors {
top: form.bottom
left: parent.left
margins: 0
topMargin: hifi.dimensions.contentSpacing.y
CheckBoxQQC2 {
id: showPassword
text: "Show password"
}
visible: loginDialog.isSteamRunning()
InfoItem {
id: additionalInformation
text: qsTr("Your steam account informations will not be exposed to other users.")
wrapMode: Text.WordWrap
color: hifi.colors.baseGrayHighlight
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
visible: loginDialog.isSteamRunning()
text: qsTr("Your steam account informations will not be exposed to other users.")
wrapMode: Text.WordWrap
color: hifi.colors.baseGrayHighlight
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
}
Row {
id: buttons
spacing: hifi.dimensions.contentSpacing.y*2
onHeightChanged: d.resize(); onWidthChanged: d.resize();
anchors.horizontalCenter: parent.horizontalCenter
Button {
id: linkAccountButton
anchors.verticalCenter: parent.verticalCenter
width: 200
text: qsTr(loginDialog.isSteamRunning() ? "Link Account" : "Login")
color: hifi.buttons.blue
onClicked: linkAccountBody.login()
}
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Cancel")
onClicked: root.tryDestroy()
}
}
Row {
id: leftButton
anchors.horizontalCenter: parent.horizontalCenter
spacing: hifi.dimensions.contentSpacing.y*2
onHeightChanged: d.resize(); onWidthChanged: d.resize();
RalewaySemiBold {
size: hifi.fontSizes.inputLabel
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Don't have an account?")
}
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Sign Up")
visible: !loginDialog.isSteamRunning()
onClicked: {
bodyLoader.setSource("SignUpBody.qml")
if (!root.isTablet) {
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
}
}
}
}
}
// Override ScrollingWindow's keyboard that would be at very bottom of dialog.
@ -200,70 +240,22 @@ Item {
anchors {
left: parent.left
right: parent.right
bottom: buttons.top
bottom: parent.bottom
bottomMargin: keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0
}
}
Row {
id: leftButton
anchors {
left: parent.left
bottom: parent.bottom
bottomMargin: hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Sign Up")
visible: !loginDialog.isSteamRunning()
onClicked: {
bodyLoader.setSource("SignUpBody.qml")
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
}
}
}
Row {
id: buttons
anchors {
right: parent.right
bottom: parent.bottom
bottomMargin: hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
id: linkAccountButton
anchors.verticalCenter: parent.verticalCenter
width: 200
text: qsTr(loginDialog.isSteamRunning() ? "Link Account" : "Login")
color: hifi.buttons.blue
onClicked: linkAccountBody.login()
}
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Cancel")
onClicked: root.destroy()
}
}
Component.onCompleted: {
root.title = qsTr("Sign Into High Fidelity")
root.iconText = "<"
keyboardEnabled = HMD.active;
//dont rise local keyboard
keyboardEnabled = !root.isTablet && HMD.active;
//but rise Tablet's one instead for Tablet interface
if (root.isTablet) {
root.keyboardEnabled = HMD.active;
root.keyboardRaised = Qt.binding( function() { return keyboardRaised; })
}
d.resize();
if (failAfterSignUp) {
@ -311,11 +303,11 @@ Item {
}
switch (event.key) {
case Qt.Key_Enter:
case Qt.Key_Return:
event.accepted = true
linkAccountBody.login()
break
case Qt.Key_Enter:
case Qt.Key_Return:
event.accepted = true
linkAccountBody.login()
break
}
}
}

View file

@ -9,7 +9,7 @@
//
import Hifi 1.0
import QtQuick 2.4
import QtQuick 2.7
import QtQuick.Controls.Styles 1.4 as OriginalStyles
import "../controls-uit"
@ -18,8 +18,8 @@ import "../styles-uit"
Item {
id: signInBody
clip: true
width: root.pane.width
height: root.pane.height
width: root.pane.width
property bool required: false
@ -29,7 +29,7 @@ Item {
}
function cancel() {
root.destroy()
root.tryDestroy()
}
QtObject {
@ -121,8 +121,10 @@ Item {
console.log("Login Failed")
bodyLoader.source = "CompleteProfileBody.qml"
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
if (!root.isTablet) {
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
}
}
}
}

View file

@ -9,7 +9,7 @@
//
import Hifi 1.0
import QtQuick 2.4
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 as OriginalStyles
@ -44,8 +44,7 @@ Item {
function resize() {
var targetWidth = Math.max(titleWidth, form.contentWidth);
var targetHeight = hifi.dimensions.contentSpacing.y + mainTextContainer.height +
4 * hifi.dimensions.contentSpacing.y + form.height +
hifi.dimensions.contentSpacing.y + buttons.height;
4 * hifi.dimensions.contentSpacing.y + form.height;
parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth));
parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight))
@ -96,44 +95,31 @@ Item {
Column {
id: form
width: parent.width
onHeightChanged: d.resize(); onWidthChanged: d.resize();
anchors {
top: mainTextContainer.bottom
left: parent.left
margins: 0
topMargin: 2 * hifi.dimensions.contentSpacing.y
}
spacing: 2 * hifi.dimensions.contentSpacing.y
Row {
spacing: hifi.dimensions.contentSpacing.x
TextField {
id: emailField
anchors {
verticalCenter: parent.verticalCenter
}
width: 350
label: "Email"
}
TextField {
id: emailField
width: parent.width
label: "Email"
}
Row {
spacing: hifi.dimensions.contentSpacing.x
TextField {
id: usernameField
anchors {
verticalCenter: parent.verticalCenter
}
width: 350
label: "Username"
}
TextField {
id: usernameField
width: parent.width
label: "Username"
ShortcutText {
anchors {
verticalCenter: parent.verticalCenter
verticalCenter: parent.textFieldLabel.verticalCenter
left: parent.textFieldLabel.right
leftMargin: 10
}
text: qsTr("No spaces / special chars.")
@ -145,23 +131,17 @@ Item {
}
}
Row {
spacing: hifi.dimensions.contentSpacing.x
TextField {
id: passwordField
anchors {
verticalCenter: parent.verticalCenter
}
width: 350
label: "Password"
echoMode: TextInput.Password
}
TextField {
id: passwordField
width: parent.width
label: "Password"
echoMode: TextInput.Password
ShortcutText {
anchors {
verticalCenter: parent.verticalCenter
verticalCenter: parent.textFieldLabel.verticalCenter
left: parent.textFieldLabel.right
leftMargin: 10
}
text: qsTr("At least 6 characters")
@ -173,6 +153,53 @@ Item {
}
}
Row {
id: leftButton
anchors.horizontalCenter: parent.horizontalCenter
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Existing User")
onClicked: {
bodyLoader.setSource("LinkAccountBody.qml")
if (!root.isTablet) {
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
}
}
}
}
Row {
id: buttons
anchors.horizontalCenter: parent.horizontalCenter
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
id: linkAccountButton
anchors.verticalCenter: parent.verticalCenter
width: 200
text: qsTr("Sign Up")
color: hifi.buttons.blue
onClicked: signupBody.signup()
}
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Cancel")
onClicked: root.tryDestroy()
}
}
}
// Override ScrollingWindow's keyboard that would be at very bottom of dialog.
@ -182,69 +209,21 @@ Item {
anchors {
left: parent.left
right: parent.right
bottom: buttons.top
bottom: parent.bottom
bottomMargin: keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0
}
}
Row {
id: leftButton
anchors {
left: parent.left
bottom: parent.bottom
bottomMargin: hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Existing User")
onClicked: {
bodyLoader.setSource("LinkAccountBody.qml")
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
}
}
}
Row {
id: buttons
anchors {
right: parent.right
bottom: parent.bottom
bottomMargin: hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
id: linkAccountButton
anchors.verticalCenter: parent.verticalCenter
width: 200
text: qsTr("Sign Up")
color: hifi.buttons.blue
onClicked: signupBody.signup()
}
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Cancel")
onClicked: root.destroy()
}
}
Component.onCompleted: {
root.title = qsTr("Create an Account")
root.iconText = "<"
keyboardEnabled = HMD.active;
//dont rise local keyboard
keyboardEnabled = !root.isTablet && HMD.active;
//but rise Tablet's one instead for Tablet interface
if (root.isTablet) {
root.keyboardEnabled = HMD.active;
root.keyboardRaised = Qt.binding( function() { return keyboardRaised; })
}
d.resize();
emailField.forceActiveFocus();
@ -275,8 +254,10 @@ Item {
onHandleLoginFailed: {
// we failed to login, show the LoginDialog so the user will try again
bodyLoader.setSource("LinkAccountBody.qml", { "failAfterSignUp": true })
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
if (!root.isTablet) {
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
}
}
}

View file

@ -79,7 +79,7 @@ Item {
margins: 0
topMargin: hifi.dimensions.contentSpacing.y
}
width: 250
width: parent.width
placeholderText: "Choose your own"
}
@ -102,7 +102,7 @@ Item {
bottom: parent.bottom
right: parent.right
margins: 0
topMargin: hifi.dimensions.contentSpacing.y
bottomMargin: hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
@ -122,14 +122,21 @@ Item {
text: qsTr("Cancel")
onClicked: root.destroy()
onClicked: root.tryDestroy()
}
}
Component.onCompleted: {
root.title = qsTr("Complete Your Profile")
root.iconText = "<"
keyboardEnabled = HMD.active;
//dont rise local keyboard
keyboardEnabled = !root.isTablet && HMD.active;
//but rise Tablet's one instead for Tablet interface
if (root.isTablet) {
root.keyboardEnabled = HMD.active;
root.keyboardRaised = Qt.binding( function() { return keyboardRaised; })
}
d.resize();
}

View file

@ -77,7 +77,7 @@ Item {
text: qsTr("Close");
onClicked: root.destroy()
onClicked: root.tryDestroy()
}
}

View file

@ -55,7 +55,11 @@ Item {
text: "Avatars: " + root.avatarCount
}
StatText {
text: "Frame Rate: " + root.framerate.toFixed(2);
text: "Game Rate: " + root.gameLoopRate
}
StatText {
visible: root.expanded
text: root.gameUpdateStats
}
StatText {
text: "Render Rate: " + root.renderrate.toFixed(2);
@ -64,21 +68,17 @@ Item {
text: "Present Rate: " + root.presentrate.toFixed(2);
}
StatText {
text: "Present New Rate: " + root.presentnewrate.toFixed(2);
visible: root.expanded
text: " Present New Rate: " + root.presentnewrate.toFixed(2);
}
StatText {
text: "Present Drop Rate: " + root.presentdroprate.toFixed(2);
visible: root.expanded
text: " Present Drop Rate: " + root.presentdroprate.toFixed(2);
}
StatText {
text: "Stutter Rate: " + root.stutterrate.toFixed(3);
visible: root.stutterrate != -1;
}
StatText {
text: "Simrate: " + root.simrate
}
StatText {
text: "Avatar Simrate: " + root.avatarSimrate
}
StatText {
text: "Missed Frame Count: " + root.appdropped;
visible: root.appdropped > 0;
@ -206,6 +206,10 @@ Item {
text: "Audio Codec: " + root.audioCodec + " Noise Gate: " +
root.audioNoiseGate;
}
StatText {
visible: root.expanded;
text: "Entity Servers In: " + root.entityPacketsInKbps + " kbps";
}
StatText {
visible: root.expanded;
text: "Downloads: " + root.downloads + "/" + root.downloadLimit +
@ -257,9 +261,6 @@ Item {
StatText {
text: "GPU: " + root.gpuFrameTime.toFixed(1) + " ms"
}
StatText {
text: "Avatar: " + root.avatarSimulationTime.toFixed(1) + " ms"
}
StatText {
text: "Triangles: " + root.triangles +
" / Material Switches: " + root.materialSwitches

View file

@ -1,124 +0,0 @@
//
// CompleteProfileBody.qml
//
// Created by Clement on 7/18/16
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import Hifi 1.0
import QtQuick 2.4
import QtQuick.Controls.Styles 1.4 as OriginalStyles
import "../controls-uit"
import "../styles-uit"
Item {
id: completeProfileBody
clip: true
QtObject {
id: d
function resize() {}
}
Row {
id: buttons
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
margins: 0
topMargin: 2 * hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
anchors.verticalCenter: parent.verticalCenter
width: 200
text: qsTr("Create your profile")
color: hifi.buttons.blue
onClicked: loginDialog.createAccountFromStream()
}
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Cancel")
onClicked: bodyLoader.popup()
}
}
ShortcutText {
id: additionalTextContainer
anchors {
top: buttons.bottom
horizontalCenter: parent.horizontalCenter
margins: 0
topMargin: hifi.dimensions.contentSpacing.y
}
text: "<a href='https://fake.link'>Already have a High Fidelity profile? Link to an existing profile here.</a>"
wrapMode: Text.WordWrap
lineHeight: 2
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
onLinkActivated: {
bodyLoader.setSource("LinkAccountBody.qml")
}
}
InfoItem {
id: termsContainer
anchors {
top: additionalTextContainer.bottom
left: parent.left
margins: 0
topMargin: 2 * hifi.dimensions.contentSpacing.y
}
text: qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
wrapMode: Text.WordWrap
color: hifi.colors.baseGrayHighlight
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
onLinkActivated: loginDialog.openUrl(link)
}
Component.onCompleted: {
loginDialogRoot.title = qsTr("Complete Your Profile")
loginDialogRoot.iconText = "<"
d.resize();
}
Connections {
target: loginDialog
onHandleCreateCompleted: {
console.log("Create Succeeded")
loginDialog.loginThroughSteam()
}
onHandleCreateFailed: {
console.log("Create Failed: " + error)
bodyLoadersetSource("UsernameCollisionBody.qml")
}
onHandleLoginCompleted: {
console.log("Login Succeeded")
bodyLoader.setSource("WelcomeBody.qml", { "welcomeBack" : false })
}
onHandleLoginFailed: {
console.log("Login Failed")
}
}
}

View file

@ -1,296 +0,0 @@
//
// LinkAccountBody.qml
//
// Created by Clement on 7/18/16
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import Hifi 1.0
import QtQuick 2.4
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 as OriginalStyles
import "../controls-uit"
import "../styles-uit"
Item {
id: linkAccountBody
clip: true
height: parent.height
width: parent.width
property bool failAfterSignUp: false
function login() {
mainTextContainer.visible = false
toggleLoading(true)
loginDialog.login(usernameField.text, passwordField.text)
}
property bool keyboardEnabled: false
property bool keyboardRaised: false
property bool punctuationMode: false
onKeyboardRaisedChanged: d.resize();
QtObject {
id: d
function resize() {}
}
function toggleLoading(isLoading) {
linkAccountSpinner.visible = isLoading
form.visible = !isLoading
if (loginDialog.isSteamRunning()) {
additionalInformation.visible = !isLoading
}
leftButton.visible = !isLoading
buttons.visible = !isLoading
}
BusyIndicator {
id: linkAccountSpinner
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
topMargin: hifi.dimensions.contentSpacing.y
}
visible: false
running: true
width: 48
height: 48
}
ShortcutText {
id: mainTextContainer
anchors {
top: parent.top
left: parent.left
margins: 0
topMargin: hifi.dimensions.contentSpacing.y
}
visible: false
text: qsTr("Username or password incorrect.")
wrapMode: Text.WordWrap
color: hifi.colors.redAccent
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
}
Column {
id: form
anchors {
top: mainTextContainer.bottom
left: parent.left
margins: 0
topMargin: 2 * hifi.dimensions.contentSpacing.y
}
spacing: 2 * hifi.dimensions.contentSpacing.y
Row {
spacing: hifi.dimensions.contentSpacing.x
TextField {
id: usernameField
anchors {
verticalCenter: parent.verticalCenter
}
width: 350
label: "Username or Email"
}
ShortcutText {
anchors {
verticalCenter: parent.verticalCenter
}
text: "<a href='https://highfidelity.com/users/password/new'>Forgot Username?</a>"
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
linkColor: hifi.colors.blueAccent
onLinkActivated: loginDialog.openUrl(link)
}
}
Row {
spacing: hifi.dimensions.contentSpacing.x
TextField {
id: passwordField
anchors {
verticalCenter: parent.verticalCenter
}
width: 350
label: "Password"
echoMode: TextInput.Password
}
ShortcutText {
anchors {
verticalCenter: parent.verticalCenter
}
text: "<a href='https://highfidelity.com/users/password/new'>Forgot Password?</a>"
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
linkColor: hifi.colors.blueAccent
onLinkActivated: loginDialog.openUrl(link)
}
}
}
InfoItem {
id: additionalInformation
anchors {
top: form.bottom
left: parent.left
margins: 0
topMargin: hifi.dimensions.contentSpacing.y
}
visible: loginDialog.isSteamRunning()
text: qsTr("Your steam account informations will not be exposed to other users.")
wrapMode: Text.WordWrap
color: hifi.colors.baseGrayHighlight
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
}
// Override ScrollingWindow's keyboard that would be at very bottom of dialog.
Keyboard {
raised: keyboardEnabled && keyboardRaised
numeric: punctuationMode
anchors {
left: parent.left
right: parent.right
bottom: buttons.top
bottomMargin: keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0
}
}
Row {
id: leftButton
anchors {
left: parent.left
bottom: parent.bottom
bottomMargin: hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Sign Up")
visible: !loginDialog.isSteamRunning()
onClicked: {
bodyLoader.setSource("SignUpBody.qml")
}
}
}
Row {
id: buttons
anchors {
right: parent.right
bottom: parent.bottom
bottomMargin: hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
id: linkAccountButton
anchors.verticalCenter: parent.verticalCenter
width: 200
text: qsTr(loginDialog.isSteamRunning() ? "Link Account" : "Login")
color: hifi.buttons.blue
onClicked: linkAccountBody.login()
}
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Cancel")
onClicked: {
bodyLoader.popup()
}
}
}
Component.onCompleted: {
loginDialogRoot.title = qsTr("Sign Into High Fidelity")
loginDialogRoot.iconText = "<"
keyboardEnabled = HMD.active;
d.resize();
if (failAfterSignUp) {
mainTextContainer.text = "Account created successfully."
mainTextContainer.visible = true
}
usernameField.forceActiveFocus();
}
Connections {
target: loginDialog
onHandleLoginCompleted: {
console.log("Login Succeeded, linking steam account")
if (loginDialog.isSteamRunning()) {
loginDialog.linkSteam()
} else {
bodyLoader.setSource("WelcomeBody.qml", { "welcomeBack" : true })
}
}
onHandleLoginFailed: {
console.log("Login Failed")
mainTextContainer.visible = true
toggleLoading(false)
}
onHandleLinkCompleted: {
console.log("Link Succeeded")
bodyLoader.setSource("WelcomeBody.qml", { "welcomeBack" : true })
}
onHandleLinkFailed: {
console.log("Link Failed")
toggleLoading(false)
}
}
Keys.onPressed: {
if (!visible) {
return
}
switch (event.key) {
case Qt.Key_Enter:
case Qt.Key_Return:
event.accepted = true
linkAccountBody.login()
break
}
}
}

View file

@ -1,109 +0,0 @@
//
// SignInBody.qml
//
// Created by Clement on 7/18/16
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import Hifi 1.0
import QtQuick 2.4
import QtQuick.Controls.Styles 1.4 as OriginalStyles
import "../controls-uit"
import "../styles-uit"
Item {
id: signInBody
clip: true
property bool required: false
function login() {
console.log("Trying to log in")
loginDialog.loginThroughSteam()
}
function cancel() {
bodyLoader.popup()
}
QtObject {
id: d
function resize() {}
}
InfoItem {
id: mainTextContainer
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
margins: 0
topMargin: hifi.dimensions.contentSpacing.y
}
text: required ? qsTr("This domain's owner requires that you sign in:")
: qsTr("Sign in to access your user account:")
wrapMode: Text.WordWrap
color: hifi.colors.baseGrayHighlight
lineHeight: 2
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
}
Row {
id: buttons
anchors {
top: mainTextContainer.bottom
horizontalCenter: parent.horizontalCenter
margins: 0
topMargin: 2 * hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
anchors.verticalCenter: parent.verticalCenter
width: undefined // invalidate so that the image's size sets the width
height: undefined // invalidate so that the image's size sets the height
focus: true
style: OriginalStyles.ButtonStyle {
background: Image {
id: buttonImage
source: "../../images/steam-sign-in.png"
}
}
onClicked: signInBody.login()
}
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Cancel");
onClicked: signInBody.cancel()
}
}
Component.onCompleted: {
loginDialogRoot.title = required ? qsTr("Sign In Required")
: qsTr("Sign In")
loginDialogRoot.iconText = ""
d.resize();
}
Connections {
target: loginDialog
onHandleLoginCompleted: {
console.log("Login Succeeded")
bodyLoader.setSource("WelcomeBody.qml", { "welcomeBack" : true })
}
onHandleLoginFailed: {
console.log("Login Failed")
bodyLoader.setSource("CompleteProfileBody.qml")
}
}
}

View file

@ -1,276 +0,0 @@
//
// SignUpBody.qml
//
// Created by Stephen Birarda on 7 Dec 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import Hifi 1.0
import QtQuick 2.4
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 as OriginalStyles
import "../controls-uit"
import "../styles-uit"
Item {
id: signupBody
clip: true
// height: parent.height
// width: parent.width
function signup() {
mainTextContainer.visible = false
toggleLoading(true)
loginDialog.signup(emailField.text, usernameField.text, passwordField.text)
}
property bool keyboardEnabled: false
property bool keyboardRaised: false
property bool punctuationMode: false
onKeyboardRaisedChanged: d.resize();
QtObject {
id: d
function resize() {}
}
function toggleLoading(isLoading) {
linkAccountSpinner.visible = isLoading
form.visible = !isLoading
leftButton.visible = !isLoading
buttons.visible = !isLoading
}
BusyIndicator {
id: linkAccountSpinner
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
topMargin: hifi.dimensions.contentSpacing.y
}
visible: false
running: true
width: 48
height: 48
}
ShortcutText {
id: mainTextContainer
anchors {
top: parent.top
left: parent.left
margins: 0
topMargin: hifi.dimensions.contentSpacing.y
}
visible: false
text: qsTr("There was an unknown error while creating your account.")
wrapMode: Text.WordWrap
color: hifi.colors.redAccent
horizontalAlignment: Text.AlignLeft
}
Column {
id: form
anchors {
top: mainTextContainer.bottom
left: parent.left
margins: 0
topMargin: 2 * hifi.dimensions.contentSpacing.y
}
spacing: 2 * hifi.dimensions.contentSpacing.y
Row {
spacing: hifi.dimensions.contentSpacing.x
TextField {
id: emailField
anchors {
verticalCenter: parent.verticalCenter
}
width: 300
label: "Email"
}
}
Row {
spacing: hifi.dimensions.contentSpacing.x
TextField {
id: usernameField
anchors {
verticalCenter: parent.verticalCenter
}
width: 300
label: "Username"
}
ShortcutText {
anchors {
verticalCenter: parent.verticalCenter
}
text: qsTr("No spaces / special chars.")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: hifi.colors.blueAccent
}
}
Row {
spacing: hifi.dimensions.contentSpacing.x
TextField {
id: passwordField
anchors {
verticalCenter: parent.verticalCenter
}
width: 300
label: "Password"
echoMode: TextInput.Password
}
ShortcutText {
anchors {
verticalCenter: parent.verticalCenter
}
text: qsTr("At least 6 characters")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: hifi.colors.blueAccent
}
}
}
// Override ScrollingWindow's keyboard that would be at very bottom of dialog.
Keyboard {
raised: keyboardEnabled && keyboardRaised
numeric: punctuationMode
anchors {
left: parent.left
right: parent.right
bottom: buttons.top
bottomMargin: keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0
}
}
Row {
id: leftButton
anchors {
left: parent.left
bottom: parent.bottom
bottomMargin: hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Existing User")
onClicked: {
bodyLoader.setSource("LinkAccountBody.qml")
}
}
}
Row {
id: buttons
anchors {
right: parent.right
bottom: parent.bottom
bottomMargin: hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
id: linkAccountButton
anchors.verticalCenter: parent.verticalCenter
width: 200
text: qsTr("Sign Up")
color: hifi.buttons.blue
onClicked: signupBody.signup()
}
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Cancel")
onClicked: bodyLoader.popup()
}
}
Component.onCompleted: {
loginDialogRoot.title = qsTr("Create an Account")
loginDialogRoot.iconText = "<"
keyboardEnabled = HMD.active;
d.resize();
emailField.forceActiveFocus();
}
Connections {
target: loginDialog
onHandleSignupCompleted: {
console.log("Sign Up Succeeded");
// now that we have an account, login with that username and password
loginDialog.login(usernameField.text, passwordField.text)
}
onHandleSignupFailed: {
console.log("Sign Up Failed")
toggleLoading(false)
mainTextContainer.text = errorString
mainTextContainer.visible = true
d.resize();
}
onHandleLoginCompleted: {
bodyLoader.setSource("WelcomeBody.qml", { "welcomeBack": false })
}
onHandleLoginFailed: {
// we failed to login, show the LoginDialog so the user will try again
bodyLoader.setSource("LinkAccountBody.qml", { "failAfterSignUp": true })
}
}
Keys.onPressed: {
if (!visible) {
return
}
switch (event.key) {
case Qt.Key_Enter:
case Qt.Key_Return:
event.accepted = true
signupBody.signup()
break
}
}
}

View file

@ -1,157 +0,0 @@
//
// UsernameCollisionBody.qml
//
// Created by Clement on 7/18/16
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import Hifi 1.0
import QtQuick 2.4
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 as OriginalStyles
import "../controls-uit"
import "../styles-uit"
Item {
id: usernameCollisionBody
clip: true
width: parent.width
height: parent.height
function create() {
mainTextContainer.visible = false
loginDialog.createAccountFromStream(textField.text)
}
property bool keyboardEnabled: false
property bool keyboardRaised: false
property bool punctuationMode: false
onKeyboardRaisedChanged: d.resize();
QtObject {
id: d
function resize() {}
}
ShortcutText {
id: mainTextContainer
anchors {
top: parent.top
left: parent.left
margins: 0
topMargin: hifi.dimensions.contentSpacing.y
}
text: qsTr("Your Steam username is not available.")
wrapMode: Text.WordWrap
color: hifi.colors.redAccent
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
}
TextField {
id: textField
anchors {
top: mainTextContainer.bottom
left: parent.left
margins: 0
topMargin: hifi.dimensions.contentSpacing.y
}
width: 250
placeholderText: "Choose your own"
}
// Override ScrollingWindow's keyboard that would be at very bottom of dialog.
Keyboard {
raised: keyboardEnabled && keyboardRaised
numeric: punctuationMode
anchors {
left: parent.left
right: parent.right
bottom: buttons.top
bottomMargin: keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0
}
}
Row {
id: buttons
anchors {
bottom: parent.bottom
right: parent.right
margins: 0
topMargin: hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
anchors.verticalCenter: parent.verticalCenter
width: 200
text: qsTr("Create your profile")
color: hifi.buttons.blue
onClicked: usernameCollisionBody.create()
}
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Cancel")
onClicked: bodyLoader.popup()
}
}
Component.onCompleted: {
loginDialogRoot.title = qsTr("Complete Your Profile")
loginDialogRoot.iconText = "<"
keyboardEnabled = HMD.active;
d.resize();
}
Connections {
target: loginDialog
onHandleCreateCompleted: {
console.log("Create Succeeded")
loginDialog.loginThroughSteam()
}
onHandleCreateFailed: {
console.log("Create Failed: " + error)
mainTextContainer.visible = true
mainTextContainer.text = "\"" + textField.text + qsTr("\" is invalid or already taken.")
}
onHandleLoginCompleted: {
console.log("Login Succeeded")
bodyLoader.setSource("WelcomeBody.qml", { "welcomeBack" : false })
}
onHandleLoginFailed: {
console.log("Login Failed")
}
}
Keys.onPressed: {
if (!visible) {
return
}
switch (event.key) {
case Qt.Key_Enter:
case Qt.Key_Return:
event.accepted = true
usernameCollisionBody.create()
break
}
}
}

View file

@ -1,79 +0,0 @@
//
// WelcomeBody.qml
//
// Created by Clement on 7/18/16
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import Hifi 1.0
import QtQuick 2.4
import "../controls-uit"
import "../styles-uit"
Item {
id: welcomeBody
clip: true
property bool welcomeBack: false
function setTitle() {
loginDialogRoot.title = (welcomeBack ? qsTr("Welcome back <b>") : qsTr("Welcome <b>")) + Account.username + qsTr("</b>!")
loginDialogRoot.iconText = ""
d.resize();
}
QtObject {
id: d
function resize() {}
}
InfoItem {
id: mainTextContainer
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
margins: 0
topMargin: hifi.dimensions.contentSpacing.y
}
text: qsTr("You are now signed into High Fidelity")
wrapMode: Text.WordWrap
color: hifi.colors.baseGrayHighlight
lineHeight: 2
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
}
Row {
id: buttons
anchors {
top: mainTextContainer.bottom
horizontalCenter: parent.horizontalCenter
margins: 0
topMargin: 2 * hifi.dimensions.contentSpacing.y
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Close");
onClicked: bodyLoader.popup()
}
}
Component.onCompleted: {
welcomeBody.setTitle()
}
Connections {
target: Account
onUsernameChanged: welcomeBody.setTitle()
}
}

View file

@ -15,8 +15,11 @@ import QtQuick.Controls.Styles 1.4
import "../styles-uit"
Original.Button {
id: root;
property int color: 0
property int colorScheme: hifi.colorSchemes.light
property string buttonGlyph: "";
width: 120
height: hifi.dimensions.controlLineHeight
@ -28,6 +31,13 @@ Original.Button {
background: Rectangle {
radius: hifi.buttons.radius
border.width: (control.color === hifi.buttons.none ||
(control.color === hifi.buttons.noneBorderless && control.hovered) ||
(control.color === hifi.buttons.noneBorderlessWhite && control.hovered) ||
(control.color === hifi.buttons.noneBorderlessGray && control.hovered)) ? 1 : 0;
border.color: control.color === hifi.buttons.noneBorderless ? hifi.colors.blueHighlight :
(control.color === hifi.buttons.noneBorderlessGray ? hifi.colors.baseGray : hifi.colors.white);
gradient: Gradient {
GradientStop {
position: 0.2
@ -60,14 +70,35 @@ Original.Button {
}
}
label: RalewayBold {
font.capitalization: Font.AllUppercase
color: enabled ? hifi.buttons.textColor[control.color]
: hifi.buttons.disabledTextColor[control.colorScheme]
size: hifi.fontSizes.buttonLabel
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: control.text
label: Item {
HiFiGlyphs {
id: buttonGlyph;
visible: root.buttonGlyph !== "";
text: root.buttonGlyph === "" ? hifi.glyphs.question : root.buttonGlyph;
// Size
size: 34;
// Anchors
anchors.right: buttonText.left;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
// Style
color: enabled ? hifi.buttons.textColor[control.color]
: hifi.buttons.disabledTextColor[control.colorScheme];
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
RalewayBold {
id: buttonText;
anchors.centerIn: parent;
font.capitalization: Font.AllUppercase
color: enabled ? hifi.buttons.textColor[control.color]
: hifi.buttons.disabledTextColor[control.colorScheme]
size: hifi.fontSizes.buttonLabel
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: control.text
}
}
}
}

View file

@ -11,9 +11,14 @@
import QtQuick 2.0
import "."
Item {
Rectangle {
id: keyboardBase
anchors.left: parent.left
anchors.right: parent.right
color: "#252525"
property bool raised: false
property bool numeric: false
@ -105,6 +110,7 @@ Item {
height: showMirrorText ? mirrorTextHeight : 0
width: keyboardWidth
color: "#252525"
anchors.horizontalCenter: parent.horizontalCenter
TextEdit {
id: mirrorText
@ -116,6 +122,13 @@ Item {
wrapMode: Text.WordWrap
readOnly: false // we need to leave this property read-only to allow control to accept QKeyEvent
selectByMouse: false
Keys.onPressed: {
if (event.key == Qt.Key_Return) {
mirrorText.text = "";
event.accepted = true;
}
}
}
MouseArea { // ... and we need this mouse area to prevent mirrorText from getting mouse events to ensure it will never get focus

View file

@ -17,6 +17,19 @@ RalewaySemiBold {
property int colorScheme: hifi.colorSchemes.light
size: hifi.fontSizes.inputLabel
color: enabled ? (colorScheme == hifi.colorSchemes.light ? hifi.colors.lightGray : hifi.colors.lightGrayText)
: (colorScheme == hifi.colorSchemes.light ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight);
color: {
if (colorScheme === hifi.colorSchemes.dark) {
if (enabled) {
hifi.colors.lightGrayText
} else {
hifi.colors.baseGrayHighlight
}
} else {
if (enabled) {
hifi.colors.lightGray
} else {
hifi.colors.lightGrayText
}
}
}
}

View file

@ -12,8 +12,13 @@ import QtQuick 2.5
import "../styles-uit"
Item {
property int colorScheme: 0;
readonly property var topColor: [ hifi.colors.baseGrayShadow, hifi.colors.faintGray ];
readonly property var bottomColor: [ hifi.colors.baseGrayHighlight, hifi.colors.faintGray ];
// Size
height: 2;
height: colorScheme === 0 ? 2 : 1;
Rectangle {
// Size
width: parent.width;
@ -21,18 +26,19 @@ Item {
// Anchors
anchors.left: parent.left;
anchors.bottom: parent.bottom;
anchors.bottomMargin: height;
// Style
color: hifi.colors.baseGrayShadow;
color: topColor[colorScheme];
}
Rectangle {
visible: colorScheme === 0;
// Size
width: parent.width;
height: 1;
// Anchors
anchors.left: parent.left;
anchors.bottom: parent.bottom;
anchors.bottomMargin: -height;
// Style
color: hifi.colors.baseGrayHighlight;
color: bottomColor[colorScheme];
}
}

View file

@ -20,9 +20,13 @@ TextField {
property int colorScheme: hifi.colorSchemes.light
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
readonly property bool isFaintGrayColorScheme: colorScheme == hifi.colorSchemes.faintGray
property bool isSearchField: false
property string label: ""
property real controlHeight: height + (textFieldLabel.visible ? textFieldLabel.height + 1 : 0)
property bool hasRoundedBorder: false
property bool error: false;
property bool hasClearButton: false;
placeholderText: textField.placeholderText
@ -31,20 +35,58 @@ TextField {
font.pixelSize: hifi.fontSizes.textFieldInput
font.italic: textField.text == ""
height: implicitHeight + 3 // Make surrounding box higher so that highlight is vertically centered.
property alias textFieldLabel: textFieldLabel
y: textFieldLabel.visible ? textFieldLabel.height + textFieldLabel.anchors.bottomMargin : 0
style: TextFieldStyle {
textColor: isLightColorScheme
? (textField.activeFocus ? hifi.colors.black : hifi.colors.lightGray)
: (textField.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText)
textColor: {
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 {
color: isLightColorScheme
? (textField.activeFocus ? hifi.colors.white : hifi.colors.textFieldLightBackground)
: (textField.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow)
border.color: hifi.colors.primaryHighlight
border.width: textField.activeFocus ? 1 : 0
radius: isSearchField ? textField.height / 2 : 0
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: textField.activeFocus || hasRoundedBorder || textField.error ? 1 : 0
radius: isSearchField ? textField.height / 2 : (hasRoundedBorder ? 4 : 0)
HiFiGlyphs {
text: hifi.glyphs.search
@ -55,12 +97,29 @@ TextField {
anchors.leftMargin: hifi.dimensions.textPadding - 2
visible: isSearchField
}
HiFiGlyphs {
text: hifi.glyphs.error
color: textColor
size: 40
anchors.right: parent.right
anchors.rightMargin: hifi.dimensions.textPadding - 2
anchors.verticalCenter: parent.verticalCenter
visible: hasClearButton && textField.text !== "";
MouseArea {
anchors.fill: parent;
onClicked: {
textField.text = "";
}
}
}
}
placeholderTextColor: hifi.colors.lightGray
placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray
selectedTextColor: hifi.colors.black
selectionColor: hifi.colors.primaryHighlight
padding.left: (isSearchField ? textField.height - 2 : 0) + hifi.dimensions.textPadding
padding.right: hifi.dimensions.textPadding
padding.right: (hasClearButton ? textField.height - 2 : 0) + hifi.dimensions.textPadding
}
HifiControls.Label {

View file

@ -298,6 +298,23 @@ FocusScope {
pinned = !pinned
}
function isPointOnWindow(point) {
for (var i = 0; i < desktop.visibleChildren.length; i++) {
var child = desktop.visibleChildren[i];
if (child.visible) {
if (child.hasOwnProperty("modality")) {
var mappedPoint = child.mapFromGlobal(point.x, point.y);
var outLine = child.frame.children[2];
var framePoint = outLine.mapFromGlobal(point.x, point.y);
if (child.contains(mappedPoint) || outLine.contains(framePoint)) {
return true;
}
}
}
}
return false;
}
function setPinned(newPinned) {
pinned = newPinned
}

View file

@ -16,48 +16,66 @@ import "../controls-uit"
import "../styles-uit"
import "../windows"
import "../LoginDialog"
TabletModalWindow {
id: loginDialogRoot
id: realRoot
objectName: "LoginDialog"
signal sendToScript(var message);
property bool isHMD: false
property bool gotoPreviousApp: false;
color: hifi.colors.baseGray
title: qsTr("Sign in to High Fidelity")
property alias titleWidth: root.titleWidth
property alias punctuationMode: root.punctuationMode
//fake root for shared components expecting root here
property var root: QtObject {
id: root
property bool keyboardEnabled: false
property bool keyboardRaised: false
property bool punctuationMode: false
readonly property bool isTablet: true
property alias title: realRoot.title
property real width: realRoot.width
property real height: realRoot.height
property int titleWidth: 0
property string iconText: hifi.glyphs.avatar
property int iconSize: 35
property var pane: QtObject {
property real width: root.width
property real height: root.height
}
function tryDestroy() {
canceled()
}
}
//property int colorScheme: hifi.colorSchemes.dark
property int colorScheme: hifi.colorSchemes.dark
property int titleWidth: 0
property string iconText: ""
property int icon: hifi.icons.none
property int iconSize: 35
MouseArea {
width: parent.width
height: parent.height
width: realRoot.width
height: realRoot.height
}
property bool keyboardOverride: true
onIconChanged: updateIcon();
property var items;
property string label: ""
onTitleWidthChanged: d.resize();
//onTitleWidthChanged: d.resize();
property bool keyboardEnabled: false
property bool keyboardRaised: false
property bool punctuationMode: false
onKeyboardRaisedChanged: d.resize();
//onKeyboardRaisedChanged: d.resize();
signal canceled();
function updateIcon() {
if (!root) {
return;
}
iconText = hifi.glyphForIcon(root.icon);
}
property alias bodyLoader: bodyLoader
property alias loginDialog: loginDialog
property alias hifi: hifi
@ -65,9 +83,10 @@ TabletModalWindow {
HifiConstants { id: hifi }
onCanceled: {
if (loginDialogRoot.Stack.view) {
loginDialogRoot.Stack.view.pop();
} else if (gotoPreviousApp) {
if (bodyLoader.active === true) {
//bodyLoader.active = false
}
if (gotoPreviousApp) {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.returnToPreviousApp();
} else {
@ -75,45 +94,81 @@ TabletModalWindow {
}
}
LoginDialog {
id: loginDialog
width: parent.width
height: parent.height
StackView {
id: bodyLoader
property var item: currentItem
property var props
property string source: ""
TabletModalFrame {
id: mfRoot
onCurrentItemChanged: {
//cleanup source for future usage
source = ""
width: root.width
height: root.height + frameMarginTop + hifi.dimensions.contentMargin.x
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
verticalCenterOffset: -loginKeyboard.height / 2
}
LoginDialog {
id: loginDialog
anchors {
fill: parent
topMargin: parent.frameMarginTop
leftMargin: hifi.dimensions.contentMargin.x
rightMargin: hifi.dimensions.contentMargin.x
horizontalCenter: parent.horizontalCenter
}
function setSource(src, props) {
source = "../TabletLoginDialog/" + src
bodyLoader.props = props
}
function popup() {
bodyLoader.pop()
//check if last screen, if yes, dialog is popped out
if (depth === 1)
loginDialogRoot.canceled()
}
anchors.fill: parent
anchors.margins: 10
onSourceChanged: {
if (source !== "") {
bodyLoader.push(Qt.resolvedUrl(source), props)
}
}
Component.onCompleted: {
setSource(loginDialog.isSteamRunning() ?
"SignInBody.qml" :
"LinkAccountBody.qml")
Loader {
id: bodyLoader
anchors.fill: parent
anchors.horizontalCenter: parent.horizontalCenter
source: loginDialog.isSteamRunning() ? "../LoginDialog/SignInBody.qml" : "../LoginDialog/LinkAccountBody.qml"
}
}
}
Keyboard {
id: loginKeyboard
raised: root.keyboardEnabled && root.keyboardRaised
numeric: root.punctuationMode
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
}
Keys.onPressed: {
if (!visible) {
return
}
if (event.modifiers === Qt.ControlModifier)
switch (event.key) {
case Qt.Key_A:
event.accepted = true
detailedText.selectAll()
break
case Qt.Key_C:
event.accepted = true
detailedText.copy()
break
case Qt.Key_Period:
if (Qt.platform.os === "osx") {
event.accepted = true
content.reject()
}
break
} else switch (event.key) {
case Qt.Key_Escape:
case Qt.Key_Back:
event.accepted = true
destroy()
break
case Qt.Key_Enter:
case Qt.Key_Return:
event.accepted = true
break
}
}
}

View file

@ -14,10 +14,10 @@ import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.2 as OriginalDialogs
import Qt.labs.settings 1.0
import "styles-uit"
import "controls-uit" as HifiControls
import "windows"
import "dialogs"
import "../styles-uit"
import "../controls-uit" as HifiControls
import "../windows"
import "../dialogs"
ScrollingWindow {
id: root
@ -38,7 +38,7 @@ ScrollingWindow {
property var assetMappingsModel: Assets.mappingModel;
property var currentDirectory;
property var selectedItems: treeView.selection.selectedIndexes.length;
Settings {
category: "Overlay.AssetServer"
property alias x: root.x
@ -58,6 +58,23 @@ ScrollingWindow {
assetMappingsModel.autoRefreshEnabled = false;
}
function letterbox(headerGlyph, headerText, message) {
letterboxMessage.headerGlyph = headerGlyph;
letterboxMessage.headerText = headerText;
letterboxMessage.text = message;
letterboxMessage.visible = true;
letterboxMessage.popupRadius = 0;
}
function errorMessageBox(message) {
return desktop.messageBox({
icon: hifi.icons.warning,
defaultButton: OriginalDialogs.StandardButton.Ok,
title: "Error",
text: message
});
}
function doDeleteFile(path) {
console.log("Deleting " + path);
@ -154,10 +171,7 @@ ScrollingWindow {
}
function handleGetMappingsError(errorString) {
errorMessageBox(
"There was a problem retreiving the list of assets from your Asset Server.\n"
+ errorString
);
errorMessageBox("There was a problem retrieving the list of assets from your Asset Server.\n" + errorString);
}
function addToWorld() {
@ -332,7 +346,7 @@ ScrollingWindow {
if (!path) {
return;
}
var modalMessage = "";
var items = selectedItems.toString();
var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101);
@ -448,19 +462,16 @@ ScrollingWindow {
});
}
}
function errorMessageBox(message) {
return desktop.messageBox({
icon: hifi.icons.warning,
defaultButton: OriginalDialogs.StandardButton.Ok,
title: "Error",
text: message
});
}
Item {
width: pane.contentWidth
height: pane.height
// The letterbox used for popup messages
LetterboxMessage {
id: letterboxMessage;
z: 999; // Force the popup on top of everything else
}
HifiControls.ContentSection {
id: assetDirectory
@ -476,21 +487,21 @@ ScrollingWindow {
HifiControls.Button {
text: "Add To World"
color: hifi.buttons.black
color: hifi.buttons.blue
colorScheme: root.colorScheme
width: 120
enabled: canAddToWorld(assetProxyModel.data(treeView.selection.currentIndex, 0x100))
onClicked: root.addToWorld()
}
HifiControls.Button {
text: "Rename"
color: hifi.buttons.black
colorScheme: root.colorScheme
width: 80
onClicked: root.renameFile()
enabled: canRename()
}
@ -513,6 +524,7 @@ ScrollingWindow {
id: treeView
anchors.top: assetDirectory.bottom
anchors.bottom: infoRow.top
anchors.bottomMargin: 2 * hifi.dimensions.contentSpacing.y
anchors.margins: hifi.dimensions.contentMargin.x + 2 // Extra for border
anchors.left: parent.left
anchors.right: parent.right
@ -555,7 +567,7 @@ ScrollingWindow {
case "Not Baked":
return hifi.glyphs.circleSlash;
case "Baked":
return hifi.glyphs.check_2_01;
return hifi.glyphs.checkmark;
case "Error":
return hifi.glyphs.alert;
default:
@ -584,8 +596,24 @@ ScrollingWindow {
? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight)
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
elide: Text.ElideRight
horizontalAlignment: styleData.column === 1 ? TextInput.AlignHCenter : TextInput.AlignLeft
elide: Text.ElideMiddle
MouseArea {
id: mouseArea
anchors.fill: parent
acceptedButtons: Qt.NoButton
hoverEnabled: true
onEntered: {
if (parent.truncated) {
treeLabelToolTip.show(parent);
}
}
onExited: treeLabelToolTip.hide();
}
}
}
Component {
@ -668,6 +696,42 @@ ScrollingWindow {
}
}
Rectangle {
id: treeLabelToolTip
visible: false
z: 100 // Render on top
width: toolTipText.width + 2 * hifi.dimensions.textPadding
height: hifi.dimensions.tableRowHeight
color: colorScheme == hifi.colorSchemes.light ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd
border.color: colorScheme == hifi.colorSchemes.light ? hifi.colors.black : hifi.colors.lightGrayText
FiraSansSemiBold {
id: toolTipText
anchors.centerIn: parent
size: hifi.fontSizes.tableText
color: colorScheme == hifi.colorSchemes.light ? hifi.colors.black : hifi.colors.lightGrayText
}
Timer {
id: showTimer
interval: 1000
onTriggered: { treeLabelToolTip.visible = true; }
}
function show(item) {
var coord = item.mapToItem(parent, item.x, item.y);
toolTipText.text = item.text;
treeLabelToolTip.x = coord.x - hifi.dimensions.textPadding;
treeLabelToolTip.y = coord.y;
showTimer.start();
}
function hide() {
showTimer.stop();
treeLabelToolTip.visible = false;
}
}
MouseArea {
propagateComposedEvents: true
@ -717,26 +781,35 @@ ScrollingWindow {
anchors.left: treeView.left
anchors.right: treeView.right
anchors.bottom: uploadSection.top
anchors.bottomMargin: hifi.dimensions.contentSpacing.y
spacing: hifi.dimensions.contentSpacing.x
RalewayRegular {
anchors.verticalCenter: parent.verticalCenter
function makeText() {
var numPendingBakes = assetMappingsModel.numPendingBakes;
if (selectedItems > 1 || numPendingBakes === 0) {
return selectedItems + " items selected";
} else {
return numPendingBakes + " bakes pending"
}
}
size: hifi.fontSizes.sectionName
font.capitalization: Font.AllUppercase
text: selectedItems + " items selected"
text: makeText()
color: hifi.colors.lightGrayText
}
HifiControls.CheckBox {
function isChecked() {
var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105);
var bakingDisabled = (status === "Not Baked" || status === "--");
return selectedItems === 1 && !bakingDisabled;
}
HifiControls.HorizontalSpacer { }
text: "Use baked (optimized) versions"
HifiControls.CheckBox {
id: bakingCheckbox
anchors.leftMargin: 2 * hifi.dimensions.contentSpacing.x
anchors.verticalCenter: parent.verticalCenter
text: " Use baked version"
colorScheme: root.colorScheme
enabled: selectedItems === 1 && assetProxyModel.data(treeView.selection.currentIndex, 0x105) !== "--"
enabled: isEnabled()
checked: isChecked()
onClicked: {
var mappings = [];
@ -752,7 +825,66 @@ ScrollingWindow {
checked = Qt.binding(isChecked);
}
function isEnabled() {
if (!treeView.selection.hasSelection) {
return false;
}
var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105);
if (status === "--") {
return false;
}
var bakingEnabled = status !== "Not Baked";
for (var i in treeView.selection.selectedIndexes) {
var thisStatus = assetProxyModel.data(treeView.selection.selectedIndexes[i], 0x105);
if (thisStatus === "--") {
return false;
}
var thisBakingEnalbed = (thisStatus !== "Not Baked");
if (bakingEnabled !== thisBakingEnalbed) {
return false;
}
}
return true;
}
function isChecked() {
if (!treeView.selection.hasSelection) {
return false;
}
var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105);
return isEnabled() && status !== "Not Baked";
}
}
Item {
anchors.verticalCenter: parent.verticalCenter
width: infoGlyph.size;
height: infoGlyph.size;
HiFiGlyphs {
id: infoGlyph;
anchors.fill: parent;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
text: hifi.glyphs.question;
size: 35;
color: hifi.colors.lightGrayText;
}
MouseArea {
anchors.fill: parent;
hoverEnabled: true;
onEntered: infoGlyph.color = hifi.colors.blueHighlight;
onExited: infoGlyph.color = hifi.colors.lightGrayText;
onClicked: letterbox(hifi.glyphs.question,
"What is baking?",
"Baking compresses and optimizes files for faster network transfer and display. We recommend you bake your content to reduce initial load times for your visitors.");
}
}
}
HifiControls.ContentSection {
@ -790,7 +922,7 @@ ScrollingWindow {
id: image
width: 24
height: 24
source: "../images/Loading-Outer-Ring.png"
source: "../../images/Loading-Outer-Ring.png"
RotationAnimation on rotation {
loops: Animation.Infinite
from: 0
@ -801,7 +933,7 @@ ScrollingWindow {
Image {
width: 24
height: 24
source: "../images/Loading-Inner-H.png"
source: "../../images/Loading-Inner-H.png"
}
HifiControls.Label {
id: uploadProgressLabel
@ -811,10 +943,9 @@ ScrollingWindow {
text: "In progress..."
colorScheme: root.colorScheme
}
}
}
}
}
}

View file

@ -167,6 +167,9 @@ Rectangle {
}
RalewayRegular {
anchors.verticalCenter: parent.verticalCenter;
width: margins.sizeText + margins.sizeLevel
anchors.left: parent.left
anchors.leftMargin: margins.sizeCheckBox
size: 16;
color: hifi.colors.lightGrayText;
text: qsTr("CHOOSE INPUT DEVICE");

View file

@ -29,12 +29,22 @@ RowLayout {
function playSound() {
// FIXME: MyAvatar is not properly exposed to QML; MyAvatar.qmlPosition is a stopgap
// FIXME: Audio.playSystemSound should not require position
sample = Audio.playSystemSound(sound, MyAvatar.qmlPosition);
isPlaying = true;
sample.finished.connect(function() { isPlaying = false; sample = null; });
if (sample === null && !isPlaying) {
sample = Audio.playSystemSound(sound, MyAvatar.qmlPosition);
isPlaying = true;
sample.finished.connect(reset);
}
}
function stopSound() {
sample && sample.stop();
if (sample && isPlaying) {
sample.stop();
}
}
function reset() {
sample.finished.disconnect(reset);
isPlaying = false;
sample = null;
}
Component.onCompleted: createSampleSound();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,154 @@
//
// CommerceLightbox.qml
// qml/hifi/commerce/common
//
// CommerceLightbox
//
// Created by Zach Fox on 2017-09-19
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import Hifi 1.0 as Hifi
import QtQuick 2.5
import QtGraphicalEffects 1.0
import QtQuick.Controls 1.4
import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
// references XXX from root context
Rectangle {
property string titleText;
property string bodyImageSource;
property string bodyText;
property string button1text;
property string button1method;
property string button2text;
property string button2method;
readonly property string securityPicBodyText: "When you see your Security Pic, your actions and data are securely making use of your " +
"Wallet's private keys.<br><br>You can change your Security Pic in your Wallet.";
id: root;
visible: false;
anchors.fill: parent;
color: Qt.rgba(0, 0, 0, 0.5);
z: 999;
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup.
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
}
Rectangle {
anchors.centerIn: parent;
width: parent.width - 100;
height: childrenRect.height + 30;
color: "white";
RalewaySemiBold {
id: titleText;
text: root.titleText;
anchors.top: parent.top;
anchors.topMargin: 30;
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.right: parent.right;
anchors.rightMargin: 30;
height: paintedHeight;
color: hifi.colors.baseGray;
size: 24;
verticalAlignment: Text.AlignTop;
wrapMode: Text.WordWrap;
}
Image {
id: bodyImage;
visible: root.bodyImageSource;
source: root.bodyImageSource ? root.bodyImageSource : "";
anchors.top: root.titleText ? titleText.bottom : parent.top;
anchors.topMargin: root.titleText ? 20 : 30;
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.right: parent.right;
anchors.rightMargin: 30;
height: 140;
fillMode: Image.PreserveAspectFit;
mipmap: true;
cache: false;
}
RalewayRegular {
id: bodyText;
text: root.bodyText;
anchors.top: root.bodyImageSource ? bodyImage.bottom : (root.titleText ? titleText.bottom : parent.top);
anchors.topMargin: root.bodyImageSource ? 20 : (root.titleText ? 20 : 30);
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.right: parent.right;
anchors.rightMargin: 30;
height: paintedHeight;
color: hifi.colors.baseGray;
size: 20;
verticalAlignment: Text.AlignTop;
wrapMode: Text.WordWrap;
}
Item {
id: buttons;
anchors.top: bodyText.bottom;
anchors.topMargin: 30;
anchors.left: parent.left;
anchors.right: parent.right;
height: 70;
// Button 1
HifiControlsUit.Button {
color: hifi.buttons.noneBorderlessGray;
colorScheme: hifi.colorSchemes.light;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 20;
anchors.left: parent.left;
anchors.leftMargin: 10;
width: root.button2text ? parent.width/2 - anchors.leftMargin*2 : parent.width - anchors.leftMargin * 2;
text: root.button1text;
onClicked: {
eval(button1method);
}
}
// Button 2
HifiControlsUit.Button {
visible: root.button2text;
color: hifi.buttons.noneBorderless;
colorScheme: hifi.colorSchemes.light;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 20;
anchors.right: parent.right;
anchors.rightMargin: 10;
width: parent.width/2 - anchors.rightMargin*2;
text: root.button2text;
onClicked: {
eval(button2method);
}
}
}
}
//
// FUNCTION DEFINITIONS START
//
signal sendToParent(var msg);
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -0,0 +1,338 @@
//
// EmulatedMarketplaceHeader.qml
// qml/hifi/commerce/common
//
// EmulatedMarketplaceHeader
//
// Created by Zach Fox on 2017-09-18
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import Hifi 1.0 as Hifi
import QtQuick 2.7
import QtGraphicalEffects 1.0
import QtQuick.Controls 1.4
import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
// references XXX from root context
Item {
HifiConstants { id: hifi; }
id: root;
property string referrerURL: "https://metaverse.highfidelity.com/marketplace?";
readonly property int additionalDropdownHeight: usernameDropdown.height - myUsernameButton.anchors.bottomMargin;
property alias usernameDropdownVisible: usernameDropdown.visible;
height: mainContainer.height + additionalDropdownHeight;
Hifi.QmlCommerce {
id: commerce;
onWalletStatusResult: {
if (walletStatus === 0) {
sendToParent({method: "needsLogIn"});
} else if (walletStatus === 3) {
commerce.getSecurityImage();
} else if (walletStatus > 3) {
console.log("ERROR in EmulatedMarketplaceHeader.qml: Unknown wallet status: " + walletStatus);
}
}
onLoginStatusResult: {
if (!isLoggedIn) {
sendToParent({method: "needsLogIn"});
} else {
commerce.getWalletStatus();
}
}
onSecurityImageResult: {
if (exists) {
securityImage.source = "";
securityImage.source = "image://security/securityImage";
}
}
}
Component.onCompleted: {
commerce.getWalletStatus();
}
Connections {
target: GlobalServices
onMyUsernameChanged: {
commerce.getLoginStatus();
}
}
Rectangle {
id: mainContainer;
color: hifi.colors.white;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.top: parent.top;
height: 70;
Image {
id: marketplaceHeaderImage;
source: "images/marketplaceHeaderImage.png";
anchors.top: parent.top;
anchors.topMargin: 2;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 10;
anchors.left: parent.left;
anchors.leftMargin: 8;
width: 140;
fillMode: Image.PreserveAspectFit;
MouseArea {
anchors.fill: parent;
onClicked: {
sendToParent({method: "header_marketplaceImageClicked", referrerURL: root.referrerURL});
}
}
}
Item {
id: buttonAndUsernameContainer;
anchors.left: marketplaceHeaderImage.right;
anchors.leftMargin: 8;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 10;
anchors.right: securityImage.left;
anchors.rightMargin: 6;
Rectangle {
id: myPurchasesLink;
anchors.right: myUsernameButton.left;
anchors.rightMargin: 8;
anchors.verticalCenter: parent.verticalCenter;
height: 40;
width: myPurchasesText.paintedWidth + 10;
RalewaySemiBold {
id: myPurchasesText;
text: "My Purchases";
// Text size
size: 18;
// Style
color: hifi.colors.blueAccent;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
// Anchors
anchors.centerIn: parent;
}
MouseArea {
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
sendToParent({method: 'header_goToPurchases'});
}
onEntered: myPurchasesText.color = hifi.colors.blueHighlight;
onExited: myPurchasesText.color = hifi.colors.blueAccent;
}
}
FontLoader { id: ralewayRegular; source: "../../../../fonts/Raleway-Regular.ttf"; }
TextMetrics {
id: textMetrics;
font.family: ralewayRegular.name
text: usernameText.text;
}
Rectangle {
id: myUsernameButton;
anchors.right: parent.right;
anchors.verticalCenter: parent.verticalCenter;
height: 40;
width: usernameText.width + 25;
color: "white";
radius: 4;
border.width: 1;
border.color: hifi.colors.lightGray;
// Username Text
RalewayRegular {
id: usernameText;
text: Account.username;
// Text size
size: 18;
// Style
color: hifi.colors.baseGray;
elide: Text.ElideRight;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
width: Math.min(textMetrics.width + 25, 110);
// Anchors
anchors.centerIn: parent;
rightPadding: 10;
}
HiFiGlyphs {
id: dropdownIcon;
text: hifi.glyphs.caratDn;
// Size
size: 50;
// Anchors
anchors.right: parent.right;
anchors.rightMargin: -14;
anchors.verticalCenter: parent.verticalCenter;
horizontalAlignment: Text.AlignHCenter;
// Style
color: hifi.colors.baseGray;
}
MouseArea {
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
usernameDropdown.visible = !usernameDropdown.visible;
}
onEntered: usernameText.color = hifi.colors.baseGrayShadow;
onExited: usernameText.color = hifi.colors.baseGray;
}
}
}
Image {
id: securityImage;
source: "";
visible: securityImage.source !== "";
anchors.right: parent.right;
anchors.rightMargin: 6;
anchors.top: parent.top;
anchors.topMargin: 6;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 16;
width: height;
mipmap: true;
cache: false;
MouseArea {
enabled: securityImage.visible;
anchors.fill: parent;
onClicked: {
sendToParent({method: "showSecurityPicLightbox", securityImageSource: securityImage.source});
}
}
}
LinearGradient {
z: 996;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
height: 10;
start: Qt.point(0, 0);
end: Qt.point(0, height);
gradient: Gradient {
GradientStop { position: 0.0; color: hifi.colors.lightGrayText }
GradientStop { position: 1.0; color: hifi.colors.white }
}
}
Item {
id: usernameDropdown;
z: 998;
visible: false;
anchors.top: buttonAndUsernameContainer.bottom;
anchors.topMargin: -buttonAndUsernameContainer.anchors.bottomMargin;
anchors.right: buttonAndUsernameContainer.right;
height: childrenRect.height;
width: 100;
Rectangle {
id: myItemsButton;
color: hifi.colors.white;
anchors.top: parent.top;
anchors.left: parent.left;
anchors.right: parent.right;
height: 50;
RalewaySemiBold {
anchors.fill: parent;
text: "My Items"
color: hifi.colors.baseGray;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
size: 18;
}
MouseArea {
anchors.fill: parent;
hoverEnabled: true;
onEntered: {
myItemsButton.color = hifi.colors.blueHighlight;
}
onExited: {
myItemsButton.color = hifi.colors.white;
}
onClicked: {
sendToParent({method: "header_myItemsClicked"});
}
}
}
Rectangle {
id: logOutButton;
color: hifi.colors.white;
anchors.top: myItemsButton.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
height: 50;
RalewaySemiBold {
anchors.fill: parent;
text: "Log Out"
color: hifi.colors.baseGray;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
size: 18;
}
MouseArea {
anchors.fill: parent;
hoverEnabled: true;
onEntered: {
logOutButton.color = hifi.colors.blueHighlight;
}
onExited: {
logOutButton.color = hifi.colors.white;
}
onClicked: {
Account.logOut();
}
}
}
}
DropShadow {
z: 997;
visible: usernameDropdown.visible;
anchors.fill: usernameDropdown;
horizontalOffset: 3;
verticalOffset: 3;
radius: 8.0;
samples: 17;
color: "#80000000";
source: usernameDropdown;
}
}
//
// FUNCTION DEFINITIONS START
//
signal sendToParent(var msg);
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -0,0 +1,68 @@
//
// SortableListModel.qml
// qml/hifi/commerce/common
//
// SortableListModel
//
// Created by Zach Fox on 2017-09-28
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
ListModel {
id: root;
property string sortColumnName: "";
property bool isSortingDescending: true;
function swap(a, b) {
if (a < b) {
move(a, b, 1);
move (b - 1, a, 1);
} else if (a > b) {
move(b, a, 1);
move(a - 1, b, 1);
}
}
function partition(begin, end, pivot) {
var piv = get(pivot)[sortColumnName];
swap(pivot, end - 1);
var store = begin;
for (var i = begin; i < end - 1; ++i) {
if (isSortingDescending) {
if (get(i)[sortColumnName] < piv) {
swap(store, i);
++store;
}
} else {
if (get(i)[sortColumnName] > piv) {
swap(store, i);
++store;
}
}
}
swap(end - 1, store);
return store;
}
function qsort(begin, end) {
if (end - 1 > begin) {
var pivot = begin + Math.floor(Math.random() * (end - begin));
pivot = partition(begin, end, pivot);
qsort(begin, pivot);
qsort(pivot + 1, end);
}
}
function quickSort() {
qsort(0, count)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View file

@ -0,0 +1,332 @@
//
// InspectionCertificate.qml
// qml/hifi/commerce/inspectionCertificate
//
// InspectionCertificate
//
// Created by Zach Fox on 2017-09-14
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import Hifi 1.0 as Hifi
import QtQuick 2.5
import QtQuick.Controls 1.4
import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
import "../wallet" as HifiWallet
// references XXX from root context
Rectangle {
HifiConstants { id: hifi; }
id: root;
property string marketplaceId: "";
property string itemName: "--";
property string itemOwner: "--";
property string itemEdition: "--";
property string dateOfPurchase: "";
property bool isLightbox: false;
// Style
color: hifi.colors.faintGray;
Hifi.QmlCommerce {
id: commerce;
}
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup.
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
}
Image {
anchors.fill: parent;
source: "images/cert-bg.jpg";
}
// Title text
RalewayLight {
id: titleBarText;
text: "Certificate";
// Text size
size: 40;
// Anchors
anchors.top: parent.top;
anchors.topMargin: 40;
anchors.left: parent.left;
anchors.leftMargin: 45;
anchors.right: parent.right;
height: paintedHeight;
// Style
color: hifi.colors.darkGray;
}
// Title text
RalewayRegular {
id: popText;
text: "PROOF OF PURCHASE";
// Text size
size: 16;
// Anchors
anchors.top: titleBarText.bottom;
anchors.topMargin: 4;
anchors.left: titleBarText.left;
anchors.right: titleBarText.right;
height: paintedHeight;
// Style
color: hifi.colors.baseGray;
}
//
// "CERTIFICATE" START
//
Item {
id: certificateContainer;
anchors.top: popText.bottom;
anchors.topMargin: 30;
anchors.bottom: buttonsContainer.top;
anchors.left: parent.left;
anchors.right: parent.right;
RalewayRegular {
id: itemNameHeader;
text: "ITEM NAME";
// Text size
size: 16;
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
anchors.leftMargin: 45;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: paintedHeight;
// Style
color: hifi.colors.baseGray;
}
RalewaySemiBold {
id: itemName;
text: root.itemName;
// Text size
size: 28;
// Anchors
anchors.top: itemNameHeader.bottom;
anchors.topMargin: 4;
anchors.left: itemNameHeader.left;
anchors.right: itemNameHeader.right;
height: paintedHeight;
// Style
color: hifi.colors.blueAccent;
elide: Text.ElideRight;
MouseArea {
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', itemId: root.marketplaceId});
}
onEntered: itemName.color = hifi.colors.blueHighlight;
onExited: itemName.color = hifi.colors.blueAccent;
}
}
RalewayRegular {
id: ownedByHeader;
text: "OWNER";
// Text size
size: 16;
// Anchors
anchors.top: itemName.bottom;
anchors.topMargin: 20;
anchors.left: parent.left;
anchors.leftMargin: 45;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: paintedHeight;
// Style
color: hifi.colors.baseGray;
}
RalewayRegular {
id: ownedBy;
text: root.itemOwner;
// Text size
size: 22;
// Anchors
anchors.top: ownedByHeader.bottom;
anchors.topMargin: 4;
anchors.left: ownedByHeader.left;
anchors.right: ownedByHeader.right;
height: paintedHeight;
// Style
color: hifi.colors.darkGray;
elide: Text.ElideRight;
}
RalewayRegular {
id: editionHeader;
text: "EDITION";
// Text size
size: 16;
// Anchors
anchors.top: ownedBy.bottom;
anchors.topMargin: 20;
anchors.left: parent.left;
anchors.leftMargin: 45;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: paintedHeight;
// Style
color: hifi.colors.baseGray;
}
AnonymousProRegular {
id: edition;
text: root.itemEdition;
// Text size
size: 22;
// Anchors
anchors.top: editionHeader.bottom;
anchors.topMargin: 4;
anchors.left: editionHeader.left;
anchors.right: editionHeader.right;
height: paintedHeight;
// Style
color: hifi.colors.darkGray;
}
RalewayRegular {
id: dateOfPurchaseHeader;
text: "DATE OF PURCHASE";
visible: root.dateOfPurchase !== "";
// Text size
size: 16;
// Anchors
anchors.top: ownedBy.bottom;
anchors.topMargin: 20;
anchors.left: parent.left;
anchors.leftMargin: 45;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: paintedHeight;
// Style
color: hifi.colors.baseGray;
}
AnonymousProRegular {
id: dateOfPurchase;
text: root.dateOfPurchase;
visible: root.dateOfPurchase !== "";
// Text size
size: 22;
// Anchors
anchors.top: editionHeader.bottom;
anchors.topMargin: 4;
anchors.left: editionHeader.left;
anchors.right: editionHeader.right;
height: paintedHeight;
// Style
color: hifi.colors.darkGray;
}
RalewayRegular {
id: errorText;
text: "Here we will display some text if there's an <b>error</b> with the certificate " +
"(DMCA takedown, invalid cert, location of item updated)";
// Text size
size: 20;
// Anchors
anchors.top: root.dateOfPurchase !== "" ? dateOfPurchase.bottom : edition.bottom;
anchors.topMargin: 40;
anchors.left: root.dateOfPurchase !== "" ? dateOfPurchase.left : edition.left;
anchors.right: root.dateOfPurchase !== "" ? dateOfPurchase.right : edition.right;
anchors.bottom: parent.bottom;
// Style
wrapMode: Text.WordWrap;
color: hifi.colors.redHighlight;
verticalAlignment: Text.AlignTop;
}
}
//
// "CERTIFICATE" END
//
Item {
id: buttonsContainer;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 50;
anchors.left: parent.left;
anchors.right: parent.right;
height: 50;
// "Cancel" button
HifiControlsUit.Button {
color: hifi.buttons.noneBorderless;
colorScheme: hifi.colorSchemes.light;
anchors.top: parent.top;
anchors.left: parent.left;
anchors.leftMargin: 30;
width: parent.width/2 - 50;
height: 50;
text: "close";
onClicked: {
if (root.isLightbox) {
root.visible = false;
} else {
sendToScript({method: 'inspectionCertificate_closeClicked', closeGoesToPurchases: root.closeGoesToPurchases});
}
}
}
// "Show In Marketplace" button
HifiControlsUit.Button {
id: showInMarketplaceButton;
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.light;
anchors.top: parent.top;
anchors.right: parent.right;
anchors.rightMargin: 30;
width: parent.width/2 - 50;
height: 50;
text: "View In Market"
onClicked: {
sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', itemId: root.marketplaceId});
}
}
}
//
// FUNCTION DEFINITIONS START
//
//
// Function Name: fromScript()
//
// Relevant Variables:
// None
//
// Arguments:
// message: The message sent from the JavaScript, in this case the Marketplaces JavaScript.
// Messages are in format "{method, params}", like json-rpc.
//
// Description:
// Called when a message is received from a script.
//
function fromScript(message) {
switch (message.method) {
case 'inspectionCertificate_setMarketplaceId':
root.marketplaceId = message.marketplaceId;
break;
case 'inspectionCertificate_setItemInfo':
root.itemName = message.itemName;
root.itemOwner = message.itemOwner;
root.itemEdition = message.itemEdition;
break;
default:
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
}
}
signal sendToScript(var message);
//
// FUNCTION DEFINITIONS END
//
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View file

@ -0,0 +1,215 @@
//
// FirstUseTutorial.qml
// qml/hifi/commerce/purchases
//
// FirstUseTutorial
//
// Created by Zach Fox on 2017-09-13
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import Hifi 1.0 as Hifi
import QtQuick 2.5
import QtGraphicalEffects 1.0
import QtQuick.Controls 1.4
import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
// references XXX from root context
Rectangle {
HifiConstants { id: hifi; }
id: root;
property int activeView: 1;
Image {
anchors.fill: parent;
source: "images/Purchase-First-Run-" + root.activeView + ".jpg";
}
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup.
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
}
Item {
id: header;
anchors.top: parent.top;
anchors.left: parent.left;
anchors.right: parent.right;
height: childrenRect.height;
Image {
id: marketplaceHeaderImage;
source: "../common/images/marketplaceHeaderImage.png";
anchors.top: parent.top;
anchors.topMargin: 2;
anchors.left: parent.left;
anchors.leftMargin: 8;
width: 140;
height: 58;
fillMode: Image.PreserveAspectFit;
visible: false;
}
ColorOverlay {
anchors.fill: marketplaceHeaderImage;
source: marketplaceHeaderImage;
color: "#FFFFFF"
}
RalewayRegular {
id: introText1;
text: "INTRODUCTION TO";
// Text size
size: 15;
// Anchors
anchors.top: marketplaceHeaderImage.bottom;
anchors.topMargin: 8;
anchors.left: parent.left;
anchors.leftMargin: 12;
anchors.right: parent.right;
height: paintedHeight;
// Style
color: hifi.colors.white;
}
RalewayRegular {
id: introText2;
text: "My Purchases";
// Text size
size: 28;
// Anchors
anchors.top: introText1.bottom;
anchors.left: parent.left;
anchors.leftMargin: 12;
anchors.right: parent.right;
height: paintedHeight;
// Style
color: hifi.colors.white;
}
}
//
// "STEP 1" START
//
Item {
id: step_1;
visible: root.activeView === 1;
anchors.top: header.bottom;
anchors.topMargin: 100;
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.right: parent.right;
anchors.bottom: parent.bottom;
RalewayRegular {
id: step1text;
text: "The <b>'REZ IT'</b> button makes your purchase appear in front of you.";
// Text size
size: 20;
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
width: 180;
height: paintedHeight;
// Style
color: hifi.colors.white;
wrapMode: Text.WordWrap;
}
// "Next" button
HifiControlsUit.Button {
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.dark;
anchors.top: step1text.bottom;
anchors.topMargin: 16;
anchors.left: parent.left;
width: 150;
height: 40;
text: "Next";
onClicked: {
root.activeView++;
}
}
// "SKIP" button
HifiControlsUit.Button {
color: hifi.buttons.noneBorderlessGray;
colorScheme: hifi.colorSchemes.dark;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 32;
anchors.right: parent.right;
anchors.rightMargin: 16;
width: 150;
height: 40;
text: "SKIP";
onClicked: {
sendSignalToParent({method: 'tutorial_finished'});
}
}
}
//
// "STEP 1" END
//
//
// "STEP 2" START
//
Item {
id: step_2;
visible: root.activeView === 2;
anchors.top: header.bottom;
anchors.topMargin: 45;
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.right: parent.right;
anchors.bottom: parent.bottom;
RalewayRegular {
id: step2text;
text: "If you rez an item twice, the first one will disappear.";
// Text size
size: 20;
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
width: 180;
height: paintedHeight;
// Style
color: hifi.colors.white;
wrapMode: Text.WordWrap;
}
// "GOT IT" button
HifiControlsUit.Button {
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.dark;
anchors.top: step2text.bottom;
anchors.topMargin: 16;
anchors.left: parent.left;
width: 150;
height: 40;
text: "GOT IT";
onClicked: {
sendSignalToParent({method: 'tutorial_finished'});
}
}
}
//
// "STEP 2" END
//
//
// FUNCTION DEFINITIONS START
//
signal sendSignalToParent(var message);
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -13,7 +13,9 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import QtGraphicalEffects 1.0
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
@ -21,116 +23,412 @@ import "../wallet" as HifiWallet
// references XXX from root context
Rectangle {
Item {
HifiConstants { id: hifi; }
id: root;
property string itemName: "";
property string itemId: "";
property string itemPreviewImageUrl: "";
property string itemHref: "";
// Style
color: hifi.colors.white;
// Size
width: parent.width;
height: 120;
property string purchaseStatus;
property bool purchaseStatusChanged;
property bool canRezCertifiedItems: false;
property string itemName;
property string itemId;
property string itemPreviewImageUrl;
property string itemHref;
property int displayedItemCount;
property int itemEdition;
property int numberSold;
property int limitedRun;
Image {
id: itemPreviewImage;
source: root.itemPreviewImageUrl;
property string originalStatusText;
property string originalStatusColor;
height: 110;
width: parent.width;
onPurchaseStatusChangedChanged: {
if (root.purchaseStatusChanged === true && root.purchaseStatus === "confirmed") {
root.originalStatusText = statusText.text;
root.originalStatusColor = statusText.color;
statusText.text = "CONFIRMED!";
statusText.color = hifi.colors.blueAccent;
confirmedTimer.start();
}
}
Timer {
id: confirmedTimer;
interval: 3000;
onTriggered: {
statusText.text = root.originalStatusText;
statusText.color = root.originalStatusColor;
root.purchaseStatusChanged = false;
}
}
Rectangle {
id: mainContainer;
// Style
color: hifi.colors.white;
// Size
anchors.left: parent.left;
anchors.leftMargin: 8;
anchors.right: parent.right;
anchors.rightMargin: 8;
anchors.top: parent.top;
anchors.topMargin: 8;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 8;
width: 180;
fillMode: Image.PreserveAspectFit;
height: root.height - 10;
MouseArea {
anchors.fill: parent;
onClicked: {
sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId});
}
}
}
RalewayRegular {
id: itemName;
anchors.top: itemPreviewImage.top;
anchors.left: itemPreviewImage.right;
anchors.leftMargin: 8;
anchors.right: parent.right;
anchors.rightMargin: 8;
height: 30;
// Text size
size: 20;
// Style
color: hifi.colors.blueAccent;
text: root.itemName;
elide: Text.ElideRight;
// Alignment
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter;
MouseArea {
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId});
}
onEntered: {
itemName.color = hifi.colors.blueHighlight;
}
onExited: {
itemName.color = hifi.colors.blueAccent;
}
}
}
Item {
id: buttonContainer;
anchors.top: itemName.bottom;
anchors.topMargin: 8;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 8;
anchors.left: itemPreviewImage.right;
anchors.leftMargin: 8;
anchors.right: parent.right;
anchors.rightMargin: 8;
// "Rez" button
HifiControlsUit.Button {
id: rezButton;
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.dark;
anchors.top: parent.top;
Image {
id: itemPreviewImage;
source: root.itemPreviewImageUrl;
anchors.left: parent.left;
anchors.right: parent.right;
height: parent.height/2 - 4;
text: "Rez Item"
onClicked: {
if (urlHandler.canHandleUrl(root.itemHref)) {
urlHandler.handleUrl(root.itemHref);
anchors.top: parent.top;
anchors.bottom: parent.bottom;
width: height;
fillMode: Image.PreserveAspectCrop;
MouseArea {
anchors.fill: parent;
onClicked: {
sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId});
}
}
}
// "More Info" button
HifiControlsUit.Button {
id: moreInfoButton;
color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
height: parent.height/2 - 4;
text: "More Info"
onClicked: {
sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId});
RalewaySemiBold {
id: itemName;
anchors.top: itemPreviewImage.top;
anchors.topMargin: 4;
anchors.left: itemPreviewImage.right;
anchors.leftMargin: 8;
anchors.right: buttonContainer.left;
anchors.rightMargin: 8;
height: paintedHeight;
// Text size
size: 24;
// Style
color: hifi.colors.blueAccent;
text: root.itemName;
elide: Text.ElideRight;
// Alignment
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter;
MouseArea {
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId});
}
onEntered: {
itemName.color = hifi.colors.blueHighlight;
}
onExited: {
itemName.color = hifi.colors.blueAccent;
}
}
}
Item {
id: certificateContainer;
anchors.top: itemName.bottom;
anchors.topMargin: 4;
anchors.left: itemName.left;
anchors.right: buttonContainer.left;
anchors.rightMargin: 2;
height: 24;
HiFiGlyphs {
id: certificateIcon;
text: hifi.glyphs.scriptNew;
// Size
size: 30;
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
width: 32;
// Style
color: hifi.colors.lightGray;
}
RalewayRegular {
id: viewCertificateText;
text: "VIEW CERTIFICATE";
size: 14;
anchors.left: certificateIcon.right;
anchors.leftMargin: 4;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.right: parent.right;
color: hifi.colors.lightGray;
}
MouseArea {
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
sendToPurchases({method: 'purchases_itemCertificateClicked', itemMarketplaceId: root.itemId});
}
onEntered: {
certificateIcon.color = hifi.colors.black;
viewCertificateText.color = hifi.colors.black;
}
onExited: {
certificateIcon.color = hifi.colors.lightGray;
viewCertificateText.color = hifi.colors.lightGray;
}
}
}
Item {
id: editionContainer;
visible: root.displayedItemCount > 1 && !statusContainer.visible;
anchors.left: itemName.left;
anchors.top: certificateContainer.bottom;
anchors.topMargin: 8;
anchors.bottom: parent.bottom;
anchors.right: buttonContainer.left;
anchors.rightMargin: 2;
FiraSansRegular {
anchors.left: parent.left;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
width: paintedWidth;
text: "#" + root.itemEdition;
size: 15;
color: "#cc6a6a6a";
verticalAlignment: Text.AlignTop;
}
}
Item {
id: statusContainer;
visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged;
anchors.left: itemName.left;
anchors.top: certificateContainer.bottom;
anchors.topMargin: 8;
anchors.bottom: parent.bottom;
anchors.right: buttonContainer.left;
anchors.rightMargin: 2;
RalewaySemiBold {
id: statusText;
anchors.left: parent.left;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
width: paintedWidth;
text: {
if (root.purchaseStatus === "pending") {
"PENDING..."
} else if (root.purchaseStatus === "invalidated") {
"INVALIDATED"
} else if (root.numberSold !== -1) {
("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "INFTY" : root.limitedRun))
} else {
""
}
}
size: 18;
color: {
if (root.purchaseStatus === "pending") {
hifi.colors.blueAccent
} else if (root.purchaseStatus === "invalidated") {
hifi.colors.redAccent
} else {
hifi.colors.baseGray
}
}
verticalAlignment: Text.AlignTop;
}
HiFiGlyphs {
id: statusIcon;
text: {
if (root.purchaseStatus === "pending") {
hifi.glyphs.question
} else if (root.purchaseStatus === "invalidated") {
hifi.glyphs.question
} else {
""
}
}
// Size
size: 36;
// Anchors
anchors.top: parent.top;
anchors.topMargin: -8;
anchors.left: statusText.right;
anchors.bottom: parent.bottom;
// Style
color: {
if (root.purchaseStatus === "pending") {
hifi.colors.blueAccent
} else if (root.purchaseStatus === "invalidated") {
hifi.colors.redAccent
} else {
hifi.colors.baseGray
}
}
verticalAlignment: Text.AlignTop;
}
MouseArea {
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
if (root.purchaseStatus === "pending") {
sendToPurchases({method: 'showPendingLightbox'});
} else if (root.purchaseStatus === "invalidated") {
sendToPurchases({method: 'showInvalidatedLightbox'});
}
}
onEntered: {
if (root.purchaseStatus === "pending") {
statusText.color = hifi.colors.blueHighlight;
statusIcon.color = hifi.colors.blueHighlight;
} else if (root.purchaseStatus === "invalidated") {
statusText.color = hifi.colors.redAccent;
statusIcon.color = hifi.colors.redAccent;
}
}
onExited: {
if (root.purchaseStatus === "pending") {
statusText.color = hifi.colors.blueAccent;
statusIcon.color = hifi.colors.blueAccent;
} else if (root.purchaseStatus === "invalidated") {
statusText.color = hifi.colors.redHighlight;
statusIcon.color = hifi.colors.redHighlight;
}
}
}
}
Rectangle {
id: rezzedNotifContainer;
z: 998;
visible: false;
color: hifi.colors.blueHighlight;
anchors.fill: buttonContainer;
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
}
RalewayBold {
anchors.fill: parent;
text: "REZZED";
size: 18;
color: hifi.colors.white;
verticalAlignment: Text.AlignVCenter;
horizontalAlignment: Text.AlignHCenter;
}
Timer {
id: rezzedNotifContainerTimer;
interval: 2000;
onTriggered: rezzedNotifContainer.visible = false
}
}
Button {
id: buttonContainer;
property int color: hifi.buttons.red;
property int colorScheme: hifi.colorSchemes.light;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.right: parent.right;
width: height;
enabled: root.canRezCertifiedItems && root.purchaseStatus !== "invalidated";
onClicked: {
if (urlHandler.canHandleUrl(root.itemHref)) {
urlHandler.handleUrl(root.itemHref);
}
rezzedNotifContainer.visible = true;
rezzedNotifContainerTimer.start();
}
style: ButtonStyle {
background: Rectangle {
gradient: Gradient {
GradientStop {
position: 0.2
color: {
if (!control.enabled) {
hifi.buttons.disabledColorStart[control.colorScheme]
} else if (control.pressed) {
hifi.buttons.pressedColor[control.color]
} else if (control.hovered) {
hifi.buttons.hoveredColor[control.color]
} else {
hifi.buttons.colorStart[control.color]
}
}
}
GradientStop {
position: 1.0
color: {
if (!control.enabled) {
hifi.buttons.disabledColorFinish[control.colorScheme]
} else if (control.pressed) {
hifi.buttons.pressedColor[control.color]
} else if (control.hovered) {
hifi.buttons.hoveredColor[control.color]
} else {
hifi.buttons.colorFinish[control.color]
}
}
}
}
}
label: Item {
HiFiGlyphs {
id: lightningIcon;
text: hifi.glyphs.lightning;
// Size
size: 32;
// Anchors
anchors.top: parent.top;
anchors.topMargin: 12;
anchors.left: parent.left;
anchors.right: parent.right;
horizontalAlignment: Text.AlignHCenter;
// Style
color: enabled ? hifi.buttons.textColor[control.color]
: hifi.buttons.disabledTextColor[control.colorScheme]
}
RalewayBold {
anchors.top: lightningIcon.bottom;
anchors.topMargin: -20;
anchors.right: parent.right;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
font.capitalization: Font.AllUppercase
color: enabled ? hifi.buttons.textColor[control.color]
: hifi.buttons.disabledTextColor[control.colorScheme]
size: 16;
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: "Rez It"
}
}
}
}
}
DropShadow {
anchors.fill: mainContainer;
horizontalOffset: 3;
verticalOffset: 3;
radius: 8.0;
samples: 17;
color: "#80000000";
source: mainContainer;
}
//

View file

@ -18,6 +18,8 @@ import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
import "../wallet" as HifiWallet
import "../common" as HifiCommerceCommon
import "../inspectionCertificate" as HifiInspectionCertificate
// references XXX from root context
@ -30,137 +32,152 @@ Rectangle {
property bool securityImageResultReceived: false;
property bool purchasesReceived: false;
property bool punctuationMode: false;
property bool canRezCertifiedItems: Entities.canRezCertified || Entities.canRezTmpCertified;
property bool pendingInventoryReply: true;
property bool isShowingMyItems: false;
property bool isDebuggingFirstUseTutorial: false;
// Style
color: hifi.colors.baseGray;
color: hifi.colors.white;
Hifi.QmlCommerce {
id: commerce;
onAccountResult: {
if (result.status === "success") {
commerce.getKeyFilePathIfExists();
onWalletStatusResult: {
if (walletStatus === 0) {
if (root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
}
} else if (walletStatus === 1) {
if (root.activeView !== "notSetUp") {
root.activeView = "notSetUp";
notSetUpTimer.start();
}
} else if (walletStatus === 2) {
if (root.activeView !== "passphraseModal") {
root.activeView = "passphraseModal";
}
} else if (walletStatus === 3) {
if ((Settings.getValue("isFirstUseOfPurchases", true) || root.isDebuggingFirstUseTutorial) && root.activeView !== "firstUseTutorial") {
root.activeView = "firstUseTutorial";
} else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") {
root.activeView = "purchasesMain";
commerce.inventory();
}
} else {
// unsure how to handle a failure here. We definitely cannot proceed.
console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus);
}
}
onLoginStatusResult: {
if (!isLoggedIn && root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
} else if (isLoggedIn) {
root.activeView = "initialize";
commerce.account();
}
}
onKeyFilePathIfExistsResult: {
if (path === "" && root.activeView !== "notSetUp") {
root.activeView = "notSetUp";
} else if (path !== "" && root.activeView === "initialize") {
commerce.getSecurityImage();
}
}
onSecurityImageResult: {
securityImageResultReceived = true;
if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up"
root.activeView = "notSetUp";
} else if (exists && root.activeView === "initialize") {
commerce.getWalletAuthenticatedStatus();
} else if (exists) {
// just set the source again (to be sure the change was noticed)
securityImage.source = "";
securityImage.source = "image://security/securityImage";
}
}
onWalletAuthenticatedStatusResult: {
if (!isAuthenticated && !passphraseModal.visible) {
passphraseModal.visible = true;
} else if (isAuthenticated) {
root.activeView = "purchasesMain";
commerce.inventory();
} else {
commerce.getWalletStatus();
}
}
onInventoryResult: {
purchasesReceived = true;
if (result.status !== 'success') {
console.log("Failed to get purchases", result.message);
} else {
var inventoryResult = processInventoryResult(result.data.assets);
purchasesModel.clear();
purchasesModel.append(result.data.assets);
filteredPurchasesModel.clear();
filteredPurchasesModel.append(result.data.assets);
purchasesModel.append(inventoryResult);
if (previousPurchasesModel.count !== 0) {
checkIfAnyItemStatusChanged();
} else {
// Fill statusChanged default value
// Not doing this results in the default being true...
for (var i = 0; i < purchasesModel.count; i++) {
purchasesModel.setProperty(i, "statusChanged", false);
}
}
previousPurchasesModel.append(inventoryResult);
buildFilteredPurchasesModel();
if (root.pendingInventoryReply) {
inventoryTimer.start();
}
}
root.pendingInventoryReply = false;
}
}
Timer {
id: notSetUpTimer;
interval: 200;
onTriggered: {
sendToScript({method: 'checkout_walletNotSetUp'});
}
}
HifiInspectionCertificate.InspectionCertificate {
id: inspectionCertificate;
z: 999;
visible: false;
anchors.fill: parent;
Connections {
onSendToScript: {
sendToScript(message);
}
}
}
HifiWallet.SecurityImageModel {
id: securityImageModel;
HifiCommerceCommon.CommerceLightbox {
id: lightboxPopup;
visible: false;
anchors.fill: parent;
Connections {
onSendToParent: {
sendToScript(msg);
}
}
}
//
// TITLE BAR START
//
Item {
HifiCommerceCommon.EmulatedMarketplaceHeader {
id: titleBarContainer;
z: 998;
visible: !needsLogIn.visible;
// Size
height: 50;
width: parent.width;
// Anchors
anchors.left: parent.left;
anchors.right: parent.right;
anchors.top: parent.top;
// Title Bar text
RalewaySemiBold {
id: titleBarText;
text: "PURCHASES";
// Text size
size: hifi.fontSizes.overlayTitle;
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.bottom: parent.bottom;
width: paintedWidth;
// Style
color: hifi.colors.faintGray;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
Connections {
onSendToParent: {
if (msg.method === 'needsLogIn' && root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
} else if (msg.method === 'showSecurityPicLightbox') {
lightboxPopup.titleText = "Your Security Pic";
lightboxPopup.bodyImageSource = msg.securityImageSource;
lightboxPopup.bodyText = lightboxPopup.securityPicBodyText;
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button2text = "GO TO WALLET";
lightboxPopup.button2method = "sendToParent({method: 'purchases_openWallet'});";
lightboxPopup.visible = true;
} else {
sendToScript(msg);
}
}
}
// Security Image (TEMPORARY!)
Image {
id: securityImage;
// Anchors
anchors.top: parent.top;
anchors.right: parent.right;
anchors.verticalCenter: parent.verticalCenter;
height: parent.height - 10;
width: height;
fillMode: Image.PreserveAspectFit;
mipmap: true;
cache: false;
source: "image://security/securityImage";
}
Image {
id: securityImageOverlay;
source: "../wallet/images/lockOverlay.png";
width: securityImage.width * 0.45;
height: securityImage.height * 0.45;
anchors.bottom: securityImage.bottom;
anchors.right: securityImage.right;
mipmap: true;
opacity: 0.9;
}
// Separator
HifiControlsUit.Separator {
anchors.left: parent.left;
anchors.right: parent.right;
anchors.bottom: parent.bottom;
}
MouseArea {
enabled: titleBarContainer.usernameDropdownVisible;
anchors.fill: parent;
onClicked: {
titleBarContainer.usernameDropdownVisible = false;
}
}
//
@ -171,15 +188,16 @@ Rectangle {
id: initialize;
visible: root.activeView === "initialize";
anchors.top: titleBarContainer.bottom;
anchors.topMargin: -titleBarContainer.additionalDropdownHeight;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
color: hifi.colors.baseGray;
color: hifi.colors.white;
Component.onCompleted: {
securityImageResultReceived = false;
purchasesReceived = false;
commerce.getLoginStatus();
commerce.getWalletStatus();
}
}
@ -206,101 +224,42 @@ Rectangle {
HifiWallet.PassphraseModal {
id: passphraseModal;
visible: false;
anchors.top: titleBarContainer.bottom;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
visible: root.activeView === "passphraseModal";
anchors.fill: parent;
titleBarText: "Purchases";
titleBarIcon: hifi.glyphs.wallet;
Connections {
onSendSignalToParent: {
sendToScript(msg);
}
}
}
//
// "WALLET NOT SET UP" START
//
Item {
id: notSetUp;
visible: root.activeView === "notSetUp";
anchors.top: titleBarContainer.bottom;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
RalewayRegular {
id: notSetUpText;
text: "<b>Your wallet isn't set up.</b><br><br>Set up your Wallet (no credit card necessary) to claim your <b>free HFC</b> " +
"and get items from the Marketplace.";
// Text size
size: 24;
// Anchors
anchors.top: parent.top;
anchors.bottom: notSetUpActionButtonsContainer.top;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
Item {
id: notSetUpActionButtonsContainer;
// Size
width: root.width;
height: 70;
// Anchors
anchors.left: parent.left;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 24;
// "Cancel" button
HifiControlsUit.Button {
id: cancelButton;
color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark;
anchors.top: parent.top;
anchors.topMargin: 3;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 3;
anchors.left: parent.left;
anchors.leftMargin: 20;
width: parent.width/2 - anchors.leftMargin*2;
text: "Cancel"
onClicked: {
sendToScript({method: 'purchases_backClicked', referrerURL: referrerURL});
}
}
// "Set Up" button
HifiControlsUit.Button {
id: setUpButton;
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.dark;
anchors.top: parent.top;
anchors.topMargin: 3;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 3;
anchors.right: parent.right;
anchors.rightMargin: 20;
width: parent.width/2 - anchors.rightMargin*2;
text: "Set Up Wallet"
onClicked: {
sendToScript({method: 'checkout_setUpClicked'});
if (msg.method === "authSuccess") {
root.activeView = "initialize";
commerce.getWalletStatus();
} else {
sendToScript(msg);
}
}
}
}
FirstUseTutorial {
id: firstUseTutorial;
z: 999;
visible: root.activeView === "firstUseTutorial";
anchors.fill: parent;
Connections {
onSendSignalToParent: {
switch (message.method) {
case 'tutorial_skipClicked':
case 'tutorial_finished':
Settings.setValue("isFirstUseOfPurchases", false);
root.activeView = "purchasesMain";
commerce.inventory();
break;
}
}
}
}
//
// "WALLET NOT SET UP" END
//
//
// PURCHASES CONTENTS START
@ -310,13 +269,10 @@ Rectangle {
visible: root.activeView === "purchasesMain";
// Anchors
anchors.left: parent.left;
anchors.leftMargin: 4;
anchors.right: parent.right;
anchors.rightMargin: 4;
anchors.top: titleBarContainer.bottom;
anchors.topMargin: 8;
anchors.bottom: actionButtonsContainer.top;
anchors.bottomMargin: 8;
anchors.topMargin: 8 - titleBarContainer.additionalDropdownHeight;
anchors.bottom: parent.bottom;
//
// FILTER BAR START
@ -329,32 +285,38 @@ Rectangle {
anchors.left: parent.left;
anchors.leftMargin: 8;
anchors.right: parent.right;
anchors.rightMargin: 8;
anchors.rightMargin: 12;
anchors.top: parent.top;
anchors.topMargin: 4;
RalewayRegular {
id: myText;
anchors.top: parent.top;
anchors.topMargin: 10;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 10;
anchors.left: parent.left;
anchors.leftMargin: 4;
width: paintedWidth;
text: isShowingMyItems ? "My Items" : "My Purchases";
color: hifi.colors.baseGray;
size: 28;
}
HifiControlsUit.TextField {
id: filterBar;
property int previousLength: 0;
anchors.fill: parent;
placeholderText: "Filter";
colorScheme: hifi.colorSchemes.faintGray;
hasClearButton: true;
hasRoundedBorder: true;
anchors.left: myText.right;
anchors.leftMargin: 16;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.right: parent.right;
placeholderText: "filter items";
onTextChanged: {
if (filterBar.text.length < previousLength) {
filteredPurchasesModel.clear();
for (var i = 0; i < purchasesModel.count; i++) {
filteredPurchasesModel.append(purchasesModel.get(i));
}
}
for (var i = 0; i < filteredPurchasesModel.count; i++) {
if (filteredPurchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) === -1) {
filteredPurchasesModel.remove(i);
i--;
}
}
previousLength = filterBar.text.length;
buildFilteredPurchasesModel();
}
onAccepted: {
@ -366,29 +328,111 @@ Rectangle {
// FILTER BAR END
//
HifiControlsUit.Separator {
id: separator;
colorScheme: 1;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.top: filterBarContainer.bottom;
anchors.topMargin: 16;
}
ListModel {
id: purchasesModel;
}
ListModel {
id: previousPurchasesModel;
}
HifiCommerceCommon.SortableListModel {
id: filteredPurchasesModel;
}
Rectangle {
id: cantRezCertified;
visible: !root.canRezCertifiedItems;
color: "#FFC3CD";
radius: 4;
border.color: hifi.colors.redAccent;
border.width: 1;
anchors.top: separator.bottom;
anchors.topMargin: 12;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: 80;
HiFiGlyphs {
id: lightningIcon;
text: hifi.glyphs.lightning;
// Size
size: 36;
// Anchors
anchors.top: parent.top;
anchors.topMargin: 18;
anchors.left: parent.left;
anchors.leftMargin: 12;
horizontalAlignment: Text.AlignHCenter;
// Style
color: hifi.colors.lightGray;
}
RalewayRegular {
text: "You don't have permission to rez certified items in this domain. " +
'<b><font color="' + hifi.colors.blueAccent + '"><a href="#">Learn More</a></font></b>';
// Text size
size: 18;
// Anchors
anchors.top: parent.top;
anchors.topMargin: 4;
anchors.left: lightningIcon.right;
anchors.leftMargin: 8;
anchors.right: parent.right;
anchors.rightMargin: 8;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
// Style
color: hifi.colors.baseGray;
wrapMode: Text.WordWrap;
// Alignment
verticalAlignment: Text.AlignVCenter;
onLinkActivated: {
lightboxPopup.titleText = "Rez Permission Required";
lightboxPopup.bodyText = "You don't have permission to rez certified items in this domain.<br><br>" +
"Use the <b>GOTO app</b> to visit another domain or <b>go to your own sandbox.</b>";
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button2text = "OPEN GOTO";
lightboxPopup.button2method = "sendToParent({method: 'purchases_openGoTo'});";
lightboxPopup.visible = true;
}
}
}
ListView {
id: purchasesContentsList;
visible: purchasesModel.count !== 0;
visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0);
clip: true;
model: filteredPurchasesModel;
// Anchors
anchors.top: filterBarContainer.bottom;
anchors.top: root.canRezCertifiedItems ? separator.bottom : cantRezCertified.bottom;
anchors.topMargin: 12;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
width: parent.width;
delegate: PurchasedItem {
canRezCertifiedItems: root.canRezCertifiedItems;
itemName: title;
itemId: id;
itemPreviewImageUrl: preview;
itemHref: root_file_url;
purchaseStatus: status;
purchaseStatusChanged: statusChanged;
itemEdition: model.edition_number;
numberSold: model.number_sold;
limitedRun: model.limited_run;
displayedItemCount: model.displayedItemCount;
anchors.topMargin: 12;
anchors.bottomMargin: 12;
@ -396,6 +440,26 @@ Rectangle {
onSendToPurchases: {
if (msg.method === 'purchases_itemInfoClicked') {
sendToScript({method: 'purchases_itemInfoClicked', itemId: itemId});
} else if (msg.method === 'purchases_itemCertificateClicked') {
inspectionCertificate.visible = true;
inspectionCertificate.isLightbox = true;
sendToScript(msg);
} else if (msg.method === "showInvalidatedLightbox") {
lightboxPopup.titleText = "Item Invalidated";
lightboxPopup.bodyText = 'Your item is marked "invalidated" because this item has been suspended ' +
"from the Marketplace due to a claim against its author.";
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.visible = true;
} else if (msg.method === "showPendingLightbox") {
lightboxPopup.titleText = "Item Pending";
lightboxPopup.bodyText = 'Your item is marked "pending" while your purchase is being confirmed. ' +
"Usually, purchases take about 90 seconds to confirm.";
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.visible = true;
} else if (msg.method === "setFilterText") {
filterBar.text = msg.filterText;
}
}
}
@ -403,8 +467,8 @@ Rectangle {
}
Item {
id: noPurchasesAlertContainer;
visible: !purchasesContentsList.visible && root.purchasesReceived;
id: noItemsAlertContainer;
visible: !purchasesContentsList.visible && root.purchasesReceived && root.isShowingMyItems && filterBar.text === "";
anchors.top: filterBarContainer.bottom;
anchors.topMargin: 12;
anchors.left: parent.left;
@ -413,8 +477,8 @@ Rectangle {
// Explanitory text
RalewayRegular {
id: haventPurchasedYet;
text: "<b>You haven't purchased anything yet!</b><br><br>Get an item from <b>Marketplace</b> to add it to your <b>Purchases</b>.";
id: noItemsYet;
text: "<b>You haven't submitted anything to the Marketplace yet!</b><br><br>Submit an item to the Marketplace to add it to My Items.";
// Text size
size: 22;
// Anchors
@ -426,13 +490,59 @@ Rectangle {
anchors.rightMargin: 24;
height: paintedHeight;
// Style
color: hifi.colors.faintGray;
color: hifi.colors.baseGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
}
// "Set Up" button
// "Go To Marketplace" button
HifiControlsUit.Button {
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.dark;
anchors.top: noItemsYet.bottom;
anchors.topMargin: 20;
anchors.horizontalCenter: parent.horizontalCenter;
width: parent.width * 2 / 3;
height: 50;
text: "Visit Marketplace";
onClicked: {
sendToScript({method: 'purchases_goToMarketplaceClicked'});
}
}
}
Item {
id: noPurchasesAlertContainer;
visible: !purchasesContentsList.visible && root.purchasesReceived && !root.isShowingMyItems && filterBar.text === "";
anchors.top: filterBarContainer.bottom;
anchors.topMargin: 12;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
width: parent.width;
// Explanitory text
RalewayRegular {
id: haventPurchasedYet;
text: "<b>You haven't purchased anything yet!</b><br><br>Get an item from <b>Marketplace</b> to add it to My Purchases.";
// Text size
size: 22;
// Anchors
anchors.top: parent.top;
anchors.topMargin: 150;
anchors.left: parent.left;
anchors.leftMargin: 24;
anchors.right: parent.right;
anchors.rightMargin: 24;
height: paintedHeight;
// Style
color: hifi.colors.baseGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
}
// "Go To Marketplace" button
HifiControlsUit.Button {
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.dark;
@ -452,42 +562,6 @@ Rectangle {
// PURCHASES CONTENTS END
//
//
// ACTION BUTTONS START
//
Item {
id: actionButtonsContainer;
visible: purchasesContentsContainer.visible;
// Size
width: parent.width;
height: 40;
// Anchors
anchors.left: parent.left;
anchors.bottom: keyboard.top;
anchors.bottomMargin: 8;
// "Back" button
HifiControlsUit.Button {
id: backButton;
color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark;
anchors.top: parent.top;
anchors.topMargin: 3;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 3;
anchors.left: parent.left;
anchors.leftMargin: 20;
width: parent.width/2 - anchors.leftMargin*2;
text: "Back"
onClicked: {
sendToScript({method: 'purchases_backClicked', referrerURL: referrerURL});
}
}
}
//
// ACTION BUTTONS END
//
HifiControlsUit.Keyboard {
id: keyboard;
raised: HMD.mounted && filterBar.focus;
@ -499,9 +573,97 @@ Rectangle {
}
}
onVisibleChanged: {
if (!visible) {
inventoryTimer.stop();
}
}
Timer {
id: inventoryTimer;
interval: 90000;
onTriggered: {
if (root.activeView === "purchasesMain" && !root.pendingInventoryReply) {
root.pendingInventoryReply = true;
commerce.inventory();
}
}
}
//
// FUNCTION DEFINITIONS START
//
function processInventoryResult(inventory) {
for (var i = 0; i < inventory.length; i++) {
if (inventory[i].status.length > 1) {
console.log("WARNING: Inventory result index " + i + " has a status of length >1!")
}
inventory[i].status = inventory[i].status[0];
inventory[i].categories = inventory[i].categories.join(';');
}
return inventory;
}
function populateDisplayedItemCounts() {
var itemCountDictionary = {};
var currentItemId;
for (var i = 0; i < filteredPurchasesModel.count; i++) {
currentItemId = filteredPurchasesModel.get(i).id;
if (itemCountDictionary[currentItemId] === undefined) {
itemCountDictionary[currentItemId] = 1;
} else {
itemCountDictionary[currentItemId]++;
}
}
for (var i = 0; i < filteredPurchasesModel.count; i++) {
filteredPurchasesModel.setProperty(i, "displayedItemCount", itemCountDictionary[filteredPurchasesModel.get(i).id]);
}
}
function sortByDate() {
filteredPurchasesModel.sortColumnName = "purchase_date";
filteredPurchasesModel.isSortingDescending = false;
filteredPurchasesModel.quickSort();
}
function buildFilteredPurchasesModel() {
filteredPurchasesModel.clear();
for (var i = 0; i < purchasesModel.count; i++) {
if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) {
if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) {
filteredPurchasesModel.insert(0, purchasesModel.get(i));
} else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === -1) || !root.isShowingMyItems) {
filteredPurchasesModel.append(purchasesModel.get(i));
}
}
}
populateDisplayedItemCounts();
sortByDate();
}
function checkIfAnyItemStatusChanged() {
var currentPurchasesModelId, currentPurchasesModelEdition, currentPurchasesModelStatus;
var previousPurchasesModelStatus;
for (var i = 0; i < purchasesModel.count; i++) {
currentPurchasesModelId = purchasesModel.get(i).id;
currentPurchasesModelEdition = purchasesModel.get(i).edition_number;
currentPurchasesModelStatus = purchasesModel.get(i).status;
for (var j = 0; j < previousPurchasesModel.count; j++) {
previousPurchasesModelStatus = previousPurchasesModel.get(j).status;
if (currentPurchasesModelId === previousPurchasesModel.get(j).id &&
currentPurchasesModelEdition === previousPurchasesModel.get(j).edition_number &&
currentPurchasesModelStatus !== previousPurchasesModelStatus) {
purchasesModel.setProperty(i, "statusChanged", true);
}
}
}
}
//
// Function Name: fromScript()
//
@ -519,6 +681,15 @@ Rectangle {
switch (message.method) {
case 'updatePurchases':
referrerURL = message.referrerURL;
titleBarContainer.referrerURL = message.referrerURL;
filterBar.text = message.filterText ? message.filterText : "";
break;
case 'inspectionCertificate_setMarketplaceId':
case 'inspectionCertificate_setItemInfo':
inspectionCertificate.fromScript(message);
break;
case 'purchases_showMyItems':
root.isShowingMyItems = true;
break;
default:
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));

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