mirror of
https://github.com/overte-org/overte.git
synced 2025-04-23 16:13:43 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into 21665
This commit is contained in:
commit
330b266ca8
148 changed files with 5343 additions and 917 deletions
.gitignoreBUILD_ANDROID.mdCMakeLists.txt
android
app
CMakeLists.txtbuild.gradle
build.gradlebuild_recipes.mdsrc/main
AndroidManifest.xml
cpp
java
io/highfidelity/gvrinterface
org
qtproject/qt5/android/bindings
saintandreas/testapp
res/drawable
cmake/macros
interface
CMakeLists.txt
resources
src
libraries
avatars-renderer
controllers/src/controllers/impl
display-plugins
entities-renderer
entities/src
fbx/src
gl/src/gl
midi/src
networking/src
physics/src
plugins/src/plugins
pointers/src
render-utils/src
CauterizedMeshPartPayload.cppDeferredLightingEffect.cppMeshPartPayload.cppModel.cppModel.hZoneRenderer.cpp
render/src/render
script-engine/src
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -18,6 +18,9 @@ local.properties
|
|||
android/gradle*
|
||||
android/.gradle
|
||||
android/app/src/main/jniLibs
|
||||
android/app/libs
|
||||
android/app/src/main/res/values/libs.xml
|
||||
android/app/src/main/assets/bundled
|
||||
|
||||
# VSCode
|
||||
# List taken from Github Global Ignores master@435c4d92
|
||||
|
|
|
@ -2,9 +2,9 @@ Please read the [general build guide](BUILD.md) for information on building othe
|
|||
|
||||
# Dependencies
|
||||
|
||||
*Currently Android building is only supported on 64 bit Linux host environments*
|
||||
Building is currently supported on OSX, Windows and Linux platforms, but developers intending to do work on the library dependencies are strongly urged to use 64 bit Linux as a build platform
|
||||
|
||||
You will need the following tools to build our Android targets.
|
||||
You will need the following tools to build Android targets.
|
||||
|
||||
* [Gradle](https://gradle.org/install/)
|
||||
* [Android Studio](https://developer.android.com/studio/index.html)
|
||||
|
|
|
@ -39,6 +39,19 @@ else()
|
|||
option(BUILD_TESTS "Build tests" ON)
|
||||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
|
||||
set(PLATFORM_GL_BACKEND gpu-gles)
|
||||
else ()
|
||||
set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets)
|
||||
set(PLATFORM_GL_BACKEND gpu-gl)
|
||||
endif ()
|
||||
|
||||
foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS})
|
||||
list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}")
|
||||
endforeach()
|
||||
|
||||
|
||||
option(BUILD_INSTALLER "Build installer" ON)
|
||||
|
||||
MESSAGE(STATUS "Build server: " ${BUILD_SERVER})
|
||||
|
|
|
@ -1,10 +1,26 @@
|
|||
set(TARGET_NAME native-lib)
|
||||
setup_hifi_library()
|
||||
link_hifi_libraries(shared networking gl gpu gpu-gles image fbx render-utils physics)
|
||||
setup_hifi_library(Gui Qml Quick)
|
||||
|
||||
# Minimal dependencies for testing UI compositing
|
||||
#link_hifi_libraries(shared networking gl ui)
|
||||
|
||||
link_hifi_libraries(
|
||||
shared networking octree
|
||||
script-engine recording trackers
|
||||
gl ktx image gpu gpu-gles render render-utils
|
||||
physics
|
||||
audio audio-client
|
||||
ui midi controllers pointers
|
||||
model model-networking fbx animation
|
||||
entities entities-renderer
|
||||
avatars avatars-renderer
|
||||
ui-plugins input-plugins
|
||||
# display-plugins
|
||||
# auto-updater
|
||||
)
|
||||
|
||||
|
||||
target_link_libraries(native-lib android log m)
|
||||
|
||||
target_opengl()
|
||||
target_googlevr()
|
||||
|
||||
target_bullet()
|
||||
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
ext.RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0'
|
||||
ext.RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV'
|
||||
ext.BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : ''
|
||||
|
||||
android {
|
||||
compileSdkVersion 26
|
||||
defaultConfig {
|
||||
applicationId "org.saintandreas.testapp"
|
||||
applicationId "com.highfidelity.iface"
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 26
|
||||
versionCode 1
|
||||
|
@ -56,8 +53,8 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: "${project.rootDir}/libraries/jar", include: 'QtAndroid-bundled.jar')
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.google.vr:sdk-audio:1.80.0'
|
||||
compile 'com.google.vr:sdk-base:1.80.0'
|
||||
implementation 'com.google.vr:sdk-audio:1.80.0'
|
||||
implementation 'com.google.vr:sdk-base:1.80.0'
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
|
|
|
@ -1,28 +1,46 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.saintandreas.testapp">
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.highfidelity.gvrinterface">
|
||||
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="26" />
|
||||
<uses-feature android:glEsVersion="0x00030001" android:required="true" />
|
||||
<uses-feature android:glEsVersion="0x00030002" android:required="true" />
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true"/>
|
||||
<uses-feature android:name="android.hardware.sensor.gyroscope" android:required="true"/>
|
||||
|
||||
<application
|
||||
android:name="org.qtproject.qt5.android.bindings.QtApplication"
|
||||
android:hardwareAccelerated="true"
|
||||
android:allowBackup="true"
|
||||
android:screenOrientation="unspecified"
|
||||
android:theme="@style/NoSystemUI"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:launchMode="singleTop"
|
||||
android:roundIcon="@mipmap/ic_launcher_round">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:name=".InterfaceActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="landscape"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:resizeableActivity="false">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.app.lib_name" android:value="native-lib"/>
|
||||
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
|
||||
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
|
||||
<meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so:plugins/bearer/libqandroidbearer.so:lib/libQt5QuickParticles.so"/>
|
||||
<meta-data android:name="android.app.background_running" android:value="false"/>
|
||||
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
|
||||
<meta-data android:name="android.app.extract_android_style" android:value="full"/>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
19
android/app/src/main/cpp/+android/simple.qml
Normal file
19
android/app/src/main/cpp/+android/simple.qml
Normal file
|
@ -0,0 +1,19 @@
|
|||
import QtQuick 2.7
|
||||
import QtWebView 1.1
|
||||
|
||||
Rectangle {
|
||||
id: window
|
||||
anchors.fill: parent
|
||||
color: "red"
|
||||
ColorAnimation on color { from: "blue"; to: "yellow"; duration: 1000; loops: Animation.Infinite }
|
||||
|
||||
Text {
|
||||
text: "Hello"
|
||||
anchors.top: parent.top
|
||||
}
|
||||
WebView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
url: "http://doc.qt.io/qt-5/qml-qtwebview-webview.html"
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <vr/gvr/capi/include/gvr.h>
|
||||
|
||||
namespace googlevr {
|
||||
|
||||
// Convert a GVR matrix to GLM matrix
|
||||
glm::mat4 toGlm(const gvr::Mat4f &matrix) {
|
||||
glm::mat4 result;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
result[j][i] = matrix.m[i][j];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Given a field of view in degrees, compute the corresponding projection
|
||||
// matrix.
|
||||
glm::mat4 perspectiveMatrixFromView(const gvr::Rectf& fov, float z_near, float z_far) {
|
||||
const float x_left = -std::tan(fov.left * M_PI / 180.0f) * z_near;
|
||||
const float x_right = std::tan(fov.right * M_PI / 180.0f) * z_near;
|
||||
const float y_bottom = -std::tan(fov.bottom * M_PI / 180.0f) * z_near;
|
||||
const float y_top = std::tan(fov.top * M_PI / 180.0f) * z_near;
|
||||
const float Y = (2 * z_near) / (y_top - y_bottom);
|
||||
const float A = (x_right + x_left) / (x_right - x_left);
|
||||
const float B = (y_top + y_bottom) / (y_top - y_bottom);
|
||||
const float C = (z_near + z_far) / (z_near - z_far);
|
||||
const float D = (2 * z_near * z_far) / (z_near - z_far);
|
||||
|
||||
glm::mat4 result { 0 };
|
||||
result[2][0] = A;
|
||||
result[1][1] = Y;
|
||||
result[2][1] = B;
|
||||
result[2][2] = C;
|
||||
result[3][2] = D;
|
||||
result[2][3] = -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::quat toGlm(const gvr::ControllerQuat& q) {
|
||||
glm::quat result;
|
||||
result.w = q.qw;
|
||||
result.x = q.qx;
|
||||
result.y = q.qy;
|
||||
result.z = q.qz;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
153
android/app/src/main/cpp/main.cpp
Normal file
153
android/app/src/main/cpp/main.cpp
Normal file
|
@ -0,0 +1,153 @@
|
|||
#include <jni.h>
|
||||
|
||||
#include <android/log.h>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QScreen>
|
||||
#include <QtQml/QQmlEngine>
|
||||
#include <QtQml/QQmlFileSelector>
|
||||
#include <QtQuick/QQuickView>
|
||||
#include <QtGui/QOpenGLContext>
|
||||
#include <QtCore/QLoggingCategory>
|
||||
#include <QtWebView/QtWebView>
|
||||
|
||||
#include <gl/Config.h>
|
||||
#include <gl/OffscreenGLCanvas.h>
|
||||
#include <gl/GLWindow.h>
|
||||
|
||||
#include <ui/OffscreenQmlSurface.h>
|
||||
|
||||
Q_LOGGING_CATEGORY(gpugllogging, "hifi.gl")
|
||||
|
||||
bool checkGLError(const char* name) {
|
||||
GLenum error = glGetError();
|
||||
if (!error) {
|
||||
return false;
|
||||
} else {
|
||||
switch (error) {
|
||||
case GL_INVALID_ENUM:
|
||||
qCDebug(gpugllogging) << "GLBackend::" << name << ": An unacceptable value is specified for an enumerated argument.The offending command is ignored and has no other side effect than to set the error flag.";
|
||||
break;
|
||||
case GL_INVALID_VALUE:
|
||||
qCDebug(gpugllogging) << "GLBackend" << name << ": A numeric argument is out of range.The offending command is ignored and has no other side effect than to set the error flag";
|
||||
break;
|
||||
case GL_INVALID_OPERATION:
|
||||
qCDebug(gpugllogging) << "GLBackend" << name << ": The specified operation is not allowed in the current state.The offending command is ignored and has no other side effect than to set the error flag..";
|
||||
break;
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
||||
qCDebug(gpugllogging) << "GLBackend" << name << ": The framebuffer object is not complete.The offending command is ignored and has no other side effect than to set the error flag.";
|
||||
break;
|
||||
case GL_OUT_OF_MEMORY:
|
||||
qCDebug(gpugllogging) << "GLBackend" << name << ": There is not enough memory left to execute the command.The state of the GL is undefined, except for the state of the error flags, after this error is recorded.";
|
||||
break;
|
||||
default:
|
||||
qCDebug(gpugllogging) << "GLBackend" << name << ": Unknown error: " << error;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool checkGLErrorDebug(const char* name) {
|
||||
return checkGLError(name);
|
||||
}
|
||||
|
||||
|
||||
int QtMsgTypeToAndroidPriority(QtMsgType type) {
|
||||
int priority = ANDROID_LOG_UNKNOWN;
|
||||
switch (type) {
|
||||
case QtDebugMsg: priority = ANDROID_LOG_DEBUG; break;
|
||||
case QtWarningMsg: priority = ANDROID_LOG_WARN; break;
|
||||
case QtCriticalMsg: priority = ANDROID_LOG_ERROR; break;
|
||||
case QtFatalMsg: priority = ANDROID_LOG_FATAL; break;
|
||||
case QtInfoMsg: priority = ANDROID_LOG_INFO; break;
|
||||
default: break;
|
||||
}
|
||||
return priority;
|
||||
}
|
||||
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
|
||||
__android_log_write(QtMsgTypeToAndroidPriority(type), "Interface", message.toStdString().c_str());
|
||||
}
|
||||
|
||||
void qt_gl_set_global_share_context(QOpenGLContext *context);
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
qInstallMessageHandler(messageHandler);
|
||||
QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
|
||||
QGuiApplication app(argc,argv);
|
||||
app.setOrganizationName("QtProject");
|
||||
app.setOrganizationDomain("qt-project.org");
|
||||
app.setApplicationName(QFileInfo(app.applicationFilePath()).baseName());
|
||||
QtWebView::initialize();
|
||||
qputenv("QSG_RENDERER_DEBUG", (QStringList() << "render" << "build" << "change" << "upload" << "roots" << "dump").join(';').toUtf8());
|
||||
|
||||
OffscreenGLCanvas sharedCanvas;
|
||||
if (!sharedCanvas.create()) {
|
||||
qFatal("Unable to create primary offscreen context");
|
||||
}
|
||||
qt_gl_set_global_share_context(sharedCanvas.getContext());
|
||||
auto globalContext = QOpenGLContext::globalShareContext();
|
||||
|
||||
GLWindow window;
|
||||
window.create();
|
||||
window.setGeometry(qApp->primaryScreen()->availableGeometry());
|
||||
window.createContext(globalContext);
|
||||
if (!window.makeCurrent()) {
|
||||
qFatal("Unable to make primary window GL context current");
|
||||
}
|
||||
|
||||
GLuint fbo = 0;
|
||||
glGenFramebuffers(1, &fbo);
|
||||
|
||||
static const ivec2 offscreenSize { 640, 480 };
|
||||
|
||||
OffscreenQmlSurface::setSharedContext(sharedCanvas.getContext());
|
||||
OffscreenQmlSurface* qmlSurface = new OffscreenQmlSurface();
|
||||
qmlSurface->create();
|
||||
qmlSurface->resize(fromGlm(offscreenSize));
|
||||
qmlSurface->load("qrc:///simple.qml");
|
||||
qmlSurface->resume();
|
||||
|
||||
auto discardLambda = qmlSurface->getDiscardLambda();
|
||||
|
||||
window.showFullScreen();
|
||||
QTimer timer;
|
||||
timer.setInterval(10);
|
||||
timer.setSingleShot(false);
|
||||
OffscreenQmlSurface::TextureAndFence currentTextureAndFence;
|
||||
timer.connect(&timer, &QTimer::timeout, &app, [&]{
|
||||
window.makeCurrent();
|
||||
glClearColor(0, 1, 0, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
OffscreenQmlSurface::TextureAndFence newTextureAndFence;
|
||||
if (qmlSurface->fetchTexture(newTextureAndFence)) {
|
||||
if (currentTextureAndFence.first) {
|
||||
discardLambda(currentTextureAndFence.first, currentTextureAndFence.second);
|
||||
}
|
||||
currentTextureAndFence = newTextureAndFence;
|
||||
}
|
||||
checkGLErrorDebug(__FUNCTION__);
|
||||
|
||||
if (currentTextureAndFence.second) {
|
||||
glWaitSync((GLsync)currentTextureAndFence.second, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync((GLsync)currentTextureAndFence.second);
|
||||
currentTextureAndFence.second = nullptr;
|
||||
}
|
||||
|
||||
if (currentTextureAndFence.first) {
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
|
||||
glFramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, currentTextureAndFence.first, 0);
|
||||
glBlitFramebuffer(0, 0, offscreenSize.x, offscreenSize.y, 100, 100, offscreenSize.x + 100, offscreenSize.y + 100, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
window.swapBuffers();
|
||||
window.doneCurrent();
|
||||
});
|
||||
timer.start();
|
||||
return app.exec();
|
||||
}
|
6
android/app/src/main/cpp/main.qrc
Normal file
6
android/app/src/main/cpp/main.qrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>simple.qml</file>
|
||||
<file>+android/simple.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
|
@ -1,73 +0,0 @@
|
|||
#include <jni.h>
|
||||
|
||||
#include <android/log.h>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
int QtMsgTypeToAndroidPriority(QtMsgType type) {
|
||||
int priority = ANDROID_LOG_UNKNOWN;
|
||||
switch (type) {
|
||||
case QtDebugMsg: priority = ANDROID_LOG_DEBUG; break;
|
||||
case QtWarningMsg: priority = ANDROID_LOG_WARN; break;
|
||||
case QtCriticalMsg: priority = ANDROID_LOG_ERROR; break;
|
||||
case QtFatalMsg: priority = ANDROID_LOG_FATAL; break;
|
||||
case QtInfoMsg: priority = ANDROID_LOG_INFO; break;
|
||||
default: break;
|
||||
}
|
||||
return priority;
|
||||
}
|
||||
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
|
||||
__android_log_write(QtMsgTypeToAndroidPriority(type), "Interface", message.toStdString().c_str());
|
||||
}
|
||||
|
||||
static jlong toJni(NativeRenderer *renderer) {
|
||||
return reinterpret_cast<intptr_t>(renderer);
|
||||
}
|
||||
|
||||
static NativeRenderer *fromJni(jlong renderer) {
|
||||
return reinterpret_cast<NativeRenderer*>(renderer);
|
||||
}
|
||||
|
||||
#define JNI_METHOD(r, name) JNIEXPORT r JNICALL Java_org_saintandreas_testapp_MainActivity_##name
|
||||
|
||||
extern "C" {
|
||||
|
||||
JNI_METHOD(jlong, nativeCreateRenderer)
|
||||
(JNIEnv *env, jclass clazz, jobject class_loader, jobject android_context, jlong native_gvr_api) {
|
||||
qInstallMessageHandler(messageHandler);
|
||||
return toJni(new NativeRenderer());
|
||||
}
|
||||
|
||||
JNI_METHOD(void, nativeDestroyRenderer)
|
||||
(JNIEnv *env, jclass clazz, jlong renderer) {
|
||||
delete fromJni(renderer);
|
||||
}
|
||||
|
||||
JNI_METHOD(void, nativeInitializeGl)
|
||||
(JNIEnv *env, jobject obj, jlong renderer) {
|
||||
fromJni(renderer)->InitializeGl();
|
||||
}
|
||||
|
||||
JNI_METHOD(void, nativeDrawFrame)
|
||||
(JNIEnv *env, jobject obj, jlong renderer) {
|
||||
fromJni(renderer)->DrawFrame();
|
||||
}
|
||||
|
||||
JNI_METHOD(void, nativeOnTriggerEvent)
|
||||
(JNIEnv *env, jobject obj, jlong renderer) {
|
||||
fromJni(renderer)->OnTriggerEvent();
|
||||
}
|
||||
|
||||
JNI_METHOD(void, nativeOnPause)
|
||||
(JNIEnv *env, jobject obj, jlong renderer) {
|
||||
fromJni(renderer)->OnPause();
|
||||
}
|
||||
|
||||
JNI_METHOD(void, nativeOnResume)
|
||||
(JNIEnv *env, jobject obj, jlong renderer) {
|
||||
fromJni(renderer)->OnResume();
|
||||
}
|
||||
|
||||
} // extern "C"
|
|
@ -1,120 +0,0 @@
|
|||
#include "renderer.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include <gl/Config.h>
|
||||
#include <gl/GLShaders.h>
|
||||
|
||||
static const char *kSimepleVertexShader = R"glsl(#version 300 es
|
||||
#extension GL_OVR_multiview2 : enable
|
||||
|
||||
layout(num_views=2) in;
|
||||
|
||||
layout(location = 0) in vec4 a_Position;
|
||||
|
||||
out vec4 v_Color;
|
||||
|
||||
void main() {
|
||||
v_Color = vec4(a_Position.xyz, 1.0);
|
||||
gl_Position = vec4(a_Position.xyz, 1.0);
|
||||
}
|
||||
)glsl";
|
||||
|
||||
static const char *kPassthroughFragmentShader = R"glsl(#version 300 es
|
||||
precision mediump float;
|
||||
in vec4 v_Color;
|
||||
out vec4 FragColor;
|
||||
|
||||
void main() { FragColor = v_Color; }
|
||||
)glsl";
|
||||
|
||||
|
||||
int LoadGLShader(int type, const char *shadercode) {
|
||||
GLuint result = 0;
|
||||
std::string shaderError;
|
||||
static const std::string SHADER_DEFINES;
|
||||
if (!gl::compileShader(type, shadercode, SHADER_DEFINES, result, shaderError)) {
|
||||
qWarning() << "QQQ" << __FUNCTION__ << "Shader compile failure" << shaderError.c_str();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void CheckGLError(const char* label) {
|
||||
int gl_error = glGetError();
|
||||
if (gl_error != GL_NO_ERROR) {
|
||||
qWarning("GL error @ %s: %d", label, gl_error);
|
||||
// Crash immediately to make OpenGL errors obvious.
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
// Contains vertex, normal and other data.
|
||||
namespace triangle {
|
||||
static std::array<float, 9> TRIANGLE_VERTS {{
|
||||
-0.5f, -0.5f, 0.0f,
|
||||
0.5f, -0.5f, 0.0f,
|
||||
0.0f, 0.5f, 0.0f
|
||||
}};
|
||||
}
|
||||
|
||||
|
||||
void NativeRenderer::InitializeGl() {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
const uint32_t vertShader = LoadGLShader(GL_VERTEX_SHADER, kSimepleVertexShader);
|
||||
const uint32_t fragShader = LoadGLShader(GL_FRAGMENT_SHADER, kPassthroughFragmentShader);
|
||||
std::string error;
|
||||
_program = gl::compileProgram({ vertShader, fragShader }, error);
|
||||
CheckGLError("build program");
|
||||
|
||||
glGenBuffers(1, &_geometryBuffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _geometryBuffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 9, triangle::TRIANGLE_VERTS.data(), GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
CheckGLError("upload vertices");
|
||||
|
||||
glGenVertexArrays(1, &_vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _geometryBuffer);
|
||||
glBindVertexArray(_vao);
|
||||
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
|
||||
glEnableVertexAttribArray(0);
|
||||
glBindVertexArray(0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
CheckGLError("build vao ");
|
||||
}
|
||||
|
||||
|
||||
void NativeRenderer::DrawFrame() {
|
||||
auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now() - start);
|
||||
glm::vec3 v;
|
||||
v.r = (float) (now.count() % 1000) / 1000.0f;
|
||||
v.g = 1.0f - v.r;
|
||||
v.b = 1.0f;
|
||||
|
||||
glClearColor(v.r, v.g, v.b, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glUseProgram(_program);
|
||||
glBindVertexArray(_vao);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void NativeRenderer::OnTriggerEvent() {
|
||||
qDebug() << "QQQ" << __FUNCTION__;
|
||||
}
|
||||
|
||||
void NativeRenderer::OnPause() {
|
||||
qDebug() << "QQQ" << __FUNCTION__;
|
||||
}
|
||||
|
||||
void NativeRenderer::OnResume() {
|
||||
qDebug() << "QQQ" << __FUNCTION__;
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <array>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
class NativeRenderer {
|
||||
public:
|
||||
void InitializeGl();
|
||||
void DrawFrame();
|
||||
void OnTriggerEvent();
|
||||
void OnPause();
|
||||
void OnResume();
|
||||
|
||||
private:
|
||||
std::chrono::time_point<std::chrono::system_clock> start { std::chrono::system_clock::now() };
|
||||
|
||||
uint32_t _geometryBuffer { 0 };
|
||||
uint32_t _vao { 0 };
|
||||
uint32_t _program { 0 };
|
||||
};
|
10
android/app/src/main/cpp/simple.qml
Normal file
10
android/app/src/main/cpp/simple.qml
Normal file
|
@ -0,0 +1,10 @@
|
|||
import QtQuick 2.0
|
||||
|
||||
Rectangle {
|
||||
id: window
|
||||
width: 320
|
||||
height: 480
|
||||
focus: true
|
||||
color: "red"
|
||||
ColorAnimation on color { from: "red"; to: "yellow"; duration: 1000; loops: Animation.Infinite }
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,951 @@
|
|||
/*
|
||||
Copyright (c) 2016, BogDan Vatra <bogdan@kde.org>
|
||||
Contact: http://www.qt.io/licensing/
|
||||
|
||||
Commercial License Usage
|
||||
Licensees holding valid commercial Qt licenses may use this file in
|
||||
accordance with the commercial license agreement provided with the
|
||||
Software or, alternatively, in accordance with the terms contained in
|
||||
a written agreement between you and The Qt Company. For licensing terms
|
||||
and conditions see http://www.qt.io/terms-conditions. For further
|
||||
information use the contact form at http://www.qt.io/contact-us.
|
||||
|
||||
BSD License Usage
|
||||
Alternatively, this file may be used under the BSD license as follows:
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.qtproject.qt5.android.bindings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources.Theme;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ActionMode;
|
||||
import android.view.ActionMode.Callback;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class QtActivity extends Activity {
|
||||
public String APPLICATION_PARAMETERS = null;
|
||||
public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1";
|
||||
public final String[] QT_ANDROID_THEMES = new String[]{"Theme_Holo_Light"};
|
||||
public final String QT_ANDROID_DEFAULT_THEME = QT_ANDROID_THEMES[0]; // sets the default theme.
|
||||
private QtActivityLoader m_loader = new QtActivityLoader(this);
|
||||
|
||||
public QtActivity() {
|
||||
}
|
||||
|
||||
/////////////////////////// forward all notifications ////////////////////////////
|
||||
/////////////////////////// Super class calls ////////////////////////////////////
|
||||
/////////////// PLEASE DO NOT CHANGE THE FOLLOWING CODE //////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyEvent, event);
|
||||
} else {
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_dispatchKeyEvent(KeyEvent event) {
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.dispatchPopulateAccessibilityEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchPopulateAccessibilityEvent, event);
|
||||
} else {
|
||||
return super.dispatchPopulateAccessibilityEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
|
||||
return super_dispatchPopulateAccessibilityEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.dispatchTouchEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTouchEvent, ev);
|
||||
} else {
|
||||
return super.dispatchTouchEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_dispatchTouchEvent(MotionEvent event) {
|
||||
return super.dispatchTouchEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean dispatchTrackballEvent(MotionEvent ev) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.dispatchTrackballEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTrackballEvent, ev);
|
||||
} else {
|
||||
return super.dispatchTrackballEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_dispatchTrackballEvent(MotionEvent event) {
|
||||
return super.dispatchTrackballEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onActivityResult != null) {
|
||||
QtApplication.invokeDelegateMethod(QtApplication.onActivityResult, requestCode, resultCode, data);
|
||||
return;
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
public void super_onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
|
||||
if (!QtApplication.invokeDelegate(theme, resid, first).invoked) {
|
||||
super.onApplyThemeResource(theme, resid, first);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onApplyThemeResource(Theme theme, int resid, boolean first) {
|
||||
super.onApplyThemeResource(theme, resid, first);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@Override
|
||||
protected void onChildTitleChanged(Activity childActivity, CharSequence title) {
|
||||
if (!QtApplication.invokeDelegate(childActivity, title).invoked) {
|
||||
super.onChildTitleChanged(childActivity, title);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onChildTitleChanged(Activity childActivity, CharSequence title) {
|
||||
super.onChildTitleChanged(childActivity, title);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
if (!QtApplication.invokeDelegate(newConfig).invoked) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onContentChanged() {
|
||||
if (!QtApplication.invokeDelegate().invoked) {
|
||||
super.onContentChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onContentChanged() {
|
||||
super.onContentChanged();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(item);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onContextItemSelected(MenuItem item) {
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onContextMenuClosed(Menu menu) {
|
||||
if (!QtApplication.invokeDelegate(menu).invoked) {
|
||||
super.onContextMenuClosed(menu);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onContextMenuClosed(Menu menu) {
|
||||
super.onContextMenuClosed(menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
protected void onCreateHook(Bundle savedInstanceState) {
|
||||
m_loader.APPLICATION_PARAMETERS = APPLICATION_PARAMETERS;
|
||||
m_loader.ENVIRONMENT_VARIABLES = ENVIRONMENT_VARIABLES;
|
||||
m_loader.QT_ANDROID_THEMES = QT_ANDROID_THEMES;
|
||||
m_loader.QT_ANDROID_DEFAULT_THEME = QT_ANDROID_DEFAULT_THEME;
|
||||
m_loader.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
onCreateHook(savedInstanceState);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
if (!QtApplication.invokeDelegate(menu, v, menuInfo).invoked) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public CharSequence onCreateDescription() {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate();
|
||||
if (res.invoked) {
|
||||
return (CharSequence) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreateDescription();
|
||||
}
|
||||
}
|
||||
|
||||
public CharSequence super_onCreateDescription() {
|
||||
return super.onCreateDescription();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(id);
|
||||
if (res.invoked) {
|
||||
return (Dialog) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
}
|
||||
|
||||
public Dialog super_onCreateDialog(int id) {
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onCreateOptionsMenu(Menu menu) {
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onCreatePanelMenu(int featureId, Menu menu) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreatePanelMenu(featureId, menu);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onCreatePanelMenu(int featureId, Menu menu) {
|
||||
return super.onCreatePanelMenu(featureId, menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreatePanelView(int featureId) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId);
|
||||
if (res.invoked) {
|
||||
return (View) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreatePanelView(featureId);
|
||||
}
|
||||
}
|
||||
|
||||
public View super_onCreatePanelView(int featureId) {
|
||||
return super.onCreatePanelView(featureId);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(outBitmap, canvas);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreateThumbnail(outBitmap, canvas);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onCreateThumbnail(Bitmap outBitmap, Canvas canvas) {
|
||||
return super.onCreateThumbnail(outBitmap, canvas);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public View onCreateView(String name, Context context, AttributeSet attrs) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(name, context, attrs);
|
||||
if (res.invoked) {
|
||||
return (View) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreateView(name, context, attrs);
|
||||
}
|
||||
}
|
||||
|
||||
public View super_onCreateView(String name, Context context, AttributeSet attrs) {
|
||||
return super.onCreateView(name, context, attrs);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onKeyDown != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyDown, keyCode, event);
|
||||
} else {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onKeyDown(int keyCode, KeyEvent event) {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onKeyMultiple != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyMultiple, keyCode, repeatCount, event);
|
||||
} else {
|
||||
return super.onKeyMultiple(keyCode, repeatCount, event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
|
||||
return super.onKeyMultiple(keyCode, repeatCount, event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onKeyUp != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyUp, keyCode, event);
|
||||
} else {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onKeyUp(int keyCode, KeyEvent event) {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
if (!QtApplication.invokeDelegate().invoked) {
|
||||
super.onLowMemory();
|
||||
}
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, item);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onMenuItemSelected(featureId, item);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onMenuItemSelected(int featureId, MenuItem item) {
|
||||
return super.onMenuItemSelected(featureId, item);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onMenuOpened(int featureId, Menu menu) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onMenuOpened(featureId, menu);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onMenuOpened(int featureId, Menu menu) {
|
||||
return super.onMenuOpened(featureId, menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
if (!QtApplication.invokeDelegate(intent).invoked) {
|
||||
super.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(item);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onOptionsItemSelected(MenuItem item) {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onOptionsMenuClosed(Menu menu) {
|
||||
if (!QtApplication.invokeDelegate(menu).invoked) {
|
||||
super.onOptionsMenuClosed(menu);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onOptionsMenuClosed(Menu menu) {
|
||||
super.onOptionsMenuClosed(menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onPanelClosed(int featureId, Menu menu) {
|
||||
if (!QtApplication.invokeDelegate(featureId, menu).invoked) {
|
||||
super.onPanelClosed(featureId, menu);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onPanelClosed(int featureId, Menu menu) {
|
||||
super.onPanelClosed(featureId, menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
QtApplication.invokeDelegate(savedInstanceState);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onPostResume() {
|
||||
super.onPostResume();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialog(int id, Dialog dialog) {
|
||||
if (!QtApplication.invokeDelegate(id, dialog).invoked) {
|
||||
super.onPrepareDialog(id, dialog);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onPrepareDialog(int id, Dialog dialog) {
|
||||
super.onPrepareDialog(id, dialog);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onPrepareOptionsMenu(Menu menu) {
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onPreparePanel(int featureId, View view, Menu menu) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, view, menu);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onPreparePanel(featureId, view, menu);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onPreparePanel(int featureId, View view, Menu menu) {
|
||||
return super.onPreparePanel(featureId, view, menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onRestart() {
|
||||
super.onRestart();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
if (!QtApplication.invokeDelegate(savedInstanceState).invoked) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public Object onRetainNonConfigurationInstance() {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate();
|
||||
if (res.invoked) {
|
||||
return res.methodReturns;
|
||||
} else {
|
||||
return super.onRetainNonConfigurationInstance();
|
||||
}
|
||||
}
|
||||
|
||||
public Object super_onRetainNonConfigurationInstance() {
|
||||
return super.onRetainNonConfigurationInstance();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
if (!QtApplication.invokeDelegate(outState).invoked) {
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onSearchRequested() {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate();
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onSearchRequested();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onSearchRequested() {
|
||||
return super.onSearchRequested();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onTitleChanged(CharSequence title, int color) {
|
||||
if (!QtApplication.invokeDelegate(title, color).invoked) {
|
||||
super.onTitleChanged(title, color);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onTitleChanged(CharSequence title, int color) {
|
||||
super.onTitleChanged(title, color);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onTouchEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTouchEvent, event);
|
||||
} else {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onTouchEvent(MotionEvent event) {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onTrackballEvent(MotionEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onTrackballEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTrackballEvent, event);
|
||||
} else {
|
||||
return super.onTrackballEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onTrackballEvent(MotionEvent event) {
|
||||
return super.onTrackballEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onUserInteraction() {
|
||||
if (!QtApplication.invokeDelegate().invoked) {
|
||||
super.onUserInteraction();
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onUserInteraction() {
|
||||
super.onUserInteraction();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onUserLeaveHint() {
|
||||
if (!QtApplication.invokeDelegate().invoked) {
|
||||
super.onUserLeaveHint();
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onUserLeaveHint() {
|
||||
super.onUserLeaveHint();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onWindowAttributesChanged(LayoutParams params) {
|
||||
if (!QtApplication.invokeDelegate(params).invoked) {
|
||||
super.onWindowAttributesChanged(params);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onWindowAttributesChanged(LayoutParams params) {
|
||||
super.onWindowAttributesChanged(params);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
if (!QtApplication.invokeDelegate(hasFocus).invoked) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
//////////////// Activity API 5 /////////////
|
||||
//@ANDROID-5
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
if (!QtApplication.invokeDelegate().invoked) {
|
||||
super.onAttachedToWindow();
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (!QtApplication.invokeDelegate().invoked) {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onBackPressed() {
|
||||
super.onBackPressed();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
if (!QtApplication.invokeDelegate().invoked) {
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onKeyLongPress != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyLongPress, keyCode, event);
|
||||
} else {
|
||||
return super.onKeyLongPress(keyCode, event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onKeyLongPress(int keyCode, KeyEvent event) {
|
||||
return super.onKeyLongPress(keyCode, event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
//@ANDROID-5
|
||||
|
||||
//////////////// Activity API 8 /////////////
|
||||
//@ANDROID-8
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id, Bundle args) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(id, args);
|
||||
if (res.invoked) {
|
||||
return (Dialog) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreateDialog(id, args);
|
||||
}
|
||||
}
|
||||
|
||||
public Dialog super_onCreateDialog(int id, Bundle args) {
|
||||
return super.onCreateDialog(id, args);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
|
||||
if (!QtApplication.invokeDelegate(id, dialog, args).invoked) {
|
||||
super.onPrepareDialog(id, dialog, args);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onPrepareDialog(int id, Dialog dialog, Bundle args) {
|
||||
super.onPrepareDialog(id, dialog, args);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
//@ANDROID-8
|
||||
//////////////// Activity API 11 /////////////
|
||||
|
||||
//@ANDROID-11
|
||||
@Override
|
||||
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyShortcutEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyShortcutEvent, event);
|
||||
} else {
|
||||
return super.dispatchKeyShortcutEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_dispatchKeyShortcutEvent(KeyEvent event) {
|
||||
return super.dispatchKeyShortcutEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onActionModeFinished(ActionMode mode) {
|
||||
if (!QtApplication.invokeDelegate(mode).invoked) {
|
||||
super.onActionModeFinished(mode);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onActionModeFinished(ActionMode mode) {
|
||||
super.onActionModeFinished(mode);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onActionModeStarted(ActionMode mode) {
|
||||
if (!QtApplication.invokeDelegate(mode).invoked) {
|
||||
super.onActionModeStarted(mode);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onActionModeStarted(ActionMode mode) {
|
||||
super.onActionModeStarted(mode);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onAttachFragment(Fragment fragment) {
|
||||
if (!QtApplication.invokeDelegate(fragment).invoked) {
|
||||
super.onAttachFragment(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onAttachFragment(Fragment fragment) {
|
||||
super.onAttachFragment(fragment);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(parent, name, context, attrs);
|
||||
if (res.invoked) {
|
||||
return (View) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreateView(parent, name, context, attrs);
|
||||
}
|
||||
}
|
||||
|
||||
public View super_onCreateView(View parent, String name, Context context,
|
||||
AttributeSet attrs) {
|
||||
return super.onCreateView(parent, name, context, attrs);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onKeyShortcut(int keyCode, KeyEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onKeyShortcut != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyShortcut, keyCode, event);
|
||||
} else {
|
||||
return super.onKeyShortcut(keyCode, event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onKeyShortcut(int keyCode, KeyEvent event) {
|
||||
return super.onKeyShortcut(keyCode, event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public ActionMode onWindowStartingActionMode(Callback callback) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(callback);
|
||||
if (res.invoked) {
|
||||
return (ActionMode) res.methodReturns;
|
||||
} else {
|
||||
return super.onWindowStartingActionMode(callback);
|
||||
}
|
||||
}
|
||||
|
||||
public ActionMode super_onWindowStartingActionMode(Callback callback) {
|
||||
return super.onWindowStartingActionMode(callback);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
//@ANDROID-11
|
||||
//////////////// Activity API 12 /////////////
|
||||
|
||||
//@ANDROID-12
|
||||
@Override
|
||||
public boolean dispatchGenericMotionEvent(MotionEvent ev) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.dispatchGenericMotionEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchGenericMotionEvent, ev);
|
||||
} else {
|
||||
return super.dispatchGenericMotionEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_dispatchGenericMotionEvent(MotionEvent event) {
|
||||
return super.dispatchGenericMotionEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onGenericMotionEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onGenericMotionEvent, event);
|
||||
} else {
|
||||
return super.onGenericMotionEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onGenericMotionEvent(MotionEvent event) {
|
||||
return super.onGenericMotionEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
//@ANDROID-12
|
||||
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onRequestPermissionsResult != null) {
|
||||
QtApplication.invokeDelegateMethod(QtApplication.onRequestPermissionsResult, requestCode, permissions, grantResults);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,502 @@
|
|||
/*
|
||||
Copyright (c) 2016, BogDan Vatra <bogdan@kde.org>
|
||||
Contact: http://www.qt-project.org/legal
|
||||
|
||||
Commercial License Usage
|
||||
Licensees holding valid commercial Qt licenses may use this file in
|
||||
accordance with the commercial license agreement provided with the
|
||||
Software or, alternatively, in accordance with the terms contained in
|
||||
a written agreement between you and Digia. For licensing terms and
|
||||
conditions see http://qt.digia.com/licensing. For further information
|
||||
use the contact form at http://qt.digia.com/contact-us.
|
||||
|
||||
BSD License Usage
|
||||
Alternatively, this file may be used under the BSD license as follows:
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.qtproject.qt5.android.bindings;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ComponentInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Window;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import dalvik.system.DexClassLoader;
|
||||
|
||||
public class QtActivityLoader {
|
||||
private static final String DEX_PATH_KEY = "dex.path";
|
||||
private static final String LIB_PATH_KEY = "lib.path";
|
||||
private static final String NATIVE_LIBRARIES_KEY = "native.libraries";
|
||||
private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables";
|
||||
private static final String APPLICATION_PARAMETERS_KEY = "application.parameters";
|
||||
private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries";
|
||||
private static final String BUNDLED_IN_LIB_RESOURCE_ID_KEY = "android.app.bundled_in_lib_resource_id";
|
||||
private static final String BUNDLED_IN_ASSETS_RESOURCE_ID_KEY = "android.app.bundled_in_assets_resource_id";
|
||||
private static final String MAIN_LIBRARY_KEY = "main.library";
|
||||
private static final String STATIC_INIT_CLASSES_KEY = "static.init.classes";
|
||||
private static final String EXTRACT_STYLE_KEY = "extract.android.style";
|
||||
private static final String EXTRACT_STYLE_MINIMAL_KEY = "extract.android.style.option";
|
||||
private static final int BUFFER_SIZE = 1024;
|
||||
|
||||
String APPLICATION_PARAMETERS = null; // use this variable to pass any parameters to your application,
|
||||
String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1";
|
||||
String[] QT_ANDROID_THEMES = null;
|
||||
String QT_ANDROID_DEFAULT_THEME = null;
|
||||
|
||||
private String[] m_qtLibs = null; // required qt libs
|
||||
private int m_displayDensity = -1;
|
||||
private ContextWrapper m_context;
|
||||
private ComponentInfo m_contextInfo;
|
||||
private Class<?> m_delegateClass;
|
||||
|
||||
// this function is used to load and start the loader
|
||||
private void loadApplication(Bundle loaderParams) {
|
||||
try {
|
||||
// add all bundled Qt libs to loader params
|
||||
ArrayList<String> libs = new ArrayList<>();
|
||||
|
||||
String libName = m_contextInfo.metaData.getString("android.app.lib_name");
|
||||
loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function
|
||||
loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs);
|
||||
|
||||
// load and start QtLoader class
|
||||
DexClassLoader classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files
|
||||
m_context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written.
|
||||
loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists)
|
||||
m_context.getClassLoader()); // parent loader
|
||||
|
||||
Class<?> loaderClass = classLoader.loadClass(loaderClassName()); // load QtLoader class
|
||||
Object qtLoader = loaderClass.newInstance(); // create an instance
|
||||
Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication",
|
||||
contextClassName(),
|
||||
ClassLoader.class,
|
||||
Bundle.class);
|
||||
if (!(Boolean) prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams)) {
|
||||
throw new Exception("");
|
||||
}
|
||||
|
||||
QtApplication.setQtContextDelegate(m_delegateClass, qtLoader);
|
||||
|
||||
// now load the application library so it's accessible from this class loader
|
||||
if (libName != null) {
|
||||
System.loadLibrary(libName);
|
||||
}
|
||||
|
||||
Method startAppMethod = qtLoader.getClass().getMethod("startApplication");
|
||||
if (!(Boolean) startAppMethod.invoke(qtLoader)) {
|
||||
throw new Exception("");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
AlertDialog errorDialog = new AlertDialog.Builder(m_context).create();
|
||||
errorDialog.setMessage("Fatal error, your application can't be started.");
|
||||
errorDialog.setButton(DialogInterface.BUTTON_NEUTRAL, m_context.getResources().getString(android.R.string.ok), (dialog, which) -> {
|
||||
finish();
|
||||
});
|
||||
errorDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
static private void copyFile(InputStream inputStream, OutputStream outputStream)
|
||||
throws IOException {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
|
||||
int count;
|
||||
while ((count = inputStream.read(buffer)) > 0) {
|
||||
outputStream.write(buffer, 0, count);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyAsset(String source, String destination)
|
||||
throws IOException {
|
||||
// Already exists, we don't have to do anything
|
||||
File destinationFile = new File(destination);
|
||||
if (destinationFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
File parentDirectory = destinationFile.getParentFile();
|
||||
if (!parentDirectory.exists()) {
|
||||
parentDirectory.mkdirs();
|
||||
}
|
||||
|
||||
destinationFile.createNewFile();
|
||||
|
||||
AssetManager assetsManager = m_context.getAssets();
|
||||
InputStream inputStream = assetsManager.open(source);
|
||||
OutputStream outputStream = new FileOutputStream(destinationFile);
|
||||
copyFile(inputStream, outputStream);
|
||||
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
private static void createBundledBinary(String source, String destination)
|
||||
throws IOException {
|
||||
// Already exists, we don't have to do anything
|
||||
File destinationFile = new File(destination);
|
||||
if (destinationFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
File parentDirectory = destinationFile.getParentFile();
|
||||
if (!parentDirectory.exists()) {
|
||||
parentDirectory.mkdirs();
|
||||
}
|
||||
|
||||
destinationFile.createNewFile();
|
||||
|
||||
InputStream inputStream = new FileInputStream(source);
|
||||
OutputStream outputStream = new FileOutputStream(destinationFile);
|
||||
copyFile(inputStream, outputStream);
|
||||
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion) {
|
||||
File versionFile = new File(pluginsPrefix + "cache.version");
|
||||
|
||||
long cacheVersion = 0;
|
||||
if (versionFile.exists() && versionFile.canRead()) {
|
||||
try {
|
||||
DataInputStream inputStream = new DataInputStream(new FileInputStream(versionFile));
|
||||
cacheVersion = inputStream.readLong();
|
||||
inputStream.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheVersion != packageVersion) {
|
||||
deleteRecursively(new File(pluginsPrefix));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void extractBundledPluginsAndImports(String pluginsPrefix) throws IOException {
|
||||
String libsDir = m_context.getApplicationInfo().nativeLibraryDir + "/";
|
||||
long packageVersion = -1;
|
||||
try {
|
||||
PackageInfo packageInfo = m_context.getPackageManager().getPackageInfo(m_context.getPackageName(), 0);
|
||||
packageVersion = packageInfo.lastUpdateTime;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
File versionFile = new File(pluginsPrefix + "cache.version");
|
||||
|
||||
File parentDirectory = versionFile.getParentFile();
|
||||
if (!parentDirectory.exists()) {
|
||||
parentDirectory.mkdirs();
|
||||
}
|
||||
|
||||
versionFile.createNewFile();
|
||||
|
||||
DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(versionFile));
|
||||
outputStream.writeLong(packageVersion);
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
{
|
||||
String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY;
|
||||
if (m_contextInfo.metaData.containsKey(key)) {
|
||||
String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key));
|
||||
|
||||
for (String bundledImportBinary : list) {
|
||||
String[] split = bundledImportBinary.split(":");
|
||||
String sourceFileName = libsDir + split[0];
|
||||
String destinationFileName = pluginsPrefix + split[1];
|
||||
createBundledBinary(sourceFileName, destinationFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
String key = BUNDLED_IN_ASSETS_RESOURCE_ID_KEY;
|
||||
if (m_contextInfo.metaData.containsKey(key)) {
|
||||
String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key));
|
||||
|
||||
for (String fileName : list) {
|
||||
String[] split = fileName.split(":");
|
||||
String sourceFileName = split[0];
|
||||
String destinationFileName = pluginsPrefix + split[1];
|
||||
copyAsset(sourceFileName, destinationFileName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteRecursively(File directory) {
|
||||
File[] files = directory.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
deleteRecursively(file);
|
||||
} else {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
directory.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanOldCacheIfNecessary(String oldLocalPrefix, String localPrefix) {
|
||||
File newCache = new File(localPrefix);
|
||||
if (!newCache.exists()) {
|
||||
{
|
||||
File oldPluginsCache = new File(oldLocalPrefix + "plugins/");
|
||||
if (oldPluginsCache.exists() && oldPluginsCache.isDirectory()) {
|
||||
deleteRecursively(oldPluginsCache);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
File oldImportsCache = new File(oldLocalPrefix + "imports/");
|
||||
if (oldImportsCache.exists() && oldImportsCache.isDirectory()) {
|
||||
deleteRecursively(oldImportsCache);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
File oldQmlCache = new File(oldLocalPrefix + "qml/");
|
||||
if (oldQmlCache.exists() && oldQmlCache.isDirectory()) {
|
||||
deleteRecursively(oldQmlCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startApp() {
|
||||
try {
|
||||
if (m_contextInfo.metaData.containsKey("android.app.qt_libs_resource_id")) {
|
||||
int resourceId = m_contextInfo.metaData.getInt("android.app.qt_libs_resource_id");
|
||||
m_qtLibs = m_context.getResources().getStringArray(resourceId);
|
||||
}
|
||||
ArrayList<String> libraryList = new ArrayList<>();
|
||||
String localPrefix = m_context.getApplicationInfo().dataDir + "/";
|
||||
String pluginsPrefix = localPrefix + "qt-reserved-files/";
|
||||
cleanOldCacheIfNecessary(localPrefix, pluginsPrefix);
|
||||
extractBundledPluginsAndImports(pluginsPrefix);
|
||||
|
||||
for (String lib : m_qtLibs) {
|
||||
libraryList.add(localPrefix + "lib/lib" + lib + ".so");
|
||||
}
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.load_local_libs")) {
|
||||
String[] extraLibs = m_contextInfo.metaData.getString("android.app.load_local_libs").split(":");
|
||||
for (String lib : extraLibs) {
|
||||
if (lib.length() > 0) {
|
||||
if (lib.startsWith("lib/")) {
|
||||
libraryList.add(localPrefix + lib);
|
||||
} else {
|
||||
libraryList.add(pluginsPrefix + lib);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bundle loaderParams = new Bundle();
|
||||
loaderParams.putString(DEX_PATH_KEY, new String());
|
||||
if (m_contextInfo.metaData.containsKey("android.app.static_init_classes")) {
|
||||
loaderParams.putStringArray(STATIC_INIT_CLASSES_KEY,
|
||||
m_contextInfo.metaData.getString("android.app.static_init_classes").split(":"));
|
||||
}
|
||||
loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList);
|
||||
String themePath = m_context.getApplicationInfo().dataDir + "/qt-reserved-files/android-style/";
|
||||
String stylePath = themePath + m_displayDensity + "/";
|
||||
String extractOption = "full";
|
||||
if (m_contextInfo.metaData.containsKey("android.app.extract_android_style")) {
|
||||
extractOption = m_contextInfo.metaData.getString("android.app.extract_android_style");
|
||||
if (!extractOption.equals("full") && !extractOption.equals("minimal") && !extractOption.equals("none")) {
|
||||
Log.e(QtApplication.QtTAG, "Invalid extract_android_style option \"" + extractOption + "\", defaulting to full");
|
||||
extractOption = "full";
|
||||
}
|
||||
}
|
||||
|
||||
if (!(new File(stylePath)).exists() && !extractOption.equals("none")) {
|
||||
loaderParams.putString(EXTRACT_STYLE_KEY, stylePath);
|
||||
loaderParams.putBoolean(EXTRACT_STYLE_MINIMAL_KEY, extractOption.equals("minimal"));
|
||||
}
|
||||
|
||||
if (extractOption.equals("full")) {
|
||||
ENVIRONMENT_VARIABLES += "\tQT_USE_ANDROID_NATIVE_STYLE=1";
|
||||
}
|
||||
|
||||
ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEMES_ROOT_PATH=" + themePath;
|
||||
|
||||
loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES
|
||||
+ "\tQML2_IMPORT_PATH=" + pluginsPrefix + "/qml"
|
||||
+ "\tQML_IMPORT_PATH=" + pluginsPrefix + "/imports"
|
||||
+ "\tQT_PLUGIN_PATH=" + pluginsPrefix + "/plugins");
|
||||
|
||||
String appParams = null;
|
||||
if (APPLICATION_PARAMETERS != null) {
|
||||
appParams = APPLICATION_PARAMETERS;
|
||||
}
|
||||
|
||||
Intent intent = getIntent();
|
||||
if (intent != null) {
|
||||
String parameters = intent.getStringExtra("applicationArguments");
|
||||
if (parameters != null) {
|
||||
if (appParams == null) {
|
||||
appParams = parameters;
|
||||
} else {
|
||||
appParams += '\t' + parameters;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.arguments")) {
|
||||
String parameters = m_contextInfo.metaData.getString("android.app.arguments");
|
||||
if (appParams == null) {
|
||||
appParams = parameters;
|
||||
} else {
|
||||
appParams += '\t' + parameters;
|
||||
}
|
||||
}
|
||||
|
||||
if (appParams != null) {
|
||||
loaderParams.putString(APPLICATION_PARAMETERS_KEY, appParams.replace(' ', '\t').trim());
|
||||
}
|
||||
loadApplication(loaderParams);
|
||||
} catch (Exception e) {
|
||||
Log.e(QtApplication.QtTAG, "Can't create main activity", e);
|
||||
}
|
||||
}
|
||||
|
||||
QtActivity m_activity;
|
||||
|
||||
QtActivityLoader(QtActivity activity) {
|
||||
m_context = activity;
|
||||
m_delegateClass = QtActivity.class;
|
||||
m_activity = activity;
|
||||
}
|
||||
|
||||
protected String loaderClassName() {
|
||||
return "org.qtproject.qt5.android.QtActivityDelegate";
|
||||
}
|
||||
|
||||
protected Class<?> contextClassName() {
|
||||
return android.app.Activity.class;
|
||||
}
|
||||
|
||||
protected void finish() {
|
||||
m_activity.finish();
|
||||
}
|
||||
|
||||
protected String getTitle() {
|
||||
return (String) m_activity.getTitle();
|
||||
}
|
||||
|
||||
protected void runOnUiThread(Runnable run) {
|
||||
m_activity.runOnUiThread(run);
|
||||
}
|
||||
|
||||
Intent getIntent() {
|
||||
return m_activity.getIntent();
|
||||
}
|
||||
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
try {
|
||||
m_contextInfo = m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), PackageManager.GET_META_DATA);
|
||||
int theme = ((ActivityInfo) m_contextInfo).getThemeResource();
|
||||
for (Field f : Class.forName("android.R$style").getDeclaredFields()) {
|
||||
if (f.getInt(null) == theme) {
|
||||
QT_ANDROID_THEMES = new String[]{f.getName()};
|
||||
QT_ANDROID_DEFAULT_THEME = f.getName();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
m_activity.setTheme(Class.forName("android.R$style").getDeclaredField(QT_ANDROID_DEFAULT_THEME).getInt(null));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
m_activity.requestWindowFeature(Window.FEATURE_ACTION_BAR);
|
||||
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null) {
|
||||
QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState);
|
||||
return;
|
||||
}
|
||||
|
||||
m_displayDensity = m_activity.getResources().getDisplayMetrics().densityDpi;
|
||||
|
||||
ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_DEFAULT_THEME
|
||||
+ "/\tQT_ANDROID_THEME_DISPLAY_DPI=" + m_displayDensity + "\t";
|
||||
|
||||
if (null == m_activity.getLastNonConfigurationInstance()) {
|
||||
if (m_contextInfo.metaData.containsKey("android.app.background_running")
|
||||
&& m_contextInfo.metaData.getBoolean("android.app.background_running")) {
|
||||
ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t";
|
||||
} else {
|
||||
ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t";
|
||||
}
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.auto_screen_scale_factor")
|
||||
&& m_contextInfo.metaData.getBoolean("android.app.auto_screen_scale_factor")) {
|
||||
ENVIRONMENT_VARIABLES += "QT_AUTO_SCREEN_SCALE_FACTOR=1\t";
|
||||
}
|
||||
|
||||
startApp();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
Copyright (c) 2016, BogDan Vatra <bogdan@kde.org>
|
||||
Contact: http://www.qt.io/licensing/
|
||||
|
||||
Commercial License Usage
|
||||
Licensees holding valid commercial Qt licenses may use this file in
|
||||
accordance with the commercial license agreement provided with the
|
||||
Software or, alternatively, in accordance with the terms contained in
|
||||
a written agreement between you and The Qt Company. For licensing terms
|
||||
and conditions see http://www.qt.io/terms-conditions. For further
|
||||
information use the contact form at http://www.qt.io/contact-us.
|
||||
|
||||
BSD License Usage
|
||||
Alternatively, this file may be used under the BSD license as follows:
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.qtproject.qt5.android.bindings;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class QtApplication extends Application {
|
||||
public final static String QtTAG = "Qt";
|
||||
public static Object m_delegateObject = null;
|
||||
public static Map<String, ArrayList<Method>> m_delegateMethods = new HashMap<>();
|
||||
public static Method dispatchKeyEvent = null;
|
||||
public static Method dispatchPopulateAccessibilityEvent = null;
|
||||
public static Method dispatchTouchEvent = null;
|
||||
public static Method dispatchTrackballEvent = null;
|
||||
public static Method onKeyDown = null;
|
||||
public static Method onKeyMultiple = null;
|
||||
public static Method onKeyUp = null;
|
||||
public static Method onTouchEvent = null;
|
||||
public static Method onTrackballEvent = null;
|
||||
public static Method onActivityResult = null;
|
||||
public static Method onCreate = null;
|
||||
public static Method onKeyLongPress = null;
|
||||
public static Method dispatchKeyShortcutEvent = null;
|
||||
public static Method onKeyShortcut = null;
|
||||
public static Method dispatchGenericMotionEvent = null;
|
||||
public static Method onGenericMotionEvent = null;
|
||||
public static Method onRequestPermissionsResult = null;
|
||||
private static String activityClassName;
|
||||
|
||||
public static void setQtContextDelegate(Class<?> clazz, Object listener) {
|
||||
m_delegateObject = listener;
|
||||
activityClassName = clazz.getCanonicalName();
|
||||
|
||||
ArrayList<Method> delegateMethods = new ArrayList<>();
|
||||
for (Method m : listener.getClass().getMethods()) {
|
||||
if (m.getDeclaringClass().getName().startsWith("org.qtproject.qt5.android")) {
|
||||
delegateMethods.add(m);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<Field> applicationFields = new ArrayList<>();
|
||||
for (Field f : QtApplication.class.getFields()) {
|
||||
if (f.getDeclaringClass().getName().equals(QtApplication.class.getName())) {
|
||||
applicationFields.add(f);
|
||||
}
|
||||
}
|
||||
|
||||
for (Method delegateMethod : delegateMethods) {
|
||||
try {
|
||||
clazz.getDeclaredMethod(delegateMethod.getName(), delegateMethod.getParameterTypes());
|
||||
if (m_delegateMethods.containsKey(delegateMethod.getName())) {
|
||||
m_delegateMethods.get(delegateMethod.getName()).add(delegateMethod);
|
||||
} else {
|
||||
ArrayList<Method> delegateSet = new ArrayList<>();
|
||||
delegateSet.add(delegateMethod);
|
||||
m_delegateMethods.put(delegateMethod.getName(), delegateSet);
|
||||
}
|
||||
for (Field applicationField : applicationFields) {
|
||||
if (applicationField.getName().equals(delegateMethod.getName())) {
|
||||
try {
|
||||
applicationField.set(null, delegateMethod);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
if (m_delegateObject != null && m_delegateMethods.containsKey("onTerminate")) {
|
||||
invokeDelegateMethod(m_delegateMethods.get("onTerminate").get(0));
|
||||
}
|
||||
super.onTerminate();
|
||||
}
|
||||
|
||||
static class InvokeResult {
|
||||
boolean invoked = false;
|
||||
Object methodReturns = null;
|
||||
}
|
||||
|
||||
private static int stackDeep = -1;
|
||||
|
||||
public static InvokeResult invokeDelegate(Object... args) {
|
||||
InvokeResult result = new InvokeResult();
|
||||
if (m_delegateObject == null) {
|
||||
return result;
|
||||
}
|
||||
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
|
||||
if (-1 == stackDeep) {
|
||||
for (int it = 0; it < elements.length; it++) {
|
||||
if (elements[it].getClassName().equals(activityClassName)) {
|
||||
stackDeep = it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (-1 == stackDeep) {
|
||||
return result;
|
||||
}
|
||||
|
||||
final String methodName = elements[stackDeep].getMethodName();
|
||||
if (!m_delegateMethods.containsKey(methodName)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (Method m : m_delegateMethods.get(methodName)) {
|
||||
if (m.getParameterTypes().length == args.length) {
|
||||
result.methodReturns = invokeDelegateMethod(m, args);
|
||||
result.invoked = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Object invokeDelegateMethod(Method m, Object... args) {
|
||||
try {
|
||||
return m.invoke(m_delegateObject, args);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
package org.saintandreas.testapp;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.vr.ndk.base.AndroidCompat;
|
||||
import com.google.vr.ndk.base.GvrLayout;
|
||||
|
||||
import javax.microedition.khronos.egl.EGLConfig;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
|
||||
public class MainActivity extends Activity {
|
||||
private final static int IMMERSIVE_STICKY_VIEW_FLAGS = View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
|
||||
static {
|
||||
System.loadLibrary("gvr");
|
||||
System.loadLibrary("native-lib");
|
||||
}
|
||||
|
||||
private long nativeRenderer;
|
||||
private GLSurfaceView surfaceView;
|
||||
|
||||
private native long nativeCreateRenderer(ClassLoader appClassLoader, Context context);
|
||||
private native void nativeDestroyRenderer(long renderer);
|
||||
private native void nativeInitializeGl(long renderer);
|
||||
private native void nativeDrawFrame(long renderer);
|
||||
private native void nativeOnTriggerEvent(long renderer);
|
||||
private native void nativeOnPause(long renderer);
|
||||
private native void nativeOnResume(long renderer);
|
||||
|
||||
class NativeRenderer implements GLSurfaceView.Renderer {
|
||||
@Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { nativeInitializeGl(nativeRenderer); }
|
||||
@Override public void onSurfaceChanged(GL10 gl, int width, int height) { }
|
||||
@Override public void onDrawFrame(GL10 gl) {
|
||||
nativeDrawFrame(nativeRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setImmersiveSticky();
|
||||
getWindow()
|
||||
.getDecorView()
|
||||
.setOnSystemUiVisibilityChangeListener((int visibility)->{
|
||||
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { setImmersiveSticky(); }
|
||||
});
|
||||
|
||||
nativeRenderer = nativeCreateRenderer(
|
||||
getClass().getClassLoader(),
|
||||
getApplicationContext());
|
||||
|
||||
surfaceView = new GLSurfaceView(this);
|
||||
surfaceView.setEGLContextClientVersion(3);
|
||||
surfaceView.setEGLConfigChooser(8, 8, 8, 0, 0, 0);
|
||||
surfaceView.setPreserveEGLContextOnPause(true);
|
||||
surfaceView.setRenderer(new NativeRenderer());
|
||||
setContentView(surfaceView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
nativeDestroyRenderer(nativeRenderer);
|
||||
nativeRenderer = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
surfaceView.queueEvent(()->nativeOnPause(nativeRenderer));
|
||||
surfaceView.onPause();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
surfaceView.onResume();
|
||||
surfaceView.queueEvent(()->nativeOnResume(nativeRenderer));
|
||||
}
|
||||
|
||||
private void setImmersiveSticky() {
|
||||
getWindow().getDecorView().setSystemUiVisibility(IMMERSIVE_STICKY_VIEW_FLAGS);
|
||||
}
|
||||
}
|
BIN
android/app/src/main/res/drawable/icon.png
Normal file
BIN
android/app/src/main/res/drawable/icon.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 9.7 KiB |
|
@ -1,3 +1,13 @@
|
|||
import de.undercouch.gradle.tasks.download.Download
|
||||
import de.undercouch.gradle.tasks.download.Verify
|
||||
import groovy.io.FileType
|
||||
import groovy.json.JsonSlurper
|
||||
import groovy.xml.XmlUtil
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
|
@ -10,6 +20,7 @@ buildscript {
|
|||
|
||||
plugins {
|
||||
id 'de.undercouch.download' version '3.3.0'
|
||||
id "cz.malohlava" version "1.0.3"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
|
@ -19,65 +30,290 @@ allprojects {
|
|||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
ext {
|
||||
RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0'
|
||||
RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV'
|
||||
BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : ''
|
||||
EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : ''
|
||||
QT5_DEPS = [
|
||||
'Qt5Core',
|
||||
'Qt5Gui',
|
||||
'Qt5Multimedia',
|
||||
'Qt5Network',
|
||||
'Qt5OpenGL',
|
||||
'Qt5Qml',
|
||||
'Qt5Quick',
|
||||
'Qt5Script',
|
||||
'Qt5ScriptTools',
|
||||
'Qt5WebChannel',
|
||||
'Qt5WebSockets',
|
||||
'Qt5Widgets',
|
||||
'Qt5XmlPatterns',
|
||||
// Android specific
|
||||
'Qt5AndroidExtras',
|
||||
'Qt5WebView',
|
||||
]
|
||||
}
|
||||
|
||||
def baseFolder = new File(HIFI_ANDROID_PRECOMPILED)
|
||||
def jniFolder = new File('app/src/main/jniLibs/arm64-v8a')
|
||||
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
|
||||
def appDir = new File(projectDir, 'app')
|
||||
def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a')
|
||||
def baseUrl = 'https://hifi-public.s3.amazonaws.com/austin/android/'
|
||||
def qtFile='qt-5.9.3_linux_armv8-libcpp.tgz'
|
||||
def qtChecksum='547da3547d5690144e23d6504c6d6e91'
|
||||
|
||||
def qtFile='qt-5.9.3_linux_armv8-libcpp_openssl.tgz'
|
||||
def qtChecksum='04599670ccca84bd2b15f6915568eb2d'
|
||||
def qtVersionId='PeoqzN31n.YvLfs9JE2SgHgZ4.IaKAlt'
|
||||
if (Os.isFamily(Os.FAMILY_MAC)) {
|
||||
qtFile = 'qt-5.9.3_osx_armv8-libcpp.tgz'
|
||||
qtChecksum='6fa3e068cfdee863fc909b294a3a0cc6'
|
||||
qtFile = 'qt-5.9.3_osx_armv8-libcpp_openssl.tgz'
|
||||
qtChecksum='4b02de9d67d6bfb202355a808d2d9c59'
|
||||
qtVersionId='HygCmtMLPYioyil0DfXckGVzhw2SXZA9'
|
||||
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
qtFile = 'qt-5.9.3_win_armv8-libcpp.tgz'
|
||||
qtChecksum='3a757378a7e9dbbfc662177e0eb46408'
|
||||
qtFile = 'qt-5.9.3_win_armv8-libcpp_openssl.tgz'
|
||||
qtChecksum='a93d22c0c59aa112fda18c4c6d157d17'
|
||||
qtVersionId='0Bl9NSUWb5CBKLT_NXaxTt75SNBBZ9sB'
|
||||
}
|
||||
|
||||
def packages = [
|
||||
qt: [
|
||||
file: qtFile,
|
||||
versionId: qtVersionId,
|
||||
checksum: qtChecksum,
|
||||
sharedLibFolder: '',
|
||||
includeLibs: ['lib/*.so', 'plugins/*/*.so']
|
||||
],
|
||||
bullet: [
|
||||
file: 'bullet-2.83_armv8-libcpp.tgz',
|
||||
checksum: '2c558d604fce337f5eba3eb7ec1252fd'
|
||||
versionId: 'ljb7v.1IjVRqyopUKVDbVnLA4z88J8Eo',
|
||||
checksum: '2c558d604fce337f5eba3eb7ec1252fd',
|
||||
],
|
||||
draco: [
|
||||
file: 'draco_armv8-libcpp.tgz',
|
||||
checksum: '617a80d213a5ec69fbfa21a1f2f738cd'
|
||||
versionId: 'cA3tVJSmkvb1naA3l6D_Jv2Noh.4yc4m',
|
||||
checksum: '617a80d213a5ec69fbfa21a1f2f738cd',
|
||||
],
|
||||
glm: [
|
||||
file: 'glm-0.9.8.tgz',
|
||||
versionId: 'BlkJNwaYV2Gfy5XwMeU7K0uzPDRKFMt2',
|
||||
checksum: 'd2b42cee31d2bc17bab6ce69e6b3f30a',
|
||||
],
|
||||
gvr: [
|
||||
file: 'gvrsdk_v1.101.0.tgz',
|
||||
checksum: '57fd02baa069176ba18597a29b6b4fc7'
|
||||
versionId: 'UTberAIFraEfF9IVjoV66u1DTPTopgeY',
|
||||
checksum: '57fd02baa069176ba18597a29b6b4fc7',
|
||||
],
|
||||
openssl: [
|
||||
file: 'openssl-1.1.0g_armv8.tgz',
|
||||
checksum: 'cabb681fbccd79594f65fcc266e02f32'
|
||||
versionId: 'DmahmSGFS4ltpHyTdyQvv35WOeUOiib9',
|
||||
checksum: 'cabb681fbccd79594f65fcc266e02f32',
|
||||
],
|
||||
polyvox: [
|
||||
file: 'polyvox_armv8-libcpp.tgz',
|
||||
checksum: '5c918288741ee754c16aeb12bb46b9e1',
|
||||
versionId: 'LDJtzMTvdm4SAc2KYg8Cg6uwWk4Vq3e3',
|
||||
checksum: '349ad5b72aaf2749ca95d847e60c5314',
|
||||
sharedLibFolder: 'lib',
|
||||
includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so']
|
||||
includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'],
|
||||
],
|
||||
tbb: [
|
||||
file: 'tbb-2018_U1_armv8_libcpp.tgz',
|
||||
versionId: 'YZliDD8.Menh1IVXKEuLPeO3xAjJ1UdF',
|
||||
checksum: '20768f298f53b195e71b414b0ae240c4',
|
||||
sharedLibFolder: 'lib/release',
|
||||
includeLibs: ['libtbb.so', 'libtbbmalloc.so']
|
||||
includeLibs: ['libtbb.so', 'libtbbmalloc.so'],
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
def scribeLocalFile='scribe' + EXEC_SUFFIX
|
||||
|
||||
def scribeFile='scribe_linux_x86_64'
|
||||
def scribeChecksum='c98678d9726bd8bbf1bab792acf3ff6c'
|
||||
if (Os.isFamily(Os.FAMILY_MAC)) {
|
||||
scribeFile = 'scribe_osx_x86_64'
|
||||
scribeChecksum='a137ad62c1bf7cca739da219544a9a16'
|
||||
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
scribeFile = 'scribe_win32_x86_64.exe'
|
||||
scribeChecksum='75c2ce9ed45d17de375e3988bfaba816'
|
||||
}
|
||||
|
||||
def options = [
|
||||
files: new TreeSet<File>(),
|
||||
features: new HashSet<String>(),
|
||||
permissions: new HashSet<String>()
|
||||
]
|
||||
|
||||
def qmlRoot = new File(HIFI_ANDROID_PRECOMPILED, 'qt')
|
||||
|
||||
def captureOutput = { String command ->
|
||||
def proc = command.execute()
|
||||
def sout = new StringBuilder(), serr = new StringBuilder()
|
||||
proc.consumeProcessOutput(sout, serr)
|
||||
proc.waitForOrKill(10000)
|
||||
def errorOutput = serr.toString()
|
||||
if (!errorOutput.isEmpty()) {
|
||||
throw new GradleException("Command '${command}' failed with error ${errorOutput}")
|
||||
}
|
||||
return sout.toString()
|
||||
}
|
||||
|
||||
def relativize = { File root, File absolute ->
|
||||
def relativeURI = root.toURI().relativize(absolute.toURI())
|
||||
return new File(relativeURI.toString())
|
||||
}
|
||||
|
||||
def scanQmlImports = { File qmlRootPath ->
|
||||
def qmlImportCommandFile = new File(qmlRoot, 'bin/qmlimportscanner' + EXEC_SUFFIX)
|
||||
if (!qmlImportCommandFile.exists()) {
|
||||
throw new GradleException('Unable to find required qmlimportscanner executable at ' + qmlImportCommandFile.parent.toString())
|
||||
}
|
||||
|
||||
def command = qmlImportCommandFile.absolutePath +
|
||||
" -rootPath ${qmlRootPath.absolutePath}" +
|
||||
" -importPath ${qmlRoot.absolutePath}/qml"
|
||||
|
||||
def commandResult = captureOutput(command)
|
||||
new JsonSlurper().parseText(commandResult).each {
|
||||
if (!it.containsKey('path')) {
|
||||
println "Warning: QML import could not be resolved in any of the import paths: ${it.name}"
|
||||
return
|
||||
}
|
||||
def file = new File(it.path)
|
||||
// Ignore non-existent files
|
||||
if (!file.exists()) {
|
||||
return
|
||||
}
|
||||
// Ignore files in the import path
|
||||
if (file.canonicalPath.startsWith(qmlRootPath.canonicalPath)) {
|
||||
return
|
||||
}
|
||||
if (file.isFile()) {
|
||||
options.files.add(file)
|
||||
} else {
|
||||
file.eachFileRecurse(FileType.FILES, {
|
||||
options.files.add(it)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def parseQtDependencies = { List qtLibs ->
|
||||
qtLibs.each({
|
||||
def libFile = new File(qmlRoot, "lib/lib${it}.so")
|
||||
options.files.add(libFile)
|
||||
|
||||
def androidDeps = new File(qmlRoot, "lib/${it}-android-dependencies.xml")
|
||||
if (!libFile.exists()) return
|
||||
if (!androidDeps.exists()) return
|
||||
|
||||
new XmlSlurper().parse(androidDeps).dependencies.lib.depends.'*'.each{ node ->
|
||||
switch (node.name()) {
|
||||
case 'lib':
|
||||
case 'bundled':
|
||||
def relativeFilename = node.@file.toString()
|
||||
|
||||
// Special case, since this is handled by qmlimportscanner instead
|
||||
if (relativeFilename.startsWith('qml'))
|
||||
return
|
||||
|
||||
def file = new File(qmlRoot, relativeFilename)
|
||||
|
||||
if (!file.exists())
|
||||
return
|
||||
|
||||
if (file.isFile()) {
|
||||
options.files.add(file)
|
||||
} else {
|
||||
file.eachFileRecurse(FileType.FILES, { options.files.add(it) })
|
||||
}
|
||||
break
|
||||
|
||||
|
||||
case 'jar':
|
||||
if (node.@bundling == "1") {
|
||||
def jar = new File(qmlRoot, node.@file.toString())
|
||||
if (!jar.exists()) {
|
||||
throw new GradleException('Unable to find required JAR ' + jar.path)
|
||||
}
|
||||
options.files.add(jar)
|
||||
}
|
||||
break
|
||||
|
||||
case 'permission':
|
||||
options.permissions.add(node.@name)
|
||||
break
|
||||
|
||||
case 'feature':
|
||||
options.features.add(node.@name)
|
||||
break
|
||||
|
||||
default:
|
||||
throw new GradleException('Unhandled Android Dependency node ' + node.name())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
def generateLibsXml = {
|
||||
def libDestinationDirectory = jniFolder
|
||||
def jarDestinationDirectory = new File(appDir, 'libs')
|
||||
def assetDestinationDirectory = new File(appDir, 'src/main/assets/bundled');
|
||||
def libsXmlFile = new File(appDir, 'src/main/res/values/libs.xml')
|
||||
def libPrefix = 'lib' + File.separator
|
||||
def jarPrefix = 'jar' + File.separator
|
||||
|
||||
def xmlParser = new XmlParser()
|
||||
def libsXmlRoot = xmlParser.parseText('<?xml version="1.0" encoding="UTF-8"?><resources/>')
|
||||
def qtLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'qt_libs'])
|
||||
def bundledLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_lib'])
|
||||
def bundledAssetsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_assets'])
|
||||
|
||||
options.files.each {
|
||||
def sourceFile = it
|
||||
if (!sourceFile.exists()) {
|
||||
throw new GradleException("Unable to find dependency file " + sourceFile.toString())
|
||||
}
|
||||
|
||||
def relativePath = relativize( qmlRoot, sourceFile ).toString()
|
||||
def destinationFile
|
||||
if (relativePath.endsWith('.so')) {
|
||||
def garbledFileName
|
||||
if (relativePath.startsWith(libPrefix)) {
|
||||
garbledFileName = relativePath.substring(libPrefix.size())
|
||||
Pattern p = ~/lib(Qt5.*).so/
|
||||
Matcher m = p.matcher(garbledFileName)
|
||||
assert m.matches()
|
||||
def libName = m.group(1)
|
||||
xmlParser.createNode(qtLibsNode, 'item', [:]).setValue(libName)
|
||||
} else {
|
||||
garbledFileName = 'lib' + relativePath.replace(File.separator, '_'[0])
|
||||
xmlParser.createNode(bundledLibsNode, 'item', [:]).setValue("${garbledFileName}:${relativePath}".replace(File.separator, '/'))
|
||||
}
|
||||
destinationFile = new File(libDestinationDirectory, garbledFileName)
|
||||
} else if (relativePath.startsWith('jar')) {
|
||||
destinationFile = new File(jarDestinationDirectory, relativePath.substring(jarPrefix.size()))
|
||||
} else {
|
||||
xmlParser.createNode(bundledAssetsNode, 'item', [:]).setValue("bundled/${relativePath}:${relativePath}".replace(File.separator, '/'))
|
||||
destinationFile = new File(assetDestinationDirectory, relativePath)
|
||||
}
|
||||
|
||||
copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) }
|
||||
assert destinationFile.exists() && destinationFile.isFile()
|
||||
}
|
||||
def xml = XmlUtil.serialize(libsXmlRoot)
|
||||
new FileWriter(libsXmlFile).withPrintWriter { writer ->
|
||||
writer.write(xml)
|
||||
}
|
||||
}
|
||||
|
||||
task downloadDependencies {
|
||||
doLast {
|
||||
packages.each { entry ->
|
||||
def filename = entry.value['file'];
|
||||
def url = baseUrl + filename;
|
||||
if (entry.value.containsKey('versionId')) {
|
||||
url = url + '?versionId=' + entry.value['versionId']
|
||||
}
|
||||
download {
|
||||
src url
|
||||
dest new File(baseFolder, filename)
|
||||
|
@ -87,8 +323,6 @@ task downloadDependencies {
|
|||
}
|
||||
}
|
||||
|
||||
import de.undercouch.gradle.tasks.download.Verify
|
||||
|
||||
task verifyQt(type: Verify) { def p = packages['qt']; src new File(baseFolder, p['file']); checksum p['checksum']; }
|
||||
task verifyBullet(type: Verify) { def p = packages['bullet']; src new File(baseFolder, p['file']); checksum p['checksum'] }
|
||||
task verifyDraco(type: Verify) { def p = packages['draco']; src new File(baseFolder, p['file']); checksum p['checksum'] }
|
||||
|
@ -109,19 +343,26 @@ verifyDependencyDownloads.dependsOn verifyTBB
|
|||
task extractDependencies(dependsOn: verifyDependencyDownloads) {
|
||||
doLast {
|
||||
packages.each { entry ->
|
||||
def folder = entry.key;
|
||||
def filename = entry.value['file'];
|
||||
def folder = entry.key
|
||||
def filename = entry.value['file']
|
||||
def localFile = new File(HIFI_ANDROID_PRECOMPILED, filename)
|
||||
def localFolder = new File(HIFI_ANDROID_PRECOMPILED, folder)
|
||||
def fileTree;
|
||||
if (filename.endsWith('zip')) {
|
||||
fileTree = zipTree(localFile)
|
||||
} else {
|
||||
fileTree = tarTree(resources.gzip(localFile))
|
||||
}
|
||||
copy {
|
||||
from tarTree(resources.gzip(localFile))
|
||||
from fileTree
|
||||
into localFolder
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task copyDependencies(dependsOn: extractDependencies) {
|
||||
// Copies the non Qt dependencies. Qt dependencies (primary libraries and plugins) are handled by the qtBundle task
|
||||
task copyDependencies(dependsOn: [ extractDependencies ]) {
|
||||
doLast {
|
||||
packages.each { entry ->
|
||||
def packageName = entry.key
|
||||
|
@ -132,31 +373,17 @@ task copyDependencies(dependsOn: extractDependencies) {
|
|||
if (currentPackage.containsKey('includeLibs')) {
|
||||
currentPackage['includeLibs'].each { includeSpec -> tree.include includeSpec }
|
||||
}
|
||||
tree.visit { element ->
|
||||
tree.visit { element ->
|
||||
if (!element.file.isDirectory()) {
|
||||
println "Copying " + element.file + " to " + jniFolder
|
||||
copy { from element.file; into jniFolder }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def scribeFile='scribe_linux_x86_64'
|
||||
def scribeLocalFile='scribe'
|
||||
def scribeChecksum='c98678d9726bd8bbf1bab792acf3ff6c'
|
||||
if (Os.isFamily(Os.FAMILY_MAC)) {
|
||||
scribeFile = 'scribe_osx_x86_64'
|
||||
scribeChecksum='a137ad62c1bf7cca739da219544a9a16'
|
||||
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
scribeFile = 'scribe_win32_x86_64.exe'
|
||||
scribeLocalFile = 'scribe.exe'
|
||||
scribeChecksum='75c2ce9ed45d17de375e3988bfaba816'
|
||||
|
||||
}
|
||||
|
||||
import de.undercouch.gradle.tasks.download.Download
|
||||
|
||||
task downloadScribe(type: Download) {
|
||||
src baseUrl + scribeFile
|
||||
dest new File(baseFolder, scribeLocalFile)
|
||||
|
@ -203,14 +430,101 @@ task extractGvrBinaries(dependsOn: extractDependencies) {
|
|||
|
||||
}
|
||||
|
||||
task setupDependencies(dependsOn: [setupScribe, copyDependencies, extractGvrBinaries]) {
|
||||
// Copy required Qt main libraries and required plugins based on the predefined list here
|
||||
// FIXME eventually we would like to use the readelf functionality to automatically detect dependencies
|
||||
// from our built applications and use that during the full build process. However doing so would mean
|
||||
// hooking existing Android build tasks since the output from the qtBundle logic adds JNI libs, asset
|
||||
// files and resources files and potentially modifies the AndroidManifest.xml
|
||||
task qtBundle {
|
||||
doLast {
|
||||
parseQtDependencies(QT5_DEPS)
|
||||
//def qmlImportFolder = new File("${appDir}/../../interface/resources/qml/")
|
||||
def qmlImportFolder = new File("${projectDir}/app/src/main/cpp")
|
||||
scanQmlImports(qmlImportFolder)
|
||||
generateLibsXml()
|
||||
}
|
||||
}
|
||||
|
||||
task setupDependencies(dependsOn: [setupScribe, copyDependencies, extractGvrBinaries, qtBundle]) { }
|
||||
|
||||
task cleanDependencies(type: Delete) {
|
||||
delete HIFI_ANDROID_PRECOMPILED
|
||||
delete 'app/src/main/jniLibs/arm64-v8a'
|
||||
delete 'app/src/main/assets/bundled'
|
||||
delete 'app/src/main/res/values/libs.xml'
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
// FIXME this code is prototyping the desired functionality for doing build time binary dependency resolution.
|
||||
// See the comment on the qtBundle task above
|
||||
/*
|
||||
// FIXME derive the path from the gradle environment
|
||||
def toolchain = [
|
||||
version: '4.9',
|
||||
prefix: 'aarch64-linux-android',
|
||||
// FIXME derive from the host OS
|
||||
ndkHost: 'windows-x86_64',
|
||||
]
|
||||
|
||||
def findDependentLibrary = { String name ->
|
||||
def libFolders = [
|
||||
new File(qmlRoot, 'lib'),
|
||||
new File("${HIFI_ANDROID_PRECOMPILED}/tbb/lib/release"),
|
||||
new File("${HIFI_ANDROID_PRECOMPILED}/polyvox/lib/Release"),
|
||||
new File("${HIFI_ANDROID_PRECOMPILED}/polyvox/lib/"),
|
||||
new File("${HIFI_ANDROID_PRECOMPILED}/gvr/gvr-android-sdk-1.101.0/libraries"),
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
def readElfBinary = new File(android.ndkDirectory, "/toolchains/${toolchain.prefix}-${toolchain.version}/prebuilt/${toolchain.ndkHost}/bin/${toolchain.prefix}-readelf${EXEC_SUFFIX}")
|
||||
|
||||
def getDependencies = { File elfBinary ->
|
||||
Set<File> result = []
|
||||
Queue<File> pending = new LinkedList<>()
|
||||
pending.add(elfBinary)
|
||||
Set<File> scanned = []
|
||||
|
||||
Pattern p = ~/.*\(NEEDED\).*Shared library: \[(.*\.so)\]/
|
||||
while (!pending.isEmpty()) {
|
||||
File current = pending.remove()
|
||||
if (scanned.contains(current)) {
|
||||
continue
|
||||
}
|
||||
scanned.add(current)
|
||||
def command = "${readElfBinary} -d -W ${current.absolutePath}"
|
||||
captureOutput(command).split('[\r\n]').each { line ->
|
||||
Matcher m = p.matcher(line)
|
||||
if (!m.matches()) {
|
||||
return
|
||||
}
|
||||
def libName = m.group(1)
|
||||
def file = new File(qmlRoot, "lib/${libName}")
|
||||
if (file.exists()) {
|
||||
result.add(file)
|
||||
pending.add(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
task testElf (dependsOn: 'externalNativeBuildDebug') {
|
||||
doLast {
|
||||
def appLibraries = new HashSet<File>()
|
||||
def qtDependencies = new HashSet<File>()
|
||||
externalNativeBuildDebug.nativeBuildConfigurationsJsons.each { File file ->
|
||||
def json = new JsonSlurper().parse(file)
|
||||
json.libraries.each { node ->
|
||||
def outputFile = new File(node.value.output)
|
||||
if (outputFile.canonicalPath.startsWith(projectDir.canonicalPath)) {
|
||||
appLibraries.add(outputFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appLibraries.each { File file ->
|
||||
println getDependencies(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
87
android/build_recipes.md
Normal file
87
android/build_recipes.md
Normal file
|
@ -0,0 +1,87 @@
|
|||
Different libraries require different mechanism for building. Some are easiest with the standalone toolchain. Some are easiest with the ndk-build tool. Some can rely on CMake to do the right thing.
|
||||
|
||||
## Setup
|
||||
|
||||
### Android build environment
|
||||
|
||||
You need the Android NDK and SDK. The easiest way to get these is to download Android Studio for your platform and use the SDK manager in Studio to install the items. In particular you will need Android API levels 24 and 26, as well as the NDK, CMake, and LLDB. You should be using NDK version 16 or higher.
|
||||
|
||||
The Studio installation can install the SDK wherver you like. This guide assumes you install it to `$HOME/Android/SDK`. It will install the NDK inside the SDK in a folder named `ndk-bundle` but for convenience we will assume a symlink from `$HOME/Android/NDK` to the actual NDK folder exists
|
||||
|
||||
Additionally, some of the tools require a standalone toolchain in order to build. From the NDK build/tools directory you can execute the following command
|
||||
|
||||
`./make-standalone-toolchain.sh --arch=arm64 --platform=android-24 --install-dir=$HOME/Android/arm64_toolchain`
|
||||
|
||||
This will create the toolchain and install it in `$HOME/Android/arm64_toolchain`
|
||||
|
||||
When doing a build that relies on the toolchain you can execute the following commands to enable it
|
||||
|
||||
```
|
||||
target_host=aarch64-linux-android
|
||||
export PATH=$PATH:$HOME/Android/arm64_toolchain/bin
|
||||
export AR=$target_host-ar
|
||||
export AS=$target_host-as
|
||||
export CC=$target_host-gcc
|
||||
export CXX=$target_host-g++
|
||||
export LD=$target_host-ld
|
||||
export STRIP=$target_host-strip
|
||||
export CFLAGS="-fPIE -fPIC"
|
||||
export LDFLAGS="-pie"
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Qt
|
||||
|
||||
### Windows host
|
||||
|
||||
* Install the Android SDK
|
||||
* Install the Android NDK
|
||||
* Install Git for Windows
|
||||
* Install Strawberry Perl
|
||||
* Install Java 8 (Do NOT use Java 9, it will fail)
|
||||
* Install Python 3.6 for Windows
|
||||
* Open a Git Bash command prompt
|
||||
* Ensure the following commands are visible in the path with `which <command>`
|
||||
* gcc
|
||||
* javac
|
||||
* python
|
||||
* gmake
|
||||
* If any of them fail, fix your path and restart the bash prompt
|
||||
* Fetch the pre-built OpenSSL binaries for Android/iOS from here: https://github.com/leenjewel/openssl_for_ios_and_android/releases
|
||||
* Grab the latest release of the 1.0.2 series
|
||||
* Open the archive and extract the `/android/openssl-arm64-v8a` folder
|
||||
|
||||
* Download the Qt sources
|
||||
* `git clone git://code.qt.io/qt/qt5.git`
|
||||
* `cd qt5`
|
||||
* `perl init-repository`
|
||||
* `git checkout v5.9.3`
|
||||
* `git submodule update --recursive`
|
||||
* `cd ..`
|
||||
* Create a build directory with the command `mkdir qt5build`
|
||||
* Configure the Qt5 build with the command `../qt5/configure -xplatform android-clang -android-ndk-host windows-x86_64 -confirm-license -opensource --disable-rpath -nomake tests -nomake examples -skip qttranslations -skip qtserialport -skip qt3d -skip qtwebengine -skip qtlocation -skip qtwayland -skip qtsensors -skip qtgamepad -skip qtgamepad -skip qtspeech -skip qtcharts -skip qtx11extras -skip qtmacextras -skip qtvirtualkeyboard -skip qtpurchasing -skip qtdatavis3d -android-ndk C:/Android/NDK -android-toolchain-version 4.9 -android-arch arm64-v8a -no-warnings-are-errors -android-ndk-platform android-24 -v -platform win32-g++ -prefix C:/qt5build_debug -android-sdk C:/Android/SDK -c++std c++14 -openssl-linked -L<PATH_TO_SSL>/lib -I<PATH_TO_SSL>/include`
|
||||
|
||||
|
||||
|
||||
## TBB
|
||||
|
||||
Use the ndk-build tool
|
||||
|
||||
ndk-build tbb tbbmalloc target=android arch=ia32 tbb_os=windows ndk_version=16
|
||||
|
||||
## OpenSSL
|
||||
|
||||
Use a standalone toolchain
|
||||
|
||||
* Grab the latest 1.1.0x series source from https://github.com/openssl/openssl/releases
|
||||
* Follow the NDK guidelines for building a standalone toolchain for aarch64
|
||||
* Use the following script to configure and build OpenSSL
|
||||
* Enable the standalone toolchain with the export commands described above
|
||||
* Configure SSL with the command `./Configure android64-aarch64 no-asm no-ssl2 no-ssl3 no-comp no-hw no-engine --prefix=$HOME/Android/openssl_1.1.0g`
|
||||
* Build and install SSL with the command `make depend && make && make install`
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -6,7 +6,11 @@
|
|||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
macro(TARGET_GLM)
|
||||
add_dependency_external_projects(glm)
|
||||
find_package(GLM REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})
|
||||
if (ANDROID)
|
||||
set(GLM_INCLUDE_DIRS "${HIFI_ANDROID_PRECOMPILED}/glm/include")
|
||||
else()
|
||||
add_dependency_external_projects(glm)
|
||||
find_package(GLM REQUIRED)
|
||||
endif()
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})
|
||||
endmacro()
|
|
@ -12,10 +12,8 @@ function(JOIN VALUES GLUE OUTPUT)
|
|||
endfunction()
|
||||
|
||||
|
||||
if (NOT DEV_BUILD)
|
||||
set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc)
|
||||
generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg)
|
||||
endif()
|
||||
set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc)
|
||||
generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg)
|
||||
|
||||
# set a default root dir for each of our optional externals if it was not passed
|
||||
set(OPTIONAL_EXTERNALS "LeapMotion")
|
||||
|
@ -58,14 +56,6 @@ else ()
|
|||
list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP})
|
||||
endif ()
|
||||
|
||||
if (ANDROID)
|
||||
set(PLATFORM_QT_COMPONENTS AndroidExtras)
|
||||
set(PLATFORM_QT_LIBRARIES Qt5::AndroidExtras)
|
||||
else ()
|
||||
set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets)
|
||||
set(PLATFORM_QT_LIBRARIES Qt5::WebEngine Qt5::WebEngineWidgets)
|
||||
endif ()
|
||||
|
||||
find_package(
|
||||
Qt5 COMPONENTS
|
||||
Gui Multimedia Network OpenGL Qml Quick Script Svg
|
||||
|
@ -82,9 +72,7 @@ qt5_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}")
|
|||
# add them to the interface source files
|
||||
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}")
|
||||
|
||||
if (NOT DEV_BUILD)
|
||||
list(APPEND INTERFACE_SRCS ${INTERFACE_QML_QRC})
|
||||
endif()
|
||||
|
||||
if (UNIX)
|
||||
install(
|
||||
|
@ -213,14 +201,14 @@ endif()
|
|||
|
||||
# link required hifi libraries
|
||||
link_hifi_libraries(
|
||||
shared octree ktx gpu gl gpu-gl procedural model render
|
||||
shared octree ktx gpu gl procedural model render
|
||||
pointers
|
||||
recording fbx networking model-networking entities avatars trackers
|
||||
audio audio-client animation script-engine physics
|
||||
render-utils entities-renderer avatars-renderer ui auto-updater midi
|
||||
controllers plugins image trackers
|
||||
ui-plugins display-plugins input-plugins
|
||||
${NON_ANDROID_LIBRARIES}
|
||||
${PLATFORM_GL_BACKEND}
|
||||
)
|
||||
|
||||
# include the binary directory of render-utils for shader includes
|
||||
|
|
|
@ -56,29 +56,28 @@
|
|||
|
||||
{
|
||||
"from": "Vive.LeftFoot", "to" : "Standard.LeftFoot",
|
||||
"filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}]
|
||||
"filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}]
|
||||
},
|
||||
|
||||
{
|
||||
"from": "Vive.RightFoot", "to" : "Standard.RightFoot",
|
||||
"filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}]
|
||||
"filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}]
|
||||
},
|
||||
|
||||
{
|
||||
"from": "Vive.Hips", "to" : "Standard.Hips",
|
||||
"filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}]
|
||||
"filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}]
|
||||
},
|
||||
|
||||
{
|
||||
"from": "Vive.Spine2", "to" : "Standard.Spine2",
|
||||
"filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}]
|
||||
"filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}]
|
||||
},
|
||||
|
||||
{ "from": "Vive.Head", "to" : "Standard.Head"},
|
||||
|
||||
{ "from": "Vive.RightArm", "to" : "Standard.RightArm" },
|
||||
{ "from": "Vive.LeftArm", "to" : "Standard.LeftArm" },
|
||||
|
||||
|
||||
{ "from": "Vive.TrackedObject00", "to" : "Standard.TrackedObject00" },
|
||||
{ "from": "Vive.TrackedObject01", "to" : "Standard.TrackedObject01" },
|
||||
{ "from": "Vive.TrackedObject02", "to" : "Standard.TrackedObject02" },
|
||||
|
|
|
@ -597,18 +597,11 @@ Item {
|
|||
// Function body by Howard Stearns 2017-01-08
|
||||
function goToUserInDomain(avatarUuid) {
|
||||
var avatar = AvatarList.getAvatar(avatarUuid);
|
||||
if (!avatar) {
|
||||
if (!avatar || !avatar.position || !avatar.orientation) {
|
||||
console.log("This avatar is no longer present. goToUserInDomain() failed.");
|
||||
return;
|
||||
}
|
||||
// FIXME: We would like the avatar to recompute the avatar's "maybe fly" test at the new position, so that if high enough up,
|
||||
// the avatar goes into fly mode rather than falling. However, that is not exposed to Javascript right now.
|
||||
// FIXME: it would be nice if this used the same teleport steps and smoothing as in the teleport.js script.
|
||||
// Note, however, that this script allows teleporting to a person in the air, while teleport.js is going to a grounded target.
|
||||
// Position avatar 2 metres from the target in the direction that target avatar was facing.
|
||||
MyAvatar.position = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.orientation, {x: 0, y: 0, z: -2}));
|
||||
|
||||
// Rotate avatar on Y axis to face target avatar and cancel out any inherited roll and pitch.
|
||||
MyAvatar.orientation = Quat.cancelOutRollAndPitch(Quat.multiply(avatar.orientation, {y: 1}));
|
||||
// This is the last step of what AddressManager.goToUser does, but we don't need to resolve the username.
|
||||
MyAvatar.goToLocation(avatar.position, true, Quat.cancelOutRollAndPitch(avatar.orientation), true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ Rectangle {
|
|||
property bool pendingInventoryReply: true;
|
||||
property bool isShowingMyItems: false;
|
||||
property bool isDebuggingFirstUseTutorial: false;
|
||||
property int pendingItemCount: 0;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
Connections {
|
||||
|
@ -79,18 +80,22 @@ Rectangle {
|
|||
onInventoryResult: {
|
||||
purchasesReceived = true;
|
||||
|
||||
if (root.pendingInventoryReply) {
|
||||
inventoryTimer.start();
|
||||
}
|
||||
|
||||
if (result.status !== 'success') {
|
||||
console.log("Failed to get purchases", result.message);
|
||||
} else {
|
||||
} else if (!purchasesContentsList.dragging) { // Don't modify the view if the user's scrolling
|
||||
var inventoryResult = processInventoryResult(result.data.assets);
|
||||
|
||||
var currentIndex = purchasesContentsList.currentIndex === -1 ? 0 : purchasesContentsList.currentIndex;
|
||||
purchasesModel.clear();
|
||||
purchasesModel.append(inventoryResult);
|
||||
|
||||
root.pendingItemCount = 0;
|
||||
for (var i = 0; i < purchasesModel.count; i++) {
|
||||
if (purchasesModel.get(i).status === "pending") {
|
||||
root.pendingItemCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (previousPurchasesModel.count !== 0) {
|
||||
checkIfAnyItemStatusChanged();
|
||||
} else {
|
||||
|
@ -103,6 +108,12 @@ Rectangle {
|
|||
previousPurchasesModel.append(inventoryResult);
|
||||
|
||||
buildFilteredPurchasesModel();
|
||||
|
||||
purchasesContentsList.positionViewAtIndex(currentIndex, ListView.Beginning);
|
||||
}
|
||||
|
||||
if (root.pendingInventoryReply && root.pendingItemCount > 0) {
|
||||
inventoryTimer.start();
|
||||
}
|
||||
|
||||
root.pendingInventoryReply = false;
|
||||
|
@ -419,6 +430,8 @@ Rectangle {
|
|||
visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0);
|
||||
clip: true;
|
||||
model: filteredPurchasesModel;
|
||||
snapMode: ListView.SnapToItem;
|
||||
highlightRangeMode: ListView.StrictlyEnforceRange;
|
||||
// Anchors
|
||||
anchors.top: root.canRezCertifiedItems ? separator.bottom : cantRezCertified.bottom;
|
||||
anchors.topMargin: 12;
|
||||
|
|
|
@ -53,7 +53,7 @@ Item {
|
|||
|
||||
// Title Bar text
|
||||
RalewaySemiBold {
|
||||
text: "HIFI COMMERCE - LOGIN";
|
||||
text: "Log in to continue";
|
||||
// Text size
|
||||
size: hifi.fontSizes.overlayTitle;
|
||||
// Anchors
|
||||
|
|
|
@ -25,8 +25,12 @@ Item {
|
|||
HifiConstants { id: hifi; }
|
||||
|
||||
id: root;
|
||||
property bool historyReceived: false;
|
||||
property bool initialHistoryReceived: false;
|
||||
property bool historyRequestPending: true;
|
||||
property bool noMoreHistoryData: false;
|
||||
property int pendingCount: 0;
|
||||
property int currentHistoryPage: 1;
|
||||
property var pagesAlreadyAdded: new Array();
|
||||
|
||||
Connections {
|
||||
target: Commerce;
|
||||
|
@ -36,32 +40,86 @@ Item {
|
|||
}
|
||||
|
||||
onHistoryResult : {
|
||||
historyReceived = true;
|
||||
if (result.status === 'success') {
|
||||
var sameItemCount = 0;
|
||||
tempTransactionHistoryModel.clear();
|
||||
|
||||
tempTransactionHistoryModel.append(result.data.history);
|
||||
|
||||
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
|
||||
if (!transactionHistoryModel.get(i)) {
|
||||
sameItemCount = -1;
|
||||
break;
|
||||
} else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type &&
|
||||
tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) {
|
||||
sameItemCount++;
|
||||
}
|
||||
}
|
||||
root.initialHistoryReceived = true;
|
||||
root.historyRequestPending = false;
|
||||
|
||||
if (sameItemCount !== tempTransactionHistoryModel.count) {
|
||||
transactionHistoryModel.clear();
|
||||
if (result.status === 'success') {
|
||||
var currentPage = parseInt(result.current_page);
|
||||
|
||||
if (result.data.history.length === 0) {
|
||||
root.noMoreHistoryData = true;
|
||||
console.log("No more data to retrieve from Commerce.history() endpoint.")
|
||||
} else if (root.currentHistoryPage === 1) {
|
||||
var sameItemCount = 0;
|
||||
tempTransactionHistoryModel.clear();
|
||||
|
||||
tempTransactionHistoryModel.append(result.data.history);
|
||||
|
||||
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
|
||||
transactionHistoryModel.append(tempTransactionHistoryModel.get(i));
|
||||
if (!transactionHistoryModel.get(i)) {
|
||||
sameItemCount = -1;
|
||||
break;
|
||||
} else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type &&
|
||||
tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) {
|
||||
sameItemCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (sameItemCount !== tempTransactionHistoryModel.count) {
|
||||
transactionHistoryModel.clear();
|
||||
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
|
||||
transactionHistoryModel.append(tempTransactionHistoryModel.get(i));
|
||||
}
|
||||
calculatePendingAndInvalidated();
|
||||
}
|
||||
} else {
|
||||
if (root.pagesAlreadyAdded.indexOf(currentPage) !== -1) {
|
||||
console.log("Page " + currentPage + " of history has already been added to the list.");
|
||||
} else {
|
||||
// First, add the history result to a temporary model
|
||||
tempTransactionHistoryModel.clear();
|
||||
tempTransactionHistoryModel.append(result.data.history);
|
||||
|
||||
// Make a note that we've already added this page to the model...
|
||||
root.pagesAlreadyAdded.push(currentPage);
|
||||
|
||||
var insertionIndex = 0;
|
||||
// If there's nothing in the model right now, we don't need to modify insertionIndex.
|
||||
if (transactionHistoryModel.count !== 0) {
|
||||
var currentIteratorPage;
|
||||
// Search through the whole transactionHistoryModel and look for the insertion point.
|
||||
// The insertion point is found when the result page from the server is less than
|
||||
// the page that the current item came from, OR when we've reached the end of the whole model.
|
||||
for (var i = 0; i < transactionHistoryModel.count; i++) {
|
||||
currentIteratorPage = transactionHistoryModel.get(i).resultIsFromPage;
|
||||
|
||||
if (currentPage < currentIteratorPage) {
|
||||
insertionIndex = i;
|
||||
break;
|
||||
} else if (i === transactionHistoryModel.count - 1) {
|
||||
insertionIndex = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go through the results we just got back from the server, setting the "resultIsFromPage"
|
||||
// property of those results and adding them to the main model.
|
||||
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
|
||||
tempTransactionHistoryModel.setProperty(i, "resultIsFromPage", currentPage);
|
||||
transactionHistoryModel.insert(i + insertionIndex, tempTransactionHistoryModel.get(i))
|
||||
}
|
||||
|
||||
calculatePendingAndInvalidated();
|
||||
}
|
||||
calculatePendingAndInvalidated();
|
||||
}
|
||||
}
|
||||
refreshTimer.start();
|
||||
|
||||
// Only auto-refresh if the user hasn't scrolled
|
||||
// and there is more data to grab
|
||||
if (transactionHistory.atYBeginning && !root.noMoreHistoryData) {
|
||||
refreshTimer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,9 +192,13 @@ Item {
|
|||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
historyReceived = false;
|
||||
transactionHistoryModel.clear();
|
||||
Commerce.balance();
|
||||
Commerce.history();
|
||||
initialHistoryReceived = false;
|
||||
root.currentHistoryPage = 1;
|
||||
root.noMoreHistoryData = false;
|
||||
root.historyRequestPending = true;
|
||||
Commerce.history(root.currentHistoryPage);
|
||||
} else {
|
||||
refreshTimer.stop();
|
||||
}
|
||||
|
@ -164,9 +226,12 @@ Item {
|
|||
id: refreshTimer;
|
||||
interval: 4000;
|
||||
onTriggered: {
|
||||
console.log("Refreshing Wallet Home...");
|
||||
Commerce.balance();
|
||||
Commerce.history();
|
||||
if (transactionHistory.atYBeginning) {
|
||||
console.log("Refreshing 1st Page of Recent Activity...");
|
||||
root.historyRequestPending = true;
|
||||
Commerce.balance();
|
||||
Commerce.history(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,7 +306,7 @@ Item {
|
|||
anchors.right: parent.right;
|
||||
|
||||
Item {
|
||||
visible: transactionHistoryModel.count === 0 && root.historyReceived;
|
||||
visible: transactionHistoryModel.count === 0 && root.initialHistoryReceived;
|
||||
anchors.centerIn: parent;
|
||||
width: parent.width - 12;
|
||||
height: parent.height;
|
||||
|
@ -364,7 +429,12 @@ Item {
|
|||
onAtYEndChanged: {
|
||||
if (transactionHistory.atYEnd) {
|
||||
console.log("User scrolled to the bottom of 'Recent Activity'.");
|
||||
// Grab next page of results and append to model
|
||||
if (!root.historyRequestPending && !root.noMoreHistoryData) {
|
||||
// Grab next page of results and append to model
|
||||
root.historyRequestPending = true;
|
||||
Commerce.history(++root.currentHistoryPage);
|
||||
console.log("Fetching Page " + root.currentHistoryPage + " of Recent Activity...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,10 +72,14 @@ StackView {
|
|||
Component { id: tabletWebView; TabletWebView {} }
|
||||
Component.onCompleted: {
|
||||
updateLocationText(false);
|
||||
addressLine.focus = !HMD.active;
|
||||
root.parentChanged.connect(center);
|
||||
center();
|
||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
|
||||
Qt.callLater(function() {
|
||||
addressBarDialog.keyboardEnabled = HMD.active;
|
||||
addressLine.forceActiveFocus();
|
||||
})
|
||||
}
|
||||
Component.onDestruction: {
|
||||
root.parentChanged.disconnect(center);
|
||||
|
|
|
@ -127,9 +127,15 @@ Item {
|
|||
|
||||
GridView {
|
||||
id: gridView
|
||||
|
||||
keyNavigationEnabled: false
|
||||
highlightFollowsCurrentItem: false
|
||||
|
||||
property int previousGridIndex: -1
|
||||
|
||||
// true if any of the buttons contains mouse
|
||||
property bool containsMouse: false
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: 20
|
||||
|
@ -162,15 +168,29 @@ Item {
|
|||
flow: GridView.LeftToRight
|
||||
model: page.proxyModel
|
||||
|
||||
delegate: Item {
|
||||
delegate: Control {
|
||||
id: wrapper
|
||||
width: gridView.cellWidth
|
||||
height: gridView.cellHeight
|
||||
|
||||
hoverEnabled: true
|
||||
|
||||
property bool containsMouse: gridView.containsMouse
|
||||
onHoveredChanged: {
|
||||
if (hovered && !gridView.containsMouse) {
|
||||
gridView.containsMouse = true
|
||||
} else {
|
||||
gridView.containsMouse = false
|
||||
}
|
||||
}
|
||||
|
||||
property var proxy: modelData
|
||||
|
||||
TabletButton {
|
||||
id: tabletButton
|
||||
scale: wrapper.hovered ? 1.25 : wrapper.containsMouse ? 0.75 : 1.0
|
||||
Behavior on scale { NumberAnimation { duration: 200; easing.type: Easing.Linear } }
|
||||
|
||||
anchors.centerIn: parent
|
||||
gridView: wrapper.GridView.view
|
||||
buttonIndex: page.proxyModel.buttonIndex(uuid);
|
||||
|
@ -224,6 +244,7 @@ Item {
|
|||
PageIndicator {
|
||||
id: pageIndicator
|
||||
currentIndex: swipeView.currentIndex
|
||||
visible: swipeView.count > 1
|
||||
|
||||
delegate: Item {
|
||||
width: 15
|
||||
|
|
|
@ -154,7 +154,6 @@
|
|||
#include "scripting/Audio.h"
|
||||
#include "networking/CloseEventSender.h"
|
||||
#include "scripting/TestScriptingInterface.h"
|
||||
#include "scripting/AccountScriptingInterface.h"
|
||||
#include "scripting/AssetMappingsScriptingInterface.h"
|
||||
#include "scripting/ClipboardScriptingInterface.h"
|
||||
#include "scripting/DesktopScriptingInterface.h"
|
||||
|
@ -739,6 +738,7 @@ const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 100.0f;
|
|||
const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f;
|
||||
const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true;
|
||||
const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false;
|
||||
const bool DEFAULT_PREFER_STYLUS_OVER_LASER = false;
|
||||
const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false;
|
||||
const QString DEFAULT_CURSOR_NAME = "DEFAULT";
|
||||
|
||||
|
@ -758,6 +758,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_desktopTabletScale("desktopTabletScale", DEFAULT_DESKTOP_TABLET_SCALE_PERCENT),
|
||||
_desktopTabletBecomesToolbarSetting("desktopTabletBecomesToolbar", DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR),
|
||||
_hmdTabletBecomesToolbarSetting("hmdTabletBecomesToolbar", DEFAULT_HMD_TABLET_BECOMES_TOOLBAR),
|
||||
_preferStylusOverLaserSetting("preferStylusOverLaser", DEFAULT_PREFER_STYLUS_OVER_LASER),
|
||||
_preferAvatarFingerOverStylusSetting("preferAvatarFingerOverStylus", DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS),
|
||||
_constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true),
|
||||
_preferredCursor("preferredCursor", DEFAULT_CURSOR_NAME),
|
||||
|
@ -2372,7 +2373,7 @@ void Application::initializeUi() {
|
|||
surfaceContext->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data());
|
||||
surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get<InputConfiguration>().data());
|
||||
|
||||
surfaceContext->setContextProperty("Account", AccountScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("Account", GlobalServicesScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface);
|
||||
surfaceContext->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("FaceTracker", DependencyManager::get<DdeFaceTracker>().data());
|
||||
|
@ -2581,6 +2582,10 @@ void Application::setHmdTabletBecomesToolbarSetting(bool value) {
|
|||
updateSystemTabletMode();
|
||||
}
|
||||
|
||||
void Application::setPreferStylusOverLaser(bool value) {
|
||||
_preferStylusOverLaserSetting.set(value);
|
||||
}
|
||||
|
||||
void Application::setPreferAvatarFingerOverStylus(bool value) {
|
||||
_preferAvatarFingerOverStylusSetting.set(value);
|
||||
}
|
||||
|
@ -5739,7 +5744,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
|
|||
scriptEngine->registerGlobalObject("ModelCache", DependencyManager::get<ModelCache>().data());
|
||||
scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
|
||||
|
||||
scriptEngine->registerGlobalObject("Account", AccountScriptingInterface::getInstance());
|
||||
scriptEngine->registerGlobalObject("Account", GlobalServicesScriptingInterface::getInstance());
|
||||
scriptEngine->registerGlobalObject("DialogsManager", _dialogsManagerScriptingInterface);
|
||||
|
||||
scriptEngine->registerGlobalObject("GlobalServices", GlobalServicesScriptingInterface::getInstance());
|
||||
|
|
|
@ -207,7 +207,11 @@ public:
|
|||
void setDesktopTabletBecomesToolbarSetting(bool value);
|
||||
bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); }
|
||||
void setHmdTabletBecomesToolbarSetting(bool value);
|
||||
bool getPreferAvatarFingerOverStylus() { return _preferAvatarFingerOverStylusSetting.get(); }
|
||||
bool getPreferStylusOverLaser() { return _preferStylusOverLaserSetting.get(); }
|
||||
void setPreferStylusOverLaser(bool value);
|
||||
// FIXME: Remove setting completely or make available through JavaScript API?
|
||||
//bool getPreferAvatarFingerOverStylus() { return _preferAvatarFingerOverStylusSetting.get(); }
|
||||
bool getPreferAvatarFingerOverStylus() { return false; }
|
||||
void setPreferAvatarFingerOverStylus(bool value);
|
||||
|
||||
float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); }
|
||||
|
@ -551,6 +555,7 @@ private:
|
|||
Setting::Handle<float> _desktopTabletScale;
|
||||
Setting::Handle<bool> _desktopTabletBecomesToolbarSetting;
|
||||
Setting::Handle<bool> _hmdTabletBecomesToolbarSetting;
|
||||
Setting::Handle<bool> _preferStylusOverLaserSetting;
|
||||
Setting::Handle<bool> _preferAvatarFingerOverStylusSetting;
|
||||
Setting::Handle<bool> _constrainToolbarPosition;
|
||||
Setting::Handle<QString> _preferredCursor;
|
||||
|
|
|
@ -424,6 +424,7 @@ void MyAvatar::update(float deltaTime) {
|
|||
emit positionGoneTo();
|
||||
// Run safety tests as soon as we can after goToLocation, or clear if we're not colliding.
|
||||
_physicsSafetyPending = getCollisionsEnabled();
|
||||
_characterController.recomputeFlying(); // In case we've gone to into the sky.
|
||||
}
|
||||
if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) {
|
||||
// When needed and ready, arrange to check and fix.
|
||||
|
@ -2315,6 +2316,19 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition,
|
|||
bool hasOrientation, const glm::quat& newOrientation,
|
||||
bool shouldFaceLocation) {
|
||||
|
||||
// Most cases of going to a place or user go through this now. Some possible improvements to think about in the future:
|
||||
// - It would be nice if this used the same teleport steps and smoothing as in the teleport.js script, as long as it
|
||||
// still worked if the target is in the air.
|
||||
// - Sometimes (such as the response from /api/v1/users/:username/location), the location can be stale, but there is a
|
||||
// node_id supplied by which we could update the information after going to the stale location first and "looking around".
|
||||
// This could be passed through AddressManager::goToAddressFromObject => AddressManager::handleViewpoint => here.
|
||||
// The trick is that you have to yield enough time to resolve the node_id.
|
||||
// - Instead of always doing the same thing for shouldFaceLocation -- which places users uncomfortabley on top of each other --
|
||||
// it would be nice to see how many users are already "at" a person or place, and place ourself in semicircle or other shape
|
||||
// around the target. Avatars and entities (specified by the node_id) could define an adjustable "face me" method that would
|
||||
// compute the position (e.g., so that if I'm on stage, going to me would compute an available seat in the audience rather than
|
||||
// being in my face on-stage). Note that this could work for going to an entity as well as to a person.
|
||||
|
||||
qCDebug(interfaceapp).nospace() << "MyAvatar goToLocation - moving to " << newPosition.x << ", "
|
||||
<< newPosition.y << ", " << newPosition.z;
|
||||
|
||||
|
@ -3152,6 +3166,7 @@ glm::mat4 MyAvatar::getLeftHandCalibrationMat() const {
|
|||
}
|
||||
|
||||
bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& orientation) {
|
||||
std::lock_guard<std::mutex> guard(_pinnedJointsMutex);
|
||||
auto hipsIndex = getJointIndex("Hips");
|
||||
if (index != hipsIndex) {
|
||||
qWarning() << "Pinning is only supported for the hips joint at the moment.";
|
||||
|
@ -3171,7 +3186,14 @@ bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& o
|
|||
return true;
|
||||
}
|
||||
|
||||
bool MyAvatar::isJointPinned(int index) {
|
||||
std::lock_guard<std::mutex> guard(_pinnedJointsMutex);
|
||||
auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index);
|
||||
return it != _pinnedJoints.end();
|
||||
}
|
||||
|
||||
bool MyAvatar::clearPinOnJoint(int index) {
|
||||
std::lock_guard<std::mutex> guard(_pinnedJointsMutex);
|
||||
auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index);
|
||||
if (it != _pinnedJoints.end()) {
|
||||
_pinnedJoints.erase(it);
|
||||
|
|
|
@ -448,9 +448,8 @@ public:
|
|||
virtual void clearJointData(const QString& name) override;
|
||||
virtual void clearJointsData() override;
|
||||
|
||||
|
||||
|
||||
Q_INVOKABLE bool pinJoint(int index, const glm::vec3& position, const glm::quat& orientation);
|
||||
bool isJointPinned(int index);
|
||||
Q_INVOKABLE bool clearPinOnJoint(int index);
|
||||
|
||||
Q_INVOKABLE float getIKErrorOnLastSolve() const;
|
||||
|
@ -837,6 +836,7 @@ private:
|
|||
bool getIsAway() const { return _isAway; }
|
||||
void setAway(bool value);
|
||||
|
||||
std::mutex _pinnedJointsMutex;
|
||||
std::vector<int> _pinnedJoints;
|
||||
|
||||
// height of user in sensor space, when standing erect.
|
||||
|
|
|
@ -34,12 +34,25 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle
|
|||
}
|
||||
|
||||
static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
|
||||
|
||||
glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix());
|
||||
|
||||
// check for pinned hips.
|
||||
auto hipsIndex = myAvatar->getJointIndex("Hips");
|
||||
if (myAvatar->isJointPinned(hipsIndex)) {
|
||||
Transform avatarTransform = myAvatar->getTransform();
|
||||
AnimPose result = AnimPose(worldToSensorMat * avatarTransform.getMatrix() * Matrices::Y_180);
|
||||
result.scale() = glm::vec3(1.0f, 1.0f, 1.0f);
|
||||
return result;
|
||||
} else {
|
||||
DebugDraw::getInstance().removeMarker("pinnedHips");
|
||||
}
|
||||
|
||||
glm::mat4 hipsMat = myAvatar->deriveBodyFromHMDSensor();
|
||||
glm::vec3 hipsPos = extractTranslation(hipsMat);
|
||||
glm::quat hipsRot = glmExtractRotation(hipsMat);
|
||||
|
||||
glm::mat4 avatarToWorldMat = myAvatar->getTransform().getMatrix();
|
||||
glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix());
|
||||
glm::mat4 avatarToSensorMat = worldToSensorMat * avatarToWorldMat;
|
||||
|
||||
// dampen hips rotation, by mixing it with the avatar orientation in sensor space
|
||||
|
@ -323,17 +336,25 @@ void MySkeletonModel::updateFingers() {
|
|||
for (auto& link : chain) {
|
||||
int index = _rig.indexOfJoint(link.second);
|
||||
if (index >= 0) {
|
||||
auto rotationFrameOffset = _jointRotationFrameOffsetMap.find(index);
|
||||
if (rotationFrameOffset == _jointRotationFrameOffsetMap.end()) {
|
||||
_jointRotationFrameOffsetMap.insert(std::pair<int, int>(index, 0));
|
||||
rotationFrameOffset = _jointRotationFrameOffsetMap.find(index);
|
||||
}
|
||||
auto pose = myAvatar->getControllerPoseInSensorFrame(link.first);
|
||||
|
||||
if (pose.valid) {
|
||||
glm::quat relRot = glm::inverse(prevAbsRot) * pose.getRotation();
|
||||
// only set the rotation for the finger joints, not the hands.
|
||||
if (link.first != controller::Action::LEFT_HAND && link.first != controller::Action::RIGHT_HAND) {
|
||||
_rig.setJointRotation(index, true, relRot, CONTROLLER_PRIORITY);
|
||||
rotationFrameOffset->second = 0;
|
||||
}
|
||||
prevAbsRot = pose.getRotation();
|
||||
} else {
|
||||
} else if (rotationFrameOffset->second == 1) { // if the pose is invalid and was set on previous frame we do clear ( current frame offset = 1 )
|
||||
_rig.clearJointAnimationPriority(index);
|
||||
}
|
||||
rotationFrameOffset->second++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ private:
|
|||
|
||||
AnimPose _prevHips; // sensor frame
|
||||
bool _prevHipsValid { false };
|
||||
|
||||
std::map<int, int> _jointRotationFrameOffsetMap;
|
||||
};
|
||||
|
||||
#endif // hifi_MySkeletonModel_h
|
||||
|
|
|
@ -72,11 +72,16 @@ void Ledger::signedSend(const QString& propertyName, const QByteArray& text, con
|
|||
send(endpoint, success, fail, QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request);
|
||||
}
|
||||
|
||||
void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) {
|
||||
void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& requestParams) {
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
QJsonObject request;
|
||||
request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys());
|
||||
send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, request);
|
||||
requestParams["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys());
|
||||
|
||||
send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, requestParams);
|
||||
}
|
||||
|
||||
void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) {
|
||||
QJsonObject requestParams;
|
||||
keysQuery(endpoint, success, fail, requestParams);
|
||||
}
|
||||
|
||||
void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure) {
|
||||
|
@ -169,6 +174,7 @@ void Ledger::historySuccess(QNetworkReply& reply) {
|
|||
QJsonObject newDataData;
|
||||
newDataData["history"] = newHistoryArray;
|
||||
newData["data"] = newDataData;
|
||||
newData["current_page"] = data["current_page"].toInt();
|
||||
emit historyResult(newData);
|
||||
}
|
||||
|
||||
|
@ -176,8 +182,11 @@ void Ledger::historyFailure(QNetworkReply& reply) {
|
|||
failResponse("history", reply);
|
||||
}
|
||||
|
||||
void Ledger::history(const QStringList& keys) {
|
||||
keysQuery("history", "historySuccess", "historyFailure");
|
||||
void Ledger::history(const QStringList& keys, const int& pageNumber) {
|
||||
QJsonObject params;
|
||||
params["per_page"] = 100;
|
||||
params["page"] = pageNumber;
|
||||
keysQuery("history", "historySuccess", "historyFailure", params);
|
||||
}
|
||||
|
||||
// The api/failResponse is called just for the side effect of logging.
|
||||
|
|
|
@ -29,7 +29,7 @@ public:
|
|||
bool receiveAt(const QString& hfc_key, const QString& old_key);
|
||||
void balance(const QStringList& keys);
|
||||
void inventory(const QStringList& keys);
|
||||
void history(const QStringList& keys);
|
||||
void history(const QStringList& keys, const int& pageNumber);
|
||||
void account();
|
||||
void reset();
|
||||
void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false);
|
||||
|
@ -79,6 +79,7 @@ private:
|
|||
QJsonObject apiResponse(const QString& label, QNetworkReply& reply);
|
||||
QJsonObject failResponse(const QString& label, QNetworkReply& reply);
|
||||
void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request);
|
||||
void keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& extraRequestParams);
|
||||
void keysQuery(const QString& endpoint, const QString& success, const QString& fail);
|
||||
void signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail, const bool controlled_failure = false);
|
||||
};
|
||||
|
|
|
@ -96,12 +96,12 @@ void QmlCommerce::inventory() {
|
|||
}
|
||||
}
|
||||
|
||||
void QmlCommerce::history() {
|
||||
void QmlCommerce::history(const int& pageNumber) {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
QStringList cachedPublicKeys = wallet->listPublicKeys();
|
||||
if (!cachedPublicKeys.isEmpty()) {
|
||||
ledger->history(cachedPublicKeys);
|
||||
ledger->history(cachedPublicKeys, pageNumber);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ protected:
|
|||
Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false);
|
||||
Q_INVOKABLE void balance();
|
||||
Q_INVOKABLE void inventory();
|
||||
Q_INVOKABLE void history();
|
||||
Q_INVOKABLE void history(const int& pageNumber);
|
||||
Q_INVOKABLE void generateKeyPair();
|
||||
Q_INVOKABLE void reset();
|
||||
Q_INVOKABLE void resetLocalWalletOnly();
|
||||
|
|
|
@ -218,14 +218,40 @@ Pointer::PickedObject LaserPointer::getHoveredObject(const PickResultPointer& pi
|
|||
return PickedObject(rayPickResult->objectID, rayPickResult->type);
|
||||
}
|
||||
|
||||
Pointer::Buttons LaserPointer::getPressedButtons() {
|
||||
Pointer::Buttons LaserPointer::getPressedButtons(const PickResultPointer& pickResult) {
|
||||
std::unordered_set<std::string> toReturn;
|
||||
for (const PointerTrigger& trigger : _triggers) {
|
||||
// TODO: right now, LaserPointers don't support axes, only on/off buttons
|
||||
if (trigger.getEndpoint()->peek() >= 1.0f) {
|
||||
toReturn.insert(trigger.getButton());
|
||||
auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
|
||||
|
||||
if (rayPickResult) {
|
||||
for (const PointerTrigger& trigger : _triggers) {
|
||||
std::string button = trigger.getButton();
|
||||
TriggerState& state = _states[button];
|
||||
// TODO: right now, LaserPointers don't support axes, only on/off buttons
|
||||
if (trigger.getEndpoint()->peek() >= 1.0f) {
|
||||
toReturn.insert(button);
|
||||
|
||||
if (_previousButtons.find(button) == _previousButtons.end()) {
|
||||
// start triggering for buttons that were just pressed
|
||||
state.triggeredObject = PickedObject(rayPickResult->objectID, rayPickResult->type);
|
||||
state.intersection = rayPickResult->intersection;
|
||||
state.triggerPos2D = findPos2D(state.triggeredObject, rayPickResult->intersection);
|
||||
state.triggerStartTime = usecTimestampNow();
|
||||
state.surfaceNormal = rayPickResult->surfaceNormal;
|
||||
state.deadspotExpired = false;
|
||||
state.wasTriggering = true;
|
||||
state.triggering = true;
|
||||
_latestState = state;
|
||||
}
|
||||
} else {
|
||||
// stop triggering for buttons that aren't pressed
|
||||
state.wasTriggering = state.triggering;
|
||||
state.triggering = false;
|
||||
_latestState = state;
|
||||
}
|
||||
}
|
||||
_previousButtons = toReturn;
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
|
@ -303,7 +329,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
|
|||
return RenderState(startID, pathID, endID);
|
||||
}
|
||||
|
||||
PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover) const {
|
||||
PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) {
|
||||
QUuid pickedID;
|
||||
glm::vec3 intersection, surfaceNormal, direction, origin;
|
||||
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
|
||||
|
@ -316,20 +342,48 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const P
|
|||
pickedID = rayPickResult->objectID;
|
||||
}
|
||||
|
||||
glm::vec2 pos2D;
|
||||
if (pickedID != target.objectID) {
|
||||
if (target.type == ENTITY) {
|
||||
intersection = RayPick::intersectRayWithEntityXYPlane(target.objectID, origin, direction);
|
||||
} else if (target.type == OVERLAY) {
|
||||
intersection = RayPick::intersectRayWithOverlayXYPlane(target.objectID, origin, direction);
|
||||
}
|
||||
intersection = findIntersection(target, origin, direction);
|
||||
}
|
||||
if (target.type == ENTITY) {
|
||||
pos2D = RayPick::projectOntoEntityXYPlane(target.objectID, intersection);
|
||||
} else if (target.type == OVERLAY) {
|
||||
pos2D = RayPick::projectOntoOverlayXYPlane(target.objectID, intersection);
|
||||
} else if (target.type == HUD) {
|
||||
pos2D = DependencyManager::get<PickManager>()->calculatePos2DFromHUD(intersection);
|
||||
glm::vec2 pos2D = findPos2D(target, intersection);
|
||||
|
||||
// If we just started triggering and we haven't moved too much, don't update intersection and pos2D
|
||||
TriggerState& state = hover ? _latestState : _states[button];
|
||||
float sensorToWorldScale = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
|
||||
float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale;
|
||||
bool withinDeadspot = usecTimestampNow() - state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, state.triggerPos2D) < deadspotSquared;
|
||||
if ((state.triggering || state.wasTriggering) && !state.deadspotExpired && withinDeadspot) {
|
||||
pos2D = state.triggerPos2D;
|
||||
intersection = state.intersection;
|
||||
surfaceNormal = state.surfaceNormal;
|
||||
}
|
||||
if (!withinDeadspot) {
|
||||
state.deadspotExpired = true;
|
||||
}
|
||||
|
||||
return PointerEvent(pos2D, intersection, surfaceNormal, direction);
|
||||
}
|
||||
|
||||
glm::vec3 LaserPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) {
|
||||
switch (pickedObject.type) {
|
||||
case ENTITY:
|
||||
return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction);
|
||||
case OVERLAY:
|
||||
return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction);
|
||||
default:
|
||||
return glm::vec3(NAN);
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec2 LaserPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) {
|
||||
switch (pickedObject.type) {
|
||||
case ENTITY:
|
||||
return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin);
|
||||
case OVERLAY:
|
||||
return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin);
|
||||
case HUD:
|
||||
return DependencyManager::get<PickManager>()->calculatePos2DFromHUD(origin);
|
||||
default:
|
||||
return glm::vec2(NAN);
|
||||
}
|
||||
}
|
|
@ -80,10 +80,10 @@ public:
|
|||
static RenderState buildRenderState(const QVariantMap& propMap);
|
||||
|
||||
protected:
|
||||
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover = true) const override;
|
||||
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override;
|
||||
|
||||
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
|
||||
Pointer::Buttons getPressedButtons() override;
|
||||
Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override;
|
||||
|
||||
bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
|
||||
bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
|
||||
|
@ -105,6 +105,23 @@ private:
|
|||
void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState);
|
||||
void disableRenderState(const RenderState& renderState);
|
||||
|
||||
struct TriggerState {
|
||||
PickedObject triggeredObject;
|
||||
glm::vec3 intersection { NAN };
|
||||
glm::vec3 surfaceNormal { NAN };
|
||||
glm::vec2 triggerPos2D { NAN };
|
||||
quint64 triggerStartTime { 0 };
|
||||
bool deadspotExpired { true };
|
||||
bool triggering { false };
|
||||
bool wasTriggering { false };
|
||||
};
|
||||
|
||||
Pointer::Buttons _previousButtons;
|
||||
std::unordered_map<std::string, TriggerState> _states;
|
||||
TriggerState _latestState;
|
||||
static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction);
|
||||
static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin);
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_LaserPointer_h
|
||||
|
|
|
@ -92,4 +92,4 @@ glm::vec2 RayPick::projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::
|
|||
glm::vec2 RayPick::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized) {
|
||||
auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
|
||||
return projectOntoXYPlane(worldPos, props.getPosition(), props.getRotation(), props.getDimensions(), props.getRegistrationPoint(), unNormalized);
|
||||
}
|
||||
}
|
|
@ -28,10 +28,6 @@ static const float TABLET_MAX_TOUCH_DISTANCE = 0.005f;
|
|||
static const float HOVER_HYSTERESIS = 0.01f;
|
||||
static const float TOUCH_HYSTERESIS = 0.001f;
|
||||
|
||||
static const float STYLUS_MOVE_DELAY = 0.33f * USECS_PER_SECOND;
|
||||
static const float TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481f;
|
||||
static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED = TOUCH_PRESS_TO_MOVE_DEADSPOT * TOUCH_PRESS_TO_MOVE_DEADSPOT;
|
||||
|
||||
StylusPointer::StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled) :
|
||||
Pointer(DependencyManager::get<PickScriptingInterface>()->createStylusPick(props), enabled, hover),
|
||||
_stylusOverlay(stylusOverlay)
|
||||
|
@ -112,37 +108,37 @@ bool StylusPointer::shouldHover(const PickResultPointer& pickResult) {
|
|||
|
||||
bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) {
|
||||
auto stylusPickResult = std::static_pointer_cast<const StylusPickResult>(pickResult);
|
||||
bool wasTriggering = false;
|
||||
if (_renderState == EVENTS_ON && stylusPickResult) {
|
||||
auto sensorScaleFactor = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
|
||||
float distance = stylusPickResult->distance;
|
||||
|
||||
// If we're triggering on an object, recalculate the distance instead of using the pickResult
|
||||
glm::vec3 origin = vec3FromVariant(stylusPickResult->pickVariant["position"]);
|
||||
glm::vec3 direction = -_state.surfaceNormal;
|
||||
if (!_state.triggeredObject.objectID.isNull() && stylusPickResult->objectID != _state.triggeredObject.objectID) {
|
||||
glm::vec3 direction = _state.triggering ? -_state.surfaceNormal : -stylusPickResult->surfaceNormal;
|
||||
if ((_state.triggering || _state.wasTriggering) && stylusPickResult->objectID != _state.triggeredObject.objectID) {
|
||||
distance = glm::dot(findIntersection(_state.triggeredObject, origin, direction) - origin, direction);
|
||||
}
|
||||
|
||||
float hysteresis = _state.triggering ? TOUCH_HYSTERESIS * sensorScaleFactor : 0.0f;
|
||||
if (isWithinBounds(distance, TABLET_MIN_TOUCH_DISTANCE * sensorScaleFactor,
|
||||
TABLET_MAX_TOUCH_DISTANCE * sensorScaleFactor, hysteresis)) {
|
||||
if (_state.triggeredObject.objectID.isNull()) {
|
||||
TABLET_MAX_TOUCH_DISTANCE * sensorScaleFactor, hysteresis)) {
|
||||
_state.wasTriggering = _state.triggering;
|
||||
if (!_state.triggering) {
|
||||
_state.triggeredObject = PickedObject(stylusPickResult->objectID, stylusPickResult->type);
|
||||
_state.intersection = findIntersection(_state.triggeredObject, origin, direction);
|
||||
_state.intersection = stylusPickResult->intersection;
|
||||
_state.triggerPos2D = findPos2D(_state.triggeredObject, origin);
|
||||
_state.triggerStartTime = usecTimestampNow();
|
||||
_state.surfaceNormal = stylusPickResult->surfaceNormal;
|
||||
_state.deadspotExpired = false;
|
||||
_state.triggering = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
wasTriggering = _state.triggering;
|
||||
}
|
||||
|
||||
_state.triggeredObject = PickedObject();
|
||||
_state.intersection = glm::vec3(NAN);
|
||||
_state.triggerPos2D = glm::vec2(NAN);
|
||||
_state.triggerStartTime = 0;
|
||||
_state.surfaceNormal = glm::vec3(NAN);
|
||||
_state.wasTriggering = wasTriggering;
|
||||
_state.triggering = false;
|
||||
return false;
|
||||
}
|
||||
|
@ -155,13 +151,13 @@ Pointer::PickedObject StylusPointer::getHoveredObject(const PickResultPointer& p
|
|||
return PickedObject(stylusPickResult->objectID, stylusPickResult->type);
|
||||
}
|
||||
|
||||
Pointer::Buttons StylusPointer::getPressedButtons() {
|
||||
Pointer::Buttons StylusPointer::getPressedButtons(const PickResultPointer& pickResult) {
|
||||
// TODO: custom buttons for styluses
|
||||
Pointer::Buttons toReturn({ "Primary", "Focus" });
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
PointerEvent StylusPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover) const {
|
||||
PointerEvent StylusPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) {
|
||||
QUuid pickedID;
|
||||
glm::vec2 pos2D;
|
||||
glm::vec3 intersection, surfaceNormal, direction, origin;
|
||||
|
@ -177,18 +173,22 @@ PointerEvent StylusPointer::buildPointerEvent(const PickedObject& target, const
|
|||
}
|
||||
|
||||
// If we just started triggering and we haven't moved too much, don't update intersection and pos2D
|
||||
if (!_state.triggeredObject.objectID.isNull() && usecTimestampNow() - _state.triggerStartTime < STYLUS_MOVE_DELAY &&
|
||||
glm::distance2(pos2D, _state.triggerPos2D) < TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED) {
|
||||
float sensorToWorldScale = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
|
||||
float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale;
|
||||
bool withinDeadspot = usecTimestampNow() - _state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, _state.triggerPos2D) < deadspotSquared;
|
||||
if ((_state.triggering || _state.wasTriggering) && !_state.deadspotExpired && withinDeadspot) {
|
||||
pos2D = _state.triggerPos2D;
|
||||
intersection = _state.intersection;
|
||||
} else if (pickedID != target.objectID) {
|
||||
intersection = findIntersection(target, origin, direction);
|
||||
}
|
||||
if (!withinDeadspot) {
|
||||
_state.deadspotExpired = true;
|
||||
}
|
||||
|
||||
return PointerEvent(pos2D, intersection, surfaceNormal, direction);
|
||||
}
|
||||
|
||||
|
||||
bool StylusPointer::isWithinBounds(float distance, float min, float max, float hysteresis) {
|
||||
return (distance == glm::clamp(distance, min - hysteresis, max + hysteresis));
|
||||
}
|
||||
|
|
|
@ -37,11 +37,11 @@ public:
|
|||
|
||||
protected:
|
||||
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
|
||||
Buttons getPressedButtons() override;
|
||||
Buttons getPressedButtons(const PickResultPointer& pickResult) override;
|
||||
bool shouldHover(const PickResultPointer& pickResult) override;
|
||||
bool shouldTrigger(const PickResultPointer& pickResult) override;
|
||||
|
||||
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover = true) const override;
|
||||
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override;
|
||||
|
||||
private:
|
||||
void show(const StylusTip& tip);
|
||||
|
@ -53,7 +53,9 @@ private:
|
|||
glm::vec2 triggerPos2D { NAN };
|
||||
glm::vec3 surfaceNormal { NAN };
|
||||
quint64 triggerStartTime { 0 };
|
||||
bool deadspotExpired { true };
|
||||
bool triggering { false };
|
||||
bool wasTriggering { false };
|
||||
|
||||
bool hovering { false };
|
||||
};
|
||||
|
|
|
@ -12,47 +12,25 @@
|
|||
#include "AccountManager.h"
|
||||
|
||||
#include "AccountScriptingInterface.h"
|
||||
#include "GlobalServicesScriptingInterface.h"
|
||||
|
||||
AccountScriptingInterface* AccountScriptingInterface::getInstance() {
|
||||
static AccountScriptingInterface sharedInstance;
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
QObject::connect(accountManager.data(), &AccountManager::profileChanged,
|
||||
&sharedInstance, &AccountScriptingInterface::usernameChanged);
|
||||
QObject::connect(accountManager.data(), &AccountManager::usernameChanged,
|
||||
&sharedInstance, &AccountScriptingInterface::onUsernameChanged);
|
||||
return &sharedInstance;
|
||||
}
|
||||
|
||||
bool AccountScriptingInterface::isLoggedIn() {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
return accountManager->isLoggedIn();
|
||||
}
|
||||
|
||||
bool AccountScriptingInterface::checkAndSignalForAccessToken() {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
return accountManager->checkAndSignalForAccessToken();
|
||||
return GlobalServicesScriptingInterface::getInstance()->isLoggedIn();
|
||||
}
|
||||
|
||||
void AccountScriptingInterface::logOut() {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
return accountManager->logout();
|
||||
GlobalServicesScriptingInterface::getInstance()->logOut();
|
||||
}
|
||||
|
||||
AccountScriptingInterface::AccountScriptingInterface(QObject *parent): QObject(parent) {
|
||||
m_loggedIn = isLoggedIn();
|
||||
emit loggedInChanged(m_loggedIn);
|
||||
}
|
||||
|
||||
void AccountScriptingInterface::onUsernameChanged(QString username) {
|
||||
m_loggedIn = (username != QString());
|
||||
emit loggedInChanged(m_loggedIn);
|
||||
bool AccountScriptingInterface::loggedIn() const {
|
||||
return GlobalServicesScriptingInterface::getInstance()->loggedIn();
|
||||
}
|
||||
|
||||
QString AccountScriptingInterface::getUsername() {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
if (accountManager->isLoggedIn()) {
|
||||
return accountManager->getAccountInfo().getUsername();
|
||||
} else {
|
||||
return "Unknown user";
|
||||
}
|
||||
return GlobalServicesScriptingInterface::getInstance()->getUsername();
|
||||
}
|
||||
|
|
|
@ -17,18 +17,12 @@
|
|||
class AccountScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString username READ getUsername NOTIFY usernameChanged)
|
||||
Q_PROPERTY(bool loggedIn READ loggedIn NOTIFY loggedInChanged)
|
||||
|
||||
/**jsdoc
|
||||
* @namespace Account
|
||||
* @property username {String} username if user is logged in, otherwise it returns "Unknown user"
|
||||
*/
|
||||
|
||||
public:
|
||||
|
||||
Q_PROPERTY(QUrl metaverseServerURL READ getMetaverseServerURL)
|
||||
QUrl getMetaverseServerURL() { return DependencyManager::get<AccountManager>()->getMetaverseServerURL(); }
|
||||
Q_PROPERTY(QString username READ getUsername)
|
||||
Q_PROPERTY(bool loggedIn READ loggedIn)
|
||||
|
||||
signals:
|
||||
|
||||
|
@ -56,21 +50,11 @@ public slots:
|
|||
* @return {bool} true when user is logged into the High Fidelity metaverse.
|
||||
*/
|
||||
bool isLoggedIn();
|
||||
bool checkAndSignalForAccessToken();
|
||||
void logOut();
|
||||
|
||||
public:
|
||||
AccountScriptingInterface(QObject* parent = nullptr);
|
||||
bool loggedIn() const {
|
||||
return m_loggedIn;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void onUsernameChanged(QString username);
|
||||
|
||||
private:
|
||||
bool m_loggedIn { false };
|
||||
|
||||
AccountScriptingInterface(QObject* parent = nullptr) {}
|
||||
bool loggedIn() const;
|
||||
};
|
||||
|
||||
#endif // hifi_AccountScriptingInterface_h
|
||||
|
|
|
@ -31,6 +31,8 @@ AssetMappingsScriptingInterface::AssetMappingsScriptingInterface() {
|
|||
_proxyModel.setSourceModel(&_assetMappingModel);
|
||||
_proxyModel.setSortRole(Qt::DisplayRole);
|
||||
_proxyModel.setDynamicSortFilter(true);
|
||||
_proxyModel.setSortLocaleAware(true);
|
||||
_proxyModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
_proxyModel.sort(0);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
connect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged);
|
||||
connect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::onUsernameChanged);
|
||||
connect(accountManager.data(), &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut);
|
||||
connect(accountManager.data(), &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected);
|
||||
|
||||
|
@ -31,11 +31,14 @@ GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() {
|
|||
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
|
||||
connect(discoverabilityManager.data(), &DiscoverabilityManager::discoverabilityModeChanged,
|
||||
this, &GlobalServicesScriptingInterface::discoverabilityModeChanged);
|
||||
|
||||
_loggedIn = isLoggedIn();
|
||||
emit loggedInChanged(_loggedIn);
|
||||
}
|
||||
|
||||
GlobalServicesScriptingInterface::~GlobalServicesScriptingInterface() {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
disconnect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged);
|
||||
disconnect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::onUsernameChanged);
|
||||
disconnect(accountManager.data(), &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut);
|
||||
disconnect(accountManager.data(), &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected);
|
||||
}
|
||||
|
@ -45,8 +48,28 @@ GlobalServicesScriptingInterface* GlobalServicesScriptingInterface::getInstance(
|
|||
return &sharedInstance;
|
||||
}
|
||||
|
||||
const QString& GlobalServicesScriptingInterface::getUsername() const {
|
||||
return DependencyManager::get<AccountManager>()->getAccountInfo().getUsername();
|
||||
const QString GlobalServicesScriptingInterface::getUsername() const {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
if (accountManager->isLoggedIn()) {
|
||||
return accountManager->getAccountInfo().getUsername();
|
||||
} else {
|
||||
return "Unknown user";
|
||||
}
|
||||
}
|
||||
|
||||
bool GlobalServicesScriptingInterface::isLoggedIn() {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
return accountManager->isLoggedIn();
|
||||
}
|
||||
|
||||
bool GlobalServicesScriptingInterface::checkAndSignalForAccessToken() {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
return accountManager->checkAndSignalForAccessToken();
|
||||
}
|
||||
|
||||
void GlobalServicesScriptingInterface::logOut() {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
return accountManager->logout();
|
||||
}
|
||||
|
||||
void GlobalServicesScriptingInterface::loggedOut() {
|
||||
|
@ -77,6 +100,12 @@ void GlobalServicesScriptingInterface::discoverabilityModeChanged(Discoverabilit
|
|||
emit findableByChanged(DiscoverabilityManager::findableByString(discoverabilityMode));
|
||||
}
|
||||
|
||||
void GlobalServicesScriptingInterface::onUsernameChanged(const QString& username) {
|
||||
_loggedIn = (username != QString());
|
||||
emit myUsernameChanged(username);
|
||||
emit loggedInChanged(_loggedIn);
|
||||
}
|
||||
|
||||
DownloadInfoResult::DownloadInfoResult() :
|
||||
downloading(QList<float>()),
|
||||
pending(0.0f)
|
||||
|
|
|
@ -35,17 +35,25 @@ void DownloadInfoResultFromScriptValue(const QScriptValue& object, DownloadInfoR
|
|||
class GlobalServicesScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString username READ getUsername)
|
||||
Q_PROPERTY(QString username READ getUsername NOTIFY myUsernameChanged)
|
||||
Q_PROPERTY(bool loggedIn READ loggedIn NOTIFY loggedInChanged)
|
||||
Q_PROPERTY(QString findableBy READ getFindableBy WRITE setFindableBy NOTIFY findableByChanged)
|
||||
Q_PROPERTY(QUrl metaverseServerURL READ getMetaverseServerURL)
|
||||
|
||||
public:
|
||||
static GlobalServicesScriptingInterface* getInstance();
|
||||
|
||||
const QString& getUsername() const;
|
||||
const QString getUsername() const;
|
||||
bool loggedIn() const { return _loggedIn; }
|
||||
QUrl getMetaverseServerURL() { return DependencyManager::get<AccountManager>()->getMetaverseServerURL(); }
|
||||
|
||||
public slots:
|
||||
DownloadInfoResult getDownloadInfo();
|
||||
void updateDownloadInfo();
|
||||
|
||||
bool isLoggedIn();
|
||||
bool checkAndSignalForAccessToken();
|
||||
void logOut();
|
||||
|
||||
private slots:
|
||||
void loggedOut();
|
||||
|
@ -55,18 +63,22 @@ private slots:
|
|||
void setFindableBy(const QString& discoverabilityMode);
|
||||
void discoverabilityModeChanged(Discoverability::Mode discoverabilityMode);
|
||||
|
||||
void onUsernameChanged(const QString& username);
|
||||
|
||||
signals:
|
||||
void connected();
|
||||
void disconnected(const QString& reason);
|
||||
void myUsernameChanged(const QString& username);
|
||||
void downloadInfoChanged(DownloadInfoResult info);
|
||||
void findableByChanged(const QString& discoverabilityMode);
|
||||
void loggedInChanged(bool loggedIn);
|
||||
|
||||
private:
|
||||
GlobalServicesScriptingInterface();
|
||||
~GlobalServicesScriptingInterface();
|
||||
|
||||
bool _downloading;
|
||||
bool _loggedIn{ false };
|
||||
};
|
||||
|
||||
#endif // hifi_GlobalServicesScriptingInterface_h
|
||||
|
|
|
@ -192,8 +192,7 @@ void WindowScriptingInterface::ensureReticleVisible() const {
|
|||
/// Display a "browse to directory" dialog. If `directory` is an invalid file or directory the browser will start at the current
|
||||
/// working directory.
|
||||
/// \param const QString& title title of the window
|
||||
/// \param const QString& directory directory to start the file browser at
|
||||
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
|
||||
/// \param const QString& directory directory to start the directory browser at
|
||||
/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
|
||||
QScriptValue WindowScriptingInterface::browseDir(const QString& title, const QString& directory) {
|
||||
ensureReticleVisible();
|
||||
|
@ -214,8 +213,7 @@ QScriptValue WindowScriptingInterface::browseDir(const QString& title, const QSt
|
|||
/// Display a "browse to directory" dialog. If `directory` is an invalid file or directory the browser will start at the current
|
||||
/// working directory.
|
||||
/// \param const QString& title title of the window
|
||||
/// \param const QString& directory directory to start the file browser at
|
||||
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
|
||||
/// \param const QString& directory directory to start the directory browser at
|
||||
void WindowScriptingInterface::browseDirAsync(const QString& title, const QString& directory) {
|
||||
ensureReticleVisible();
|
||||
QString path = directory;
|
||||
|
@ -459,6 +457,41 @@ int WindowScriptingInterface::openMessageBox(QString title, QString text, int bu
|
|||
return createMessageBox(title, text, buttons, defaultButton);
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* <p>The buttons that may be included in a message box created by {@link Window.openMessageBox|openMessageBox} are defined by
|
||||
* numeric values:
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr>
|
||||
* <th>Button</th>
|
||||
* <th>Value</th>
|
||||
* <th>Description</th>
|
||||
* </tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr> <td><strong>NoButton</strong></td> <td><code>0x0</code></td> <td>An invalid button.</td> </tr>
|
||||
* <tr> <td><strong>Ok</strong></td> <td><code>0x400</code></td> <td>"OK"</td> </tr>
|
||||
* <tr> <td><strong>Save</strong></td> <td><code>0x800</code></td> <td>"Save"</td> </tr>
|
||||
* <tr> <td><strong>SaveAll</strong></td> <td><code>0x1000</code></td> <td>"Save All"</td> </tr>
|
||||
* <tr> <td><strong>Open</strong></td> <td><code>0x2000</code></td> <td>"Open"</td> </tr>
|
||||
* <tr> <td><strong>Yes</strong></td> <td><code>0x4000</code></td> <td>"Yes"</td> </tr>
|
||||
* <tr> <td><strong>YesToAll</strong></td> <td><code>0x8000</code></td> <td>"Yes to All"</td> </tr>
|
||||
* <tr> <td><strong>No</strong></td> <td><code>0x10000</code></td> <td>"No"</td> </tr>
|
||||
* <tr> <td><strong>NoToAll</strong></td> <td><code>0x20000</code></td> <td>"No to All"</td> </tr>
|
||||
* <tr> <td><strong>Abort</strong></td> <td><code>0x40000</code></td> <td>"Abort"</td> </tr>
|
||||
* <tr> <td><strong>Retry</strong></td> <td><code>0x80000</code></td> <td>"Retry"</td> </tr>
|
||||
* <tr> <td><strong>Ignore</strong></td> <td><code>0x100000</code></td> <td>"Ignore"</td> </tr>
|
||||
* <tr> <td><strong>Close</strong></td> <td><code>0x200000</code></td> <td>"Close"</td> </tr>
|
||||
* <tr> <td><strong>Cancel</strong></td> <td><code>0x400000</code></td> <td>"Cancel"</td> </tr>
|
||||
* <tr> <td><strong>Discard</strong></td> <td><code>0x800000</code></td> <td>"Discard" or "Don't Save"</td> </tr>
|
||||
* <tr> <td><strong>Help</strong></td> <td><code>0x1000000</code></td> <td>"Help"</td> </tr>
|
||||
* <tr> <td><strong>Apply</strong></td> <td><code>0x2000000</code></td> <td>"Apply"</td> </tr>
|
||||
* <tr> <td><strong>Reset</strong></td> <td><code>0x4000000</code></td> <td>"Reset"</td> </tr>
|
||||
* <tr> <td><strong>RestoreDefaults</strong></td> <td><code>0x8000000</code></td> <td>"Restore Defaults"</td> </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* @typedef Window.MessageBoxButton
|
||||
*/
|
||||
int WindowScriptingInterface::createMessageBox(QString title, QString text, int buttons, int defaultButton) {
|
||||
auto messageBox = DependencyManager::get<OffscreenUi>()->createMessageBox(OffscreenUi::ICON_INFORMATION, title, text,
|
||||
static_cast<QFlags<QMessageBox::StandardButton>>(buttons), static_cast<QMessageBox::StandardButton>(defaultButton));
|
||||
|
|
|
@ -33,6 +33,21 @@ QScriptValue CustomPromptResultToScriptValue(QScriptEngine* engine, const Custom
|
|||
void CustomPromptResultFromScriptValue(const QScriptValue& object, CustomPromptResult& result);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* The Window API provides various facilities not covered elsewhere: window dimensions, window focus, normal or entity camera
|
||||
* view, clipboard, announcements, user connections, common dialog boxes, snapshots, file import, domain changes, domain
|
||||
* physics.
|
||||
*
|
||||
* @namespace Window
|
||||
* @property {number} innerWidth - The width of the drawable area of the Interface window (i.e., without borders or other
|
||||
* chrome), in pixels. <em>Read-only.</em>
|
||||
* @property {number} innerHeight - The height of the drawable area of the Interface window (i.e., without borders or other
|
||||
* chrome) plus the height of the menu bar, in pixels. <em>Read-only.</em>
|
||||
* @property {object} location - Provides facilities for working with your current metaverse location. See {@link location}.
|
||||
* @property {number} x - The x coordinate of the top left corner of the Interface window on the display. <em>Read-only.</em>
|
||||
* @property {number} y - The y coordinate of the top left corner of the Interface window on the display. <em>Read-only.</em>
|
||||
*/
|
||||
|
||||
class WindowScriptingInterface : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int innerWidth READ getInnerWidth)
|
||||
|
@ -48,63 +63,622 @@ public:
|
|||
int getY();
|
||||
|
||||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* Check if the Interface window has focus.
|
||||
* @function Window.hasFocus
|
||||
* @returns {boolean} <code>true</code> if the Interface window has focus, otherwise <code>false</code>.
|
||||
*/
|
||||
QScriptValue hasFocus();
|
||||
|
||||
/**jsdoc
|
||||
* Make the Interface window have focus.
|
||||
* @function Window.setFocus
|
||||
*/
|
||||
void setFocus();
|
||||
|
||||
/**jsdoc
|
||||
* Raise the Interface window if it is minimized, and give it focus.
|
||||
* @function Window.raiseMainWindow
|
||||
*/
|
||||
void raiseMainWindow();
|
||||
|
||||
/**jsdoc
|
||||
* Display a dialog with the specified message and an "OK" button. The dialog is non-modal; the script continues without
|
||||
* waiting for a user response.
|
||||
* @function Window.alert
|
||||
* @param {string} message="" - The message to display.
|
||||
* @example <caption>Display a friendly greeting.</caption>
|
||||
* Window.alert("Welcome!");
|
||||
* print("Script continues without waiting");
|
||||
*/
|
||||
void alert(const QString& message = "");
|
||||
|
||||
/**jsdoc
|
||||
* Prompt the user to confirm something. Displays a modal dialog with a message plus "Yes" and "No" buttons.
|
||||
* responds.
|
||||
* @function Window.confirm
|
||||
* @param {string} message="" - The question to display.
|
||||
* @returns {boolean} <code>true</code> if the user selects "Yes", otherwise <code>false</code>.
|
||||
* @example <caption>Ask the user a question requiring a yes/no answer.</caption>
|
||||
* var answer = Window.confirm("Are you sure?");
|
||||
* print(answer); // true or false
|
||||
*/
|
||||
QScriptValue confirm(const QString& message = "");
|
||||
|
||||
/**jsdoc
|
||||
* Prompt the user to enter some text. Displays a modal dialog with a message and a text box, plus "OK" and "Cancel"
|
||||
* buttons.
|
||||
* @function Window.prompt
|
||||
* @param {string} message - The question to display.
|
||||
* @param {string} defaultText - The default answer text.
|
||||
* @returns {string} The text that the user entered if they select "OK", otherwise "".
|
||||
* @example <caption>Ask the user a question requiring a text answer.</caption>
|
||||
* var answer = Window.prompt("Question", "answer");
|
||||
* if (answer === "") {
|
||||
* print("User canceled");
|
||||
* } else {
|
||||
* print("User answer: " + answer);
|
||||
* }
|
||||
*/
|
||||
QScriptValue prompt(const QString& message, const QString& defaultText);
|
||||
|
||||
/**jsdoc
|
||||
* Prompt the user to enter some text. Displays a non-modal dialog with a message and a text box, plus "OK" and "Cancel"
|
||||
* buttons. A {@link Window.promptTextChanged|promptTextChanged} signal is emitted when the user OKs the dialog; no signal
|
||||
* is emitted if the user cancels the dialog.
|
||||
* @function Window.promptAsync
|
||||
* @param {string} message - The question to display.
|
||||
* @param {string} defaultText - The default answer text.
|
||||
* @example <caption>Ask the user a question requiring a text answer without waiting for the answer.</caption>
|
||||
* function onPromptTextChanged(text) {
|
||||
* print("User answer: " + text);
|
||||
* }
|
||||
* Window.promptTextChanged.connect(onPromptTextChanged);
|
||||
*
|
||||
* Window.promptAsync("Question", "answer");
|
||||
* print("Script continues without waiting");
|
||||
*/
|
||||
void promptAsync(const QString& message = "", const QString& defaultText = "");
|
||||
|
||||
/**jsdoc
|
||||
* Prompt the user for input in a custom, modal dialog.
|
||||
* @deprecated This funtion is deprecated and will be removed.
|
||||
* @function Window.customPrompt
|
||||
* @param {object} config - Configures the modal dialog.
|
||||
* @returns {object} The user's response.
|
||||
*/
|
||||
CustomPromptResult customPrompt(const QVariant& config);
|
||||
|
||||
/**jsdoc
|
||||
* Prompt the user to choose a directory. Displays a modal dialog that navigates the directory tree.
|
||||
* @function Window.browseDir
|
||||
* @param {string} title="" - The title to display at the top of the dialog.
|
||||
* @param {string} directory="" - The initial directory to start browsing at.
|
||||
* @returns {string} The path of the directory if one is chosen, otherwise <code>null</code>.
|
||||
* @example <caption>Ask the user to choose a directory.</caption>
|
||||
* var directory = Window.browseDir("Select Directory", Paths.resources);
|
||||
* print("Directory: " + directory);
|
||||
*/
|
||||
QScriptValue browseDir(const QString& title = "", const QString& directory = "");
|
||||
|
||||
/**jsdoc
|
||||
* Prompt the user to choose a directory. Displays a non-modal dialog that navigates the directory tree. A
|
||||
* {@link Window.browseDirChanged|browseDirChanged} signal is emitted when a directory is chosen; no signal is emitted if
|
||||
* the user cancels the dialog.
|
||||
* @function Window.browseDirAsync
|
||||
* @param {string} title="" - The title to display at the top of the dialog.
|
||||
* @param {string} directory="" - The initial directory to start browsing at.
|
||||
* @example <caption>Ask the user to choose a directory without waiting for the answer.</caption>
|
||||
* function onBrowseDirChanged(directory) {
|
||||
* print("Directory: " + directory);
|
||||
* }
|
||||
* Window.browseDirChanged.connect(onBrowseDirChanged);
|
||||
*
|
||||
* Window.browseDirAsync("Select Directory", Paths.resources);
|
||||
* print("Script continues without waiting");
|
||||
*/
|
||||
void browseDirAsync(const QString& title = "", const QString& directory = "");
|
||||
|
||||
/**jsdoc
|
||||
* Prompt the user to choose a file. Displays a modal dialog that navigates the directory tree.
|
||||
* @function Window.browse
|
||||
* @param {string} title="" - The title to display at the top of the dialog.
|
||||
* @param {string} directory="" - The initial directory to start browsing at.
|
||||
* @param {string} nameFilter="" - The types of files to display. Examples: <code>"*.json"</code> and
|
||||
* <code>"Images (*.png *.jpg *.svg)"</code>. All files are displayed if a filter isn't specified.
|
||||
* @returns {string} The path and name of the file if one is chosen, otherwise <code>null</code>.
|
||||
* @example <caption>Ask the user to choose an image file.</caption>
|
||||
* var filename = Window.browse("Select Image File", Paths.resources, "Images (*.png *.jpg *.svg)");
|
||||
* print("File: " + filename);
|
||||
*/
|
||||
QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
|
||||
/**jsdoc
|
||||
* Prompt the user to choose a file. Displays a non-modal dialog that navigates the directory tree. A
|
||||
* {@link Window.openFileChanged|openFileChanged} signal is emitted when a file is chosen; no signal is emitted if the user
|
||||
* cancels the dialog.
|
||||
* @function Window.browseAsync
|
||||
* @param {string} title="" - The title to display at the top of the dialog.
|
||||
* @param {string} directory="" - The initial directory to start browsing at.
|
||||
* @param {string} nameFilter="" - The types of files to display. Examples: <code>"*.json"</code> and
|
||||
* <code>"Images (*.png *.jpg *.svg)"</code>. All files are displayed if a filter isn't specified.
|
||||
* @example <caption>Ask the user to choose an image file without waiting for the answer.</caption>
|
||||
* function onOpenFileChanged(filename) {
|
||||
* print("File: " + filename);
|
||||
* }
|
||||
* Window.openFileChanged.connect(onOpenFileChanged);
|
||||
*
|
||||
* Window.browseAsync("Select Image File", Paths.resources, "Images (*.png *.jpg *.svg)");
|
||||
* print("Script continues without waiting");
|
||||
*/
|
||||
void browseAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
|
||||
/**jsdoc
|
||||
* Prompt the user to specify the path and name of a file to save to. Displays a model dialog that navigates the directory
|
||||
* tree and allows the user to type in a file name.
|
||||
* @function Window.save
|
||||
* @param {string} title="" - The title to display at the top of the dialog.
|
||||
* @param {string} directory="" - The initial directory to start browsing at.
|
||||
* @param {string} nameFilter="" - The types of files to display. Examples: <code>"*.json"</code> and
|
||||
* <code>"Images (*.png *.jpg *.svg)"</code>. All files are displayed if a filter isn't specified.
|
||||
* @returns {string} The path and name of the file if one is specified, otherwise <code>null</code>. If a single file type
|
||||
* is specified in the nameFilter, that file type extension is automatically appended to the result when appropriate.
|
||||
* @example <caption>Ask the user to specify a file to save to.</caption>
|
||||
* var filename = Window.save("Save to JSON file", Paths.resources, "*.json");
|
||||
* print("File: " + filename);
|
||||
*/
|
||||
QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
|
||||
/**jsdoc
|
||||
* Prompt the user to specify the path and name of a file to save to. Displays a non-model dialog that navigates the
|
||||
* directory tree and allows the user to type in a file name. A {@link Window.saveFileChanged|saveFileChanged} signal is
|
||||
* emitted when a file is specified; no signal is emitted if the user cancels the dialog.
|
||||
* @function Window.saveAsync
|
||||
* @param {string} title="" - The title to display at the top of the dialog.
|
||||
* @param {string} directory="" - The initial directory to start browsing at.
|
||||
* @param {string} nameFilter="" - The types of files to display. Examples: <code>"*.json"</code> and
|
||||
* <code>"Images (*.png *.jpg *.svg)"</code>. All files are displayed if a filter isn't specified.
|
||||
* @example <caption>Ask the user to specify a file to save to without waiting for an answer.</caption>
|
||||
* function onSaveFileChanged(filename) {
|
||||
* print("File: " + filename);
|
||||
* }
|
||||
* Window.saveFileChanged.connect(onSaveFileChanged);
|
||||
*
|
||||
* Window.saveAsync("Save to JSON file", Paths.resources, "*.json");
|
||||
* print("Script continues without waiting");
|
||||
*/
|
||||
void saveAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
|
||||
/**jsdoc
|
||||
* Prompt the user to choose an Asset Server item. Displays a modal dialog that navigates the tree of assets on the Asset
|
||||
* Server.
|
||||
* @function Window.browseAssets
|
||||
* @param {string} title="" - The title to display at the top of the dialog.
|
||||
* @param {string} directory="" - The initial directory to start browsing at.
|
||||
* @param {string} nameFilter="" - The types of files to display. Examples: <code>"*.json"</code> and
|
||||
* <code>"Images (*.png *.jpg *.svg)"</code>. All files are displayed if a filter isn't specified.
|
||||
* @returns {string} The path and name of the asset if one is chosen, otherwise <code>null</code>.
|
||||
* @example <caption>Ask the user to select an FBX asset.</caption>
|
||||
* var asset = Window.browseAssets("Select FBX File", "/", "*.fbx");
|
||||
* print("FBX file: " + asset);
|
||||
*/
|
||||
QScriptValue browseAssets(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
|
||||
/**jsdoc
|
||||
* Prompt the user to choose an Asset Server item. Displays a non-modal dialog that navigates the tree of assets on the
|
||||
* Asset Server. A {@link Window.assetsDirChanged|assetsDirChanged} signal is emitted when an asset is chosen; no signal is
|
||||
* emitted if the user cancels the dialog.
|
||||
* @function Window.browseAssetsAsync
|
||||
* @param {string} title="" - The title to display at the top of the dialog.
|
||||
* @param {string} directory="" - The initial directory to start browsing at.
|
||||
* @param {string} nameFilter="" - The types of files to display. Examples: <code>"*.json"</code> and
|
||||
* <code>"Images (*.png *.jpg *.svg)"</code>. All files are displayed if a filter isn't specified.
|
||||
* @example
|
||||
* function onAssetsDirChanged(asset) {
|
||||
* print("FBX file: " + asset);
|
||||
* }
|
||||
* Window.assetsDirChanged.connect(onAssetsDirChanged);
|
||||
*
|
||||
* Window.browseAssetsAsync("Select FBX File", "/", "*.fbx");
|
||||
* print("Script continues without waiting");
|
||||
*/
|
||||
void browseAssetsAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
|
||||
/**jsdoc
|
||||
* Open the Asset Browser dialog. If a file to upload is specified, the user is prompted to enter the folder and name to
|
||||
* map the file to on the asset server.
|
||||
* @function Window.showAssetServer
|
||||
* @param {string} uploadFile="" - The path and name of a file to upload to the asset server.
|
||||
* @example <caption>Upload a file to the asset server.</caption>
|
||||
* var filename = Window.browse("Select File to Add to Asset Server", Paths.resources);
|
||||
* print("File: " + filename);
|
||||
* Window.showAssetServer(filename);
|
||||
*/
|
||||
void showAssetServer(const QString& upload = "");
|
||||
|
||||
/**jsdoc
|
||||
* Get Interface's build number.
|
||||
* @function Window.checkVersion
|
||||
* @returns {string} - Interface's build number.
|
||||
*/
|
||||
QString checkVersion();
|
||||
|
||||
/**jsdoc
|
||||
* Copies text to the operating system's clipboard.
|
||||
* @function Window.copyToClipboard
|
||||
* @param {string} text - The text to copy to the operating system's clipboard.
|
||||
*/
|
||||
void copyToClipboard(const QString& text);
|
||||
|
||||
/**jsdoc
|
||||
* Takes a snapshot of the current Interface view from the primary camera. When a still image only is captured,
|
||||
* {@link Window.stillSnapshotTaken|stillSnapshotTaken} is emitted; when a still image plus moving images are captured,
|
||||
* {@link Window.processingGifStarted|processingGifStarted} and {@link Window.processingGifCompleted|processingGifCompleted}
|
||||
* are emitted. The path to store the snapshots and the length of the animated GIF to capture are specified in Settings >
|
||||
* General > Snapshots.
|
||||
* @function Window.takeSnapshot
|
||||
* @param {boolean} notify=true - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken}
|
||||
* signal.
|
||||
* @param {boolean} includeAnimated=false - If <code>true</code>, a moving image is captured as an animated GIF in addition
|
||||
* to a still image.
|
||||
* @param {number} aspectRatio=0 - The width/height ratio of the snapshot required. If the value is <code>0</code> the
|
||||
* full resolution is used (window dimensions in desktop mode; HMD display dimensions in HMD mode), otherwise one of the
|
||||
* dimensions is adjusted in order to match the aspect ratio.
|
||||
* @example <caption>Using the snapshot function and signals.</caption>
|
||||
* function onStillSnapshottaken(path, notify) {
|
||||
* print("Still snapshot taken: " + path);
|
||||
* print("Notify: " + notify);
|
||||
* }
|
||||
*
|
||||
* function onProcessingGifStarted(stillPath) {
|
||||
* print("Still snapshot taken: " + stillPath);
|
||||
* }
|
||||
*
|
||||
* function onProcessingGifCompleted(animatedPath) {
|
||||
* print("Animated snapshot taken: " + animatedPath);
|
||||
* }
|
||||
*
|
||||
* Window.stillSnapshotTaken.connect(onStillSnapshottaken);
|
||||
* Window.processingGifStarted.connect(onProcessingGifStarted);
|
||||
* Window.processingGifCompleted.connect(onProcessingGifCompleted);
|
||||
*
|
||||
* var notify = true;
|
||||
* var animated = true;
|
||||
* var aspect = 1920 / 1080;
|
||||
* Window.takeSnapshot(notify, animated, aspect);
|
||||
*/
|
||||
void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f);
|
||||
|
||||
/**jsdoc
|
||||
* Takes a still snapshot of the current view from the secondary camera that can be set up through the {@link Render} API.
|
||||
* @function Window.takeSecondaryCameraSnapshot
|
||||
*/
|
||||
void takeSecondaryCameraSnapshot();
|
||||
|
||||
/**jsdoc
|
||||
* Emit a {@link Window.connectionAdded|connectionAdded} or a {@link Window.connectionError|connectionError} signal that
|
||||
* indicates whether or not a user connection was successfully made using the Web API.
|
||||
* @function Window.makeConnection
|
||||
* @param {boolean} success - If <code>true</code> then {@link Window.connectionAdded|connectionAdded} is emitted, otherwise
|
||||
* {@link Window.connectionError|connectionError} is emitted.
|
||||
* @param {string} description - Descriptive text about the connection success or error. This is sent in the signal emitted.
|
||||
*/
|
||||
void makeConnection(bool success, const QString& userNameOrError);
|
||||
|
||||
/**jsdoc
|
||||
* Display a notification message. Notifications are displayed in panels by the default script, nofications.js. An
|
||||
* {@link Window.announcement|announcement} signal is emitted when this function is called.
|
||||
* @function Window.displayAnnouncement
|
||||
* @param {string} message - The announcement message.
|
||||
* @example <caption>Send and capture an announcement message.</caption>
|
||||
* function onAnnouncement(message) {
|
||||
* // The message is also displayed as a notification by notifications.js.
|
||||
* print("Announcement: " + message);
|
||||
* }
|
||||
* Window.announcement.connect(onAnnouncement);
|
||||
*
|
||||
* Window.displayAnnouncement("Hello");
|
||||
*/
|
||||
void displayAnnouncement(const QString& message);
|
||||
|
||||
/**jsdoc
|
||||
* Prepare a snapshot ready for sharing. A {@link Window.snapshotShared|snapshotShared} signal is emitted when the snapshot
|
||||
* has been prepared.
|
||||
* @function Window.shareSnapshot
|
||||
* @param {string} path - The path and name of the image file to share.
|
||||
* @param {string} href="" - The metaverse location where the snapshot was taken.
|
||||
*/
|
||||
void shareSnapshot(const QString& path, const QUrl& href = QUrl(""));
|
||||
|
||||
/**jsdoc
|
||||
* Check to see if physics is active for you in the domain you're visiting - there is a delay between your arrival at a
|
||||
* domain and physics becoming active for you in that domain.
|
||||
* @function Window.isPhysicsEnabled
|
||||
* @returns {boolean} <code>true</code> if physics is currently active for you, otherwise <code>false</code>.
|
||||
* @example <caption>Wait for physics to be enabled when you change domains.</caption>
|
||||
* function checkForPhysics() {
|
||||
* var isPhysicsEnabled = Window.isPhysicsEnabled();
|
||||
* print("Physics enabled: " + isPhysicsEnabled);
|
||||
* if (!isPhysicsEnabled) {
|
||||
* Script.setTimeout(checkForPhysics, 1000);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* function onDomainChanged(domain) {
|
||||
* print("Domain changed: " + domain);
|
||||
* Script.setTimeout(checkForPhysics, 1000);
|
||||
* }
|
||||
*
|
||||
* Window.domainChanged.connect(onDomainChanged);
|
||||
*/
|
||||
bool isPhysicsEnabled();
|
||||
|
||||
/**jsdoc
|
||||
* Set what to show on the PC display: normal view or entity camera view. The entity camera is configured using
|
||||
* {@link Camera.setCameraEntity} and {@link Camera|Camera.mode}.
|
||||
* @function Window.setDisplayTexture
|
||||
* @param {Window.DisplayTexture} texture - The view to display.
|
||||
* @returns {boolean} <code>true</code> if the display texture was successfully set, otherwise <code>false</code>.
|
||||
*/
|
||||
// See spectatorCamera.js for Valid parameter values.
|
||||
/**jsdoc
|
||||
* <p>The views that may be displayed on the PC display.</p>
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr>
|
||||
* <th>Value</th>
|
||||
* <th>View Displayed</th>
|
||||
* </tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr>
|
||||
* <td><code>""</code></td>
|
||||
* <td>Normal view.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><code>"resource://spectatorCameraFrame"</code></td>
|
||||
* <td>Entity camera view.</td>
|
||||
* </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* @typedef Window.DisplayTexture
|
||||
*/
|
||||
bool setDisplayTexture(const QString& name);
|
||||
|
||||
/**jsdoc
|
||||
* Check if a 2D point is within the desktop window if in desktop mode, or the drawable area of the HUD overlay if in HMD
|
||||
* mode.
|
||||
* @function Window.isPointOnDesktopWindow
|
||||
* @param {Vec2} point - The point to check.
|
||||
* @returns {boolean} <code>true</code> if the point is within the window or HUD, otherwise <code>false</code>.
|
||||
*/
|
||||
bool isPointOnDesktopWindow(QVariant point);
|
||||
|
||||
/**jsdoc
|
||||
* Get the size of the drawable area of the Interface window if in desktop mode or the HMD rendering surface if in HMD mode.
|
||||
* @function Window.getDeviceSize
|
||||
* @returns {Vec2} The width and height of the Interface window or HMD rendering surface, in pixels.
|
||||
*/
|
||||
glm::vec2 getDeviceSize() const;
|
||||
|
||||
/**jsdoc
|
||||
* Open a non-modal message box that can have a variety of button combinations. See also,
|
||||
* {@link Window.updateMessageBox|updateMessageBox} and {@link Window.closeMessageBox|closeMessageBox}.
|
||||
* @function Window.openMessageBox
|
||||
* @param {string} title - The title to display for the message box.
|
||||
* @param {string} text - Text to display in the message box.
|
||||
* @param {Window.MessageBoxButton} buttons - The buttons to display on the message box; one or more button values added
|
||||
* together.
|
||||
* @param {Window.MessageBoxButton} defaultButton - The button that has focus when the message box is opened.
|
||||
* @returns {number} The ID of the message box created.
|
||||
* @example <caption>Ask the user whether that want to reset something.</caption>
|
||||
* var messageBox;
|
||||
* var resetButton = 0x4000000;
|
||||
* var cancelButton = 0x400000;
|
||||
*
|
||||
* function onMessageBoxClosed(id, button) {
|
||||
* if (id === messageBox) {
|
||||
* if (button === resetButton) {
|
||||
* print("Reset");
|
||||
* } else {
|
||||
* print("Don't reset");
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* Window.messageBoxClosed.connect(onMessageBoxClosed);
|
||||
*
|
||||
* messageBox = Window.openMessageBox("Reset Something",
|
||||
* "Do you want to reset something?",
|
||||
* resetButton + cancelButton, cancelButton);
|
||||
*/
|
||||
int openMessageBox(QString title, QString text, int buttons, int defaultButton);
|
||||
|
||||
/**jsdoc
|
||||
* Update the content of a message box that was opened with {@link Window.openMessageBox|openMessageBox}.
|
||||
* @function Window.updateMessageBox
|
||||
* @param {number} id - The ID of the message box.
|
||||
* @param {string} title - The title to display for the message box.
|
||||
* @param {string} text - Text to display in the message box.
|
||||
* @param {Window.MessageBoxButton} buttons - The buttons to display on the message box; one or more button values added
|
||||
* together.
|
||||
* @param {Window.MessageBoxButton} defaultButton - The button that has focus when the message box is opened.
|
||||
*/
|
||||
void updateMessageBox(int id, QString title, QString text, int buttons, int defaultButton);
|
||||
|
||||
/**jsdoc
|
||||
* Close a message box that was opened with {@link Window.openMessageBox|openMessageBox}.
|
||||
* @function Window.closeMessageBox
|
||||
* @param {number} id - The ID of the message box.
|
||||
*/
|
||||
void closeMessageBox(int id);
|
||||
|
||||
private slots:
|
||||
void onMessageBoxSelected(int button);
|
||||
|
||||
signals:
|
||||
void domainChanged(const QString& domainHostname);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when you change the domain you're visiting. <strong>Warning:</strong> Is not emitted if you go to domain that
|
||||
* isn't running.
|
||||
* @function Window.domainChanged
|
||||
* @param {string} domain - The domain's IP address.
|
||||
* @returns {Signal}
|
||||
* @example <caption>Report when you change domains.</caption>
|
||||
* function onDomainChanged(domain) {
|
||||
* print("Domain changed: " + domain);
|
||||
* }
|
||||
*
|
||||
* Window.domainChanged.connect(onDomainChanged);
|
||||
*/
|
||||
void domainChanged(const QString& domain);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when you try to navigate to a *.json, *.svo, or *.svo.json URL in a Web browser within Interface.
|
||||
* @function Window.svoImportRequested
|
||||
* @param {string} url - The URL of the file to import.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void svoImportRequested(const QString& url);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when you try to visit a domain but are refused connection.
|
||||
* @function Window.domainConnectionRefused
|
||||
* @param {string} reasonMessage - A description of the refusal.
|
||||
* @param {Window.ConnectionRefusedReason} reasonCode - Integer number that enumerates the reason for the refusal.
|
||||
* @param {string} extraInfo - Extra information about the refusal.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void domainConnectionRefused(const QString& reasonMessage, int reasonCode, const QString& extraInfo);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when a still snapshot has been taken by calling {@link Window.takeSnapshot|takeSnapshot} with
|
||||
* <code>includeAnimated = false</code>.
|
||||
* @function Window.stillSnapshotTaken
|
||||
* @param {string} pathStillSnapshot - The path and name of the snapshot image file.
|
||||
* @param {boolean} notify - The value of the <code>notify</code> parameter that {@link Window.takeSnapshot|takeSnapshot}
|
||||
* was called with.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void stillSnapshotTaken(const QString& pathStillSnapshot, bool notify);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when a snapshot submitted via {@link Window.shareSnapshot|shareSnapshot} is ready for sharing. The snapshot
|
||||
* may then be shared via the {@link Account.metaverseServerURL} Web API.
|
||||
* @function Window.snapshotShared
|
||||
* @param {boolean} isError - <code>true</code> if an error was encountered preparing the snapshot for sharing, otherwise
|
||||
* <code>false</code>.
|
||||
* @param {string} reply - JSON-formatted information about the snapshot.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void snapshotShared(bool isError, const QString& reply);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the snapshot images have been captured by {@link Window.takeSnapshot|takeSnapshot} and the GIF is
|
||||
* starting to be processed.
|
||||
* @function Window.processingGifStarted
|
||||
* @param {string} pathStillSnapshot - The path and name of the still snapshot image file.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void processingGifStarted(const QString& pathStillSnapshot);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when a GIF has been prepared of the snapshot images captured by {@link Window.takeSnapshot|takeSnapshot}.
|
||||
* @function Window.processingGifCompleted
|
||||
* @param {string} pathAnimatedSnapshot - The path and name of the moving snapshot GIF file.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void processingGifCompleted(const QString& pathAnimatedSnapshot);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when you've successfully made a user connection.
|
||||
* @function Window.connectionAdded
|
||||
* @param {string} message - A description of the success.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void connectionAdded(const QString& connectionName);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when you failed to make a user connection.
|
||||
* @function Window.connectionError
|
||||
* @param {string} message - A description of the error.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void connectionError(const QString& errorString);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when a message is announced by {@link Window.displayAnnouncement|displayAnnouncement}.
|
||||
* @function Window.announcement
|
||||
* @param {string} message - The message text.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void announcement(const QString& message);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the user closes a message box that was opened with {@link Window.openMessageBox|openMessageBox}.
|
||||
* @function Window.messageBoxClosed
|
||||
* @param {number} id - The ID of the message box that was closed.
|
||||
* @param {number} button - The button that the user clicked. If the user presses Esc, the Cancel button value is returned,
|
||||
* whether or not the Cancel button is displayed in the message box.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void messageBoxClosed(int id, int button);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the user chooses a directory in a {@link Window.browseDirAsync|browseDirAsync} dialog.
|
||||
* @function Window.browseDirChanged
|
||||
* @param {string} directory - The directory the user chose in the dialog.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void browseDirChanged(QString browseDir);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the user chooses an asset in a {@link Window.browseAssetsAsync|browseAssetsAsync} dialog.
|
||||
* @function Window.assetsDirChanged
|
||||
* @param {string} asset - The path and name of the asset the user chose in the dialog.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void assetsDirChanged(QString assetsDir);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the user specifies a file in a {@link Window.saveAsync|saveAsync} dialog.
|
||||
* @function Window.saveFileChanged
|
||||
* @param {string} filename - The path and name of the file that the user specified in the dialog.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void saveFileChanged(QString filename);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the user chooses a file in a {@link Window.browseAsync|browseAsync} dialog.
|
||||
* @function Window.openFileChanged
|
||||
* @param {string} filename - The path and name of the file the user chose in the dialog.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void openFileChanged(QString filename);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the user OKs a {@link Window.promptAsync|promptAsync} dialog.
|
||||
* @function Window.promptTextChanged
|
||||
* @param {string} text - The text the user entered in the dialog.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void promptTextChanged(QString text);
|
||||
|
||||
// triggered when window size or position changes
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the position or size of the Interface window changes.
|
||||
* @function Window.geometryChanged
|
||||
* @param {Rect} geometry - The position and size of the drawable area of the Interface window.
|
||||
* @returns {Signal}
|
||||
* @example <caption>Report the position of size of the Interface window when it changes.</caption>
|
||||
* function onWindowGeometryChanged(rect) {
|
||||
* print("Window geometry: " + JSON.stringify(rect));
|
||||
* }
|
||||
*
|
||||
* Window.geometryChanged.connect(onWindowGeometryChanged);
|
||||
*/
|
||||
void geometryChanged(QRect geometry);
|
||||
|
||||
private:
|
||||
|
|
|
@ -90,11 +90,19 @@ void setupPreferences() {
|
|||
preference->setMax(500);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = []()->bool { return qApp->getPreferStylusOverLaser(); };
|
||||
auto setter = [](bool value) { qApp->setPreferStylusOverLaser(value); };
|
||||
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Stylus Over Laser", getter, setter));
|
||||
}
|
||||
// FIXME: Remove setting completely or make available through JavaScript API?
|
||||
/*
|
||||
{
|
||||
auto getter = []()->bool { return qApp->getPreferAvatarFingerOverStylus(); };
|
||||
auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); };
|
||||
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter));
|
||||
}
|
||||
*/
|
||||
{
|
||||
static const QString RETICLE_ICON_NAME = { Cursor::Manager::getIconName(Cursor::Icon::RETICLE) };
|
||||
auto getter = []()->bool { return qApp->getPreferredCursor() == RETICLE_ICON_NAME; };
|
||||
|
|
|
@ -151,7 +151,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID&
|
|||
glm::vec3 normal;
|
||||
boundingBox.findRayIntersection(cameraPosition, direction, distance, face, normal);
|
||||
float offsetAngle = -CONTEXT_OVERLAY_OFFSET_ANGLE;
|
||||
if (DependencyManager::get<PointerManager>()->isLeftHand(event.getID())) {
|
||||
if (event.getID() == 1) { // "1" is left hand
|
||||
offsetAngle *= -1.0f;
|
||||
}
|
||||
contextOverlayPosition = cameraPosition +
|
||||
|
|
|
@ -97,6 +97,11 @@ void ModelOverlay::update(float deltatime) {
|
|||
_model->setLayeredInHUD(getDrawHUDLayer(), scene);
|
||||
}
|
||||
scene->enqueueTransaction(transaction);
|
||||
|
||||
if (!_texturesLoaded && _model->getGeometry() && _model->getGeometry()->areTexturesLoaded()) {
|
||||
_texturesLoaded = true;
|
||||
_model->updateRenderItems();
|
||||
}
|
||||
}
|
||||
|
||||
bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) {
|
||||
|
@ -170,10 +175,12 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
|
|||
_url = urlValue.toString();
|
||||
_updateModel = true;
|
||||
_isLoaded = false;
|
||||
_texturesLoaded = false;
|
||||
}
|
||||
|
||||
auto texturesValue = properties["textures"];
|
||||
if (texturesValue.isValid() && texturesValue.canConvert(QVariant::Map)) {
|
||||
_texturesLoaded = false;
|
||||
QVariantMap textureMap = texturesValue.toMap();
|
||||
QMetaObject::invokeMethod(_model.get(), "setTextures", Qt::AutoConnection,
|
||||
Q_ARG(const QVariantMap&, textureMap));
|
||||
|
|
|
@ -79,6 +79,7 @@ private:
|
|||
|
||||
ModelPointer _model;
|
||||
QVariantMap _modelTextures;
|
||||
bool _texturesLoaded { false };
|
||||
|
||||
render::ItemIDs _subRenderItemIDs;
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
#include <UserActivityLoggerScriptingInterface.h>
|
||||
#include <AbstractViewStateInterface.h>
|
||||
#include <AddressManager.h>
|
||||
#include "scripting/AccountScriptingInterface.h"
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
#include "scripting/AssetMappingsScriptingInterface.h"
|
||||
#include "scripting/MenuScriptingInterface.h"
|
||||
|
@ -193,7 +192,7 @@ void Web3DOverlay::setupQmlSurface() {
|
|||
|
||||
_webSurface->getSurfaceContext()->setContextProperty("offscreenFlags", flags);
|
||||
_webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("Account", AccountScriptingInterface::getInstance());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("Account", GlobalServicesScriptingInterface::getInstance());
|
||||
|
||||
// in Qt 5.10.0 there is already an "Audio" object in the QML context
|
||||
// though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface"
|
||||
|
@ -518,7 +517,6 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
*
|
||||
* @property {string} url - The URL of the Web page to display.
|
||||
* @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page.
|
||||
* @property {Vec2} resolution - <strong>Deprecated:</strong> This property has been removed.
|
||||
* @property {number} dpi=30 - The dots per inch to display the Web page at, on the overlay.
|
||||
* @property {Vec2} dimensions=1,1 - The size of the overlay to display the Web page on, in meters. Synonyms:
|
||||
* <code>scale</code>, <code>size</code>.
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
set(TARGET_NAME avatars-renderer)
|
||||
AUTOSCRIBE_SHADER_LIB(gpu model render render-utils)
|
||||
setup_hifi_library(Widgets Network Script)
|
||||
link_hifi_libraries(shared gpu model animation model-networking script-engine render image render-utils)
|
||||
link_hifi_libraries(shared gpu model animation model-networking script-engine render render-utils image trackers entities-renderer)
|
||||
include_hifi_library_headers(avatars)
|
||||
include_hifi_library_headers(networking)
|
||||
include_hifi_library_headers(fbx)
|
||||
include_hifi_library_headers(recording)
|
||||
include_hifi_library_headers(trackers)
|
||||
include_hifi_library_headers(ktx)
|
||||
include_hifi_library_headers(procedural)
|
||||
include_hifi_library_headers(physics)
|
||||
|
|
|
@ -37,6 +37,16 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
|
|||
SkeletonModel::~SkeletonModel() {
|
||||
}
|
||||
|
||||
void SkeletonModel::setURL(const QUrl& url) {
|
||||
_texturesLoaded = false;
|
||||
Model::setURL(url);
|
||||
}
|
||||
|
||||
void SkeletonModel::setTextures(const QVariantMap& textures) {
|
||||
_texturesLoaded = false;
|
||||
Model::setTextures(textures);
|
||||
}
|
||||
|
||||
void SkeletonModel::initJointStates() {
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset);
|
||||
|
@ -142,6 +152,13 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
|
|||
Parent::simulate(deltaTime, fullUpdate);
|
||||
}
|
||||
|
||||
// FIXME: This texture loading logic should probably live in Avatar, to mirror RenderableModelEntityItem and ModelOverlay,
|
||||
// but Avatars don't get updates in the same way
|
||||
if (!_texturesLoaded && getGeometry() && getGeometry()->areTexturesLoaded()) {
|
||||
_texturesLoaded = true;
|
||||
updateRenderItems();
|
||||
}
|
||||
|
||||
if (!isActive() || !_owningAvatar->isMyAvatar()) {
|
||||
return; // only simulate for own avatar
|
||||
}
|
||||
|
|
|
@ -31,6 +31,9 @@ public:
|
|||
SkeletonModel(Avatar* owningAvatar, QObject* parent = nullptr);
|
||||
~SkeletonModel();
|
||||
|
||||
Q_INVOKABLE void setURL(const QUrl& url) override;
|
||||
Q_INVOKABLE void setTextures(const QVariantMap& textures) override;
|
||||
|
||||
void initJointStates() override;
|
||||
|
||||
void simulate(float deltaTime, bool fullUpdate = true) override;
|
||||
|
@ -115,8 +118,6 @@ protected:
|
|||
|
||||
void computeBoundingShape();
|
||||
|
||||
protected:
|
||||
|
||||
bool getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
|
||||
|
||||
Avatar* _owningAvatar;
|
||||
|
@ -128,6 +129,9 @@ protected:
|
|||
glm::vec3 _defaultEyeModelPosition;
|
||||
|
||||
float _headClipDistance; // Near clip distance to use if no separate head model
|
||||
|
||||
private:
|
||||
bool _texturesLoaded { false };
|
||||
};
|
||||
|
||||
#endif // hifi_SkeletonModel_h
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "filters/PostTransformFilter.h"
|
||||
#include "filters/RotateFilter.h"
|
||||
#include "filters/LowVelocityFilter.h"
|
||||
#include "filters/ExponentialSmoothingFilter.h"
|
||||
|
||||
using namespace controller;
|
||||
|
||||
|
@ -49,6 +50,7 @@ REGISTER_FILTER_CLASS_INSTANCE(TransformFilter, "transform")
|
|||
REGISTER_FILTER_CLASS_INSTANCE(PostTransformFilter, "postTransform")
|
||||
REGISTER_FILTER_CLASS_INSTANCE(RotateFilter, "rotate")
|
||||
REGISTER_FILTER_CLASS_INSTANCE(LowVelocityFilter, "lowVelocity")
|
||||
REGISTER_FILTER_CLASS_INSTANCE(ExponentialSmoothingFilter, "exponentialSmoothing")
|
||||
|
||||
const QString JSON_FILTER_TYPE = QStringLiteral("type");
|
||||
const QString JSON_FILTER_PARAMS = QStringLiteral("params");
|
||||
|
@ -93,7 +95,7 @@ bool Filter::parseSingleFloatParameter(const QJsonValue& parameters, const QStri
|
|||
output = objectParameters[name].toDouble();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -117,7 +119,7 @@ bool Filter::parseVec3Parameter(const QJsonValue& parameters, glm::vec3& output)
|
|||
objectParameters["z"].toDouble());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -126,7 +128,7 @@ bool Filter::parseMat4Parameter(const QJsonValue& parameters, glm::mat4& output)
|
|||
auto objectParameters = parameters.toObject();
|
||||
|
||||
|
||||
if (objectParameters.contains("r0c0") &&
|
||||
if (objectParameters.contains("r0c0") &&
|
||||
objectParameters.contains("r1c0") &&
|
||||
objectParameters.contains("r2c0") &&
|
||||
objectParameters.contains("r3c0") &&
|
||||
|
@ -169,9 +171,9 @@ bool Filter::parseMat4Parameter(const QJsonValue& parameters, glm::mat4& output)
|
|||
bool Filter::parseQuatParameter(const QJsonValue& parameters, glm::quat& output) {
|
||||
if (parameters.isObject()) {
|
||||
auto objectParameters = parameters.toObject();
|
||||
if (objectParameters.contains("w") &&
|
||||
objectParameters.contains("x") &&
|
||||
objectParameters.contains("y") &&
|
||||
if (objectParameters.contains("w") &&
|
||||
objectParameters.contains("x") &&
|
||||
objectParameters.contains("y") &&
|
||||
objectParameters.contains("z")) {
|
||||
|
||||
output = glm::quat(objectParameters["w"].toDouble(),
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "filters/PostTransformFilter.h"
|
||||
#include "filters/RotateFilter.h"
|
||||
#include "filters/LowVelocityFilter.h"
|
||||
#include "filters/ExponentialSmoothingFilter.h"
|
||||
#include "conditionals/AndConditional.h"
|
||||
|
||||
using namespace controller;
|
||||
|
@ -134,6 +135,11 @@ QObject* RouteBuilderProxy::lowVelocity(float rotationConstant, float translatio
|
|||
return this;
|
||||
}
|
||||
|
||||
QObject* RouteBuilderProxy::exponentialSmoothing(float rotationConstant, float translationConstant) {
|
||||
addFilter(std::make_shared<ExponentialSmoothingFilter>(rotationConstant, translationConstant));
|
||||
return this;
|
||||
}
|
||||
|
||||
QObject* RouteBuilderProxy::constrainToInteger() {
|
||||
addFilter(std::make_shared<ConstrainToIntegerFilter>());
|
||||
return this;
|
||||
|
|
|
@ -53,6 +53,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* postTransform(glm::mat4 transform);
|
||||
Q_INVOKABLE QObject* rotate(glm::quat rotation);
|
||||
Q_INVOKABLE QObject* lowVelocity(float rotationConstant, float translationConstant);
|
||||
Q_INVOKABLE QObject* exponentialSmoothing(float rotationConstant, float translationConstant);
|
||||
Q_INVOKABLE QObject* logicalNot();
|
||||
|
||||
private:
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// Created by Anthony Thibault 2017/12/07
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#include "ExponentialSmoothingFilter.h"
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include "../../UserInputMapper.h"
|
||||
#include "../../Input.h"
|
||||
#include <DependencyManager.h>
|
||||
|
||||
static const QString JSON_ROTATION = QStringLiteral("rotation");
|
||||
static const QString JSON_TRANSLATION = QStringLiteral("translation");
|
||||
namespace controller {
|
||||
|
||||
Pose ExponentialSmoothingFilter::apply(Pose value) const {
|
||||
|
||||
if (value.isValid()) {
|
||||
|
||||
// to perform filtering in sensor space, we need to compute the transformations.
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
const InputCalibrationData calibrationData = userInputMapper->getInputCalibrationData();
|
||||
glm::mat4 sensorToAvatarMat = glm::inverse(calibrationData.avatarMat) * calibrationData.sensorToWorldMat;
|
||||
glm::mat4 avatarToSensorMat = glm::inverse(calibrationData.sensorToWorldMat) * calibrationData.avatarMat;
|
||||
|
||||
// transform pose into sensor space.
|
||||
Pose sensorValue = value.transform(avatarToSensorMat);
|
||||
|
||||
if (_prevSensorValue.isValid()) {
|
||||
// exponential smoothing filter
|
||||
sensorValue.translation = _translationConstant * sensorValue.getTranslation() + (1.0f - _translationConstant) * _prevSensorValue.getTranslation();
|
||||
sensorValue.rotation = safeMix(sensorValue.getRotation(), _prevSensorValue.getRotation(), _rotationConstant);
|
||||
|
||||
// remember previous sensor space value.
|
||||
_prevSensorValue = sensorValue;
|
||||
|
||||
// transform back into avatar space
|
||||
return sensorValue.transform(sensorToAvatarMat);
|
||||
} else {
|
||||
// remember previous sensor space value.
|
||||
_prevSensorValue = sensorValue;
|
||||
|
||||
// no previous value to smooth with, so return value unchanged
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
// return invalid value unchanged
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
bool ExponentialSmoothingFilter::parseParameters(const QJsonValue& parameters) {
|
||||
|
||||
if (parameters.isObject()) {
|
||||
auto obj = parameters.toObject();
|
||||
if (obj.contains(JSON_ROTATION) && obj.contains(JSON_TRANSLATION)) {
|
||||
_rotationConstant = glm::clamp((float)obj[JSON_ROTATION].toDouble(), 0.0f, 1.0f);
|
||||
_translationConstant = glm::clamp((float)obj[JSON_TRANSLATION].toDouble(), 0.0f, 1.0f);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// Created by Anthony Thibault 2017/12/17
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_Controllers_Filters_Exponential_Smoothing_h
|
||||
#define hifi_Controllers_Filters_Exponential_Smoothing_h
|
||||
|
||||
#include "../Filter.h"
|
||||
|
||||
namespace controller {
|
||||
|
||||
class ExponentialSmoothingFilter : public Filter {
|
||||
REGISTER_FILTER_CLASS(ExponentialSmoothingFilter);
|
||||
|
||||
public:
|
||||
ExponentialSmoothingFilter() {}
|
||||
ExponentialSmoothingFilter(float rotationConstant, float translationConstant) :
|
||||
_translationConstant(translationConstant), _rotationConstant(rotationConstant) {}
|
||||
|
||||
float apply(float value) const override { return value; }
|
||||
Pose apply(Pose value) const override;
|
||||
bool parseParameters(const QJsonValue& parameters) override;
|
||||
|
||||
private:
|
||||
|
||||
// Constant between 0 and 1.
|
||||
// 1 indicates no smoothing at all, poses are passed through unaltered.
|
||||
// Values near 1 are less smooth with lower latency.
|
||||
// Values near 0 are more smooth with higher latency.
|
||||
float _translationConstant { 0.375f };
|
||||
float _rotationConstant { 0.375f };
|
||||
|
||||
mutable Pose _prevSensorValue { Pose() }; // sensor space
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,7 +1,7 @@
|
|||
set(TARGET_NAME display-plugins)
|
||||
AUTOSCRIBE_SHADER_LIB(gpu display-plugins)
|
||||
setup_hifi_library(OpenGL)
|
||||
link_hifi_libraries(shared plugins ui-plugins gl gpu-gl ui render-utils)
|
||||
link_hifi_libraries(shared plugins ui-plugins gl ui render-utils ${PLATFORM_GL_BACKEND})
|
||||
include_hifi_library_headers(gpu)
|
||||
include_hifi_library_headers(model-networking)
|
||||
include_hifi_library_headers(networking)
|
||||
|
|
|
@ -15,9 +15,4 @@ include_hifi_library_headers(avatars)
|
|||
include_hifi_library_headers(controllers)
|
||||
|
||||
target_bullet()
|
||||
|
||||
add_dependency_external_projects(polyvox)
|
||||
find_package(PolyVox REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${POLYVOX_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${POLYVOX_LIBRARIES})
|
||||
|
||||
target_polyvox()
|
||||
|
|
|
@ -12,13 +12,13 @@
|
|||
#ifndef hifi_EntityTreeRenderer_h
|
||||
#define hifi_EntityTreeRenderer_h
|
||||
|
||||
#include <QSet>
|
||||
#include <QStack>
|
||||
#include <QtCore/QSet>
|
||||
#include <QtCore/QStack>
|
||||
#include <QtGui/QMouseEvent>
|
||||
|
||||
#include <AbstractAudioInterface.h>
|
||||
#include <EntityScriptingInterface.h> // for RayToEntityIntersectionResult
|
||||
#include <EntityTree.h>
|
||||
#include <QMouseEvent>
|
||||
#include <PointerEvent.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <TextureCache.h>
|
||||
|
|
|
@ -373,7 +373,7 @@ void EntityRenderer::onAddToScene(const EntityItemPointer& entity) {
|
|||
renderer->onEntityChanged(_entity->getID());
|
||||
}
|
||||
}, Qt::QueuedConnection);
|
||||
_changeHandlerId = entity->registerChangeHandler([this](const EntityItemID& changedEntity) {
|
||||
_changeHandlerId = entity->registerChangeHandler([](const EntityItemID& changedEntity) {
|
||||
auto renderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
if (renderer) {
|
||||
renderer->onEntityChanged(changedEntity);
|
||||
|
|
|
@ -1080,6 +1080,10 @@ bool ModelEntityRenderer::needsRenderUpdate() const {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!_texturesLoaded && model->getGeometry() && model->getGeometry()->areTexturesLoaded()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (model->needsReload()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -1216,6 +1220,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
|
|||
// From here on, we are guaranteed a populated model
|
||||
withWriteLock([&] {
|
||||
if (_parsedModelURL != model->getURL()) {
|
||||
_texturesLoaded = false;
|
||||
model->setURL(_parsedModelURL);
|
||||
}
|
||||
});
|
||||
|
@ -1247,6 +1252,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
|
|||
}
|
||||
|
||||
if (_lastTextures != entity->getTextures()) {
|
||||
_texturesLoaded = false;
|
||||
_lastTextures = entity->getTextures();
|
||||
auto newTextures = parseTexturesToMap(_lastTextures, entity->_originalTextures);
|
||||
if (newTextures != _currentTextures) {
|
||||
|
@ -1301,12 +1307,17 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
|
|||
}
|
||||
}
|
||||
|
||||
if (!_texturesLoaded && model->getGeometry() && model->getGeometry()->areTexturesLoaded()) {
|
||||
_texturesLoaded = true;
|
||||
model->updateRenderItems();
|
||||
}
|
||||
|
||||
// When the individual mesh parts of a model finish fading, they will mark their Model as needing updating
|
||||
// we will watch for that and ask the model to update it's render items
|
||||
if (model->getRenderItemsNeedUpdate()) {
|
||||
model->updateRenderItems();
|
||||
}
|
||||
|
||||
|
||||
// The code to deal with the change of properties is now in ModelEntityItem.cpp
|
||||
// That is where _currentFrame and _lastAnimated were updated.
|
||||
if (_animating) {
|
||||
|
|
|
@ -158,10 +158,11 @@ private:
|
|||
virtual bool isTransparent() const override { return false; }
|
||||
|
||||
bool _hasModel { false };
|
||||
::ModelPointer _model;
|
||||
ModelPointer _model;
|
||||
GeometryResource::Pointer _compoundShapeResource;
|
||||
QString _lastTextures;
|
||||
QVariantMap _currentTextures;
|
||||
bool _texturesLoaded { false };
|
||||
AnimationPropertyGroup _renderAnimationProperties;
|
||||
int _lastKnownCurrentFrame { -1 };
|
||||
#ifdef MODEL_ENTITY_USE_FADE_EFFECT
|
||||
|
|
|
@ -965,7 +965,10 @@ void EntityItem::setMass(float mass) {
|
|||
|
||||
void EntityItem::setHref(QString value) {
|
||||
auto href = value.toLower();
|
||||
if (! (value.toLower().startsWith("hifi://")) ) {
|
||||
|
||||
// If the string has something and doesn't start with with "hifi://" it shouldn't be set
|
||||
// We allow the string to be empty, because that's the initial state of this property
|
||||
if ( !(value.toLower().startsWith("hifi://")) && !value.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
withWriteLock([&] {
|
||||
|
|
|
@ -355,11 +355,11 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
|
|||
} else if (submittedID == senderID) {
|
||||
// the sender is trying to take or continue ownership
|
||||
if (entity->getSimulatorID().isNull()) {
|
||||
// the sender it taking ownership
|
||||
// the sender is taking ownership
|
||||
properties.promoteSimulationPriority(RECRUIT_SIMULATION_PRIORITY);
|
||||
simulationBlocked = false;
|
||||
} else if (entity->getSimulatorID() == senderID) {
|
||||
// the sender is asserting ownership
|
||||
// the sender is asserting ownership, maybe changing priority
|
||||
simulationBlocked = false;
|
||||
} else {
|
||||
// the sender is trying to steal ownership from another simulator
|
||||
|
|
|
@ -457,13 +457,33 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi
|
|||
// vertex-index/texture-index
|
||||
// vertex-index/texture-index/surface-normal-index
|
||||
QByteArray token = tokenizer.getDatum();
|
||||
if (!isdigit(token[0])) { // Tokenizer treats line endings as whitespace. Non-digit indicates done;
|
||||
auto firstChar = token[0];
|
||||
// Tokenizer treats line endings as whitespace. Non-digit and non-negative sign indicates done;
|
||||
if (!isdigit(firstChar) && firstChar != '-') {
|
||||
tokenizer.pushBackToken(OBJTokenizer::DATUM_TOKEN);
|
||||
break;
|
||||
}
|
||||
QList<QByteArray> parts = token.split('/');
|
||||
assert(parts.count() >= 1);
|
||||
assert(parts.count() <= 3);
|
||||
// If indices are negative relative indices then adjust them to absolute indices based on current vector sizes
|
||||
// Also add 1 to each index as 1 will be subtracted later on from each index in OBJFace::add
|
||||
for (int i = 0; i < parts.count(); ++i) {
|
||||
int part = parts[i].toInt();
|
||||
if (part < 0) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
parts[i].setNum(vertices.size() + part + 1);
|
||||
break;
|
||||
case 1:
|
||||
parts[i].setNum(textureUVs.size() + part + 1);
|
||||
break;
|
||||
case 2:
|
||||
parts[i].setNum(normals.size() + part + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const QByteArray noData {};
|
||||
face.add(parts[0], (parts.count() > 1) ? parts[1] : noData, (parts.count() > 2) ? parts[2] : noData,
|
||||
vertices, vertexColors);
|
||||
|
|
|
@ -14,10 +14,15 @@
|
|||
|
||||
#include <QtCore/QtGlobal>
|
||||
|
||||
#if defined(QT_OPENGL_ES_3_1)
|
||||
// Minimum GL ES version required is 3.1
|
||||
#if defined(Q_OS_ANDROID)
|
||||
#define HIFI_GLES
|
||||
#define HIFI_EGL
|
||||
#endif
|
||||
|
||||
#if defined(HIFI_GLES)
|
||||
// Minimum GL ES version required is 3.2
|
||||
#define GL_MIN_VERSION_MAJOR 0x03
|
||||
#define GL_MIN_VERSION_MINOR 0x01
|
||||
#define GL_MIN_VERSION_MINOR 0x02
|
||||
#define GL_DEFAULT_VERSION_MAJOR GL_MIN_VERSION_MAJOR
|
||||
#define GL_DEFAULT_VERSION_MINOR GL_MIN_VERSION_MINOR
|
||||
#else
|
||||
|
@ -30,10 +35,12 @@
|
|||
|
||||
#define MINIMUM_GL_VERSION ((GL_MIN_VERSION_MAJOR << 8) | GL_MIN_VERSION_MINOR)
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
|
||||
#if defined(HIFI_GLES)
|
||||
#include <EGL/egl.h>
|
||||
#include <GLES3/gl31.h>
|
||||
#endif
|
||||
|
||||
#if defined(HIFI_GLES)
|
||||
#include <GLES3/gl32.h>
|
||||
|
||||
#define GL_DEPTH_COMPONENT32_OES 0x81A7
|
||||
#define GL_TIME_ELAPSED_EXT 0x88BF
|
||||
|
@ -55,7 +62,7 @@ extern "C" {
|
|||
extern PFNGLFRAMEBUFFERTEXTUREEXTPROC glFramebufferTextureEXT;
|
||||
}
|
||||
|
||||
#else // !defined(Q_OS_ANDROID)
|
||||
#else // !defined(HIFI_GLES)
|
||||
|
||||
#define GL_GLEXT_PROTOTYPES 1
|
||||
#include <GL/glew.h>
|
||||
|
|
|
@ -28,7 +28,7 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() {
|
|||
static QSurfaceFormat format;
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [] {
|
||||
#if defined(QT_OPENGL_ES_3_1)
|
||||
#if defined(HIFI_GLES)
|
||||
format.setRenderableType(QSurfaceFormat::OpenGLES);
|
||||
format.setRedBufferSize(8);
|
||||
format.setGreenBufferSize(8);
|
||||
|
|
|
@ -25,8 +25,9 @@ class QSurfaceFormat;
|
|||
class QGLFormat;
|
||||
|
||||
template<class F>
|
||||
#if defined(QT_OPENGL_ES_3_1)
|
||||
void setGLFormatVersion(F& format, int major = 3, int minor = 1)
|
||||
// https://bugreports.qt.io/browse/QTBUG-64703 prevents us from using "defined(QT_OPENGL_ES_3_1)"
|
||||
#if defined(Q_OS_ANDROID)
|
||||
void setGLFormatVersion(F& format, int major = 3, int minor = 2)
|
||||
#else
|
||||
void setGLFormatVersion(F& format, int major = 4, int minor = 5)
|
||||
#endif
|
||||
|
|
|
@ -46,15 +46,27 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) {
|
|||
_context->setShareContext(sharedContext);
|
||||
}
|
||||
_context->setFormat(getDefaultOpenGLSurfaceFormat());
|
||||
|
||||
if (_context->create()) {
|
||||
_offscreenSurface->setFormat(_context->format());
|
||||
_offscreenSurface->create();
|
||||
return _offscreenSurface->isValid();
|
||||
if (!_context->create()) {
|
||||
qFatal("Failed to create OffscreenGLCanvas context");
|
||||
}
|
||||
qWarning("Failed to create OffscreenGLCanvas context");
|
||||
|
||||
return false;
|
||||
_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");
|
||||
}
|
||||
#else
|
||||
if (!_offscreenSurface->isValid()) {
|
||||
qFatal("Offscreen surface is invalid");
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OffscreenGLCanvas::makeCurrent() {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// libraries/midi/src
|
||||
//
|
||||
// Created by Burt Sloane
|
||||
// Modified by Bruce Brown
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
|
@ -14,30 +15,45 @@
|
|||
|
||||
#include <QtCore/QLoggingCategory>
|
||||
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#include "Windows.h"
|
||||
#endif
|
||||
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
const int MIDI_BYTE_MASK = 0x0FF;
|
||||
const int MIDI_NIBBLE_MASK = 0x00F;
|
||||
const int MIDI_PITCH_BEND_MASK = 0x3F80;
|
||||
const int MIDI_SHIFT_STATUS = 4;
|
||||
const int MIDI_SHIFT_NOTE = 8;
|
||||
const int MIDI_SHIFT_VELOCITY = 16;
|
||||
const int MIDI_SHIFT_PITCH_BEND = 9;
|
||||
// Status Decode
|
||||
const int MIDI_NOTE_OFF = 0x8;
|
||||
const int MIDI_NOTE_ON = 0x9;
|
||||
const int MIDI_POLYPHONIC_KEY_PRESSURE = 0xa;
|
||||
const int MIDI_PROGRAM_CHANGE = 0xc;
|
||||
const int MIDI_CHANNEL_PRESSURE = 0xd;
|
||||
const int MIDI_PITCH_BEND_CHANGE = 0xe;
|
||||
const int MIDI_SYSTEM_MESSAGE = 0xf;
|
||||
#endif
|
||||
const int MIDI_STATUS_MASK = 0x0F0;
|
||||
const int MIDI_NOTE_OFF = 0x080;
|
||||
const int MIDI_NOTE_ON = 0x090;
|
||||
const int MIDI_CONTROL_CHANGE = 0x0b0;
|
||||
|
||||
const int MIDI_CONTROL_CHANGE = 0xb;
|
||||
const int MIDI_CHANNEL_MODE_ALL_NOTES_OFF = 0x07b;
|
||||
|
||||
|
||||
static Midi* instance = NULL; // communicate this to non-class callbacks
|
||||
static Midi* instance = NULL; // communicate this to non-class callbacks
|
||||
static bool thruModeEnabled = false;
|
||||
static bool broadcastEnabled = false;
|
||||
static bool typeNoteOffEnabled = true;
|
||||
static bool typeNoteOnEnabled = true;
|
||||
static bool typePolyKeyPressureEnabled = false;
|
||||
static bool typeControlChangeEnabled = true;
|
||||
static bool typeProgramChangeEnabled = true;
|
||||
static bool typeChanPressureEnabled = false;
|
||||
static bool typePitchBendEnabled = true;
|
||||
static bool typeSystemMessageEnabled = false;
|
||||
|
||||
std::vector<QString> Midi::midiinexclude;
|
||||
std::vector<QString> Midi::midioutexclude;
|
||||
|
||||
std::vector<QString> Midi::midiInExclude;
|
||||
std::vector<QString> Midi::midiOutExclude;
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
|
||||
|
@ -47,7 +63,6 @@ std::vector<QString> Midi::midioutexclude;
|
|||
std::vector<HMIDIIN> midihin;
|
||||
std::vector<HMIDIOUT> midihout;
|
||||
|
||||
|
||||
void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||
switch (wMsg) {
|
||||
case MIM_OPEN:
|
||||
|
@ -58,23 +73,64 @@ void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD
|
|||
if (midihin[i] == hMidiIn) {
|
||||
midihin[i] = NULL;
|
||||
instance->allNotesOff();
|
||||
instance->midiHardwareChange();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MIM_DATA: {
|
||||
int status = MIDI_BYTE_MASK & dwParam1;
|
||||
int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE);
|
||||
int vel = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY);
|
||||
if (thruModeEnabled) {
|
||||
instance->sendNote(status, note, vel); // relay the note on to all other midi devices
|
||||
int device = -1;
|
||||
for (int i = 0; i < midihin.size(); i++) {
|
||||
if (midihin[i] == hMidiIn) {
|
||||
device = i;
|
||||
}
|
||||
}
|
||||
instance->noteReceived(status, note, vel); // notify the javascript
|
||||
int raw = dwParam1;
|
||||
int channel = (MIDI_NIBBLE_MASK & dwParam1) + 1;
|
||||
int status = MIDI_BYTE_MASK & dwParam1;
|
||||
int type = MIDI_NIBBLE_MASK & (dwParam1 >> MIDI_SHIFT_STATUS);
|
||||
int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE);
|
||||
int velocity = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY);
|
||||
int bend = 0;
|
||||
int program = 0;
|
||||
if (!typeNoteOffEnabled && type == MIDI_NOTE_OFF) {
|
||||
return;
|
||||
}
|
||||
if (!typeNoteOnEnabled && type == MIDI_NOTE_ON) {
|
||||
return;
|
||||
}
|
||||
if (!typePolyKeyPressureEnabled && type == MIDI_POLYPHONIC_KEY_PRESSURE) {
|
||||
return;
|
||||
}
|
||||
if (!typeControlChangeEnabled && type == MIDI_CONTROL_CHANGE) {
|
||||
return;
|
||||
}
|
||||
if (typeProgramChangeEnabled && type == MIDI_PROGRAM_CHANGE) {
|
||||
program = note;
|
||||
note = 0;
|
||||
}
|
||||
if (typeChanPressureEnabled && type == MIDI_CHANNEL_PRESSURE) {
|
||||
velocity = note;
|
||||
note = 0;
|
||||
}
|
||||
if (typePitchBendEnabled && type == MIDI_PITCH_BEND_CHANGE) {
|
||||
bend = ((MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE)) |
|
||||
(MIDI_PITCH_BEND_MASK & (dwParam1 >> MIDI_SHIFT_PITCH_BEND))) - 8192;
|
||||
channel = 0; // Weird values on different instruments
|
||||
note = 0;
|
||||
velocity = 0;
|
||||
}
|
||||
if (!typeSystemMessageEnabled && type == MIDI_SYSTEM_MESSAGE) {
|
||||
return;
|
||||
}
|
||||
if (thruModeEnabled) {
|
||||
instance->sendNote(status, note, velocity); // relay the message on to all other midi devices.
|
||||
}
|
||||
instance->midiReceived(device, raw, channel, status, type, note, velocity, bend, program); // notify the javascript
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||
switch (wMsg) {
|
||||
case MOM_OPEN:
|
||||
|
@ -85,21 +141,45 @@ void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_P
|
|||
if (midihout[i] == hmo) {
|
||||
midihout[i] = NULL;
|
||||
instance->allNotesOff();
|
||||
instance->midiHardwareChange();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Midi::sendNote(int status, int note, int vel) {
|
||||
for (int i = 0; i < midihout.size(); i++) {
|
||||
if (midihout[i] != NULL) {
|
||||
midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (vel << MIDI_SHIFT_VELOCITY));
|
||||
void Midi::sendRawMessage(int device, int raw) {
|
||||
if (broadcastEnabled) {
|
||||
for (int i = 0; i < midihout.size(); i++) {
|
||||
if (midihout[i] != NULL) {
|
||||
midiOutShortMsg(midihout[i], raw);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
midiOutShortMsg(midihout[device], raw);
|
||||
}
|
||||
}
|
||||
|
||||
void Midi::sendMessage(int device, int channel, int type, int note, int velocity) {
|
||||
int message = (channel - 1) | (type << MIDI_SHIFT_STATUS);
|
||||
if (broadcastEnabled) {
|
||||
for (int i = 0; i < midihout.size(); i++) {
|
||||
if (midihout[i] != NULL) {
|
||||
midiOutShortMsg(midihout[i], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
midiOutShortMsg(midihout[device], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY));
|
||||
}
|
||||
}
|
||||
|
||||
void Midi::sendNote(int status, int note, int velocity) {
|
||||
for (int i = 0; i < midihout.size(); i++) {
|
||||
if (midihout[i] != NULL) {
|
||||
midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (velocity << MIDI_SHIFT_VELOCITY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Midi::MidiSetup() {
|
||||
midihin.clear();
|
||||
|
@ -110,8 +190,8 @@ void Midi::MidiSetup() {
|
|||
midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS));
|
||||
|
||||
bool found = false;
|
||||
for (int j = 0; j < midiinexclude.size(); j++) {
|
||||
if (midiinexclude[j].toStdString().compare(incaps.szPname) == 0) {
|
||||
for (int j = 0; j < midiInExclude.size(); j++) {
|
||||
if (midiInExclude[j].toStdString().compare(incaps.szPname) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
@ -122,7 +202,6 @@ void Midi::MidiSetup() {
|
|||
midiInStart(tmphin);
|
||||
midihin.push_back(tmphin);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MIDIOUTCAPS outcaps;
|
||||
|
@ -130,8 +209,8 @@ void Midi::MidiSetup() {
|
|||
midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS));
|
||||
|
||||
bool found = false;
|
||||
for (int j = 0; j < midioutexclude.size(); j++) {
|
||||
if (midioutexclude[j].toStdString().compare(outcaps.szPname) == 0) {
|
||||
for (int j = 0; j < midiOutExclude.size(); j++) {
|
||||
if (midiOutExclude[j].toStdString().compare(outcaps.szPname) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
@ -164,7 +243,13 @@ void Midi::MidiCleanup() {
|
|||
midihout.clear();
|
||||
}
|
||||
#else
|
||||
void Midi::sendNote(int status, int note, int vel) {
|
||||
void Midi::sendRawMessage(int device, int raw) {
|
||||
}
|
||||
|
||||
void Midi::sendNote(int status, int note, int velocity) {
|
||||
}
|
||||
|
||||
void Midi::sendMessage(int device, int channel, int type, int note, int velocity){
|
||||
}
|
||||
|
||||
void Midi::MidiSetup() {
|
||||
|
@ -176,26 +261,30 @@ void Midi::MidiCleanup() {
|
|||
}
|
||||
#endif
|
||||
|
||||
void Midi::noteReceived(int status, int note, int velocity) {
|
||||
if (((status & MIDI_STATUS_MASK) != MIDI_NOTE_OFF) &&
|
||||
((status & MIDI_STATUS_MASK) != MIDI_NOTE_ON) &&
|
||||
((status & MIDI_STATUS_MASK) != MIDI_CONTROL_CHANGE)) {
|
||||
return; // NOTE: only sending note-on, note-off, and control-change to Javascript
|
||||
}
|
||||
|
||||
void Midi::midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program) {
|
||||
QVariantMap eventData;
|
||||
eventData["device"] = device;
|
||||
eventData["raw"] = raw;
|
||||
eventData["channel"] = channel;
|
||||
eventData["status"] = status;
|
||||
eventData["type"] = type;
|
||||
eventData["note"] = note;
|
||||
eventData["velocity"] = velocity;
|
||||
emit midiNote(eventData);
|
||||
eventData["bend"] = bend;
|
||||
eventData["program"] = program;
|
||||
emit midiNote(eventData);// Legacy
|
||||
emit midiMessage(eventData);
|
||||
}
|
||||
|
||||
void Midi::midiHardwareChange() {
|
||||
emit midiReset();
|
||||
}
|
||||
//
|
||||
|
||||
Midi::Midi() {
|
||||
instance = this;
|
||||
#if defined Q_OS_WIN32
|
||||
midioutexclude.push_back("Microsoft GS Wavetable Synth"); // we don't want to hear this thing
|
||||
midiOutExclude.push_back("Microsoft GS Wavetable Synth"); // we don't want to hear this thing (Lags)
|
||||
#endif
|
||||
MidiSetup();
|
||||
}
|
||||
|
@ -203,10 +292,18 @@ Midi::Midi() {
|
|||
Midi::~Midi() {
|
||||
}
|
||||
|
||||
void Midi::sendRawDword(int device, int raw) {
|
||||
sendRawMessage(device, raw);
|
||||
}
|
||||
|
||||
void Midi::playMidiNote(int status, int note, int velocity) {
|
||||
sendNote(status, note, velocity);
|
||||
}
|
||||
|
||||
void Midi::sendMidiMessage(int device, int channel, int type, int note, int velocity) {
|
||||
sendMessage(device, channel, type, note, velocity);
|
||||
}
|
||||
|
||||
void Midi::allNotesOff() {
|
||||
sendNote(MIDI_CONTROL_CHANGE, MIDI_CHANNEL_MODE_ALL_NOTES_OFF, 0); // all notes off
|
||||
}
|
||||
|
@ -219,6 +316,7 @@ void Midi::resetDevices() {
|
|||
void Midi::USBchanged() {
|
||||
instance->MidiCleanup();
|
||||
instance->MidiSetup();
|
||||
instance->midiHardwareChange();
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -245,16 +343,16 @@ QStringList Midi::listMidiDevices(bool output) {
|
|||
|
||||
void Midi::unblockMidiDevice(QString name, bool output) {
|
||||
if (output) {
|
||||
for (unsigned long i = 0; i < midioutexclude.size(); i++) {
|
||||
if (midioutexclude[i].toStdString().compare(name.toStdString()) == 0) {
|
||||
midioutexclude.erase(midioutexclude.begin() + i);
|
||||
for (unsigned long i = 0; i < midiOutExclude.size(); i++) {
|
||||
if (midiOutExclude[i].toStdString().compare(name.toStdString()) == 0) {
|
||||
midiOutExclude.erase(midiOutExclude.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (unsigned long i = 0; i < midiinexclude.size(); i++) {
|
||||
if (midiinexclude[i].toStdString().compare(name.toStdString()) == 0) {
|
||||
midiinexclude.erase(midiinexclude.begin() + i);
|
||||
for (unsigned long i = 0; i < midiInExclude.size(); i++) {
|
||||
if (midiInExclude[i].toStdString().compare(name.toStdString()) == 0) {
|
||||
midiInExclude.erase(midiInExclude.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -264,9 +362,9 @@ void Midi::unblockMidiDevice(QString name, bool output) {
|
|||
void Midi::blockMidiDevice(QString name, bool output) {
|
||||
unblockMidiDevice(name, output); // make sure it's only in there once
|
||||
if (output) {
|
||||
midioutexclude.push_back(name);
|
||||
midiOutExclude.push_back(name);
|
||||
} else {
|
||||
midiinexclude.push_back(name);
|
||||
midiInExclude.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,3 +372,38 @@ void Midi::thruModeEnable(bool enable) {
|
|||
thruModeEnabled = enable;
|
||||
}
|
||||
|
||||
void Midi::broadcastEnable(bool enable) {
|
||||
broadcastEnabled = enable;
|
||||
}
|
||||
|
||||
void Midi::typeNoteOffEnable(bool enable) {
|
||||
typeNoteOffEnabled = enable;
|
||||
}
|
||||
|
||||
void Midi::typeNoteOnEnable(bool enable) {
|
||||
typeNoteOnEnabled = enable;
|
||||
}
|
||||
|
||||
void Midi::typePolyKeyPressureEnable(bool enable) {
|
||||
typePolyKeyPressureEnabled = enable;
|
||||
}
|
||||
|
||||
void Midi::typeControlChangeEnable(bool enable) {
|
||||
typeControlChangeEnabled = enable;
|
||||
}
|
||||
|
||||
void Midi::typeProgramChangeEnable(bool enable) {
|
||||
typeProgramChangeEnabled = enable;
|
||||
}
|
||||
|
||||
void Midi::typeChanPressureEnable(bool enable) {
|
||||
typeChanPressureEnabled = enable;
|
||||
}
|
||||
|
||||
void Midi::typePitchBendEnable(bool enable) {
|
||||
typePitchBendEnabled = enable;
|
||||
}
|
||||
|
||||
void Midi::typeSystemMessageEnable(bool enable) {
|
||||
typeSystemMessageEnabled = enable;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// libraries/midi/src
|
||||
//
|
||||
// Created by Burt Sloane
|
||||
// Modified by Bruce Brown
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
|
@ -24,13 +25,16 @@ class Midi : public QObject, public Dependency {
|
|||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
void noteReceived(int status, int note, int velocity); // relay a note to Javascript
|
||||
void sendNote(int status, int note, int vel); // relay a note to MIDI outputs
|
||||
void midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program); // relay a note to Javascript
|
||||
void midiHardwareChange(); // relay hardware change to Javascript
|
||||
void sendRawMessage(int device, int raw); // relay midi message to MIDI outputs
|
||||
void sendNote(int status, int note, int velocity); // relay a note to MIDI outputs
|
||||
void sendMessage(int device, int channel, int type, int note, int velocity); // relay a message to MIDI outputs
|
||||
static void USBchanged();
|
||||
|
||||
private:
|
||||
static std::vector<QString> midiinexclude;
|
||||
static std::vector<QString> midioutexclude;
|
||||
static std::vector<QString> midiInExclude;
|
||||
static std::vector<QString> midiOutExclude;
|
||||
|
||||
private:
|
||||
void MidiSetup();
|
||||
|
@ -38,31 +42,63 @@ private:
|
|||
|
||||
signals:
|
||||
void midiNote(QVariantMap eventData);
|
||||
void midiMessage(QVariantMap eventData);
|
||||
void midiReset();
|
||||
|
||||
public slots:
|
||||
/// play a note on all connected devices
|
||||
/// @param {int} status: 0x80 is noteoff, 0x90 is noteon (if velocity=0, noteoff), etc
|
||||
/// @param {int} note: midi note number
|
||||
/// @param {int} velocity: note velocity (0 means noteoff)
|
||||
Q_INVOKABLE void playMidiNote(int status, int note, int velocity);
|
||||
public slots:
|
||||
// Send Raw Midi Packet to all connected devices
|
||||
Q_INVOKABLE void sendRawDword(int device, int raw);
|
||||
/// Send Raw Midi message to selected device
|
||||
/// @param {int} device: device number
|
||||
/// @param {int} raw: raw midi message (DWORD)
|
||||
|
||||
/// turn off all notes on all connected devices
|
||||
Q_INVOKABLE void allNotesOff();
|
||||
// Send Midi Message to all connected devices
|
||||
Q_INVOKABLE void sendMidiMessage(int device, int channel, int type, int note, int velocity);
|
||||
/// Send midi message to selected device/devices
|
||||
/// @param {int} device: device number
|
||||
/// @param {int} channel: channel number
|
||||
/// @param {int} type: 0x8 is noteoff, 0x9 is noteon (if velocity=0, noteoff), etc
|
||||
/// @param {int} note: midi note number
|
||||
/// @param {int} velocity: note velocity (0 means noteoff)
|
||||
|
||||
/// clean up and re-discover attached devices
|
||||
Q_INVOKABLE void resetDevices();
|
||||
// Send Midi Message to all connected devices
|
||||
Q_INVOKABLE void playMidiNote(int status, int note, int velocity);
|
||||
/// play a note on all connected devices
|
||||
/// @param {int} status: 0x80 is noteoff, 0x90 is noteon (if velocity=0, noteoff), etc
|
||||
/// @param {int} note: midi note number
|
||||
/// @param {int} velocity: note velocity (0 means noteoff)
|
||||
|
||||
/// ask for a list of inputs/outputs
|
||||
Q_INVOKABLE QStringList listMidiDevices(bool output);
|
||||
/// turn off all notes on all connected devices
|
||||
Q_INVOKABLE void allNotesOff();
|
||||
|
||||
/// block an input/output by name
|
||||
Q_INVOKABLE void blockMidiDevice(QString name, bool output);
|
||||
/// clean up and re-discover attached devices
|
||||
Q_INVOKABLE void resetDevices();
|
||||
|
||||
/// unblock an input/output by name
|
||||
Q_INVOKABLE void unblockMidiDevice(QString name, bool output);
|
||||
/// ask for a list of inputs/outputs
|
||||
Q_INVOKABLE QStringList listMidiDevices(bool output);
|
||||
|
||||
/// block an input/output by name
|
||||
Q_INVOKABLE void blockMidiDevice(QString name, bool output);
|
||||
|
||||
/// unblock an input/output by name
|
||||
Q_INVOKABLE void unblockMidiDevice(QString name, bool output);
|
||||
|
||||
/// repeat all incoming notes to all outputs (default disabled)
|
||||
Q_INVOKABLE void thruModeEnable(bool enable);
|
||||
|
||||
/// broadcast on all unblocked devices
|
||||
Q_INVOKABLE void broadcastEnable(bool enable);
|
||||
|
||||
/// filter by event types
|
||||
Q_INVOKABLE void typeNoteOffEnable(bool enable);
|
||||
Q_INVOKABLE void typeNoteOnEnable(bool enable);
|
||||
Q_INVOKABLE void typePolyKeyPressureEnable(bool enable);
|
||||
Q_INVOKABLE void typeControlChangeEnable(bool enable);
|
||||
Q_INVOKABLE void typeProgramChangeEnable(bool enable);
|
||||
Q_INVOKABLE void typeChanPressureEnable(bool enable);
|
||||
Q_INVOKABLE void typePitchBendEnable(bool enable);
|
||||
Q_INVOKABLE void typeSystemMessageEnable(bool enable);
|
||||
|
||||
/// repeat all incoming notes to all outputs (default disabled)
|
||||
Q_INVOKABLE void thruModeEnable(bool enable);
|
||||
|
||||
public:
|
||||
Midi();
|
||||
|
|
|
@ -596,7 +596,7 @@ bool AddressManager::handleDomainID(const QString& host) {
|
|||
void AddressManager::handlePath(const QString& path, LookupTrigger trigger, bool wasPathOnly) {
|
||||
if (!handleViewpoint(path, false, trigger, wasPathOnly)) {
|
||||
qCDebug(networking) << "User entered path could not be handled as a viewpoint - " << path <<
|
||||
"- wll attempt to ask domain-server to resolve.";
|
||||
"- will attempt to ask domain-server to resolve.";
|
||||
|
||||
if (!wasPathOnly) {
|
||||
// if we received a path with a host then we need to remember what it was here so we can not
|
||||
|
|
|
@ -84,6 +84,46 @@ public:
|
|||
|
||||
void softReset();
|
||||
|
||||
/**jsdoc
|
||||
* <p>The reasons that you may be refused connection to a domain are defined by numeric values:</p>
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr>
|
||||
* <th>Reason</th>
|
||||
* <th>Value</th>
|
||||
* <th>Description</th>
|
||||
* </tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr>
|
||||
* <td><strong>Unknown</strong></td>
|
||||
* <td><code>0</code></td>
|
||||
* <td>Some unknown reason.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><strong>ProtocolMismatch</strong></td>
|
||||
* <td><code>1</code></td>
|
||||
* <td>The communications protocols of the domain and your Interface are not the same.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><strong>LoginError</strong></td>
|
||||
* <td><code>2</code></td>
|
||||
* <td>You could not be logged into the domain.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><strong>NotAuthorized</strong></td>
|
||||
* <td><code>3</code></td>
|
||||
* <td>You are not authorized to connect to the domain.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td><strong>TooManyUsers</strong></td>
|
||||
* <td><code>4</code></td>
|
||||
* <td>The domain already has its maximum number of users.</td>
|
||||
* </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* @typedef Window.ConnectionRefusedReason
|
||||
*/
|
||||
enum class ConnectionRefusedReason : uint8_t {
|
||||
Unknown,
|
||||
ProtocolMismatch,
|
||||
|
|
|
@ -391,6 +391,10 @@ void CharacterController::setState(State desiredState) {
|
|||
}
|
||||
}
|
||||
|
||||
void CharacterController::recomputeFlying() {
|
||||
_pendingFlags |= PENDING_FLAG_RECOMPUTE_FLYING;
|
||||
}
|
||||
|
||||
void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale) {
|
||||
float x = scale.x;
|
||||
float z = scale.z;
|
||||
|
@ -657,6 +661,13 @@ void CharacterController::updateState() {
|
|||
if (!_dynamicsWorld) {
|
||||
return;
|
||||
}
|
||||
if (_pendingFlags & PENDING_FLAG_RECOMPUTE_FLYING) {
|
||||
SET_STATE(CharacterController::State::Hover, "recomputeFlying");
|
||||
_hasSupport = false;
|
||||
_stepHeight = _minStepHeight; // clears memory of last step obstacle
|
||||
_pendingFlags &= ~PENDING_FLAG_RECOMPUTE_FLYING;
|
||||
}
|
||||
|
||||
const btScalar FLY_TO_GROUND_THRESHOLD = 0.1f * _radius;
|
||||
const btScalar GROUND_TO_FLY_THRESHOLD = 0.8f * _radius + _halfHeight;
|
||||
const quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 250 * MSECS_PER_SECOND;
|
||||
|
|
|
@ -31,6 +31,7 @@ const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1;
|
|||
const uint32_t PENDING_FLAG_UPDATE_SHAPE = 1U << 2;
|
||||
const uint32_t PENDING_FLAG_JUMP = 1U << 3;
|
||||
const uint32_t PENDING_FLAG_UPDATE_COLLISION_GROUP = 1U << 4;
|
||||
const uint32_t PENDING_FLAG_RECOMPUTE_FLYING = 1U << 5;
|
||||
const float DEFAULT_MIN_FLOOR_NORMAL_DOT_UP = cosf(PI / 3.0f);
|
||||
|
||||
class btRigidBody;
|
||||
|
@ -54,6 +55,7 @@ public:
|
|||
|
||||
void setGravity(float gravity);
|
||||
float getGravity();
|
||||
void recomputeFlying();
|
||||
|
||||
virtual void updateShapeIfNecessary() = 0;
|
||||
|
||||
|
|
|
@ -155,7 +155,7 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
|
|||
// (1) we own it but may need to change the priority OR...
|
||||
// (2) we don't own it but should bid (because a local script has been changing physics properties)
|
||||
uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority();
|
||||
_outgoingPriority = glm::max(_outgoingPriority, newPriority);
|
||||
upgradeOutgoingPriority(newPriority);
|
||||
|
||||
// reset bid expiry so that we bid ASAP
|
||||
_nextOwnershipBid = 0;
|
||||
|
@ -403,7 +403,8 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
|||
}
|
||||
|
||||
if (_entity->dynamicDataNeedsTransmit()) {
|
||||
_outgoingPriority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY;
|
||||
uint8_t priority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY;
|
||||
upgradeOutgoingPriority(priority);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -502,17 +503,21 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
|
|||
// we don't own the simulation
|
||||
|
||||
// NOTE: we do not volunteer to own kinematic or static objects
|
||||
uint8_t insufficientPriority = _body->isStaticOrKinematicObject() ? VOLUNTEER_SIMULATION_PRIORITY : 0;
|
||||
uint8_t volunteerPriority = _body->isStaticOrKinematicObject() ? VOLUNTEER_SIMULATION_PRIORITY : 0;
|
||||
|
||||
bool shouldBid = _outgoingPriority > insufficientPriority && // but we would like to own it AND
|
||||
bool shouldBid = _outgoingPriority > volunteerPriority && // but we would like to own it AND
|
||||
usecTimestampNow() > _nextOwnershipBid; // it is time to bid again
|
||||
if (shouldBid && _outgoingPriority < _entity->getSimulationPriority()) {
|
||||
// we are insufficiently interested so clear our interest
|
||||
// we are insufficiently interested so clear _outgoingPriority
|
||||
// and reset the bid expiry
|
||||
_outgoingPriority = 0;
|
||||
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
|
||||
}
|
||||
return shouldBid;
|
||||
} else {
|
||||
// When we own the simulation: make sure _outgoingPriority is not less than current owned priority
|
||||
// because: an _outgoingPriority of zero indicates that we should drop ownership when we have it.
|
||||
upgradeOutgoingPriority(_entity->getSimulationPriority());
|
||||
}
|
||||
|
||||
return remoteSimulationOutOfSync(simulationStep);
|
||||
|
@ -618,8 +623,10 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
|||
_entity->setPendingOwnershipPriority(_outgoingPriority, now);
|
||||
// don't forget to remember that we have made a bid
|
||||
_entity->rememberHasSimulationOwnershipBid();
|
||||
// ...then reset _outgoingPriority in preparation for the next frame
|
||||
// ...then reset _outgoingPriority
|
||||
_outgoingPriority = 0;
|
||||
// _outgoingPrioriuty will be re-computed before next bid,
|
||||
// or will be set to agree with ownership priority should we win the bid
|
||||
} else if (_outgoingPriority != _entity->getSimulationPriority()) {
|
||||
// we own the simulation but our desired priority has changed
|
||||
if (_outgoingPriority == 0) {
|
||||
|
|
|
@ -317,6 +317,7 @@ void PhysicsEngine::stepSimulation() {
|
|||
|
||||
auto onSubStep = [this]() {
|
||||
this->updateContactMap();
|
||||
this->doOwnershipInfectionForConstraints();
|
||||
};
|
||||
|
||||
int numSubsteps = _dynamicsWorld->stepSimulationWithSubstepCallback(timeStep, PHYSICS_ENGINE_MAX_NUM_SUBSTEPS,
|
||||
|
@ -451,7 +452,7 @@ void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const
|
|||
// NOTE: we might own the simulation of a kinematic object (A)
|
||||
// but we don't claim ownership of kinematic objects (B) based on collisions here.
|
||||
if (!objectB->isStaticOrKinematicObject() && motionStateB->getSimulatorID() != Physics::getSessionUUID()) {
|
||||
quint8 priorityA = motionStateA ? motionStateA->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY;
|
||||
uint8_t priorityA = motionStateA ? motionStateA->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY;
|
||||
motionStateB->bump(priorityA);
|
||||
}
|
||||
} else if (motionStateA &&
|
||||
|
@ -460,7 +461,7 @@ void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const
|
|||
// SIMILARLY: we might own the simulation of a kinematic object (B)
|
||||
// but we don't claim ownership of kinematic objects (A) based on collisions here.
|
||||
if (!objectA->isStaticOrKinematicObject() && motionStateA->getSimulatorID() != Physics::getSessionUUID()) {
|
||||
quint8 priorityB = motionStateB ? motionStateB->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY;
|
||||
uint8_t priorityB = motionStateB ? motionStateB->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY;
|
||||
motionStateA->bump(priorityB);
|
||||
}
|
||||
}
|
||||
|
@ -501,6 +502,54 @@ void PhysicsEngine::updateContactMap() {
|
|||
}
|
||||
}
|
||||
|
||||
void PhysicsEngine::doOwnershipInfectionForConstraints() {
|
||||
BT_PROFILE("ownershipInfectionForConstraints");
|
||||
const btCollisionObject* characterObject = _myAvatarController ? _myAvatarController->getCollisionObject() : nullptr;
|
||||
foreach(const auto& dynamic, _objectDynamics) {
|
||||
if (!dynamic) {
|
||||
continue;
|
||||
}
|
||||
QList<btRigidBody*> bodies = std::static_pointer_cast<ObjectDynamic>(dynamic)->getRigidBodies();
|
||||
if (bodies.size() > 1) {
|
||||
int32_t numOwned = 0;
|
||||
int32_t numStatic = 0;
|
||||
uint8_t priority = VOLUNTEER_SIMULATION_PRIORITY;
|
||||
foreach(btRigidBody* body, bodies) {
|
||||
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(body->getUserPointer());
|
||||
if (body->isStaticObject()) {
|
||||
++numStatic;
|
||||
} else if (motionState->getType() == MOTIONSTATE_TYPE_AVATAR) {
|
||||
// we can never take ownership of this constraint
|
||||
numOwned = 0;
|
||||
break;
|
||||
} else {
|
||||
if (motionState && motionState->getSimulatorID() == Physics::getSessionUUID()) {
|
||||
priority = glm::max(priority, motionState->getSimulationPriority());
|
||||
} else if (body == characterObject) {
|
||||
priority = glm::max(priority, PERSONAL_SIMULATION_PRIORITY);
|
||||
}
|
||||
numOwned++;
|
||||
}
|
||||
}
|
||||
|
||||
if (numOwned > 0) {
|
||||
if (numOwned + numStatic != bodies.size()) {
|
||||
// we have partial ownership but it isn't complete so we walk each object
|
||||
// and bump the simulation priority to the highest priority we encountered earlier
|
||||
foreach(btRigidBody* body, bodies) {
|
||||
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(body->getUserPointer());
|
||||
if (motionState) {
|
||||
// NOTE: we submit priority+1 because the default behavior of bump() is to actually use priority - 1
|
||||
// and we want all priorities of the objects to be at the SAME level
|
||||
motionState->bump(priority + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CollisionEvents& PhysicsEngine::getCollisionEvents() {
|
||||
_collisionEvents.clear();
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ public:
|
|||
void harvestPerformanceStats();
|
||||
void printPerformanceStatsToFile(const QString& filename);
|
||||
void updateContactMap();
|
||||
void doOwnershipInfectionForConstraints();
|
||||
|
||||
bool hasOutgoingChanges() const { return _hasOutgoingChanges; }
|
||||
|
||||
|
|
|
@ -177,10 +177,6 @@ const SteamClientPluginPointer PluginManager::getSteamClientPlugin() {
|
|||
return steamClientPlugin;
|
||||
}
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
|
||||
static DisplayPluginList displayPlugins;
|
||||
|
||||
const DisplayPluginList& PluginManager::getDisplayPlugins() {
|
||||
static std::once_flag once;
|
||||
static auto deviceAddedCallback = [](QString deviceName) {
|
||||
|
@ -194,7 +190,7 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() {
|
|||
|
||||
std::call_once(once, [&] {
|
||||
// Grab the built in plugins
|
||||
displayPlugins = _displayPluginProvider();
|
||||
_displayPlugins = _displayPluginProvider();
|
||||
|
||||
|
||||
// Now grab the dynamic plugins
|
||||
|
@ -202,11 +198,11 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() {
|
|||
DisplayProvider* displayProvider = qobject_cast<DisplayProvider*>(loader->instance());
|
||||
if (displayProvider) {
|
||||
for (auto displayPlugin : displayProvider->getDisplayPlugins()) {
|
||||
displayPlugins.push_back(displayPlugin);
|
||||
_displayPlugins.push_back(displayPlugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto plugin : displayPlugins) {
|
||||
for (auto plugin : _displayPlugins) {
|
||||
connect(plugin.get(), &Plugin::deviceConnected, this, deviceAddedCallback, Qt::QueuedConnection);
|
||||
connect(plugin.get(), &Plugin::subdeviceConnected, this, subdeviceAddedCallback, Qt::QueuedConnection);
|
||||
plugin->setContainer(_container);
|
||||
|
@ -214,21 +210,17 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() {
|
|||
}
|
||||
|
||||
});
|
||||
return displayPlugins;
|
||||
return _displayPlugins;
|
||||
}
|
||||
|
||||
void PluginManager::disableDisplayPlugin(const QString& name) {
|
||||
for (size_t i = 0; i < displayPlugins.size(); ++i) {
|
||||
if (displayPlugins[i]->getName() == name) {
|
||||
displayPlugins.erase(displayPlugins.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::remove_if(_displayPlugins.begin(), _displayPlugins.end(), [&](const DisplayPluginPointer& plugin){
|
||||
return plugin->getName() == name;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const InputPluginList& PluginManager::getInputPlugins() {
|
||||
static InputPluginList inputPlugins;
|
||||
static std::once_flag once;
|
||||
static auto deviceAddedCallback = [](QString deviceName) {
|
||||
qCDebug(plugins) << "Added device: " << deviceName;
|
||||
|
@ -240,7 +232,7 @@ const InputPluginList& PluginManager::getInputPlugins() {
|
|||
};
|
||||
|
||||
std::call_once(once, [&] {
|
||||
inputPlugins = _inputPluginProvider();
|
||||
_inputPlugins = _inputPluginProvider();
|
||||
|
||||
// Now grab the dynamic plugins
|
||||
for (auto loader : getLoadedPlugins()) {
|
||||
|
@ -248,20 +240,20 @@ const InputPluginList& PluginManager::getInputPlugins() {
|
|||
if (inputProvider) {
|
||||
for (auto inputPlugin : inputProvider->getInputPlugins()) {
|
||||
if (inputPlugin->isSupported()) {
|
||||
inputPlugins.push_back(inputPlugin);
|
||||
_inputPlugins.push_back(inputPlugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto plugin : inputPlugins) {
|
||||
for (auto plugin : _inputPlugins) {
|
||||
connect(plugin.get(), &Plugin::deviceConnected, this, deviceAddedCallback, Qt::QueuedConnection);
|
||||
connect(plugin.get(), &Plugin::subdeviceConnected, this, subdeviceAddedCallback, Qt::QueuedConnection);
|
||||
plugin->setContainer(_container);
|
||||
plugin->init();
|
||||
}
|
||||
});
|
||||
return inputPlugins;
|
||||
return _inputPlugins;
|
||||
}
|
||||
|
||||
void PluginManager::setPreferredDisplayPlugins(const QStringList& displays) {
|
||||
|
@ -328,4 +320,3 @@ void PluginManager::shutdown() {
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -44,4 +44,6 @@ private:
|
|||
CodecPluginProvider _codecPluginProvider { []()->CodecPluginList { return {}; } };
|
||||
InputPluginSettingsPersister _inputSettingsPersister { [](const InputPluginList& list) {} };
|
||||
PluginContainer* _container { nullptr };
|
||||
DisplayPluginList _displayPlugins;
|
||||
InputPluginList _inputPlugins;
|
||||
};
|
||||
|
|
|
@ -11,6 +11,12 @@
|
|||
#include "PickManager.h"
|
||||
#include "PointerManager.h"
|
||||
|
||||
#include "NumericalConstants.h"
|
||||
|
||||
const float Pointer::POINTER_MOVE_DELAY = 0.33f * USECS_PER_SECOND;
|
||||
const float TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481f;
|
||||
const float Pointer::TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED = TOUCH_PRESS_TO_MOVE_DEADSPOT * TOUCH_PRESS_TO_MOVE_DEADSPOT;
|
||||
|
||||
Pointer::~Pointer() {
|
||||
DependencyManager::get<PickManager>()->removePick(_pickUID);
|
||||
}
|
||||
|
@ -77,7 +83,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
|
|||
Buttons newButtons;
|
||||
Buttons sameButtons;
|
||||
if (_enabled && shouldTrigger(pickResult)) {
|
||||
buttons = getPressedButtons();
|
||||
buttons = getPressedButtons(pickResult);
|
||||
for (const std::string& button : buttons) {
|
||||
if (_prevButtons.find(button) == _prevButtons.end()) {
|
||||
newButtons.insert(button);
|
||||
|
@ -175,17 +181,6 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
|
|||
}
|
||||
}
|
||||
|
||||
// send hoverEnd events if we disable the pointer or disable hovering
|
||||
if (_hover && ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover))) {
|
||||
if (_prevHoveredObject.type == ENTITY) {
|
||||
emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent);
|
||||
} else if (_prevHoveredObject.type == OVERLAY) {
|
||||
emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent);
|
||||
} else if (_prevHoveredObject.type == HUD) {
|
||||
emit pointerManager->hoverEndHUD(hoveredEvent);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger begin
|
||||
const std::string SHOULD_FOCUS_BUTTON = "Focus";
|
||||
for (const std::string& button : newButtons) {
|
||||
|
@ -204,7 +199,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
|
|||
|
||||
// Trigger continue
|
||||
for (const std::string& button : sameButtons) {
|
||||
PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult, false);
|
||||
PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult, button, false);
|
||||
triggeredEvent.setID(pointerID);
|
||||
triggeredEvent.setType(PointerEvent::Move);
|
||||
triggeredEvent.setButton(chooseButton(button));
|
||||
|
@ -219,7 +214,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
|
|||
|
||||
// Trigger end
|
||||
for (const std::string& button : _prevButtons) {
|
||||
PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult, false);
|
||||
PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult, button, false);
|
||||
triggeredEvent.setID(pointerID);
|
||||
triggeredEvent.setType(PointerEvent::Release);
|
||||
triggeredEvent.setButton(chooseButton(button));
|
||||
|
@ -233,6 +228,17 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
|
|||
_triggeredObjects.erase(button);
|
||||
}
|
||||
|
||||
// if we disable the pointer or disable hovering, send hoverEnd events after triggerEnd
|
||||
if (_hover && ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover))) {
|
||||
if (_prevHoveredObject.type == ENTITY) {
|
||||
emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent);
|
||||
} else if (_prevHoveredObject.type == OVERLAY) {
|
||||
emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent);
|
||||
} else if (_prevHoveredObject.type == HUD) {
|
||||
emit pointerManager->hoverEndHUD(hoveredEvent);
|
||||
}
|
||||
}
|
||||
|
||||
_prevHoveredObject = hoveredObject;
|
||||
_prevButtons = buttons;
|
||||
_prevEnabled = _enabled;
|
||||
|
|
|
@ -82,14 +82,17 @@ protected:
|
|||
bool _enabled;
|
||||
bool _hover;
|
||||
|
||||
virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover = true) const = 0;
|
||||
virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) = 0;
|
||||
|
||||
virtual PickedObject getHoveredObject(const PickResultPointer& pickResult) = 0;
|
||||
virtual Buttons getPressedButtons() = 0;
|
||||
virtual Buttons getPressedButtons(const PickResultPointer& pickResult) = 0;
|
||||
|
||||
virtual bool shouldHover(const PickResultPointer& pickResult) { return true; }
|
||||
virtual bool shouldTrigger(const PickResultPointer& pickResult) { return true; }
|
||||
|
||||
static const float POINTER_MOVE_DELAY;
|
||||
static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED;
|
||||
|
||||
private:
|
||||
PickedObject _prevHoveredObject;
|
||||
Buttons _prevButtons;
|
||||
|
|
|
@ -39,7 +39,6 @@ void CauterizedMeshPartPayload::updateTransformForCauterizedMesh(const Transform
|
|||
}
|
||||
|
||||
void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const {
|
||||
// Still relying on the raw data from the model
|
||||
bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE && renderMode != RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) && _enableCauterization;
|
||||
if (useCauterizedMesh) {
|
||||
if (_cauterizedClusterBuffer) {
|
||||
|
@ -47,10 +46,7 @@ void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::S
|
|||
}
|
||||
batch.setModelTransform(_cauterizedTransform);
|
||||
} else {
|
||||
if (_clusterBuffer) {
|
||||
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _clusterBuffer);
|
||||
}
|
||||
batch.setModelTransform(_transform);
|
||||
ModelMeshPartPayload::bindTransform(batch, locations, renderMode);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -71,12 +71,12 @@ enum DeferredShader_BufferSlot {
|
|||
SCATTERING_PARAMETERS_BUFFER_SLOT,
|
||||
LIGHTING_MODEL_BUFFER_SLOT = render::ShapePipeline::Slot::LIGHTING_MODEL,
|
||||
LIGHT_GPU_SLOT = render::ShapePipeline::Slot::LIGHT,
|
||||
LIGHT_AMBIENT_SLOT,
|
||||
LIGHT_AMBIENT_SLOT = render::ShapePipeline::Slot::LIGHT_AMBIENT_BUFFER,
|
||||
HAZE_MODEL_BUFFER_SLOT = render::ShapePipeline::Slot::HAZE_MODEL,
|
||||
LIGHT_INDEX_GPU_SLOT,
|
||||
LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT,
|
||||
LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT,
|
||||
LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT,
|
||||
HAZE_MODEL_BUFFER_SLOT
|
||||
};
|
||||
|
||||
static void loadLightProgram(const char* vertSource, const char* fragSource, bool lightVolume, gpu::PipelinePointer& program, LightLocationsPtr& locations);
|
||||
|
|
|
@ -480,28 +480,18 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
|
|||
}
|
||||
|
||||
void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) {
|
||||
if (!_isBlendShaped) {
|
||||
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
|
||||
batch.setInputFormat((_drawMesh->getVertexFormat()));
|
||||
batch.setInputStream(0, _drawMesh->getVertexStream());
|
||||
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
|
||||
batch.setInputFormat((_drawMesh->getVertexFormat()));
|
||||
if (_isBlendShaped && _blendedVertexBuffer) {
|
||||
batch.setInputBuffer(0, _blendedVertexBuffer, 0, sizeof(glm::vec3));
|
||||
batch.setInputBuffer(1, _blendedVertexBuffer, _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3));
|
||||
batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2));
|
||||
} else {
|
||||
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
|
||||
batch.setInputFormat((_drawMesh->getVertexFormat()));
|
||||
|
||||
if (_blendedVertexBuffer) {
|
||||
batch.setInputBuffer(0, _blendedVertexBuffer, 0, sizeof(glm::vec3));
|
||||
batch.setInputBuffer(1, _blendedVertexBuffer, _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3));
|
||||
batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2));
|
||||
} else {
|
||||
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
|
||||
batch.setInputFormat((_drawMesh->getVertexFormat()));
|
||||
batch.setInputStream(0, _drawMesh->getVertexStream());
|
||||
}
|
||||
batch.setInputStream(0, _drawMesh->getVertexStream());
|
||||
}
|
||||
}
|
||||
|
||||
void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const {
|
||||
// Still relying on the raw data from the model
|
||||
if (_clusterBuffer) {
|
||||
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _clusterBuffer);
|
||||
}
|
||||
|
|
|
@ -163,7 +163,7 @@ void Model::setScale(const glm::vec3& scale) {
|
|||
_scaledToFit = false;
|
||||
}
|
||||
|
||||
const float SCALE_CHANGE_EPSILON = 0.001f;
|
||||
const float SCALE_CHANGE_EPSILON = 0.0000001f;
|
||||
|
||||
void Model::setScaleInternal(const glm::vec3& scale) {
|
||||
if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) {
|
||||
|
|
|
@ -78,7 +78,7 @@ public:
|
|||
|
||||
/// Sets the URL of the model to render.
|
||||
// Should only be called from the model's rendering thread to avoid access violations of changed geometry.
|
||||
Q_INVOKABLE void setURL(const QUrl& url);
|
||||
Q_INVOKABLE virtual void setURL(const QUrl& url);
|
||||
const QUrl& getURL() const { return _url; }
|
||||
|
||||
// new Scene/Engine rendering support
|
||||
|
@ -136,7 +136,7 @@ public:
|
|||
const Geometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; }
|
||||
|
||||
const QVariantMap getTextures() const { assert(isLoaded()); return _renderGeometry->getTextures(); }
|
||||
Q_INVOKABLE void setTextures(const QVariantMap& textures);
|
||||
Q_INVOKABLE virtual void setTextures(const QVariantMap& textures);
|
||||
|
||||
/// Provided as a convenience, will crash if !isLoaded()
|
||||
// And so that getGeometry() isn't chained everywhere
|
||||
|
|
|
@ -70,7 +70,7 @@ void SetupZones::run(const RenderContextPointer& context, const Inputs& inputs)
|
|||
|
||||
lightStage->_currentFrame.pushSunLight(0);
|
||||
lightStage->_currentFrame.pushAmbientLight(0);
|
||||
|
||||
hazeStage->_currentFrame.pushHaze(0);
|
||||
backgroundStage->_currentFrame.pushBackground(0);
|
||||
}
|
||||
|
||||
|
|
|
@ -237,8 +237,8 @@ public:
|
|||
LIGHTING_MODEL,
|
||||
LIGHT,
|
||||
LIGHT_AMBIENT_BUFFER,
|
||||
HAZE_MODEL,
|
||||
FADE_PARAMETERS,
|
||||
HAZE_MODEL
|
||||
};
|
||||
|
||||
enum MAP {
|
||||
|
|
|
@ -21,9 +21,10 @@
|
|||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtScript/QScriptable>
|
||||
#include <QList>
|
||||
#include <QHash>
|
||||
|
||||
// Scriptable interface of "console" object. Used exclusively in the JavaScript API
|
||||
class ConsoleScriptingInterface : public QObject, protected QScriptable {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue