diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6956fd22c3..4d616e1f3a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -80,6 +80,7 @@ endif()
if (ANDROID)
set(GLES_OPTION ON)
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
+ add_definitions(-DHIFI_ANDROID_APP=\"${HIFI_ANDROID_APP}\")
else ()
set(PLATFORM_QT_COMPONENTS WebEngine)
endif ()
diff --git a/android/apps/framePlayer/build.gradle b/android/apps/framePlayer/build.gradle
index fc8651fce1..a210c8300e 100644
--- a/android/apps/framePlayer/build.gradle
+++ b/android/apps/framePlayer/build.gradle
@@ -3,10 +3,10 @@ apply plugin: 'com.android.application'
android {
signingConfigs {
release {
- keyAlias 'key0'
- keyPassword 'password'
- storeFile file('C:/android/keystore.jks')
- storePassword 'password'
+ storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null
+ storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : ''
+ keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : ''
+ keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : ''
}
}
diff --git a/android/apps/interface/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java b/android/apps/interface/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java
deleted file mode 100644
index aad769de70..0000000000
--- a/android/apps/interface/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java
+++ /dev/null
@@ -1,42 +0,0 @@
-//
-// InterfaceActivity.java
-// gvr-interface/java
-//
-// Created by Stephen Birarda on 1/26/15.
-// 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
-//
-
-package io.highfidelity.gvrinterface;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.View;
-import android.view.WindowManager;
-import android.util.Log;
-import org.qtproject.qt5.android.bindings.QtActivity;
-
-public class InterfaceActivity extends QtActivity {
-
- public static native void handleHifiURL(String hifiURLString);
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
- // Get the intent that started this activity in case we have a hifi:// URL to parse
- Intent intent = getIntent();
- if (intent.getAction() == Intent.ACTION_VIEW) {
- Uri data = intent.getData();
-
- if (data.getScheme().equals("hifi")) {
- handleHifiURL(data.toString());
- }
- }
-
- }
-}
\ No newline at end of file
diff --git a/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java
index b7d2157737..6428044df0 100644
--- a/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java
+++ b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java
@@ -31,6 +31,7 @@ import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.SlidingDrawer;
+import org.qtproject.qt5.android.QtNative;
import org.qtproject.qt5.android.QtLayout;
import org.qtproject.qt5.android.QtSurface;
import org.qtproject.qt5.android.bindings.QtActivity;
@@ -166,8 +167,27 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
@Override
protected void onDestroy() {
- super.onDestroy();
nativeOnDestroy();
+ /*
+ cduarte https://highfidelity.manuscript.com/f/cases/16712/App-freezes-on-opening-randomly
+ After Qt upgrade to 5.11 we had a black screen crash after closing the application with
+ the hardware button "Back" and trying to start the app again. It could only be fixed after
+ totally closing the app swiping it in the list of running apps.
+ This problem did not happen with the previous Qt version.
+ After analysing changes we came up with this case and change:
+ https://codereview.qt-project.org/#/c/218882/
+ In summary they've moved libs loading to the same thread as main() and as a matter of correctness
+ in the onDestroy method in QtActivityDelegate, they exit that thread with `QtNative.m_qtThread.exit();`
+ That exit call is the main reason of this problem.
+
+ In this fix we just replace the `QtApplication.invokeDelegate();` call that may end using the
+ entire onDestroy method including that thread exit line for other three lines that purposely
+ terminate qt (borrowed from QtActivityDelegate::onDestroy as well).
+ */
+ QtNative.terminateQt();
+ QtNative.setActivity(null, null);
+ System.exit(0);
+ super.onDestroy();
}
@Override
diff --git a/android/apps/questFramePlayer/CMakeLists.txt b/android/apps/questFramePlayer/CMakeLists.txt
new file mode 100644
index 0000000000..5889585a6c
--- /dev/null
+++ b/android/apps/questFramePlayer/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(TARGET_NAME questFramePlayer)
+setup_hifi_library(AndroidExtras)
+link_hifi_libraries(shared ktx shaders gpu gl oculusMobile ${PLATFORM_GL_BACKEND})
+target_include_directories(${TARGET_NAME} PRIVATE ${HIFI_ANDROID_PRECOMPILED}/ovr/VrApi/Include)
+target_link_libraries(${TARGET_NAME} android log m)
+target_opengl()
+target_oculus_mobile()
+
+
diff --git a/android/apps/questFramePlayer/build.gradle b/android/apps/questFramePlayer/build.gradle
new file mode 100644
index 0000000000..899f9cb955
--- /dev/null
+++ b/android/apps/questFramePlayer/build.gradle
@@ -0,0 +1,51 @@
+apply plugin: 'com.android.application'
+
+android {
+ signingConfigs {
+ release {
+ storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null
+ storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : ''
+ keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : ''
+ keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : ''
+ }
+ }
+
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "io.highfidelity.frameplayer"
+ minSdkVersion 25
+ targetSdkVersion 28
+ ndk { abiFilters 'arm64-v8a' }
+ externalNativeBuild {
+ cmake {
+ arguments '-DHIFI_ANDROID=1',
+ '-DHIFI_ANDROID_APP=questFramePlayer',
+ '-DANDROID_TOOLCHAIN=clang',
+ '-DANDROID_STL=c++_shared',
+
+ '-DCMAKE_VERBOSE_MAKEFILE=ON'
+ targets = ['questFramePlayer']
+ }
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ signingConfig signingConfigs.release
+ }
+ }
+
+ externalNativeBuild.cmake.path '../../../CMakeLists.txt'
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs')
+ implementation project(':oculus')
+ implementation project(':qt')
+}
diff --git a/android/apps/questFramePlayer/proguard-rules.pro b/android/apps/questFramePlayer/proguard-rules.pro
new file mode 100644
index 0000000000..b3c0078513
--- /dev/null
+++ b/android/apps/questFramePlayer/proguard-rules.pro
@@ -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
diff --git a/android/apps/questFramePlayer/src/main/AndroidManifest.xml b/android/apps/questFramePlayer/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..721e8cee89
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/AndroidManifest.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.cpp b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.cpp
new file mode 100644
index 0000000000..ec2986298e
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.cpp
@@ -0,0 +1,25 @@
+//
+// Created by Bradley Austin Davis on 2018/10/21
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "PlayerWindow.h"
+
+#include
+
+PlayerWindow::PlayerWindow() {
+ installEventFilter(this);
+ setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint);
+ setSurfaceType(QSurface::OpenGLSurface);
+ create();
+ showFullScreen();
+ // Ensure the window is visible and the GL context is valid
+ QCoreApplication::processEvents();
+ _renderThread.initialize(this);
+}
+
+PlayerWindow::~PlayerWindow() {
+}
diff --git a/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.h b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.h
new file mode 100644
index 0000000000..e4dd6cef43
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.h
@@ -0,0 +1,29 @@
+//
+// Created by Bradley Austin Davis on 2018/10/21
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#pragma once
+#include
+#include
+
+#include
+#include "RenderThread.h"
+
+// Create a simple OpenGL window that renders text in various ways
+class PlayerWindow : public QWindow {
+public:
+ PlayerWindow();
+ virtual ~PlayerWindow();
+
+protected:
+ //bool eventFilter(QObject* obj, QEvent* event) override;
+ //void keyPressEvent(QKeyEvent* event) override;
+
+private:
+ QSettings _settings;
+ RenderThread _renderThread;
+};
diff --git a/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp b/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp
new file mode 100644
index 0000000000..5eabe6b9b1
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp
@@ -0,0 +1,240 @@
+//
+// Created by Bradley Austin Davis on 2018/10/21
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "RenderThread.h"
+
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+static JNIEnv* _env { nullptr };
+static JavaVM* _vm { nullptr };
+static jobject _activity { nullptr };
+
+struct HandController{
+ ovrInputTrackedRemoteCapabilities caps {};
+ ovrInputStateTrackedRemote state {};
+ ovrResult stateResult{ ovrSuccess };
+ ovrTracking tracking {};
+ ovrResult trackingResult{ ovrSuccess };
+
+ void update(ovrMobile* session, double time = 0.0) {
+ const auto& deviceId = caps.Header.DeviceID;
+ stateResult = vrapi_GetCurrentInputState(session, deviceId, &state.Header);
+ trackingResult = vrapi_GetInputTrackingState(session, deviceId, 0.0, &tracking);
+ }
+};
+
+std::vector devices;
+
+extern "C" {
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *, void *) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ", __FUNCTION__);
+ return JNI_VERSION_1_6;
+}
+
+
+JNIEXPORT void JNICALL Java_io_highfidelity_frameplayer_QuestQtActivity_nativeOnCreate(JNIEnv* env, jobject obj) {
+ env->GetJavaVM(&_vm);
+ _activity = env->NewGlobalRef(obj);
+}
+}
+
+static const char* FRAME_FILE = "assets:/frames/20190121_1220.json";
+
+static void textureLoader(const std::string& filename, const gpu::TexturePointer& texture, uint16_t layer) {
+ QImage image;
+ QImageReader(filename.c_str()).read(&image);
+ if (layer > 0) {
+ return;
+ }
+ texture->assignStoredMip(0, image.byteCount(), image.constBits());
+}
+
+void RenderThread::submitFrame(const gpu::FramePointer& frame) {
+ std::unique_lock lock(_frameLock);
+ _pendingFrames.push(frame);
+}
+
+void RenderThread::move(const glm::vec3& v) {
+ std::unique_lock lock(_frameLock);
+ _correction = glm::inverse(glm::translate(mat4(), v)) * _correction;
+}
+
+void RenderThread::initialize(QWindow* window) {
+ std::unique_lock lock(_frameLock);
+ setObjectName("RenderThread");
+ Parent::initialize();
+ _window = window;
+ _thread->setObjectName("RenderThread");
+}
+
+void RenderThread::setup() {
+ // Wait until the context has been moved to this thread
+ { std::unique_lock lock(_frameLock); }
+
+
+ ovr::VrHandler::initVr();
+ __android_log_write(ANDROID_LOG_WARN, "QQQ", "Launching oculus activity");
+ _vm->AttachCurrentThread(&_env, nullptr);
+ jclass cls = _env->GetObjectClass(_activity);
+ jmethodID mid = _env->GetMethodID(cls, "launchOculusActivity", "()V");
+ _env->CallVoidMethod(_activity, mid);
+ __android_log_write(ANDROID_LOG_WARN, "QQQ", "Launching oculus activity done");
+ ovr::VrHandler::setHandler(this);
+
+ makeCurrent();
+
+ // GPU library init
+ gpu::Context::init();
+ _gpuContext = std::make_shared();
+ _backend = _gpuContext->getBackend();
+ _gpuContext->beginFrame();
+ _gpuContext->endFrame();
+
+ makeCurrent();
+ glGenTextures(1, &_externalTexture);
+ glBindTexture(GL_TEXTURE_2D, _externalTexture);
+ static const glm::u8vec4 color{ 0,1,0,0 };
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &color);
+
+ if (QFileInfo(FRAME_FILE).exists()) {
+ auto frame = gpu::readFrame(FRAME_FILE, _externalTexture, &textureLoader);
+ submitFrame(frame);
+ }
+}
+
+void RenderThread::shutdown() {
+ _activeFrame.reset();
+ while (!_pendingFrames.empty()) {
+ _gpuContext->consumeFrameUpdates(_pendingFrames.front());
+ _pendingFrames.pop();
+ }
+ _gpuContext->shutdown();
+ _gpuContext.reset();
+}
+
+void RenderThread::handleInput() {
+ static std::once_flag once;
+ std::call_once(once, [&]{
+ withOvrMobile([&](ovrMobile* session){
+ int deviceIndex = 0;
+ ovrInputCapabilityHeader capsHeader;
+ while (vrapi_EnumerateInputDevices(session, deviceIndex, &capsHeader) >= 0) {
+ if (capsHeader.Type == ovrControllerType_TrackedRemote) {
+ HandController controller = {};
+ controller.caps.Header = capsHeader;
+ controller.state.Header.ControllerType = ovrControllerType_TrackedRemote;
+ vrapi_GetInputDeviceCapabilities( session, &controller.caps.Header);
+ devices.push_back(controller);
+ }
+ ++deviceIndex;
+ }
+ });
+ });
+
+ auto readResult = ovr::VrHandler::withOvrMobile([&](ovrMobile *session) {
+ for (auto &controller : devices) {
+ controller.update(session);
+ }
+ });
+
+ if (readResult) {
+ for (auto &controller : devices) {
+ const auto &caps = controller.caps;
+ if (controller.stateResult >= 0) {
+ const auto &remote = controller.state;
+ if (remote.Joystick.x != 0.0f || remote.Joystick.y != 0.0f) {
+ glm::vec3 translation;
+ float rotation = 0.0f;
+ if (caps.ControllerCapabilities & ovrControllerCaps_LeftHand) {
+ translation = glm::vec3{0.0f, -remote.Joystick.y, 0.0f};
+ } else {
+ translation = glm::vec3{remote.Joystick.x, 0.0f, -remote.Joystick.y};
+ }
+ float scale = 0.1f + (1.9f * remote.GripTrigger);
+ _correction = glm::translate(glm::mat4(), translation * scale) * _correction;
+ }
+ }
+ }
+ }
+}
+
+void RenderThread::renderFrame() {
+ GLuint finalTexture = 0;
+ glm::uvec2 finalTextureSize;
+ const auto& tracking = beginFrame();
+ if (_activeFrame) {
+ const auto& frame = _activeFrame;
+ auto& eyeProjections = frame->stereoState._eyeProjections;
+ auto& eyeOffsets = frame->stereoState._eyeViews;
+ // Quest
+ auto frameCorrection = _correction * ovr::toGlm(tracking.HeadPose.Pose);
+ _backend->setCameraCorrection(glm::inverse(frameCorrection), frame->view);
+ ovr::for_each_eye([&](ovrEye eye){
+ const auto& eyeInfo = tracking.Eye[eye];
+ eyeProjections[eye] = ovr::toGlm(eyeInfo.ProjectionMatrix);
+ eyeOffsets[eye] = ovr::toGlm(eyeInfo.ViewMatrix);
+ });
+ _backend->recycle();
+ _backend->syncCache();
+ _gpuContext->enableStereo(true);
+ if (frame && !frame->batches.empty()) {
+ _gpuContext->executeFrame(frame);
+ }
+ auto& glbackend = (gpu::gl::GLBackend&)(*_backend);
+ finalTextureSize = { frame->framebuffer->getWidth(), frame->framebuffer->getHeight() };
+ finalTexture = glbackend.getTextureID(frame->framebuffer->getRenderBuffer(0));
+ }
+ presentFrame(finalTexture, finalTextureSize, tracking);
+}
+
+bool RenderThread::process() {
+ pollTask();
+
+ if (!vrActive()) {
+ QThread::msleep(1);
+ return true;
+ }
+
+ std::queue pendingFrames;
+ {
+ std::unique_lock lock(_frameLock);
+ pendingFrames.swap(_pendingFrames);
+ }
+
+ makeCurrent();
+ while (!pendingFrames.empty()) {
+ _activeFrame = pendingFrames.front();
+ pendingFrames.pop();
+ _gpuContext->consumeFrameUpdates(_activeFrame);
+ _activeFrame->stereoState._enable = true;
+ }
+
+ handleInput();
+ renderFrame();
+ return true;
+}
diff --git a/android/apps/questFramePlayer/src/main/cpp/RenderThread.h b/android/apps/questFramePlayer/src/main/cpp/RenderThread.h
new file mode 100644
index 0000000000..701cd25f5b
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/cpp/RenderThread.h
@@ -0,0 +1,44 @@
+//
+// Created by Bradley Austin Davis on 2018/10/21
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#pragma once
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+class RenderThread : public GenericThread, ovr::VrHandler {
+ using Parent = GenericThread;
+public:
+ QWindow* _window{ nullptr };
+ std::mutex _mutex;
+ gpu::ContextPointer _gpuContext; // initialized during window creation
+ std::shared_ptr _backend;
+ std::atomic _presentCount{ 0 };
+ std::mutex _frameLock;
+ std::queue _pendingFrames;
+ gpu::FramePointer _activeFrame;
+ uint32_t _externalTexture{ 0 };
+ glm::mat4 _correction;
+
+ void move(const glm::vec3& v);
+ void setup() override;
+ bool process() override;
+ void shutdown() override;
+
+ void handleInput();
+
+ void submitFrame(const gpu::FramePointer& frame);
+ void initialize(QWindow* window);
+ void renderFrame();
+};
diff --git a/android/apps/questFramePlayer/src/main/cpp/main.cpp b/android/apps/questFramePlayer/src/main/cpp/main.cpp
new file mode 100644
index 0000000000..4730d3fa15
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/cpp/main.cpp
@@ -0,0 +1,56 @@
+//
+// Created by Bradley Austin Davis on 2018/11/22
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include
+
+#include
+#include
+#include
+
+#include
+
+#include "PlayerWindow.h"
+
+void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
+ if (!message.isEmpty()) {
+ const char * local=message.toStdString().c_str();
+ switch (type) {
+ case QtDebugMsg:
+ __android_log_write(ANDROID_LOG_DEBUG,"Interface",local);
+ break;
+ case QtInfoMsg:
+ __android_log_write(ANDROID_LOG_INFO,"Interface",local);
+ break;
+ case QtWarningMsg:
+ __android_log_write(ANDROID_LOG_WARN,"Interface",local);
+ break;
+ case QtCriticalMsg:
+ __android_log_write(ANDROID_LOG_ERROR,"Interface",local);
+ break;
+ case QtFatalMsg:
+ default:
+ __android_log_write(ANDROID_LOG_FATAL,"Interface",local);
+ abort();
+ }
+ }
+}
+
+int main(int argc, char** argv) {
+ setupHifiApplication("gpuFramePlayer");
+ QGuiApplication app(argc, argv);
+ auto oldMessageHandler = qInstallMessageHandler(messageHandler);
+ DependencyManager::set();
+ PlayerWindow window;
+ __android_log_write(ANDROID_LOG_FATAL,"QQQ","Exec");
+ app.exec();
+ __android_log_write(ANDROID_LOG_FATAL,"QQQ","Exec done");
+ qInstallMessageHandler(oldMessageHandler);
+ return 0;
+}
+
+
diff --git a/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestQtActivity.java b/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestQtActivity.java
new file mode 100644
index 0000000000..d498e27547
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestQtActivity.java
@@ -0,0 +1,53 @@
+//
+// Created by Bradley Austin Davis on 2018/11/20
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+package io.highfidelity.frameplayer;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import org.qtproject.qt5.android.bindings.QtActivity;
+
+import io.highfidelity.oculus.OculusMobileActivity;
+
+
+public class QuestQtActivity extends QtActivity {
+ private native void nativeOnCreate();
+ private boolean launchedQuestMode = false;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log.w("QQQ_Qt", "QuestQtActivity::onCreate");
+ super.onCreate(savedInstanceState);
+ nativeOnCreate();
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.w("QQQ_Qt", "QuestQtActivity::onDestroy");
+ super.onDestroy();
+ }
+
+ public void launchOculusActivity() {
+ Log.w("QQQ_Qt", "QuestQtActivity::launchOculusActivity");
+ runOnUiThread(()->{
+ keepInterfaceRunning = true;
+ launchedQuestMode = true;
+ moveTaskToBack(true);
+ startActivity(new Intent(this, QuestRenderActivity.class));
+ });
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (launchedQuestMode) {
+ moveTaskToBack(true);
+ }
+ }
+}
diff --git a/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestRenderActivity.java b/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestRenderActivity.java
new file mode 100644
index 0000000000..a395a32b68
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestRenderActivity.java
@@ -0,0 +1,14 @@
+package io.highfidelity.frameplayer;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import io.highfidelity.oculus.OculusMobileActivity;
+
+public class QuestRenderActivity extends OculusMobileActivity {
+ @Override
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+ startActivity(new Intent(this, QuestQtActivity.class));
+ }
+}
diff --git a/android/apps/questFramePlayer/src/main/res/drawable/ic_launcher.xml b/android/apps/questFramePlayer/src/main/res/drawable/ic_launcher.xml
new file mode 100644
index 0000000000..03b1edc4e9
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/res/drawable/ic_launcher.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/apps/questFramePlayer/src/main/res/values/strings.xml b/android/apps/questFramePlayer/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..8bf550f74e
--- /dev/null
+++ b/android/apps/questFramePlayer/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ GPU Frame Player
+
diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile
index c37f73cb2a..fe3a83950a 100644
--- a/android/docker/Dockerfile
+++ b/android/docker/Dockerfile
@@ -73,13 +73,10 @@ RUN mkdir "$HIFI_BASE" && \
RUN git clone https://github.com/jherico/hifi.git && \
cd ~/hifi && \
- git checkout feature/quest_move_interface
+ git checkout feature/quest_frame_player
WORKDIR /home/jenkins/hifi
-RUN touch .test6 && \
- git fetch && git reset origin/feature/quest_move_interface --hard
-
RUN mkdir build
# Pre-cache the vcpkg managed dependencies
diff --git a/android/libraries/oculus/build.gradle b/android/libraries/oculus/build.gradle
new file mode 100644
index 0000000000..b072f99eb7
--- /dev/null
+++ b/android/libraries/oculus/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 28
+
+ defaultConfig {
+ minSdkVersion 24
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
diff --git a/android/libraries/oculus/src/main/AndroidManifest.xml b/android/libraries/oculus/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..57df1a4226
--- /dev/null
+++ b/android/libraries/oculus/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java
new file mode 100644
index 0000000000..01d74ea94d
--- /dev/null
+++ b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java
@@ -0,0 +1,103 @@
+//
+// Created by Bradley Austin Davis on 2018/11/20
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+package io.highfidelity.oculus;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.WindowManager;
+
+/**
+ * Contains a native surface and forwards the activity lifecycle and surface lifecycle
+ * events to the OculusMobileDisplayPlugin
+ */
+public class OculusMobileActivity extends Activity implements SurfaceHolder.Callback {
+ private static final String TAG = OculusMobileActivity.class.getSimpleName();
+ static { System.loadLibrary("oculusMobile"); }
+ private native void nativeOnCreate();
+ private native static void nativeOnResume();
+ private native static void nativeOnPause();
+ private native static void nativeOnDestroy();
+ private native static void nativeOnSurfaceChanged(Surface s);
+
+ private SurfaceView mView;
+ private SurfaceHolder mSurfaceHolder;
+
+
+ public static void launch(Activity activity) {
+ if (activity != null) {
+ activity.runOnUiThread(()->{
+ activity.startActivity(new Intent(activity, OculusMobileActivity.class));
+ });
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log.w(TAG, "QQQ onCreate");
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ // Create a native surface for VR rendering (Qt GL surfaces are not suitable
+ // because of the lack of fine control over the surface callbacks)
+ mView = new SurfaceView(this);
+ setContentView(mView);
+ mView.getHolder().addCallback(this);
+
+ // Forward the create message to the JNI code
+ nativeOnCreate();
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.w(TAG, "QQQ onDestroy");
+ if (mSurfaceHolder != null) {
+ nativeOnSurfaceChanged(null);
+ }
+ nativeOnDestroy();
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onResume() {
+ Log.w(TAG, "QQQ onResume");
+ super.onResume();
+ nativeOnResume();
+ }
+
+ @Override
+ protected void onPause() {
+ Log.w(TAG, "QQQ onPause");
+ nativeOnPause();
+ super.onPause();
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ Log.w(TAG, "QQQ surfaceCreated");
+ nativeOnSurfaceChanged(holder.getSurface());
+ mSurfaceHolder = holder;
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Log.w(TAG, "QQQ surfaceChanged");
+ nativeOnSurfaceChanged(holder.getSurface());
+ mSurfaceHolder = holder;
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ Log.w(TAG, "QQQ surfaceDestroyed");
+ nativeOnSurfaceChanged(null);
+ mSurfaceHolder = null;
+ }
+}
\ No newline at end of file
diff --git a/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java
index 93ae2bc227..40e1863d69 100644
--- a/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java
+++ b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java
@@ -364,25 +364,7 @@ public class QtActivity extends Activity {
@Override
protected void onDestroy() {
super.onDestroy();
- /*
- cduarte https://highfidelity.manuscript.com/f/cases/16712/App-freezes-on-opening-randomly
- After Qt upgrade to 5.11 we had a black screen crash after closing the application with
- the hardware button "Back" and trying to start the app again. It could only be fixed after
- totally closing the app swiping it in the list of running apps.
- This problem did not happen with the previous Qt version.
- After analysing changes we came up with this case and change:
- https://codereview.qt-project.org/#/c/218882/
- In summary they've moved libs loading to the same thread as main() and as a matter of correctness
- in the onDestroy method in QtActivityDelegate, they exit that thread with `QtNative.m_qtThread.exit();`
- That exit call is the main reason of this problem.
-
- In this fix we just replace the `QtApplication.invokeDelegate();` call that may end using the
- entire onDestroy method including that thread exit line for other three lines that purposely
- terminate qt (borrowed from QtActivityDelegate::onDestroy as well).
- */
- QtNative.terminateQt();
- QtNative.setActivity(null, null);
- System.exit(0);
+ QtApplication.invokeDelegate();
}
//---------------------------------------------------------------------------
diff --git a/android/settings.gradle b/android/settings.gradle
index 1e7b3c768a..699f617cce 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -1,8 +1,26 @@
+//
+// Libraries
+//
+
+include ':oculus'
+project(':oculus').projectDir = new File(settingsDir, 'libraries/oculus')
+
include ':qt'
project(':qt').projectDir = new File(settingsDir, 'libraries/qt')
+//
+// Applications
+//
+
include ':interface'
project(':interface').projectDir = new File(settingsDir, 'apps/interface')
-//include ':framePlayer'
-//project(':framePlayer').projectDir = new File(settingsDir, 'apps/framePlayer')
+//
+// Test projects
+//
+
+include ':framePlayer'
+project(':framePlayer').projectDir = new File(settingsDir, 'apps/framePlayer')
+
+include ':questFramePlayer'
+project(':questFramePlayer').projectDir = new File(settingsDir, 'apps/questFramePlayer')
diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp
index ef0c807bc4..f1a6c97831 100644
--- a/assignment-client/src/scripts/EntityScriptServer.cpp
+++ b/assignment-client/src/scripts/EntityScriptServer.cpp
@@ -112,7 +112,6 @@ void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointerunloadEntityScript(entityID);
checkAndCallPreload(entityID, true);
}
}
@@ -184,7 +183,6 @@ void EntityScriptServer::updateEntityPPS() {
pps = std::min(_maxEntityPPS, pps);
}
_entityEditSender.setPacketsPerSecond(pps);
- qDebug() << QString("Updating entity PPS to: %1 @ %2 PPS per script = %3 PPS").arg(numRunningScripts).arg(_entityPPSPerScript).arg(pps);
}
void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer message, SharedNodePointer senderNode) {
@@ -525,23 +523,25 @@ void EntityScriptServer::deletingEntity(const EntityItemID& entityID) {
void EntityScriptServer::entityServerScriptChanging(const EntityItemID& entityID, bool reload) {
if (_entityViewer.getTree() && !_shuttingDown) {
- _entitiesScriptEngine->unloadEntityScript(entityID, true);
checkAndCallPreload(entityID, reload);
}
}
-void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, bool reload) {
+void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, bool forceRedownload) {
if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptEngine) {
EntityItemPointer entity = _entityViewer.getTree()->findEntityByEntityItemID(entityID);
EntityScriptDetails details;
- bool notRunning = !_entitiesScriptEngine->getEntityScriptDetails(entityID, details);
- if (entity && (reload || notRunning || details.scriptText != entity->getServerScripts())) {
+ bool isRunning = _entitiesScriptEngine->getEntityScriptDetails(entityID, details);
+ if (entity && (forceRedownload || !isRunning || details.scriptText != entity->getServerScripts())) {
+ if (isRunning) {
+ _entitiesScriptEngine->unloadEntityScript(entityID, true);
+ }
+
QString scriptUrl = entity->getServerScripts();
if (!scriptUrl.isEmpty()) {
scriptUrl = DependencyManager::get()->normalizeURL(scriptUrl);
- qCDebug(entity_script_server) << "Loading entity server script" << scriptUrl << "for" << entityID;
- _entitiesScriptEngine->loadEntityScript(entityID, scriptUrl, reload);
+ _entitiesScriptEngine->loadEntityScript(entityID, scriptUrl, forceRedownload);
}
}
}
diff --git a/assignment-client/src/scripts/EntityScriptServer.h b/assignment-client/src/scripts/EntityScriptServer.h
index 9c6c4c752e..944fee36a3 100644
--- a/assignment-client/src/scripts/EntityScriptServer.h
+++ b/assignment-client/src/scripts/EntityScriptServer.h
@@ -67,7 +67,7 @@ private:
void addingEntity(const EntityItemID& entityID);
void deletingEntity(const EntityItemID& entityID);
void entityServerScriptChanging(const EntityItemID& entityID, bool reload);
- void checkAndCallPreload(const EntityItemID& entityID, bool reload = false);
+ void checkAndCallPreload(const EntityItemID& entityID, bool forceRedownload = false);
void cleanupOldKilledListeners();
diff --git a/cmake/macros/IncludeHifiLibraryHeaders.cmake b/cmake/macros/IncludeHifiLibraryHeaders.cmake
index 913d1e1181..008d76a8dc 100644
--- a/cmake/macros/IncludeHifiLibraryHeaders.cmake
+++ b/cmake/macros/IncludeHifiLibraryHeaders.cmake
@@ -10,5 +10,5 @@
#
macro(include_hifi_library_headers LIBRARY)
- include_directories("${HIFI_LIBRARY_DIR}/${LIBRARY}/src")
+ target_include_directories(${TARGET_NAME} PRIVATE "${HIFI_LIBRARY_DIR}/${LIBRARY}/src")
endmacro(include_hifi_library_headers _library _root_dir)
\ No newline at end of file
diff --git a/cmake/macros/LinkHifiLibraries.cmake b/cmake/macros/LinkHifiLibraries.cmake
index 7a6a136799..6a430f5b13 100644
--- a/cmake/macros/LinkHifiLibraries.cmake
+++ b/cmake/macros/LinkHifiLibraries.cmake
@@ -19,8 +19,8 @@ function(LINK_HIFI_LIBRARIES)
endforeach()
foreach(HIFI_LIBRARY ${LIBRARIES_TO_LINK})
- include_directories("${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src")
- include_directories("${CMAKE_BINARY_DIR}/libraries/${HIFI_LIBRARY}")
+ target_include_directories(${TARGET_NAME} PRIVATE "${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src")
+ target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/${HIFI_LIBRARY}")
# link the actual library - it is static so don't bubble it up
target_link_libraries(${TARGET_NAME} ${HIFI_LIBRARY})
endforeach()
diff --git a/cmake/macros/TargetEGL.cmake b/cmake/macros/TargetEGL.cmake
new file mode 100644
index 0000000000..1d8ce26d83
--- /dev/null
+++ b/cmake/macros/TargetEGL.cmake
@@ -0,0 +1,4 @@
+macro(target_egl)
+ find_library(EGL EGL)
+ target_link_libraries(${TARGET_NAME} ${EGL})
+endmacro()
diff --git a/cmake/macros/TargetOculusMobile.cmake b/cmake/macros/TargetOculusMobile.cmake
new file mode 100644
index 0000000000..3eaa008b14
--- /dev/null
+++ b/cmake/macros/TargetOculusMobile.cmake
@@ -0,0 +1,20 @@
+
+macro(target_oculus_mobile)
+ set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculus/VrApi)
+
+ # Mobile SDK
+ set(OVR_MOBILE_INCLUDE_DIRS ${INSTALL_DIR}/Include)
+ target_include_directories(${TARGET_NAME} PRIVATE ${OVR_MOBILE_INCLUDE_DIRS})
+ set(OVR_MOBILE_LIBRARY_DIR ${INSTALL_DIR}/Libs/Android/arm64-v8a)
+ set(OVR_MOBILE_LIBRARY_RELEASE ${OVR_MOBILE_LIBRARY_DIR}/Release/libvrapi.so)
+ set(OVR_MOBILE_LIBRARY_DEBUG ${OVR_MOBILE_LIBRARY_DIR}/Debug/libvrapi.so)
+ select_library_configurations(OVR_MOBILE)
+ target_link_libraries(${TARGET_NAME} ${OVR_MOBILE_LIBRARIES})
+
+ # Platform SDK
+ set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculusPlatform)
+ set(OVR_PLATFORM_INCLUDE_DIRS ${INSTALL_DIR}/Include)
+ target_include_directories(${TARGET_NAME} PRIVATE ${OVR_PLATFORM_INCLUDE_DIRS})
+ set(OVR_PLATFORM_LIBRARIES ${INSTALL_DIR}/Android/libs/arm64-v8a/libovrplatformloader.so)
+ target_link_libraries(${TARGET_NAME} ${OVR_PLATFORM_LIBRARIES})
+endmacro()
diff --git a/gvr-interface/CMakeLists.txt b/gvr-interface/CMakeLists.txt
deleted file mode 100644
index 72f1096881..0000000000
--- a/gvr-interface/CMakeLists.txt
+++ /dev/null
@@ -1,85 +0,0 @@
-set(TARGET_NAME gvr-interface)
-
-if (ANDROID)
- set(ANDROID_APK_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk-build")
- set(ANDROID_APK_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk")
-
- set(ANDROID_SDK_ROOT $ENV{ANDROID_HOME})
- set(ANDROID_APP_DISPLAY_NAME Interface)
- set(ANDROID_API_LEVEL 19)
- set(ANDROID_APK_PACKAGE io.highfidelity.gvrinterface)
- set(ANDROID_ACTIVITY_NAME io.highfidelity.gvrinterface.InterfaceActivity)
- set(ANDROID_APK_VERSION_NAME "0.1")
- set(ANDROID_APK_VERSION_CODE 1)
- set(ANDROID_APK_FULLSCREEN TRUE)
- set(ANDROID_DEPLOY_QT_INSTALL "--install")
-
- set(BUILD_SHARED_LIBS ON)
- set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}")
-
- setup_hifi_library(Gui AndroidExtras)
-else ()
- setup_hifi_project(Gui)
-endif ()
-
-include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS})
-
-link_hifi_libraries(shared networking audio-client avatars)
-
-if (ANDROID)
- find_package(LibOVR)
-
- if (LIBOVR_FOUND)
- add_definitions(-DHAVE_LIBOVR)
- target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES} ${LIBOVR_ANDROID_LIBRARIES} ${TURBOJPEG_LIBRARY})
- include_directories(SYSTEM ${LIBOVR_INCLUDE_DIRS})
-
- # we need VRLib, so add a project.properties to our apk build folder that says that
- file(RELATIVE_PATH RELATIVE_VRLIB_PATH ${ANDROID_APK_OUTPUT_DIR} "${LIBOVR_VRLIB_DIR}")
- file(WRITE "${ANDROID_APK_BUILD_DIR}/project.properties" "android.library.reference.1=${RELATIVE_VRLIB_PATH}")
-
- list(APPEND IGNORE_COPY_LIBS ${LIBOVR_ANDROID_LIBRARIES})
- endif ()
-
-endif ()
-
-# the presence of a HOCKEY_APP_ID means we are making a beta build
-if (ANDROID AND HOCKEY_APP_ID)
- set(HOCKEY_APP_ENABLED true)
- set(HOCKEY_APP_ACTIVITY "\n")
- set(ANDROID_ACTIVITY_NAME io.highfidelity.gvrinterface.InterfaceBetaActivity)
- set(ANDROID_DEPLOY_QT_INSTALL "")
- set(ANDROID_APK_CUSTOM_NAME "Interface-beta.apk")
-
- # set the ANDROID_APK_VERSION_CODE to the number of git commits
- execute_process(
- COMMAND git rev-list --first-parent --count HEAD
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
- OUTPUT_VARIABLE GIT_COMMIT_COUNT
- OUTPUT_STRIP_TRAILING_WHITESPACE
- )
-
- set(ANDROID_APK_VERSION_CODE ${GIT_COMMIT_COUNT})
-
- configure_file("${CMAKE_CURRENT_SOURCE_DIR}/templates/InterfaceBetaActivity.java.in" "${ANDROID_APK_BUILD_DIR}/src/io/highfidelity/gvrinterface/InterfaceBetaActivity.java")
-elseif (ANDROID)
- set(HOCKEY_APP_ENABLED false)
-endif ()
-
-if (ANDROID)
-
- set(HIFI_URL_INTENT "\
- \n \
- \n \
- \n \
- \n \
- \n "
- )
-
- set(ANDROID_EXTRA_APPLICATION_XML "${HOCKEY_APP_ACTIVITY}")
- set(ANDROID_EXTRA_ACTIVITY_XML "${HIFI_URL_INTENT}")
-
- configure_file("${CMAKE_CURRENT_SOURCE_DIR}/templates/hockeyapp.xml.in" "${ANDROID_APK_BUILD_DIR}/res/values/hockeyapp.xml")
- qt_create_apk()
-
-endif (ANDROID)
diff --git a/gvr-interface/res/drawable/icon.png b/gvr-interface/res/drawable/icon.png
deleted file mode 100644
index 70aaf9b4ed..0000000000
Binary files a/gvr-interface/res/drawable/icon.png and /dev/null differ
diff --git a/gvr-interface/src/Client.cpp b/gvr-interface/src/Client.cpp
deleted file mode 100644
index 8f064c7fd5..0000000000
--- a/gvr-interface/src/Client.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-//
-// Client.cpp
-// gvr-interface/src
-//
-// Created by Stephen Birarda on 1/20/15.
-// 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
-//
-
-#include "Client.h"
-
-#include
-#include
-#include
-#include
-#include
-
-Client::Client(QObject* parent) :
- QObject(parent)
-{
- // we need to make sure that required dependencies are created
- DependencyManager::set();
-
- setupNetworking();
-}
-
-void Client::setupNetworking() {
- // once Application order of instantiation is fixed this should be done from AccountManager
- AccountManager::getInstance().setAuthURL(DEFAULT_NODE_AUTH_URL);
-
- // setup the NodeList for this client
- DependencyManager::registerInheritance();
- auto nodeList = DependencyManager::set(NodeType::Agent, 0);
-
- // while datagram processing remains simple for targets using Client, we'll handle datagrams
- connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &Client::processDatagrams);
-
- // every second, ask the NodeList to check in with the domain server
- QTimer* domainCheckInTimer = new QTimer(this);
- domainCheckInTimer->setInterval(DOMAIN_SERVER_CHECK_IN_MSECS);
- connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
-
- // TODO: once the Client knows its Address on start-up we should be able to immediately send a check in here
- domainCheckInTimer->start();
-
- // handle the case where the domain stops talking to us
- // TODO: can we just have the nodelist do this when it sets up? Is there a user of the NodeList that wouldn't want this?
- connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset);
-}
-
-void Client::processVerifiedPacket(const HifiSockAddr& senderSockAddr, const QByteArray& incomingPacket) {
- DependencyManager::get()->processNodeData(senderSockAddr, incomingPacket);
-}
-
-void Client::processDatagrams() {
- HifiSockAddr senderSockAddr;
-
- static QByteArray incomingPacket;
-
- auto nodeList = DependencyManager::get();
-
- while (DependencyManager::get()->getNodeSocket().hasPendingDatagrams()) {
- incomingPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
- nodeList->getNodeSocket().readDatagram(incomingPacket.data(), incomingPacket.size(),
- senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
-
- if (nodeList->packetVersionAndHashMatch(incomingPacket)) {
- processVerifiedPacket(senderSockAddr, incomingPacket);
- }
- }
-}
diff --git a/gvr-interface/src/Client.h b/gvr-interface/src/Client.h
deleted file mode 100644
index 6fbe40f165..0000000000
--- a/gvr-interface/src/Client.h
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-// Client.h
-// gvr-interface/src
-//
-// Created by Stephen Birarda on 1/20/15.
-// 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
-//
-
-#ifndef hifi_Client_h
-#define hifi_Client_h
-
-#include
-
-#include
-
-class Client : public QObject {
- Q_OBJECT
-public:
- Client(QObject* parent = 0);
-
- virtual void cleanupBeforeQuit() = 0;
-protected:
-
- void setupNetworking();
- virtual void processVerifiedPacket(const HifiSockAddr& senderSockAddr, const QByteArray& incomingPacket);
-private slots:
- void processDatagrams();
-};
-
-#endif // hifi_Client_h
diff --git a/gvr-interface/src/GVRInterface.cpp b/gvr-interface/src/GVRInterface.cpp
deleted file mode 100644
index f9a29d4ac4..0000000000
--- a/gvr-interface/src/GVRInterface.cpp
+++ /dev/null
@@ -1,191 +0,0 @@
-//
-// GVRInterface.cpp
-// gvr-interface/src
-//
-// 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
-//
-
-#include "GVRInterface.h"
-
-#ifdef ANDROID
-
-#include
-
-#include
-#include
-#include
-
-#ifdef HAVE_LIBOVR
-
-#include
-#include
-
-#endif
-#endif
-
-#include
-#include
-#include
-
-#include "GVRMainWindow.h"
-#include "RenderingClient.h"
-
-static QString launchURLString = QString();
-
-#ifdef ANDROID
-
-extern "C" {
-
-JNIEXPORT void Java_io_highfidelity_gvrinterface_InterfaceActivity_handleHifiURL(JNIEnv *jni, jclass clazz, jstring hifiURLString) {
- launchURLString = QAndroidJniObject(hifiURLString).toString();
-}
-
-}
-
-#endif
-
-GVRInterface::GVRInterface(int argc, char* argv[]) :
- QApplication(argc, argv),
- _mainWindow(NULL),
- _inVRMode(false)
-{
- setApplicationName("gvr-interface");
- setOrganizationName("highfidelity");
- setOrganizationDomain("io");
-
- if (!launchURLString.isEmpty()) {
- // did we get launched with a lookup URL? If so it is time to give that to the AddressManager
- qDebug() << "We were opened via a hifi URL -" << launchURLString;
- }
-
- _client = new RenderingClient(this, launchURLString);
-
- launchURLString = QString();
-
- connect(this, &QGuiApplication::applicationStateChanged, this, &GVRInterface::handleApplicationStateChange);
-
-#if defined(ANDROID) && defined(HAVE_LIBOVR)
- QAndroidJniEnvironment jniEnv;
-
- QPlatformNativeInterface* interface = QApplication::platformNativeInterface();
- jobject activity = (jobject) interface->nativeResourceForIntegration("QtActivity");
-
- ovr_RegisterHmtReceivers(&*jniEnv, activity);
-
- // PLATFORMACTIVITY_REMOVAL: Temp workaround for PlatformActivity being
- // stripped from UnityPlugin. Alternate is to use LOCAL_WHOLE_STATIC_LIBRARIES
- // but that increases the size of the plugin by ~1MiB
- OVR::linkerPlatformActivity++;
-#endif
-
- // call our idle function whenever we can
- QTimer* idleTimer = new QTimer(this);
- connect(idleTimer, &QTimer::timeout, this, &GVRInterface::idle);
- idleTimer->start(0);
-
- // call our quit handler before we go down
- connect(this, &QCoreApplication::aboutToQuit, this, &GVRInterface::handleApplicationQuit);
-}
-
-void GVRInterface::handleApplicationQuit() {
- _client->cleanupBeforeQuit();
-}
-
-void GVRInterface::idle() {
-#if defined(ANDROID) && defined(HAVE_LIBOVR)
- if (!_inVRMode && ovr_IsHeadsetDocked()) {
- qDebug() << "The headset just got docked - enter VR mode.";
- enterVRMode();
- } else if (_inVRMode) {
-
- if (ovr_IsHeadsetDocked()) {
- static int counter = 0;
-
- // Get the latest head tracking state, predicted ahead to the midpoint of the time
- // it will be displayed. It will always be corrected to the real values by
- // time warp, but the closer we get, the less black will be pulled in at the edges.
- const double now = ovr_GetTimeInSeconds();
- static double prev;
- const double rawDelta = now - prev;
- prev = now;
- const double clampedPrediction = std::min( 0.1, rawDelta * 2);
- ovrSensorState sensor = ovrHmd_GetSensorState(OvrHmd, now + clampedPrediction, true );
-
- auto ovrOrientation = sensor.Predicted.Pose.Orientation;
- glm::quat newOrientation(ovrOrientation.w, ovrOrientation.x, ovrOrientation.y, ovrOrientation.z);
- _client->setOrientation(newOrientation);
-
- if (counter++ % 100000 == 0) {
- qDebug() << "GetSensorState in frame" << counter << "-"
- << ovrOrientation.x << ovrOrientation.y << ovrOrientation.z << ovrOrientation.w;
- }
- } else {
- qDebug() << "The headset was undocked - leaving VR mode.";
-
- leaveVRMode();
- }
- }
-
- OVR::KeyState& backKeyState = _mainWindow->getBackKeyState();
- auto backEvent = backKeyState.Update(ovr_GetTimeInSeconds());
-
- if (backEvent == OVR::KeyState::KEY_EVENT_LONG_PRESS) {
- qDebug() << "Attemping to start the Platform UI Activity.";
- ovr_StartPackageActivity(_ovr, PUI_CLASS_NAME, PUI_GLOBAL_MENU);
- } else if (backEvent == OVR::KeyState::KEY_EVENT_DOUBLE_TAP || backEvent == OVR::KeyState::KEY_EVENT_SHORT_PRESS) {
- qDebug() << "Got an event we should cancel for!";
- } else if (backEvent == OVR::KeyState::KEY_EVENT_DOUBLE_TAP) {
- qDebug() << "The button is down!";
- }
-#endif
-}
-
-void GVRInterface::handleApplicationStateChange(Qt::ApplicationState state) {
- switch(state) {
- case Qt::ApplicationActive:
- qDebug() << "The application is active.";
- break;
- case Qt::ApplicationSuspended:
- qDebug() << "The application is being suspended.";
- break;
- default:
- break;
- }
-}
-
-void GVRInterface::enterVRMode() {
-#if defined(ANDROID) && defined(HAVE_LIBOVR)
- // Default vrModeParms
- ovrModeParms vrModeParms;
- vrModeParms.AsynchronousTimeWarp = true;
- vrModeParms.AllowPowerSave = true;
- vrModeParms.DistortionFileName = NULL;
- vrModeParms.EnableImageServer = false;
- vrModeParms.CpuLevel = 2;
- vrModeParms.GpuLevel = 2;
- vrModeParms.GameThreadTid = 0;
-
- QAndroidJniEnvironment jniEnv;
-
- QPlatformNativeInterface* interface = QApplication::platformNativeInterface();
- jobject activity = (jobject) interface->nativeResourceForIntegration("QtActivity");
-
- vrModeParms.ActivityObject = activity;
-
- ovrHmdInfo hmdInfo;
- _ovr = ovr_EnterVrMode(vrModeParms, &hmdInfo);
-
- _inVRMode = true;
-#endif
-}
-
-void GVRInterface::leaveVRMode() {
-#if defined(ANDROID) && defined(HAVE_LIBOVR)
- ovr_LeaveVrMode(_ovr);
- _inVRMode = false;
-#endif
-}
diff --git a/gvr-interface/src/GVRInterface.h b/gvr-interface/src/GVRInterface.h
deleted file mode 100644
index 9ffbd52909..0000000000
--- a/gvr-interface/src/GVRInterface.h
+++ /dev/null
@@ -1,72 +0,0 @@
-//
-// GVRInterface.h
-// gvr-interface/src
-//
-// 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
-//
-
-#ifndef hifi_GVRInterface_h
-#define hifi_GVRInterface_h
-
-#include
-
-#if defined(ANDROID) && defined(HAVE_LIBOVR)
-class ovrMobile;
-class ovrHmdInfo;
-
-// This is set by JNI_OnLoad() when the .so is initially loaded.
-// Must use to attach each thread that will use JNI:
-namespace OVR {
- // PLATFORMACTIVITY_REMOVAL: Temp workaround for PlatformActivity being
- // stripped from UnityPlugin. Alternate is to use LOCAL_WHOLE_STATIC_LIBRARIES
- // but that increases the size of the plugin by ~1MiB
- extern int linkerPlatformActivity;
-}
-
-#endif
-
-class GVRMainWindow;
-class RenderingClient;
-class QKeyEvent;
-
-#if defined(qApp)
-#undef qApp
-#endif
-#define qApp (static_cast(QApplication::instance()))
-
-class GVRInterface : public QApplication {
- Q_OBJECT
-public:
- GVRInterface(int argc, char* argv[]);
- RenderingClient* getClient() { return _client; }
-
- void setMainWindow(GVRMainWindow* mainWindow) { _mainWindow = mainWindow; }
-
-protected:
- void keyPressEvent(QKeyEvent* event);
-
-private slots:
- void handleApplicationStateChange(Qt::ApplicationState state);
- void idle();
-private:
- void handleApplicationQuit();
-
- void enterVRMode();
- void leaveVRMode();
-
-#if defined(ANDROID) && defined(HAVE_LIBOVR)
- ovrMobile* _ovr;
- ovrHmdInfo* _hmdInfo;
-#endif
-
- GVRMainWindow* _mainWindow;
-
- RenderingClient* _client;
- bool _inVRMode;
-};
-
-#endif // hifi_GVRInterface_h
diff --git a/gvr-interface/src/GVRMainWindow.cpp b/gvr-interface/src/GVRMainWindow.cpp
deleted file mode 100644
index 5495354233..0000000000
--- a/gvr-interface/src/GVRMainWindow.cpp
+++ /dev/null
@@ -1,176 +0,0 @@
-//
-// GVRMainWindow.cpp
-// gvr-interface/src
-//
-// Created by Stephen Birarda on 1/20/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
-//
-
-#include "GVRMainWindow.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#ifndef ANDROID
-
-#include
-
-#elif defined(HAVE_LIBOVR)
-
-#include
-
-const float LIBOVR_DOUBLE_TAP_DURATION = 0.25f;
-const float LIBOVR_LONG_PRESS_DURATION = 0.75f;
-
-#endif
-
-#include
-
-#include "InterfaceView.h"
-#include "LoginDialog.h"
-#include "RenderingClient.h"
-
-
-
-GVRMainWindow::GVRMainWindow(QWidget* parent) :
- QMainWindow(parent),
-#if defined(ANDROID) && defined(HAVE_LIBOVR)
- _backKeyState(LIBOVR_DOUBLE_TAP_DURATION, LIBOVR_LONG_PRESS_DURATION),
- _wasBackKeyDown(false),
-#endif
- _mainLayout(NULL),
- _menuBar(NULL),
- _loginAction(NULL)
-{
-
-#ifndef ANDROID
- const int NOTE_4_WIDTH = 2560;
- const int NOTE_4_HEIGHT = 1440;
- setFixedSize(NOTE_4_WIDTH / 2, NOTE_4_HEIGHT / 2);
-#endif
-
- setupMenuBar();
-
- QWidget* baseWidget = new QWidget(this);
-
- // setup a layout so we can vertically align to top
- _mainLayout = new QVBoxLayout(baseWidget);
- _mainLayout->setAlignment(Qt::AlignTop);
-
- // set the layout on the base widget
- baseWidget->setLayout(_mainLayout);
-
- setCentralWidget(baseWidget);
-
- // add the interface view
- new InterfaceView(baseWidget);
-}
-
-GVRMainWindow::~GVRMainWindow() {
- delete _menuBar;
-}
-
-void GVRMainWindow::keyPressEvent(QKeyEvent* event) {
-#ifdef ANDROID
- if (event->key() == Qt::Key_Back) {
- // got the Android back key, hand off to OVR KeyState
- _backKeyState.HandleEvent(ovr_GetTimeInSeconds(), true, (_wasBackKeyDown ? 1 : 0));
- _wasBackKeyDown = true;
- return;
- }
-#endif
- QWidget::keyPressEvent(event);
-}
-
-void GVRMainWindow::keyReleaseEvent(QKeyEvent* event) {
-#ifdef ANDROID
- if (event->key() == Qt::Key_Back) {
- // release on the Android back key, hand off to OVR KeyState
- _backKeyState.HandleEvent(ovr_GetTimeInSeconds(), false, 0);
- _wasBackKeyDown = false;
- }
-#endif
- QWidget::keyReleaseEvent(event);
-}
-
-void GVRMainWindow::setupMenuBar() {
- QMenu* fileMenu = new QMenu("File");
- QMenu* helpMenu = new QMenu("Help");
-
- _menuBar = new QMenuBar(0);
-
- _menuBar->addMenu(fileMenu);
- _menuBar->addMenu(helpMenu);
-
- QAction* goToAddress = new QAction("Go to Address", fileMenu);
- connect(goToAddress, &QAction::triggered, this, &GVRMainWindow::showAddressBar);
- fileMenu->addAction(goToAddress);
-
- _loginAction = new QAction("Login", fileMenu);
- fileMenu->addAction(_loginAction);
-
- // change the login action depending on our logged in/out state
- AccountManager& accountManager = AccountManager::getInstance();
- connect(&accountManager, &AccountManager::loginComplete, this, &GVRMainWindow::refreshLoginAction);
- connect(&accountManager, &AccountManager::logoutComplete, this, &GVRMainWindow::refreshLoginAction);
-
- // refresh the state now
- refreshLoginAction();
-
- QAction* aboutQt = new QAction("About Qt", helpMenu);
- connect(aboutQt, &QAction::triggered, qApp, &QApplication::aboutQt);
- helpMenu->addAction(aboutQt);
-
- setMenuBar(_menuBar);
-}
-
-void GVRMainWindow::showAddressBar() {
- // setup the address QInputDialog
- QInputDialog* addressDialog = new QInputDialog(this);
- addressDialog->setLabelText("Address");
-
- // add the address dialog to the main layout
- _mainLayout->addWidget(addressDialog);
-
- connect(addressDialog, &QInputDialog::textValueSelected,
- DependencyManager::get().data(), &AddressManager::handleLookupString);
-}
-
-void GVRMainWindow::showLoginDialog() {
- LoginDialog* loginDialog = new LoginDialog(this);
-
- // have the acccount manager handle credentials from LoginDialog
- AccountManager& accountManager = AccountManager::getInstance();
- connect(loginDialog, &LoginDialog::credentialsEntered, &accountManager, &AccountManager::requestAccessToken);
- connect(&accountManager, &AccountManager::loginFailed, this, &GVRMainWindow::showLoginFailure);
-
- _mainLayout->addWidget(loginDialog);
-}
-
-void GVRMainWindow::showLoginFailure() {
- QMessageBox::warning(this, "Login Failed",
- "Could not log in with that username and password. Please try again!");
-}
-
-void GVRMainWindow::refreshLoginAction() {
- AccountManager& accountManager = AccountManager::getInstance();
- disconnect(_loginAction, &QAction::triggered, &accountManager, 0);
-
- if (accountManager.isLoggedIn()) {
- _loginAction->setText("Logout");
- connect(_loginAction, &QAction::triggered, &accountManager, &AccountManager::logout);
- } else {
- _loginAction->setText("Login");
- connect(_loginAction, &QAction::triggered, this, &GVRMainWindow::showLoginDialog);
- }
-
-}
diff --git a/gvr-interface/src/GVRMainWindow.h b/gvr-interface/src/GVRMainWindow.h
deleted file mode 100644
index c28c19a6c1..0000000000
--- a/gvr-interface/src/GVRMainWindow.h
+++ /dev/null
@@ -1,58 +0,0 @@
-//
-// GVRMainWindow.h
-// gvr-interface/src
-//
-// Created by Stephen Birarda on 1/20/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
-//
-
-#ifndef hifi_GVRMainWindow_h
-#define hifi_GVRMainWindow_h
-
-#include
-
-#if defined(ANDROID) && defined(HAVE_LIBOVR)
-#include
-#endif
-
-class QKeyEvent;
-class QMenuBar;
-class QVBoxLayout;
-
-class GVRMainWindow : public QMainWindow {
- Q_OBJECT
-public:
- GVRMainWindow(QWidget* parent = 0);
- ~GVRMainWindow();
-public slots:
- void showAddressBar();
- void showLoginDialog();
-
- void showLoginFailure();
-
-#if defined(ANDROID) && defined(HAVE_LIBOVR)
- OVR::KeyState& getBackKeyState() { return _backKeyState; }
-#endif
-
-protected:
- void keyPressEvent(QKeyEvent* event);
- void keyReleaseEvent(QKeyEvent* event);
-private slots:
- void refreshLoginAction();
-private:
- void setupMenuBar();
-
-#if defined(ANDROID) && defined(HAVE_LIBOVR)
- OVR::KeyState _backKeyState;
- bool _wasBackKeyDown;
-#endif
-
- QVBoxLayout* _mainLayout;
- QMenuBar* _menuBar;
- QAction* _loginAction;
-};
-
-#endif // hifi_GVRMainWindow_h
diff --git a/gvr-interface/src/InterfaceView.cpp b/gvr-interface/src/InterfaceView.cpp
deleted file mode 100644
index e7992d3921..0000000000
--- a/gvr-interface/src/InterfaceView.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-// InterfaceView.cpp
-// gvr-interface/src
-//
-// Created by Stephen Birarda on 1/28/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
-//
-
-#include "InterfaceView.h"
-
-InterfaceView::InterfaceView(QWidget* parent, Qt::WindowFlags flags) :
- QOpenGLWidget(parent, flags)
-{
-
-}
\ No newline at end of file
diff --git a/gvr-interface/src/InterfaceView.h b/gvr-interface/src/InterfaceView.h
deleted file mode 100644
index 3d358a3e64..0000000000
--- a/gvr-interface/src/InterfaceView.h
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-// InterfaceView.h
-// gvr-interface/src
-//
-// Created by Stephen Birarda on 1/28/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
-//
-
-#ifndef hifi_InterfaceView_h
-#define hifi_InterfaceView_h
-
-#include
-
-class InterfaceView : public QOpenGLWidget {
- Q_OBJECT
-public:
- InterfaceView(QWidget* parent = 0, Qt::WindowFlags flags = 0);
-};
-
-#endif // hifi_InterfaceView_h
diff --git a/gvr-interface/src/LoginDialog.cpp b/gvr-interface/src/LoginDialog.cpp
deleted file mode 100644
index d4efd425bd..0000000000
--- a/gvr-interface/src/LoginDialog.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-//
-// LoginDialog.cpp
-// gvr-interface/src
-//
-// Created by Stephen Birarda on 2015-02-03.
-// 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
-//
-
-#include "LoginDialog.h"
-
-#include
-#include
-#include
-#include
-#include
-
-LoginDialog::LoginDialog(QWidget* parent) :
- QDialog(parent)
-{
- setupGUI();
- setWindowTitle("Login");
- setModal(true);
-}
-
-void LoginDialog::setupGUI() {
- // setup a grid layout
- QGridLayout* formGridLayout = new QGridLayout(this);
-
- _usernameLineEdit = new QLineEdit(this);
-
- QLabel* usernameLabel = new QLabel(this);
- usernameLabel->setText("Username");
- usernameLabel->setBuddy(_usernameLineEdit);
-
- formGridLayout->addWidget(usernameLabel, 0, 0);
- formGridLayout->addWidget(_usernameLineEdit, 1, 0);
-
- _passwordLineEdit = new QLineEdit(this);
- _passwordLineEdit->setEchoMode(QLineEdit::Password);
-
- QLabel* passwordLabel = new QLabel(this);
- passwordLabel->setText("Password");
- passwordLabel->setBuddy(_passwordLineEdit);
-
- formGridLayout->addWidget(passwordLabel, 2, 0);
- formGridLayout->addWidget(_passwordLineEdit, 3, 0);
-
- QDialogButtonBox* buttons = new QDialogButtonBox(this);
-
- QPushButton* okButton = buttons->addButton(QDialogButtonBox::Ok);
- QPushButton* cancelButton = buttons->addButton(QDialogButtonBox::Cancel);
-
- okButton->setText("Login");
-
- connect(cancelButton, &QPushButton::clicked, this, &QDialog::close);
- connect(okButton, &QPushButton::clicked, this, &LoginDialog::loginButtonClicked);
-
- formGridLayout->addWidget(buttons, 4, 0, 1, 2);
-
- setLayout(formGridLayout);
-}
-
-void LoginDialog::loginButtonClicked() {
- emit credentialsEntered(_usernameLineEdit->text(), _passwordLineEdit->text());
- close();
-}
diff --git a/gvr-interface/src/LoginDialog.h b/gvr-interface/src/LoginDialog.h
deleted file mode 100644
index 13f630818d..0000000000
--- a/gvr-interface/src/LoginDialog.h
+++ /dev/null
@@ -1,34 +0,0 @@
-//
-// LoginDialog.h
-// gvr-interface/src
-//
-// Created by Stephen Birarda on 2015-02-03.
-// 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
-//
-
-#ifndef hifi_LoginDialog_h
-#define hifi_LoginDialog_h
-
-#include
-
-class QLineEdit;
-
-class LoginDialog : public QDialog {
- Q_OBJECT
-public:
- LoginDialog(QWidget* parent = 0);
-signals:
- void credentialsEntered(const QString& username, const QString& password);
-private slots:
- void loginButtonClicked();
-private:
- void setupGUI();
-
- QLineEdit* _usernameLineEdit;
- QLineEdit* _passwordLineEdit;
-};
-
-#endif // hifi_LoginDialog_h
\ No newline at end of file
diff --git a/gvr-interface/src/RenderingClient.cpp b/gvr-interface/src/RenderingClient.cpp
deleted file mode 100644
index 4c691a48e6..0000000000
--- a/gvr-interface/src/RenderingClient.cpp
+++ /dev/null
@@ -1,156 +0,0 @@
-//
-// RenderingClient.cpp
-// gvr-interface/src
-//
-// Created by Stephen Birarda on 1/20/15.
-// 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
-//
-
-#include "RenderingClient.h"
-
-#include
-#include
-
-#include
-#include
-#include
-#include
-
-RenderingClient* RenderingClient::_instance = NULL;
-
-RenderingClient::RenderingClient(QObject *parent, const QString& launchURLString) :
- Client(parent)
-{
- _instance = this;
-
- // connect to AddressManager and pass it the launch URL, if we have one
- auto addressManager = DependencyManager::get();
- connect(addressManager.data(), &AddressManager::locationChangeRequired, this, &RenderingClient::goToLocation);
- addressManager->loadSettings(launchURLString);
-
- // tell the NodeList which node types all rendering clients will want to know about
- DependencyManager::get()->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer);
-
- DependencyManager::set();
-
- // get our audio client setup on its own thread
- auto audioClient = DependencyManager::set();
- audioClient->setPositionGetter(getPositionForAudio);
- audioClient->setOrientationGetter(getOrientationForAudio);
- audioClient->startThread();
-
-
- connect(&_avatarTimer, &QTimer::timeout, this, &RenderingClient::sendAvatarPacket);
- _avatarTimer.setInterval(16); // 60 FPS
- _avatarTimer.start();
- _fakeAvatar.setDisplayName("GearVR");
- _fakeAvatar.setFaceModelURL(QUrl(DEFAULT_HEAD_MODEL_URL));
- _fakeAvatar.setSkeletonModelURL(QUrl(DEFAULT_BODY_MODEL_URL));
- _fakeAvatar.toByteArray(); // Creates HeadData
-}
-
-void RenderingClient::sendAvatarPacket() {
- _fakeAvatar.setPosition(_position);
- _fakeAvatar.setHeadOrientation(_orientation);
-
- QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarData);
- packet.append(_fakeAvatar.toByteArray());
- DependencyManager::get()->broadcastToNodes(packet, NodeSet() << NodeType::AvatarMixer);
- _fakeAvatar.sendIdentityPacket();
-}
-
-void RenderingClient::cleanupBeforeQuit() {
- DependencyManager::get()->cleanupBeforeQuit();
- // destroy the AudioClient so it and its thread will safely go down
- DependencyManager::destroy();
-}
-
-void RenderingClient::processVerifiedPacket(const HifiSockAddr& senderSockAddr, const QByteArray& incomingPacket) {
- auto nodeList = DependencyManager::get();
- PacketType incomingType = packetTypeForPacket(incomingPacket);
-
- switch (incomingType) {
- case PacketTypeAudioEnvironment:
- case PacketTypeAudioStreamStats:
- case PacketTypeMixedAudio:
- case PacketTypeSilentAudioFrame: {
-
- if (incomingType == PacketTypeAudioStreamStats) {
- QMetaObject::invokeMethod(DependencyManager::get().data(), "parseAudioStreamStatsPacket",
- Qt::QueuedConnection,
- Q_ARG(QByteArray, incomingPacket));
- } else if (incomingType == PacketTypeAudioEnvironment) {
- QMetaObject::invokeMethod(DependencyManager::get().data(), "parseAudioEnvironmentData",
- Qt::QueuedConnection,
- Q_ARG(QByteArray, incomingPacket));
- } else {
- QMetaObject::invokeMethod(DependencyManager::get().data(), "addReceivedAudioToStream",
- Qt::QueuedConnection,
- Q_ARG(QByteArray, incomingPacket));
- }
-
- // update having heard from the audio-mixer and record the bytes received
- SharedNodePointer audioMixer = nodeList->sendingNodeForPacket(incomingPacket);
-
- if (audioMixer) {
- audioMixer->setLastHeardMicrostamp(usecTimestampNow());
- }
-
- break;
- }
- case PacketTypeBulkAvatarData:
- case PacketTypeKillAvatar:
- case PacketTypeAvatarIdentity:
- case PacketTypeAvatarBillboard: {
- // update having heard from the avatar-mixer and record the bytes received
- SharedNodePointer avatarMixer = nodeList->sendingNodeForPacket(incomingPacket);
-
- if (avatarMixer) {
- avatarMixer->setLastHeardMicrostamp(usecTimestampNow());
-
- QMetaObject::invokeMethod(DependencyManager::get().data(),
- "processAvatarMixerDatagram",
- Q_ARG(const QByteArray&, incomingPacket),
- Q_ARG(const QWeakPointer&, avatarMixer));
- }
- break;
- }
- default:
- Client::processVerifiedPacket(senderSockAddr, incomingPacket);
- break;
- }
-}
-
-void RenderingClient::goToLocation(const glm::vec3& newPosition,
- bool hasOrientationChange, const glm::quat& newOrientation,
- bool shouldFaceLocation) {
- qDebug().nospace() << "RenderingClient goToLocation - moving to " << newPosition.x << ", "
- << newPosition.y << ", " << newPosition.z;
-
- glm::vec3 shiftedPosition = newPosition;
-
- if (hasOrientationChange) {
- qDebug().nospace() << "RenderingClient goToLocation - new orientation is "
- << newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z << ", " << newOrientation.w;
-
- // orient the user to face the target
- glm::quat quatOrientation = newOrientation;
-
- if (shouldFaceLocation) {
-
- quatOrientation = newOrientation * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
-
- // move the user a couple units away
- const float DISTANCE_TO_USER = 2.0f;
- shiftedPosition = newPosition - quatOrientation * glm::vec3( 0.0f, 0.0f,-1.0f) * DISTANCE_TO_USER;
- }
-
- _orientation = quatOrientation;
- }
-
- _position = shiftedPosition;
-
-}
diff --git a/gvr-interface/src/RenderingClient.h b/gvr-interface/src/RenderingClient.h
deleted file mode 100644
index c4724bc086..0000000000
--- a/gvr-interface/src/RenderingClient.h
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-// RenderingClient.h
-// gvr-interface/src
-//
-// Created by Stephen Birarda on 1/20/15.
-// 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
-//
-
-
-#ifndef hifi_RenderingClient_h
-#define hifi_RenderingClient_h
-
-#include
-#include
-
-#include
-
-#include
-
-#include "Client.h"
-
-class RenderingClient : public Client {
- Q_OBJECT
-public:
- RenderingClient(QObject* parent = 0, const QString& launchURLString = QString());
-
- const glm::vec3& getPosition() const { return _position; }
- const glm::quat& getOrientation() const { return _orientation; }
- void setOrientation(const glm::quat& orientation) { _orientation = orientation; }
-
- static glm::vec3 getPositionForAudio() { return _instance->getPosition(); }
- static glm::quat getOrientationForAudio() { return _instance->getOrientation(); }
-
- virtual void cleanupBeforeQuit();
-
-private slots:
- void goToLocation(const glm::vec3& newPosition,
- bool hasOrientationChange, const glm::quat& newOrientation,
- bool shouldFaceLocation);
- void sendAvatarPacket();
-
-private:
- virtual void processVerifiedPacket(const HifiSockAddr& senderSockAddr, const QByteArray& incomingPacket);
-
- static RenderingClient* _instance;
-
- glm::vec3 _position;
- glm::quat _orientation;
-
- QTimer _avatarTimer;
- AvatarData _fakeAvatar;
-};
-
-#endif // hifi_RenderingClient_h
diff --git a/gvr-interface/src/java/io/highfidelity/gvrinterface/InterfaceActivity.java b/gvr-interface/src/java/io/highfidelity/gvrinterface/InterfaceActivity.java
deleted file mode 100644
index c7cbdd3dff..0000000000
--- a/gvr-interface/src/java/io/highfidelity/gvrinterface/InterfaceActivity.java
+++ /dev/null
@@ -1,41 +0,0 @@
-//
-// InterfaceActivity.java
-// gvr-interface/java
-//
-// Created by Stephen Birarda on 1/26/15.
-// 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
-//
-
-package io.highfidelity.gvrinterface;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.WindowManager;
-import android.util.Log;
-import org.qtproject.qt5.android.bindings.QtActivity;
-
-public class InterfaceActivity extends QtActivity {
-
- public static native void handleHifiURL(String hifiURLString);
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
- // Get the intent that started this activity in case we have a hifi:// URL to parse
- Intent intent = getIntent();
- if (intent.getAction() == Intent.ACTION_VIEW) {
- Uri data = intent.getData();
-
- if (data.getScheme().equals("hifi")) {
- handleHifiURL(data.toString());
- }
- }
-
- }
-}
\ No newline at end of file
diff --git a/gvr-interface/src/main.cpp b/gvr-interface/src/main.cpp
deleted file mode 100644
index 26576393fb..0000000000
--- a/gvr-interface/src/main.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-//
-// main.cpp
-// gvr-interface/src
-//
-// Created by Stephen Birarda on 11/17/14.
-// Copyright 2014 High Fidelity, Inc.
-//
-// Distributed under the Apache License, Version 2.0.
-// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-#include "GVRMainWindow.h"
-#include "GVRInterface.h"
-
-int main(int argc, char* argv[]) {
- GVRInterface app(argc, argv);
-
- GVRMainWindow mainWindow;
-#ifdef ANDROID
- mainWindow.showFullScreen();
-#else
- mainWindow.showMaximized();
-#endif
-
- app.setMainWindow(&mainWindow);
-
- return app.exec();
-}
\ No newline at end of file
diff --git a/gvr-interface/templates/InterfaceBetaActivity.java.in b/gvr-interface/templates/InterfaceBetaActivity.java.in
deleted file mode 100644
index 6698cfa409..0000000000
--- a/gvr-interface/templates/InterfaceBetaActivity.java.in
+++ /dev/null
@@ -1,51 +0,0 @@
-//
-// InterfaceBetaActivity.java
-// gvr-interface/java
-//
-// Created by Stephen Birarda on 1/27/15.
-// 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
-//
-
-package io.highfidelity.gvrinterface;
-
-import android.os.Bundle;
-import net.hockeyapp.android.CrashManager;
-import net.hockeyapp.android.UpdateManager;
-
-public class InterfaceBetaActivity extends InterfaceActivity {
-
- public String _hockeyAppID;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- _hockeyAppID = getString(R.string.HockeyAppID);
-
- checkForUpdates();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- UpdateManager.unregister();
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- checkForCrashes();
- }
-
- private void checkForCrashes() {
- CrashManager.register(this, _hockeyAppID);
- }
-
- private void checkForUpdates() {
- // Remove this for store / production builds!
- UpdateManager.register(this, _hockeyAppID);
- }
-}
\ No newline at end of file
diff --git a/gvr-interface/templates/hockeyapp.xml.in b/gvr-interface/templates/hockeyapp.xml.in
deleted file mode 100644
index edf2d0a8aa..0000000000
--- a/gvr-interface/templates/hockeyapp.xml.in
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- ${HOCKEY_APP_ID}
- ${HOCKEY_APP_ENABLED}
-
diff --git a/hifi_android.py b/hifi_android.py
index 13c9cdccf2..2e6a42d127 100644
--- a/hifi_android.py
+++ b/hifi_android.py
@@ -52,6 +52,13 @@ ANDROID_PACKAGES = {
'sharedLibFolder': 'VrApi/Libs/Android/arm64-v8a/Release',
'includeLibs': ['libvrapi.so']
},
+ 'oculusPlatform': {
+ 'file': 'OVRPlatformSDK_v1.32.0.zip',
+ 'versionId': 'jG9DB16zOGxSrmtZy4jcQnwO0TJUuaeL',
+ 'checksum': 'ab5b203b3a39a56ab148d68fff769e05',
+ 'sharedLibFolder': 'Android/libs/arm64-v8a',
+ 'includeLibs': ['libovrplatformloader.so']
+ },
'openssl': {
'file': 'openssl-1.1.0g_armv8.tgz',
'versionId': 'AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW',
diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt
index c013cfacd3..81b9935aa5 100644
--- a/interface/CMakeLists.txt
+++ b/interface/CMakeLists.txt
@@ -265,7 +265,7 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
endforeach()
# include headers for interface and InterfaceConfig.
-include_directories("${PROJECT_SOURCE_DIR}/src")
+target_include_directories(${TARGET_NAME} PRIVATE "${PROJECT_SOURCE_DIR}/src")
if (ANDROID)
find_library(ANDROID_LOG_LIB log)
diff --git a/interface/resources/icons/+android/backward.svg b/interface/resources/icons/+android_interface/backward.svg
old mode 100755
new mode 100644
similarity index 100%
rename from interface/resources/icons/+android/backward.svg
rename to interface/resources/icons/+android_interface/backward.svg
diff --git a/interface/resources/icons/+android/bubble-a.svg b/interface/resources/icons/+android_interface/bubble-a.svg
similarity index 100%
rename from interface/resources/icons/+android/bubble-a.svg
rename to interface/resources/icons/+android_interface/bubble-a.svg
diff --git a/interface/resources/icons/+android/bubble-i.svg b/interface/resources/icons/+android_interface/bubble-i.svg
similarity index 100%
rename from interface/resources/icons/+android/bubble-i.svg
rename to interface/resources/icons/+android_interface/bubble-i.svg
diff --git a/interface/resources/icons/+android/button-a.svg b/interface/resources/icons/+android_interface/button-a.svg
similarity index 100%
rename from interface/resources/icons/+android/button-a.svg
rename to interface/resources/icons/+android_interface/button-a.svg
diff --git a/interface/resources/icons/+android/button.svg b/interface/resources/icons/+android_interface/button.svg
similarity index 100%
rename from interface/resources/icons/+android/button.svg
rename to interface/resources/icons/+android_interface/button.svg
diff --git a/interface/resources/icons/+android/forward.svg b/interface/resources/icons/+android_interface/forward.svg
similarity index 100%
rename from interface/resources/icons/+android/forward.svg
rename to interface/resources/icons/+android_interface/forward.svg
diff --git a/interface/resources/icons/+android/go-a.svg b/interface/resources/icons/+android_interface/go-a.svg
old mode 100755
new mode 100644
similarity index 100%
rename from interface/resources/icons/+android/go-a.svg
rename to interface/resources/icons/+android_interface/go-a.svg
diff --git a/interface/resources/icons/+android/go-i.svg b/interface/resources/icons/+android_interface/go-i.svg
similarity index 100%
rename from interface/resources/icons/+android/go-i.svg
rename to interface/resources/icons/+android_interface/go-i.svg
diff --git a/interface/resources/icons/+android/hand.svg b/interface/resources/icons/+android_interface/hand.svg
similarity index 100%
rename from interface/resources/icons/+android/hand.svg
rename to interface/resources/icons/+android_interface/hand.svg
diff --git a/interface/resources/icons/+android/hide.svg b/interface/resources/icons/+android_interface/hide.svg
similarity index 100%
rename from interface/resources/icons/+android/hide.svg
rename to interface/resources/icons/+android_interface/hide.svg
diff --git a/interface/resources/icons/+android/mic-mute-a.svg b/interface/resources/icons/+android_interface/mic-mute-a.svg
similarity index 100%
rename from interface/resources/icons/+android/mic-mute-a.svg
rename to interface/resources/icons/+android_interface/mic-mute-a.svg
diff --git a/interface/resources/icons/+android/mic-mute-i.svg b/interface/resources/icons/+android_interface/mic-mute-i.svg
similarity index 100%
rename from interface/resources/icons/+android/mic-mute-i.svg
rename to interface/resources/icons/+android_interface/mic-mute-i.svg
diff --git a/interface/resources/icons/+android/mic-unmute-a.svg b/interface/resources/icons/+android_interface/mic-unmute-a.svg
old mode 100755
new mode 100644
similarity index 100%
rename from interface/resources/icons/+android/mic-unmute-a.svg
rename to interface/resources/icons/+android_interface/mic-unmute-a.svg
diff --git a/interface/resources/icons/+android/mic-unmute-i.svg b/interface/resources/icons/+android_interface/mic-unmute-i.svg
similarity index 100%
rename from interface/resources/icons/+android/mic-unmute-i.svg
rename to interface/resources/icons/+android_interface/mic-unmute-i.svg
diff --git a/interface/resources/icons/+android/myview-a.svg b/interface/resources/icons/+android_interface/myview-a.svg
old mode 100755
new mode 100644
similarity index 100%
rename from interface/resources/icons/+android/myview-a.svg
rename to interface/resources/icons/+android_interface/myview-a.svg
diff --git a/interface/resources/icons/+android/myview-hover.svg b/interface/resources/icons/+android_interface/myview-hover.svg
old mode 100755
new mode 100644
similarity index 100%
rename from interface/resources/icons/+android/myview-hover.svg
rename to interface/resources/icons/+android_interface/myview-hover.svg
diff --git a/interface/resources/icons/+android/myview-i.svg b/interface/resources/icons/+android_interface/myview-i.svg
old mode 100755
new mode 100644
similarity index 100%
rename from interface/resources/icons/+android/myview-i.svg
rename to interface/resources/icons/+android_interface/myview-i.svg
diff --git a/interface/resources/icons/+android/show-up.svg b/interface/resources/icons/+android_interface/show-up.svg
similarity index 100%
rename from interface/resources/icons/+android/show-up.svg
rename to interface/resources/icons/+android_interface/show-up.svg
diff --git a/interface/resources/icons/+android/stats.svg b/interface/resources/icons/+android_interface/stats.svg
similarity index 100%
rename from interface/resources/icons/+android/stats.svg
rename to interface/resources/icons/+android_interface/stats.svg
diff --git a/interface/resources/icons/+android/tick.svg b/interface/resources/icons/+android_interface/tick.svg
similarity index 100%
rename from interface/resources/icons/+android/tick.svg
rename to interface/resources/icons/+android_interface/tick.svg
diff --git a/interface/resources/qml/+android/StatText.qml b/interface/resources/qml/+android_interface/StatText.qml
similarity index 100%
rename from interface/resources/qml/+android/StatText.qml
rename to interface/resources/qml/+android_interface/StatText.qml
diff --git a/interface/resources/qml/+android/Stats.qml b/interface/resources/qml/+android_interface/Stats.qml
similarity index 100%
rename from interface/resources/qml/+android/Stats.qml
rename to interface/resources/qml/+android_interface/Stats.qml
diff --git a/interface/resources/qml/+android/Web3DSurface.qml b/interface/resources/qml/+android_interface/Web3DSurface.qml
similarity index 100%
rename from interface/resources/qml/+android/Web3DSurface.qml
rename to interface/resources/qml/+android_interface/Web3DSurface.qml
diff --git a/interface/resources/qml/+webengine/Browser.qml b/interface/resources/qml/+webengine/Browser.qml
new file mode 100644
index 0000000000..52157bdf42
--- /dev/null
+++ b/interface/resources/qml/+webengine/Browser.qml
@@ -0,0 +1,275 @@
+import QtQuick 2.5
+import QtWebChannel 1.0
+import QtWebEngine 1.5
+
+import controlsUit 1.0
+import stylesUit 1.0
+import "qrc:////qml//windows"
+
+ScrollingWindow {
+ id: root
+ HifiConstants { id: hifi }
+ //HifiStyles.HifiConstants { id: hifistyles }
+ title: "Browser"
+ resizable: true
+ destroyOnHidden: true
+ width: 800
+ height: 600
+ property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
+ property alias url: webview.url
+ property alias webView: webview
+
+ signal loadingChanged(int status)
+
+ x: 100
+ y: 100
+
+ Component.onCompleted: {
+ focus = true
+ shown = true
+ addressBar.text = webview.url
+ }
+
+ function setProfile(profile) {
+ webview.profile = profile;
+ }
+
+ function showPermissionsBar(){
+ permissionsContainer.visible=true;
+ }
+
+ function hidePermissionsBar(){
+ permissionsContainer.visible=false;
+ }
+
+ function allowPermissions(){
+ webview.grantFeaturePermission(permissionsBar.securityOrigin, permissionsBar.feature, true);
+ hidePermissionsBar();
+ }
+
+ function setAutoAdd(auto) {
+ desktop.setAutoAdd(auto);
+ }
+
+ Item {
+ id:item
+ width: pane.contentWidth
+ implicitHeight: pane.scrollHeight
+
+ Row {
+ id: buttons
+ spacing: 4
+ anchors.top: parent.top
+ anchors.topMargin: 8
+ anchors.left: parent.left
+ anchors.leftMargin: 8
+ HiFiGlyphs {
+ id: back;
+ enabled: webview.canGoBack;
+ text: hifi.glyphs.backward
+ color: enabled ? hifi.colors.text : hifi.colors.disabledText
+ size: 48
+ MouseArea { anchors.fill: parent; onClicked: webview.goBack() }
+ }
+
+ HiFiGlyphs {
+ id: forward;
+ enabled: webview.canGoForward;
+ text: hifi.glyphs.forward
+ color: enabled ? hifi.colors.text : hifi.colors.disabledText
+ size: 48
+ MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
+ }
+
+ HiFiGlyphs {
+ id: reload;
+ enabled: webview.canGoForward;
+ text: webview.loading ? hifi.glyphs.close : hifi.glyphs.reload
+ color: enabled ? hifi.colors.text : hifi.colors.disabledText
+ size: 48
+ MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
+ }
+
+ }
+
+ Item {
+ id: border
+ height: 48
+ anchors.top: parent.top
+ anchors.topMargin: 8
+ anchors.right: parent.right
+ anchors.rightMargin: 8
+ anchors.left: buttons.right
+ anchors.leftMargin: 8
+
+ Item {
+ id: barIcon
+ width: parent.height
+ height: parent.height
+ Image {
+ source: webview.icon;
+ x: (parent.height - height) / 2
+ y: (parent.width - width) / 2
+ sourceSize: Qt.size(width, height);
+ verticalAlignment: Image.AlignVCenter;
+ horizontalAlignment: Image.AlignHCenter
+ }
+ }
+
+ TextField {
+ id: addressBar
+ anchors.right: parent.right
+ anchors.rightMargin: 8
+ anchors.left: barIcon.right
+ anchors.leftMargin: 0
+ anchors.verticalCenter: parent.verticalCenter
+ focus: true
+ colorScheme: hifi.colorSchemes.dark
+ placeholderText: "Enter URL"
+ Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i")
+ Keys.onPressed: {
+ switch(event.key) {
+ case Qt.Key_Enter:
+ case Qt.Key_Return:
+ event.accepted = true
+ if (text.indexOf("http") != 0) {
+ text = "http://" + text;
+ }
+ root.hidePermissionsBar();
+ root.keyboardRaised = false;
+ webview.url = text;
+ break;
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ id:permissionsContainer
+ visible:false
+ color: "#000000"
+ width: parent.width
+ anchors.top: buttons.bottom
+ height:40
+ z:100
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: "black" }
+ GradientStop { position: 1.0; color: "grey" }
+ }
+
+ RalewayLight {
+ id: permissionsInfo
+ anchors.right:permissionsRow.left
+ anchors.rightMargin: 32
+ anchors.topMargin:8
+ anchors.top:parent.top
+ text: "This site wants to use your microphone/camera"
+ size: 18
+ color: hifi.colors.white
+ }
+
+ Row {
+ id: permissionsRow
+ spacing: 4
+ anchors.top:parent.top
+ anchors.topMargin: 8
+ anchors.right: parent.right
+ visible: true
+ z:101
+
+ Button {
+ id:allow
+ text: "Allow"
+ color: hifi.buttons.blue
+ colorScheme: root.colorScheme
+ width: 120
+ enabled: true
+ onClicked: root.allowPermissions();
+ z:101
+ }
+
+ Button {
+ id:block
+ text: "Block"
+ color: hifi.buttons.red
+ colorScheme: root.colorScheme
+ width: 120
+ enabled: true
+ onClicked: root.hidePermissionsBar();
+ z:101
+ }
+ }
+ }
+
+ WebView {
+ id: webview
+ url: "https://highfidelity.com/"
+ profile: FileTypeProfile;
+
+ // Create a global EventBridge object for raiseAndLowerKeyboard.
+ WebEngineScript {
+ id: createGlobalEventBridge
+ sourceCode: eventBridgeJavaScriptToInject
+ injectionPoint: WebEngineScript.Deferred
+ worldId: WebEngineScript.MainWorld
+ }
+
+ // Detect when may want to raise and lower keyboard.
+ WebEngineScript {
+ id: raiseAndLowerKeyboard
+ injectionPoint: WebEngineScript.Deferred
+ sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
+ worldId: WebEngineScript.MainWorld
+ }
+
+ userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
+
+ anchors.top: buttons.bottom
+ anchors.topMargin: 8
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ onFeaturePermissionRequested: {
+ if (feature == 2) { // QWebEnginePage::MediaAudioCapture
+ grantFeaturePermission(securityOrigin, feature, true);
+ } else {
+ permissionsBar.securityOrigin = securityOrigin;
+ permissionsBar.feature = feature;
+ root.showPermissionsBar();
+ }
+ }
+
+ onLoadingChanged: {
+ if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
+ addressBar.text = loadRequest.url
+ }
+ root.loadingChanged(loadRequest.status);
+ }
+
+ onWindowCloseRequested: {
+ root.destroy();
+ }
+
+ Component.onCompleted: {
+ webChannel.registerObject("eventBridge", eventBridge);
+ webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
+ desktop.initWebviewProfileHandlers(webview.profile);
+ }
+ }
+
+ } // item
+
+
+ Keys.onPressed: {
+ switch(event.key) {
+ case Qt.Key_L:
+ if (event.modifiers == Qt.ControlModifier) {
+ event.accepted = true
+ addressBar.selectAll()
+ addressBar.forceActiveFocus()
+ }
+ break;
+ }
+ }
+} // dialog
diff --git a/interface/resources/qml/+webengine/InfoView.qml b/interface/resources/qml/+webengine/InfoView.qml
new file mode 100644
index 0000000000..eb190c3c45
--- /dev/null
+++ b/interface/resources/qml/+webengine/InfoView.qml
@@ -0,0 +1,50 @@
+//
+// InfoView.qml
+//
+// Created by Bradley Austin Davis on 27 Apr 2015
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import QtQuick 2.5
+import Hifi 1.0 as Hifi
+
+import controlsUit 1.0
+import "qrc:////qml//windows" as Windows
+
+Windows.ScrollingWindow {
+ id: root
+ width: 800
+ height: 800
+ resizable: true
+
+ Hifi.InfoView {
+ id: infoView
+ width: pane.contentWidth
+ implicitHeight: pane.scrollHeight
+
+ WebView {
+ id: webview
+ objectName: "WebView"
+ anchors.fill: parent
+ url: infoView.url
+ }
+ }
+
+ Component.onCompleted: {
+ centerWindow(root);
+ }
+
+ onVisibleChanged: {
+ if (visible) {
+ centerWindow(root);
+ }
+ }
+
+ function centerWindow() {
+ desktop.centerOnVisible(root);
+ }
+
+}
diff --git a/interface/resources/qml/+webengine/TabletBrowser.qml b/interface/resources/qml/+webengine/TabletBrowser.qml
new file mode 100644
index 0000000000..720a904231
--- /dev/null
+++ b/interface/resources/qml/+webengine/TabletBrowser.qml
@@ -0,0 +1,125 @@
+import QtQuick 2.5
+import QtWebChannel 1.0
+import QtWebEngine 1.5
+
+import "controls"
+import controlsUit 1.0 as HifiControls
+import "styles" as HifiStyles
+import stylesUit 1.0
+import "windows"
+
+Item {
+ id: root
+ HifiConstants { id: hifi }
+ HifiStyles.HifiConstants { id: hifistyles }
+
+ height: 600
+ property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
+ property alias url: webview.url
+
+ property bool canGoBack: webview.canGoBack
+ property bool canGoForward: webview.canGoForward
+
+
+ signal loadingChanged(int status)
+
+ x: 0
+ y: 0
+
+ function setProfile(profile) {
+ webview.profile = profile;
+ }
+
+ WebEngineView {
+ id: webview
+ objectName: "webEngineView"
+ x: 0
+ y: 0
+ width: parent.width
+ height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height
+
+ profile: HFWebEngineProfile;
+
+ property string userScriptUrl: ""
+
+ // creates a global EventBridge object.
+ WebEngineScript {
+ id: createGlobalEventBridge
+ sourceCode: eventBridgeJavaScriptToInject
+ injectionPoint: WebEngineScript.DocumentCreation
+ worldId: WebEngineScript.MainWorld
+ }
+
+ // detects when to raise and lower virtual keyboard
+ WebEngineScript {
+ id: raiseAndLowerKeyboard
+ injectionPoint: WebEngineScript.Deferred
+ sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
+ worldId: WebEngineScript.MainWorld
+ }
+
+ // User script.
+ WebEngineScript {
+ id: userScript
+ sourceUrl: webview.userScriptUrl
+ injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
+ worldId: WebEngineScript.MainWorld
+ }
+
+ userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
+
+ property string newUrl: ""
+
+ Component.onCompleted: {
+ webChannel.registerObject("eventBridge", eventBridge);
+ webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
+
+ // Ensure the JS from the web-engine makes it to our logging
+ webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
+ console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
+ });
+
+ webview.profile.httpUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36";
+ web.address = url;
+ }
+
+ onFeaturePermissionRequested: {
+ grantFeaturePermission(securityOrigin, feature, true);
+ }
+
+ onLoadingChanged: {
+ keyboardRaised = false;
+ punctuationMode = false;
+ keyboard.resetShiftMode(false);
+
+ // Required to support clicking on "hifi://" links
+ if (WebEngineView.LoadStartedStatus == loadRequest.status) {
+ urlAppend(loadRequest.url.toString())
+ var url = loadRequest.url.toString();
+ if (urlHandler.canHandleUrl(url)) {
+ if (urlHandler.handleUrl(url)) {
+ root.stop();
+ }
+ }
+ }
+ }
+
+ onNewViewRequested: {
+ request.openIn(webView);
+ }
+
+ HifiControls.WebSpinner { }
+ }
+
+ Keys.onPressed: {
+ switch(event.key) {
+ case Qt.Key_L:
+ if (event.modifiers == Qt.ControlModifier) {
+ event.accepted = true
+ addressBar.selectAll()
+ addressBar.forceActiveFocus()
+ }
+ break;
+ }
+ }
+}
diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml
index 4ddee15a10..78ffb51e67 100644
--- a/interface/resources/qml/Browser.qml
+++ b/interface/resources/qml/Browser.qml
@@ -1,16 +1,14 @@
import QtQuick 2.5
-import QtWebChannel 1.0
-import QtWebEngine 1.5
import controlsUit 1.0
-import "styles" as HifiStyles
import stylesUit 1.0
+
import "windows"
ScrollingWindow {
id: root
HifiConstants { id: hifi }
- HifiStyles.HifiConstants { id: hifistyles }
+ //HifiStyles.HifiConstants { id: hifistyles }
title: "Browser"
resizable: true
destroyOnHidden: true
@@ -32,7 +30,6 @@ ScrollingWindow {
}
function setProfile(profile) {
- webview.profile = profile;
}
function showPermissionsBar(){
@@ -44,7 +41,6 @@ ScrollingWindow {
}
function allowPermissions(){
- webview.grantFeaturePermission(permissionsBar.securityOrigin, permissionsBar.feature, true);
hidePermissionsBar();
}
@@ -68,7 +64,7 @@ ScrollingWindow {
id: back;
enabled: webview.canGoBack;
text: hifi.glyphs.backward
- color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText
+ color: enabled ? hifi.colors.text : hifi.colors.disabledText
size: 48
MouseArea { anchors.fill: parent; onClicked: webview.goBack() }
}
@@ -77,7 +73,7 @@ ScrollingWindow {
id: forward;
enabled: webview.canGoForward;
text: hifi.glyphs.forward
- color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText
+ color: enabled ? hifi.colors.text : hifi.colors.disabledText
size: 48
MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
}
@@ -86,7 +82,7 @@ ScrollingWindow {
id: reload;
enabled: webview.canGoForward;
text: webview.loading ? hifi.glyphs.close : hifi.glyphs.reload
- color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText
+ color: enabled ? hifi.colors.text : hifi.colors.disabledText
size: 48
MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
}
@@ -202,61 +198,10 @@ ScrollingWindow {
}
}
- WebView {
+ ProxyWebView {
id: webview
+ anchors.centerIn: parent
url: "https://highfidelity.com/"
- profile: FileTypeProfile;
-
- // Create a global EventBridge object for raiseAndLowerKeyboard.
- WebEngineScript {
- id: createGlobalEventBridge
- sourceCode: eventBridgeJavaScriptToInject
- injectionPoint: WebEngineScript.Deferred
- worldId: WebEngineScript.MainWorld
- }
-
- // Detect when may want to raise and lower keyboard.
- WebEngineScript {
- id: raiseAndLowerKeyboard
- injectionPoint: WebEngineScript.Deferred
- sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
- worldId: WebEngineScript.MainWorld
- }
-
- userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
-
- anchors.top: buttons.bottom
- anchors.topMargin: 8
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- anchors.right: parent.right
-
- onFeaturePermissionRequested: {
- if (feature == 2) { // QWebEnginePage::MediaAudioCapture
- grantFeaturePermission(securityOrigin, feature, true);
- } else {
- permissionsBar.securityOrigin = securityOrigin;
- permissionsBar.feature = feature;
- root.showPermissionsBar();
- }
- }
-
- onLoadingChanged: {
- if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
- addressBar.text = loadRequest.url
- }
- root.loadingChanged(loadRequest.status);
- }
-
- onWindowCloseRequested: {
- root.destroy();
- }
-
- Component.onCompleted: {
- webChannel.registerObject("eventBridge", eventBridge);
- webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
- desktop.initWebviewProfileHandlers(webview.profile);
- }
}
} // item
diff --git a/interface/resources/qml/InfoView.qml b/interface/resources/qml/InfoView.qml
index 8c5900b4c3..5c2c7fcff9 100644
--- a/interface/resources/qml/InfoView.qml
+++ b/interface/resources/qml/InfoView.qml
@@ -19,13 +19,12 @@ Windows.ScrollingWindow {
width: 800
height: 800
resizable: true
-
Hifi.InfoView {
id: infoView
width: pane.contentWidth
implicitHeight: pane.scrollHeight
- WebView {
+ ProxyWebView {
id: webview
objectName: "WebView"
anchors.fill: parent
diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml
similarity index 100%
rename from interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml
rename to interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml
diff --git a/interface/resources/qml/LoginDialog/+android/SignUpBody.qml b/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml
similarity index 100%
rename from interface/resources/qml/LoginDialog/+android/SignUpBody.qml
rename to interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml
diff --git a/interface/resources/qml/TabletBrowser.qml b/interface/resources/qml/TabletBrowser.qml
index 720a904231..f83a9c81a5 100644
--- a/interface/resources/qml/TabletBrowser.qml
+++ b/interface/resources/qml/TabletBrowser.qml
@@ -1,17 +1,11 @@
import QtQuick 2.5
-import QtWebChannel 1.0
-import QtWebEngine 1.5
-import "controls"
import controlsUit 1.0 as HifiControls
-import "styles" as HifiStyles
import stylesUit 1.0
-import "windows"
Item {
id: root
HifiConstants { id: hifi }
- HifiStyles.HifiConstants { id: hifistyles }
height: 600
property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
@@ -30,96 +24,9 @@ Item {
webview.profile = profile;
}
- WebEngineView {
+ HifiControls.ProxyWebView {
id: webview
- objectName: "webEngineView"
- x: 0
- y: 0
width: parent.width
- height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height
-
- profile: HFWebEngineProfile;
-
- property string userScriptUrl: ""
-
- // creates a global EventBridge object.
- WebEngineScript {
- id: createGlobalEventBridge
- sourceCode: eventBridgeJavaScriptToInject
- injectionPoint: WebEngineScript.DocumentCreation
- worldId: WebEngineScript.MainWorld
- }
-
- // detects when to raise and lower virtual keyboard
- WebEngineScript {
- id: raiseAndLowerKeyboard
- injectionPoint: WebEngineScript.Deferred
- sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
- worldId: WebEngineScript.MainWorld
- }
-
- // User script.
- WebEngineScript {
- id: userScript
- sourceUrl: webview.userScriptUrl
- injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
- worldId: WebEngineScript.MainWorld
- }
-
- userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
-
- property string newUrl: ""
-
- Component.onCompleted: {
- webChannel.registerObject("eventBridge", eventBridge);
- webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
-
- // Ensure the JS from the web-engine makes it to our logging
- webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
- console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
- });
-
- webview.profile.httpUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36";
- web.address = url;
- }
-
- onFeaturePermissionRequested: {
- grantFeaturePermission(securityOrigin, feature, true);
- }
-
- onLoadingChanged: {
- keyboardRaised = false;
- punctuationMode = false;
- keyboard.resetShiftMode(false);
-
- // Required to support clicking on "hifi://" links
- if (WebEngineView.LoadStartedStatus == loadRequest.status) {
- urlAppend(loadRequest.url.toString())
- var url = loadRequest.url.toString();
- if (urlHandler.canHandleUrl(url)) {
- if (urlHandler.handleUrl(url)) {
- root.stop();
- }
- }
- }
- }
-
- onNewViewRequested: {
- request.openIn(webView);
- }
-
- HifiControls.WebSpinner { }
- }
-
- Keys.onPressed: {
- switch(event.key) {
- case Qt.Key_L:
- if (event.modifiers == Qt.ControlModifier) {
- event.accepted = true
- addressBar.selectAll()
- addressBar.forceActiveFocus()
- }
- break;
- }
+ height: parent.height
}
}
diff --git a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml
new file mode 100644
index 0000000000..823d0107a2
--- /dev/null
+++ b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml
@@ -0,0 +1,189 @@
+import QtQuick 2.7
+import QtWebEngine 1.5
+import QtWebChannel 1.0
+
+import QtQuick.Controls 2.2
+
+import stylesUit 1.0 as StylesUIt
+
+Item {
+ id: flick
+
+ property alias url: webViewCore.url
+ property alias canGoBack: webViewCore.canGoBack
+ property alias webViewCore: webViewCore
+ property alias webViewCoreProfile: webViewCore.profile
+ property string webViewCoreUserAgent
+
+ property string userScriptUrl: ""
+ property string urlTag: "noDownload=false";
+
+ signal newViewRequestedCallback(var request)
+ signal loadingChangedCallback(var loadRequest)
+
+
+ width: parent.width
+
+ property bool interactive: false
+
+ property bool blurOnCtrlShift: true
+
+ StylesUIt.HifiConstants {
+ id: hifi
+ }
+
+ function stop() {
+ webViewCore.stop();
+ }
+
+ Timer {
+ id: delayedUnfocuser
+ repeat: false
+ interval: 200
+ onTriggered: {
+
+ // The idea behind this is to delay unfocusing, so that fast lower/raise will not result actual unfocusing.
+ // Fast lower/raise happens every time keyboard is being re-raised (see the code below in OffscreenQmlSurface::setKeyboardRaised)
+ //
+ // if (raised) {
+ // item->setProperty("keyboardRaised", QVariant(!raised));
+ // }
+ //
+ // item->setProperty("keyboardRaised", QVariant(raised));
+ //
+
+ webViewCore.runJavaScript("if (document.activeElement) document.activeElement.blur();", function(result) {
+ console.log('unfocus completed: ', result);
+ });
+ }
+ }
+
+ function unfocus() {
+ delayedUnfocuser.start();
+ }
+
+ function stopUnfocus() {
+ delayedUnfocuser.stop();
+ }
+
+ function onLoadingChanged(loadRequest) {
+ if (WebEngineView.LoadStartedStatus === loadRequest.status) {
+
+ // Required to support clicking on "hifi://" links
+ var url = loadRequest.url.toString();
+ url = (url.indexOf("?") >= 0) ? url + urlTag : url + "?" + urlTag;
+ if (urlHandler.canHandleUrl(url)) {
+ if (urlHandler.handleUrl(url)) {
+ webViewCore.stop();
+ }
+ }
+ }
+
+ if (WebEngineView.LoadFailedStatus === loadRequest.status) {
+ console.log("Tablet WebEngineView failed to load url: " + loadRequest.url.toString());
+ }
+
+ if (WebEngineView.LoadSucceededStatus === loadRequest.status) {
+ //disable Chromium's scroll bars
+ }
+ }
+
+ WebEngineView {
+ id: webViewCore
+
+ width: parent.width
+ height: parent.height
+
+ profile: HFWebEngineProfile;
+ settings.pluginsEnabled: true
+ settings.touchIconsEnabled: true
+ settings.allowRunningInsecureContent: true
+
+ // creates a global EventBridge object.
+ WebEngineScript {
+ id: createGlobalEventBridge
+ sourceCode: eventBridgeJavaScriptToInject
+ injectionPoint: WebEngineScript.DocumentCreation
+ worldId: WebEngineScript.MainWorld
+ }
+
+ // detects when to raise and lower virtual keyboard
+ WebEngineScript {
+ id: raiseAndLowerKeyboard
+ injectionPoint: WebEngineScript.Deferred
+ sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
+ worldId: WebEngineScript.MainWorld
+ }
+
+ // User script.
+ WebEngineScript {
+ id: userScript
+ sourceUrl: flick.userScriptUrl
+ injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
+ worldId: WebEngineScript.MainWorld
+ }
+
+ userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
+
+ Component.onCompleted: {
+ webChannel.registerObject("eventBridge", eventBridge);
+ webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
+
+ if (webViewCoreUserAgent !== undefined) {
+ webViewCore.profile.httpUserAgent = webViewCoreUserAgent
+ } else {
+ webViewCore.profile.httpUserAgent += " (HighFidelityInterface)";
+ }
+ // Ensure the JS from the web-engine makes it to our logging
+ webViewCore.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
+ console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
+ });
+ }
+
+ onFeaturePermissionRequested: {
+ grantFeaturePermission(securityOrigin, feature, true);
+ }
+
+ //disable popup
+ onContextMenuRequested: {
+ request.accepted = true;
+ }
+
+ onNewViewRequested: {
+ newViewRequestedCallback(request)
+ }
+
+ // Prior to 5.10, the WebEngineView loading property is true during initial page loading and then stays false
+ // as in-page javascript adds more html content. However, in 5.10 there is a bug such that adding html turns
+ // loading true, and never turns it false again. safeLoading provides a workaround, but it should be removed
+ // when QT fixes this.
+ property bool safeLoading: false
+ property bool loadingLatched: false
+ property var loadingRequest: null
+ onLoadingChanged: {
+ webViewCore.loadingRequest = loadRequest;
+ webViewCore.safeLoading = webViewCore.loading && !loadingLatched;
+ webViewCore.loadingLatched |= webViewCore.loading;
+ }
+ onSafeLoadingChanged: {
+ flick.onLoadingChanged(webViewCore.loadingRequest)
+ loadingChangedCallback(webViewCore.loadingRequest)
+ }
+ }
+
+ AnimatedImage {
+ //anchoring doesnt works here when changing content size
+ x: flick.width/2 - width/2
+ y: flick.height/2 - height/2
+ source: "../../icons/loader-snake-64-w.gif"
+ visible: webViewCore.safeLoading && /^(http.*|)$/i.test(webViewCore.url.toString())
+ playing: visible
+ z: 10000
+ }
+
+ Keys.onPressed: {
+ if (blurOnCtrlShift && (event.modifiers & Qt.ShiftModifier) && (event.modifiers & Qt.ControlModifier)) {
+ webViewCore.focus = false;
+ }
+ }
+}
diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml
index 823d0107a2..a844c8b624 100644
--- a/interface/resources/qml/controls/FlickableWebViewCore.qml
+++ b/interface/resources/qml/controls/FlickableWebViewCore.qml
@@ -1,10 +1,9 @@
import QtQuick 2.7
-import QtWebEngine 1.5
-import QtWebChannel 1.0
import QtQuick.Controls 2.2
import stylesUit 1.0 as StylesUIt
+import controlsUit 1.0 as ControlsUit
Item {
id: flick
@@ -33,157 +32,21 @@ Item {
}
function stop() {
- webViewCore.stop();
- }
- Timer {
- id: delayedUnfocuser
- repeat: false
- interval: 200
- onTriggered: {
-
- // The idea behind this is to delay unfocusing, so that fast lower/raise will not result actual unfocusing.
- // Fast lower/raise happens every time keyboard is being re-raised (see the code below in OffscreenQmlSurface::setKeyboardRaised)
- //
- // if (raised) {
- // item->setProperty("keyboardRaised", QVariant(!raised));
- // }
- //
- // item->setProperty("keyboardRaised", QVariant(raised));
- //
-
- webViewCore.runJavaScript("if (document.activeElement) document.activeElement.blur();", function(result) {
- console.log('unfocus completed: ', result);
- });
- }
}
function unfocus() {
- delayedUnfocuser.start();
}
function stopUnfocus() {
- delayedUnfocuser.stop();
}
function onLoadingChanged(loadRequest) {
- if (WebEngineView.LoadStartedStatus === loadRequest.status) {
-
- // Required to support clicking on "hifi://" links
- var url = loadRequest.url.toString();
- url = (url.indexOf("?") >= 0) ? url + urlTag : url + "?" + urlTag;
- if (urlHandler.canHandleUrl(url)) {
- if (urlHandler.handleUrl(url)) {
- webViewCore.stop();
- }
- }
- }
-
- if (WebEngineView.LoadFailedStatus === loadRequest.status) {
- console.log("Tablet WebEngineView failed to load url: " + loadRequest.url.toString());
- }
-
- if (WebEngineView.LoadSucceededStatus === loadRequest.status) {
- //disable Chromium's scroll bars
- }
}
- WebEngineView {
+ ControlsUit.ProxyWebView {
id: webViewCore
-
width: parent.width
height: parent.height
-
- profile: HFWebEngineProfile;
- settings.pluginsEnabled: true
- settings.touchIconsEnabled: true
- settings.allowRunningInsecureContent: true
-
- // creates a global EventBridge object.
- WebEngineScript {
- id: createGlobalEventBridge
- sourceCode: eventBridgeJavaScriptToInject
- injectionPoint: WebEngineScript.DocumentCreation
- worldId: WebEngineScript.MainWorld
- }
-
- // detects when to raise and lower virtual keyboard
- WebEngineScript {
- id: raiseAndLowerKeyboard
- injectionPoint: WebEngineScript.Deferred
- sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
- worldId: WebEngineScript.MainWorld
- }
-
- // User script.
- WebEngineScript {
- id: userScript
- sourceUrl: flick.userScriptUrl
- injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
- worldId: WebEngineScript.MainWorld
- }
-
- userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
-
- Component.onCompleted: {
- webChannel.registerObject("eventBridge", eventBridge);
- webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
-
- if (webViewCoreUserAgent !== undefined) {
- webViewCore.profile.httpUserAgent = webViewCoreUserAgent
- } else {
- webViewCore.profile.httpUserAgent += " (HighFidelityInterface)";
- }
- // Ensure the JS from the web-engine makes it to our logging
- webViewCore.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
- console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
- });
- }
-
- onFeaturePermissionRequested: {
- grantFeaturePermission(securityOrigin, feature, true);
- }
-
- //disable popup
- onContextMenuRequested: {
- request.accepted = true;
- }
-
- onNewViewRequested: {
- newViewRequestedCallback(request)
- }
-
- // Prior to 5.10, the WebEngineView loading property is true during initial page loading and then stays false
- // as in-page javascript adds more html content. However, in 5.10 there is a bug such that adding html turns
- // loading true, and never turns it false again. safeLoading provides a workaround, but it should be removed
- // when QT fixes this.
- property bool safeLoading: false
- property bool loadingLatched: false
- property var loadingRequest: null
- onLoadingChanged: {
- webViewCore.loadingRequest = loadRequest;
- webViewCore.safeLoading = webViewCore.loading && !loadingLatched;
- webViewCore.loadingLatched |= webViewCore.loading;
- }
- onSafeLoadingChanged: {
- flick.onLoadingChanged(webViewCore.loadingRequest)
- loadingChangedCallback(webViewCore.loadingRequest)
- }
- }
-
- AnimatedImage {
- //anchoring doesnt works here when changing content size
- x: flick.width/2 - width/2
- y: flick.height/2 - height/2
- source: "../../icons/loader-snake-64-w.gif"
- visible: webViewCore.safeLoading && /^(http.*|)$/i.test(webViewCore.url.toString())
- playing: visible
- z: 10000
- }
-
- Keys.onPressed: {
- if (blurOnCtrlShift && (event.modifiers & Qt.ShiftModifier) && (event.modifiers & Qt.ControlModifier)) {
- webViewCore.focus = false;
- }
}
}
diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml
index 3959dbf01b..9cbbd48a22 100644
--- a/interface/resources/qml/controls/TabletWebView.qml
+++ b/interface/resources/qml/controls/TabletWebView.qml
@@ -1,5 +1,4 @@
import QtQuick 2.7
-import QtWebEngine 1.5
import controlsUit 1.0 as HiFiControls
import "../styles" as HifiStyles
import stylesUit 1.0
diff --git a/interface/resources/qml/controlsUit/+android/ImageButton.qml b/interface/resources/qml/controlsUit/+android_interface/ImageButton.qml
similarity index 100%
rename from interface/resources/qml/controlsUit/+android/ImageButton.qml
rename to interface/resources/qml/controlsUit/+android_interface/ImageButton.qml
diff --git a/interface/resources/qml/controlsUit/+webengine/BaseWebView.qml b/interface/resources/qml/controlsUit/+webengine/BaseWebView.qml
new file mode 100644
index 0000000000..fdd9c12220
--- /dev/null
+++ b/interface/resources/qml/controlsUit/+webengine/BaseWebView.qml
@@ -0,0 +1,38 @@
+//
+// WebView.qml
+//
+// Created by Bradley Austin Davis on 12 Jan 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 QtQuick 2.7
+import QtWebEngine 1.5
+
+WebEngineView {
+ id: root
+
+ Component.onCompleted: {
+ console.log("Connecting JS messaging to Hifi Logging")
+ // Ensure the JS from the web-engine makes it to our logging
+ root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
+ console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message);
+ });
+ }
+
+ onLoadingChanged: {
+ // Required to support clicking on "hifi://" links
+ if (WebEngineView.LoadStartedStatus == loadRequest.status) {
+ var url = loadRequest.url.toString();
+ if (urlHandler.canHandleUrl(url)) {
+ if (urlHandler.handleUrl(url)) {
+ root.stop();
+ }
+ }
+ }
+ }
+
+ WebSpinner { }
+}
diff --git a/interface/resources/qml/controlsUit/BaseWebView.qml b/interface/resources/qml/controlsUit/BaseWebView.qml
index fdd9c12220..52b2d1f3db 100644
--- a/interface/resources/qml/controlsUit/BaseWebView.qml
+++ b/interface/resources/qml/controlsUit/BaseWebView.qml
@@ -8,31 +8,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
-import QtQuick 2.7
-import QtWebEngine 1.5
+import "."
-WebEngineView {
+ProxyWebView {
id: root
-
- Component.onCompleted: {
- console.log("Connecting JS messaging to Hifi Logging")
- // Ensure the JS from the web-engine makes it to our logging
- root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
- console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message);
- });
- }
-
- onLoadingChanged: {
- // Required to support clicking on "hifi://" links
- if (WebEngineView.LoadStartedStatus == loadRequest.status) {
- var url = loadRequest.url.toString();
- if (urlHandler.canHandleUrl(url)) {
- if (urlHandler.handleUrl(url)) {
- root.stop();
- }
- }
- }
- }
-
- WebSpinner { }
}
diff --git a/interface/resources/qml/controlsUit/ProxyWebView.qml b/interface/resources/qml/controlsUit/ProxyWebView.qml
new file mode 100644
index 0000000000..adcc472831
--- /dev/null
+++ b/interface/resources/qml/controlsUit/ProxyWebView.qml
@@ -0,0 +1,30 @@
+import QtQuick 2.7
+import stylesUit 1.0
+
+Rectangle {
+ HifiConstants {
+ id: hifi
+ }
+
+ color: hifi.colors.darkGray
+
+ signal onNewViewRequested();
+
+ property string url: "";
+ property bool canGoBack: false
+ property bool canGoForward: false
+ property string icon: ""
+ property var profile: {}
+
+ property bool safeLoading: false
+ property bool loadingLatched: false
+ property var loadingRequest: null
+
+
+ Text {
+ anchors.centerIn: parent
+ text: "This feature is not supported"
+ font.pixelSize: 32
+ color: hifi.colors.white
+ }
+}
diff --git a/interface/resources/qml/controlsUit/qmldir b/interface/resources/qml/controlsUit/qmldir
index d0577f5575..e1665df40e 100644
--- a/interface/resources/qml/controlsUit/qmldir
+++ b/interface/resources/qml/controlsUit/qmldir
@@ -29,6 +29,7 @@ TextEdit 1.0 TextEdit.qml
TextField 1.0 TextField.qml
ToolTip 1.0 ToolTip.qml
Tree 1.0 Tree.qml
+ProxyWebView 1.0 ProxyWebView.qml
VerticalSpacer 1.0 VerticalSpacer.qml
WebGlyphButton 1.0 WebGlyphButton.qml
WebSpinner 1.0 WebSpinner.qml
diff --git a/interface/resources/qml/desktop/+android/FocusHack.qml b/interface/resources/qml/desktop/+android_interface/FocusHack.qml
similarity index 100%
rename from interface/resources/qml/desktop/+android/FocusHack.qml
rename to interface/resources/qml/desktop/+android_interface/FocusHack.qml
diff --git a/interface/resources/qml/hifi/+android/ActionBar.qml b/interface/resources/qml/hifi/+android_interface/ActionBar.qml
similarity index 100%
rename from interface/resources/qml/hifi/+android/ActionBar.qml
rename to interface/resources/qml/hifi/+android_interface/ActionBar.qml
diff --git a/interface/resources/qml/hifi/+android/AudioBar.qml b/interface/resources/qml/hifi/+android_interface/AudioBar.qml
similarity index 100%
rename from interface/resources/qml/hifi/+android/AudioBar.qml
rename to interface/resources/qml/hifi/+android_interface/AudioBar.qml
diff --git a/interface/resources/qml/hifi/+android/AvatarOption.qml b/interface/resources/qml/hifi/+android_interface/AvatarOption.qml
similarity index 100%
rename from interface/resources/qml/hifi/+android/AvatarOption.qml
rename to interface/resources/qml/hifi/+android_interface/AvatarOption.qml
diff --git a/interface/resources/qml/hifi/+android/Desktop.qml b/interface/resources/qml/hifi/+android_interface/Desktop.qml
similarity index 100%
rename from interface/resources/qml/hifi/+android/Desktop.qml
rename to interface/resources/qml/hifi/+android_interface/Desktop.qml
diff --git a/interface/resources/qml/hifi/+android/HifiConstants.qml b/interface/resources/qml/hifi/+android_interface/HifiConstants.qml
similarity index 100%
rename from interface/resources/qml/hifi/+android/HifiConstants.qml
rename to interface/resources/qml/hifi/+android_interface/HifiConstants.qml
diff --git a/interface/resources/qml/hifi/+android/StatsBar.qml b/interface/resources/qml/hifi/+android_interface/StatsBar.qml
similarity index 100%
rename from interface/resources/qml/hifi/+android/StatsBar.qml
rename to interface/resources/qml/hifi/+android_interface/StatsBar.qml
diff --git a/interface/resources/qml/hifi/+android/WindowHeader.qml b/interface/resources/qml/hifi/+android_interface/WindowHeader.qml
similarity index 100%
rename from interface/resources/qml/hifi/+android/WindowHeader.qml
rename to interface/resources/qml/hifi/+android_interface/WindowHeader.qml
diff --git a/interface/resources/qml/hifi/+android/bottomHudOptions.qml b/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml
similarity index 100%
rename from interface/resources/qml/hifi/+android/bottomHudOptions.qml
rename to interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml
diff --git a/interface/resources/qml/hifi/+android/button.qml b/interface/resources/qml/hifi/+android_interface/button.qml
similarity index 100%
rename from interface/resources/qml/hifi/+android/button.qml
rename to interface/resources/qml/hifi/+android_interface/button.qml
diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android_interface/modesbar.qml
similarity index 100%
rename from interface/resources/qml/hifi/+android/modesbar.qml
rename to interface/resources/qml/hifi/+android_interface/modesbar.qml
diff --git a/interface/resources/qml/hifi/avatarapp/+android/TransparencyMask.qml b/interface/resources/qml/hifi/avatarapp/+android_interface/TransparencyMask.qml
similarity index 100%
rename from interface/resources/qml/hifi/avatarapp/+android/TransparencyMask.qml
rename to interface/resources/qml/hifi/avatarapp/+android_interface/TransparencyMask.qml
diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml
index c76f5a428a..2d5f77f006 100644
--- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml
+++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml
@@ -662,7 +662,7 @@ Rectangle {
anchors.right: parent.right;
text: "Cancel"
onClicked: {
- sendToScript({method: 'checkout_cancelClicked', params: itemId});
+ sendToScript({method: 'checkout_cancelClicked', itemId: itemId});
}
}
}
diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml
index 0d0af875d1..759d61b924 100644
--- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml
+++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml
@@ -24,11 +24,8 @@ Item {
HifiConstants { id: hifi; }
id: root;
- property string referrerURL: (Account.metaverseServerURL + "/marketplace?");
- readonly property int additionalDropdownHeight: usernameDropdown.height - myUsernameButton.anchors.bottomMargin;
- property alias usernameDropdownVisible: usernameDropdown.visible;
- height: mainContainer.height + additionalDropdownHeight;
+ height: mainContainer.height;
Connections {
target: Commerce;
@@ -93,77 +90,7 @@ Item {
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;
-
- TextMetrics {
- id: textMetrics;
- font.family: "Raleway"
- 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;
+ sendToParent({method: "header_marketplaceImageClicked"});
}
}
}
@@ -205,92 +132,6 @@ Item {
}
}
- Item {
- id: usernameDropdown;
- z: 998;
- visible: false;
- anchors.top: buttonAndUsernameContainer.bottom;
- anchors.topMargin: -buttonAndUsernameContainer.anchors.bottomMargin;
- anchors.right: buttonAndUsernameContainer.right;
- height: childrenRect.height;
- width: 150;
-
- 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 Submissions"
- 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;
- }
}
diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml
index bc8816e0ea..68d437a346 100644
--- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml
+++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml
@@ -21,11 +21,10 @@ import "../../../../controls" as HifiControls
import "../" as HifiCommerceCommon
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
-Rectangle {
+Item {
HifiConstants { id: hifi; }
id: root;
- color: hifi.colors.baseGray
property int parentAppTitleBarHeight;
property int parentAppNavBarHeight;
property string currentActiveView: "sendAssetHome";
diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml
index 232e17d851..8ca34af28a 100644
--- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml
+++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml
@@ -22,7 +22,6 @@ Rectangle {
HifiConstants { id: hifi; }
id: root;
- property string marketplaceUrl: "";
property string entityId: "";
property string certificateId: "";
property string itemName: "--";
@@ -30,6 +29,7 @@ Rectangle {
property string itemEdition: "--";
property string dateAcquired: "--";
property string itemCost: "--";
+ property string marketplace_item_id: "";
property string certTitleTextColor: hifi.colors.darkGray;
property string certTextColor: hifi.colors.white;
property string infoTextColor: hifi.colors.blueAccent;
@@ -69,7 +69,7 @@ Rectangle {
errorText.text = "Information about this certificate is currently unavailable. Please try again later.";
}
} else {
- root.marketplaceUrl = result.data.marketplace_item_url;
+ root.marketplace_item_id = result.data.marketplace_item_id;
root.isMyCert = result.isMyCert ? result.isMyCert : false;
if (root.certInfoReplaceMode > 3) {
@@ -352,7 +352,7 @@ Rectangle {
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
- sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl});
+ sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', itemId: root.marketplace_item_id});
}
onEntered: itemName.color = hifi.colors.blueHighlight;
onExited: itemName.color = root.certTextColor;
@@ -391,7 +391,7 @@ Rectangle {
// "Show In Marketplace" button
HifiControlsUit.Button {
id: showInMarketplaceButton;
- enabled: root.marketplaceUrl;
+ enabled: root.marketplace_item_id && marketplace_item_id !== "";
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.light;
anchors.bottom: parent.bottom;
@@ -401,7 +401,7 @@ Rectangle {
height: 40;
text: "View In Market"
onClicked: {
- sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl});
+ sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', itemId: root.marketplace_item_id});
}
}
}
@@ -620,7 +620,7 @@ Rectangle {
root.itemOwner = "--";
root.itemEdition = "--";
root.dateAcquired = "--";
- root.marketplaceUrl = "";
+ root.marketplace_item_id = "";
root.itemCost = "--";
root.isMyCert = false;
errorText.text = "";
diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml
new file mode 100644
index 0000000000..351c43286c
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml
@@ -0,0 +1,1216 @@
+//
+// Marketplace.qml
+// qml/hifi/commerce/marketplace
+//
+// Marketplace
+//
+// Created by Roxanne Skelly on 2019-01-18
+// Copyright 2019 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtGraphicalEffects 1.0
+import stylesUit 1.0
+import controlsUit 1.0 as HifiControlsUit
+import "../../../controls" as HifiControls
+import "../common" as HifiCommerceCommon
+import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
+import "../common/sendAsset"
+import "../.." as HifiCommon
+
+Rectangle {
+ HifiConstants {
+ id: hifi
+ }
+
+ id: root
+
+ property string activeView: "initialize"
+ property int currentSortIndex: 0
+ property string sortString: "recent"
+ property string categoryString: ""
+ property string searchString: ""
+ property bool keyboardEnabled: HMD.active
+ property bool keyboardRaised: false
+ property string searchScopeString: "Featured"
+ property bool isLoggedIn: false;
+ property bool supports3DHTML: true;
+
+ anchors.fill: (typeof parent === undefined) ? undefined : parent
+
+ function getMarketplaceItems() {
+ marketplaceItemView.visible = false;
+ itemsList.visible = true;
+ licenseInfo.visible = false;
+ marketBrowseModel.getFirstPage();
+ {
+ if(root.searchString !== undefined && root.searchString !== "") {
+ root.searchScopeString = "Search Results: \"" + root.searchString + "\"";
+ } else if (root.categoryString !== "") {
+ root.searchScopeString = root.categoryString;
+ } else {
+ root.searchScopeString = "Featured";
+ }
+ }
+ }
+
+ Component.onCompleted: {
+ Commerce.getLoginStatus();
+
+ supports3DHTML = PlatformInfo.has3DHTML();
+ }
+
+ Component.onDestruction: {
+ keyboard.raised = false;
+ }
+
+ Connections {
+ target: GlobalServices
+
+ onMyUsernameChanged: {
+ Commerce.getLoginStatus();
+ }
+ }
+
+ Connections {
+ target: MarketplaceScriptingInterface
+
+ onGetMarketplaceCategoriesResult: {
+ if (result.status !== 'success') {
+ console.log("Failed to get Marketplace Categories", result.data.message);
+ } else {
+ categoriesModel.clear();
+ categoriesModel.append({
+ id: -1,
+ name: "Everything"
+ });
+ result.data.items.forEach(function(category) {
+ categoriesModel.append({
+ id: category.id,
+ name: category.name
+ });
+ });
+ }
+ getMarketplaceItems();
+ }
+ onGetMarketplaceItemsResult: {
+ marketBrowseModel.handlePage(result.status !== "success" && result.message, result);
+ }
+
+ onGetMarketplaceItemResult: {
+ if (result.status !== 'success') {
+ console.log("Failed to get Marketplace Item", result.data.message);
+ } else {
+ marketplaceItem.supports3DHTML = root.supports3DHTML;
+ marketplaceItem.item_id = result.data.id;
+ marketplaceItem.image_url = result.data.thumbnail_url;
+ marketplaceItem.name = result.data.title;
+ marketplaceItem.likes = result.data.likes;
+ if(result.data.has_liked !== undefined) {
+ marketplaceItem.liked = result.data.has_liked;
+ }
+ marketplaceItem.creator = result.data.creator;
+ marketplaceItem.categories = result.data.categories;
+ marketplaceItem.price = result.data.cost;
+ marketplaceItem.description = result.data.description;
+ marketplaceItem.attributions = result.data.attributions;
+ marketplaceItem.license = result.data.license;
+ marketplaceItem.available = result.data.availability === "available";
+ marketplaceItem.created_at = result.data.created_at;
+ marketplaceItemScrollView.contentHeight = marketplaceItemContent.height;
+ itemsList.visible = false;
+ marketplaceItemView.visible = true;
+ }
+ }
+ }
+
+ Connections {
+ target: Commerce
+
+ onLoginStatusResult: {
+ root.isLoggedIn = isLoggedIn;
+ }
+ }
+
+ HifiCommerceCommon.CommerceLightbox {
+ id: lightboxPopup
+ visible: false
+ anchors.fill: parent
+ }
+
+ //
+ // HEADER BAR START
+ //
+
+ Rectangle {
+ id: headerShadowBase
+ anchors.fill: header
+ color: "white"
+ }
+ DropShadow {
+ anchors.fill: headerShadowBase
+ source: headerShadowBase
+ verticalOffset: 4
+ horizontalOffset: 4
+ radius: 6
+ samples: 9
+ color: hifi.colors.baseGrayShadow
+ z:5
+ }
+ Rectangle {
+ id: header;
+
+ visible: true
+ anchors {
+ left: parent.left
+ top: parent.top
+ right: parent.right
+ }
+
+ height: childrenRect.height+5
+ z: 5
+
+ Rectangle {
+ id: titleBarContainer
+
+ anchors.left: parent.left
+ anchors.top: parent.top
+ width: parent.width
+ height: 60
+ visible: true
+
+ Image {
+ id: marketplaceHeaderImage;
+ source: "../common/images/marketplaceHeaderImage.png";
+ anchors.top: parent.top;
+ anchors.topMargin: 2;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 0;
+ anchors.left: parent.left;
+ anchors.leftMargin: 8;
+ width: 140;
+ fillMode: Image.PreserveAspectFit;
+
+ MouseArea {
+ anchors.fill: parent;
+ onClicked: {
+ sendToParent({method: "header_marketplaceImageClicked"});
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ id: searchBarContainer
+
+ anchors.top: titleBarContainer.bottom
+ width: parent.width
+ height: 50
+
+ visible: true
+ clip: false
+
+ //
+ // TODO: Possibly change this to be a combo box
+ //
+ Rectangle {
+ id: categoriesButton
+
+ anchors {
+ left: parent.left
+ leftMargin: 10
+ verticalCenter: parent.verticalCenter
+ }
+ height: 34
+ width: categoriesText.width + 25
+
+ color: hifi.colors.white
+ radius: 4
+ border.width: 1
+ border.color: hifi.colors.lightGrayText
+
+ RalewayRegular {
+ id: categoriesText
+
+ anchors.centerIn: parent
+
+ text: "Categories"
+ size: 14
+ color: hifi.colors.lightGrayText
+ elide: Text.ElideRight
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ rightPadding: 10
+ }
+
+ HiFiGlyphs {
+ id: categoriesDropdownIcon
+
+ anchors {
+ right: parent.right
+ rightMargin: -8
+ verticalCenter: parent.verticalCenter
+ }
+
+ text: hifi.glyphs.caratDn
+ size: 34
+ horizontalAlignment: Text.AlignHCenter
+ color: hifi.colors.baseGray
+ }
+
+ MouseArea {
+ anchors.fill: parent
+
+ hoverEnabled: enabled
+ onClicked: {
+ categoriesDropdown.visible = !categoriesDropdown.visible;
+ categoriesButton.color = categoriesDropdown.visible ? hifi.colors.lightGray : hifi.colors.white;
+ categoriesDropdown.forceActiveFocus();
+ }
+ onEntered: categoriesText.color = hifi.colors.baseGrayShadow
+ onExited: categoriesText.color = hifi.colors.baseGray
+ }
+
+ Component.onCompleted: {
+ MarketplaceScriptingInterface.getMarketplaceCategories();
+ }
+ }
+
+ // or
+ RalewayRegular {
+ id: orText
+
+ anchors.left: categoriesButton.right
+ anchors.verticalCenter: parent.verticalCenter
+ width: 25
+
+ text: "or"
+ size: 18;
+ color: hifi.colors.baseGray
+ elide: Text.ElideRight
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ rightPadding: 10
+ leftPadding: 10
+ }
+
+ HifiControlsUit.TextField {
+ id: searchField
+
+ anchors {
+ verticalCenter: parent.verticalCenter
+ right: parent.right
+ left: orText.right
+ rightMargin: 10
+ }
+ height: 34
+
+ isSearchField: true
+ colorScheme: hifi.colorSchemes.faintGray
+ font.family: "Fira Sans"
+ font.pixelSize: hifi.fontSizes.textFieldInput
+ placeholderText: "Search Marketplace"
+
+ // workaround for https://bugreports.qt.io/browse/QTBUG-49297
+ Keys.onPressed: {
+ switch (event.key) {
+ case Qt.Key_Return:
+ case Qt.Key_Enter:
+ event.accepted = true;
+
+ // emit accepted signal manually
+ if (acceptableInput) {
+ searchField.accepted();
+ searchField.forceActiveFocus();
+ }
+ break;
+ case Qt.Key_Backspace:
+ if (searchField.text === "") {
+ primaryFilter_index = -1;
+ }
+ break;
+ }
+ }
+
+ onAccepted: {
+ root.searchString = searchField.text;
+ getMarketplaceItems();
+ searchField.forceActiveFocus();
+ }
+
+ onActiveFocusChanged: {
+ root.keyboardRaised = activeFocus;
+ }
+
+ }
+ }
+ }
+ //
+ // HEADER BAR END
+ //
+
+ //
+ // CATEGORIES LIST START
+ //
+ Item {
+ id: categoriesDropdown
+
+ anchors.fill: parent;
+
+ visible: false
+ z: 10
+ MouseArea {
+ anchors.fill: parent
+ propagateComposedEvents: true
+ onClicked: {
+ categoriesDropdown.visible = false;
+ categoriesButton.color = hifi.colors.white;
+ }
+ }
+
+ Rectangle {
+ anchors {
+ left: parent.left;
+ bottom: parent.bottom;
+ top: parent.top;
+ topMargin: 100;
+ }
+ width: parent.width/3
+
+ color: hifi.colors.white
+
+ ListModel {
+ id: categoriesModel
+ }
+
+ ListView {
+ id: categoriesListView;
+
+ anchors.fill: parent
+ anchors.rightMargin: 10
+ width: parent.width
+ currentIndex: -1;
+ clip: true
+
+ model: categoriesModel
+ delegate: ItemDelegate {
+ height: 34
+ width: parent.width
+
+ clip: true
+ contentItem: Rectangle {
+ id: categoriesItem
+
+ anchors.fill: parent
+
+ color: hifi.colors.white
+ visible: true
+
+ RalewayRegular {
+ id: categoriesItemText
+
+ anchors.leftMargin: 15
+ anchors.fill:parent
+
+ text: model.name
+ color: ListView.isCurrentItem ? hifi.colors.lightBlueHighlight : hifi.colors.baseGray
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ size: 14
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ z: 10
+
+ hoverEnabled: true
+ propagateComposedEvents: false
+
+ onEntered: {
+ categoriesItem.color = ListView.isCurrentItem ? hifi.colors.white : hifi.colors.lightBlueHighlight;
+ }
+
+ onExited: {
+ categoriesItem.color = ListView.isCurrentItem ? hifi.colors.lightBlueHighlight : hifi.colors.white;
+ }
+
+ onClicked: {
+ categoriesListView.currentIndex = index;
+ categoriesText.text = categoriesItemText.text;
+ categoriesDropdown.visible = false;
+ categoriesButton.color = hifi.colors.white;
+ root.categoryString = categoriesItemText.text;
+ getMarketplaceItems();
+ }
+ }
+ }
+
+ ScrollBar.vertical: ScrollBar {
+ parent: categoriesListView.parent
+
+ anchors {
+ top: categoriesListView.top;
+ bottom: categoriesListView.bottom;
+ left: categoriesListView.right;
+ }
+
+ contentItem.opacity: 1
+ }
+ }
+ }
+ }
+ //
+ // CATEGORIES LIST END
+ //
+
+ //
+ // ITEMS LIST START
+ //
+ Item {
+ id: itemsList
+
+ anchors {
+ fill: parent
+ topMargin: 115
+ bottomMargin: 50
+ }
+
+ visible: true
+
+ HifiModels.PSFListModel {
+ id: marketBrowseModel
+
+ itemsPerPage: 7
+ listModelName: 'marketBrowse'
+ listView: marketBrowseList
+
+ getPage: function () {
+ MarketplaceScriptingInterface.getMarketplaceItems(
+ root.searchString === "" ? undefined : root.searchString,
+ "",
+ root.categoryString.toLowerCase(),
+ "",
+ "",
+ root.sortString,
+ false,
+ marketBrowseModel.currentPageToRetrieve,
+ marketBrowseModel.itemsPerPage
+ );
+ }
+
+ processPage: function(data) {
+ return data.items;
+ }
+ }
+ ListView {
+ id: marketBrowseList
+
+ anchors.fill: parent
+ anchors.rightMargin: 10
+
+ model: marketBrowseModel
+
+ orientation: ListView.Vertical
+ focus: true
+ clip: true
+
+ delegate: MarketplaceListItem {
+ item_id: model.id
+
+ anchors.topMargin: 10
+
+ image_url:model.thumbnail_url
+ name: model.title
+ likes: model.likes
+ liked: model.has_liked ? model.has_liked : false
+ creator: model.creator
+ category: model.primary_category
+ price: model.cost
+ available: model.availability === "available"
+ isLoggedIn: root.isLoggedIn;
+
+ onShowItem: {
+ MarketplaceScriptingInterface.getMarketplaceItem(item_id);
+ }
+
+ onBuy: {
+ sendToScript({method: 'marketplace_checkout', itemId: item_id});
+ }
+
+ onCategoryClicked: {
+ root.categoryString = category;
+ categoriesText.text = category;
+ getMarketplaceItems();
+ }
+
+ onRequestReload: getMarketplaceItems();
+ }
+
+ ScrollBar.vertical: ScrollBar {
+ parent: marketBrowseList.parent
+
+ anchors {
+ top: marketBrowseList.top
+ bottom: marketBrowseList.bottom
+ left: marketBrowseList.right
+ }
+
+ contentItem.opacity: 1
+ }
+
+ headerPositioning: ListView.InlineHeader
+
+ header: Item {
+ id: itemsHeading
+
+ height: childrenRect.height
+ width: parent.width
+
+ Rectangle {
+ id: itemsSpacer;
+ height: 20
+ }
+
+ Rectangle {
+ id: itemsLoginStatus;
+ anchors {
+ top: itemsSpacer.bottom
+ left: parent.left
+ right: parent.right
+ leftMargin: 15
+ }
+ height: root.isLoggedIn ? 0 : 80
+
+ visible: !root.isLoggedIn
+ color: hifi.colors.greenHighlight
+ border.color: hifi.colors.greenShadow
+ border.width: 1
+ radius: 4
+ z: 10000
+
+ HifiControlsUit.Button {
+ id: loginButton;
+ anchors {
+ left: parent.left
+ top: parent.top
+ bottom: parent.bottom
+ leftMargin: 15
+ topMargin:10
+ bottomMargin: 10
+ }
+ width: 80;
+
+ text: "LOG IN"
+
+ onClicked: {
+ sendToScript({method: 'needsLogIn_loginClicked'});
+ }
+ }
+
+ RalewayRegular {
+ id: itemsLoginText
+
+ anchors {
+ leftMargin: 15
+ top: parent.top;
+ bottom: parent.bottom;
+ right: parent.right;
+ left: loginButton.right
+ }
+
+ text: "to get items from the Marketplace."
+ color: hifi.colors.baseGray
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ size: 18
+ }
+ }
+
+ Item {
+ id: breadcrumbs
+
+ anchors.top: itemsLoginStatus.bottom;
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: 34
+ visible: categoriesListView.currentIndex >= 0
+
+ RalewayRegular {
+ id: categoriesItemText
+
+ anchors.leftMargin: 15
+ anchors.fill:parent
+
+ text: "Home /"
+ color: hifi.colors.blueHighlight
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ size: 18
+ }
+
+ MouseArea {
+ anchors.fill: parent
+
+ onClicked: {
+ categoriesListView.currentIndex = -1;
+ categoriesText.text = "Categories";
+ root.categoryString = "";
+ searchField.text = "";
+ getMarketplaceItems();
+ }
+ }
+ }
+
+ Item {
+ id: searchScope
+
+ anchors {
+ top: breadcrumbs.bottom
+ left: parent.left
+ right: parent.right
+ }
+ height: 50
+
+ RalewaySemiBold {
+ id: searchScopeText;
+
+ anchors {
+ leftMargin: 15
+ fill:parent
+ topMargin: 10
+ }
+
+ text: searchScopeString
+ color: hifi.colors.baseGray
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ size: 22
+ }
+ }
+ Item {
+ id: sort
+ visible: searchString === undefined || searchString === ""
+
+ anchors {
+ top: searchScope.bottom;
+ left: parent.left;
+ right: parent.right;
+ topMargin: 10;
+ leftMargin: 15;
+ }
+ height: visible ? childrenRect.height : 0
+
+ RalewayRegular {
+ id: sortText
+
+ anchors.leftMargin: 15
+ anchors.rightMargin: 20
+ height: 34
+
+ text: "Sort:"
+ color: hifi.colors.lightGrayText
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ size: 14
+ }
+
+ Rectangle {
+ anchors {
+ left: sortText.right
+ top: parent.top
+ leftMargin: 20
+ }
+ width: root.isLoggedIn ? 322 : 242
+ height: 36
+
+ radius: 4
+ border.width: 1
+ border.color: hifi.colors.faintGray
+
+ ListModel {
+ id: sortModel
+
+ ListElement {
+ name: "Name";
+ glyph: ";"
+ sortString: "alpha"
+ }
+
+ ListElement {
+ name: "Date";
+ glyph: ";";
+ sortString: "recent";
+ }
+
+ ListElement {
+ name: "Popular";
+ glyph: ";";
+ sortString: "likes";
+ }
+
+ ListElement {
+ name: "My Likes";
+ glyph: ";";
+ sortString: "my_likes";
+ }
+ }
+
+ ListView {
+ id: sortListView
+
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ left: parent.left
+ topMargin:1
+ bottomMargin:1
+ leftMargin:1
+ rightMargin:1
+ }
+ width: childrenRect.width
+ height: 34
+
+ orientation: ListView.Horizontal
+ focus: true
+ clip: true
+ highlightFollowsCurrentItem: false
+ currentIndex: 1;
+
+ delegate: SortButton {
+ width: 80
+ height: parent.height
+
+ glyph: model.glyph
+ text: model.name
+
+ visible: root.isLoggedIn || model.sortString != "my_likes"
+
+ checked: ListView.isCurrentItem
+
+ onClicked: {
+ root.currentSortIndex = index;
+ sortListView.positionViewAtIndex(index, ListView.Beginning);
+ sortListView.currentIndex = index;
+ root.sortString = model.sortString;
+ getMarketplaceItems();
+ }
+ }
+ highlight: Rectangle {
+ width: 80
+ height: parent.height
+
+ color: hifi.colors.faintGray
+ x: sortListView.currentItem.x
+ }
+
+ model: sortModel
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //
+ // ITEMS LIST END
+ //
+
+ //
+ // ITEM START
+ //
+ Item {
+ id: marketplaceItemView
+
+ anchors.fill: parent
+ anchors.topMargin: 115
+ anchors.bottomMargin: 50
+ width: parent.width
+
+ visible: false
+
+ ScrollView {
+ id: marketplaceItemScrollView
+
+ anchors.fill: parent
+
+ clip: true
+ ScrollBar.vertical.policy: ScrollBar.AlwaysOn
+ contentWidth: parent.width
+ contentHeight: childrenRect.height
+
+ function resize() {
+ contentHeight = (marketplaceItemContent.y - itemSpacer.y + marketplaceItemContent.height);
+ }
+
+ Item {
+ id: itemSpacer
+ anchors.top: parent.top
+ height: 15
+ }
+
+ Rectangle {
+ id: itemLoginStatus;
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: itemSpacer.bottom
+ topMargin: 10
+ leftMargin: 15
+ rightMargin: 15
+ }
+ height: root.isLoggedIn ? 0 : 80
+
+ visible: !root.isLoggedIn
+ color: hifi.colors.greenHighlight
+ border.color: hifi.colors.greenShadow
+ border.width: 1
+ radius: 4
+ z: 10000
+
+ HifiControlsUit.Button {
+ id: loginButton;
+ anchors {
+ left: parent.left
+ top: parent.top
+ bottom: parent.bottom
+ leftMargin: 15
+ topMargin:10
+ bottomMargin: 10
+ }
+ width: 80;
+
+ text: "LOG IN"
+
+ onClicked: {
+ sendToScript({method: 'needsLogIn_loginClicked'});
+ }
+ }
+
+ RalewayRegular {
+ id: itemsLoginText
+
+ anchors {
+ leftMargin: 15
+ top: parent.top;
+ bottom: parent.bottom;
+ right: parent.right;
+ left: loginButton.right
+ }
+
+ text: "to get items from the Marketplace."
+ color: hifi.colors.baseGray
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ size: 18
+ }
+ }
+
+
+ Rectangle {
+ id: marketplaceItemContent
+ anchors.top: itemLoginStatus.bottom
+ width: parent.width
+ height: childrenRect.height;
+
+ RalewaySemiBold {
+ id: backText
+
+ anchors {
+ top: parent.top
+ left: parent.left
+ topMargin: 10
+ leftMargin: 15
+ bottomMargin: 10
+ }
+ width: paintedWidth
+ height: paintedHeight
+
+ text: "Back"
+ size: hifi.fontSizes.overlayTitle
+ color: hifi.colors.blueHighlight
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ MouseArea {
+ anchors.fill: backText
+
+ onClicked: {
+ getMarketplaceItems();
+ }
+ }
+
+ MarketplaceItem {
+ id: marketplaceItem
+
+
+ anchors.topMargin: 15
+ anchors.top: backText.bottom
+ width: parent.width
+ height: childrenRect.height
+
+ isLoggedIn: root.isLoggedIn;
+
+ onBuy: {
+ sendToScript({method: 'marketplace_checkout', itemId: item_id, itemEdition: edition});
+ }
+
+ onShowLicense: {
+ var xhr = new XMLHttpRequest;
+ xhr.open("GET", url);
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == XMLHttpRequest.DONE) {
+ console.log(xhr.responseText);
+ licenseText.text = xhr.responseText;
+ licenseInfo.visible = true;
+ }
+ };
+ xhr.send();
+ }
+ onCategoryClicked: {
+ root.categoryString = category;
+ categoriesText.text = category;
+ getMarketplaceItems();
+ }
+
+ onResized: {
+ marketplaceItemScrollView.resize();
+ }
+ }
+ }
+ }
+ }
+ //
+ // ITEM END
+ //
+
+ //
+ // FOOTER START
+ //
+
+ Rectangle {
+ id: footer
+
+ anchors {
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ }
+ height: 50
+
+ color: hifi.colors.blueHighlight
+
+ Item {
+ anchors {
+ fill: parent
+ rightMargin: 15
+ leftMargin: 15
+ }
+
+
+
+ Item {
+ id: footerText
+
+ anchors.fill: parent
+ visible: root.supports3DHTML && itemsList.visible
+
+ HiFiGlyphs {
+ id: footerGlyph
+
+ anchors {
+ left: parent.left
+ top: parent.top
+ bottom: parent.bottom
+ rightMargin: 10
+ }
+
+ text: hifi.glyphs.info
+ size: 34
+ color: hifi.colors.white
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ RalewaySemiBold {
+ id: footerInfo
+
+ anchors {
+ left: footerGlyph.right
+ top: parent.top
+ bottom: parent.bottom
+ }
+
+ text: "Get items from Clara.io!"
+ color: hifi.colors.white
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ size: 18
+ }
+ }
+
+ HifiControlsUit.Button {
+ anchors {
+ left: parent.left
+ top: parent.top
+ bottom: parent.bottom
+ topMargin: 10
+ bottomMargin: 10
+ leftMargin: 10
+ rightMargin: 10
+ }
+
+ visible: marketplaceItemView.visible
+ text: "< BACK"
+ width: 100
+
+ onClicked: {
+ marketplaceItemView.visible = false;
+ itemsList.visible = true;
+ licenseInfo.visible = false;
+ }
+ }
+
+ HifiControlsUit.Button {
+ anchors {
+ right: parent.right
+ top: parent.top
+ bottom: parent.bottom
+ topMargin: 10
+ bottomMargin: 10
+ leftMargin: 10
+ rightMargin: 10
+ }
+
+ visible: root.supports3DHTML
+
+ text: "SEE ALL MARKETS"
+ width: 180
+
+ onClicked: {
+ sendToScript({method: 'marketplace_marketplaces', itemId: marketplaceItemView.visible ? marketplaceItem.item_id : undefined});
+ }
+ }
+ }
+ }
+ //
+ // FOOTER END
+ //
+
+
+ //
+ // LICENSE START
+ //
+ Rectangle {
+ id: licenseInfo
+
+ anchors {
+ fill: root
+ topMargin: 120
+ bottomMargin: 0
+ }
+
+ visible: false;
+
+ ScrollView {
+ anchors {
+ bottomMargin: 1
+ topMargin: 60
+ leftMargin: 15
+ fill: parent
+ }
+
+ RalewayRegular {
+ id: licenseText
+
+ width:440
+ wrapMode: Text.Wrap
+
+ text: ""
+ size: 18;
+ color: hifi.colors.baseGray
+ }
+ }
+
+ Item {
+ id: licenseClose
+
+ anchors {
+ top: parent.top
+ right: parent.right
+ topMargin: 10
+ rightMargin: 10
+ }
+ width: 30
+ height: 30
+
+ HiFiGlyphs {
+ anchors {
+ fill: parent
+ rightMargin: 0
+ verticalCenter: parent.verticalCenter
+ }
+ height: 30
+
+ text: hifi.glyphs.close
+ size: 34
+ horizontalAlignment: Text.AlignHCenter
+ color: hifi.colors.baseGray
+ }
+
+ MouseArea {
+ anchors.fill: licenseClose
+
+ onClicked: licenseInfo.visible = false;
+ }
+ }
+ }
+ //
+ // LICENSE END
+ //
+
+ HifiControlsUit.Keyboard {
+ id: keyboard
+
+ anchors {
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ }
+
+ raised: parent.keyboardEnabled && parent.keyboardRaised
+ numeric: false
+ }
+
+ //
+ // 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 'updateMarketplaceQMLItem':
+ if (!message.params.itemId) {
+ console.log("A message with method 'updateMarketplaceQMLItem' was sent without an itemId!");
+ return;
+ }
+ marketplaceItem.edition = message.params.edition ? message.params.edition : -1;
+ MarketplaceScriptingInterface.getMarketplaceItem(message.params.itemId);
+ break;
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml
new file mode 100644
index 0000000000..24ef528673
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml
@@ -0,0 +1,648 @@
+//
+// MarketplaceListItem.qml
+// qml/hifi/commerce/marketplace
+//
+// MarketplaceListItem
+//
+// Created by Roxanne Skelly on 2019-01-22
+// Copyright 2019 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtGraphicalEffects 1.0
+import QtWebEngine 1.5
+import stylesUit 1.0
+import controlsUit 1.0 as HifiControlsUit
+import "../../../controls" as HifiControls
+import "../common" as HifiCommerceCommon
+import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
+import "../common/sendAsset"
+import "../.." as HifiCommon
+
+Rectangle {
+ id: root;
+
+ property string item_id: ""
+ property string image_url: ""
+ property string name: ""
+ property int likes: 0
+ property bool liked: false
+ property string creator: ""
+ property var categories: []
+ property int price: 0
+ property var attributions: []
+ property string description: ""
+ property string license: ""
+ property string posted: ""
+ property bool available: false
+ property string created_at: ""
+ property bool isLoggedIn: false;
+ property int edition: -1;
+ property bool supports3DHTML: false;
+
+
+ onCategoriesChanged: {
+ categoriesListModel.clear();
+ categories.forEach(function(category) {
+ categoriesListModel.append({"category":category});
+ });
+ }
+
+ onDescriptionChanged: {
+
+ if(root.supports3DHTML) {
+ descriptionTextModel.clear();
+ descriptionTextModel.append({text: description});
+ } else {
+ descriptionText.text = description;
+ }
+ }
+
+ onAttributionsChanged: {
+ attributionsModel.clear();
+ if(root.attributions) {
+ root.attributions.forEach(function(attribution) {
+ attributionsModel.append(attribution);
+ });
+ }
+ footer.evalHeight();
+ }
+
+ signal buy()
+ signal categoryClicked(string category)
+ signal showLicense(string url)
+
+ HifiConstants {
+ id: hifi
+ }
+
+ Connections {
+ target: MarketplaceScriptingInterface
+
+ onMarketplaceItemLikeResult: {
+ if (result.status !== 'success') {
+ console.log("Like/Unlike item", result.data.message);
+ } else {
+ root.liked = !root.liked;
+ root.likes = root.liked ? root.likes + 1 : root.likes - 1;
+ }
+ }
+ }
+ function getFormattedDate(timestamp) {
+ function addLeadingZero(n) {
+ return n < 10 ? '0' + n : '' + n;
+ }
+
+ var a = new Date(timestamp);
+
+ var year = a.getFullYear();
+ var month = addLeadingZero(a.getMonth() + 1);
+ var day = addLeadingZero(a.getDate());
+ var hour = a.getHours();
+ var drawnHour = hour;
+ if (hour === 0) {
+ drawnHour = 12;
+ } else if (hour > 12) {
+ drawnHour -= 12;
+ }
+ drawnHour = addLeadingZero(drawnHour);
+
+ var amOrPm = "AM";
+ if (hour >= 12) {
+ amOrPm = "PM";
+ }
+
+ var min = addLeadingZero(a.getMinutes());
+ var sec = addLeadingZero(a.getSeconds());
+ return a.toDateString() + " " + drawnHour + ':' + min + amOrPm;
+ }
+ function evalHeight() {
+ height = footer.y - header.y + footer.height;
+ }
+
+ signal resized()
+
+ onHeightChanged: {
+ resized();
+ }
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ leftMargin: 15
+ rightMargin: 15
+ }
+ height: footer.y - header.y + footer.height
+
+ Rectangle {
+ id: header
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: parent.top
+ }
+ height: 50
+
+ RalewaySemiBold {
+ id: nameText
+
+ anchors {
+ top: parent.top
+ left: parent.left
+ bottom: parent.bottom
+ }
+ width: paintedWidth
+
+ text: root.name
+ size: 24
+ color: hifi.colors.baseGray
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Item {
+ id: likes
+
+ anchors {
+ top: parent.top
+ right: parent.right
+ bottom: parent.bottom
+ rightMargin: 5
+ }
+
+ RalewaySemiBold {
+ id: heart
+
+ anchors {
+ top: parent.top
+ right: parent.right
+ rightMargin: 0
+ verticalCenter: parent.verticalCenter
+ }
+
+ text: "\u2665"
+ size: 20
+ horizontalAlignment: Text.AlignHCenter
+ color: root.liked ? hifi.colors.redAccent : hifi.colors.lightGrayText
+ }
+
+ RalewaySemiBold {
+ id: likesText
+
+ anchors {
+ top: parent.top
+ right: heart.left
+ rightMargin: 5
+ leftMargin: 15
+ bottom: parent.bottom
+ }
+ width: paintedWidth
+
+ text: root.likes
+ size: hifi.fontSizes.overlayTitle
+ color: hifi.colors.baseGray
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ MouseArea {
+ anchors {
+ left: likesText.left
+ right: heart.right
+ top: likesText.top
+ bottom: likesText.bottom
+ }
+
+ onClicked: {
+ if (isLoggedIn) {
+ MarketplaceScriptingInterface.marketplaceItemLike(root.item_id, !root.liked);
+ }
+ }
+ }
+ }
+ }
+
+ Image {
+ id: itemImage
+
+ anchors {
+ top: header.bottom
+ left: parent.left
+ right: parent.right
+ }
+ height: width*0.5625
+
+ source: root.image_url
+ fillMode: Image.PreserveAspectCrop;
+ }
+
+ Item {
+ id: footer
+
+ anchors {
+ left: parent.left;
+ right: parent.right;
+ top: itemImage.bottom;
+ }
+ height: categoriesList.y - buyButton.y + categoriesList.height
+
+ function evalHeight() {
+ height = categoriesList.y - buyButton.y + categoriesList.height;
+ console.log("HEIGHT: " + height);
+ }
+
+ HifiControlsUit.Button {
+ id: buyButton
+
+ anchors {
+ right: parent.right
+ top: parent.top
+ left: parent.left
+ topMargin: 15
+ }
+ height: 50
+
+ text: root.edition >= 0 ? "UPGRADE FOR FREE" : (root.available ? (root.price ? root.price : "FREE") : "UNAVAILABLE (not for sale)")
+ enabled: root.edition >= 0 || root.available
+ buttonGlyph: root.available ? (root.price ? hifi.glyphs.hfc : "") : ""
+ color: hifi.buttons.blue
+
+ onClicked: root.buy();
+ }
+
+ Item {
+ id: creatorItem
+
+ anchors {
+ top: buyButton.bottom
+ leftMargin: 15
+ topMargin: 15
+ }
+ width: parent.width
+ height: childrenRect.height
+
+ RalewaySemiBold {
+ id: creatorLabel
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ width: paintedWidth
+
+ text: "CREATOR:"
+ size: 14
+ color: hifi.colors.lightGrayText
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ RalewaySemiBold {
+ id: creatorText
+
+ anchors {
+ top: creatorLabel.bottom
+ left: parent.left
+ topMargin: 10
+ }
+ width: paintedWidth
+ text: root.creator
+
+ size: 18
+ color: hifi.colors.blueHighlight
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+
+ Item {
+ id: posted
+
+ anchors {
+ top: creatorItem.bottom
+ leftMargin: 15
+ topMargin: 15
+ }
+ width: parent.width
+ height: childrenRect.height
+
+ RalewaySemiBold {
+ id: postedLabel
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ width: paintedWidth
+
+ text: "POSTED:"
+ size: 14
+ color: hifi.colors.lightGrayText
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ RalewaySemiBold {
+ id: created_at
+
+ anchors {
+ top: postedLabel.bottom
+ left: parent.left
+ right: parent.right
+ topMargin: 5
+ }
+
+ text: { getFormattedDate(root.created_at); }
+ size: 14
+ color: hifi.colors.lightGray
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+
+ Rectangle {
+
+ anchors {
+ top: posted.bottom
+ leftMargin: 15
+ topMargin: 15
+ }
+ width: parent.width
+ height: 1
+
+ color: hifi.colors.lightGrayText
+ }
+
+ Item {
+ id: attributions
+
+ anchors {
+ top: posted.bottom
+ topMargin: 30
+ left: parent.left
+ right: parent.right
+ }
+ width: parent.width
+ height: attributionsModel.count > 0 ? childrenRect.height : 0
+ visible: attributionsModel.count > 0
+
+ RalewaySemiBold {
+ id: attributionsLabel
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ width: paintedWidth
+ height: paintedHeight
+
+ text: "ATTRIBUTIONS:"
+ size: 14
+ color: hifi.colors.lightGrayText
+ verticalAlignment: Text.AlignVCenter
+ }
+ ListModel {
+ id: attributionsModel
+ }
+
+ ListView {
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: attributionsLabel.bottom
+ bottomMargin: 15
+ }
+
+ height: 24*model.count+10
+
+ model: attributionsModel
+ delegate: Item {
+ height: 24
+ width: parent.width
+ RalewaySemiBold {
+ id: attributionName
+
+ anchors.leftMargin: 15
+ height: 24
+ width: parent.width
+
+ text: model.name
+ elide: Text.ElideRight
+ size: 14
+
+ color: model.link ? hifi.colors.blueHighlight : hifi.colors.baseGray
+ verticalAlignment: Text.AlignVCenter
+ MouseArea {
+ anchors.fill: parent
+
+ onClicked: {
+ if (model.link) {
+ sendToScript({method: 'marketplace_open_link', link: model.link});
+ }
+ }
+ }
+ }
+ }
+ }
+ Rectangle {
+
+ anchors {
+ bottom: attributions.bottom
+ leftMargin: 15
+ }
+ width: parent.width
+ height: 1
+
+ color: hifi.colors.lightGrayText
+ }
+ }
+ Item {
+ id: licenseItem;
+
+ anchors {
+ top: attributions.bottom
+ left: parent.left
+ topMargin: 15
+ }
+ width: parent.width
+ height: childrenRect.height
+
+ RalewaySemiBold {
+ id: licenseLabel
+
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ width: paintedWidth;
+
+ text: "SHARED UNDER:";
+ size: 14;
+ color: hifi.colors.lightGrayText;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ RalewaySemiBold {
+ id: licenseText
+
+ anchors.top: licenseLabel.bottom
+ anchors.left: parent.left
+ anchors.topMargin: 5
+ width: paintedWidth
+
+ text: root.license
+ size: 14
+ color: hifi.colors.lightGray
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ RalewaySemiBold {
+ id: licenseHelp
+
+ anchors.top: licenseText.bottom
+ anchors.left: parent.left
+ anchors.topMargin: 5
+ width: paintedWidth
+
+ text: "More about this license"
+ size: 14
+ color: hifi.colors.blueHighlight
+ verticalAlignment: Text.AlignVCenter
+
+ MouseArea {
+ anchors.fill: parent
+
+ onClicked: {
+ licenseInfo.visible = true;
+ var url;
+ if (root.license === "No Rights Reserved (CC0)") {
+ url = "https://creativecommons.org/publicdomain/zero/1.0/"
+ } else if (root.license === "Attribution (CC BY)") {
+ url = "https://creativecommons.org/licenses/by/4.0/legalcode.txt"
+ } else if (root.license === "Attribution-ShareAlike (CC BY-SA)") {
+ url = "https://creativecommons.org/licenses/by-sa/4.0/legalcode.txt"
+ } else if (root.license === "Attribution-NoDerivs (CC BY-ND)") {
+ url = "https://creativecommons.org/licenses/by-nd/4.0/legalcode.txt"
+ } else if (root.license === "Attribution-NonCommercial (CC BY-NC)") {
+ url = "https://creativecommons.org/licenses/by-nc/4.0/legalcode.txt"
+ } else if (root.license === "Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)") {
+ url = "https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.txt"
+ } else if (root.license === "Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)") {
+ url = "https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode.txt"
+ } else if (root.license === "Proof of Provenance License (PoP License)") {
+ url = "https://digitalassetregistry.com/PoP-License/v1/"
+ }
+ if(url) {
+ showLicense(url)
+ }
+ }
+ }
+ }
+ }
+
+ Item {
+ id: descriptionItem
+ property string text: ""
+
+ anchors {
+ top: licenseItem.bottom
+ topMargin: 15
+ left: parent.left
+ right: parent.right
+ }
+ height: childrenRect.height
+ onHeightChanged: {
+ footer.evalHeight();
+ }
+ RalewaySemiBold {
+ id: descriptionLabel
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ width: paintedWidth
+ height: 20
+
+ text: "DESCRIPTION:"
+ size: 14
+ color: hifi.colors.lightGrayText
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ RalewaySemiBold {
+ id: descriptionText
+
+ anchors.top: descriptionLabel.bottom
+ anchors.left: parent.left
+ anchors.topMargin: 5
+ width: parent.width
+
+ text: root.description
+ size: 14
+ color: hifi.colors.lightGray
+ linkColor: hifi.colors.blueHighlight
+ verticalAlignment: Text.AlignVCenter
+ textFormat: Text.RichText
+ wrapMode: Text.Wrap
+ onLinkActivated: {
+ sendToScript({method: 'marketplace_open_link', link: link});
+ }
+
+ onHeightChanged: { footer.evalHeight(); }
+ }
+ }
+
+ Item {
+ id: categoriesList
+
+ anchors {
+ top: descriptionItem.bottom
+ topMargin: 15
+ left: parent.left
+ right: parent.right
+ }
+ width: parent.width
+ height: categoriesListModel.count*24 + categoryLabel.height + (isLoggedIn ? 50 : 150)
+
+ onHeightChanged: { footer.evalHeight(); }
+
+ RalewaySemiBold {
+ id: categoryLabel
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ width: paintedWidth
+
+ text: "IN:"
+ size: 14
+ color: hifi.colors.lightGrayText
+ verticalAlignment: Text.AlignVCenter
+ }
+ ListModel {
+ id: categoriesListModel
+ }
+
+ ListView {
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: categoryLabel.bottom
+ bottomMargin: 15
+ }
+
+ height: 24*model.count+10
+
+ model: categoriesListModel
+ delegate: RalewaySemiBold {
+ id: categoryText
+
+ anchors.leftMargin: 15
+ width: paintedWidth
+
+ text: model.category
+ size: 14
+ height: 24
+ color: hifi.colors.blueHighlight
+ verticalAlignment: Text.AlignVCenter
+
+ MouseArea {
+ anchors.fill: categoryText
+
+ onClicked: root.categoryClicked(model.category);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml
new file mode 100644
index 0000000000..2f37637e40
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml
@@ -0,0 +1,321 @@
+//
+// MarketplaceListItem.qml
+// qml/hifi/commerce/marketplace
+//
+// MarketplaceListItem
+//
+// Created by Roxanne Skelly on 2019-01-22
+// Copyright 2019 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtGraphicalEffects 1.0
+import stylesUit 1.0
+import controlsUit 1.0 as HifiControlsUit
+import "../../../controls" as HifiControls
+import "../common" as HifiCommerceCommon
+import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
+import "../common/sendAsset"
+import "../.." as HifiCommon
+
+Rectangle {
+ id: root
+
+ property string item_id: ""
+ property string image_url: ""
+ property string name: ""
+ property int likes: 0
+ property bool liked: false
+ property string creator: ""
+ property string category: ""
+ property int price: 0
+ property bool available: false
+ property bool isLoggedIn: false;
+
+ signal buy()
+ signal showItem()
+ signal categoryClicked(string category)
+ signal requestReload()
+
+ HifiConstants { id: hifi }
+
+ width: parent.width
+ height: childrenRect.height+50
+
+ DropShadow {
+ anchors.fill: shadowBase
+
+ source: shadowBase
+ verticalOffset: 4
+ horizontalOffset: 4
+ radius: 6
+ samples: 9
+ color: hifi.colors.baseGrayShadow
+ }
+
+ Rectangle {
+ id: shadowBase
+
+ anchors.fill: itemRect
+
+ color: "white"
+ }
+
+ Rectangle {
+ id: itemRect
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: parent.top
+ topMargin: 20
+ bottomMargin: 10
+ leftMargin: 15
+ rightMargin: 15
+ }
+ height: childrenRect.height
+
+ MouseArea {
+ anchors.fill: parent
+
+ hoverEnabled: true
+
+ onClicked: root.showItem();
+ onEntered: hoverRect.visible = true;
+ onExited: hoverRect.visible = false;
+
+ }
+
+ Rectangle {
+ id: header
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: parent.top
+ }
+ height: 50
+
+ RalewaySemiBold {
+ id: nameText
+
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ rightMargin: 50
+ leftMargin: 15
+ bottom: parent.bottom
+ }
+ width: paintedWidth
+
+ text: root.name
+ size: hifi.fontSizes.overlayTitle
+ elide: Text.ElideRight
+ color: hifi.colors.blueHighlight
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Item {
+ id: likes
+
+ anchors {
+ top: parent.top
+ right: parent.right
+ rightMargin: 15
+ bottom: parent.bottom
+ }
+ width: heart.width + likesText.width
+
+ Connections {
+ target: MarketplaceScriptingInterface
+
+ onMarketplaceItemLikeResult: {
+ if (result.status !== 'success') {
+ console.log("Failed to get Marketplace Categories", result.data.message);
+ root.requestReload();
+ }
+ }
+ }
+
+ RalewaySemiBold {
+ id: heart
+
+ anchors {
+ top: parent.top
+ right: parent.right
+ rightMargin: 0
+ verticalCenter: parent.verticalCenter
+ }
+
+ text: "\u2665"
+ size: 20
+ horizontalAlignment: Text.AlignHCenter;
+ color: root.liked ? hifi.colors.redAccent : hifi.colors.lightGrayText
+ }
+
+ RalewaySemiBold {
+ id: likesText
+
+ anchors {
+ top: parent.top
+ right: heart.left
+ rightMargin: 5
+ leftMargin: 15
+ bottom: parent.bottom
+ }
+ width: paintedWidth
+
+ text: root.likes
+ size: hifi.fontSizes.overlayTitle
+ color: hifi.colors.baseGray
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ MouseArea {
+ anchors {
+ left: likesText.left
+ right: heart.right
+ top: parent.top
+ bottom: parent.bottom
+ }
+ onClicked: {
+ if (isLoggedIn) {
+ root.liked = !root.liked;
+ root.likes = root.liked ? root.likes + 1 : root.likes - 1;
+ MarketplaceScriptingInterface.marketplaceItemLike(root.item_id, root.liked);
+ }
+ }
+ }
+ }
+ }
+
+ Image {
+ id: itemImage
+
+ anchors {
+ top: header.bottom
+ left: parent.left
+ right: parent.right
+ }
+ height: width * 0.5625
+
+ source: root.image_url
+ fillMode: Image.PreserveAspectCrop
+ }
+
+ Item {
+ id: footer
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: itemImage.bottom
+ }
+ height: 60
+
+ RalewaySemiBold {
+ id: creatorLabel
+
+ anchors {
+ top: parent.top
+ left: parent.left
+ leftMargin: 15
+ }
+ width: paintedWidth
+
+ text: "CREATOR:"
+ size: 14
+ color: hifi.colors.lightGrayText
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ RalewaySemiBold {
+ id: creatorText
+
+ anchors {
+ top: creatorLabel.top;
+ left: creatorLabel.right;
+ leftMargin: 15;
+ }
+ width: paintedWidth;
+
+ text: root.creator;
+ size: 14;
+ color: hifi.colors.lightGray;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ RalewaySemiBold {
+ id: categoryLabel
+
+ anchors {
+ top: creatorLabel.bottom
+ left: parent.left
+ leftMargin: 15
+ }
+ width: paintedWidth;
+
+ text: "IN:";
+ size: 14;
+ color: hifi.colors.lightGrayText;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ RalewaySemiBold {
+ id: categoryText
+
+ anchors {
+ top: categoryLabel.top
+ left: categoryLabel.right
+ leftMargin: 15
+ }
+ width: paintedWidth
+
+ text: root.category
+ size: 14
+ color: hifi.colors.blueHighlight;
+ verticalAlignment: Text.AlignVCenter;
+
+ MouseArea {
+ anchors.fill: parent
+
+ onClicked: root.categoryClicked(root.category);
+ }
+ }
+
+ HifiControlsUit.Button {
+ anchors {
+ right: parent.right
+ top: parent.top
+ bottom: parent.bottom
+ rightMargin: 15
+ topMargin:10
+ bottomMargin: 10
+ }
+
+ text: root.price ? root.price : "FREE"
+ buttonGlyph: root.price ? hifi.glyphs.hfc : ""
+ color: hifi.buttons.blue;
+
+ onClicked: root.buy();
+ }
+ }
+
+ Rectangle {
+ id: hoverRect
+
+ anchors.fill: parent
+
+ border.color: hifi.colors.blueHighlight
+ border.width: 2
+ color: "#00000000"
+ visible: false
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/commerce/marketplace/SortButton.qml b/interface/resources/qml/hifi/commerce/marketplace/SortButton.qml
new file mode 100644
index 0000000000..37ad2735ce
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/marketplace/SortButton.qml
@@ -0,0 +1,87 @@
+//
+// SortButton.qml
+// qml/hifi/commerce/marketplace
+//
+// SortButton
+//
+// Created by Roxanne Skelly on 2019-01-18
+// Copyright 2019 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtGraphicalEffects 1.0
+import stylesUit 1.0
+import controlsUit 1.0 as HifiControlsUit
+import "../../../controls" as HifiControls
+import "../common" as HifiCommerceCommon
+import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
+import "../common/sendAsset"
+import "../.." as HifiCommon
+
+Item {
+ HifiConstants { id: hifi; }
+
+ id: root;
+
+
+ property string glyph: "";
+ property string text: "";
+ property bool checked: false;
+ signal clicked();
+
+ width: childrenRect.width;
+ height: parent.height;
+
+ Rectangle {
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ height: parent.height;
+ width: 2;
+ color: hifi.colors.faintGray;
+ visible: index > 0;
+ }
+
+ HiFiGlyphs {
+ id: buttonGlyph;
+ text: root.glyph;
+ // Size
+ size: 14;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.leftMargin: 0;
+ anchors.top: parent.top;
+ anchors.verticalCenter: parent.verticalCenter;
+ height: parent.height;
+ horizontalAlignment: Text.AlignHCenter;
+ // Style
+ color: hifi.colors.lightGray;
+ }
+ RalewayRegular {
+ id: buttonText;
+ text: root.text;
+ // Text size
+ size: 14;
+ // Style
+ color: hifi.colors.lightGray;
+ elide: Text.ElideRight;
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ anchors.top: parent.top;
+ height: parent.height;
+ }
+ MouseArea {
+ anchors.fill: parent;
+ hoverEnabled: enabled;
+ onClicked: {
+ root.clicked();
+ }
+ }
+}
\ No newline at end of file
diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
index f7dc26df5f..df6e216b32 100644
--- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
+++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
@@ -28,6 +28,7 @@ Item {
property string purchaseStatus;
property string itemName;
property string itemId;
+ property string updateItemId;
property string itemPreviewImageUrl;
property string itemHref;
property string certificateId;
@@ -45,9 +46,9 @@ Item {
property bool cardBackVisible;
property bool isInstalled;
property string wornEntityID;
- property string upgradeUrl;
+ property string updatedItemId;
property string upgradeTitle;
- property bool updateAvailable: root.upgradeUrl !== "";
+ property bool updateAvailable: root.updateItemId && root.updateItemId !== "";
property bool valid;
property string originalStatusText;
@@ -175,7 +176,7 @@ Item {
Item {
property alias buttonGlyphText: buttonGlyph.text;
- property alias buttonText: buttonText.text;
+ property alias itemButtonText: buttonText.text;
property alias glyphSize: buttonGlyph.size;
property string buttonColor: hifi.colors.black;
property string buttonColor_hover: hifi.colors.blueHighlight;
@@ -243,7 +244,7 @@ Item {
onLoaded: {
item.enabled = root.valid;
item.buttonGlyphText = hifi.glyphs.gift;
- item.buttonText = "Gift";
+ item.itemButtonText = "Gift";
item.buttonClicked = function() {
sendToPurchases({ method: 'flipCard', closeAll: true });
sendToPurchases({
@@ -270,7 +271,7 @@ Item {
onLoaded: {
item.buttonGlyphText = hifi.glyphs.market;
- item.buttonText = "View in Marketplace";
+ item.itemButtonText = "View in Marketplace";
item.buttonClicked = function() {
sendToPurchases({ method: 'flipCard', closeAll: true });
sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId});
@@ -288,7 +289,7 @@ Item {
onLoaded: {
item.buttonGlyphText = hifi.glyphs.certificate;
- item.buttonText = "View Certificate";
+ item.itemButtonText = "View Certificate";
item.buttonClicked = function() {
sendToPurchases({ method: 'flipCard', closeAll: true });
sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId});
@@ -307,7 +308,7 @@ Item {
onLoaded: {
item.buttonGlyphText = hifi.glyphs.uninstall;
- item.buttonText = "Uninstall";
+ item.itemButtonText = "Uninstall";
item.buttonClicked = function() {
sendToPurchases({ method: 'flipCard', closeAll: true });
Commerce.uninstallApp(root.itemHref);
@@ -330,15 +331,14 @@ Item {
onLoaded: {
item.buttonGlyphText = hifi.glyphs.update;
- item.buttonText = "Update";
+ item.itemButtonText = "Update";
item.buttonColor = "#E2334D";
item.buttonClicked = function() {
sendToPurchases({ method: 'flipCard', closeAll: true });
sendToPurchases({
method: 'updateItemClicked',
- itemId: root.itemId,
+ itemId: root.updateAvailable ? root.updateItemId : root.itemId,
itemEdition: root.itemEdition,
- upgradeUrl: root.upgradeUrl,
itemHref: root.itemHref,
itemType: root.itemType,
isInstalled: root.isInstalled,
@@ -378,10 +378,10 @@ Item {
function updateProperties() {
if (updateButton.visible && uninstallButton.visible) {
- item.buttonText = "";
+ item.itemButtonText = "";
item.glyphSize = 20;
} else {
- item.buttonText = "Send to Trash";
+ item.itemButtonText = "Send to Trash";
item.glyphSize = 30;
}
}
diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
index 9433618b6b..bcc2a2821c 100644
--- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
+++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
@@ -29,7 +29,6 @@ Rectangle {
id: root;
property string activeView: "initialize";
- property string referrerURL: "";
property bool securityImageResultReceived: false;
property bool purchasesReceived: false;
property bool punctuationMode: false;
@@ -154,55 +153,10 @@ Rectangle {
}
}
- //
- // TITLE BAR START
- //
- HifiCommerceCommon.EmulatedMarketplaceHeader {
- id: titleBarContainer;
- z: 997;
- visible: false;
- height: 100;
- // Size
- width: parent.width;
- // Anchors
- anchors.left: parent.left;
- anchors.top: parent.top;
-
- 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 = function() {
- lightboxPopup.visible = false;
- }
- lightboxPopup.visible = true;
- } else {
- sendToScript(msg);
- }
- }
- }
- }
- MouseArea {
- enabled: titleBarContainer.usernameDropdownVisible;
- anchors.fill: parent;
- onClicked: {
- titleBarContainer.usernameDropdownVisible = false;
- }
- }
- //
- // TITLE BAR END
- //
-
Rectangle {
id: initialize;
visible: root.activeView === "initialize";
- anchors.top: titleBarContainer.bottom;
- anchors.topMargin: -titleBarContainer.additionalDropdownHeight;
+ anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
@@ -219,8 +173,7 @@ Rectangle {
id: installedAppsContainer;
z: 998;
visible: false;
- anchors.top: titleBarContainer.bottom;
- anchors.topMargin: -titleBarContainer.additionalDropdownHeight;
+ anchors.top: parent.top;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
width: parent.width;
@@ -422,8 +375,8 @@ Rectangle {
// Anchors
anchors.left: parent.left;
anchors.right: parent.right;
- anchors.top: titleBarContainer.bottom;
- anchors.topMargin: 8 - titleBarContainer.additionalDropdownHeight;
+ anchors.top: parent.top;
+ anchors.topMargin: 8;
anchors.bottom: parent.bottom;
//
@@ -585,6 +538,7 @@ Rectangle {
delegate: PurchasedItem {
itemName: title;
itemId: id;
+ updateItemId: model.upgrade_id ? model.upgrade_id : "";
itemPreviewImageUrl: preview;
itemHref: download_url;
certificateId: certificate_id;
@@ -596,7 +550,6 @@ Rectangle {
cardBackVisible: model.cardBackVisible || false;
isInstalled: model.isInstalled || false;
wornEntityID: model.wornEntityID;
- upgradeUrl: model.upgrade_url;
upgradeTitle: model.upgrade_title;
itemType: model.item_type;
valid: model.valid;
@@ -1083,8 +1036,6 @@ Rectangle {
function fromScript(message) {
switch (message.method) {
case 'updatePurchases':
- referrerURL = message.referrerURL || "";
- titleBarContainer.referrerURL = message.referrerURL || "";
filterBar.text = message.filterText ? message.filterText : "";
break;
case 'purchases_showMyItems':
diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
index cf293a06df..eb8aa0f809 100644
--- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
@@ -335,7 +335,7 @@ Item {
if (link.indexOf("users/") !== -1) {
sendSignalToWallet({method: 'transactionHistory_usernameLinkClicked', usernameLink: link});
} else {
- sendSignalToWallet({method: 'transactionHistory_linkClicked', marketplaceLink: link});
+ sendSignalToWallet({method: 'transactionHistory_linkClicked', itemId: model.marketplace_item});
}
}
}
diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml
index b19dcbb919..93a23f1b9d 100644
--- a/interface/resources/qml/hifi/tablet/TabletRoot.qml
+++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml
@@ -3,10 +3,13 @@ import Hifi 1.0
import "../../dialogs"
import "../../controls"
+import stylesUit 1.0
-Item {
+Rectangle {
+ HifiConstants { id: hifi; }
id: tabletRoot
objectName: "tabletRoot"
+ color: hifi.colors.baseGray
property string username: "Unknown user"
property string usernameShort: "Unknown user"
property var rootMenu;
diff --git a/interface/resources/qml/stylesUit/+android/HifiConstants.qml b/interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml
similarity index 100%
rename from interface/resources/qml/stylesUit/+android/HifiConstants.qml
rename to interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index daf2dd6363..e16a54ec74 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -235,6 +235,7 @@
#include "commerce/Ledger.h"
#include "commerce/Wallet.h"
#include "commerce/QmlCommerce.h"
+#include "commerce/QmlMarketplace.h"
#include "ResourceRequestObserver.h"
#include "webbrowser/WebBrowserSuggestionsEngine.h"
@@ -761,6 +762,11 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset";
bool suppressPrompt = cmdOptionExists(argc, const_cast(argv), SUPPRESS_SETTINGS_RESET);
+ // set the OCULUS_STORE property so the oculus plugin can know if we ran from the Oculus Store
+ static const auto OCULUS_STORE_ARG = "--oculus-store";
+ bool isStore = cmdOptionExists(argc, const_cast(argv), OCULUS_STORE_ARG);
+ qApp->setProperty(hifi::properties::OCULUS_STORE, isStore);
+
// Ignore any previous crashes if running from command line with a test script.
bool inTestMode { false };
for (int i = 0; i < argc; ++i) {
@@ -1138,10 +1144,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
qCDebug(interfaceapp) << "[VERSION] We will use DEVELOPMENT global services.";
#endif
- // set the OCULUS_STORE property so the oculus plugin can know if we ran from the Oculus Store
- static const QString OCULUS_STORE_ARG = "--oculus-store";
- bool isStore = arguments().indexOf(OCULUS_STORE_ARG) != -1;
- setProperty(hifi::properties::OCULUS_STORE, isStore);
+ bool isStore = property(hifi::properties::OCULUS_STORE).toBool();
+
DependencyManager::get()->setLimitedCommerce(isStore); // Or we could make it a separate arg, or if either arg is set, etc. And should this instead by a hifi::properties?
updateHeartbeat();
@@ -3005,7 +3009,23 @@ void Application::initializeUi() {
QUrl{ "hifi/dialogs/security/SecurityImageModel.qml" },
QUrl{ "hifi/dialogs/security/SecurityImageSelection.qml" },
QUrl{ "hifi/tablet/TabletMenu.qml" },
+ QUrl{ "hifi/commerce/marketplace/Marketplace.qml" },
}, commerceCallback);
+
+ QmlContextCallback marketplaceCallback = [](QQmlContext* context) {
+ context->setContextProperty("MarketplaceScriptingInterface", new QmlMarketplace());
+ };
+ OffscreenQmlSurface::addWhitelistContextHandler({
+ QUrl{ "hifi/commerce/marketplace/Marketplace.qml" },
+ }, marketplaceCallback);
+
+ QmlContextCallback platformInfoCallback = [](QQmlContext* context) {
+ context->setContextProperty("PlatformInfo", new PlatformInfoScriptingInterface());
+ };
+ OffscreenQmlSurface::addWhitelistContextHandler({
+ QUrl{ "hifi/commerce/marketplace/Marketplace.qml" },
+ }, platformInfoCallback);
+
QmlContextCallback ttsCallback = [](QQmlContext* context) {
context->setContextProperty("TextToSpeech", DependencyManager::get().data());
};
@@ -6309,6 +6329,8 @@ void Application::update(float deltaTime) {
_physicsEngine->processTransaction(transaction);
myAvatar->getCharacterController()->handleProcessedPhysicsTransaction(transaction);
myAvatar->prepareForPhysicsSimulation();
+ _physicsEngine->enableGlobalContactAddedCallback(myAvatar->isFlying());
+
_physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) {
dynamic->prepareForPhysicsSimulation();
});
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 92d9270d20..b198edc245 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -75,7 +75,6 @@ const float PITCH_SPEED_DEFAULT = 75.0f; // degrees/sec
const float MAX_BOOST_SPEED = 0.5f * DEFAULT_AVATAR_MAX_WALKING_SPEED; // action motor gets additive boost below this speed
const float MIN_AVATAR_SPEED = 0.05f;
-const float MIN_AVATAR_SPEED_SQUARED = MIN_AVATAR_SPEED * MIN_AVATAR_SPEED; // speed is set to zero below this
float MIN_SCRIPTED_MOTOR_TIMESCALE = 0.005f;
float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f;
@@ -847,6 +846,7 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
updateOrientation(deltaTime);
updatePosition(deltaTime);
+ updateViewBoom();
}
// update sensorToWorldMatrix for camera and hand controllers
@@ -3323,21 +3323,22 @@ void MyAvatar::updateActionMotor(float deltaTime) {
direction = Vectors::ZERO;
}
+ float sensorToWorldScale = getSensorToWorldScale();
if (state == CharacterController::State::Hover) {
// we're flying --> complex acceleration curve that builds on top of current motor speed and caps at some max speed
float motorSpeed = glm::length(_actionMotorVelocity);
- float finalMaxMotorSpeed = getSensorToWorldScale() * DEFAULT_AVATAR_MAX_FLYING_SPEED * _walkSpeedScalar;
+ float finalMaxMotorSpeed = sensorToWorldScale * DEFAULT_AVATAR_MAX_FLYING_SPEED * _walkSpeedScalar;
float speedGrowthTimescale = 2.0f;
float speedIncreaseFactor = 1.8f * _walkSpeedScalar;
motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale, 0.0f, 1.0f) * speedIncreaseFactor;
- const float maxBoostSpeed = getSensorToWorldScale() * MAX_BOOST_SPEED;
+ const float maxBoostSpeed = sensorToWorldScale * MAX_BOOST_SPEED;
if (_isPushing) {
if (motorSpeed < maxBoostSpeed) {
// an active action motor should never be slower than this
float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed;
- motorSpeed += getSensorToWorldScale() * MIN_AVATAR_SPEED * boostCoefficient;
+ motorSpeed += sensorToWorldScale * MIN_AVATAR_SPEED * boostCoefficient;
} else if (motorSpeed > finalMaxMotorSpeed) {
motorSpeed = finalMaxMotorSpeed;
}
@@ -3348,45 +3349,21 @@ void MyAvatar::updateActionMotor(float deltaTime) {
const glm::vec2 currentVel = { direction.x, direction.z };
float scaledSpeed = scaleSpeedByDirection(currentVel, _walkSpeed.get(), _walkBackwardSpeed.get());
// _walkSpeedScalar is a multiplier if we are in sprint mode, otherwise 1.0
- _actionMotorVelocity = getSensorToWorldScale() * (scaledSpeed * _walkSpeedScalar) * direction;
- }
-
- float previousBoomLength = _boomLength;
- float boomChange = getDriveKey(ZOOM);
- _boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange;
- _boomLength = glm::clamp(_boomLength, ZOOM_MIN, ZOOM_MAX);
-
- // May need to change view if boom length has changed
- if (previousBoomLength != _boomLength) {
- qApp->changeViewAsNeeded(_boomLength);
+ _actionMotorVelocity = sensorToWorldScale * (scaledSpeed * _walkSpeedScalar) * direction;
}
}
void MyAvatar::updatePosition(float deltaTime) {
- if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
- updateActionMotor(deltaTime);
- }
-
- vec3 velocity = getWorldVelocity();
- float sensorToWorldScale = getSensorToWorldScale();
- float sensorToWorldScale2 = sensorToWorldScale * sensorToWorldScale;
- const float MOVING_SPEED_THRESHOLD_SQUARED = 0.0001f; // 0.01 m/s
- if (!_characterController.isEnabledAndReady()) {
- // _characterController is not in physics simulation but it can still compute its target velocity
- updateMotors();
- _characterController.computeNewVelocity(deltaTime, velocity);
-
- float speed2 = glm::length(velocity);
- if (speed2 > sensorToWorldScale2 * MIN_AVATAR_SPEED_SQUARED) {
- // update position ourselves
- applyPositionDelta(deltaTime * velocity);
+ if (_characterController.isEnabledAndReady()) {
+ if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
+ updateActionMotor(deltaTime);
}
- measureMotionDerivatives(deltaTime);
- _moving = speed2 > sensorToWorldScale2 * MOVING_SPEED_THRESHOLD_SQUARED;
- } else {
+ float sensorToWorldScale = getSensorToWorldScale();
+ float sensorToWorldScale2 = sensorToWorldScale * sensorToWorldScale;
+ vec3 velocity = getWorldVelocity();
float speed2 = glm::length2(velocity);
+ const float MOVING_SPEED_THRESHOLD_SQUARED = 0.0001f; // 0.01 m/s
_moving = speed2 > sensorToWorldScale2 * MOVING_SPEED_THRESHOLD_SQUARED;
-
if (_moving) {
// scan for walkability
glm::vec3 position = getWorldPosition();
@@ -3398,6 +3375,18 @@ void MyAvatar::updatePosition(float deltaTime) {
}
}
+void MyAvatar::updateViewBoom() {
+ float previousBoomLength = _boomLength;
+ float boomChange = getDriveKey(ZOOM);
+ _boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange;
+ _boomLength = glm::clamp(_boomLength, ZOOM_MIN, ZOOM_MAX);
+
+ // May need to change view if boom length has changed
+ if (previousBoomLength != _boomLength) {
+ qApp->changeViewAsNeeded(_boomLength);
+ }
+}
+
void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTime, float frequency) {
// COLLISION SOUND API in Audio has been removed
}
@@ -5300,6 +5289,21 @@ void MyAvatar::releaseGrab(const QUuid& grabID) {
bool tellHandler { false };
_avatarGrabsLock.withWriteLock([&] {
+
+ std::map::iterator itr;
+ itr = _avatarGrabs.find(grabID);
+ if (itr != _avatarGrabs.end()) {
+ GrabPointer grab = itr->second;
+ if (grab) {
+ grab->setReleased(true);
+ bool success;
+ SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success);
+ if (target && success) {
+ target->disableGrab(grab);
+ }
+ }
+ }
+
if (_avatarGrabData.remove(grabID)) {
_grabsToDelete.push_back(grabID);
tellHandler = true;
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 0d27988543..c53eae65d4 100755
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -1732,6 +1732,7 @@ private:
void updateOrientation(float deltaTime);
void updateActionMotor(float deltaTime);
void updatePosition(float deltaTime);
+ void updateViewBoom();
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
void initHeadBones();
void initAnimGraph();
diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp
index 5236c5a7fb..046e697b6d 100644
--- a/interface/src/commerce/QmlCommerce.cpp
+++ b/interface/src/commerce/QmlCommerce.cpp
@@ -54,11 +54,10 @@ void QmlCommerce::openSystemApp(const QString& appName) {
{"GOTO", "hifi/tablet/TabletAddressDialog.qml"},
{"PEOPLE", "hifi/Pal.qml"},
{"WALLET", "hifi/commerce/wallet/Wallet.qml"},
- {"MARKET", "/marketplace.html"}
+ {"MARKET", "hifi/commerce/marketplace/Marketplace.qml"}
};
static const QMap systemInject{
- {"MARKET", "/scripts/system/html/js/marketplacesInject.js"}
};
diff --git a/interface/src/commerce/QmlMarketplace.cpp b/interface/src/commerce/QmlMarketplace.cpp
new file mode 100644
index 0000000000..07a9e570bd
--- /dev/null
+++ b/interface/src/commerce/QmlMarketplace.cpp
@@ -0,0 +1,135 @@
+//
+// QmlMarketplace.cpp
+// interface/src/commerce
+//
+// Guard for safe use of Marketplace by authorized QML.
+//
+// Created by Roxanne Skelly on 1/18/19.
+// Copyright 2019 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+
+#include "QmlMarketplace.h"
+#include "CommerceLogging.h"
+#include "Application.h"
+#include "DependencyManager.h"
+#include
+#include
+#include
+#include
+#include "scripting/HMDScriptingInterface.h"
+
+#define ApiHandler(NAME) void QmlMarketplace::NAME##Success(QNetworkReply* reply) { emit NAME##Result(apiResponse(#NAME, reply)); }
+#define FailHandler(NAME) void QmlMarketplace::NAME##Failure(QNetworkReply* reply) { emit NAME##Result(failResponse(#NAME, reply)); }
+#define Handler(NAME) ApiHandler(NAME) FailHandler(NAME)
+Handler(getMarketplaceItems)
+Handler(getMarketplaceItem)
+Handler(marketplaceItemLike)
+Handler(getMarketplaceCategories)
+
+QmlMarketplace::QmlMarketplace() {
+}
+
+void QmlMarketplace::openMarketplace(const QString& marketplaceItemId) {
+ auto tablet = dynamic_cast(
+ DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"));
+ tablet->loadQMLSource("hifi/commerce/marketplace/Marketplace.qml");
+ DependencyManager::get()->openTablet();
+ if (!marketplaceItemId.isEmpty()) {
+ tablet->sendToQml(QVariantMap({ { "method", "marketplace_openItem" }, { "itemId", marketplaceItemId } }));
+ }
+}
+
+void QmlMarketplace::getMarketplaceItems(
+ const QString& q,
+ const QString& view,
+ const QString& category,
+ const QString& adminFilter,
+ const QString& adminFilterCost,
+ const QString& sort,
+ const bool isFree,
+ const int& page,
+ const int& perPage) {
+
+ QString endpoint = "items";
+ QUrlQuery request;
+ request.addQueryItem("q", q);
+ request.addQueryItem("view", view);
+ request.addQueryItem("category", category);
+ request.addQueryItem("adminFilter", adminFilter);
+ request.addQueryItem("adminFilterCost", adminFilterCost);
+ request.addQueryItem("sort", sort);
+ if (isFree) {
+ request.addQueryItem("isFree", "true");
+ }
+ request.addQueryItem("page", QString::number(page));
+ request.addQueryItem("perPage", QString::number(perPage));
+ send(endpoint, "getMarketplaceItemsSuccess", "getMarketplaceItemsFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::Optional, request);
+}
+
+void QmlMarketplace::getMarketplaceItem(const QString& marketplaceItemId) {
+ QString endpoint = QString("items/") + marketplaceItemId;
+ QUrlQuery request;
+ send(endpoint, "getMarketplaceItemSuccess", "getMarketplaceItemFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::Optional, request);
+}
+
+void QmlMarketplace::marketplaceItemLike(const QString& marketplaceItemId, const bool like) {
+ QString endpoint = QString("items/") + marketplaceItemId + "/like";
+ QUrlQuery request;
+ send(endpoint, "marketplaceItemLikeSuccess", "marketplaceItemLikeFailure", like ? QNetworkAccessManager::PostOperation : QNetworkAccessManager::DeleteOperation, AccountManagerAuth::Required, request);
+}
+
+void QmlMarketplace::getMarketplaceCategories() {
+ QString endpoint = "categories";
+ QUrlQuery request;
+ send(endpoint, "getMarketplaceCategoriesSuccess", "getMarketplaceCategoriesFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::None, request);
+}
+
+
+void QmlMarketplace::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, const QUrlQuery & request) {
+ auto accountManager = DependencyManager::get();
+ const QString URL = "/api/v1/marketplace/";
+ JSONCallbackParameters callbackParams(this, success, fail);
+
+ accountManager->sendRequest(URL + endpoint,
+ authType,
+ method,
+ callbackParams,
+ QByteArray(),
+ NULL,
+ QVariantMap(),
+ request);
+
+}
+
+QJsonObject QmlMarketplace::apiResponse(const QString& label, QNetworkReply* reply) {
+ QByteArray response = reply->readAll();
+ QJsonObject data = QJsonDocument::fromJson(response).object();
+#if defined(DEV_BUILD) // Don't expose user's personal data in the wild. But during development this can be handy.
+ qInfo(commerce) << label << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact);
+#endif
+ return data;
+}
+
+// Non-200 responses are not json:
+QJsonObject QmlMarketplace::failResponse(const QString& label, QNetworkReply* reply) {
+ QString response = reply->readAll();
+ qWarning(commerce) << "FAILED" << label << response;
+
+ // tempResult will be NULL if the response isn't valid JSON.
+ QJsonDocument tempResult = QJsonDocument::fromJson(response.toLocal8Bit());
+ if (tempResult.isNull()) {
+ QJsonObject result
+ {
+ { "status", "fail" },
+ { "message", response }
+ };
+ return result;
+ }
+ else {
+ return tempResult.object();
+ }
+}
\ No newline at end of file
diff --git a/interface/src/commerce/QmlMarketplace.h b/interface/src/commerce/QmlMarketplace.h
new file mode 100644
index 0000000000..f954198371
--- /dev/null
+++ b/interface/src/commerce/QmlMarketplace.h
@@ -0,0 +1,68 @@
+//
+// QmlMarketplace.h
+// interface/src/commerce
+//
+// Guard for safe use of Marketplace by authorized QML.
+//
+// Created by Roxanne Skelly on 1/18/19.
+// Copyright 2019 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#pragma once
+#ifndef hifi_QmlMarketplace_h
+#define hifi_QmlMarketplace_h
+
+#include
+
+#include
+#include
+#include "AccountManager.h"
+
+class QmlMarketplace : public QObject {
+ Q_OBJECT
+
+public:
+ QmlMarketplace();
+
+public slots:
+ void getMarketplaceItemsSuccess(QNetworkReply* reply);
+ void getMarketplaceItemsFailure(QNetworkReply* reply);
+ void getMarketplaceItemSuccess(QNetworkReply* reply);
+ void getMarketplaceItemFailure(QNetworkReply* reply);
+ void getMarketplaceCategoriesSuccess(QNetworkReply* reply);
+ void getMarketplaceCategoriesFailure(QNetworkReply* reply);
+ void marketplaceItemLikeSuccess(QNetworkReply* reply);
+ void marketplaceItemLikeFailure(QNetworkReply* reply);
+
+protected:
+ Q_INVOKABLE void openMarketplace(const QString& marketplaceItemId = QString());
+ Q_INVOKABLE void getMarketplaceItems(
+ const QString& q = QString(),
+ const QString& view = QString(),
+ const QString& category = QString(),
+ const QString& adminFilter = QString("published"),
+ const QString& adminFilterCost = QString(),
+ const QString& sort = QString(),
+ const bool isFree = false,
+ const int& page = 1,
+ const int& perPage = 20);
+ Q_INVOKABLE void getMarketplaceItem(const QString& marketplaceItemId);
+ Q_INVOKABLE void marketplaceItemLike(const QString& marketplaceItemId, const bool like = true);
+ Q_INVOKABLE void getMarketplaceCategories();
+
+signals:
+ void getMarketplaceItemsResult(QJsonObject result);
+ void getMarketplaceItemResult(QJsonObject result);
+ void getMarketplaceCategoriesResult(QJsonObject result);
+ void marketplaceItemLikeResult(QJsonObject result);
+
+private:
+ void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, const QUrlQuery & request);
+ QJsonObject apiResponse(const QString& label, QNetworkReply* reply);
+ QJsonObject failResponse(const QString& label, QNetworkReply* reply);
+};
+
+#endif // hifi_QmlMarketplace_h
diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.cpp b/interface/src/scripting/PlatformInfoScriptingInterface.cpp
index b6e4df0d40..b390ab7119 100644
--- a/interface/src/scripting/PlatformInfoScriptingInterface.cpp
+++ b/interface/src/scripting/PlatformInfoScriptingInterface.cpp
@@ -133,3 +133,11 @@ bool PlatformInfoScriptingInterface::hasRiftControllers() {
bool PlatformInfoScriptingInterface::hasViveControllers() {
return qApp->hasViveControllers();
}
+
+bool PlatformInfoScriptingInterface::has3DHTML() {
+#if defined(Q_OS_ANDROID)
+ return false;
+#else
+ return true;
+#endif
+}
diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.h b/interface/src/scripting/PlatformInfoScriptingInterface.h
index 3ed57965c9..aece09b008 100644
--- a/interface/src/scripting/PlatformInfoScriptingInterface.h
+++ b/interface/src/scripting/PlatformInfoScriptingInterface.h
@@ -65,6 +65,12 @@ public slots:
* @function Window.hasRift
* @returns {boolean} true
if running on Windows, otherwise false
.*/
bool hasViveControllers();
+
+ /**jsdoc
+ * Returns true if device supports 3d HTML
+ * @function Window.hasRift
+ * @returns {boolean} true
if device supports 3d HTML, otherwise false
.*/
+ bool has3DHTML();
};
#endif // hifi_PlatformInfoScriptingInterface_h
diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp
index 789a2a2bdf..799d7ea182 100644
--- a/interface/src/ui/AddressBarDialog.cpp
+++ b/interface/src/ui/AddressBarDialog.cpp
@@ -59,7 +59,7 @@ void AddressBarDialog::loadHome() {
auto locationBookmarks = DependencyManager::get();
QString homeLocation = locationBookmarks->addressForBookmark(LocationBookmarks::HOME_BOOKMARK);
if (homeLocation == "") {
- homeLocation = DEFAULT_HIFI_ADDRESS;
+ homeLocation = DEFAULT_HOME_ADDRESS;
}
DependencyManager::get()->handleLookupString(homeLocation);
}
diff --git a/libraries/animation/src/AnimBlendLinearMove.cpp b/libraries/animation/src/AnimBlendLinearMove.cpp
index 07e1c17f77..28b8bb4a9f 100644
--- a/libraries/animation/src/AnimBlendLinearMove.cpp
+++ b/libraries/animation/src/AnimBlendLinearMove.cpp
@@ -151,10 +151,10 @@ void AnimBlendLinearMove::setFrameAndPhase(float dt, float alpha, int prevPoseIn
if (_phase < 0.0f) {
_phase = 0.0f;
}
-
+
// detect loop trigger events
if (_phase >= 1.0f) {
- triggersOut.setTrigger(_id + "Loop");
+ triggersOut.setTrigger(_id + "OnLoop");
_phase = glm::fract(_phase);
}
diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h
index 0f921984b1..eb9ebd33dd 100644
--- a/libraries/animation/src/AnimVariant.h
+++ b/libraries/animation/src/AnimVariant.h
@@ -209,15 +209,14 @@ public:
void set(const QString& key, const QString& value) { _map[key] = AnimVariant(value); }
void unset(const QString& key) { _map.erase(key); }
- void setTrigger(const QString& key) { _triggers.insert(key); }
- void clearTriggers() { _triggers.clear(); }
+ void setTrigger(const QString& key) { _map[key] = AnimVariant(true); }
void setRigToGeometryTransform(const glm::mat4& rigToGeometry) {
_rigToGeometryMat = rigToGeometry;
_rigToGeometryRot = glmExtractRotation(rigToGeometry);
}
- void clearMap() { _map.clear(); }
+ void clearMap() { _map.clear(); _triggers.clear(); }
bool hasKey(const QString& key) const { return _map.find(key) != _map.end(); }
const AnimVariant& get(const QString& key) const {
@@ -238,7 +237,7 @@ public:
// For stat debugging.
std::map toDebugMap() const;
-#ifdef NDEBUG
+#ifndef NDEBUG
void dump() const {
qCDebug(animation) << "AnimVariantMap =";
for (auto& pair : _map) {
diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index bc4dca54f2..7842ec0804 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -1207,9 +1207,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
_networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
}
_lastAnimVars = _animVars;
- _animVars.clearTriggers();
_animVars = triggersOut;
- _networkVars.clearTriggers();
_networkVars = networkTriggersOut;
_lastContext = context;
}
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
index 07c1ca9a32..b8626c813e 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
@@ -412,6 +412,9 @@ void Avatar::accumulateGrabPositions(std::map& g
if (!grab || !grab->getActionID().isNull()) {
continue; // the accumulated value isn't used, in this case.
}
+ if (grab->getReleased()) {
+ continue;
+ }
glm::vec3 jointTranslation = getAbsoluteJointTranslationInObjectFrame(grab->getParentJointIndex());
glm::quat jointRotation = getAbsoluteJointRotationInObjectFrame(grab->getParentJointIndex());
diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp
index 4e95774efb..c733cfa291 100755
--- a/libraries/avatars/src/AvatarData.cpp
+++ b/libraries/avatars/src/AvatarData.cpp
@@ -632,9 +632,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
// include jointData if there is room for the most minimal section. i.e. no translations or rotations.
IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, AvatarDataPacket::minJointDataSize(numJoints)) {
- // Allow for faux joints + translation bit-vector:
- const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat)
- + jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE;
+ // Minimum space required for another rotation joint -
+ // size of joint + following translation bit-vector + translation scale + faux joints:
+ const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) + jointBitVectorSize +
+ sizeof(float) + AvatarDataPacket::FAUX_JOINTS_SIZE;
+
auto startSection = destinationBuffer;
// compute maxTranslationDimension before we send any joint data.
@@ -724,6 +726,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
const JointData& data = joints[i];
const JointData& last = lastSentJointData[i];
+ // Note minSizeForJoint is conservative since there isn't a following bit-vector + scale.
if (packetEnd - destinationBuffer >= minSizeForJoint) {
if (!data.translationIsDefaultPose) {
if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation)
diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp
index c71b296a74..44025fc8f4 100644
--- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp
+++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp
@@ -1048,7 +1048,7 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool
QString scriptUrl = entity->getScript();
if ((shouldLoad && unloadFirst) || scriptUrl.isEmpty()) {
if (_entitiesScriptEngine) {
- _entitiesScriptEngine->unloadEntityScript(entityID);
+ _entitiesScriptEngine->unloadEntityScript(entityID);
}
entity->scriptHasUnloaded();
}
diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt
index fcbe563f88..e359c7132f 100644
--- a/libraries/entities/CMakeLists.txt
+++ b/libraries/entities/CMakeLists.txt
@@ -1,6 +1,6 @@
set(TARGET_NAME entities)
setup_hifi_library(Network Script)
-include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
+target_include_directories(${TARGET_NAME} PRIVATE "${OPENSSL_INCLUDE_DIR}")
include_hifi_library_headers(hfm)
include_hifi_library_headers(fbx)
include_hifi_library_headers(gpu)
diff --git a/libraries/entities/src/EntityDynamicInterface.h b/libraries/entities/src/EntityDynamicInterface.h
index 836dae2057..c911eda471 100644
--- a/libraries/entities/src/EntityDynamicInterface.h
+++ b/libraries/entities/src/EntityDynamicInterface.h
@@ -59,6 +59,7 @@ public:
virtual bool isReadyForAdd() const { return true; }
bool isActive() { return _active; }
+ void deactivate() { _active = false; }
virtual void removeFromSimulation(EntitySimulationPointer simulation) const = 0;
virtual EntityItemWeakPointer getOwnerEntity() const = 0;
diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp
index 41e4f43a5d..c5d72d2d7b 100644
--- a/libraries/entities/src/EntityItem.cpp
+++ b/libraries/entities/src/EntityItem.cpp
@@ -88,13 +88,13 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_REGISTRATION_POINT;
requestedProperties += PROP_CREATED;
requestedProperties += PROP_LAST_EDITED_BY;
- //requestedProperties += PROP_ENTITY_HOST_TYPE; // not sent over the wire
- //requestedProperties += PROP_OWNING_AVATAR_ID; // not sent over the wire
+ requestedProperties += PROP_ENTITY_HOST_TYPE;
+ requestedProperties += PROP_OWNING_AVATAR_ID;
requestedProperties += PROP_PARENT_ID;
requestedProperties += PROP_PARENT_JOINT_INDEX;
requestedProperties += PROP_QUERY_AA_CUBE;
requestedProperties += PROP_CAN_CAST_SHADOW;
- // requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA; // not sent over the wire
+ requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA;
requestedProperties += PROP_RENDER_LAYER;
requestedProperties += PROP_PRIMITIVE_MODE;
requestedProperties += PROP_IGNORE_PICK_INTERSECTION;
@@ -180,6 +180,11 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
EntityPropertyFlags propertyFlags(PROP_LAST_ITEM);
EntityPropertyFlags requestedProperties = getEntityProperties(params);
+ // these properties are not sent over the wire
+ requestedProperties -= PROP_ENTITY_HOST_TYPE;
+ requestedProperties -= PROP_OWNING_AVATAR_ID;
+ requestedProperties -= PROP_VISIBLE_IN_SECONDARY_CAMERA;
+
// If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item,
// then our entityTreeElementExtraEncodeData should include data about which properties we need to append.
if (entityTreeElementExtraEncodeData && entityTreeElementExtraEncodeData->entities.contains(getEntityItemID())) {
@@ -3506,3 +3511,13 @@ void EntityItem::removeGrab(GrabPointer grab) {
}
disableNoBootstrap();
}
+
+void EntityItem::disableGrab(GrabPointer grab) {
+ QUuid actionID = grab->getActionID();
+ if (!actionID.isNull()) {
+ EntityDynamicPointer action = _grabActions.value(actionID);
+ if (action) {
+ action->deactivate();
+ }
+ }
+}
diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h
index ec7ad78313..27b207b6f3 100644
--- a/libraries/entities/src/EntityItem.h
+++ b/libraries/entities/src/EntityItem.h
@@ -561,6 +561,7 @@ public:
virtual void addGrab(GrabPointer grab) override;
virtual void removeGrab(GrabPointer grab) override;
+ virtual void disableGrab(GrabPointer grab) override;
signals:
void requestRenderUpdate();
diff --git a/libraries/gl/CMakeLists.txt b/libraries/gl/CMakeLists.txt
index 925cf9b288..855452e8f0 100644
--- a/libraries/gl/CMakeLists.txt
+++ b/libraries/gl/CMakeLists.txt
@@ -1,5 +1,5 @@
set(TARGET_NAME gl)
-setup_hifi_library(Gui Widgets Qml Quick)
+setup_hifi_library(Gui Widgets)
link_hifi_libraries(shared)
target_opengl()
diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp
index 7d27b42909..a0d52ee223 100644
--- a/libraries/gl/src/gl/Context.cpp
+++ b/libraries/gl/src/gl/Context.cpp
@@ -195,6 +195,21 @@ GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB;
Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context();
+#if defined(GL_CUSTOM_CONTEXT)
+bool Context::makeCurrent() {
+ BOOL result = wglMakeCurrent(_hdc, _hglrc);
+ assert(result);
+ updateSwapchainMemoryCounter();
+ return result;
+}
+ void Context::swapBuffers() {
+ SwapBuffers(_hdc);
+}
+ void Context::doneCurrent() {
+ wglMakeCurrent(0, 0);
+}
+#endif
+
void Context::create(QOpenGLContext* shareContext) {
if (!shareContext) {
shareContext = qt_gl_global_share_context();
@@ -297,7 +312,11 @@ void Context::create(QOpenGLContext* shareContext) {
contextAttribs.push_back(0);
}
contextAttribs.push_back(0);
- HGLRC shareHglrc = (HGLRC)QOpenGLContextWrapper::nativeContext(shareContext);
+ HGLRC shareHglrc = nullptr;
+ if (shareContext) {
+ auto nativeContextPointer = QOpenGLContextWrapper(shareContext).getNativeContext();
+ shareHglrc = (HGLRC)nativeContextPointer->context();
+ }
_hglrc = wglCreateContextAttribsARB(_hdc, shareHglrc, &contextAttribs[0]);
}
diff --git a/libraries/gl/src/gl/ContextQt.cpp b/libraries/gl/src/gl/ContextQt.cpp
index 82724dfd62..24ae29e4ca 100644
--- a/libraries/gl/src/gl/ContextQt.cpp
+++ b/libraries/gl/src/gl/ContextQt.cpp
@@ -61,12 +61,12 @@ void Context::debugMessageHandler(const QOpenGLDebugMessage& debugMessage) {
switch (severity) {
case QOpenGLDebugMessage::NotificationSeverity:
case QOpenGLDebugMessage::LowSeverity:
+ qCDebug(glLogging) << debugMessage;
return;
default:
+ qCWarning(glLogging) << debugMessage;
break;
}
- qWarning(glLogging) << debugMessage;
- return;
}
void Context::setupDebugLogging(QOpenGLContext *context) {
@@ -82,6 +82,8 @@ void Context::setupDebugLogging(QOpenGLContext *context) {
}
}
+
+#if !defined(GL_CUSTOM_CONTEXT)
bool Context::makeCurrent() {
updateSwapchainMemoryCounter();
bool result = _qglContext->makeCurrent(_window);
@@ -98,6 +100,7 @@ void Context::doneCurrent() {
_qglContext->doneCurrent();
}
}
+#endif
Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context();
const QSurfaceFormat& getDefaultOpenGLSurfaceFormat();
diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp
index f05acb50e9..69b41da821 100644
--- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp
+++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp
@@ -65,21 +65,9 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) {
_offscreenSurface->setFormat(_context->format());
_offscreenSurface->create();
-
- // Due to a https://bugreports.qt.io/browse/QTBUG-65125 we can't rely on `isValid`
- // to determine if the offscreen surface was successfully created, so we use
- // makeCurrent as a proxy test. Bug is fixed in Qt 5.9.4
-#if defined(Q_OS_ANDROID)
- if (!_context->makeCurrent(_offscreenSurface)) {
- qFatal("Unable to make offscreen surface current");
- }
- _context->doneCurrent();
-#else
if (!_offscreenSurface->isValid()) {
qFatal("Offscreen surface is invalid");
}
-#endif
-
return true;
}
diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp
index fbebb1128d..842c7abd08 100644
--- a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp
+++ b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp
@@ -17,6 +17,22 @@
#include
#endif
+QOpenGLContextWrapper::Pointer QOpenGLContextWrapper::currentContextWrapper() {
+ return std::make_shared(QOpenGLContext::currentContext());
+}
+
+
+QOpenGLContextWrapper::NativeContextPointer QOpenGLContextWrapper::getNativeContext() const {
+ QOpenGLContextWrapper::NativeContextPointer result;
+ auto nativeHandle = _context->nativeHandle();
+ if (nativeHandle.canConvert()) {
+ result = std::make_shared();
+ *result = nativeHandle.value();
+ }
+ return result;
+}
+
+
uint32_t QOpenGLContextWrapper::currentContextVersion() {
QOpenGLContext* context = QOpenGLContext::currentContext();
if (!context) {
@@ -49,19 +65,6 @@ void QOpenGLContextWrapper::setFormat(const QSurfaceFormat& format) {
_context->setFormat(format);
}
-#ifdef Q_OS_WIN
-void* QOpenGLContextWrapper::nativeContext(QOpenGLContext* context) {
- HGLRC result = 0;
- if (context != nullptr) {
- auto nativeHandle = context->nativeHandle();
- if (nativeHandle.canConvert()) {
- result = nativeHandle.value().context();
- }
- }
- return result;
-}
-#endif
-
bool QOpenGLContextWrapper::create() {
return _context->create();
}
diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.h b/libraries/gl/src/gl/QOpenGLContextWrapper.h
index 32ba7f22e8..1fade3e7fa 100644
--- a/libraries/gl/src/gl/QOpenGLContextWrapper.h
+++ b/libraries/gl/src/gl/QOpenGLContextWrapper.h
@@ -12,19 +12,31 @@
#ifndef hifi_QOpenGLContextWrapper_h
#define hifi_QOpenGLContextWrapper_h
-#include
#include
+#include
class QOpenGLContext;
class QSurface;
class QSurfaceFormat;
class QThread;
+#if defined(Q_OS_ANDROID)
+#include
+#include
+using QGLNativeContext = QEGLNativeContext;
+#elif defined(Q_OS_WIN)
+class QWGLNativeContext;
+using QGLNativeContext = QWGLNativeContext;
+#else
+using QGLNativeContext = void*;
+#endif
+
class QOpenGLContextWrapper {
public:
-#ifdef Q_OS_WIN
- static void* nativeContext(QOpenGLContext* context);
-#endif
+ using Pointer = std::shared_ptr;
+ using NativeContextPointer = std::shared_ptr;
+ static Pointer currentContextWrapper();
+
QOpenGLContextWrapper();
QOpenGLContextWrapper(QOpenGLContext* context);
@@ -37,6 +49,8 @@ public:
void setShareContext(QOpenGLContext* otherContext);
void moveToThread(QThread* thread);
+ NativeContextPointer getNativeContext() const;
+
static QOpenGLContext* currentContext();
static uint32_t currentContextVersion();
diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp
index 82f4f97e3b..1cf331cd1a 100644
--- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp
+++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp
@@ -426,6 +426,9 @@ void GLBackend::render(const Batch& batch) {
GL_PROFILE_RANGE(render_gpu_gl, batch.getName().c_str());
_transform._skybox = _stereo._skybox = batch.isSkyboxEnabled();
+ // FIXME move this to between the transfer and draw passes, so that
+ // framebuffer setup can see the proper stereo state and enable things
+ // like foveation
// Allow the batch to override the rendering stereo settings
// for things like full framebuffer copy operations (deferred lighting passes)
bool savedStereo = _stereo._enable;
diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h
index b5a279a54c..671d4e11d7 100644
--- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h
+++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h
@@ -95,7 +95,7 @@ public:
// Shutdown rendering and persist any required resources
void shutdown() override;
- void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset = false);
+ void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset = false) override;
void render(const Batch& batch) final override;
// This call synchronize the Full Backend cache with the current GLState
diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp
index 61423bf970..7f61ca78f6 100644
--- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp
+++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendQuery.cpp
@@ -24,6 +24,17 @@ const uint32_t MAX_RANGE_QUERY_DEPTH = 10000;
static bool timeElapsed = false;
#endif
+#if defined(USE_GLES)
+static bool hasTimerExtension() {
+ static std::once_flag once;
+ static bool result = false;
+ std::call_once(once, [&] {
+ result = glGetQueryObjectui64vEXT != nullptr;
+ });
+ return result;
+}
+#endif
+
void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) {
auto query = batch._queries.get(batch._params[paramOffset]._uint);
GLQuery* glquery = syncGPUObject(*query);
@@ -33,7 +44,11 @@ void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) {
++_queryStage._rangeQueryDepth;
glquery->_batchElapsedTimeBegin = std::chrono::high_resolution_clock::now();
-#if !defined(USE_GLES)
+#if defined(USE_GLES)
+ if (hasTimerExtension()) {
+ glQueryCounterEXT(glquery->_beginqo, GL_TIMESTAMP_EXT);
+ }
+#else
if (timeElapsed) {
if (_queryStage._rangeQueryDepth <= MAX_RANGE_QUERY_DEPTH) {
glBeginQuery(GL_TIME_ELAPSED, glquery->_endqo);
@@ -52,7 +67,11 @@ void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) {
auto query = batch._queries.get(batch._params[paramOffset]._uint);
GLQuery* glquery = syncGPUObject(*query);
if (glquery) {
-#if !defined(USE_GLES)
+#if defined(USE_GLES)
+ if (hasTimerExtension()) {
+ glQueryCounterEXT(glquery->_endqo, GL_TIMESTAMP_EXT);
+ }
+#else
if (timeElapsed) {
if (_queryStage._rangeQueryDepth <= MAX_RANGE_QUERY_DEPTH) {
glEndQuery(GL_TIME_ELAPSED);
@@ -79,7 +98,21 @@ void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) {
if (glquery->_rangeQueryDepth > MAX_RANGE_QUERY_DEPTH) {
query->triggerReturnHandler(glquery->_result, glquery->_batchElapsedTime);
} else {
-#if !defined(USE_GLES)
+#if defined(USE_GLES)
+ glquery->_result = 0;
+ if (hasTimerExtension()) {
+ glGetQueryObjectui64vEXT(glquery->_endqo, GL_QUERY_RESULT_AVAILABLE, &glquery->_result);
+ if (glquery->_result == GL_TRUE) {
+ GLuint64 start, end;
+ glGetQueryObjectui64vEXT(glquery->_beginqo, GL_QUERY_RESULT, &start);
+ glGetQueryObjectui64vEXT(glquery->_endqo, GL_QUERY_RESULT, &end);
+ glquery->_result = end - start;
+ query->triggerReturnHandler(glquery->_result, glquery->_batchElapsedTime);
+ }
+ } else {
+ query->triggerReturnHandler(0, glquery->_batchElapsedTime);
+ }
+#else
glGetQueryObjectui64v(glquery->_endqo, GL_QUERY_RESULT_AVAILABLE, &glquery->_result);
if (glquery->_result == GL_TRUE) {
if (timeElapsed) {
@@ -92,9 +125,6 @@ void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) {
}
query->triggerReturnHandler(glquery->_result, glquery->_batchElapsedTime);
}
-#else
- // gles3 is not supporting true time query returns just the batch elapsed time
- query->triggerReturnHandler(0, glquery->_batchElapsedTime);
#endif
(void)CHECK_GL_ERROR();
}
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
index e02f12819e..a8b5ec85e8 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
@@ -24,6 +24,10 @@
#include
#include
+static const QString FORCE_MOBILE_TEXTURES_STRING{ "HIFI_FORCE_MOBILE_TEXTURES" };
+static bool FORCE_MOBILE_TEXTURES = QProcessEnvironment::systemEnvironment().contains(FORCE_MOBILE_TEXTURES_STRING);
+
+
using namespace gpu;
using namespace gpu::gl;
using namespace gpu::gl45;
@@ -45,9 +49,10 @@ bool GL45Backend::supportedTextureFormat(const gpu::Element& format) {
case gpu::Semantic::COMPRESSED_EAC_RED_SIGNED:
case gpu::Semantic::COMPRESSED_EAC_XY:
case gpu::Semantic::COMPRESSED_EAC_XY_SIGNED:
- return false;
+ return FORCE_MOBILE_TEXTURES;
+
default:
- return true;
+ return FORCE_MOBILE_TEXTURES ? !format.isCompressed() : true;
}
}
diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h
index aaa1be5892..636518c85a 100644
--- a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h
+++ b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h
@@ -48,6 +48,7 @@ public:
class GLESTexture : public GLTexture {
using Parent = GLTexture;
friend class GLESBackend;
+ friend class GLESFramebuffer;
GLuint allocate(const Texture& texture);
protected:
GLESTexture(const std::weak_ptr& backend, const Texture& buffer);
diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp
index 9c3a83ce13..90ce8c853a 100644
--- a/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp
+++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp
@@ -17,6 +17,34 @@
namespace gpu { namespace gles {
+
+// returns the FOV from the projection matrix
+static inline vec4 extractFov( const glm::mat4& m) {
+ static const std::array CLIPS{ {
+ { 1, 0, 0, 1 },
+ { -1, 0, 0, 1 },
+ { 0, 1, 0, 1 },
+ { 0, -1, 0, 1 }
+ } };
+
+ glm::mat4 mt = glm::transpose(m);
+ vec4 v, result;
+ // Left
+ v = mt * CLIPS[0];
+ result.x = -atanf(v.z / v.x);
+ // Right
+ v = mt * CLIPS[1];
+ result.y = atanf(v.z / v.x);
+ // Down
+ v = mt * CLIPS[2];
+ result.z = -atanf(v.z / v.y);
+ // Up
+ v = mt * CLIPS[3];
+ result.w = atanf(v.z / v.y);
+ return result;
+}
+
+
class GLESFramebuffer : public gl::GLFramebuffer {
using Parent = gl::GLFramebuffer;
static GLuint allocate() {
@@ -29,6 +57,24 @@ public:
GLint currentFBO = -1;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO);
glBindFramebuffer(GL_FRAMEBUFFER, _fbo);
+
+ vec2 focalPoint{ -1.0f };
+
+#if 0
+ {
+ auto backend = _backend.lock();
+ if (backend && backend->isStereo()) {
+ glm::mat4 projections[2];
+ backend->getStereoProjections(projections);
+ vec4 fov = extractFov(projections[0]);
+ float fovwidth = fov.x + fov.y;
+ float fovheight = fov.z + fov.w;
+ focalPoint.x = fov.y / fovwidth;
+ focalPoint.y = (fov.z / fovheight) - 0.5f;
+ }
+ }
+#endif
+
gl::GLTexture* gltexture = nullptr;
TexturePointer surface;
if (_gpuObject.getColorStamps() != _colorStamps) {
@@ -58,7 +104,7 @@ public:
surface = b._texture;
if (surface) {
Q_ASSERT(TextureUsageType::RENDERBUFFER == surface->getUsageType());
- gltexture = backend->syncGPUObject(surface);
+ gltexture = backend->syncGPUObject(surface);
} else {
gltexture = nullptr;
}
@@ -66,6 +112,24 @@ public:
if (gltexture) {
if (gltexture->_target == GL_TEXTURE_2D) {
glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0);
+#if 0
+ if (glTextureFoveationParametersQCOM && focalPoint.x != -1.0f) {
+ static GLint FOVEATION_QUERY = 0;
+ static std::once_flag once;
+ std::call_once(once, [&]{
+ glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_FOVEATED_FEATURE_QUERY_QCOM, &FOVEATION_QUERY);
+ });
+ static const float foveaArea = 4.0f;
+ static const float gain = 16.0f;
+ GLESBackend::GLESTexture* glestexture = static_cast(gltexture);
+ glestexture->withPreservedTexture([=]{
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_FOVEATED_FEATURE_BITS_QCOM, GL_FOVEATION_ENABLE_BIT_QCOM | GL_FOVEATION_SCALED_BIN_METHOD_BIT_QCOM);
+ glTextureFoveationParametersQCOM(_id, 0, 0, -focalPoint.x, focalPoint.y, gain * 2.0f, gain, foveaArea);
+ glTextureFoveationParametersQCOM(_id, 0, 1, focalPoint.x, focalPoint.y, gain * 2.0f, gain, foveaArea);
+ });
+
+ }
+#endif
} else {
glFramebufferTextureLayer(GL_FRAMEBUFFER, colorAttachments[unit], gltexture->_texture, 0,
b._subresource);
diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h
index b080b0ceac..7109b3dfeb 100644
--- a/libraries/gpu/src/gpu/Context.h
+++ b/libraries/gpu/src/gpu/Context.h
@@ -66,6 +66,7 @@ public:
virtual void syncProgram(const gpu::ShaderPointer& program) = 0;
virtual void recycle() const = 0;
virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0;
+ virtual void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset = false) {}
virtual bool supportedTextureFormat(const gpu::Element& format) = 0;
@@ -117,7 +118,6 @@ public:
static ContextMetricSize textureResourcePopulatedGPUMemSize;
static ContextMetricSize textureResourceIdealGPUMemSize;
-protected:
virtual bool isStereo() const {
return _stereo.isStereo();
}
@@ -127,6 +127,7 @@ protected:
eyeProjections[i] = _stereo._eyeProjections[i];
}
}
+protected:
void getStereoViews(mat4* eyeViews) const {
for (int i = 0; i < 2; ++i) {
diff --git a/libraries/gpu/src/gpu/FrameReader.cpp b/libraries/gpu/src/gpu/FrameReader.cpp
index c63eaf51c3..6e39a38097 100644
--- a/libraries/gpu/src/gpu/FrameReader.cpp
+++ b/libraries/gpu/src/gpu/FrameReader.cpp
@@ -34,11 +34,26 @@ public:
return filename;
}
+ static std::string getBaseDir(const std::string& filename) {
+ std::string result;
+ if (0 == filename.find("assets:")) {
+ auto lastSlash = filename.rfind('/');
+ result = filename.substr(0, lastSlash + 1);
+ } else {
+ std::string result = QFileInfo(filename.c_str()).absoluteDir().canonicalPath().toStdString();
+ if (*result.rbegin() != '/') {
+ result += '/';
+ }
+ }
+ return result;
+ }
+
Deserializer(const std::string& filename, uint32_t externalTexture, const TextureLoader& loader) :
- basename(getBaseName(filename)), externalTexture(externalTexture), textureLoader(loader) {}
+ basename(getBaseName(filename)), basedir(getBaseDir(filename)), externalTexture(externalTexture), textureLoader(loader) {
+ }
const std::string basename;
- std::string basedir;
+ const std::string basedir;
std::string binaryFile;
const uint32_t externalTexture;
TextureLoader textureLoader;
@@ -302,6 +317,21 @@ TexturePointer Deserializer::readTexture(const json& node, uint32_t external) {
return nullptr;
}
+ std::string source;
+ readOptional(source, node, keys::source);
+
+ std::string ktxFile;
+ readOptional(ktxFile, node, keys::ktxFile);
+ Element ktxTexelFormat, ktxMipFormat;
+ if (!ktxFile.empty()) {
+ if (QFileInfo(ktxFile.c_str()).isRelative()) {
+ ktxFile = basedir + ktxFile;
+ }
+ ktx::StoragePointer ktxStorage{ new storage::FileStorage(ktxFile.c_str()) };
+ auto ktxObject = ktx::KTX::create(ktxStorage);
+ Texture::evalTextureFormat(ktxObject->getHeader(), ktxTexelFormat, ktxMipFormat);
+ }
+
TextureUsageType usageType = node[keys::usageType];
Texture::Type type = node[keys::type];
glm::u16vec4 dims;
@@ -312,6 +342,9 @@ TexturePointer Deserializer::readTexture(const json& node, uint32_t external) {
uint16 mips = node[keys::mips];
uint16 samples = node[keys::samples];
Element texelFormat = readElement(node[keys::texelFormat]);
+ if (!ktxFile.empty() && (ktxMipFormat.isCompressed() != texelFormat.isCompressed())) {
+ texelFormat = ktxMipFormat;
+ }
Sampler sampler;
readOptionalTransformed(sampler, node, keys::sampler, [](const json& node) { return readSampler(node); });
TexturePointer result;
@@ -325,8 +358,6 @@ TexturePointer Deserializer::readTexture(const json& node, uint32_t external) {
auto& texture = *result;
readOptional(texture._source, node, keys::source);
- std::string ktxFile;
- readOptional(ktxFile, node, keys::ktxFile);
if (!ktxFile.empty()) {
if (QFileInfo(ktxFile.c_str()).isRelative()) {
ktxFile = basedir + "/" + ktxFile;
@@ -359,6 +390,7 @@ ShaderPointer Deserializer::readShader(const json& node) {
// FIXME support procedural shaders
Shader::Type type = node[keys::type];
+ std::string name = node[keys::name];
uint32_t id = node[keys::id];
ShaderPointer result;
switch (type) {
@@ -374,6 +406,9 @@ ShaderPointer Deserializer::readShader(const json& node) {
default:
throw std::runtime_error("not implemented");
}
+ if (result->getSource().name != name) {
+ throw std::runtime_error("Bad name match");
+ }
return result;
}
@@ -747,12 +782,6 @@ StereoState readStereoState(const json& node) {
FramePointer Deserializer::deserializeFrame() {
{
std::string filename{ basename + ".json" };
- if (0 == basename.find("assets:")) {
- auto lastSlash = basename.rfind('/');
- basedir = basename.substr(0, lastSlash);
- } else {
- basedir = QFileInfo(basename.c_str()).absolutePath().toStdString();
- }
storage::FileStorage mappedFile(filename.c_str());
frameNode = json::parse(std::string((const char*)mappedFile.data(), mappedFile.size()));
}
@@ -808,7 +837,7 @@ FramePointer Deserializer::deserializeFrame() {
swapchains =
readArray(frameNode, keys::swapchains, [this](const json& node) { return readSwapchain(node); });
- queries = readArray(frameNode, keys::queries, [this](const json& node) { return readQuery(node); });
+ queries = readArray(frameNode, keys::queries, [](const json& node) { return readQuery(node); });
frame.framebuffer = framebuffers[frameNode[keys::framebuffer].get()];
frame.view = readMat4(frameNode[keys::view]);
frame.pose = readMat4(frameNode[keys::pose]);
diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp
index f74b337ee7..cf77c1cad5 100644
--- a/libraries/networking/src/AccountManager.cpp
+++ b/libraries/networking/src/AccountManager.cpp
@@ -208,7 +208,7 @@ void AccountManager::setSessionID(const QUuid& sessionID) {
}
}
-QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth::Type authType) {
+QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth::Type authType, const QUrlQuery & query) {
QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter());
@@ -227,6 +227,7 @@ QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth::
} else {
requestURL.setPath("/" + path);
}
+ requestURL.setQuery(query);
if (authType != AccountManagerAuth::None ) {
if (hasValidAccessToken()) {
@@ -263,13 +264,14 @@ void AccountManager::sendRequest(const QString& path,
Q_ARG(const JSONCallbackParameters&, callbackParams),
Q_ARG(const QByteArray&, dataByteArray),
Q_ARG(QHttpMultiPart*, dataMultiPart),
- Q_ARG(QVariantMap, propertyMap));
+ Q_ARG(QVariantMap, propertyMap),
+ Q_ARG(QUrlQuery, query));
return;
}
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
- QNetworkRequest networkRequest = createRequest(path, authType);
+ QNetworkRequest networkRequest = createRequest(path, authType, query);
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qCDebug(networking) << "Making a request to" << qPrintable(networkRequest.url().toString());
diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h
index 477488031d..2ccebdd73c 100644
--- a/libraries/networking/src/AccountManager.h
+++ b/libraries/networking/src/AccountManager.h
@@ -61,7 +61,7 @@ class AccountManager : public QObject, public Dependency {
public:
AccountManager(UserAgentGetter userAgentGetter = DEFAULT_USER_AGENT_GETTER);
- QNetworkRequest createRequest(QString path, AccountManagerAuth::Type authType);
+ QNetworkRequest createRequest(QString path, AccountManagerAuth::Type authType, const QUrlQuery & query = QUrlQuery());
Q_INVOKABLE void sendRequest(const QString& path,
AccountManagerAuth::Type authType,
QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp
index e6957728e8..9145b4a79e 100644
--- a/libraries/networking/src/AddressManager.cpp
+++ b/libraries/networking/src/AddressManager.cpp
@@ -30,7 +30,8 @@
#include "UserActivityLogger.h"
#include "udt/PacketHeaders.h"
-const QString DEFAULT_HIFI_ADDRESS = "file:///~/serverless/tutorial.json";
+const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome";
+const QString DEFAULT_HOME_ADDRESS = "file:///~/serverless/tutorial.json";
const QString REDIRECT_HIFI_ADDRESS = "file:///~/serverless/redirect.json";
const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager";
const QString SETTINGS_CURRENT_ADDRESS_KEY = "address";
diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h
index 5318822cdc..450b71023c 100644
--- a/libraries/networking/src/AddressManager.h
+++ b/libraries/networking/src/AddressManager.h
@@ -24,6 +24,7 @@
extern const QString DEFAULT_HIFI_ADDRESS;
extern const QString REDIRECT_HIFI_ADDRESS;
+extern const QString DEFAULT_HOME_ADDRESS;
const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost";
const QString INDEX_PATH = "/";
diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp
index a79f0a0c2b..8ab502e951 100755
--- a/libraries/octree/src/OctreePacketData.cpp
+++ b/libraries/octree/src/OctreePacketData.cpp
@@ -729,12 +729,6 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto
uint16_t length;
memcpy(&length, dataBytes, sizeof(uint16_t));
dataBytes += sizeof(length);
-
- // FIXME - this size check is wrong if we allow larger packets
- if (length * sizeof(glm::vec3) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) {
- result.resize(0);
- return sizeof(uint16_t);
- }
result.resize(length);
memcpy(result.data(), dataBytes, length * sizeof(glm::vec3));
return sizeof(uint16_t) + length * sizeof(glm::vec3);
@@ -744,14 +738,7 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto
uint16_t length;
memcpy(&length, dataBytes, sizeof(uint16_t));
dataBytes += sizeof(length);
-
- // FIXME - this size check is wrong if we allow larger packets
- if (length * sizeof(glm::quat) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) {
- result.resize(0);
- return sizeof(uint16_t);
- }
result.resize(length);
-
const unsigned char *start = dataBytes;
for (int i = 0; i < length; i++) {
dataBytes += unpackOrientationQuatFromBytes(dataBytes, result[i]);
@@ -764,12 +751,6 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto
uint16_t length;
memcpy(&length, dataBytes, sizeof(uint16_t));
dataBytes += sizeof(length);
-
- // FIXME - this size check is wrong if we allow larger packets
- if (length * sizeof(float) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) {
- result.resize(0);
- return sizeof(uint16_t);
- }
result.resize(length);
memcpy(result.data(), dataBytes, length * sizeof(float));
return sizeof(uint16_t) + length * sizeof(float);
@@ -779,14 +760,7 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto
uint16_t length;
memcpy(&length, dataBytes, sizeof(uint16_t));
dataBytes += sizeof(length);
-
- // FIXME - this size check is wrong if we allow larger packets
- if (length / 8 > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) {
- result.resize(0);
- return sizeof(uint16_t);
- }
result.resize(length);
-
int bit = 0;
unsigned char current = 0;
const unsigned char *start = dataBytes;
@@ -797,7 +771,6 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto
result[i] = (bool)(current & (1 << bit));
bit = (bit + 1) % BITS_IN_BYTE;
}
-
return (dataBytes - start) + (int)sizeof(uint16_t);
}
diff --git a/libraries/oculusMobile/CMakeLists.txt b/libraries/oculusMobile/CMakeLists.txt
new file mode 100644
index 0000000000..213721722c
--- /dev/null
+++ b/libraries/oculusMobile/CMakeLists.txt
@@ -0,0 +1,11 @@
+if (ANDROID)
+ set(TARGET_NAME oculusMobile)
+ # don't use the setup_hifi_library macro, we don't want ANY qt dependencies
+ file(GLOB_RECURSE LIB_SRCS "src/*.h" "src/*.cpp" "src/*.c" "src/*.qrc")
+ add_library(${TARGET_NAME} SHARED ${LIB_SRCS})
+ target_glm()
+ target_egl()
+ target_glad()
+ target_oculus_mobile()
+ target_link_libraries(${TARGET_NAME} android log)
+endif()
diff --git a/libraries/oculusMobile/src/ovr/Forward.h b/libraries/oculusMobile/src/ovr/Forward.h
new file mode 100644
index 0000000000..5881dde9a7
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/Forward.h
@@ -0,0 +1,17 @@
+//
+// Created by Bradley Austin Davis on 2018/11/23
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+#include
+#include
+
+namespace ovr {
+ using Mutex = std::mutex;
+ using Condition = std::condition_variable;
+ using Lock = std::unique_lock;
+ using Task = std::function;
+}
\ No newline at end of file
diff --git a/libraries/oculusMobile/src/ovr/Framebuffer.cpp b/libraries/oculusMobile/src/ovr/Framebuffer.cpp
new file mode 100644
index 0000000000..4c4fd2a983
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/Framebuffer.cpp
@@ -0,0 +1,93 @@
+//
+// Created by Bradley Austin Davis on 2018/11/20
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#include "Framebuffer.h"
+
+#include
+#include
+#include
+
+#include
+#include
+
+using namespace ovr;
+
+void Framebuffer::updateLayer(int eye, ovrLayerProjection2& layer, const ovrMatrix4f* projectionMatrix ) const {
+ auto& layerTexture = layer.Textures[eye];
+ layerTexture.ColorSwapChain = _swapChain;
+ layerTexture.SwapChainIndex = _index;
+ if (projectionMatrix) {
+ layerTexture.TexCoordsFromTanAngles = ovrMatrix4f_TanAngleMatrixFromProjection( projectionMatrix );
+ }
+ layerTexture.TextureRect = { 0, 0, 1, 1 };
+}
+
+void Framebuffer::create(const glm::uvec2& size) {
+ _size = size;
+ _index = 0;
+ _validTexture = false;
+
+ // Depth renderbuffer
+ glGenRenderbuffers(1, &_depth);
+ glBindRenderbuffer(GL_RENDERBUFFER, _depth);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, _size.x, _size.y);
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+
+ // Framebuffer
+ glGenFramebuffers(1, &_fbo);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
+ glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depth);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ _swapChain = vrapi_CreateTextureSwapChain3(VRAPI_TEXTURE_TYPE_2D, GL_RGBA8, _size.x, _size.y, 1, 3);
+ _length = vrapi_GetTextureSwapChainLength(_swapChain);
+ if (!_length) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "Unable to count swap chain textures");
+ return;
+ }
+
+ for (int i = 0; i < _length; ++i) {
+ GLuint chainTexId = vrapi_GetTextureSwapChainHandle(_swapChain, i);
+ glBindTexture(GL_TEXTURE_2D, chainTexId);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ }
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+void Framebuffer::destroy() {
+ if (0 != _fbo) {
+ glDeleteFramebuffers(1, &_fbo);
+ _fbo = 0;
+ }
+ if (0 != _depth) {
+ glDeleteRenderbuffers(1, &_depth);
+ _depth = 0;
+ }
+ if (_swapChain != nullptr) {
+ vrapi_DestroyTextureSwapChain(_swapChain);
+ _swapChain = nullptr;
+ }
+ _index = -1;
+ _length = -1;
+}
+
+void Framebuffer::advance() {
+ _index = (_index + 1) % _length;
+ _validTexture = false;
+}
+
+void Framebuffer::bind() {
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
+ if (!_validTexture) {
+ GLuint chainTexId = vrapi_GetTextureSwapChainHandle(_swapChain, _index);
+ glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, chainTexId, 0);
+ _validTexture = true;
+ }
+}
diff --git a/libraries/oculusMobile/src/ovr/Framebuffer.h b/libraries/oculusMobile/src/ovr/Framebuffer.h
new file mode 100644
index 0000000000..5127574462
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/Framebuffer.h
@@ -0,0 +1,34 @@
+//
+// Created by Bradley Austin Davis on 2018/11/20
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+
+#include
+#include
+
+#include
+
+namespace ovr {
+
+struct Framebuffer {
+public:
+ void updateLayer(int eye, ovrLayerProjection2& layer, const ovrMatrix4f* projectionMatrix = nullptr) const;
+ void create(const glm::uvec2& size);
+ void advance();
+ void destroy();
+ void bind();
+
+ uint32_t _depth { 0 };
+ uint32_t _fbo{ 0 };
+ int _length{ -1 };
+ int _index{ -1 };
+ bool _validTexture{ false };
+ glm::uvec2 _size;
+ ovrTextureSwapChain* _swapChain{ nullptr };
+};
+
+} // namespace ovr
\ No newline at end of file
diff --git a/libraries/oculusMobile/src/ovr/GLContext.cpp b/libraries/oculusMobile/src/ovr/GLContext.cpp
new file mode 100644
index 0000000000..449ba67084
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/GLContext.cpp
@@ -0,0 +1,182 @@
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "GLContext.h"
+
+#include
+#include
+#include
+
+#include
+
+#if !defined(EGL_OPENGL_ES3_BIT_KHR)
+#define EGL_OPENGL_ES3_BIT_KHR 0x0040
+#endif
+
+using namespace ovr;
+
+static void* getGlProcessAddress(const char *namez) {
+ auto result = eglGetProcAddress(namez);
+ return (void*)result;
+}
+
+
+void GLContext::initModule() {
+ static std::once_flag once;
+ std::call_once(once, [&]{
+ gladLoadGLES2Loader(getGlProcessAddress);
+ });
+}
+
+void APIENTRY debugMessageCallback(GLenum source,
+ GLenum type,
+ GLuint id,
+ GLenum severity,
+ GLsizei length,
+ const GLchar* message,
+ const void* userParam) {
+ if (type == GL_DEBUG_TYPE_PERFORMANCE_KHR) {
+ return;
+ }
+ switch (severity) {
+ case GL_DEBUG_SEVERITY_HIGH:
+ case GL_DEBUG_SEVERITY_MEDIUM:
+ break;
+ default:
+ return;
+ }
+
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_GL", message);
+}
+
+GLContext::~GLContext() {
+ destroy();
+}
+
+EGLConfig GLContext::findConfig(EGLDisplay display) {
+ // Do NOT use eglChooseConfig, because the Android EGL code pushes in multisample
+ // flags in eglChooseConfig if the user has selected the "force 4x MSAA" option in
+ // settings, and that is completely wasted for our warp target.
+ std::vector configs;
+ {
+ const int MAX_CONFIGS = 1024;
+ EGLConfig configsBuffer[MAX_CONFIGS];
+ EGLint numConfigs = 0;
+ if (eglGetConfigs(display, configsBuffer, MAX_CONFIGS, &numConfigs) == EGL_FALSE) {
+ __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed to fetch configs");
+ return 0;
+ }
+ configs.resize(numConfigs);
+ memcpy(configs.data(), configsBuffer, sizeof(EGLConfig) * numConfigs);
+ }
+
+ std::vector> configAttribs{
+ { EGL_RED_SIZE, 8 }, { EGL_GREEN_SIZE, 8 }, { EGL_BLUE_SIZE, 8 }, { EGL_ALPHA_SIZE, 8 },
+ { EGL_DEPTH_SIZE, 0 }, { EGL_STENCIL_SIZE, 0 }, { EGL_SAMPLES, 0 },
+ };
+
+ auto matchAttrib = [&](EGLConfig config, const std::pair& attribAndValue) {
+ EGLint value = 0;
+ eglGetConfigAttrib(display, config, attribAndValue.first, &value);
+ return (attribAndValue.second == value);
+ };
+
+ auto matchAttribFlags = [&](EGLConfig config, const std::pair& attribAndValue) {
+ EGLint value = 0;
+ eglGetConfigAttrib(display, config, attribAndValue.first, &value);
+ return (value & attribAndValue.second) == attribAndValue.second;
+ };
+
+ auto matchConfig = [&](EGLConfig config) {
+ if (!matchAttribFlags(config, { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR})) {
+ return false;
+ }
+ // The pbuffer config also needs to be compatible with normal window rendering
+ // so it can share textures with the window context.
+ if (!matchAttribFlags(config, { EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT})) {
+ return false;
+ }
+
+ for (const auto& attrib : configAttribs) {
+ if (!matchAttrib(config, attrib)) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+
+ for (const auto& config : configs) {
+ if (matchConfig(config)) {
+ return config;
+ }
+ }
+
+ return 0;
+}
+
+bool GLContext::makeCurrent() {
+ return eglMakeCurrent(display, surface, surface, context) != EGL_FALSE;
+}
+
+void GLContext::doneCurrent() {
+ eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+}
+
+bool GLContext::create(EGLDisplay display, EGLContext shareContext) {
+ this->display = display;
+
+ auto config = findConfig(display);
+
+ if (config == 0) {
+ __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglChooseConfig");
+ return false;
+ }
+
+ EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE };
+
+ context = eglCreateContext(display, config, shareContext, contextAttribs);
+ if (context == EGL_NO_CONTEXT) {
+ __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglCreateContext");
+ return false;
+ }
+
+ const EGLint surfaceAttribs[] = { EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE };
+ surface = eglCreatePbufferSurface(display, config, surfaceAttribs);
+ if (surface == EGL_NO_SURFACE) {
+ __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglCreatePbufferSurface");
+ return false;
+ }
+
+ if (!makeCurrent()) {
+ __android_log_print(ANDROID_LOG_WARN, "QQQ_GL", "Failed eglMakeCurrent");
+ return false;
+ }
+
+ ovr::GLContext::initModule();
+
+#ifndef NDEBUG
+ glDebugMessageCallback(debugMessageCallback, this);
+ glEnable(GL_DEBUG_OUTPUT);
+ glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+#endif
+ return true;
+}
+
+void GLContext::destroy() {
+ if (context != EGL_NO_CONTEXT) {
+ eglDestroyContext(display, context);
+ context = EGL_NO_CONTEXT;
+ }
+
+ if (surface != EGL_NO_SURFACE) {
+ eglDestroySurface(display, surface);
+ surface = EGL_NO_SURFACE;
+ }
+}
diff --git a/libraries/oculusMobile/src/ovr/GLContext.h b/libraries/oculusMobile/src/ovr/GLContext.h
new file mode 100644
index 0000000000..04f96e8d47
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/GLContext.h
@@ -0,0 +1,37 @@
+//
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+
+#include
+
+#include
+#include
+
+namespace ovr {
+
+struct GLContext {
+ using Pointer = std::shared_ptr;
+ EGLSurface surface{ EGL_NO_SURFACE };
+ EGLContext context{ EGL_NO_CONTEXT };
+ EGLDisplay display{ EGL_NO_DISPLAY };
+
+ ~GLContext();
+ static EGLConfig findConfig(EGLDisplay display);
+ bool makeCurrent();
+ void doneCurrent();
+ bool create(EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY), EGLContext shareContext = EGL_NO_CONTEXT);
+ void destroy();
+ operator bool() const { return context != EGL_NO_CONTEXT; }
+ static void initModule();
+};
+
+}
+
+
+#define CHECK_GL_ERROR() if(false) {}
\ No newline at end of file
diff --git a/libraries/oculusMobile/src/ovr/Helpers.cpp b/libraries/oculusMobile/src/ovr/Helpers.cpp
new file mode 100644
index 0000000000..a48d37311e
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/Helpers.cpp
@@ -0,0 +1,38 @@
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "Helpers.h"
+
+#include
+#include
+#include
+#include
+
+using namespace ovr;
+
+void Fov::extend(const Fov& other) {
+ for (size_t i = 0; i < 4; ++i) {
+ leftRightUpDown[i] = std::max(leftRightUpDown[i], other.leftRightUpDown[i]);
+ }
+}
+
+void Fov::extract(const ovrMatrix4f& mat) {
+ auto& fs = leftRightUpDown;
+ ovrMatrix4f_ExtractFov( &mat, fs, fs + 1, fs + 2, fs + 3);
+}
+
+glm::mat4 Fov::withZ(float nearZ, float farZ) const {
+ const auto& fs = leftRightUpDown;
+ return ovr::toGlm(ovrMatrix4f_CreateProjectionAsymmetricFov(fs[0], fs[1], fs[2], fs[3], nearZ, farZ));
+}
+
+glm::mat4 Fov::withZ(const glm::mat4& other) const {
+ // FIXME
+ return withZ(0.01f, 1000.0f);
+}
+
diff --git a/libraries/oculusMobile/src/ovr/Helpers.h b/libraries/oculusMobile/src/ovr/Helpers.h
new file mode 100644
index 0000000000..2bd0b7f603
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/Helpers.h
@@ -0,0 +1,94 @@
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+namespace ovr {
+
+struct Fov {
+ float leftRightUpDown[4];
+ Fov() {}
+ Fov(const ovrMatrix4f& mat) { extract(mat); }
+ void extract(const ovrMatrix4f& mat);
+ void extend(const Fov& other);
+ glm::mat4 withZ(const glm::mat4& other) const;
+ glm::mat4 withZ(float nearZ, float farZ) const;
+};
+
+// Convenience method for looping over each eye with a lambda
+static inline void for_each_eye(const std::function& f) {
+ f(VRAPI_EYE_LEFT);
+ f(VRAPI_EYE_RIGHT);
+}
+
+static inline void for_each_hand(const std::function& f) {
+ f(VRAPI_HAND_LEFT);
+ f(VRAPI_HAND_RIGHT);
+}
+
+static inline glm::mat4 toGlm(const ovrMatrix4f& om) {
+ return glm::transpose(glm::make_mat4(&om.M[0][0]));
+}
+
+static inline glm::vec3 toGlm(const ovrVector3f& ov) {
+ return glm::make_vec3(&ov.x);
+}
+
+static inline glm::vec2 toGlm(const ovrVector2f& ov) {
+ return glm::make_vec2(&ov.x);
+}
+
+static inline glm::quat toGlm(const ovrQuatf& oq) {
+ return glm::make_quat(&oq.x);
+}
+
+static inline glm::mat4 toGlm(const ovrPosef& op) {
+ glm::mat4 orientation = glm::mat4_cast(toGlm(op.Orientation));
+ glm::mat4 translation = glm::translate(glm::mat4(), toGlm(op.Position));
+ return translation * orientation;
+}
+
+static inline ovrMatrix4f fromGlm(const glm::mat4& m) {
+ ovrMatrix4f result;
+ glm::mat4 transposed(glm::transpose(m));
+ memcpy(result.M, &(transposed[0][0]), sizeof(float) * 16);
+ return result;
+}
+
+static inline ovrVector3f fromGlm(const glm::vec3& v) {
+ return { v.x, v.y, v.z };
+}
+
+static inline ovrVector2f fromGlm(const glm::vec2& v) {
+ return { v.x, v.y };
+}
+
+static inline ovrQuatf fromGlm(const glm::quat& q) {
+ return { q.x, q.y, q.z, q.w };
+}
+
+static inline ovrPosef poseFromGlm(const glm::mat4& m) {
+ glm::vec3 translation = glm::vec3(m[3]) / m[3].w;
+ glm::quat orientation = glm::quat_cast(m);
+ ovrPosef result;
+ result.Orientation = fromGlm(orientation);
+ result.Position = fromGlm(translation);
+ return result;
+}
+
+}
+
+
+
+
+
diff --git a/libraries/oculusMobile/src/ovr/TaskQueue.cpp b/libraries/oculusMobile/src/ovr/TaskQueue.cpp
new file mode 100644
index 0000000000..5506a35acd
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/TaskQueue.cpp
@@ -0,0 +1,40 @@
+//
+// Created by Bradley Austin Davis on 2018/11/23
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#include "TaskQueue.h"
+
+using namespace ovr;
+
+void TaskQueue::submitTaskBlocking(Lock& lock, const Task& newTask) {
+ _task = newTask;
+ _taskPending = true;
+ _taskCondition.wait(lock, [=]() -> bool { return !_taskPending; });
+}
+
+void TaskQueue::submitTaskBlocking(const Task& task) {
+ Lock lock(_mutex);
+ submitTaskBlocking(lock, task);
+}
+
+void TaskQueue::pollTask() {
+ Lock lock(_mutex);
+ if (_taskPending) {
+ _task();
+ _taskPending = false;
+ _taskCondition.notify_one();
+ }
+}
+
+void TaskQueue::withLock(const Task& task) {
+ Lock lock(_mutex);
+ task();
+}
+
+void TaskQueue::withLockConditional(const LockTask& task) {
+ Lock lock(_mutex);
+ task(lock);
+}
diff --git a/libraries/oculusMobile/src/ovr/TaskQueue.h b/libraries/oculusMobile/src/ovr/TaskQueue.h
new file mode 100644
index 0000000000..4ec055ece9
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/TaskQueue.h
@@ -0,0 +1,42 @@
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+
+#include
+#include
+
+namespace ovr {
+
+using Mutex = std::mutex;
+using Condition = std::condition_variable;
+using Lock = std::unique_lock;
+using Task = std::function;
+using LockTask = std::function;
+
+class TaskQueue {
+public:
+ // Execute a task on another thread
+ void submitTaskBlocking(const Task& task);
+ void submitTaskBlocking(Lock& lock, const Task& task);
+ void pollTask();
+
+ void withLock(const Task& task);
+ void withLockConditional(const LockTask& task);
+private:
+ Mutex _mutex;
+ Task _task;
+ bool _taskPending{ false };
+ Condition _taskCondition;
+};
+
+}
+
+
+
+
+
diff --git a/libraries/oculusMobile/src/ovr/VrHandler.cpp b/libraries/oculusMobile/src/ovr/VrHandler.cpp
new file mode 100644
index 0000000000..de2b4e1ff6
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/VrHandler.cpp
@@ -0,0 +1,337 @@
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#include "VrHandler.h"
+
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+//#include
+
+#include "GLContext.h"
+#include "Helpers.h"
+#include "Framebuffer.h"
+
+
+
+using namespace ovr;
+
+static thread_local bool isRenderThread { false };
+
+struct VrSurface : public TaskQueue {
+ using HandlerTask = VrHandler::HandlerTask;
+
+ JavaVM* vm{nullptr};
+ jobject oculusActivity{ nullptr };
+ ANativeWindow* nativeWindow{ nullptr };
+
+ VrHandler* handler{nullptr};
+ ovrMobile* session{nullptr};
+ bool resumed { false };
+ GLContext vrglContext;
+ Framebuffer eyeFbos[2];
+ uint32_t readFbo{0};
+ std::atomic presentIndex{1};
+ double displayTime{0};
+
+ static constexpr float EYE_BUFFER_SCALE = 1.0f;
+
+ void onCreate(JNIEnv* env, jobject activity) {
+ env->GetJavaVM(&vm);
+ oculusActivity = env->NewGlobalRef(activity);
+ }
+
+ void setResumed(bool newResumed) {
+ this->resumed = newResumed;
+ submitRenderThreadTask([this](VrHandler* handler){ updateVrMode(); });
+ }
+
+ void setNativeWindow(ANativeWindow* newNativeWindow) {
+ auto oldNativeWindow = nativeWindow;
+ nativeWindow = newNativeWindow;
+ if (oldNativeWindow) {
+ ANativeWindow_release(oldNativeWindow);
+ }
+ submitRenderThreadTask([this](VrHandler* handler){ updateVrMode(); });
+ }
+
+ void init() {
+ if (!handler) {
+ return;
+ }
+
+ EGLContext currentContext = eglGetCurrentContext();
+ EGLDisplay currentDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ vrglContext.create(currentDisplay, currentContext);
+ vrglContext.makeCurrent();
+
+ glm::uvec2 eyeTargetSize;
+ withEnv([&](JNIEnv* env){
+ ovrJava java{ vm, env, oculusActivity };
+ eyeTargetSize = glm::uvec2 {
+ vrapi_GetSystemPropertyInt(&java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_WIDTH) * EYE_BUFFER_SCALE,
+ vrapi_GetSystemPropertyInt(&java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_HEIGHT) * EYE_BUFFER_SCALE,
+ };
+ });
+ __android_log_print(ANDROID_LOG_WARN, "QQQ_OVR", "QQQ Eye Size %d, %d", eyeTargetSize.x, eyeTargetSize.y);
+ ovr::for_each_eye([&](ovrEye eye) {
+ eyeFbos[eye].create(eyeTargetSize);
+ });
+ glGenFramebuffers(1, &readFbo);
+ vrglContext.doneCurrent();
+ }
+
+ void shutdown() {
+ }
+
+ void setHandler(VrHandler *newHandler) {
+ withLock([&] {
+ isRenderThread = newHandler != nullptr;
+ if (handler != newHandler) {
+ shutdown();
+ handler = newHandler;
+ init();
+ if (handler) {
+ updateVrMode();
+ }
+ }
+ });
+ }
+
+ void submitRenderThreadTask(const HandlerTask &task) {
+ withLockConditional([&](Lock &lock) {
+ if (handler != nullptr) {
+ submitTaskBlocking(lock, [&] {
+ task(handler);
+ });
+ }
+ });
+ }
+
+ void withEnv(const std::function& f) {
+ JNIEnv* env = nullptr;
+ bool attached = false;
+ vm->GetEnv((void**)&env, JNI_VERSION_1_6);
+ if (!env) {
+ attached = true;
+ vm->AttachCurrentThread(&env, nullptr);
+ }
+ f(env);
+ if (attached) {
+ vm->DetachCurrentThread();
+ }
+ }
+
+ void updateVrMode() {
+ // For VR mode to be valid, the activity must be between an onResume and
+ // an onPause call and must additionally have a valid native window handle
+ bool vrReady = resumed && nullptr != nativeWindow;
+ // If we're IN VR mode, we'll have a non-null ovrMobile pointer in session
+ bool vrRunning = session != nullptr;
+ if (vrReady != vrRunning) {
+ if (vrRunning) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "vrapi_LeaveVrMode");
+ vrapi_LeaveVrMode(session);
+ session = nullptr;
+ oculusActivity = nullptr;
+ } else {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "vrapi_EnterVrMode");
+ withEnv([&](JNIEnv* env){
+ ovrJava java{ vm, env, oculusActivity };
+ ovrModeParms modeParms = vrapi_DefaultModeParms(&java);
+ modeParms.Flags |= VRAPI_MODE_FLAG_NATIVE_WINDOW;
+ modeParms.Display = (unsigned long long) vrglContext.display;
+ modeParms.ShareContext = (unsigned long long) vrglContext.context;
+ modeParms.WindowSurface = (unsigned long long) nativeWindow;
+ session = vrapi_EnterVrMode(&modeParms);
+ ovrPosef trackingTransform = vrapi_GetTrackingTransform( session, VRAPI_TRACKING_TRANSFORM_SYSTEM_CENTER_EYE_LEVEL);
+ vrapi_SetTrackingTransform( session, trackingTransform );
+ vrapi_SetPerfThread(session, VRAPI_PERF_THREAD_TYPE_RENDERER, pthread_self());
+ vrapi_SetClockLevels(session, 2, 4);
+ vrapi_SetExtraLatencyMode(session, VRAPI_EXTRA_LATENCY_MODE_DYNAMIC);
+ vrapi_SetDisplayRefreshRate(session, 72);
+ });
+ }
+ }
+ }
+
+ void presentFrame(uint32_t sourceTexture, const glm::uvec2 &sourceSize, const ovrTracking2& tracking) {
+ ovrLayerProjection2 layer = vrapi_DefaultLayerProjection2();
+ layer.HeadPose = tracking.HeadPose;
+ if (sourceTexture) {
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
+ glFramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, sourceTexture, 0);
+ GLenum framebufferStatus = glCheckFramebufferStatus(GL_READ_FRAMEBUFFER);
+ if (GL_FRAMEBUFFER_COMPLETE != framebufferStatus) {
+ __android_log_print(ANDROID_LOG_WARN, "QQQ_OVR", "incomplete framebuffer");
+ }
+ }
+ GLenum invalidateAttachment = GL_COLOR_ATTACHMENT0;
+
+ ovr::for_each_eye([&](ovrEye eye) {
+ const auto &eyeTracking = tracking.Eye[eye];
+ auto &eyeFbo = eyeFbos[eye];
+ const auto &destSize = eyeFbo._size;
+ eyeFbo.bind();
+ glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, 1, &invalidateAttachment);
+ if (sourceTexture) {
+ auto sourceWidth = sourceSize.x / 2;
+ auto sourceX = (eye == VRAPI_EYE_LEFT) ? 0 : sourceWidth;
+ glBlitFramebuffer(
+ sourceX, 0, sourceX + sourceWidth, sourceSize.y,
+ 0, 0, destSize.x, destSize.y,
+ GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ }
+ eyeFbo.updateLayer(eye, layer, &eyeTracking.ProjectionMatrix);
+ eyeFbo.advance();
+ });
+ if (sourceTexture) {
+ glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, &invalidateAttachment);
+ glFramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0);
+ }
+ glFlush();
+
+ ovrLayerHeader2 *layerHeader = &layer.Header;
+ ovrSubmitFrameDescription2 frameDesc = {};
+ frameDesc.SwapInterval = 2;
+ frameDesc.FrameIndex = presentIndex;
+ frameDesc.DisplayTime = displayTime;
+ frameDesc.LayerCount = 1;
+ frameDesc.Layers = &layerHeader;
+ vrapi_SubmitFrame2(session, &frameDesc);
+ ++presentIndex;
+ }
+
+ ovrTracking2 beginFrame() {
+ displayTime = vrapi_GetPredictedDisplayTime(session, presentIndex);
+ return vrapi_GetPredictedTracking2(session, displayTime);
+ }
+};
+
+static VrSurface SURFACE;
+
+bool VrHandler::vrActive() const {
+ return SURFACE.session != nullptr;
+}
+
+void VrHandler::setHandler(VrHandler* handler) {
+ SURFACE.setHandler(handler);
+}
+
+void VrHandler::pollTask() {
+ SURFACE.pollTask();
+}
+
+void VrHandler::makeCurrent() {
+ if (!SURFACE.vrglContext.makeCurrent()) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ", "Failed to make GL current");
+ }
+}
+
+void VrHandler::doneCurrent() {
+ SURFACE.vrglContext.doneCurrent();
+}
+
+uint32_t VrHandler::currentPresentIndex() const {
+ return SURFACE.presentIndex;
+}
+
+ovrTracking2 VrHandler::beginFrame() {
+ return SURFACE.beginFrame();
+}
+
+void VrHandler::presentFrame(uint32_t sourceTexture, const glm::uvec2 &sourceSize, const ovrTracking2& tracking) const {
+ SURFACE.presentFrame(sourceTexture, sourceSize, tracking);
+}
+
+bool VrHandler::withOvrJava(const OvrJavaTask& task) {
+ SURFACE.withEnv([&](JNIEnv* env){
+ ovrJava java{ SURFACE.vm, env, SURFACE.oculusActivity };
+ task(&java);
+ });
+ return true;
+}
+
+bool VrHandler::withOvrMobile(const OvrMobileTask &task) {
+ auto sessionTask = [&]()->bool{
+ if (!SURFACE.session) {
+ return false;
+ }
+ task(SURFACE.session);
+ return true;
+ };
+
+ if (isRenderThread) {
+ return sessionTask();
+ }
+
+ bool result = false;
+ SURFACE.withLock([&]{
+ result = sessionTask();
+ });
+ return result;
+}
+
+
+void VrHandler::initVr(const char* appId) {
+ withOvrJava([&](const ovrJava* java){
+ ovrInitParms initParms = vrapi_DefaultInitParms(java);
+ initParms.GraphicsAPI = VRAPI_GRAPHICS_API_OPENGL_ES_3;
+ if (vrapi_Initialize(&initParms) != VRAPI_INITIALIZE_SUCCESS) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "Failed vrapi init");
+ }
+ });
+
+ // if (appId) {
+ // auto platformInitResult = ovr_PlatformInitializeAndroid(appId, activity.object(), env);
+ // if (ovrPlatformInitialize_Success != platformInitResult) {
+ // __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "Failed ovr platform init");
+ // }
+ // }
+}
+
+void VrHandler::shutdownVr() {
+ vrapi_Shutdown();
+}
+
+extern "C" {
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *, void *) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ", "oculusMobile::JNI_OnLoad");
+ return JNI_VERSION_1_6;
+}
+
+JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnCreate(JNIEnv* env, jobject obj) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__);
+ SURFACE.onCreate(env, obj);
+}
+
+JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnDestroy(JNIEnv*, jclass) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__);
+}
+
+JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnResume(JNIEnv*, jclass) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__);
+ SURFACE.setResumed(true);
+}
+
+JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnPause(JNIEnv*, jclass) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__);
+ SURFACE.setResumed(false);
+}
+
+JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnSurfaceChanged(JNIEnv* env, jclass, jobject surface) {
+ __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__);
+ SURFACE.setNativeWindow(surface ? ANativeWindow_fromSurface( env, surface ) : nullptr);
+}
+
+} // extern "C"
diff --git a/libraries/oculusMobile/src/ovr/VrHandler.h b/libraries/oculusMobile/src/ovr/VrHandler.h
new file mode 100644
index 0000000000..3c36ee8626
--- /dev/null
+++ b/libraries/oculusMobile/src/ovr/VrHandler.h
@@ -0,0 +1,47 @@
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+
+#include
+#include
+#include
+
+#include "TaskQueue.h"
+
+typedef struct ovrMobile ovrMobile;
+namespace ovr {
+
+class VrHandler {
+public:
+ using HandlerTask = std::function;
+ using OvrMobileTask = std::function;
+ using OvrJavaTask = std::function;
+ static void setHandler(VrHandler* handler);
+ static bool withOvrMobile(const OvrMobileTask& task);
+
+protected:
+ static void initVr(const char* appId = nullptr);
+ static void shutdownVr();
+ static bool withOvrJava(const OvrJavaTask& task);
+
+ uint32_t currentPresentIndex() const;
+ ovrTracking2 beginFrame();
+ void presentFrame(uint32_t textureId, const glm::uvec2& size, const ovrTracking2& tracking) const;
+
+ bool vrActive() const;
+ void pollTask();
+ void makeCurrent();
+ void doneCurrent();
+};
+
+}
+
+
+
+
+
diff --git a/libraries/oculusMobilePlugin/CMakeLists.txt b/libraries/oculusMobilePlugin/CMakeLists.txt
new file mode 100644
index 0000000000..b404c51f02
--- /dev/null
+++ b/libraries/oculusMobilePlugin/CMakeLists.txt
@@ -0,0 +1,29 @@
+#
+# Created by Bradley Austin Davis on 2018/11/15
+# Copyright 2013-2018 High Fidelity, Inc.
+#
+# Distributed under the Apache License, Version 2.0.
+# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
+#
+
+if (ANDROID)
+ set(TARGET_NAME oculusMobilePlugin)
+ setup_hifi_library(AndroidExtras Multimedia)
+
+ # if we were passed an Oculus App ID for entitlement checks, send that along
+ if (DEFINED ENV{OCULUS_APP_ID})
+ target_compile_definitions(${TARGET_NAME} -DOCULUS_APP_ID="$ENV{OCULUS_APP_ID}")
+ endif ()
+
+ link_hifi_libraries(
+ shared task gl shaders gpu controllers ui qml
+ plugins ui-plugins display-plugins input-plugins
+ audio-client networking render-utils
+ render graphics
+ oculusMobile
+ ${PLATFORM_GL_BACKEND}
+ )
+
+ include_hifi_library_headers(octree)
+ target_oculus_mobile()
+endif()
diff --git a/libraries/oculusMobilePlugin/src/Logging.cpp b/libraries/oculusMobilePlugin/src/Logging.cpp
new file mode 100644
index 0000000000..3d8628b0cb
--- /dev/null
+++ b/libraries/oculusMobilePlugin/src/Logging.cpp
@@ -0,0 +1,4 @@
+#include "Logging.h"
+
+Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display")
+Q_LOGGING_CATEGORY(oculusLog, "hifi.plugins.display.oculus")
diff --git a/libraries/oculusMobilePlugin/src/Logging.h b/libraries/oculusMobilePlugin/src/Logging.h
new file mode 100644
index 0000000000..066712ef6a
--- /dev/null
+++ b/libraries/oculusMobilePlugin/src/Logging.h
@@ -0,0 +1,13 @@
+//
+// Created by Bradley Austin Davis on 2018/11/20
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+#pragma once
+
+#include
+
+Q_DECLARE_LOGGING_CATEGORY(displayplugins)
+Q_DECLARE_LOGGING_CATEGORY(oculusLog)
diff --git a/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp
new file mode 100644
index 0000000000..8de563ee4c
--- /dev/null
+++ b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.cpp
@@ -0,0 +1,694 @@
+//
+// Created by Bradley Austin Davis on 2018/11/15
+// Copyright 2013-2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "OculusMobileControllerManager.h"
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "Logging.h"
+#include
+
+const char* OculusMobileControllerManager::NAME = "Oculus";
+const quint64 LOST_TRACKING_DELAY = 3000000;
+
+namespace ovr {
+
+ controller::Pose toControllerPose(ovrHandedness hand, const ovrRigidBodyPosef& handPose) {
+ // When the sensor-to-world rotation is identity the coordinate axes look like this:
+ //
+ // user
+ // forward
+ // -z
+ // |
+ // y| user
+ // y o----x right
+ // o-----x user
+ // | up
+ // |
+ // z
+ //
+ // Rift
+
+ // From ABOVE the hand canonical axes looks like this:
+ //
+ // | | | | y | | | |
+ // | | | | | | | | |
+ // | | | | |
+ // |left | / x---- + \ |right|
+ // | _/ z \_ |
+ // | | | |
+ // | | | |
+ //
+
+ // So when the user is in Rift space facing the -zAxis with hands outstretched and palms down
+ // the rotation to align the Touch axes with those of the hands is:
+ //
+ // touchToHand = halfTurnAboutY * quaterTurnAboutX
+
+ // Due to how the Touch controllers fit into the palm there is an offset that is different for each hand.
+ // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that
+ // the combination (measurement * offset) is identity at this orientation.
+ //
+ // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down)
+ //
+ // An approximate offset for the Touch can be obtained by inspection:
+ //
+ // Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis))
+ //
+ // So the full equation is:
+ //
+ // Q = combinedMeasurement * touchToHand
+ //
+ // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX)
+ //
+ // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX)
+ static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
+ static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X);
+ static const glm::quat touchToHand = yFlip * quarterX;
+
+ static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z);
+ static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z);
+
+ static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ) * touchToHand;
+ static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand;
+
+ static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
+ static const glm::vec3 CONTROLLER_OFFSET =
+ glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f);
+ static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
+ static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
+
+ auto translationOffset = (hand == VRAPI_HAND_LEFT ? leftTranslationOffset : rightTranslationOffset);
+ auto rotationOffset = (hand == VRAPI_HAND_LEFT ? leftRotationOffset : rightRotationOffset);
+
+ glm::quat rotation = toGlm(handPose.Pose.Orientation);
+
+ controller::Pose pose;
+ pose.translation = toGlm(handPose.Pose.Position);
+ pose.translation += rotation * translationOffset;
+ pose.rotation = rotation * rotationOffset;
+ pose.angularVelocity = rotation * toGlm(handPose.AngularVelocity);
+ pose.velocity = toGlm(handPose.LinearVelocity);
+ pose.valid = true;
+ return pose;
+ }
+
+ controller::Pose toControllerPose(ovrHandedness hand,
+ const ovrRigidBodyPosef& handPose,
+ const ovrRigidBodyPosef& lastHandPose) {
+ static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
+ static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X);
+ static const glm::quat touchToHand = yFlip * quarterX;
+
+ static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z);
+ static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z);
+
+ static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ) * touchToHand;
+ static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand;
+
+ static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
+ static const glm::vec3 CONTROLLER_OFFSET =
+ glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f);
+ static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
+ static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
+
+ auto translationOffset = (hand == VRAPI_HAND_LEFT ? leftTranslationOffset : rightTranslationOffset);
+ auto rotationOffset = (hand == VRAPI_HAND_LEFT ? leftRotationOffset : rightRotationOffset);
+
+ glm::quat rotation = toGlm(handPose.Pose.Orientation);
+
+ controller::Pose pose;
+ pose.translation = toGlm(lastHandPose.Pose.Position);
+ pose.translation += rotation * translationOffset;
+ pose.rotation = rotation * rotationOffset;
+ pose.angularVelocity = toGlm(lastHandPose.AngularVelocity);
+ pose.velocity = toGlm(lastHandPose.LinearVelocity);
+ pose.valid = true;
+ return pose;
+ }
+
+}
+
+
+class OculusMobileInputDevice : public controller::InputDevice {
+ friend class OculusMobileControllerManager;
+public:
+ using Pointer = std::shared_ptr;
+ static Pointer check(ovrMobile* session);
+
+ OculusMobileInputDevice(ovrMobile* session, const std::vector& devicesCaps);
+ void updateHands(ovrMobile* session);
+
+ controller::Input::NamedVector getAvailableInputs() const override;
+ QString getDefaultMappingConfig() const override;
+ void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;
+ void focusOutEvent() override;
+ bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override;
+
+private:
+ void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData,
+ ovrHandedness hand, const ovrRigidBodyPosef& handPose);
+ void handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData,
+ ovrHandedness hand, const ovrRigidBodyPosef& handPose);
+ void handleHeadPose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData,
+ const ovrRigidBodyPosef& headPose);
+
+ // perform an action when the TouchDevice mutex is acquired.
+ using Locker = std::unique_lock;
+
+ template
+ void withLock(F&& f) { Locker locker(_lock); f(); }
+
+ mutable std::recursive_mutex _lock;
+ ovrTracking2 _headTracking;
+ struct HandData {
+ HandData() {
+ state.Header.ControllerType =ovrControllerType_TrackedRemote;
+ }
+
+ float hapticDuration { 0.0f };
+ float hapticStrength { 0.0f };
+ bool valid{ false };
+ bool lostTracking{ false };
+ quint64 regainTrackingDeadline;
+ ovrRigidBodyPosef lastPose;
+ ovrInputTrackedRemoteCapabilities caps;
+ ovrInputStateTrackedRemote state;
+ ovrResult stateResult{ ovrError_NotInitialized };
+ ovrTracking tracking;
+ ovrResult trackingResult{ ovrError_NotInitialized };
+ bool setHapticFeedback(float strength, float duration) {
+#if 0
+ bool success = true;
+ bool sessionSuccess = ovr::VrHandler::withOvrMobile([&](ovrMobile* session){
+ if (strength == 0.0f) {
+ hapticStrength = 0.0f;
+ hapticDuration = 0.0f;
+ } else {
+ hapticStrength = (duration > hapticDuration) ? strength : hapticStrength;
+ if (vrapi_SetHapticVibrationSimple(session, caps.Header.DeviceID, hapticStrength) != ovrSuccess) {
+ success = false;
+ }
+ hapticDuration = std::max(duration, hapticDuration);
+ }
+ });
+ return success && sessionSuccess;
+#else
+ return true;
+#endif
+ }
+
+ void stopHapticPulse() {
+ ovr::VrHandler::withOvrMobile([&](ovrMobile* session){
+ vrapi_SetHapticVibrationSimple(session, caps.Header.DeviceID, 0.0f);
+ });
+ }
+
+ bool isValid() const {
+ return (stateResult == ovrSuccess) && (trackingResult == ovrSuccess);
+ }
+
+ void update(ovrMobile* session, double time = 0.0) {
+ const auto& deviceId = caps.Header.DeviceID;
+ stateResult = vrapi_GetCurrentInputState(session, deviceId, &state.Header);
+ trackingResult = vrapi_GetInputTrackingState(session, deviceId, 0.0, &tracking);
+ }
+ };
+ std::array _hands;
+};
+
+OculusMobileInputDevice::Pointer OculusMobileInputDevice::check(ovrMobile *session) {
+ Pointer result;
+
+ std::vector devicesCaps;
+ {
+ uint32_t deviceIndex { 0 };
+ ovrInputCapabilityHeader capsHeader;
+ while (vrapi_EnumerateInputDevices(session, deviceIndex, &capsHeader) >= 0) {
+ if (capsHeader.Type == ovrControllerType_TrackedRemote) {
+ ovrInputTrackedRemoteCapabilities caps;
+ caps.Header = capsHeader;
+ vrapi_GetInputDeviceCapabilities(session, &caps.Header);
+ devicesCaps.push_back(caps);
+ }
+ ++deviceIndex;
+ }
+ }
+ if (!devicesCaps.empty()) {
+ result.reset(new OculusMobileInputDevice(session, devicesCaps));
+ }
+ return result;
+}
+
+static OculusMobileInputDevice::Pointer oculusMobileControllers;
+
+bool OculusMobileControllerManager::isHandController() const {
+ return oculusMobileControllers.operator bool();
+}
+
+bool OculusMobileControllerManager::isSupported() const {
+ return true;
+}
+
+bool OculusMobileControllerManager::activate() {
+ InputPlugin::activate();
+ checkForConnectedDevices();
+ return true;
+}
+
+void OculusMobileControllerManager::checkForConnectedDevices() {
+ if (oculusMobileControllers) {
+ return;
+ }
+
+ ovr::VrHandler::withOvrMobile([&](ovrMobile* session){
+ oculusMobileControllers = OculusMobileInputDevice::check(session);
+ if (oculusMobileControllers) {
+ auto userInputMapper = DependencyManager::get();
+ userInputMapper->registerDevice(oculusMobileControllers);
+ }
+ });
+}
+
+void OculusMobileControllerManager::deactivate() {
+ InputPlugin::deactivate();
+
+ // unregister with UserInputMapper
+ auto userInputMapper = DependencyManager::get();
+ if (oculusMobileControllers) {
+ userInputMapper->removeDevice(oculusMobileControllers->getDeviceID());
+ oculusMobileControllers.reset();
+ }
+}
+
+void OculusMobileControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
+ PerformanceTimer perfTimer("OculusMobileInputDevice::update");
+
+ checkForConnectedDevices();
+
+ if (!oculusMobileControllers) {
+ return;
+ }
+
+ bool updated = ovr::VrHandler::withOvrMobile([&](ovrMobile* session){
+ oculusMobileControllers->updateHands(session);
+ });
+
+ if (updated) {
+ oculusMobileControllers->update(deltaTime, inputCalibrationData);
+ }
+}
+
+void OculusMobileControllerManager::pluginFocusOutEvent() {
+ if (oculusMobileControllers) {
+ oculusMobileControllers->focusOutEvent();
+ }
+}
+
+QStringList OculusMobileControllerManager::getSubdeviceNames() {
+ QStringList devices;
+ if (oculusMobileControllers) {
+ devices << oculusMobileControllers->getName();
+ }
+ return devices;
+}
+
+using namespace controller;
+
+static const std::vector> BUTTON_MAP { {
+ { ovrButton_Up, DU },
+ { ovrButton_Down, DD },
+ { ovrButton_Left, DL },
+ { ovrButton_Right, DR },
+ { ovrButton_Enter, START },
+ { ovrButton_Back, BACK },
+ { ovrButton_X, X },
+ { ovrButton_Y, Y },
+ { ovrButton_A, A },
+ { ovrButton_B, B },
+ { ovrButton_LThumb, LS },
+ { ovrButton_RThumb, RS },
+ //{ ovrButton_LShoulder, LB },
+ //{ ovrButton_RShoulder, RB },
+} };
+
+static const std::vector> LEFT_TOUCH_MAP { {
+ { ovrTouch_X, LEFT_PRIMARY_THUMB_TOUCH },
+ { ovrTouch_Y, LEFT_SECONDARY_THUMB_TOUCH },
+ { ovrTouch_LThumb, LS_TOUCH },
+ { ovrTouch_ThumbUp, LEFT_THUMB_UP },
+ { ovrTouch_IndexTrigger, LEFT_PRIMARY_INDEX_TOUCH },
+ { ovrTouch_IndexPointing, LEFT_INDEX_POINT },
+} };
+
+
+static const std::vector> RIGHT_TOUCH_MAP { {
+ { ovrTouch_A, RIGHT_PRIMARY_THUMB_TOUCH },
+ { ovrTouch_B, RIGHT_SECONDARY_THUMB_TOUCH },
+ { ovrTouch_RThumb, RS_TOUCH },
+ { ovrTouch_ThumbUp, RIGHT_THUMB_UP },
+ { ovrTouch_IndexTrigger, RIGHT_PRIMARY_INDEX_TOUCH },
+ { ovrTouch_IndexPointing, RIGHT_INDEX_POINT },
+} };
+
+void OculusMobileInputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
+ _buttonPressedMap.clear();
+
+ int numTrackedControllers = 0;
+ quint64 currentTime = usecTimestampNow();
+ handleHeadPose(deltaTime, inputCalibrationData, _headTracking.HeadPose);
+
+ static const auto REQUIRED_HAND_STATUS = VRAPI_TRACKING_STATUS_ORIENTATION_TRACKED | VRAPI_TRACKING_STATUS_POSITION_TRACKED;
+ ovr::for_each_hand([&](ovrHandedness hand) {
+ size_t handIndex = (hand == VRAPI_HAND_LEFT) ? 0 : 1;
+ int controller = (hand == VRAPI_HAND_LEFT) ? controller::LEFT_HAND : controller::RIGHT_HAND;
+ auto& handData = _hands[handIndex];
+ const auto& tracking = handData.tracking;
+ ++numTrackedControllers;
+
+ // Disable hand tracking while in Oculus Dash (Dash renders it's own hands)
+// if (!hasInputFocus) {
+// _poseStateMap.erase(controller);
+// _poseStateMap[controller].valid = false;
+// return;
+// }
+
+ if (REQUIRED_HAND_STATUS == (tracking.Status & REQUIRED_HAND_STATUS)) {
+ _poseStateMap.erase(controller);
+ handlePose(deltaTime, inputCalibrationData, hand, tracking.HeadPose);
+ handData.lostTracking = false;
+ handData.lastPose = tracking.HeadPose;
+ return;
+ }
+
+ if (handData.lostTracking) {
+ if (currentTime > handData.regainTrackingDeadline) {
+ _poseStateMap.erase(controller);
+ _poseStateMap[controller].valid = false;
+ return;
+ }
+ } else {
+ quint64 deadlineToRegainTracking = currentTime + LOST_TRACKING_DELAY;
+ handData.regainTrackingDeadline = deadlineToRegainTracking;
+ handData.lostTracking = true;
+ }
+ handleRotationForUntrackedHand(inputCalibrationData, hand, tracking.HeadPose);
+ });
+
+
+ using namespace controller;
+ // Axes
+ {
+ const auto& inputState = _hands[0].state;
+ _axisStateMap[LX].value = inputState.JoystickNoDeadZone.x;
+ _axisStateMap[LY].value = inputState.JoystickNoDeadZone.y;
+ _axisStateMap[LT].value = inputState.IndexTrigger;
+ _axisStateMap[LEFT_GRIP].value = inputState.GripTrigger;
+ for (const auto& pair : BUTTON_MAP) {
+ if (inputState.Buttons & pair.first) {
+ _buttonPressedMap.insert(pair.second);
+ qDebug()<<"AAAA:BUTTON PRESSED "<The Controller.Hardware.OculusTouch
object has properties representing Oculus Rift. The property values are
+ * integer IDs, uniquely identifying each output. Read-only. These can be mapped to actions or functions or
+ * Controller.Standard
items in a {@link RouteObject} mapping.
+ *
+ *
+ * Property | Type | Data | Description |
+ *
+ *
+ * Buttons |
+ * A | number | number | "A" button pressed. |
+ * B | number | number | "B" button pressed. |
+ * X | number | number | "X" button pressed. |
+ * Y | number | number | "Y" button pressed. |
+ * LeftApplicationMenu | number | number | Left application menu button pressed.
+ * |
+ * RightApplicationMenu | number | number | Right application menu button pressed.
+ * |
+ * Sticks |
+ * LX | number | number | Left stick x-axis scale. |
+ * LY | number | number | Left stick y-axis scale. |
+ * RX | number | number | Right stick x-axis scale. |
+ * RY | number | number | Right stick y-axis scale. |
+ * LS | number | number | Left stick button pressed. |
+ * RS | number | number | Right stick button pressed. |
+ * LSTouch | number | number | Left stick is touched. |
+ * RSTouch | number | number | Right stick is touched. |
+ * Triggers |
+ * LT | number | number | Left trigger scale. |
+ * RT | number | number | Right trigger scale. |
+ * LeftGrip | number | number | Left grip scale. |
+ * RightGrip | number | number | Right grip scale. |
+ * Finger Abstractions |
+ * LeftPrimaryThumbTouch | number | number | Left thumb touching primary thumb
+ * button. |
+ * LeftSecondaryThumbTouch | number | number | Left thumb touching secondary thumb
+ * button. |
+ * LeftThumbUp | number | number | Left thumb not touching primary or secondary
+ * thumb buttons. |
+ * RightPrimaryThumbTouch | number | number | Right thumb touching primary thumb
+ * button. |
+ * RightSecondaryThumbTouch | number | number | Right thumb touching secondary thumb
+ * button. |
+ * RightThumbUp | number | number | Right thumb not touching primary or secondary
+ * thumb buttons. |
+ * LeftPrimaryIndexTouch | number | number | Left index finger is touching primary
+ * index finger control. |
+ * LeftIndexPoint | number | number | Left index finger is pointing, not touching
+ * primary or secondary index finger controls. |
+ * RightPrimaryIndexTouch | number | number | Right index finger is touching primary
+ * index finger control. |
+ * RightIndexPoint | number | number | Right index finger is pointing, not touching
+ * primary or secondary index finger controls. |
+ * Avatar Skeleton |
+ * Head | number | {@link Pose} | Head pose. |
+ * LeftHand | number | {@link Pose} | Left hand pose. |
+ * RightHand | number | {@link Pose} | right hand pose. |
+ *
+ *
+ * @typedef {object} Controller.Hardware-OculusTouch
+ */
+controller::Input::NamedVector OculusMobileInputDevice::getAvailableInputs() const {
+ using namespace controller;
+ QVector availableInputs{
+ // buttons
+ makePair(A, "A"),
+ makePair(B, "B"),
+ makePair(X, "X"),
+ makePair(Y, "Y"),
+
+ // trackpad analogs
+ makePair(LX, "LX"),
+ makePair(LY, "LY"),
+ makePair(RX, "RX"),
+ makePair(RY, "RY"),
+
+ // triggers
+ makePair(LT, "LT"),
+ makePair(RT, "RT"),
+
+ // trigger buttons
+ //makePair(LB, "LB"),
+ //makePair(RB, "RB"),
+
+ // side grip triggers
+ makePair(LEFT_GRIP, "LeftGrip"),
+ makePair(RIGHT_GRIP, "RightGrip"),
+
+ // joystick buttons
+ makePair(LS, "LS"),
+ makePair(RS, "RS"),
+
+ makePair(LEFT_HAND, "LeftHand"),
+ makePair(RIGHT_HAND, "RightHand"),
+ makePair(HEAD, "Head"),
+
+ makePair(LEFT_PRIMARY_THUMB_TOUCH, "LeftPrimaryThumbTouch"),
+ makePair(LEFT_SECONDARY_THUMB_TOUCH, "LeftSecondaryThumbTouch"),
+ makePair(RIGHT_PRIMARY_THUMB_TOUCH, "RightPrimaryThumbTouch"),
+ makePair(RIGHT_SECONDARY_THUMB_TOUCH, "RightSecondaryThumbTouch"),
+ makePair(LEFT_PRIMARY_INDEX_TOUCH, "LeftPrimaryIndexTouch"),
+ makePair(RIGHT_PRIMARY_INDEX_TOUCH, "RightPrimaryIndexTouch"),
+ makePair(LS_TOUCH, "LSTouch"),
+ makePair(RS_TOUCH, "RSTouch"),
+ makePair(LEFT_THUMB_UP, "LeftThumbUp"),
+ makePair(RIGHT_THUMB_UP, "RightThumbUp"),
+ makePair(LEFT_INDEX_POINT, "LeftIndexPoint"),
+ makePair(RIGHT_INDEX_POINT, "RightIndexPoint"),
+
+ makePair(BACK, "LeftApplicationMenu"),
+ makePair(START, "RightApplicationMenu"),
+ };
+ return availableInputs;
+}
+
+OculusMobileInputDevice::OculusMobileInputDevice(ovrMobile* session, const std::vector& devicesCaps) : controller::InputDevice("OculusTouch") {
+ qWarning() << "QQQ" << __FUNCTION__ << "Found " << devicesCaps.size() << "devices";
+ for (const auto& deviceCaps : devicesCaps) {
+ size_t handIndex = -1;
+ if (deviceCaps.ControllerCapabilities & ovrControllerCaps_LeftHand) {
+ handIndex = 0;
+ } else if (deviceCaps.ControllerCapabilities & ovrControllerCaps_RightHand) {
+ handIndex = 1;
+ } else {
+ continue;
+ }
+ HandData& handData = _hands[handIndex];
+ handData.state.Header.ControllerType = ovrControllerType_TrackedRemote;
+ handData.valid = true;
+ handData.caps = deviceCaps;
+ handData.update(session);
+ }
+}
+
+void OculusMobileInputDevice::updateHands(ovrMobile* session) {
+ _headTracking = vrapi_GetPredictedTracking2(session, 0.0);
+ for (auto& hand : _hands) {
+ hand.update(session);
+ }
+}
+
+QString OculusMobileInputDevice::getDefaultMappingConfig() const {
+ static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/oculus_touch.json";
+ return MAPPING_JSON;
+}
+
+// TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class
+InputPluginList getInputPlugins() {
+ InputPlugin* PLUGIN_POOL[] = {
+ new KeyboardMouseDevice(),
+ new OculusMobileControllerManager(),
+ nullptr
+ };
+
+ InputPluginList result;
+ for (int i = 0; PLUGIN_POOL[i]; ++i) {
+ InputPlugin* plugin = PLUGIN_POOL[i];
+ if (plugin->isSupported()) {
+ result.push_back(InputPluginPointer(plugin));
+ }
+ }
+ return result;
+}
\ No newline at end of file
diff --git a/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.h b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.h
new file mode 100644
index 0000000000..43ead8d6a2
--- /dev/null
+++ b/libraries/oculusMobilePlugin/src/OculusMobileControllerManager.h
@@ -0,0 +1,43 @@
+//
+// Created by Bradley Austin Davis on 2016/03/04
+// Copyright 2013-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
+//
+
+#ifndef hifi__OculusMobileControllerManager
+#define hifi__OculusMobileControllerManager
+
+#include
+#include
+#include