Merge branch 'menu-updates' of github.com:vladest/hifi into menu-updates

This commit is contained in:
Dante Ruiz 2018-05-16 10:41:40 -07:00
commit 37c51a7246
85 changed files with 1867 additions and 1068 deletions
android/app
interface
libraries
display-plugins/src/display-plugins
entities-renderer/src
graphics/src/graphics
networking/src
physics/src
procedural/src/procedural
render-utils/src
render/src/render
shared/src
ui/src
plugins/openvr/src
scripts/system
tests-manual/render-texture-load/src
tools/auto-tester/src

View file

@ -104,6 +104,11 @@ dependencies {
implementation 'com.android.support:design:26.1.0'
implementation 'com.android.support:appcompat-v7:26.1.0'
compile 'com.android.support:recyclerview-v7:26.1.0'
compile 'com.android.support:cardview-v7:26.1.0'
compile 'com.squareup.retrofit2:retrofit:2.4.0'
compile 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation fileTree(include: ['*.jar'], dir: 'libs')
}

View file

@ -42,18 +42,18 @@
android:name=".HomeActivity"
android:label="@string/home"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar">
android:theme="@style/Theme.AppCompat.Translucent.NoActionBar">
</activity>
<activity
android:name=".GotoActivity"
android:label="@string/go_to"
android:theme="@style/AppTheme">
</activity>
android:theme="@style/AppTheme" />
<activity android:name=".LoginActivity"
android:theme="@style/AppTheme"/>
<activity
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|locale|fontScale|keyboard|keyboardHidden|navigation"
android:name=".InterfaceActivity"
android:label="@string/app_name"
android:screenOrientation="landscape"
android:launchMode="singleTop"
>
@ -75,6 +75,11 @@
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
<activity
android:name=".SplashActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Translucent.NoActionBar" />
</application>
<uses-feature android:name="android.software.vr.mode" android:required="true"/>

View file

@ -1,24 +0,0 @@
{
"hifi_domains" : [
{
"name": "Your last location",
"url": "",
"thumbnail": ""
},
{
"name": "dev-mobile-master",
"url": "hifi://dev-mobile-master",
"thumbnail": ""
},
{
"name": "dev-mobile",
"url": "hifi://dev-mobile",
"thumbnail": ""
},
{
"name": "dev-welcome",
"url": "hifi://dev-welcome",
"thumbnail": ""
}
]
}

View file

@ -21,8 +21,11 @@
#include <AddressManager.h>
#include "AndroidHelper.h"
#include <udt/PacketHeaders.h>
QAndroidJniObject __activity;
QAndroidJniObject __interfaceActivity;
QAndroidJniObject __loginActivity;
QAndroidJniObject __loadCompleteListener;
void tempMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
if (!message.isEmpty()) {
@ -142,16 +145,16 @@ void unpackAndroidAssets() {
extern "C" {
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject obj, jobject instance, jobject asset_mgr) {
qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId();
g_assetManager = AAssetManager_fromJava(env, asset_mgr);
__activity = QAndroidJniObject(instance);
qRegisterMetaType<QAndroidJniObject>("QAndroidJniObject");
__interfaceActivity = QAndroidJniObject(instance);
auto oldMessageHandler = qInstallMessageHandler(tempMessageHandler);
unpackAndroidAssets();
qInstallMessageHandler(oldMessageHandler);
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested, [](const QString& a) {
QAndroidJniObject string = QAndroidJniObject::fromString(a);
__activity.callMethod<void>("openAndroidActivity", "(Ljava/lang/String;)V", string.object<jstring>());
__interfaceActivity.callMethod<void>("openAndroidActivity", "(Ljava/lang/String;)V", string.object<jstring>());
});
}
@ -167,15 +170,12 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGotoUr
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnPause(JNIEnv* env, jobject obj) {
qDebug() << "nativeOnPause";
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnResume(JNIEnv* env, jobject obj) {
qDebug() << "nativeOnResume";
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnExitVr(JNIEnv* env, jobject obj) {
qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId();
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGoBackFromAndroidActivity(JNIEnv *env, jobject instance) {
@ -204,4 +204,68 @@ JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_getCurren
return env->NewStringUTF(str.toLatin1().data());
}
JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_protocolVersionSignature(JNIEnv *env, jobject instance) {
return env->NewStringUTF(protocolVersionsSignatureBase64().toLatin1().data());
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_LoginActivity_nativeLogin(JNIEnv *env, jobject instance,
jstring username_, jstring password_) {
const char *c_username = env->GetStringUTFChars(username_, 0);
const char *c_password = env->GetStringUTFChars(password_, 0);
QString username = QString(c_username);
QString password = QString(c_password);
env->ReleaseStringUTFChars(username_, c_username);
env->ReleaseStringUTFChars(password_, c_password);
auto accountManager = AndroidHelper::instance().getAccountManager();
__loginActivity = QAndroidJniObject(instance);
QObject::connect(accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) {
jboolean jSuccess = (jboolean) true;
__loginActivity.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
});
QObject::connect(accountManager.data(), &AccountManager::loginFailed, []() {
jboolean jSuccess = (jboolean) false;
__loginActivity.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
});
QMetaObject::invokeMethod(accountManager.data(), "requestAccessToken", Q_ARG(const QString&, username), Q_ARG(const QString&, password));
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(JNIEnv *env,
jobject instance) {
__loadCompleteListener = QAndroidJniObject(instance);
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, []() {
__loadCompleteListener.callMethod<void>("onAppLoadedComplete", "()V");
__interfaceActivity.callMethod<void>("onAppLoadedComplete", "()V");
QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, nullptr,
nullptr);
});
}
JNIEXPORT jboolean JNICALL
Java_io_highfidelity_hifiinterface_HomeActivity_nativeIsLoggedIn(JNIEnv *env, jobject instance) {
return AndroidHelper::instance().getAccountManager()->isLoggedIn();
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_HomeActivity_nativeLogout(JNIEnv *env, jobject instance) {
AndroidHelper::instance().getAccountManager()->logout();
}
JNIEXPORT jstring JNICALL
Java_io_highfidelity_hifiinterface_HomeActivity_nativeGetDisplayName(JNIEnv *env,
jobject instance) {
QString username = AndroidHelper::instance().getAccountManager()->getAccountInfo().getUsername();
return env->NewStringUTF(username.toLatin1().data());
}
}

View file

@ -9,12 +9,8 @@ import android.support.v7.widget.Toolbar;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import java.net.URI;
import java.net.URISyntaxException;
public class GotoActivity extends AppCompatActivity {
public static final String PARAM_DOMAIN_URL = "domain_url";
@ -61,15 +57,7 @@ public class GotoActivity extends AppCompatActivity {
private void actionGo() {
String urlString = mUrlEditText.getText().toString();
if (!urlString.trim().isEmpty()) {
URI uri;
try {
uri = new URI(urlString);
} catch (URISyntaxException e) {
return;
}
if (uri.getScheme() == null || uri.getScheme().isEmpty()) {
urlString = "hifi://" + urlString;
}
urlString = HifiUtils.getInstance().sanitizeHifiUrl(urlString);
Intent intent = new Intent();
intent.putExtra(GotoActivity.PARAM_DOMAIN_URL, urlString);

View file

@ -1,11 +1,16 @@
package io.highfidelity.hifiinterface;
import java.net.URI;
import java.net.URISyntaxException;
/**
* Created by Gabriel Calero & Cristian Duarte on 4/13/18.
*/
public class HifiUtils {
public static final String BASE_URL = "https://metaverse.highfidelity.com";
private static HifiUtils instance;
private HifiUtils() {
@ -18,6 +23,40 @@ public class HifiUtils {
return instance;
}
public String sanitizeHifiUrl(String urlString) {
urlString = urlString.trim();
if (!urlString.isEmpty()) {
URI uri;
try {
uri = new URI(urlString);
} catch (URISyntaxException e) {
return urlString;
}
if (uri.getScheme() == null || uri.getScheme().isEmpty()) {
urlString = "hifi://" + urlString;
}
}
return urlString;
}
public String absoluteHifiAssetUrl(String urlString) {
urlString = urlString.trim();
if (!urlString.isEmpty()) {
URI uri;
try {
uri = new URI(urlString);
} catch (URISyntaxException e) {
return urlString;
}
if (uri.getScheme() == null || uri.getScheme().isEmpty()) {
urlString = BASE_URL + urlString;
}
}
return urlString;
}
public native String getCurrentAddress();
public native String protocolVersionSignature();
}

View file

@ -1,12 +1,11 @@
package io.highfidelity.hifiinterface;
import android.app.ProgressDialog;
import android.content.Intent;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
@ -14,30 +13,43 @@ import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.SearchView;
import android.widget.TabHost;
import android.widget.TabWidget;
import android.view.Window;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import io.highfidelity.hifiinterface.QtPreloader.QtPreloader;
import org.qtproject.qt5.android.bindings.QtActivity;
import io.highfidelity.hifiinterface.view.DomainAdapter;
public class HomeActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
public native boolean nativeIsLoggedIn();
public native void nativeLogout();
public native String nativeGetDisplayName();
/**
* Set this intent extra param to NOT start a new InterfaceActivity after a domain is selected"
*/
public static final String PARAM_NOT_START_INTERFACE_ACTIVITY = "not_start_interface_activity";
//public static final String PARAM_NOT_START_INTERFACE_ACTIVITY = "not_start_interface_activity";
public static final int ENTER_DOMAIN_URL = 1;
private DomainAdapter domainAdapter;
private DomainAdapter mDomainAdapter;
private DrawerLayout mDrawerLayout;
private ProgressDialog mDialog;
private NavigationView mNavigationView;
private RecyclerView mDomainsView;
private TextView searchNoResultsView;
private ImageView mSearchIconView;
private ImageView mClearSearch;
private EditText mSearchView;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -56,114 +68,111 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On
mNavigationView = (NavigationView) findViewById(R.id.nav_view);
mNavigationView.setNavigationItemSelectedListener(this);
TabHost tabs = (TabHost)findViewById(R.id.tabhost);
tabs.setup();
searchNoResultsView = findViewById(R.id.searchNoResultsView);
TabHost.TabSpec spec=tabs.newTabSpec("featured");
spec.setContent(R.id.featured);
spec.setIndicator(getString(R.string.featured));
tabs.addTab(spec);
spec = tabs.newTabSpec("popular");
spec.setContent(R.id.popular);
spec.setIndicator(getString(R.string.popular));
tabs.addTab(spec);
spec = tabs.newTabSpec("bookmarks");
spec.setContent(R.id.bookmarks);
spec.setIndicator(getString(R.string.bookmarks));
tabs.addTab(spec);
tabs.setCurrentTab(0);
TabWidget tabwidget=tabs.getTabWidget();
for(int i=0; i<tabwidget.getChildCount(); i++){
TextView tv=(TextView) tabwidget.getChildAt(i).findViewById(android.R.id.title);
tv.setTextAppearance(R.style.TabText);
}
RecyclerView domainsView = findViewById(R.id.rvDomains);
mDomainsView = findViewById(R.id.rvDomains);
int numberOfColumns = 1;
GridLayoutManager gridLayoutMgr = new GridLayoutManager(this, numberOfColumns);
domainsView.setLayoutManager(gridLayoutMgr);
domainAdapter = new DomainAdapter(this);
domainAdapter.setClickListener(new DomainAdapter.ItemClickListener() {
mDomainsView.setLayoutManager(gridLayoutMgr);
mDomainAdapter = new DomainAdapter(this, HifiUtils.getInstance().protocolVersionSignature());
mDomainAdapter.setClickListener(new DomainAdapter.ItemClickListener() {
@Override
public void onItemClick(View view, int position, DomainAdapter.Domain domain) {
gotoDomain(domain.url);
new Handler(getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
gotoDomain(domain.url);
}
}, 400); // a delay so the ripple effect can be seen
}
});
domainsView.setAdapter(domainAdapter);
mDomainAdapter.setListener(new DomainAdapter.AdapterListener() {
@Override
public void onEmptyAdapter() {
searchNoResultsView.setText(R.string.search_no_results);
searchNoResultsView.setVisibility(View.VISIBLE);
mDomainsView.setVisibility(View.GONE);
}
SearchView searchView = findViewById(R.id.searchView);
int searchPlateId = searchView.getContext().getResources().getIdentifier("android:id/search_plate", null, null);
View searchPlate = searchView.findViewById(searchPlateId);
if (searchPlate != null) {
searchPlate.setBackgroundColor (Color.TRANSPARENT);
int searchTextId = searchPlate.getContext ().getResources ().getIdentifier ("android:id/search_src_text", null, null);
TextView searchTextView = searchView.findViewById(searchTextId);
searchTextView.setTextAppearance(R.style.SearchText);
@Override
public void onNonEmptyAdapter() {
searchNoResultsView.setVisibility(View.GONE);
mDomainsView.setVisibility(View.VISIBLE);
}
@Override
public void onError(Exception e, String message) {
}
});
mDomainsView.setAdapter(mDomainAdapter);
mSearchView = findViewById(R.id.searchView);
mSearchIconView = findViewById(R.id.search_mag_icon);
mClearSearch = findViewById(R.id.search_clear);
mSearchView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void afterTextChanged(Editable editable) {
mDomainAdapter.loadDomains(editable.toString());
if (editable.length() > 0) {
mSearchIconView.setVisibility(View.GONE);
mClearSearch.setVisibility(View.VISIBLE);
} else {
mSearchIconView.setVisibility(View.VISIBLE);
mClearSearch.setVisibility(View.GONE);
}
}
});
mSearchView.setOnKeyListener((view, i, keyEvent) -> {
if (i == KeyEvent.KEYCODE_ENTER) {
String urlString = mSearchView.getText().toString();
if (!urlString.trim().isEmpty()) {
urlString = HifiUtils.getInstance().sanitizeHifiUrl(urlString);
}
gotoDomain(urlString);
return true;
}
return false;
});
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
updateLoginMenu();
Window window = getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(ContextCompat.getColor(this, R.color.statusbar_color));
}
private void updateLoginMenu() {
TextView loginOption = findViewById(R.id.login);
TextView logoutOption = findViewById(R.id.logout);
if (nativeIsLoggedIn()) {
loginOption.setVisibility(View.GONE);
logoutOption.setVisibility(View.VISIBLE);
} else {
loginOption.setVisibility(View.VISIBLE);
logoutOption.setVisibility(View.GONE);
}
if (getIntent() == null ||
!getIntent().hasExtra(PARAM_NOT_START_INTERFACE_ACTIVITY) ||
!getIntent().getBooleanExtra(PARAM_NOT_START_INTERFACE_ACTIVITY, false)) {
preloadQt();
showActivityIndicator();
}
}
private void gotoDomain(String domainUrl) {
Intent intent = new Intent(HomeActivity.this, InterfaceActivity.class);
intent.putExtra(InterfaceActivity.DOMAIN_URL, domainUrl);
HomeActivity.this.finish();
if (getIntent() != null &&
getIntent().hasExtra(PARAM_NOT_START_INTERFACE_ACTIVITY) &&
getIntent().getBooleanExtra(PARAM_NOT_START_INTERFACE_ACTIVITY, false)) {
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
}
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
}
private void showActivityIndicator() {
if (mDialog == null) {
mDialog = new ProgressDialog(this);
}
mDialog.setMessage("Please wait...");
mDialog.setCancelable(false);
mDialog.show();
}
private void cancelActivityIndicator() {
if (mDialog != null) {
mDialog.cancel();
}
}
private AsyncTask preloadTask;
private void preloadQt() {
if (preloadTask == null) {
preloadTask = new AsyncTask() {
@Override
protected Object doInBackground(Object[] objects) {
new QtPreloader(HomeActivity.this).initQt();
runOnUiThread(new Runnable() {
@Override
public void run() {
cancelActivityIndicator();
}
});
return null;
}
};
preloadTask.execute();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
@ -188,7 +197,6 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On
@Override
protected void onDestroy() {
cancelActivityIndicator();
super.onDestroy();
}
@ -199,8 +207,6 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On
Intent i = new Intent(this, GotoActivity.class);
startActivityForResult(i, ENTER_DOMAIN_URL);
return true;
case R.id.action_settings:
return true;
}
return false;
}
@ -213,8 +219,27 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On
}
}
@Override
protected void onStart() {
super.onStart();
updateLoginMenu();
}
public void onLoginClicked(View view) {
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
}
public void onLogoutClicked(View view) {
nativeLogout();
updateLoginMenu();
}
@Override
public void onBackPressed() {
finishAffinity();
}
public void onSearchClear(View view) {
mSearchView.setText("");
}
}

View file

@ -36,8 +36,6 @@ public class InterfaceActivity extends QtActivity {
//public static native void handleHifiURL(String hifiURLString);
private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager);
//private native void nativeOnPause();
//private native void nativeOnResume();
private native void nativeOnDestroy();
private native void nativeGotoUrl(String url);
private native void nativeGoBackFromAndroidActivity();
@ -63,6 +61,7 @@ public class InterfaceActivity extends QtActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.isLoading = true;
Intent intent = getIntent();
if (intent.hasExtra(DOMAIN_URL) && !intent.getStringExtra(DOMAIN_URL).isEmpty()) {
intent.putExtra("applicationArguments", "--url " + intent.getStringExtra(DOMAIN_URL));
@ -112,31 +111,33 @@ public class InterfaceActivity extends QtActivity {
}
}
});
startActivity(new Intent(this, SplashActivity.class));
}
@Override
protected void onPause() {
super.onPause();
//nativeOnPause();
nativeEnterBackground();
//gvrApi.pauseTracking();
}
@Override
protected void onStart() {
super.onStart();
nativeEnterForeground();
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
@Override
protected void onStop() {
super.onStop();
nativeEnterBackground();
}
@Override
protected void onResume() {
super.onResume();
//nativeOnResume();
nativeEnterForeground();
//gvrApi.resumeTracking();
}
@ -201,7 +202,6 @@ public class InterfaceActivity extends QtActivity {
switch (activityName) {
case "Home": {
Intent intent = new Intent(this, HomeActivity.class);
intent.putExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY, true);
startActivity(intent);
break;
}
@ -212,6 +212,10 @@ public class InterfaceActivity extends QtActivity {
}
}
public void onAppLoadedComplete() {
super.isLoading = false;
}
@Override
public void onBackPressed() {
openAndroidActivity("Home");

View file

@ -0,0 +1,106 @@
package io.highfidelity.hifiinterface;
import android.app.ProgressDialog;
import android.support.annotation.MainThread;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class LoginActivity extends AppCompatActivity {
public native void nativeLogin(String username, String password);
private EditText mUsername;
private EditText mPassword;
private TextView mError;
private Button mLoginButton;
private ProgressDialog mDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mUsername = findViewById(R.id.username);
mPassword = findViewById(R.id.password);
mError = findViewById(R.id.error);
mLoginButton = findViewById(R.id.loginButton);
mPassword.setOnEditorActionListener(
new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
mLoginButton.performClick();
return true;
}
return false;
}
});
}
@Override
protected void onStop() {
super.onStop();
cancelActivityIndicator();
}
public void login(View view) {
String username = mUsername.getText().toString();
String password = mPassword.getText().toString();
if (username.isEmpty() || password.isEmpty()) {
showError(getString(R.string.login_username_or_password_incorrect));
} else {
mLoginButton.setEnabled(false);
hideError();
showActivityIndicator();
nativeLogin(username, password);
}
}
private void showActivityIndicator() {
if (mDialog == null) {
mDialog = new ProgressDialog(this);
}
mDialog.setMessage(getString(R.string.logging_in));
mDialog.setCancelable(false);
mDialog.show();
}
private void cancelActivityIndicator() {
if (mDialog != null) {
mDialog.cancel();
}
}
private void showError(String error) {
mError.setText(error);
mError.setVisibility(View.VISIBLE);
}
private void hideError() {
mError.setText("");
mError.setVisibility(View.INVISIBLE);
}
public void handleLoginCompleted(boolean success) {
runOnUiThread(() -> {
mLoginButton.setEnabled(true);
cancelActivityIndicator();
if (success) {
finish();
} else {
showError(getString(R.string.login_username_or_password_incorrect));
}
});
}
}

View file

@ -63,8 +63,7 @@ public class PermissionChecker extends Activity {
}
private void launchActivityWithPermissions(){
finish();
Intent i = new Intent(this, HomeActivity.class);
Intent i = new Intent(this, InterfaceActivity.class);
startActivity(i);
finish();
}

View file

@ -1,315 +0,0 @@
package io.highfidelity.hifiinterface.QtPreloader;
import android.content.ComponentName;
import android.content.Context;
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 org.qtproject.qt5.android.bindings.QtApplication;
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.Method;
import java.util.ArrayList;
import dalvik.system.DexClassLoader;
/**
* Created by Gabriel Calero & Cristian Duarte on 3/22/18.
*/
public class QtPreloader {
public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1";
private ComponentInfo m_contextInfo;
private String[] m_qtLibs = null; // required qt libs
private Context m_context;
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 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 int BUFFER_SIZE = 1024;
public QtPreloader(Context context) {
m_context = context;
}
public void initQt() {
try {
m_contextInfo = m_context.getPackageManager().getActivityInfo(new ComponentName("io.highfidelity.hifiinterface", "io.highfidelity.hifiinterface.InterfaceActivity"),
PackageManager.GET_META_DATA);
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());
loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList);
loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES
+ "\tQML2_IMPORT_PATH=" + pluginsPrefix + "/qml"
+ "\tQML_IMPORT_PATH=" + pluginsPrefix + "/imports"
+ "\tQT_PLUGIN_PATH=" + pluginsPrefix + "/plugins");
// 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);
prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams);
// now load the application library so it's accessible from this class loader
if (libName != null) {
System.loadLibrary(libName);
}
} catch (Exception e) {
Log.e(QtApplication.QtTAG, "Error pre-loading HiFi Qt app", e);
}
}
protected String loaderClassName() {
return "org.qtproject.qt5.android.QtActivityDelegate";
}
protected Class<?> contextClassName() {
return android.app.Activity.class;
}
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);
}
}
}
}
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);
}
}
}
}
}

View file

@ -0,0 +1,33 @@
package io.highfidelity.hifiinterface;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
public class SplashActivity extends Activity {
private native void registerLoadCompleteListener();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
registerLoadCompleteListener();
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onStop() {
super.onStop();
}
public void onAppLoadedComplete() {
startActivity(new Intent(this, HomeActivity.class)); // + 2 sec
SplashActivity.this.finish();
}
}

View file

@ -0,0 +1,9 @@
package io.highfidelity.hifiinterface.provider;
/**
* Created by cduarte on 4/18/18.
*/
public interface Callback<T> {
public void callback(T t);
}

View file

@ -0,0 +1,19 @@
package io.highfidelity.hifiinterface.provider;
import java.util.List;
import io.highfidelity.hifiinterface.view.DomainAdapter;
/**
* Created by cduarte on 4/17/18.
*/
public interface DomainProvider {
void retrieve(String filterText, DomainCallback domainCallback);
interface DomainCallback {
void retrieveOk(List<DomainAdapter.Domain> domain);
void retrieveError(Exception e, String message);
}
}

View file

@ -0,0 +1,235 @@
package io.highfidelity.hifiinterface.provider;
import android.util.Log;
import android.util.MutableInt;
import java.util.ArrayList;
import java.util.List;
import io.highfidelity.hifiinterface.HifiUtils;
import io.highfidelity.hifiinterface.view.DomainAdapter;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.GET;
import retrofit2.http.Query;
/**
* Created by cduarte on 4/17/18.
*/
public class UserStoryDomainProvider implements DomainProvider {
public static final String BASE_URL = "https://metaverse.highfidelity.com/";
private static final String INCLUDE_ACTIONS_FOR_PLACES = "concurrency";
private static final String INCLUDE_ACTIONS_FOR_FULL_SEARCH = "concurrency,announcements,snapshot";
private static final int MAX_PAGES_TO_GET = 10;
private String mProtocol;
private Retrofit mRetrofit;
private UserStoryDomainProviderService mUserStoryDomainProviderService;
private boolean startedToGetFromAPI = false;
private List<UserStory> allStories; // All retrieved stories from the API
private List<DomainAdapter.Domain> suggestions; // Filtered places to show
public UserStoryDomainProvider(String protocol) {
mRetrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
mUserStoryDomainProviderService = mRetrofit.create(UserStoryDomainProviderService.class);
mProtocol = protocol;
allStories = new ArrayList<>();
suggestions = new ArrayList<>();
}
private void fillDestinations(String filterText, DomainCallback domainCallback) {
StoriesFilter filter = new StoriesFilter(filterText);
final MutableInt counter = new MutableInt(0);
allStories.clear();
getUserStoryPage(1,
e -> {
allStories.subList(counter.value, allStories.size()).forEach(userStory -> {
filter.filterOrAdd(userStory);
});
if (domainCallback != null) {
domainCallback.retrieveOk(suggestions); //ended
}
},
a -> {
allStories.forEach(userStory -> {
counter.value++;
filter.filterOrAdd(userStory);
});
}
);
}
private void handleError(String url, Throwable t, Callback<Exception> restOfPagesCallback) {
restOfPagesCallback.callback(new Exception("Error accessing url [" + url + "]", t));
}
private void getUserStoryPage(int pageNumber, Callback<Exception> restOfPagesCallback, Callback<Void> firstPageCallback) {
Call<UserStories> userStories = mUserStoryDomainProviderService.getUserStories(
INCLUDE_ACTIONS_FOR_PLACES,
"open",
true,
mProtocol,
pageNumber);
Log.d("API-USER-STORY-DOMAINS", "Protocol [" + mProtocol + "] include_actions [" + INCLUDE_ACTIONS_FOR_PLACES + "]");
userStories.enqueue(new retrofit2.Callback<UserStories>() {
@Override
public void onResponse(Call<UserStories> call, Response<UserStories> response) {
UserStories data = response.body();
allStories.addAll(data.user_stories);
if (data.current_page < data.total_pages && data.current_page <= MAX_PAGES_TO_GET) {
if (pageNumber == 1 && firstPageCallback != null) {
firstPageCallback.callback(null);
}
getUserStoryPage(pageNumber + 1, restOfPagesCallback, null);
return;
}
restOfPagesCallback.callback(null);
}
@Override
public void onFailure(Call<UserStories> call, Throwable t) {
handleError(call.request().url().toString(), t, restOfPagesCallback);
}
});
}
private class StoriesFilter {
String[] mWords = new String[]{};
public StoriesFilter(String filterText) {
mWords = filterText.toUpperCase().split("\\s+");
}
private boolean matches(UserStory story) {
if (mWords.length <= 0) {
return true;
}
for (String word : mWords) {
if (!story.searchText().contains(word)) {
return false;
}
}
return true;
}
private void addToSuggestions(UserStory story) {
suggestions.add(story.toDomain());
}
public void filterOrAdd(UserStory story) {
if (matches(story)) {
addToSuggestions(story);
}
}
}
private void filterChoicesByText(String filterText, DomainCallback domainCallback) {
suggestions.clear();
StoriesFilter storiesFilter = new StoriesFilter(filterText);
allStories.forEach(story -> {
storiesFilter.filterOrAdd(story);
});
domainCallback.retrieveOk(suggestions);
}
@Override
public synchronized void retrieve(String filterText, DomainCallback domainCallback) {
if (!startedToGetFromAPI) {
startedToGetFromAPI = true;
fillDestinations(filterText, domainCallback);
} else {
filterChoicesByText(filterText, domainCallback);
}
}
public void retrieveNot(DomainCallback domainCallback) {
// TODO Call multiple pages
Call<UserStories> userStories = mUserStoryDomainProviderService.getUserStories(
INCLUDE_ACTIONS_FOR_PLACES,
"open",
true,
mProtocol,
1);
Log.d("API-USER-STORY-DOMAINS", "Protocol [" + mProtocol + "] include_actions [" + INCLUDE_ACTIONS_FOR_PLACES + "]");
userStories.enqueue(new retrofit2.Callback<UserStories>() {
@Override
public void onResponse(Call<UserStories> call, Response<UserStories> response) {
UserStories userStories = response.body();
if (userStories == null) {
domainCallback.retrieveOk(new ArrayList<>(0));
}
List<DomainAdapter.Domain> domains = new ArrayList<>(userStories.total_entries);
userStories.user_stories.forEach(userStory -> {
domains.add(userStory.toDomain());
});
domainCallback.retrieveOk(domains);
}
@Override
public void onFailure(Call<UserStories> call, Throwable t) {
domainCallback.retrieveError(new Exception(t), t.getMessage());
}
});
}
public interface UserStoryDomainProviderService {
@GET("api/v1/user_stories")
Call<UserStories> getUserStories(@Query("include_actions") String includeActions,
@Query("restriction") String restriction,
@Query("require_online") boolean requireOnline,
@Query("protocol") String protocol,
@Query("page") int pageNumber);
}
class UserStory {
public UserStory() {}
String place_name;
String path;
String thumbnail_url;
String place_id;
String domain_id;
private String searchText;
// New fields? tags, description
String searchText() {
if (searchText == null) {
searchText = place_name == null? "" : place_name.toUpperCase();
}
return searchText;
}
DomainAdapter.Domain toDomain() {
// TODO Proper url creation (it can or can't have hifi
// TODO Or use host value from api?
String absoluteThumbnailUrl = HifiUtils.getInstance().absoluteHifiAssetUrl(thumbnail_url);
DomainAdapter.Domain domain = new DomainAdapter.Domain(
place_name,
HifiUtils.getInstance().sanitizeHifiUrl(place_name) + "/" + path,
absoluteThumbnailUrl
);
return domain;
}
}
class UserStories {
String status;
int current_page;
int total_pages;
int total_entries;
List<UserStory> user_stories;
}
}

View file

@ -1,6 +1,7 @@
package io.highfidelity.hifiinterface.view;
import android.content.Context;
import android.net.Uri;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
@ -9,6 +10,8 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -19,69 +22,59 @@ import java.util.ArrayList;
import java.util.List;
import io.highfidelity.hifiinterface.R;
import io.highfidelity.hifiinterface.provider.DomainProvider;
import io.highfidelity.hifiinterface.provider.UserStoryDomainProvider;
/**
* Created by Gabriel Calero & Cristian Duarte on 3/20/18.
*/
public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder> {
private static final java.lang.String DOMAINS_FILE = "hifi_domains.json";
private static final String TAG = "HiFi Interface";
private Context mContext;
private LayoutInflater mInflater;
private ItemClickListener mClickListener;
public class Domain {
public String name;
public String url;
public String thumbnail;
Domain(String name, String url, String thumbnail) {
this.name = name;
this.thumbnail = thumbnail;
this.url = url;
}
}
private String mProtocol;
private UserStoryDomainProvider domainProvider;
private AdapterListener mAdapterListener;
// references to our domains
private Domain[] mDomains = {};
public DomainAdapter(Context c) {
public DomainAdapter(Context c, String protocol) {
mContext = c;
this.mInflater = LayoutInflater.from(mContext);
loadDomains();
mProtocol = protocol;
domainProvider = new UserStoryDomainProvider(mProtocol);
loadDomains("");
}
private void loadDomains() {
try {
JSONObject json = new JSONObject(loadJSONFromAsset());
JSONArray hifiDomains = json.getJSONArray("hifi_domains");
List<Domain> domains = new ArrayList<>();
for (int i = 0; i < hifiDomains.length(); i++) {
JSONObject jDomain = hifiDomains.getJSONObject(i);
public void setListener(AdapterListener adapterListener) {
mAdapterListener = adapterListener;
}
String domainName = jDomain.getString("name");
String domainUrl = jDomain.getString("url");
String domainThumb = jDomain.getString("thumbnail");
domains.add(new Domain(domainName, domainUrl, domainThumb));
public void loadDomains(String filterText) {
domainProvider.retrieve(filterText, new DomainProvider.DomainCallback() {
@Override
public void retrieveOk(List<Domain> domain) {
mDomains = new Domain[domain.size()];
mDomains = domain.toArray(mDomains);
notifyDataSetChanged();
if (mAdapterListener != null) {
if (mDomains.length == 0) {
mAdapterListener.onEmptyAdapter();
} else {
mAdapterListener.onNonEmptyAdapter();
}
}
}
mDomains = domains.toArray(mDomains);
} catch (IOException e) {
Log.e(TAG, "Error loading domains from local file", e);
} catch (JSONException e) {
Log.e(TAG, "Error loading domains from local file", e);
}
}
public String loadJSONFromAsset() throws IOException {
String json = null;
InputStream is = mContext.getAssets().open(DOMAINS_FILE);
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
json = new String(buffer, "UTF-8");
return json;
@Override
public void retrieveError(Exception e, String message) {
Log.e("DOMAINS", message, e);
if (mAdapterListener != null) mAdapterListener.onError(e, message);
}
});
}
@Override
@ -94,7 +87,10 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
public void onBindViewHolder(ViewHolder holder, int position) {
// TODO
//holder.thumbnail.setImageResource(mDomains[position].thumbnail);
holder.mDomainName.setText(mDomains[position].name);
Domain domain = mDomains[position];
holder.mDomainName.setText(domain.name);
Uri uri = Uri.parse(domain.thumbnail);
Picasso.get().load(uri).into(holder.mThumbnail);
}
@Override
@ -128,4 +124,21 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
public interface ItemClickListener {
void onItemClick(View view, int position, Domain domain);
}
public static class Domain {
public String name;
public String url;
public String thumbnail;
public Domain(String name, String url, String thumbnail) {
this.name = name;
this.thumbnail = thumbnail;
this.url = url;
}
}
public interface AdapterListener {
void onEmptyAdapter();
void onNonEmptyAdapter();
void onError(Exception e, String message);
}
}

View file

@ -68,6 +68,8 @@ public class QtActivity extends Activity {
public final String QT_ANDROID_DEFAULT_THEME = QT_ANDROID_THEMES[0]; // sets the default theme.
private QtActivityLoader m_loader = new QtActivityLoader(this);
public boolean isLoading;
public QtActivity() {
}
@ -499,7 +501,11 @@ public class QtActivity extends Activity {
@Override
protected void onPause() {
super.onPause();
QtApplication.invokeDelegate();
// GC: this trick allow us to show a splash activity until Qt app finishes
// loading
if (!isLoading) {
QtApplication.invokeDelegate();
}
}
//---------------------------------------------------------------------------
@ -640,6 +646,7 @@ public class QtActivity extends Activity {
super.onStop();
QtApplication.invokeDelegate();
}
//---------------------------------------------------------------------------
@Override

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:top="1dp" android:left="12dp" android:bottom="6dp" android:right="12dp">
<shape android:shape="rectangle">
<solid android:color="@color/backgroundDark" />
<corners android:radius="4dp"/>
</shape>
</item>
</layer-list>

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="350dp"
android:height="100dp"
android:viewportWidth="350"
android:viewportHeight="100">
<path
android:fillColor="#00B4F0"
android:pathData="M132.646,40.971 L132.646,62.955 L127.959,62.955 L127.959,53.361 L120.222,53.361 L120.222,62.955 L115.535,62.955 L115.535,40.971 L120.222,40.971 L120.222,49.453 L127.959,49.453 L127.959,40.971 L132.646,40.971 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M140.959,62.955 L136.271,62.955 L136.271,40.971 L140.959,40.971 L140.959,62.955 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M154.488,44.479 C153.383,44.479,152.454,44.568,151.658,44.79 C150.906,45.012,150.287,45.367,149.801,45.856 C149.315,46.345,149.006,47.055,148.784,47.899 C148.563,48.743,148.474,49.809,148.474,51.053 L148.474,53.051 C148.474,54.383,148.563,55.449,148.74,56.293 C148.917,57.137,149.226,57.803,149.668,58.247 C150.066,58.736,150.641,59.047,151.305,59.225 C151.968,59.403,152.808,59.492,153.781,59.492 C154.047,59.492,154.356,59.492,154.665,59.448 C154.976,59.448,155.284,59.403,155.638,59.358 L155.638,53.408 L152.898,53.408 L153.339,49.765 L160.06,49.765 L160.06,62.29 C159.22,62.601,158.16,62.823,156.876,63.001 C155.638,63.179,154.312,63.267,152.985,63.267 C151.305,63.267,149.89,63.045,148.741,62.646 C147.548,62.247,146.619,61.625,145.867,60.782 C145.116,59.937,144.585,58.916,144.231,57.628 C143.877,56.385,143.745,54.874,143.745,53.188 L143.745,50.921 C143.745,49.367,143.922,47.99,144.231,46.702 C144.585,45.414,145.16,44.348,145.956,43.46 C146.751,42.572,147.812,41.861,149.095,41.373 C150.377,40.884,152.013,40.618,153.958,40.618 C155.02,40.618,156.081,40.706,157.097,40.884 C158.115,41.062,158.954,41.284,159.618,41.506 L158.866,45.059 C158.291,44.881,157.628,44.748,156.921,44.615 C156.212,44.523,155.416,44.479,154.488,44.479 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M181.104,40.971 L181.104,62.955 L176.417,62.955 L176.417,53.361 L168.724,53.361 L168.724,62.955 L164.037,62.955 L164.037,40.971 L168.724,40.971 L168.724,49.453 L176.461,49.453 L176.461,40.971 L181.104,40.971 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M206.084,40.971 L205.555,44.79 L197.375,44.79 L197.375,49.365 L204.936,49.365 L204.405,53.183 L197.377,53.183 L197.377,62.954 L192.689,62.954 L192.689,40.971 L206.084,40.971 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M213.688,62.955 L209.001,62.955 L209.001,40.971 L213.688,40.971 L213.688,62.955 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M217.447,40.971 L224.609,40.971 C226.465,40.971,228.013,41.237,229.252,41.726 C230.491,42.215,231.463,42.925,232.215,43.769 C232.966,44.657,233.452,45.679,233.762,46.922 C234.073,48.166,234.203,49.498,234.203,50.92 L234.203,53.007 C234.203,54.473,234.071,55.805,233.762,57.049 C233.452,58.292,232.922,59.315,232.215,60.202 C231.463,61.09,230.49,61.756,229.252,62.245 C228.014,62.732,226.467,63,224.609,63 L217.491,63 L217.491,40.971 L217.447,40.971 L217.447,40.971 Z M222.09,59.137 L224.123,59.137 C225.008,59.137,225.76,59.047,226.467,58.825 C227.13,58.603,227.705,58.248,228.147,57.76 C228.589,57.272,228.899,56.606,229.121,55.805 C229.342,55.006,229.431,53.985,229.431,52.742 L229.431,51.098 C229.431,49.854,229.342,48.832,229.121,48.034 C228.899,47.235,228.591,46.569,228.147,46.08 C227.707,45.591,227.174,45.236,226.467,45.059 C225.803,44.881,225.008,44.748,224.123,44.748 L222.09,44.748 L222.09,59.137 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M250.871,40.971 L250.385,44.657 L241.942,44.657 L241.942,49.453 L249.899,49.453 L249.459,53.141 L241.985,53.141 L241.985,59.315 L250.918,59.315 L250.43,63 L237.254,63 L237.254,40.971 L250.871,40.971 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M258.609,40.971 L258.609,59.137 L267.496,59.137 L266.965,62.955 L253.922,62.955 L253.922,40.971 L258.609,40.971 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M275.232,62.955 L270.544,62.955 L270.544,40.971 L275.232,40.971 L275.232,62.955 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M295.482,40.971 L294.952,44.79 L289.027,44.79 L289.027,62.955 L284.339,62.955 L284.339,44.79 L277.884,44.79 L278.415,40.971 L295.482,40.971 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M306.58,49.543 L311.134,40.971 L315.953,40.971 L308.791,53.363 L308.791,62.956 L304.103,62.956 L304.103,53.363 L297.03,40.971 L302.159,40.971 L306.58,49.543 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M68.848,83.209 C64.427,83.209,60.094,82.32,56.07,80.633 C52.135,78.989,48.642,76.591,45.636,73.571 C42.63,70.551,40.242,66.999,38.606,63.091 C36.837,58.96,35.953,54.653,35.953,50.166 C35.953,45.725,36.837,41.372,38.517,37.33 C40.153,33.377,42.54,29.869,45.547,26.848 C48.553,23.828,52.09,21.43,55.981,19.786 C60.049,18.054,64.337,17.21,68.758,17.21 C73.179,17.21,77.513,18.098,81.536,19.786 C85.471,21.429,88.964,23.828,91.97,26.848 C94.977,29.869,97.365,33.422,98.999,37.33 C100.724,41.416,101.563,45.725,101.563,50.166 C101.563,54.606,100.681,58.959,99,63.001 C97.365,66.954,94.978,70.462,91.971,73.483 C88.965,76.503,85.428,78.901,81.537,80.544 C77.602,82.32,73.269,83.209,68.848,83.209 Z M68.848,20.584 C52.621,20.584,39.402,33.864,39.402,50.164 C39.402,66.465,52.621,79.744,68.848,79.744 S98.293,66.465,98.293,50.164 C98.293,33.864,85.074,20.584,68.848,20.584 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M78.884,64.199 L78.884,41.014 C80.343,40.525,81.36,39.149,81.36,37.55 C81.36,35.551,79.725,33.908,77.735,33.908 C75.745,33.908,74.11,35.552,74.11,37.55 C74.11,39.105,75.038,40.393,76.409,40.97 L76.409,51.762 L61.376,44.745 L61.376,36.217 C62.835,35.729,63.852,34.352,63.852,32.753 C63.852,30.754,62.216,29.111,60.227,29.111 C58.238,29.111,56.602,30.755,56.602,32.753 C56.602,34.308,57.531,35.596,58.901,36.173 L58.901,59.491 C57.574,60.023,56.602,61.355,56.602,62.911 C56.602,64.91,58.238,66.553,60.227,66.553 C62.216,66.553,63.852,64.91,63.852,62.911 C63.852,61.312,62.835,59.935,61.376,59.447 L61.376,47.676 L76.409,54.694 L76.409,64.244 C75.083,64.775,74.11,66.109,74.11,67.663 C74.11,69.662,75.745,71.305,77.735,71.305 C79.725,71.305,81.36,69.662,81.36,67.663 C81.359,66.02,80.343,64.688,78.884,64.199 Z" />
</vector>

View file

@ -0,0 +1,12 @@
<vector android:height="24dp" android:viewportHeight="15.0"
android:viewportWidth="15.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#404040" android:pathData="M7.5,7.5m-7.5,0a7.5,7.5 0,1 1,15 0a7.5,7.5 0,1 1,-15 0"/>
<path android:fillColor="#00000000"
android:pathData="M4.683,4.583L10,10.27"
android:strokeColor="#E3E3E3" android:strokeLineCap="round"
android:strokeLineJoin="bevel" android:strokeWidth="1"/>
<path android:fillColor="#00000000"
android:pathData="M10,4.73L4.683,10.417"
android:strokeColor="#E3E3E3" android:strokeLineCap="round"
android:strokeLineJoin="bevel" android:strokeWidth="1"/>
</vector>

View file

@ -0,0 +1,4 @@
<vector android:height="24dp" android:viewportHeight="18.0"
android:viewportWidth="18.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#F2F2F2" android:fillType="evenOdd" android:pathData="M12.5,11L11.7,11L11.4,10.7C12.4,9.6 13,8.1 13,6.5C13,2.9 10.1,0 6.5,0C2.9,0 0,2.9 0,6.5C0,10.1 2.9,13 6.5,13C8.1,13 9.6,12.4 10.7,11.4L11,11.7L11,12.5L16,17.5L17.5,16L12.5,11ZM6.5,11C4,11 2,9 2,6.5C2,4 4,2 6.5,2C9,2 11,4 11,6.5C11,9 9,11 6.5,11Z"/>
</vector>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_pressed="true" >
<shape android:shape="rectangle" >
<corners android:radius="4dip" />
<stroke android:width="1dip" android:color="@color/colorButton1" />
<solid android:color="@color/colorButton1"/>
</shape>
</item>
<item android:state_focused="true">
<shape android:shape="rectangle" >
<corners android:radius="4dip" />
<stroke android:width="1dip" android:color="@color/colorButton1" />
<solid android:color="@color/colorButton1"/>
</shape>
</item>
<item>
<shape android:shape="rectangle" >
<corners android:radius="4dip" />
<stroke android:width="1dip" android:color="@color/colorButton1" />
<solid android:color="@color/colorButton1"/>
</shape>
</item>
</selector>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" android:padding="9dp">
<corners android:radius="4dip" />
<stroke android:width="1dip" android:color="@android:color/black" />
<solid android:color="@color/backgroundEditText"/>
</shape>

View file

@ -18,8 +18,9 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
android:background="@color/colorPrimaryDark"
android:elevation="4dp"
/>
<include layout="@layout/content_home" />
@ -32,7 +33,31 @@
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
android:background="@color/colorPrimaryDark"
app:menu="@menu/menu_home"
/>
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:clickable="true"
android:orientation="vertical"
android:padding="@dimen/activity_horizontal_margin">
<TextView
android:id="@+id/login"
android:text="@string/login"
android:onClick="onLoginClicked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/menuOption" />
<TextView
android:id="@+id/logout"
android:text="@string/logout"
android:onClick="onLogoutClicked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/menuOption" />
</LinearLayout>
</android.support.design.widget.NavigationView>
</android.support.v4.widget.DrawerLayout>

View file

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundLight"
tools:context="io.highfidelity.hifiinterface.LoginActivity">
<ImageView
android:id="@+id/imageView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/hifi_header"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/username"
android:layout_marginBottom="75dp"
/>
<TextView
android:id="@+id/error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:fontFamily="@font/raleway"
android:textColor="@color/colorLoginError"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@id/username"
app:layout_constraintLeft_toLeftOf="@id/username"
android:visibility="invisible"/>
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:background="@drawable/rounded_edit"
android:padding="9dp"
android:paddingRight="12dp"
android:ems="10"
android:fontFamily="@font/raleway"
android:inputType="textEmailAddress"
android:textStyle="italic"
android:textColor="@color/editTextColor"
android:textColorHint="@color/editTextColor"
android:gravity="right"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:hint="@string/username_or_email" />
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:background="@drawable/rounded_edit"
android:padding="9dp"
android:paddingRight="12dp"
android:ems="10"
android:fontFamily="@font/raleway"
android:inputType="textPassword"
android:textStyle="italic"
android:textColor="@color/editTextColor"
android:textColorHint="@color/editTextColor"
android:gravity="right"
android:layout_marginTop="14dp"
app:layout_constraintTop_toBottomOf="@id/username"
android:hint="@string/password"
android:imeOptions="actionDone"/>
<Button
android:id="@+id/loginButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/rounded_button"
android:textColor="@color/white_opaque"
android:text="@string/login"
app:layout_constraintRight_toRightOf="@id/username"
app:layout_constraintTop_toBottomOf="@id/password"
android:paddingRight="30dp"
android:paddingLeft="30dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:layout_marginTop="16dp"
android:onClick="login"/>
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:fontFamily="@font/raleway_semibold"
android:text="@string/forgot_password"
android:textStyle="italic"
android:padding="10dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="@id/loginButton"
android:textColor="@color/colorButton1"/>
</android.support.constraint.ConstraintLayout>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_activity_splash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundLight">
<!-- TODO -->
</RelativeLayout>

View file

@ -2,75 +2,75 @@
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/contentHomeRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:background="@color/colorPrimary"
tools:context="io.highfidelity.hifiinterface.HomeActivity"
tools:showIn="@layout/activity_home">
<TabHost
android:id="@+id/tabhost"
<EditText
android:id="@+id/searchView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_height="@dimen/searchEditHeight"
app:layout_constraintTop_toTopOf="@id/contentHomeRoot"
android:background="@drawable/search_bg"
android:layout_marginTop="@dimen/searchEditAdditionalMarginTop"
android:gravity="center_vertical|end"
android:paddingStart="@dimen/searchEditPaddingStart"
android:paddingEnd="@dimen/searchEditPaddingEnd"
android:fontFamily="@font/raleway"
android:hint="@string/search_hint"
android:textSize="@dimen/searchEditTextSize"
android:inputType="textUri"
android:imeOptions="actionGo"
/>
<ImageView
android:id="@+id/search_mag_icon"
android:layout_width="@dimen/searchEditMagIconWidth"
android:layout_height="@dimen/searchEditMagIconHeight"
app:layout_constraintEnd_toEndOf="@id/searchView"
app:layout_constraintTop_toTopOf="@id/searchView"
app:layout_constraintBottom_toBottomOf="@id/searchView"
android:layout_marginEnd="@dimen/searchEditMagMarginEnd"
android:src="@drawable/ic_search"
/>
<ImageView
android:id="@+id/search_clear"
android:layout_width="@dimen/searchEditClearIconWidth"
android:layout_height="@dimen/searchEditClearIconHeight"
app:layout_constraintEnd_toEndOf="@id/searchView"
app:layout_constraintTop_toTopOf="@id/searchView"
app:layout_constraintBottom_toBottomOf="@id/searchView"
android:layout_marginEnd="@dimen/searchEditClearMarginEnd"
android:visibility="gone"
android:src="@drawable/ic_clear"
android:onClick="onSearchClear"
/>
<TextView
android:id="@+id/searchNoResultsView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SearchText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/searchView"
android:layout_marginTop="32dp"
android:text="@string/search_no_results"
android:visibility="gone"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/backgroundDark"/>
<SearchView
android:id="@+id/searchView"
android:layout_width="match_parent"
android:layout_height="42dp"
android:background="@drawable/search_bg"
android:gravity="right"
android:layout_gravity="right"
/>
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/featured"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/rvDomains"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:id="@+id/popular"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
<LinearLayout
android:id="@+id/bookmarks"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
</FrameLayout>
</LinearLayout>
</TabHost>
<android.support.v7.widget.RecyclerView
android:id="@+id/rvDomains"
app:layout_constraintTop_toBottomOf="@id/searchView"
app:layout_constraintBottom_toBottomOf="@id/contentHomeRoot"
app:layout_constraintStart_toStartOf="@id/contentHomeRoot"
app:layout_constraintEnd_toEndOf="@id/contentHomeRoot"
android:layout_width="0dp"
android:layout_height="0dp" />
</android.support.constraint.ConstraintLayout>

View file

@ -1,74 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="113dp"
android:paddingLeft="14dp"
android:paddingRight="14dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:background="@drawable/domain_bg">
<ImageView
android:id="@+id/domainThumbnail"
android:layout_height="@dimen/domainItemHeight"
android:layout_marginStart="@dimen/domainMarginStart"
android:layout_marginEnd="@dimen/domainMarginEnd"
android:layout_marginTop="@dimen/domainMarginTop"
android:layout_marginBottom="@dimen/domainMarginBottom"
android:foreground="@drawable/rippleable"
android:clickable="true"
android:focusable="true"
android:elevation="0dp"
app:cardElevation="0dp"
app:cardCornerRadius="@dimen/item_corner_radius">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:srcCompat="@android:drawable/ic_menu_gallery" />
android:layout_height="match_parent">
<TextView
android:id="@+id/domainName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="14dp"
android:layout_marginLeft="10dp"
android:fontFamily="@font/raleway_semibold"
android:text=""
android:textSize="21sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
<ImageView
android:id="@+id/domainThumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:srcCompat="@android:drawable/ic_menu_gallery" />
<TextView
android:id="@+id/amountOfPeople"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="7dp"
android:layout_marginTop="8dp"
android:fontFamily="@font/raleway"
android:text="10"
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/domainName"
android:layout_width="match_parent"
android:layout_height="@dimen/domainNameHeight"
android:fontFamily="@font/raleway_semibold"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:gravity="center_vertical"
android:text=""
android:textSize="21sp"
android:textColor="@color/white_opaque"
android:background="@color/black_060"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
<ImageView
android:id="@+id/imageView2"
android:layout_width="12dp"
android:layout_height="13dp"
android:layout_marginLeft="4dp"
android:layout_marginBottom="3dp"
app:srcCompat="@drawable/ic_person"
app:layout_constraintBottom_toBottomOf="@id/amountOfPeople"
app:layout_constraintLeft_toRightOf="@id/amountOfPeople"/>
<ImageButton
android:id="@+id/imageButtonBookmark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_bookmark"
android:tint="@color/colorPrimary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:padding="1dp"
android:layout_marginRight="10dp"
android:layout_marginTop="12dp"/>
<ImageButton
android:id="@+id/imageViewShare"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="9dp"
app:layout_constraintTop_toBottomOf="@id/imageButtonBookmark"
app:layout_constraintLeft_toLeftOf="@id/imageButtonBookmark"
app:layout_constraintRight_toRightOf="@id/imageButtonBookmark"
android:background="@drawable/ic_share"
/>
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>

View file

@ -1,13 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="white_opaque">#ffffff</color>
<color name="colorPrimary">#272727</color>
<color name="colorPrimaryDark">#000000</color>
<color name="colorPrimary">@color/backgroundLight</color>
<color name="colorPrimaryDark">@color/backgroundDark</color>
<color name="colorAccent">#54D7FD</color>
<color name="backgroundEditText">#E3E3E3</color>
<color name="editTextColor">#575757</color>
<color name="tabs">#1EB5EC</color>
<color name="colorButton1">#00B4EF</color>
<color name="backgroundDark">#333333</color>
<color name="backgroundLight">#4F4F4F</color>
<color name="backgroundSearch">#33999999</color>
<color name="editText">#212121</color>
<color name="editTextHint">#9e9e9e</color>
<color name="menuOption">#F2F2F2</color>
<color name="colorLoginError">#FF7171</color>
<color name="black_060">#99000000</color>
<color name="statusbar_color">#292929</color>
</resources>

View file

@ -11,4 +11,26 @@
<dimen name="text_size_subtitle_material_toolbar">12dp</dimen>
<dimen name="button_horizontal_margin">12dp</dimen>
<dimen name="edit_text_padding">8dp</dimen>
<dimen name="item_corner_radius">4dp</dimen>
<!-- Search (domains) screen dimensions -->
<dimen name="searchEditHeight">47.5dp</dimen>
<dimen name="searchEditAdditionalMarginTop">11dp</dimen>
<dimen name="searchEditPaddingStart">24dp</dimen>
<dimen name="searchEditPaddingEnd">51dp</dimen>
<dimen name="searchEditTextSize">19sp</dimen>
<dimen name="searchEditMagIconWidth">16dp</dimen>
<dimen name="searchEditMagIconHeight">16dp</dimen>
<dimen name="searchEditMagMarginEnd">22dp</dimen>
<dimen name="searchEditClearIconWidth">16dp</dimen>
<dimen name="searchEditClearIconHeight">16dp</dimen>
<dimen name="searchEditClearMarginEnd">22dp</dimen>
<dimen name="domainItemHeight">163dp</dimen>
<dimen name="domainMarginStart">14dp</dimen>
<dimen name="domainMarginEnd">14dp</dimen>
<dimen name="domainMarginTop">2dp</dimen>
<dimen name="domainMarginBottom">6dp</dimen>
<dimen name="domainNameHeight">64dp</dimen>
</resources>

View file

@ -13,5 +13,14 @@
<string name="action_goto">Go To</string>
<string name="goto_url_hint">Type a domain url</string>
<string name="go">Go</string>
<string name="username_or_email">Username or email</string>
<string name="password">Password</string>
<string name="login">Login</string>
<string name="logout">Logout</string>
<string name="forgot_password">Forgot password?</string>
<string name="login_username_or_password_incorrect">Username or password incorrect.</string>
<string name="logging_in">Logging into High Fidelity</string>
<string name="search_hint"><i>Search for a place by name</i>\u00A0</string>
<string name="search_loading">Loading places…</string>
<string name="search_no_results">No places exist with that name</string>
</resources>

View file

@ -7,7 +7,14 @@
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="Theme.AppCompat.Translucent.NoActionBar" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">@android:style/Animation</item>
<item name="android:windowActionBar">false</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
@ -35,7 +42,7 @@
<style name="SearchText">
<item name="android:fontFamily">@font/raleway_light_italic</item>
<item name="android:textSize">15sp</item>
<item name="android:textSize">19sp</item>
</style>
<!-- Overriding text size so it's not so big in portrait -->

View file

@ -7,6 +7,9 @@
//
// Sends messages over the EventBridge when text input is required.
//
/* global document, window, console, setTimeout, setInterval, EventBridge */
(function () {
var POLL_FREQUENCY = 500; // ms
var MAX_WARNINGS = 3;
@ -37,22 +40,24 @@
}
return false;
}
};
}
function shouldSetNumeric() {
return document.activeElement.type === "number";
};
}
function scheduleBringToView(timeout) {
var timer = setTimeout(function () {
clearTimeout(timer);
setTimeout(function () {
// If the element is not visible because the keyboard has been raised over the top of it, scroll it up into view.
// If the element is not visible because the keyboard raising has moved it off screen, scroll it down into view.
var elementRect = document.activeElement.getBoundingClientRect();
var absoluteElementTop = elementRect.top + window.scrollY;
var middle = absoluteElementTop - (window.innerHeight / 2);
window.scrollTo(0, middle);
var VISUAL_MARGIN = 3;
var delta = elementRect.y + elementRect.height + VISUAL_MARGIN - window.innerHeight;
if (delta > 0) {
window.scrollBy(0, delta);
} else if (elementRect.y < VISUAL_MARGIN) {
window.scrollBy(0, elementRect.y - VISUAL_MARGIN);
}
}, timeout);
}
@ -62,11 +67,13 @@
var passwordField = shouldSetPasswordField();
if (isWindowFocused &&
(keyboardRaised !== window.isKeyboardRaised || numericKeyboard !== window.isNumericKeyboard || passwordField !== window.isPasswordField)) {
(keyboardRaised !== window.isKeyboardRaised || numericKeyboard !== window.isNumericKeyboard
|| passwordField !== window.isPasswordField)) {
if (typeof EventBridge !== "undefined" && EventBridge !== null) {
EventBridge.emitWebEvent(
keyboardRaised ? ("_RAISE_KEYBOARD" + (numericKeyboard ? "_NUMERIC" : "") + (passwordField ? "_PASSWORD" : "")) : "_LOWER_KEYBOARD"
keyboardRaised ? ("_RAISE_KEYBOARD" + (numericKeyboard ? "_NUMERIC" : "")
+ (passwordField ? "_PASSWORD" : "")) : "_LOWER_KEYBOARD"
);
} else {
if (numWarnings < MAX_WARNINGS) {
@ -77,7 +84,7 @@
if (!window.isKeyboardRaised) {
scheduleBringToView(250); // Allow time for keyboard to be raised in QML.
// 2DO: should it be rather done from 'client area height changed' event?
// 2DO: should it be rather done from 'client area height changed' event?
}
window.isKeyboardRaised = keyboardRaised;

View file

@ -0,0 +1,57 @@
//
// RadioButtonsPreference.qml
//
// Created by Cain Kilgore on 20th July 2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import "../../controls-uit"
Preference {
id: root
height: control.height + hifi.dimensions.controlInterlineHeight
Component.onCompleted: {
repeater.itemAt(preference.value).checked = true
}
function save() {
var value = 0;
for (var i = 0; i < repeater.count; i++) {
if (repeater.itemAt(i).checked) {
value = i;
break;
}
}
preference.value = value;
preference.save();
}
Row {
id: control
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
spacing: 5
Repeater {
id: repeater
model: preference.items.length
delegate: RadioButton {
text: preference.items[index]
anchors {
verticalCenter: parent.verticalCenter
}
colorScheme: hifi.colorSchemes.dark
}
}
}
}

View file

@ -74,6 +74,7 @@ Preference {
property var comboBoxBuilder: Component { ComboBoxPreference { } }
property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } }
property var primaryHandBuilder: Component { PrimaryHandPreference { } }
property var radioButtonsBuilder: Component { RadioButtonsPreference { } }
property var preferences: []
property int checkBoxCount: 0
@ -140,6 +141,10 @@ Preference {
checkBoxCount++;
builder = primaryHandBuilder;
break;
case Preference.RadioButtons:
checkBoxCount++;
builder = radioButtonsBuilder;
break;
};
if (builder) {

View file

@ -49,6 +49,7 @@ Item {
property string upgradeTitle;
property bool updateAvailable: root.upgradeUrl !== "" && !root.isShowingMyItems;
property bool isShowingMyItems;
property bool valid;
property string originalStatusText;
property string originalStatusColor;
@ -239,6 +240,7 @@ Item {
width: 62;
onLoaded: {
item.enabled = root.valid;
item.buttonGlyphText = hifi.glyphs.gift;
item.buttonText = "Gift";
item.buttonClicked = function() {
@ -463,7 +465,7 @@ Item {
Item {
id: statusContainer;
visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.numberSold > -1;
visible: root.purchaseStatus === "pending" || !root.valid || root.numberSold > -1;
anchors.left: itemName.left;
anchors.right: itemName.right;
anchors.top: itemName.bottom;
@ -480,7 +482,7 @@ Item {
text: {
if (root.purchaseStatus === "pending") {
"PENDING..."
} else if (root.purchaseStatus === "invalidated") {
} else if (!root.valid) {
"INVALIDATED"
} else if (root.numberSold > -1) {
("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "\u221e" : root.limitedRun))
@ -492,7 +494,7 @@ Item {
color: {
if (root.purchaseStatus === "pending") {
hifi.colors.blueAccent
} else if (root.purchaseStatus === "invalidated") {
} else if (!root.valid) {
hifi.colors.redAccent
} else {
hifi.colors.baseGray
@ -506,7 +508,7 @@ Item {
text: {
if (root.purchaseStatus === "pending") {
hifi.glyphs.question
} else if (root.purchaseStatus === "invalidated") {
} else if (!root.valid) {
hifi.glyphs.question
} else {
""
@ -523,7 +525,7 @@ Item {
color: {
if (root.purchaseStatus === "pending") {
hifi.colors.blueAccent
} else if (root.purchaseStatus === "invalidated") {
} else if (!root.valid) {
hifi.colors.redAccent
} else {
hifi.colors.baseGray
@ -538,7 +540,7 @@ Item {
onClicked: {
if (root.purchaseStatus === "pending") {
sendToPurchases({method: 'showPendingLightbox'});
} else if (root.purchaseStatus === "invalidated") {
} else if (!root.valid) {
sendToPurchases({method: 'showInvalidatedLightbox'});
}
}
@ -546,7 +548,7 @@ Item {
if (root.purchaseStatus === "pending") {
statusText.color = hifi.colors.blueHighlight;
statusIcon.color = hifi.colors.blueHighlight;
} else if (root.purchaseStatus === "invalidated") {
} else if (!root.valid) {
statusText.color = hifi.colors.redAccent;
statusIcon.color = hifi.colors.redAccent;
}
@ -555,7 +557,7 @@ Item {
if (root.purchaseStatus === "pending") {
statusText.color = hifi.colors.blueAccent;
statusIcon.color = hifi.colors.blueAccent;
} else if (root.purchaseStatus === "invalidated") {
} else if (!root.valid) {
statusText.color = hifi.colors.redHighlight;
statusIcon.color = hifi.colors.redHighlight;
}
@ -645,8 +647,8 @@ Item {
width: 160;
height: 40;
enabled: root.hasPermissionToRezThis &&
root.purchaseStatus !== "invalidated" &&
MyAvatar.skeletonModelURL !== root.itemHref;
MyAvatar.skeletonModelURL !== root.itemHref &&
root.valid;
onHoveredChanged: {
if (hovered) {

View file

@ -616,6 +616,7 @@ Rectangle {
upgradeTitle: model.upgrade_title;
itemType: model.itemType;
isShowingMyItems: root.isShowingMyItems;
valid: model.valid;
anchors.topMargin: 10;
anchors.bottomMargin: 10;
@ -995,10 +996,6 @@ Rectangle {
for (var i = 0; i < purchasesModel.count; i++) {
if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) {
if (!purchasesModel.get(i).valid) {
continue;
}
if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) {
tempPurchasesModel.insert(0, purchasesModel.get(i));
} else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") ||
@ -1055,10 +1052,6 @@ Rectangle {
var currentId;
for (var i = 0; i < tempPurchasesModel.count; i++) {
currentId = tempPurchasesModel.get(i).id;
if (!purchasesModel.get(i).valid) {
continue;
}
filteredPurchasesModel.append(tempPurchasesModel.get(i));
filteredPurchasesModel.setProperty(i, 'cardBackVisible', false);
filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1));

View file

@ -220,7 +220,7 @@ Item {
source: InputConfiguration.configurationLayout(box.currentText);
onLoaded: {
if (loader.item.hasOwnProperty("pluginName")) {
if (box.currentText === "Vive") {
if (box.currentText === "HTC Vive") {
loader.item.pluginName = "OpenVR";
} else {
loader.item.pluginName = box.currentText;

View file

@ -82,6 +82,7 @@ Preference {
property var comboBoxBuilder: Component { ComboBoxPreference { } }
property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } }
property var primaryHandBuilder: Component { PrimaryHandPreference { } }
property var radioButtonsBuilder: Component { RadioButtonsPreference { } }
property var preferences: []
property int checkBoxCount: 0
@ -154,6 +155,10 @@ Preference {
checkBoxCount++;
builder = primaryHandBuilder;
break;
case Preference.RadioButtons:
checkBoxCount++;
builder = radioButtonsBuilder;
break;
};
if (builder) {

View file

@ -10,11 +10,42 @@
//
#include "AndroidHelper.h"
#include <QDebug>
#include <AccountManager.h>
AndroidHelper::AndroidHelper() {
}
AndroidHelper::~AndroidHelper() {
workerThread.quit();
workerThread.wait();
}
void AndroidHelper::init() {
workerThread.start();
_accountManager = QSharedPointer<AccountManager>(new AccountManager, &QObject::deleteLater);
_accountManager->setIsAgent(true);
_accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL());
_accountManager->setSessionID(DependencyManager::get<AccountManager>()->getSessionID());
connect(_accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) {
DependencyManager::get<AccountManager>()->setAccountInfo(AndroidHelper::instance().getAccountManager()->getAccountInfo());
DependencyManager::get<AccountManager>()->setAuthURL(authURL);
});
connect(_accountManager.data(), &AccountManager::logoutComplete, [] () {
DependencyManager::get<AccountManager>()->logout();
});
_accountManager->moveToThread(&workerThread);
}
void AndroidHelper::requestActivity(const QString &activityName) {
emit androidActivityRequested(activityName);
}
void AndroidHelper::notifyLoadComplete() {
emit qtAppLoadComplete();
}
void AndroidHelper::goBackFromAndroidActivity() {
emit backFromAndroidActivity();
}
}

View file

@ -13,6 +13,8 @@
#define hifi_Android_Helper_h
#include <QObject>
#include <QThread>
#include <AccountManager.h>
class AndroidHelper : public QObject {
Q_OBJECT
@ -21,17 +23,24 @@ public:
static AndroidHelper instance;
return instance;
}
void init();
void requestActivity(const QString &activityName);
void notifyLoadComplete();
void goBackFromAndroidActivity();
QSharedPointer<AccountManager> getAccountManager() { return _accountManager; }
AndroidHelper(AndroidHelper const&) = delete;
void operator=(AndroidHelper const&) = delete;
signals:
void androidActivityRequested(const QString &activityName);
void backFromAndroidActivity();
void qtAppLoadComplete();
private:
AndroidHelper() {}
AndroidHelper();
~AndroidHelper();
QSharedPointer<AccountManager> _accountManager;
QThread workerThread;
};
#endif

View file

@ -737,9 +737,9 @@ extern InputPluginList getInputPlugins();
extern void saveInputPluginSettings(const InputPluginList& plugins);
// Parameters used for running tests from teh command line
const QString TEST_SCRIPT_COMMAND { "--testScript" };
const QString TEST_QUIT_WHEN_FINISHED_OPTION { "quitWhenFinished" };
const QString TEST_SNAPSHOT_LOCATION_COMMAND { "--testSnapshotLocation" };
const QString TEST_SCRIPT_COMMAND{ "--testScript" };
const QString TEST_QUIT_WHEN_FINISHED_OPTION{ "quitWhenFinished" };
const QString TEST_RESULTS_LOCATION_COMMAND{ "--testResultsLocation" };
bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
const char** constArgv = const_cast<const char**>(argv);
@ -853,7 +853,11 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<Cursor::Manager>();
DependencyManager::set<VirtualPad::Manager>();
DependencyManager::set<DesktopPreviewProvider>();
#if defined(Q_OS_ANDROID)
DependencyManager::set<AccountManager>(); // use the default user agent getter
#else
DependencyManager::set<AccountManager>(std::bind(&Application::getUserAgent, qApp));
#endif
DependencyManager::set<StatTracker>();
DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT);
DependencyManager::set<ScriptInitializerMixin, NativeScriptInitializers>();
@ -1017,22 +1021,25 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// If the URL scheme is http(s) or ftp, then use as is, else - treat it as a local file
// This is done so as not break previous command line scripts
if (testScriptPath.left(URL_SCHEME_HTTP.length()) == URL_SCHEME_HTTP || testScriptPath.left(URL_SCHEME_FTP.length()) == URL_SCHEME_FTP) {
if (testScriptPath.left(URL_SCHEME_HTTP.length()) == URL_SCHEME_HTTP ||
testScriptPath.left(URL_SCHEME_FTP.length()) == URL_SCHEME_FTP) {
setProperty(hifi::properties::TEST, QUrl::fromUserInput(testScriptPath));
} else if (QFileInfo(testScriptPath).exists()) {
setProperty(hifi::properties::TEST, QUrl::fromLocalFile(testScriptPath));
}
// quite when finished parameter must directly follow the test script
// quite when finished parameter must directly follow the test script
if ((i + 2) < args.size() && args.at(i + 2) == TEST_QUIT_WHEN_FINISHED_OPTION) {
quitWhenFinished = true;
}
} else if (args.at(i) == TEST_SNAPSHOT_LOCATION_COMMAND) {
} else if (args.at(i) == TEST_RESULTS_LOCATION_COMMAND) {
// Set test snapshot location only if it is a writeable directory
QString pathname(args.at(i + 1));
QFileInfo fileInfo(pathname);
QString path(args.at(i + 1));
QFileInfo fileInfo(path);
if (fileInfo.isDir() && fileInfo.isWritable()) {
testSnapshotLocation = pathname;
TestScriptingInterface::getInstance()->setTestResultsLocation(path);
}
}
}
@ -2251,6 +2258,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_pendingRenderEvent = false;
qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID());
#if defined(Q_OS_ANDROID)
AndroidHelper::instance().init();
AndroidHelper::instance().notifyLoadComplete();
#endif
}
void Application::updateVerboseLogging() {
@ -7574,7 +7586,9 @@ void Application::loadAvatarBrowser() const {
void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) {
postLambdaEvent([notify, includeAnimated, aspectRatio, filename, this] {
// Get a screenshot and save it
QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio), filename, testSnapshotLocation);
QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio), filename,
TestScriptingInterface::getInstance()->getTestResultsLocation());
// If we're not doing an animated snapshot as well...
if (!includeAnimated) {
// Tell the dependency manager that the capture of the still snapshot has taken place.
@ -7588,7 +7602,9 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa
void Application::takeSecondaryCameraSnapshot(const QString& filename) {
postLambdaEvent([filename, this] {
QString snapshotPath = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename, testSnapshotLocation);
QString snapshotPath = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename,
TestScriptingInterface::getInstance()->getTestResultsLocation());
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, true);
});
}
@ -8240,17 +8256,17 @@ void Application::openAndroidActivity(const QString& activityName) {
void Application::enterBackground() {
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
"stop", Qt::BlockingQueuedConnection);
//GC: commenting it out until we fix it
//getActiveDisplayPlugin()->deactivate();
if (getActiveDisplayPlugin()->isActive()) {
getActiveDisplayPlugin()->deactivate();
}
}
void Application::enterForeground() {
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
"start", Qt::BlockingQueuedConnection);
//GC: commenting it out until we fix it
/*if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) {
if (!getActiveDisplayPlugin() || getActiveDisplayPlugin()->isActive() || !getActiveDisplayPlugin()->activate()) {
qWarning() << "Could not re-activate display plugin";
}*/
}
}
#endif

View file

@ -417,7 +417,6 @@ public slots:
void updateVerboseLogging();
Q_INVOKABLE void openAndroidActivity(const QString& activityName);
private slots:
void onDesktopRootItemCreated(QQuickItem* qmlContext);
void onDesktopRootContextCreated(QQmlContext* qmlContext);
@ -751,7 +750,6 @@ private:
std::atomic<bool> _pendingIdleEvent { true };
std::atomic<bool> _pendingRenderEvent { true };
QString testSnapshotLocation;
bool quitWhenFinished { false };
};
#endif // hifi_Application_h

View file

@ -70,7 +70,7 @@ void LODManager::autoAdjustLOD(float realTimeDelta) {
// Note: we MUST clamp the blend to 1.0 for stability
float blend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f;
_avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * maxRenderTime; // msec
if (!_automaticLODAdjust) {
if (!_automaticLODAdjust || _avgRenderTime == 0.0f) {
// early exit
return;
}

View file

@ -192,7 +192,6 @@ namespace MenuOption {
const QString ShowOtherLookAtVectors = "Show Other Eye Vectors";
const QString EnableLookAtSnapping = "Enable LookAt Snapping";
const QString ShowRealtimeEntityStats = "Show Realtime Entity Stats";
const QString StandingHMDSensorMode = "Standing HMD Sensor Mode";
const QString SimulateEyeTracking = "Simulate";
const QString SMIEyeTracking = "SMI Eye Tracking";
const QString SparseTextureManagement = "Enable Sparse Texture Management";

View file

@ -160,3 +160,29 @@ void TestScriptingInterface::clearCaches() {
qApp->reloadResourceCaches();
}
// Writes a JSON object from javascript to a file
void TestScriptingInterface::saveObject(QVariant variant, const QString& filename) {
if (_testResultsLocation.isNull()) {
return;
}
QJsonDocument jsonDocument;
jsonDocument = QJsonDocument::fromVariant(variant);
if (jsonDocument.isNull()) {
return;
}
QByteArray jsonData = jsonDocument.toJson();
// Append trailing slash if needed
if (_testResultsLocation.right(1) != "/") {
_testResultsLocation += "/";
}
QString filepath = QDir::cleanPath(_testResultsLocation + filename);
QFile file(filepath);
file.open(QFile::WriteOnly);
file.write(jsonData);
file.close();
}

View file

@ -18,6 +18,10 @@ class QScriptValue;
class TestScriptingInterface : public QObject {
Q_OBJECT
public:
void setTestResultsLocation(const QString path) { _testResultsLocation = path; }
const QString& getTestResultsLocation() { return _testResultsLocation; };
public slots:
static TestScriptingInterface* getInstance();
@ -46,7 +50,6 @@ public slots:
*/
void waitIdle();
bool waitForConnection(qint64 maxWaitMs = 10000);
void wait(int milliseconds);
@ -83,8 +86,14 @@ public slots:
*/
void clearCaches();
/**jsdoc
* Save a JSON object to a file in the test results location
*/
void saveObject(QVariant v, const QString& filename);
private:
bool waitForCondition(qint64 maxWaitMs, std::function<bool()> condition);
QString _testResultsLocation;
};
#endif // hifi_TestScriptingInterface_h
#endif // hifi_TestScriptingInterface_h

View file

@ -250,18 +250,6 @@ void setupPreferences() {
}
static const QString MOVEMENT{ "VR Movement" };
{
auto getter = [=]()->bool { return myAvatar->getFlyingEnabled(); };
auto setter = [=](bool value) { myAvatar->setFlyingEnabled(value); };
preferences->addPreference(new CheckPreference(MOVEMENT, "Flying & jumping", getter, setter));
}
{
auto getter = [=]()->bool { return myAvatar->getSnapTurn(); };
auto setter = [=](bool value) { myAvatar->setSnapTurn(value); };
preferences->addPreference(new CheckPreference(MOVEMENT, "Snap turn / Smooth turn", getter, setter));
}
//TODO: Update with advanced movement logic, test that it works
{
static const QString movementsControlChannel = QStringLiteral("Hifi-Advanced-Movement-Disabler");
@ -271,7 +259,20 @@ void setupPreferences() {
QStringLiteral("Advanced movement for hand controllers"),
getter, setter));
}
{
auto getter = [=]()->bool { return myAvatar->getFlyingEnabled(); };
auto setter = [=](bool value) { myAvatar->setFlyingEnabled(value); };
preferences->addPreference(new CheckPreference(MOVEMENT, "Flying & jumping", getter, setter));
}
{
auto getter = [=]()->int { return myAvatar->getSnapTurn() ? 0 : 1; };
auto setter = [=](int value) { myAvatar->setSnapTurn(value == 0); };
auto preference = new RadioButtonsPreference(MOVEMENT, "Snap turn / Smooth turn", getter, setter);
QStringList items;
items << "Snap turn" << "Smooth turn";
preference->setItems(items);
preferences->addPreference(preference);
}
{
auto getter = [=]()->float { return myAvatar->getUserHeight(); };
auto setter = [=](float value) { myAvatar->setUserHeight(value); };

View file

@ -60,7 +60,7 @@ public:
bool intersects { false };
OverlayID overlayID { UNKNOWN_OVERLAY_ID };
float distance { 0 };
BoxFace face;
BoxFace face { UNKNOWN_FACE };
glm::vec3 surfaceNormal;
glm::vec3 intersection;
QVariantMap extraInfo;

View file

@ -28,69 +28,76 @@ void Basic2DWindowOpenGLDisplayPlugin::customizeContext() {
qreal dpi = getFullscreenTarget()->physicalDotsPerInch();
_virtualPadPixelSize = dpi * VirtualPad::Manager::BASE_DIAMETER_PIXELS / VirtualPad::Manager::DPI;
auto iconPath = PathUtils::resourcesPath() + "images/analog_stick.png";
auto image = QImage(iconPath);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
if ((image.width() > 0) && (image.height() > 0)) {
image = image.scaled(_virtualPadPixelSize, _virtualPadPixelSize, Qt::KeepAspectRatio);
if (!_virtualPadStickTexture) {
auto iconPath = PathUtils::resourcesPath() + "images/analog_stick.png";
auto image = QImage(iconPath);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
if ((image.width() > 0) && (image.height() > 0)) {
image = image.scaled(_virtualPadPixelSize, _virtualPadPixelSize, Qt::KeepAspectRatio);
_virtualPadStickTexture = gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
_virtualPadStickTexture->setSource("virtualPad stick");
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
_virtualPadStickTexture->setUsage(usage.build());
_virtualPadStickTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
_virtualPadStickTexture->assignStoredMip(0, image.byteCount(), image.constBits());
_virtualPadStickTexture->setAutoGenerateMips(true);
_virtualPadStickTexture = gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
_virtualPadStickTexture->setSource("virtualPad stick");
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
_virtualPadStickTexture->setUsage(usage.build());
_virtualPadStickTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
_virtualPadStickTexture->assignStoredMip(0, image.byteCount(), image.constBits());
_virtualPadStickTexture->setAutoGenerateMips(true);
}
}
iconPath = PathUtils::resourcesPath() + "images/analog_stick_base.png";
image = QImage(iconPath);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
if ((image.width() > 0) && (image.height() > 0)) {
image = image.scaled(_virtualPadPixelSize, _virtualPadPixelSize, Qt::KeepAspectRatio);
if (!_virtualPadStickBaseTexture) {
auto iconPath = PathUtils::resourcesPath() + "images/analog_stick_base.png";
auto image = QImage(iconPath);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
if ((image.width() > 0) && (image.height() > 0)) {
image = image.scaled(_virtualPadPixelSize, _virtualPadPixelSize, Qt::KeepAspectRatio);
_virtualPadStickBaseTexture = gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
_virtualPadStickBaseTexture->setSource("virtualPad base");
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
_virtualPadStickBaseTexture->setUsage(usage.build());
_virtualPadStickBaseTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
_virtualPadStickBaseTexture->assignStoredMip(0, image.byteCount(), image.constBits());
_virtualPadStickBaseTexture->setAutoGenerateMips(true);
_virtualPadStickBaseTexture = gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
_virtualPadStickBaseTexture->setSource("virtualPad base");
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
_virtualPadStickBaseTexture->setUsage(usage.build());
_virtualPadStickBaseTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
_virtualPadStickBaseTexture->assignStoredMip(0, image.byteCount(), image.constBits());
_virtualPadStickBaseTexture->setAutoGenerateMips(true);
}
}
_virtualPadJumpBtnPixelSize = dpi * VirtualPad::Manager::JUMP_BTN_FULL_PIXELS / VirtualPad::Manager::DPI;
iconPath = PathUtils::resourcesPath() + "images/fly.png";
image = QImage(iconPath);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
if ((image.width() > 0) && (image.height() > 0)) {
image = image.scaled(_virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize, Qt::KeepAspectRatio);
image = image.mirrored();
if (!_virtualPadJumpBtnTexture) {
auto iconPath = PathUtils::resourcesPath() + "images/fly.png";
auto image = QImage(iconPath);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
if ((image.width() > 0) && (image.height() > 0)) {
image = image.scaled(_virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize, Qt::KeepAspectRatio);
image = image.mirrored();
_virtualPadJumpBtnTexture = gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
_virtualPadJumpBtnTexture->setSource("virtualPad jump");
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
_virtualPadJumpBtnTexture->setUsage(usage.build());
_virtualPadJumpBtnTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
_virtualPadJumpBtnTexture->assignStoredMip(0, image.byteCount(), image.constBits());
_virtualPadJumpBtnTexture->setAutoGenerateMips(true);
_virtualPadJumpBtnTexture = gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
_virtualPadJumpBtnTexture->setSource("virtualPad jump");
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
_virtualPadJumpBtnTexture->setUsage(usage.build());
_virtualPadJumpBtnTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
_virtualPadJumpBtnTexture->assignStoredMip(0, image.byteCount(), image.constBits());
_virtualPadJumpBtnTexture->setAutoGenerateMips(true);
}
}
#endif
Parent::customizeContext();
@ -124,44 +131,32 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() {
// render stick base
auto stickBaseTransform = DependencyManager::get<CompositorHelper>()->getPoint2DTransform(virtualPadManager.getLeftVirtualPad()->getFirstTouch(),
_virtualPadPixelSize, _virtualPadPixelSize);
render([&](gpu::Batch& batch) {
batch.enableStereo(false);
batch.setProjectionTransform(mat4());
batch.setPipeline(_cursorPipeline);
batch.setResourceTexture(0, _virtualPadStickBaseTexture);
batch.resetViewTransform();
batch.setModelTransform(stickBaseTransform);
batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize()));
batch.draw(gpu::TRIANGLE_STRIP, 4);
});
// render stick head
auto stickTransform = DependencyManager::get<CompositorHelper>()->getPoint2DTransform(virtualPadManager.getLeftVirtualPad()->getCurrentTouch(),
_virtualPadPixelSize, _virtualPadPixelSize);
_virtualPadPixelSize, _virtualPadPixelSize);
auto jumpTransform = DependencyManager::get<CompositorHelper>()->getPoint2DTransform(virtualPadManager.getJumpButtonPosition(),
_virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize);
render([&](gpu::Batch& batch) {
batch.enableStereo(false);
batch.setFramebuffer(_compositeFramebuffer);
batch.resetViewTransform();
batch.setProjectionTransform(mat4());
batch.setPipeline(_cursorPipeline);
batch.setResourceTexture(0, _virtualPadStickTexture);
batch.resetViewTransform();
batch.setModelTransform(stickTransform);
batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize()));
batch.setResourceTexture(0, _virtualPadStickBaseTexture);
batch.setModelTransform(stickBaseTransform);
batch.draw(gpu::TRIANGLE_STRIP, 4);
});
if (!virtualPadManager.getLeftVirtualPad()->isBeingTouched()) {
// render stick head
auto jumpTransform = DependencyManager::get<CompositorHelper>()->getPoint2DTransform(virtualPadManager.getJumpButtonPosition(),
_virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize);
render([&](gpu::Batch& batch) {
batch.enableStereo(false);
batch.setProjectionTransform(mat4());
batch.setPipeline(_cursorPipeline);
batch.setResourceTexture(0, _virtualPadStickTexture);
batch.setModelTransform(stickTransform);
batch.draw(gpu::TRIANGLE_STRIP, 4);
if (!virtualPadManager.getLeftVirtualPad()->isBeingTouched()) {
batch.setResourceTexture(0, _virtualPadJumpBtnTexture);
batch.resetViewTransform();
batch.setModelTransform(jumpTransform);
batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize()));
batch.draw(gpu::TRIANGLE_STRIP, 4);
});
}
}
});
}
#endif
Parent::compositeExtra();

View file

@ -251,7 +251,7 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen
updateAmbientLightFromEntity(entity);
}
if (skyboxChanged) {
if (skyboxChanged || _proceduralUserData != entity->getUserData()) {
updateKeyBackgroundFromEntity(entity);
}
@ -295,6 +295,10 @@ bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint
return true;
}
if (entity->getUserData() != _proceduralUserData) {
return true;
}
#if 0
if (_typedEntity->getCompoundShapeURL() != _lastShapeURL) {
return true;

View file

@ -35,8 +35,11 @@ void main(void) {
#ifdef PROCEDURAL
vec3 color = getSkyboxColor();
// Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline
color = pow(color, vec3(2.2));
// Protect from NaNs and negative values
color = mix(color, vec3(0), isnan(color));
color = max(color, vec3(0));
// Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline
color = pow(color, vec3(2.2));
_fragColor = vec4(color, 0.0);
// FIXME: scribe does not yet scrub out else statements

View file

@ -453,6 +453,20 @@ void AccountManager::removeAccountFromFile() {
<< "from settings file.";
}
void AccountManager::setAccountInfo(const DataServerAccountInfo &newAccountInfo) {
_accountInfo = newAccountInfo;
_pendingPrivateKey.clear();
if (_isAgent && !_accountInfo.getAccessToken().token.isEmpty() && !_accountInfo.hasProfile()) {
// we are missing profile information, request it now
requestProfile();
}
// prepare to refresh our token if it is about to expire
if (needsToRefreshToken()) {
refreshAccessToken();
}
}
bool AccountManager::hasValidAccessToken() {
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {

View file

@ -88,6 +88,7 @@ public:
void requestProfile();
DataServerAccountInfo& getAccountInfo() { return _accountInfo; }
void setAccountInfo(const DataServerAccountInfo &newAccountInfo);
static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply);

View file

@ -731,7 +731,9 @@ void EntityMotionState::measureBodyAcceleration() {
// hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt
glm::vec3 velocity = getBodyLinearVelocityGTSigma();
_measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt;
const float MIN_DAMPING_FACTOR = 0.01f;
float invDampingAttenuationFactor = 1.0f / glm::max(powf(1.0f - _body->getLinearDamping(), dt), MIN_DAMPING_FACTOR);
_measuredAcceleration = (velocity * invDampingAttenuationFactor - _lastVelocity) * invDt;
_lastVelocity = velocity;
if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) {
// we fall in here when _lastMeasureStep is old: the body has just become active

View file

@ -272,18 +272,24 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm
// Leave this here for debugging
// qCDebug(procedural) << "FragmentShader:\n" << fragmentShaderSource.c_str();
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("iChannel0"), 0));
slotBindings.insert(gpu::Shader::Binding(std::string("iChannel1"), 1));
slotBindings.insert(gpu::Shader::Binding(std::string("iChannel2"), 2));
slotBindings.insert(gpu::Shader::Binding(std::string("iChannel3"), 3));
_opaqueFragmentShader = gpu::Shader::createPixel(opaqueShaderSource);
_opaqueShader = gpu::Shader::createProgram(_vertexShader, _opaqueFragmentShader);
_transparentFragmentShader = gpu::Shader::createPixel(transparentShaderSource);
_transparentShader = gpu::Shader::createProgram(_vertexShader, _transparentFragmentShader);
gpu::Shader::makeProgram(*_opaqueShader, slotBindings);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("iChannel0"), 0));
slotBindings.insert(gpu::Shader::Binding(std::string("iChannel1"), 1));
slotBindings.insert(gpu::Shader::Binding(std::string("iChannel2"), 2));
slotBindings.insert(gpu::Shader::Binding(std::string("iChannel3"), 3));
gpu::Shader::makeProgram(*_opaqueShader, slotBindings);
gpu::Shader::makeProgram(*_transparentShader, slotBindings);
if (!transparentShaderSource.empty() && transparentShaderSource != opaqueShaderSource) {
_transparentFragmentShader = gpu::Shader::createPixel(transparentShaderSource);
_transparentShader = gpu::Shader::createProgram(_vertexShader, _transparentFragmentShader);
gpu::Shader::makeProgram(*_transparentShader, slotBindings);
} else {
_transparentFragmentShader = _opaqueFragmentShader;
_transparentShader = _opaqueShader;
}
_opaquePipeline = gpu::Pipeline::create(_opaqueShader, _opaqueState);
_transparentPipeline = gpu::Pipeline::create(_transparentShader, _transparentState);

View file

@ -21,6 +21,8 @@
#include <render/ShapePipeline.h>
#include <render/FilterTask.h>
#include "StencilMaskPass.h"
#include "ZoneRenderer.h"
#include "FadeEffect.h"
@ -53,8 +55,9 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend
const auto& transparents = items.get0()[RenderFetchCullSortTask::TRANSPARENT_SHAPE];
// const auto& lights = items.get0()[RenderFetchCullSortTask::LIGHT];
const auto& metas = items.get0()[RenderFetchCullSortTask::META];
// const auto& overlayOpaques = items.get0()[RenderFetchCullSortTask::OVERLAY_OPAQUE_SHAPE];
// const auto& overlayTransparents = items.get0()[RenderFetchCullSortTask::OVERLAY_TRANSPARENT_SHAPE];
const auto& overlayOpaques = items.get0()[RenderFetchCullSortTask::OVERLAY_OPAQUE_SHAPE];
const auto& overlayTransparents = items.get0()[RenderFetchCullSortTask::OVERLAY_TRANSPARENT_SHAPE];
//const auto& background = items.get0()[RenderFetchCullSortTask::BACKGROUND];
// const auto& spatialSelection = items[1];
@ -75,6 +78,17 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend
// draw a stencil mask in hidden regions of the framebuffer.
task.addJob<PrepareStencil>("PrepareStencil", framebuffer);
// Layered Overlays
const auto filteredOverlaysOpaque = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT);
const auto filteredOverlaysTransparent = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT);
const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN<FilterLayeredItems::Outputs>(0);
const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN<FilterLayeredItems::Outputs>(0);
const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel, nullptr).asVarying();
const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel, nullptr).asVarying();
task.addJob<DrawOverlay3D>("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true);
task.addJob<DrawOverlay3D>("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false);
// Draw opaques forward
const auto opaqueInputs = DrawForward::Inputs(opaques, lightingModel).asVarying();
task.addJob<DrawForward>("DrawOpaques", opaqueInputs, shapePlumber);

View file

@ -149,9 +149,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con
batch.setStateScissorRect(viewport);
batch.setFramebuffer(fbo);
batch.clearFramebuffer(
gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_DEPTH,
vec4(vec3(1.0, 1.0, 1.0), 0.0), 1.0, 0, true);
batch.clearDepthFramebuffer(1.0, false);
glm::mat4 projMat;
Transform viewMat;
@ -232,12 +230,11 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende
const auto queryResolution = setupOutput.getN<RenderShadowSetup::Outputs>(2);
// Fetch and cull the items from the scene
// Enable models to not cast shadows (otherwise, models will always cast shadows)
static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask).withShadowCaster();
static const auto shadowCasterReceiverFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask);
const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterFilter, queryResolution).asVarying();
const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterReceiverFilter, queryResolution).asVarying();
const auto shadowSelection = task.addJob<FetchSpatialTree>("FetchShadowTree", fetchInput);
const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying();
const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterReceiverFilter).asVarying();
const auto shadowItems = task.addJob<FetchSpatialSelection>("FetchShadowSelection", selectionInputs);
// Cull objects that are not visible in camera view. Hopefully the cull functor only performs LOD culling, not
@ -261,21 +258,22 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende
char jobName[64];
sprintf(jobName, "ShadowCascadeSetup%d", i);
const auto cascadeSetupOutput = task.addJob<RenderShadowCascadeSetup>(jobName, i, _cullFunctor, tagBits, tagMask);
const auto shadowFilter = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(0);
const auto shadowRenderFilter = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(0);
const auto shadowBoundsFilter = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(1);
auto antiFrustum = render::Varying(ViewFrustumPointer());
cascadeFrustums[i] = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(1);
cascadeFrustums[i] = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(2);
if (i > 1) {
antiFrustum = cascadeFrustums[i - 2];
}
// CPU jobs: finer grained culling
const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowFilter, antiFrustum).asVarying();
const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowRenderFilter, shadowBoundsFilter, antiFrustum).asVarying();
const auto culledShadowItemsAndBounds = task.addJob<CullShapeBounds>("CullShadowCascade", cullInputs, shadowCullFunctor, RenderDetails::SHADOW);
// GPU jobs: Render to shadow map
sprintf(jobName, "RenderShadowMap%d", i);
task.addJob<RenderShadowMap>(jobName, culledShadowItemsAndBounds, shapePlumber, i);
task.addJob<RenderShadowCascadeTeardown>("ShadowCascadeTeardown", shadowFilter);
task.addJob<RenderShadowCascadeTeardown>("ShadowCascadeTeardown", shadowRenderFilter);
}
task.addJob<RenderShadowTeardown>("ShadowTeardown", setupOutput);
@ -406,7 +404,11 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon
const auto globalShadow = lightStage->getCurrentKeyShadow();
if (globalShadow && _cascadeIndex<globalShadow->getCascadeCount()) {
output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask).withShadowCaster();
auto baseFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask);
// Second item filter is to filter items to keep in shadow frustum computation (here we need to keep shadow receivers)
output.edit1() = baseFilter;
// First item filter is to filter items to render in shadow map (so only keep casters)
output.edit0() = baseFilter.withShadowCaster();
// Set the keylight render args
auto& cascade = globalShadow->getCascade(_cascadeIndex);
@ -419,10 +421,11 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon
texelSize *= minTexelCount;
_cullFunctor._minSquareSize = texelSize * texelSize;
output.edit1() = cascadeFrustum;
output.edit2() = cascadeFrustum;
} else {
output.edit0() = ItemFilter::Builder::nothing();
output.edit1() = ViewFrustumPointer();
output.edit1() = ItemFilter::Builder::nothing();
output.edit2() = ViewFrustumPointer();
}
}

View file

@ -118,7 +118,7 @@ private:
class RenderShadowCascadeSetup {
public:
using Outputs = render::VaryingSet2<render::ItemFilter, ViewFrustumPointer>;
using Outputs = render::VaryingSet3<render::ItemFilter, render::ItemFilter, ViewFrustumPointer>;
using JobModel = render::Job::ModelO<RenderShadowCascadeSetup, Outputs>;
RenderShadowCascadeSetup(unsigned int cascadeIndex, RenderShadowTask::CullFunctor& cullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00) :

View file

@ -368,17 +368,19 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input
RenderArgs* args = renderContext->args;
const auto& inShapes = inputs.get0();
const auto& filter = inputs.get1();
const auto& antiFrustum = inputs.get2();
const auto& cullFilter = inputs.get1();
const auto& boundsFilter = inputs.get2();
const auto& antiFrustum = inputs.get3();
auto& outShapes = outputs.edit0();
auto& outBounds = outputs.edit1();
outShapes.clear();
outBounds = AABox();
if (!filter.selectsNothing()) {
if (!cullFilter.selectsNothing() || !boundsFilter.selectsNothing()) {
auto& details = args->_details.edit(_detailType);
Test test(_cullFunctor, args, details, antiFrustum);
auto scene = args->_scene;
for (auto& inItems : inShapes) {
auto key = inItems.first;
@ -393,16 +395,26 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input
if (antiFrustum == nullptr) {
for (auto& item : inItems.second) {
if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound)) {
outItems->second.emplace_back(item);
outBounds += item.bound;
const auto shapeKey = scene->getItem(item.id).getKey();
if (cullFilter.test(shapeKey)) {
outItems->second.emplace_back(item);
}
if (boundsFilter.test(shapeKey)) {
outBounds += item.bound;
}
}
}
} else {
for (auto& item : inItems.second) {
if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound) && test.antiFrustumTest(item.bound)) {
outItems->second.emplace_back(item);
outBounds += item.bound;
}
const auto shapeKey = scene->getItem(item.id).getKey();
if (cullFilter.test(shapeKey)) {
outItems->second.emplace_back(item);
}
if (boundsFilter.test(shapeKey)) {
outBounds += item.bound;
}
}
}
}
details._rendered += (int)outItems->second.size();

View file

@ -110,7 +110,7 @@ namespace render {
class CullShapeBounds {
public:
using Inputs = render::VaryingSet3<ShapeBounds, ItemFilter, ViewFrustumPointer>;
using Inputs = render::VaryingSet4<ShapeBounds, ItemFilter, ItemFilter, ViewFrustumPointer>;
using Outputs = render::VaryingSet2<ShapeBounds, AABox>;
using JobModel = Job::ModelIO<CullShapeBounds, Inputs, Outputs>;

View file

@ -58,7 +58,8 @@ public:
ComboBox,
PrimaryHand,
// Special casing for an unusual preference
Avatar
Avatar,
RadioButtons
};
explicit Preference(QObject* parent = nullptr) : QObject(parent) {}
@ -353,6 +354,20 @@ public:
Type getType() override { return PrimaryHand; }
};
class RadioButtonsPreference : public IntPreference {
Q_OBJECT
Q_PROPERTY(QStringList items READ getItems CONSTANT)
public:
RadioButtonsPreference(const QString& category, const QString& name, Getter getter, Setter setter)
: IntPreference(category, name, getter, setter) { }
Type getType() override { return RadioButtons; }
const QStringList& getItems() { return _items; }
void setItems(const QStringList& items) { _items = items; }
protected:
QStringList _items;
};
#endif

View file

@ -58,7 +58,7 @@ namespace VirtualPad {
private:
Instance _leftVPadInstance;
bool _enabled;
bool _enabled {true};
bool _hidden;
glm::vec2 _jumpButtonPosition;
int _extraBottomMargin {0};

View file

@ -36,7 +36,6 @@
Q_DECLARE_LOGGING_CATEGORY(displayplugins)
const char* StandingHMDSensorMode { "Standing HMD Sensor Mode" }; // this probably shouldn't be hardcoded here
const char* OpenVrThreadedSubmit { "OpenVR Threaded Submit" }; // this probably shouldn't be hardcoded here
PoseData _nextRenderPoseData;
@ -451,7 +450,6 @@ bool OpenVrDisplayPlugin::internalActivate() {
qDebug() << "OpenVR Threaded submit enabled: " << _threadedSubmit;
_openVrDisplayActive = true;
_container->setIsOptionChecked(StandingHMDSensorMode, true);
_system->GetRecommendedRenderTargetSize(&_renderTargetSize.x, &_renderTargetSize.y);
// Recommended render target size is per-eye, so double the X size for
// left + right eyes
@ -507,7 +505,6 @@ void OpenVrDisplayPlugin::internalDeactivate() {
Parent::internalDeactivate();
_openVrDisplayActive = false;
_container->setIsOptionChecked(StandingHMDSensorMode, false);
if (_system) {
// TODO: Invalidate poses. It's fine if someone else sets these shared values, but we're about to stop updating them, and
// we don't want ViveControllerManager to consider old values to be valid.

View file

@ -0,0 +1,167 @@
"use strict";
//
// displayNames.js
// scripts/system/
//
// Created by Cristian Duarte & Gabriel Calero on May 3, 2018
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() { // BEGIN LOCAL_SCOPE
var MAX_DISTANCE_PX = 20; // Should we use dp instead?
var UNKNOWN_NAME = "Unknown";
var METERS_ABOVE_HEAD = 0.4;
var TEXT_LINE_HEIGHT = .1;
var TEXT_MARGIN = 0.025;
var HIDE_MS = 10000;
var currentTouchToAnalyze = null;
var currentlyShownAvatar = {
avatarID: null,
avatar: null,
overlay: null
};
var logEnabled = false;
var hideTimer = null;
function printd(str) {
if (logEnabled) {
print("[displayNames.js] " + str);
}
}
function clearOverlay() {
currentlyShownAvatar.avatar = null;
if (currentlyShownAvatar.overlay) {
Overlays.editOverlay(currentlyShownAvatar.overlay, {visible: false});
}
}
function touchedAvatar(avatarID, avatarData) {
printd("[AVATARNAME] touchEnd FOUND " + JSON.stringify(avatarData));
if (hideTimer) {
Script.clearTimeout(hideTimer);
}
// Case: touching an already selected avatar
if (currentlyShownAvatar.avatar && currentlyShownAvatar.avatarID == avatarID) {
clearOverlay();
return;
}
// Save currently selected avatar
currentlyShownAvatar.avatarID = avatarID;
currentlyShownAvatar.avatar = avatarData;
if (currentlyShownAvatar.overlay == null) {
var over = Overlays.addOverlay("text3d", {
lineHeight: TEXT_LINE_HEIGHT,
color: { red: 255, green: 255, blue: 255},
backgroundColor: {red: 0, green: 0, blue: 0},
leftMargin: TEXT_MARGIN,
topMargin: TEXT_MARGIN,
rightMargin: TEXT_MARGIN,
bottomMargin: TEXT_MARGIN,
alpha: 0.6,
solid: true,
isFacingAvatar: true,
visible: false
});
currentlyShownAvatar.overlay = over;
}
var nameToShow = avatarData.displayName ? avatarData.displayName :
(avatarData.sessionDisplayName ? avatarData.sessionDisplayName : UNKNOWN_NAME);
var textSize = Overlays.textSize(currentlyShownAvatar.overlay, nameToShow);
Overlays.editOverlay(currentlyShownAvatar.overlay, {
dimensions: {
x: textSize.width + 2 * TEXT_MARGIN,
y: TEXT_LINE_HEIGHT + 2 * TEXT_MARGIN
},
localPosition: { x: 0, y: METERS_ABOVE_HEAD, z: 0 },
text: nameToShow,
parentID: avatarData.sessionUUID,
parentJointIndex: avatarData.getJointIndex("Head"),
visible: true
});
hideTimer = Script.setTimeout(function() {
clearOverlay();
}, HIDE_MS);
}
function touchBegin(event) {
currentTouchToAnalyze = event;
}
function touchEnd(event) {
if (Vec3.distance({x: event.x, y: event.y }, {x: currentTouchToAnalyze.x, y: currentTouchToAnalyze.y}) > MAX_DISTANCE_PX) {
printd("[AVATARNAME] touchEnd moved too much");
currentTouchToAnalyze = null;
return;
}
var pickRay = Camera.computePickRay(event.x, event.y);
var avatarRay = AvatarManager.findRayIntersection(pickRay, [], [MyAvatar.sessionUUID])
if (avatarRay.intersects) {
touchedAvatar(avatarRay.avatarID, AvatarManager.getAvatar(avatarRay.avatarID));
} else {
printd("[AVATARNAME] touchEnd released outside the avatar");
}
currentTouchToAnalyze = null;
}
var runAtLeastOnce = false;
function ending() {
if (!runAtLeastOnce) {
return;
}
Controller.touchBeginEvent.disconnect(touchBegin);
Controller.touchEndEvent.disconnect(touchEnd);
Controller.mousePressEvent.disconnect(touchBegin);
Controller.mouseReleaseEvent.disconnect(touchEnd);
if (currentlyShownAvatar.overlay) {
Overlays.deleteOverlay(currentlyShownAvatar.overlay);
currentlyShownAvatar.overlay = null;
}
if (currentlyShownAvatar.avatar) {
currentlyShownAvatar.avatar = null;
}
}
function init() {
Controller.touchBeginEvent.connect(touchBegin);
Controller.touchEndEvent.connect(touchEnd);
Controller.mousePressEvent.connect(touchBegin);
Controller.mouseReleaseEvent.connect(touchEnd);
Script.scriptEnding.connect(function () {
ending();
});
runAtLeastOnce = true;
}
module.exports = {
init: init,
ending: ending
}
//init(); // Enable to use in desktop as a standalone
}()); // END LOCAL_SCOPE

View file

@ -28,6 +28,7 @@ modeLabel[MODE_MY_VIEW]="MY VIEW";
var logEnabled = false;
var radar = Script.require('./radar.js');
var uniqueColor = Script.require('./uniqueColor.js');
var displayNames = Script.require('./displayNames.js');
function printd(str) {
if (logEnabled) {
@ -87,8 +88,10 @@ function switchToMode(newMode) {
if (currentMode == MODE_RADAR) {
radar.startRadarMode();
displayNames.ending();
} else if (currentMode == MODE_MY_VIEW) {
// nothing to do yet
displayNames.init();
} else {
printd("Unknown view mode " + currentMode);
}

View file

@ -6,11 +6,11 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND,
getControllerJointIndex, enableDispatcherModule, disableDispatcherModule,
/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, Camera,
getControllerJointIndex, enableDispatcherModule, disableDispatcherModule, entityIsFarGrabbedByOther,
Messages, makeDispatcherModuleParameters, makeRunningValues, Settings, entityHasActions,
Vec3, Overlays, flatten, Xform, getControllerWorldLocation, ensureDynamic, entityIsCloneable,
cloneEntity, DISPATCHER_PROPERTIES, TEAR_AWAY_DISTANCE, Uuid, unhighlightTargetEntity
cloneEntity, DISPATCHER_PROPERTIES, Uuid, unhighlightTargetEntity, isInEditMode
*/
Script.include("/~/system/libraries/Xform.js");
@ -781,7 +781,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
}
}
};
var clearGrabActions = function(entityID) {
var actionIDs = Entities.getActionIDs(entityID);
var myGrabTag = "grab-" + MyAvatar.sessionUUID;
@ -794,7 +794,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
}
}
};
var onMousePress = function(event) {
if (isInEditMode() || !event.isLeftButton) { // don't consider any left clicks on the entity while in edit
return;
@ -808,7 +808,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
if (hasEquipData && entityProperties.parentID === EMPTY_PARENT_ID && !entityIsFarGrabbedByOther(entityID)) {
entityProperties.id = entityID;
var rightHandPosition = MyAvatar.getJointPosition("RightHand");
var leftHandPosition = MyAvatar.getJointPosition("LeftHand");
var leftHandPosition = MyAvatar.getJointPosition("LeftHand");
var distanceToRightHand = Vec3.distance(entityProperties.position, rightHandPosition);
var distanceToLeftHand = Vec3.distance(entityProperties.position, leftHandPosition);
var leftHandAvailable = leftEquipEntity.targetEntityID === null;
@ -828,7 +828,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
};
var onKeyPress = function(event) {
if (event.text === UNEQUIP_KEY) {
if (event.text.toLowerCase() === UNEQUIP_KEY) {
if (rightEquipEntity.targetEntityID) {
rightEquipEntity.endEquipEntity();
}

View file

@ -10,7 +10,7 @@
propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable,
Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation, makeDispatcherModuleParameters, makeRunningValues,
TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent, entityIsCloneable, propsAreCloneDynamic, cloneEntity,
HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, unhighlightTargetEntity
HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, unhighlightTargetEntity, Uuid
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");

View file

@ -7,12 +7,8 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND,
getControllerJointIndex, getGrabbableData, enableDispatcherModule, disableDispatcherModule,
propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable,
Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation, makeDispatcherModuleParameters, makeRunningValues,
TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent, entityIsCloneable, propsAreCloneDynamic, cloneEntity,
HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, AddressManager
/* global Script, MyAvatar, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule,
makeDispatcherModuleParameters, makeRunningValues, TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, BUMPER_ON_VALUE, AddressManager
*/
(function() {

View file

@ -11,8 +11,7 @@
TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS,
findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH,
HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, findHandChildEntities, TEAR_AWAY_DISTANCE, MSECS_PER_SEC, TEAR_AWAY_CHECK_TIME,
TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, print, Selection, DISPATCHER_HOVERING_LIST, Uuid,
highlightTargetEntity, unhighlightTargetEntity
TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, print, Uuid, highlightTargetEntity, unhighlightTargetEntity
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -43,11 +42,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
[],
100);
// XXX does handJointIndex change if the avatar changes?
this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
this.controllerJointIndex = getControllerJointIndex(this.hand);
this.thisHandIsParent = function(props) {
if (!props) {
return false;
@ -62,8 +56,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
return true;
}
var controllerJointIndex = this.controllerJointIndex;
if (props.parentJointIndex === controllerJointIndex) {
if (props.parentJointIndex === getControllerJointIndex(this.hand)) {
return true;
}
@ -102,7 +95,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
// } else {
// handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
// }
handJointIndex = this.controllerJointIndex;
handJointIndex = getControllerJointIndex(this.hand);
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
Entities.callEntityMethod(targetProps.id, "startNearGrab", args);

View file

@ -9,7 +9,7 @@
/* global Script, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, getControllerJointIndex,
enableDispatcherModule, disableDispatcherModule, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION,
makeDispatcherModuleParameters, Overlays, makeRunningValues, Vec3, resizeTablet, getTabletWidthFromSettings,
NEAR_GRAB_RADIUS
NEAR_GRAB_RADIUS, HMD, Uuid
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -37,7 +37,6 @@ Script.include("/~/system/libraries/utils.js");
// XXX does handJointIndex change if the avatar changes?
this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
this.controllerJointIndex = getControllerJointIndex(this.hand);
this.getOtherModule = function() {
return (this.hand === RIGHT_HAND) ? leftNearParentingGrabOverlay : rightNearParentingGrabOverlay;

View file

@ -10,7 +10,7 @@
/* jslint bitwise: true */
/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, getControllerJointIndex,
/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND,
enableDispatcherModule, disableDispatcherModule, Messages, makeDispatcherModuleParameters, makeRunningValues, Vec3,
HMD, Uuid, AvatarList, Picks, Pointers, PickType
*/

View file

@ -14,7 +14,6 @@
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
<script type="text/javascript" src="js/spinButtons.js"></script>
<script type="text/javascript" src="js/keyboardControl.js"></script>
<script type="text/javascript" src="js/entityList.js"></script>
</head>
<body onload='loaded();'>

View file

@ -20,7 +20,6 @@
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
<script type="text/javascript" src="js/spinButtons.js"></script>
<script type="text/javascript" src="js/keyboardControl.js"></script>
<script type="text/javascript" src="js/entityProperties.js"></script>
<script src="js/jsoneditor.min.js"></script>
</head>

View file

@ -16,7 +16,6 @@
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
<script type="text/javascript" src="js/spinButtons.js"></script>
<script type="text/javascript" src="js/keyboardControl.js"></script>
<script type="text/javascript" src="js/gridControls.js"></script>
</head>
<body onload='loaded();'>

View file

@ -444,8 +444,6 @@ function loaded() {
augmentSpinButtons();
setUpKeyboardControl();
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
document.addEventListener("contextmenu", function (event) {
event.preventDefault();

View file

@ -7,7 +7,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global alert, augmentSpinButtons, clearTimeout, console, document, Element, EventBridge,
HifiEntityUI, JSONEditor, openEventBridge, setUpKeyboardControl, setTimeout, window, _ $ */
HifiEntityUI, JSONEditor, openEventBridge, setTimeout, window, _ $ */
var PI = 3.14159265358979;
var DEGREES_TO_RADIANS = PI / 180.0;
@ -2157,8 +2157,6 @@ function loaded() {
augmentSpinButtons();
setUpKeyboardControl();
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
document.addEventListener("contextmenu", function(event) {
event.preventDefault();

View file

@ -129,8 +129,6 @@ function loaded() {
augmentSpinButtons();
setUpKeyboardControl();
EventBridge.emitWebEvent(JSON.stringify({ type: 'init' }));
});
document.addEventListener("keydown", function (keyDown) {

View file

@ -1,73 +0,0 @@
//
// keyboardControl.js
//
// Created by David Rowe on 28 Sep 2016.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
function setUpKeyboardControl() {
var lowerTimer = null;
var isRaised = false;
var KEYBOARD_HEIGHT = 200;
function raiseKeyboard() {
window.isKeyboardRaised = true;
window.isNumericKeyboard = this.type === "number";
if (lowerTimer !== null) {
clearTimeout(lowerTimer);
lowerTimer = null;
}
EventBridge.emitWebEvent("_RAISE_KEYBOARD" + (this.type === "number" ? "_NUMERIC" : ""));
if (!isRaised) {
var delta = this.getBoundingClientRect().bottom + 10 - (document.body.clientHeight - KEYBOARD_HEIGHT);
if (delta > 0) {
setTimeout(function () {
document.body.scrollTop += delta;
}, 500); // Allow time for keyboard to be raised in QML.
}
}
isRaised = true;
}
function doLowerKeyboard() {
window.isKeyboardRaised = false;
window.isNumericKeyboard = false;
EventBridge.emitWebEvent("_LOWER_KEYBOARD");
lowerTimer = null;
isRaised = false;
}
function lowerKeyboard() {
// Delay lowering keyboard a little in case immediately raise it again.
if (lowerTimer === null) {
lowerTimer = setTimeout(doLowerKeyboard, 20);
}
}
function documentBlur() {
// Action any pending Lower keyboard event immediately upon leaving document window so that they don't interfere with
// other Entities Editor tab.
if (lowerTimer !== null) {
clearTimeout(lowerTimer);
doLowerKeyboard();
}
}
var inputs = document.querySelectorAll("input[type=text], input[type=password], input[type=number], textarea");
for (var i = 0, length = inputs.length; i < length; i++) {
inputs[i].addEventListener("focus", raiseKeyboard);
inputs[i].addEventListener("blur", lowerKeyboard);
}
window.addEventListener("blur", documentBlur);
}

View file

@ -7,7 +7,7 @@
/* global module, Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform,
Selection,
Selection, Uuid,
MSECS_PER_SEC:true , LEFT_HAND:true, RIGHT_HAND:true, FORBIDDEN_GRAB_TYPES:true,
HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, ZERO_VEC:true, ONE_VEC:true,
DEFAULT_REGISTRATION_POINT:true, INCHES_TO_METERS:true,
@ -34,11 +34,12 @@
getGrabbableData:true,
entityIsGrabbable:true,
entityIsDistanceGrabbable:true,
getControllerJointIndexCacheTime:true,
getControllerJointIndexCache:true,
getControllerJointIndex:true,
propsArePhysical:true,
controllerDispatcherPluginsNeedSort:true,
projectOntoXYPlane:true,
getChildrenProps:true,
projectOntoEntityXYPlane:true,
projectOntoOverlayXYPlane:true,
makeLaserLockInfo:true,
@ -53,6 +54,8 @@
TEAR_AWAY_COUNT:true,
TEAR_AWAY_CHECK_TIME:true,
distanceBetweenPointAndEntityBoundingBox:true,
entityIsEquipped:true,
entityIsFarGrabbedByOther:true,
highlightTargetEntity:true,
clearHighlightedEntities:true,
unhighlightTargetEntity:true
@ -265,20 +268,32 @@ entityIsDistanceGrabbable = function(props) {
return true;
};
getControllerJointIndex = function (hand) {
if (HMD.isHandControllerAvailable()) {
var controllerJointIndex = -1;
if (Camera.mode === "first person") {
controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ?
"_CONTROLLER_RIGHTHAND" :
"_CONTROLLER_LEFTHAND");
} else if (Camera.mode === "third person") {
controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ?
"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" :
"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND");
}
getControllerJointIndexCacheTime = [0, 0];
getControllerJointIndexCache = [-1, -1];
return controllerJointIndex;
getControllerJointIndex = function (hand) {
var GET_CONTROLLERJOINTINDEX_CACHE_REFRESH_TIME = 3000; // msecs
var now = Date.now();
if (now - getControllerJointIndexCacheTime[hand] > GET_CONTROLLERJOINTINDEX_CACHE_REFRESH_TIME) {
if (HMD.isHandControllerAvailable()) {
var controllerJointIndex = -1;
if (Camera.mode === "first person") {
controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ?
"_CONTROLLER_RIGHTHAND" :
"_CONTROLLER_LEFTHAND");
} else if (Camera.mode === "third person") {
controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ?
"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" :
"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND");
}
getControllerJointIndexCacheTime[hand] = now;
getControllerJointIndexCache[hand] = controllerJointIndex;
return controllerJointIndex;
}
} else {
return getControllerJointIndexCache[hand];
}
return -1;

View file

@ -19,6 +19,7 @@ var selectionDisplay = null; // for gridTool.js to ignore
Script.include("/~/system/libraries/WebTablet.js");
Script.include("/~/system/libraries/gridTool.js");
Script.include("/~/system/libraries/connectionUtils.js");
var METAVERSE_SERVER_URL = Account.metaverseServerURL;
var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace";

View file

@ -251,6 +251,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
});
}
break;
case 'refresh': // old name for refreshNearby
case 'refreshNearby':
data = {};
ExtendedOverlay.some(function (overlay) { // capture the audio data
@ -743,10 +744,13 @@ function receiveMessage(channel, messageString, senderID) {
var message = JSON.parse(messageString);
switch (message.method) {
case 'select':
sendToQml(message); // Accepts objects, not just strings.
if (!onPalScreen) {
tablet.loadQMLSource(PAL_QML_SOURCE);
Script.setTimeout(function () { sendToQml(message); }, 1000);
} else {
sendToQml(message); // Accepts objects, not just strings.
}
break;
default:
print('Unrecognized PAL message', messageString);
}
}

View file

@ -15,7 +15,7 @@
// Work around for a bug in the MSVC compiler that chokes when you use GLI and Qt headers together.
#define gli glm
#ifdef Q_OS_MAC
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-variable"
#pragma clang diagnostic ignored "-Wignored-qualifiers"
@ -33,7 +33,7 @@
#include <gli/gli.hpp>
#ifdef Q_OS_MAC
#if defined(__clang__)
#pragma clang diagnostic pop
#endif

View file

@ -175,40 +175,39 @@ void Test::appendTestResultsToFile(const QString& testResultsFolderPath, TestFai
}
void Test::startTestsEvaluation(const QString& testFolder) {
QString pathToTestResultsDirectory;
if (testFolder.isNull()) {
// Get list of JPEG images in folder, sorted by name
pathToTestResultsDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly);
} else {
pathToTestResultsDirectory = testFolder;
}
// Get list of JPEG images in folder, sorted by name
QString previousSelection = snapshotDirectory;
if (pathToTestResultsDirectory == QString()) {
snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images",
previousSelection, QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (snapshotDirectory == "") {
snapshotDirectory = previousSelection;
return;
}
// Quit if test results folder could not be created
if (!createTestResultsFolderPath(pathToTestResultsDirectory)) {
if (!createTestResultsFolderPath(snapshotDirectory)) {
return;
}
// Before any processing - all images are converted to PNGs, as this is the format stored on GitHub
QStringList sortedSnapshotFilenames = createListOfAll_imagesInDirectory("jpg", pathToTestResultsDirectory);
QStringList sortedSnapshotFilenames = createListOfAll_imagesInDirectory("jpg", snapshotDirectory);
foreach(QString filename, sortedSnapshotFilenames) {
QStringList stringParts = filename.split(".");
copyJPGtoPNG(
pathToTestResultsDirectory + "/" + stringParts[0] + ".jpg",
pathToTestResultsDirectory + "/" + stringParts[0] + ".png"
copyJPGtoPNG(snapshotDirectory + "/" + stringParts[0] + ".jpg",
snapshotDirectory + "/" + stringParts[0] + ".png"
);
QFile::remove(pathToTestResultsDirectory + "/" + stringParts[0] + ".jpg");
QFile::remove(snapshotDirectory + "/" + stringParts[0] + ".jpg");
}
// Create two lists. The first is the test results, the second is the expected images
// The expected images are represented as a URL to enable download from GitHub
// Images that are in the wrong format are ignored.
QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", pathToTestResultsDirectory);
QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", snapshotDirectory);
QStringList expectedImagesURLs;
resultImagesFullFilenames.clear();
@ -216,7 +215,7 @@ void Test::startTestsEvaluation(const QString& testFolder) {
expectedImagesFullFilenames.clear();
foreach(QString currentFilename, sortedTestResultsFilenames) {
QString fullCurrentFilename = pathToTestResultsDirectory + "/" + currentFilename;
QString fullCurrentFilename = snapshotDirectory + "/" + currentFilename;
if (isInSnapshotFilenameFormat("png", currentFilename)) {
resultImagesFullFilenames << fullCurrentFilename;
@ -236,11 +235,11 @@ void Test::startTestsEvaluation(const QString& testFolder) {
QString expectedImageFilename = currentFilename.replace("/", "_").replace(".", "_EI.");
expectedImagesFilenames << expectedImageFilename;
expectedImagesFullFilenames << pathToTestResultsDirectory + "/" + expectedImageFilename;
expectedImagesFullFilenames << snapshotDirectory + "/" + expectedImageFilename;
}
}
autoTester->downloadImages(expectedImagesURLs, pathToTestResultsDirectory, expectedImagesFilenames);
autoTester->downloadImages(expectedImagesURLs, snapshotDirectory, expectedImagesFilenames);
}
void Test::finishTestsEvaluation(bool isRunningFromCommandline, bool interactiveMode, QProgressBar* progressBar) {
@ -303,25 +302,39 @@ void Test::importTest(QTextStream& textStream, const QString& testPathname) {
// This script will run all text.js scripts in every applicable sub-folder
void Test::createRecursiveScript() {
// Select folder to start recursing from
QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly);
if (topLevelDirectory == "") {
QString previousSelection = testDirectory;
testDirectory =
QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script",
previousSelection, QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testDirectory == "") {
testDirectory = previousSelection;
return;
}
createRecursiveScript(topLevelDirectory, true);
createRecursiveScript(testDirectory, true);
}
// This method creates a `testRecursive.js` script in every sub-folder.
void Test::createAllRecursiveScripts() {
// Select folder to start recursing from
QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the recursive scripts", ".", QFileDialog::ShowDirsOnly);
if (topLevelDirectory == "") {
QString previousSelection = testDirectory;
testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the recursive scripts",
previousSelection,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testDirectory == "") {
testDirectory = previousSelection;
return;
}
createRecursiveScript(topLevelDirectory, false);
createRecursiveScript(testDirectory, false);
QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
QDirIterator it(testDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();
@ -427,29 +440,42 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact
void Test::createTest() {
// Rename files sequentially, as ExpectedResult_00000.jpeg, ExpectedResult_00001.jpg and so on
// Any existing expected result images will be deleted
QString imageSourceDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly);
if (imageSourceDirectory == "") {
QString previousSelection = snapshotDirectory;
snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images",
previousSelection,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (snapshotDirectory == "") {
snapshotDirectory = previousSelection;
return;
}
QString imageDestinationDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder to save the test images", ".", QFileDialog::ShowDirsOnly);
if (imageDestinationDirectory == "") {
previousSelection = testDirectory;
QString testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder to save the test images",
previousSelection, QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testDirectory == "") {
testDirectory = previousSelection;
return;
}
QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("jpg", imageSourceDirectory);
QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("jpg", snapshotDirectory);
int i = 1;
const int maxImages = pow(10, NUM_DIGITS);
foreach (QString currentFilename, sortedImageFilenames) {
QString fullCurrentFilename = imageSourceDirectory + "/" + currentFilename;
QString fullCurrentFilename = snapshotDirectory + "/" + currentFilename;
if (isInSnapshotFilenameFormat("jpg", currentFilename)) {
if (i >= maxImages) {
QMessageBox::critical(0, "Error", "More than " + QString::number(maxImages) + " images not supported");
exit(-1);
}
QString newFilename = "ExpectedImage_" + QString::number(i - 1).rightJustified(5, '0') + ".png";
QString fullNewFileName = imageDestinationDirectory + "/" + newFilename;
QString fullNewFileName = testDirectory + "/" + newFilename;
try {
copyJPGtoPNG(fullCurrentFilename, fullNewFileName);
@ -489,31 +515,6 @@ ExtractedText Test::getTestScriptLines(QString testFileName) {
QString regexTestTitle(ws + functionPerformName + "\\(" + quotedString + "\\," + ws + ownPath + "\\," + ws + functionParameter + ws + "{" + ".*");
QRegularExpression lineContainingTitle = QRegularExpression(regexTestTitle);
// Assert platform checks that test is running on the correct OS
const QString functionAssertPlatform(ws + "autoTester" + ws + "\\." + ws + "assertPlatform");
const QString regexAssertPlatform(ws + functionAssertPlatform + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertPlatform = QRegularExpression(regexAssertPlatform);
// Assert display checks that test is running on the correct display
const QString functionAssertDisplay(ws + "autoTester" + ws + "\\." + ws + "assertDisplay");
const QString regexAssertDisplay(ws + functionAssertDisplay + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertDisplay = QRegularExpression(regexAssertDisplay);
// Assert CPU checks that test is running on the correct type of CPU
const QString functionAssertCPU(ws + "autoTester" + ws + "\\." + ws + "assertCPU");
const QString regexAssertCPU(ws + functionAssertCPU + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertCPU = QRegularExpression(regexAssertCPU);
// Assert GPU checks that test is running on the correct type of GPU
const QString functionAssertGPU(ws + "autoTester" + ws + "\\." + ws + "assertGPU");
const QString regexAssertGPU(ws + functionAssertGPU + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertGPU = QRegularExpression(regexAssertGPU);
// Assert the correct amount of memory
const QString functionAssertPhysicalMemoryGB(ws + "autoTester" + ws + "\\." + ws + "assertPhysicalMemoryGB");
const QString regexAssertPhysicalMemoryGB(ws + functionAssertPhysicalMemoryGB + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertPhysicalMemoryGB = QRegularExpression(regexAssertPhysicalMemoryGB);
// Each step is either of the following forms:
// autoTester.addStepSnapshot("Take snapshot"...
@ -523,7 +524,7 @@ ExtractedText Test::getTestScriptLines(QString testFileName) {
const QRegularExpression lineStepSnapshot = QRegularExpression(regexStepSnapshot);
const QString functionAddStepName(ws + "autoTester" + ws + "\\." + ws + "addStep");
const QString regexStep(ws + functionAddStepName + ws + "\\(" + ws + quotedString + ws + "\\)" + ".*");
const QString regexStep(ws + functionAddStepName + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineStep = QRegularExpression(regexStep);
while (!line.isNull()) {
@ -531,7 +532,6 @@ ExtractedText Test::getTestScriptLines(QString testFileName) {
if (lineContainingTitle.match(line).hasMatch()) {
QStringList tokens = line.split('"');
relevantTextFromTest.title = tokens[1];
} else if (lineStepSnapshot.match(line).hasMatch()) {
QStringList tokens = line.split('"');
QString nameOfStep = tokens[1];
@ -561,29 +561,43 @@ ExtractedText Test::getTestScriptLines(QString testFileName) {
// The folder selected must contain a script named "test.js", the file produced is named "test.md"
void Test::createMDFile() {
// Folder selection
QString testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", ".", QFileDialog::ShowDirsOnly);
QString previousSelection = testDirectory;
testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", previousSelection,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testDirectory == "") {
testDirectory = previousSelection;
return;
}
createMDFile(testDirectory);
QMessageBox::information(0, "Success", "MD file has been created");
}
void Test::createAllMDFiles() {
// Select folder to start recursing from
QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the MD files", ".", QFileDialog::ShowDirsOnly);
if (topLevelDirectory == "") {
QString previousSelection = testDirectory;
testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the MD files",
previousSelection, QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testDirectory == "") {
testDirectory = previousSelection;
return;
}
// First test if top-level folder has a test.js file
const QString testPathname{ topLevelDirectory + "/" + TEST_FILENAME };
const QString testPathname { testDirectory + "/" + TEST_FILENAME };
QFileInfo fileInfo(testPathname);
if (fileInfo.exists()) {
createMDFile(topLevelDirectory);
createMDFile(testDirectory);
}
QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
QDirIterator it(testDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();
@ -638,31 +652,33 @@ void Test::createMDFile(const QString& testDirectory) {
stream << "## Steps\n";
stream << "Press space bar to advance step by step\n\n";
// Note that snapshots of step n are taken in step n+1
// (this implies that if the LAST step requests a snapshot then this will not work - caveat emptor)
int snapShotIndex { 0 };
for (size_t i = 0; i < testScriptLines.stepList.size(); ++i) {
stream << "### Step " << QString::number(i + 1) << "\n";
stream << "- " << testScriptLines.stepList[i]->text << "\n";
if ((i + 1 < testScriptLines.stepList.size()) && testScriptLines.stepList[i + 1]->takeSnapshot) {
if ((i + 1 < testScriptLines.stepList.size()) && testScriptLines.stepList[i]->takeSnapshot) {
stream << "- ![](./ExpectedImage_" << QString::number(snapShotIndex).rightJustified(5, '0') << ".png)\n";
++snapShotIndex;
}
}
mdFile.close();
QMessageBox::information(0, "Success", "Test MD file " + mdFilename + " has been created");
}
void Test::createTestsOutline() {
QString testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", ".", QFileDialog::ShowDirsOnly);
if (testsRootDirectory == "") {
QString previousSelection = testDirectory;
testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", previousSelection,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testDirectory == "") {
testDirectory = previousSelection;
return;
}
const QString testsOutlineFilename { "testsOutline.md" };
QString mdFilename(testsRootDirectory + "/" + testsOutlineFilename);
QString mdFilename(testDirectory + "/" + testsOutlineFilename);
QFile mdFile(mdFilename);
if (!mdFile.open(QIODevice::WriteOnly)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename);
@ -676,10 +692,10 @@ void Test::createTestsOutline() {
stream << "Directories with an appended (*) have an automatic test\n\n";
// We need to know our current depth, as this isn't given by QDirIterator
int rootDepth { testsRootDirectory.count('/') };
int rootDepth { testDirectory.count('/') };
// Each test is shown as the folder name linking to the matching GitHub URL, and the path to the associated test.md file
QDirIterator it(testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
QDirIterator it(testDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();

View file

@ -89,6 +89,12 @@ private:
const int NUM_DIGITS { 5 };
const QString EXPECTED_IMAGE_PREFIX { "ExpectedImage_" };
// We have two directories to work with.
// The first is the directory containing the test we are working with
// The second contains the snapshots taken for test runs that need to be evaluated
QString testDirectory;
QString snapshotDirectory;
QStringList expectedImagesFilenames;
QStringList expectedImagesFullFilenames;
QStringList resultImagesFullFilenames;